Skip to content

Commit a075323

Browse files
(increase version minor) Merge pull request #262 from Magritte-code/min_line_opacity
Implemented other workaround for masering values
2 parents a097eb1 + 280cd9b commit a075323

File tree

8 files changed

+73
-54
lines changed

8 files changed

+73
-54
lines changed

dependencies/conda_env.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies:
1313
- vtk
1414
- jupyterlab
1515
- yt
16-
- scipy
16+
- scipy <= 1.13
1717
- mpi4py
1818
- plotly
1919
- pyyaml

docs/src/4_extra_options/NLTE_in_low_density.rst

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,27 @@ In low density regimes, the level populations might start to maser.
55
This means that the resulting line opacity (and optical depth) can become close to zero or even negative.
66
Unfortunately, our solvers cannot handle this situation too well, and might produce unphysical results.
77

8-
In more recent versions of the code (starting from version 0.5.3), a workaround has been implemented to avoid this situation by resetting specific levels at specific places to LTE.
8+
This is why we implement a workaround by setting the minimum allowed value for the line opacity (starting from version 0.7.0).
9+
This value is set to 1e-13 [W sr−1 m−3] by default, but can be changed by the user. Higher values might overestimate the impact of the less dense model regions on the intensity, while lower value can lead to numerical artifacts in the NLTE intensities.
10+
11+
.. code-block:: python
12+
13+
parameters.min_line_opacity = 1e-13#default
14+
15+
In versions 0.5.3 until 0.6.0, the code implemented another workaround to avoid this situation by resetting specific levels at specific places to LTE.
916
This might work in some cases (with the caveat of some artifacts being produced in the resulting spectrum),
1017
but might as well produce new artifacts in when doing NLTE line radiative transfer in extremely low density regions.
1118

12-
To restore the previous behavior (without the new workaround), one can call
19+
To restore that behavior (without the new workaround), one can call
20+
21+
.. code-block:: python
22+
23+
parameters.population_inversion_fraction = 1.01#previous default
24+
parameters.min_line_opacity = -1
25+
26+
Finally, to restore the behavior of the code before version 0.5.3 (not recommended when using NLTE), one can call
1327

1428
.. code-block:: python
1529
16-
parameters.population_inversion_fraction = -1
30+
parameters.population_inversion_fraction = -1
31+
parameters.min_line_opacity = -1

src/bindings/pybindings.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ PYBIND11_MODULE(core, module) {
288288
.def_readwrite("pop_prec", &Parameters::pop_prec, "Required precision for ALI.")
289289
.def_readwrite("min_opacity", &Parameters::min_opacity,
290290
"Minimum opacity that will be assumed in the solver.")
291+
.def_readwrite("min_line_opacity", &Parameters::min_line_opacity,
292+
"Minimum line opacity that will be assumed in the solver.")
291293
.def_readwrite("min_dtau", &Parameters::min_dtau,
292294
"Minimum optical depth increment that will be assumed in the solver.")
293295
.def_readwrite("population_inversion_fraction", &Parameters::population_inversion_fraction,

src/model/lines/lineProducingSpecies/lineProducingSpecies.tpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ inline Size LineProducingSpecies ::index(const Size p, const Size i) const {
2121
/// @return line emissivity for cell p and transition k
2222
//////////////////////////////////////////////////////////
2323
inline Real LineProducingSpecies ::get_emissivity(const Size p, const Size k) const {
24-
const Size i = index(p, linedata.irad[k]);
24+
const Size i = index(p, linedata.irad[k]);
25+
const Real freq = linedata.frequency[k];
2526

26-
return HH_OVER_FOUR_PI * linedata.A[k] * population(i);
27+
return freq * HH_OVER_FOUR_PI * linedata.A[k] * population(i);
2728
}
2829

2930
/// Getter for the line opacity
@@ -32,10 +33,13 @@ inline Real LineProducingSpecies ::get_emissivity(const Size p, const Size k) co
3233
/// @return line opacity for cell p and transition k
3334
///////////////////////////////////////////////////////
3435
inline Real LineProducingSpecies ::get_opacity(const Size p, const Size k) const {
35-
const Size i = index(p, linedata.irad[k]);
36-
const Size j = index(p, linedata.jrad[k]);
36+
const Size i = index(p, linedata.irad[k]);
37+
const Size j = index(p, linedata.jrad[k]);
38+
const Real freq = linedata.frequency[k];
3739

38-
return HH_OVER_FOUR_PI * (population(j) * linedata.Ba[k] - population(i) * linedata.Bs[k]);
40+
return std::max(
41+
freq * HH_OVER_FOUR_PI * (population(j) * linedata.Ba[k] - population(i) * linedata.Bs[k]),
42+
parameters->min_line_opacity);
3943
}
4044

4145
/// set_LTE_level_populations
@@ -435,8 +439,9 @@ inline void LineProducingSpecies::update_using_statistical_equilibrium(
435439

436440
for (Size k = 0; k < linedata.nrad; k++) {
437441
for (Size m = 0; m < lambda.get_size(p, k); m++) {
442+
const Real freq = linedata.frequency[k]; // line frequency
438443
const Size nr = lambda.get_nr(p, k, m);
439-
const long double v_IJ = -lambda.get_Ls(p, k, m) * get_opacity(p, k);
444+
const long double v_IJ = -lambda.get_Ls(p, k, m) * get_opacity(p, k) / freq;
440445

441446
// Note: we define our transition matrix as the transpose of R in the
442447
// paper.
@@ -628,8 +633,9 @@ inline void LineProducingSpecies::update_using_statistical_equilibrium_sparse(
628633
// Approximated Lambda operator
629634
for (Size k = 0; k < linedata.nrad; k++) {
630635
for (Size m = 0; m < lambda.get_size(p, k); m++) {
636+
const Real freq = linedata.frequency[k]; // line frequency
631637
const Size nr = lambda.get_nr(p, k, m);
632-
const long double v_IJ = -lambda.get_Ls(p, k, m) * get_opacity(p, k);
638+
const long double v_IJ = -lambda.get_Ls(p, k, m) * get_opacity(p, k) / freq;
633639

634640
if (nr != p) {
635641
throw std::runtime_error("ERROR: non-local Approximated Lambda operator.");

src/model/parameters/parameters.hpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,15 @@ struct Parameters {
7373
Real max_width_fraction = 0.35; // max diff is +-2.5%
7474
// Real max_width_fraction = 0.3;//max diff is
7575
// +-2%
76-
Real convergence_fraction = 0.995;
77-
Real min_rel_pop_for_convergence = 1.0e-10;
78-
Real pop_prec = 1.0e-6;
79-
Real min_opacity = 1.0e-26;
80-
Real min_dtau = 1.0e-15;
81-
Real population_inversion_fraction = 1.01; // threshold factor for population inversion required
82-
// for LTE to be used; set this higher than 1
76+
Real convergence_fraction = 0.995;
77+
Real min_rel_pop_for_convergence = 1.0e-10;
78+
Real pop_prec = 1.0e-6;
79+
Real min_opacity = 1.0e-26;
80+
long double min_line_opacity = 1.0e-13;
81+
Real min_dtau = 1.0e-15;
82+
Real population_inversion_fraction =
83+
-1.0; // previously 1.01; // threshold factor for population inversion required
84+
// for LTE to be used; set this higher than 1
8385
bool store_intensities = false;
8486
// bool use_Ng_acceleration = true;//Not used,
8587
// so may safely be removed

src/solver/solver.tpp

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,7 @@ accel inline void Solver ::get_eta_and_chi<None>(const Model& model, const Size
992992
// Set line emissivity and opacity
993993
for (Size l = 0; l < model.parameters->nlines(); l++) {
994994
const Real diff = freq - model.lines.line[l];
995-
const Real prof = freq * gaussian(model.lines.inverse_width(p, l), diff);
995+
const Real prof = gaussian(model.lines.inverse_width(p, l), diff);
996996

997997
eta += prof * model.lines.emissivity(p, l);
998998
chi += prof * model.lines.opacity(p, l);
@@ -1038,7 +1038,7 @@ accel inline void Solver ::get_eta_and_chi<CloseLines>(const Model& model, const
10381038
const Real diff = freq - *freq_sort_l; // should be equal to the
10391039
// previous line of code
10401040
const Real inv_width = model.lines.inverse_width(p, l);
1041-
const Real prof = freq * gaussian(model.lines.inverse_width(p, l), diff);
1041+
const Real prof = gaussian(model.lines.inverse_width(p, l), diff);
10421042
eta += prof * model.lines.emissivity(p, l);
10431043
chi += prof * model.lines.opacity(p, l);
10441044
}
@@ -1058,7 +1058,7 @@ template <>
10581058
accel inline void Solver ::get_eta_and_chi<OneLine>(
10591059
const Model& model, const Size p, const Size l, const Real freq, Real& eta, Real& chi) const {
10601060
const Real diff = freq - model.lines.line[l];
1061-
const Real prof = freq * gaussian(model.lines.inverse_width(p, l), diff);
1061+
const Real prof = gaussian(model.lines.inverse_width(p, l), diff);
10621062

10631063
eta = prof * model.lines.emissivity(p, l);
10641064
chi = prof * model.lines.opacity(p, l) + model.parameters->min_opacity;
@@ -1558,11 +1558,9 @@ inline void Solver ::compute_S_dtau_line_integrated<OneLine>(Model& model, Size
15581558
Real& Snext) {
15591559
dtau = compute_dtau_single_line(model, currpoint, nextpoint, lineidx, currfreq, nextfreq, dZ);
15601560
Scurr = model.lines.emissivity(currpoint, lineidx)
1561-
/ (model.lines.opacity(currpoint, lineidx)
1562-
+ model.parameters->min_opacity); // current source
1563-
Snext =
1564-
model.lines.emissivity(nextpoint, lineidx)
1565-
/ (model.lines.opacity(nextpoint, lineidx) + model.parameters->min_opacity); // next source
1561+
/ model.lines.opacity(currpoint, lineidx); // current source
1562+
Snext = model.lines.emissivity(nextpoint, lineidx)
1563+
/ model.lines.opacity(nextpoint, lineidx); // next source
15661564
// note: due to interaction with dtau when computing all
15671565
// sources individually, we do need to recompute Scurr and
15681566
// Snext for all position increments
@@ -1600,12 +1598,10 @@ inline void Solver ::compute_S_dtau_line_integrated<None>(Model& model, Size cur
16001598
for (Size l = 0; l < model.parameters->nlines(); l++) {
16011599
Real line_dtau =
16021600
compute_dtau_single_line(model, currpoint, nextpoint, l, currfreq, nextfreq, dZ);
1603-
Real line_Scurr =
1604-
model.lines.emissivity(currpoint, l)
1605-
/ (model.lines.opacity(currpoint, l) + model.parameters->min_opacity); // current source
1601+
Real line_Scurr = model.lines.emissivity(currpoint, l)
1602+
/ model.lines.opacity(currpoint, l); // current source
16061603
Real line_Snext =
1607-
model.lines.emissivity(nextpoint, l)
1608-
/ (model.lines.opacity(nextpoint, l) + model.parameters->min_opacity); // next source
1604+
model.lines.emissivity(nextpoint, l) / model.lines.opacity(nextpoint, l); // next source
16091605
sum_dtau += line_dtau;
16101606
sum_dtau_times_Scurr += line_dtau * line_Scurr;
16111607
sum_dtau_times_Snext += line_dtau * line_Snext;
@@ -1688,12 +1684,10 @@ inline void Solver ::compute_S_dtau_line_integrated<CloseLines>(Model& model, Si
16881684

16891685
Real line_dtau =
16901686
compute_dtau_single_line(model, currpoint, nextpoint, l, currfreq, nextfreq, dZ);
1691-
Real line_Scurr =
1692-
model.lines.emissivity(currpoint, l)
1693-
/ (model.lines.opacity(currpoint, l) + model.parameters->min_opacity); // current source
1687+
Real line_Scurr = model.lines.emissivity(currpoint, l)
1688+
/ model.lines.opacity(currpoint, l); // current source
16941689
Real line_Snext =
1695-
model.lines.emissivity(nextpoint, l)
1696-
/ (model.lines.opacity(nextpoint, l) + model.parameters->min_opacity); // next source
1690+
model.lines.emissivity(nextpoint, l) / model.lines.opacity(nextpoint, l); // next source
16971691
sum_dtau += line_dtau;
16981692
sum_dtau_times_Scurr += line_dtau * line_Scurr;
16991693
sum_dtau_times_Snext += line_dtau * line_Snext;
@@ -1972,8 +1966,8 @@ inline Real Solver ::compute_dtau_single_line(Model& model, Size curridx, Size n
19721966

19731967
// opacity is stored divided by the linefreq, so multiply
19741968
// by it
1975-
const Real curr_line_opacity = linefreq * model.lines.opacity(curridx, lineidx);
1976-
const Real next_line_opacity = linefreq * model.lines.opacity(nextidx, lineidx);
1969+
const Real curr_line_opacity = model.lines.opacity(curridx, lineidx);
1970+
const Real next_line_opacity = model.lines.opacity(nextidx, lineidx);
19771971

19781972
// if frequencies are equal, division by zero (due to the
19791973
// optical depth formula) happens if we were not to use

tests/benchmarks/analytic/all_zero_single_ray.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ def velocity(i):
6666
model.thermodynamics.temperature.gas .set( temp * np.ones(npoints))
6767
model.thermodynamics.turbulence.vturb2.set((turb/magritte.CC)**2 * np.ones(npoints))
6868

69-
model = setup.set_Delaunay_neighbor_lists (model)
70-
model = setup.set_Delaunay_boundary (model)
71-
model = setup.set_boundary_condition_CMB (model)
72-
model = setup.set_uniform_rays (model)
73-
model = setup.set_linedata_from_LAMDA_file(model, lamdaFile)
74-
model = setup.set_quadrature (model)
69+
setup.set_Delaunay_neighbor_lists (model)
70+
setup.set_Delaunay_boundary (model)
71+
setup.set_boundary_condition_CMB (model)
72+
setup.set_uniform_rays (model)
73+
setup.set_linedata_from_LAMDA_file(model, lamdaFile)
74+
setup.set_quadrature (model)
7575

7676
model.write()
7777

@@ -176,8 +176,8 @@ def u_ (x):
176176
plt.show()
177177

178178
#error bounds are chosen somewhat arbitrarily, based on previously obtained results; this should prevent serious regressions.
179-
FIRSTORDER_AS_EXPECTED=(np.max(error_u_0s)<2.0e-6)
180-
FEAUTRIER_AS_EXPECTED=(np.max(error_u_2f)<2.0e-6)
179+
FIRSTORDER_AS_EXPECTED=(np.max(error_u_0s)<6.42e-6)
180+
FEAUTRIER_AS_EXPECTED=(np.max(error_u_2f)<6.41e-6)
181181

182182
if not FIRSTORDER_AS_EXPECTED:
183183
print("First order solver max error too large: ", np.max(error_u_0s))

tests/benchmarks/analytic/density_distribution_1D.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ def nTT (r):
7171
model.thermodynamics.temperature.gas .set( temp * np.ones(npoints))
7272
model.thermodynamics.turbulence.vturb2.set((turb/magritte.CC)**2 * np.ones(npoints))
7373

74-
model = setup.set_Delaunay_neighbor_lists (model)
75-
model = setup.set_Delaunay_boundary (model)
76-
model = setup.set_boundary_condition_CMB (model)
77-
model = setup.set_rays_spherical_symmetry (model)
78-
model = setup.set_linedata_from_LAMDA_file(model, lamdaFile)
79-
model = setup.set_quadrature (model)
74+
setup.set_Delaunay_neighbor_lists (model)
75+
setup.set_Delaunay_boundary (model)
76+
setup.set_boundary_condition_CMB (model)
77+
setup.set_rays_spherical_symmetry (model)
78+
setup.set_linedata_from_LAMDA_file(model, lamdaFile)
79+
setup.set_quadrature (model)
8080

8181
model.write()
8282

@@ -238,8 +238,8 @@ def plot (r, p):
238238
#setting 'b' not yet used for testing
239239
if a_or_b == 'a':
240240
#error bounds are chosen somewhat arbitrarily, based on previously obtained results; this should prevent serious regressions.
241-
FEAUTRIER_AS_EXPECTED=(np.mean(error_u_2f)<3e-4)
242-
FIRSTORDER_AS_EXPECTED=(np.mean(error_u_0s)<2.7e-4)
241+
FEAUTRIER_AS_EXPECTED=(np.mean(error_u_2f)<4.4e-4)
242+
FIRSTORDER_AS_EXPECTED=(np.mean(error_u_0s)<4.1e-4)
243243

244244
if not FIRSTORDER_AS_EXPECTED:
245245
print("First order solver mean error too large: ", np.mean(error_u_0s))

0 commit comments

Comments
 (0)