1
2
3 """
4 Module for calculating the energy balance.
5
6 Author: R. Lombaert, H. Olofsson & M. Maercker (Chalmers, Sweden)
7
8 For the use of the EnergyBalance module, please contact one of the three authors
9 listed here.
10
11 """
12
13 import os, collections, functools, copy
14 import numpy as np
15 from scipy.interpolate import InterpolatedUnivariateSpline as spline1d
16 from scipy.interpolate import interp1d
17 from scipy.integrate import odeint, trapz, cumtrapz
18 from astropy import constants as cst
19 from astropy import units as u
20
21 import matplotlib.pyplot as plt
22
23 from cc.data import Data
24 from cc.plotting import Plotting2
25 from cc.tools.io import DataIO
26 from cc.tools.numerical import Operators as op
27 from cc.tools.numerical import Gridding
28 from cc.modeling.profilers import Velocity, Density, Profiler, Grainsize
29 from cc.modeling.profilers import Mdot, Radiance, Opacity, Temperature
30 from cc.tools.readers import CollisReader, PopReader, LamdaReader, MlineReader
31 from cc.tools.readers import RadiatReader
32 import cc.path
33
34
35
36
37
38 global k_b
39 k_b = cst.k_B.cgs.value
40
41
42 global mh
43 mh = cst.m_p.cgs.value
44
45
46
47 -def dTdr(T,r,v,gamma,rates=None,warn=1):
48
49 '''
50 The differential equation for the kinetic temperature profile.
51
52 Currently only takes into account adiabatic expansion.
53
54 This function is used by EnergyBalance to iterate and solve the ODE.
55
56 @param T: The temperature at which to evaluate the differential equation
57 @type T: float
58 @param r: The radial point(s) (cm)
59 @type r: array/float
60 @param v: The velocity profile object
61 @type v: Velocity()
62 @param gamma: The adiabatic coefficient profile as function of T
63 @type gamma: Profiler()
64
65 @keyword rates: The heating and cooling rates, summed up (erg/s/cm3, H-C).
66 Includes the density factor, without the velocity component.
67 This speeds up iteration, limiting the radial grid
68 evaluation where possible. Default if only adiabatic cooling
69 is taken into account.
70
71 (default: None)
72 @type rates: Profiler()
73 @keyword warn: Warn when extrapolation occurs in an interpolation object.
74 Not applicable to functional evaluation, or gamma in which
75 case the extrapolation is deemed safe (constant value at
76 low and high end temperatures consistent with diatomic gases)
77
78 (default: 1)
79 @type warn: bool
80
81 @return: The derivative with respect to radius (K/cm)
82 @rtype: array/float
83
84 '''
85
86
87 tg = gamma.eval(T,warn=0)
88 factor1 = 2-2*tg
89 factor2 = tg-1
90
91
92 vi = v.eval(r,warn=warn)
93 dvi = v.diff(r,warn=warn)
94
95
96
97 term1 = factor1 * (1./r + 0.5*1./vi*dvi) * T
98
99
100
101 rr = rates.eval(r,warn=warn) if rates else 0.
102 term2 = factor2 * rr / vi
103
104
105 Tprime = term1 + term2
106
107 return Tprime
108
109
110
112
113 '''
114 The energy balance class.
115
116 Author: R. Lombaert, H. Olofsson & M. Maercker (Chalmers, Sweden)
117
118 For the use of the EnergyBalance module, please contact one of the three
119 authors listed here.
120
121 Calculates energy balance and provides tools for reading and writing I/O,
122 and setting input physics.
123
124 For now limited to the energy balance from the dust formation radius up to
125 the outer radius.
126
127 An example for calculating the energy balance (see the respective functions
128 for in-depth information):
129 >>> from cc.modeling.physics import EnergyBalance as EB
130 >>> eb = EB.EnergyBalance()
131 >>> eb.iterT()
132 >>> eb.plotT()
133 >>> eb.plotRates()
134 This calculates and plots the temperature profiles and heating/cooling
135 rates across all iterations using the default settings given in
136 cc.path.aux/inputEnergyBalance_standard.dat
137
138 Default in the inputEnergyBalance_standard file can be adapted through
139 keywords passed to EnergyBalance. A few examples:
140 >>> eb = EB.EnergyBalance(a=[0.005,0.25,300,1])
141 which creates a grain-size grid from 0.005 to 0.25 micron with 300
142 points distributed in log scale.
143
144 >>> eb = EB.EnergyBalance(hterms=['dg','dt'])
145 adds dust-gas collisional heating and heat exchange to the adiabatic
146 cooling as heat/cool terms.
147
148 >>> eb = EB.EnergyBalance(template='gastronoom')
149 uses the input template to reproduce the GASTRoNOoM settings.
150
151 >>> eb = EB.EnergyBalance(gamma='h2')
152 calculates the adiabatic coefficient from the temperature-dependent measured
153 coefficient of molecular hydrogen.
154
155 >>> eb = EB.EnergyBalance(heatmode='gs2014')
156 calculates the heating and cooling terms from dust-gas interaction based on
157 the formalism of Gail & Sedlmayr 2014 as opposed to the classical
158 implementations of Decin et al. 2006 and Schoïer et al. 2001.
159
160 Furthermore, the iterative procedure can be adapted to user-specific
161 needs:
162 >>> eb.iterT(conv=0.005,imax=200,step_size=0.05)
163 sets the relative convergence criterion to 0.005 from default 0.01, the
164 maximum iterations to 200, and the step_size of the gradual maximum
165 allowed temperature change between iterations to 5%.
166
167 '''
168
169 - def __init__(self,fn=None,template='standard',**kwargs):
170
171 '''
172 Creating an EnergyBalance object used to calculate the energy balance.
173
174 Can work in conjunction with iteration with a RT code.
175
176 Takes into account several contributors to the heating and cooling of
177 an outflow:
178 - Adiabatic cooling
179 - dust-gas collisional indirect heating (elastic collisions)
180 - dust-gas direct heat exchange (accommodation)
181
182 Not yet implemented:
183 - Radiative line cooling and heating (for H2, CO, H2O, ...)
184 - Photoelectric heating
185 - Heating by cosmic rays
186
187 An instance of EnergyBalance contains three types of properties:
188 - basic input parameters given by the inputfile
189 - independent variables and basic profiles set before calculations
190 - profiles calculated from the basic parameters, variables, and
191 profiles, reset whenever any of the previous two properties are
192 changed.
193
194 First set is given in aux/inputEnergyBalance.dat.
195
196 Second set includes: radius, grain size, gas velocity,
197 gas/dust mass-loss rates, dust opacities, dust temperature, stellar
198 radiance.
199
200 Third set includes: gas temperature, gas/dust density, drift velocity.
201
202 Note that for now line cooling parameters are fixed. Hence after a new
203 RT model was calculated, a new energy balance must be calculated. In the
204 future, the level pops will be updateable.
205
206 @keyword fn: The parameter inputfile filename. If not given, a default
207 inputfile is read from aux/inputEnergyBalance.dat.
208
209 (default: aux/inputEnergyBalance.dat)
210 @type fn: str
211 @keyword template: The inputfile template used for initializing the
212 energy balance. Default is the standard, with as much
213 consistency as possible. Alternatives are 'mcp' and
214 'gastronoom' to reproduce the respective code's
215 results. Templates cannot be changed, but inputfiles
216 given by fn overwrite any input given there.
217
218 (default: 'standard')
219 @type template: str
220
221 @keyword kwargs: Input parameters listed in the default
222 inputEnergyBalance file can also be passed through the
223 initialisation of this instance. They will replace the
224 values given in the inputfile.
225
226 (default: {})
227 @type kwargs: dict
228
229 '''
230
231
232 m = "Welcome to the EnergyBalance module of ComboCode! \n"+\
233 "This module was written at Chalmers University of Technology. "+\
234 "The main author is R. Lombaert, with support from H. Olofsson "+\
235 "and M. Maercker. Please contact one of these people if you wish "+\
236 "to use the EnergyBalance with the purpose of publishing results."
237 print m
238
239
240 self.__reset()
241
242
243 self.template = template.lower()
244 if self.template not in ['mcp','gastronoom']: self.template = 'standard'
245 self.fn = fn
246 self.include_lc = 0
247
248
249
250
251 self.readInputParameters(**copy.deepcopy(kwargs))
252 self.setGrids()
253 self.setStar()
254 self.setProfiles()
255 self.setGamma()
256 self.setVelocity()
257
258
259 self.__setT()
260
261
262
263 for m in self.molecules:
264 self.setAbundance(m)
265 if self.include_lc: self.setLineCooling(m)
266
267
268
310
311
312
426
427
428
429 - def setGrids(self,r=None,a=None,l=None):
430
431 '''
432 Initialise the coordinate grids: radius (cm), grain size (cm),
433 wavelength (cm).
434
435 The grids can be given as lists, which are used as input for
436 Gridding.makeGrid. If not given, they are taken from the inputfile.
437
438 @keyword r: The radius grid (cm). [rmin,rmax,nsteps,log?]
439
440 (default: None)
441 @type r: list
442 @keyword a: The grain size grid (cm). [amin,amax,nsteps,log?] Can be
443 single value in case of one average grain size.
444
445 (default: None)
446 @type a: list/float
447 @keyword l: The wavelength grid (cm). [lmin,lmax,nsteps,log?]
448
449 (default: None)
450 @type l: list
451
452 '''
453
454
455 if self.T: self.__reset()
456
457
458 if r is None: r = self.pars['r']
459 if a is None: a = self.pars['a']
460 if l is None: l = self.pars['l']
461
462
463 a = Data.arrayify(a)
464 if a.size < 2:
465 self.a = a
466 else:
467 self.a = Gridding.makeGrid(*a)
468
469
470 self.r = Gridding.makeGrid(*r)
471 self.l = Gridding.makeGrid(*l)
472
473
474
475 - def setStar(self,rstar=None,Tstar=None,Ltype=None):
476
477 '''
478 Set the stellar properties.
479
480 If any of these are None, they are taken from the inputEnergyBalance.dat
481 file.
482
483 Defines the Radiance profile based on these properties.
484
485 @keyword rstar: The stellar radius (cm)
486
487 (default: None)
488 @type rstar: float
489 @keyword Tstar: The stellar effective temperature (K)
490
491 (default: None)
492 @type Tstar: float
493 @keyword Ltype: The type of stellar radiance profile (blackbody, ...)
494
495 (default: None)
496 @type Ltype: float
497
498 '''
499
500
501 if self.T: self.__reset()
502
503
504 if rstar is None: rstar = self.pars['rstar']
505 if Tstar is None: Tstar = self.pars['Tstar']
506 if Ltype is None: Ltype = self.pars['Ltype']
507
508 self.rstar = rstar
509 self.Tstar = Tstar
510
511
512 self.rad = Radiance.Radiance(l=self.l,func=Ltype,T=Tstar)
513 self.rad.setSurface(rstar)
514
515
516
517 - def setProfiles(self,opac=None,mdot=None,mdot_dust=None,T=None,Td=None,
518 mu=None):
519
520 '''
521 Initialise the independent profiles:opacity, mass-loss rate,
522 initial temperature, dust temperature
523
524 The grids can be given as a 1- or 2-item list, which are used as input
525 for the respective Profiler classes. If not given, they are taken from
526 the inputfile.
527
528 Format of the input lists:
529 - [constant value]
530 - [func/str,{kwargs}]
531
532 @keyword opac: The opacity profiles (l, cm^2/g). [func,{pars}]
533
534 (default: None)
535 @type opac: [func,dict]
536 @keyword mdot: The mass-loss rate profile (r, Msun/yr) [func,{pars}] or
537 constant.
538
539 (default: None)
540 @type mdot: [func,dict]
541 @keyword mdot_dust: The dust mass-loss rate profile (r, Msun/yr)
542 [func,{pars}] or constant.
543
544 (default: None)
545 @type mdot_dust: [func,dict]
546 @keyword T: The initial temperature profile (r, K). [func,{pars}]
547 The first arg is the function, followed by T0 and r0. Can be
548 string Td as well to set it to the dust temperature.
549
550 (default: None)
551 @type T: [func,dict]
552 @keyword Td: The dust temperature profile (r, K). [func,{pars}]
553 The first arg is the function.
554
555 (default: None)
556 @type Td: [func,dict]
557 @keyword mu: The mean molecular weight if nonstandard. Otherwise
558 calculated from fH and fHe. Could be specified in case,
559 e.g., some sort of dissociation is ongoing in the inner
560 wind.
561
562 (default: None)
563 @type mu: float
564
565 '''
566
567
568 if self.T: self.__reset()
569
570
571 if opac is None: opac = self.formatInput(self.pars['opac'])
572 if mdot is None: mdot = self.formatInput(self.pars['mdot'])
573 if mdot_dust is None:
574 mdot_dust = self.formatInput(self.pars['mdot_dust'])
575 if T is None: T = self.formatInput(self.pars['Tinit'])
576 if Td is None: Td = self.formatInput(self.pars['Td'])
577 if mu is None: mu = self.pars.get('mu',None)
578
579
580 self.opac = Opacity.Opacity(self.l,opac[0],**opac[1])
581
582
583
584 self.mdot = Mdot.Mdot(self.r,mdot[0],**mdot[1])
585
586
587 self.Td = Temperature.Temperature(self.r,Td[0],**Td[1])
588
589
590 self.inner = T[1]['inner']
591
592
593 if T[0].lower() == 'td':
594 self.T_iter[0] = Temperature.Temperature(self.r,Td[0],\
595 inner=self.inner,**Td[1])
596 else:
597 self.T_iter[0] = Temperature.Temperature(self.r,T[0],**T[1])
598
599
600
601
602 self.T0 = self.T_iter[0].T0
603 self.r0 = self.T_iter[0].r0
604 self.inner_eps = -np.log(self.Tstar/self.T0)/np.log(self.rstar/self.r0)
605 self.T_iter[0].setInnerEps(self.inner_eps)
606
607
608 if not mdot_dust[1].has_key('r0'): mdot_dust[1]['r0'] = self.r0
609 self.mdot_dust = Mdot.Mdot(self.r,mdot_dust[0],**mdot_dust[1])
610
611
612 if mu is None:
613 mu = Density.mean_molecular_weight(self.pars['fH'],self.pars['fHe'])
614 self.mu = mu
615
616
617
619
620 '''
621 Set the adiabatic coefficient.
622
623 Three options:
624 - Constant value
625 - Step function at 350K (going 7/5 => 5/3 at lower T, MCP model)
626 - T-dependent gamma for H2 gas
627
628 @keyword gamma: The adiabatic coefficient profile (K, /). Is either a
629 constant (float), or 'h2' in which case the adiabatic
630 coefficient is read from a file as a function of T, or
631 h2_mcp in which case gamma is a step function: 7./5. for
632 T>350 K, 5./3. otherwise.
633
634 (default: None)
635 @type gamma: float/str
636
637 '''
638
639
640
641 if isinstance(self.pars['gamma'],str):
642 gamma = self.pars['gamma'].lower()
643
644 if gamma == 'h2_mcp':
645
646
647
648 kwargs = {'x': self.T_iter[0].eval(inner_eps=self.inner_eps,\
649 warn=not self.inner),
650 'func': Profiler.step,
651 'ylow': 5./3., 'yhigh': 7./5., 'xstep': 350. }
652 gamma = Profiler.Profiler(**kwargs)
653
654 else:
655 fn = os.path.join(cc.path.aux,'h2_physical_properties.dat')
656 kwargs = {'x': self.T_iter[0].eval(inner_eps=self.inner_eps,\
657 warn=not self.inner),
658 'func': Profiler.interp_file,
659 'ikwargs': {'ext': 3, 'k': 3},
660 'filename': fn, 'ycol': -1}
661 gamma = Profiler.Profiler(**kwargs)
662
663
664
665 else:
666 kwargs = {'x': self.T_iter[0].eval(inner_eps=self.inner_eps,\
667 warn=not self.inner), \
668 'func': Profiler.constant, 'c': self.pars['gamma']}
669 gamma = Profiler.Profiler(**kwargs)
670
671 self.gamma = gamma
672
673
674
676
677 '''
678 Set the velocity profile, based on the requested function.
679
680 Done separately from other profiles, because this can depend on the
681 temperature at the inner radius (independent), and on the adiabatic
682 coefficient (for the sound velocity).
683
684 This is done before the line cooling term is set.
685
686 @keyword v: The velocity profile (r, cm/s). [func,{pars}]
687
688 (default: None)
689 @type v: [func,dict] or [cst]
690
691 '''
692
693
694 if v is None: v, vdd = self.formatInput(self.pars['v'])
695 else: v, vdd = v[0], v[1]
696
697
698
699 if v == 'vbeta' and vdd.get('v0',0) == 'vs' and not vdd.has_key('T'):
700 vdd['T'], vdd['mu'] = self.T0, self.mu
701 vdd['gamma'] = self.gamma.eval(self.T0)
702
703
704 self.v = Velocity.Velocity(self.r,v,**vdd)
705
706
707
709
710 '''
711 Set the line cooling parameters.
712
713 This includes reading the collision rates and level populations.
714
715 This is done per molecule, and upon initialisation of the EnergyBalance.
716
717 A distinction is made between the source of the spectroscopy/level pops:
718 - GASTRoNOoM: The info is taken from the MlineReader and CollisReader
719 - ALI: The info is taken from the LamdaReader, PopReader and .par
720 output file
721
722 @param m: The molecule name from the input molecules list.
723 @type m: str
724
725 '''
726
727
728
729 imol = self.molecules.index(m)
730 self.collis[m] = self.colread(self.pars['collis'][imol])
731
732
733
734 self.ipop[m] = self.i
735
736
737 if not self.pars['pop'][imol]:
738 self.setPopInitial(m)
739
740
741 elif self.pars['rtcode'].lower() == 'gastronoom':
742
743
744
745 self.mol[m] = self.pop[m]
746 else:
747 self.pop[m] = self.popread(self.pars['pop'][imol])
748
749
750 self.mol[m] = self.collis[m]
751
752
753 self.pop[m].setInterp(itype='spline',k=3,ext=3)
754 self.collis[m].setInterp(itype='spline',k=1,ext=0)
755
756
757 self.Clc(m,update_pop=0)
758
759
760 self.setTexc(m)
761
762
763
765
766 '''
767 Set an initial set of level populations.
768
769 Based on the Boltzmann distribution for a given excitation temperature.
770 The kinetic temperature is taken for now. Should likely be scalable in
771 the future.
772
773 Solves the set of equations:
774 - Sum(n_i) = 1
775 - n_i = n_0 * (g_l/g_0) exp((E_0 - E_l)/Tkin)
776
777 @param m: The molecule name from the input molecules list.
778 @type m: str
779
780 '''
781
782
783 if self.pars['rtcode'] == 'gastronoom':
784 imol = self.molecules.index(m)
785 fn = self.pars['collis'][imol].replace('collis','radiat')
786 ny = max(self.collis[m]['coll_trans']['lup'])
787 self.mol[m] = RadiatReader.RadiatReader(fn,ny=ny)
788
789
790 T = self.T.eval(inner_eps=self.inner_eps,warn=not self.inner)
791 g0 = self.mol[m].getLWeight(1)
792 E0 = self.mol[m].getLEnergy(1,unit='erg')
793 ny = self.mol[m]['pars']['ny']
794 g = self.mol[m].getLWeight(range(2,ny+1))
795 E = self.mol[m].getLEnergy(range(2,ny+1),unit='erg')
796
797
798 g.shape = (g.size,1)
799 E.shape = (E.size,1)
800
801
802 Ediff = E0 - E
803 nfactor = g/g0*np.exp(np.outer(Ediff,1./(k_b*0.999*T)))
804 denominator = 1 + sum(nfactor)
805 n0 = 1./denominator
806
807
808 n = n0*nfactor
809
810
811 self.pop[m] = PopReader.PopReader(None)
812 self.pop[m].setP(self.r)
813 self.pop[m].setNY(ny)
814 self.pop[m].setPop(index=1,n=n0)
815 for i in range(2,ny+1):
816 self.pop[m].setPop(index=i,n=n[i-2,:])
817
818
819
821
822 '''
823 Set the abundance profile for a molecule.
824
825 Two possibilities:
826 - Level populations are given, which include molecular abundance
827 profiles as well, and are RT code dependent
828 - No level pops are given, so use the default abundance profile
829
830 The second possibility requires a read method and a filename to be given
831 in the molecule definition, e.g.
832 molecule=12C16O np.loadtxt fname=waql.par usecols=[1,4] skiprows=9 unpack=1
833 Alternatively, a constant value can be given as well (not advised).
834
835 @param m: The molecule name from the input molecules list.
836 @type m: str
837
838 '''
839
840
841 imol = self.molecules.index(m)
842
843
844
845 if not self.pars['pop'] or len(self.pars['pop']) <= imol \
846 or not self.pars['pop'][imol]:
847 mlst = self.pars['molecule'][imol]
848
849 if not mlst[0]:
850 q = "No abundance input given in molecule={} ".format(m)+\
851 "definition. Cannot calculate default level populations "+\
852 "or set photoelectric heating."
853 raise IOError(q)
854
855
856
857 if len(mlst[1]) == 0:
858 p, abun = self.r, mlst[0]
859 else:
860 dd = DataIO.read(func=mlst[0],**mlst[1])
861
862
863 if dd[0][0]/self.pars['rstar']<0.1:
864 p = dd[0]*self.pars['rstar']
865 else:
866 p = dd[0]
867
868
869
870 if len(dd) == 2:
871 abun = dd[1]
872 elif len(dd) == 3:
873 abun = dd[1]/dd[2]
874
875
876
877 elif self.pars['rtcode'].lower() == 'gastronoom':
878
879
880 self.pop[m] = self.popread(self.pars['pop'][imol])
881
882
883 p = self.pop[m].getP()
884 abun = self.pop[m].getProp('amol')
885 else:
886
887
888 fn = self.pars['pop'][imol].replace('pop','log')
889
890
891 nvol = DataIO.getKeyData(filename=fn,incr=0,keyword='nvol')[0]
892 nvol = int([line.split('=')[1]
893 for line in nvol
894 if 'nvol' in line.lower()][0])
895
896
897 data = DataIO.readFile(fn,' ')
898 ixmol = DataIO.findKey(i=0,key='xmol',data=data) + 2
899 p, abun = np.genfromtxt(fn,usecols=[1,4],skip_header=ixmol+1,\
900 unpack=1,max_rows=nvol-2)
901
902
903 if len(p) != len(abun):
904 abun_interp = mlst[0]
905
906
907
908
909 else:
910 abun_interp = interp1d(x=p,y=abun,kind='linear',bounds_error=0,\
911 assume_sorted=1,fill_value=(abun[0],0.))
912 self.abun[m] = Profiler.Profiler(x=self.r,func=abun_interp)
913
914
915
917
918 '''
919 Calculate the excitation temperature for the level populations given in
920 self.pop. This method is called when the pops are read for the first
921 time (or set by setInitialPop), and when the level populations are
922 updated.
923
924 The excitation temperature is set as an array of dimensions
925 (number of transition indices, number of impact parameter), hence for a
926 given transition index, you can retrieve the excitation temperature as
927 self.Texc[m][i-1,:] as a function of impact parameter. Alternatively,
928 you can access Texc by calling getTexc and passing either the indices or
929 the lup/llow.
930
931 The impact parameter grid can be retrieved from self.pop[m].getP().
932
933 Note that the excitation temperature is calculated for all radiative
934 transitions included in the molecular spectroscopy; not for all
935 collisional transitions.
936
937 @param m: The molecule tag
938 @type m: str
939
940 '''
941
942
943
944 indices = self.mol[m].getTI(itype='trans')
945 lup = self.mol[m].getTUpper(indices)
946 llow = self.mol[m].getTLower(indices)
947 gl = self.mol[m].getLWeight(llow)
948 gu = self.mol[m].getLWeight(lup)
949 El = self.mol[m].getLEnergy(llow,unit='erg')
950 Eu = self.mol[m].getLEnergy(lup,unit='erg')
951
952
953 nl = self.pop[m].getPop(llow)
954 nu = self.pop[m].getPop(lup)
955
956
957 gl.shape = (gl.size,1)
958 gu.shape = (gu.size,1)
959 El.shape = (El.size,1)
960 Eu.shape = (Eu.size,1)
961
962
963 self.Texc[m] = -1./np.log(nu/nl*gl/gu)*(Eu-El)/k_b
964
965
966
967 - def getTexc(self,m,indices=None,lup=None,llow=None):
968
969 """
970 Return the excitation temperature profile as a function of impact
971 parameter for given transition indices of a given molecule.
972
973 Can also take upper and/or lower level indices to determine the
974 transition indices.
975
976 The excitation temperature is returned as an array of dimensions
977 (number of transition indices, number of impact parameters).
978
979 @param m: The molecule tag
980 @type m: str
981
982 @keyword indices: The transition indices. Can be indirectly given
983 through upper and/or lower level indices. If default
984 and no llow/lup are given, all Texc are returned for
985 this molecule.
986
987 (default: None)
988 @type indices: list/array
989 @keyword lup: The upper level indices used to extract the transition
990 indices. If this or llow are defined, the keyword indices
991 is overwritten.
992
993 (default: None)
994 @type lup: list/array
995 @keyword llow: The upper level indices used to extract the transition
996 indices. If this or lup are defined, the keyword indices
997 is overwritten.
998
999 (default: None)
1000 @type llow: list/array
1001
1002 @return: The excitation temperature, with array shape (n_index,n_p)
1003 @rtype: array
1004
1005 """
1006
1007
1008 if not (lup is None and llow is None):
1009 if not lup is None: lup = Data.arrayify(lup)
1010 if not llow is None: llow = Data.arrayify(llow)
1011 indices = self.mol[m].getTI(lup=lup,llow=llow)
1012
1013
1014 if indices is None: return self.Texc[m]
1015
1016
1017
1018 indices = Data.arrayify(indices)
1019 return self.Texc[m][indices-1,:]
1020
1021
1022
1024
1025 '''
1026 Reset all variables that depend on the radius, velocity, etc.
1027
1028 '''
1029
1030
1031 self.gdens = None
1032 self.ddens = None
1033 self.nd = None
1034
1035
1036 self.vd = None
1037 self.w = None
1038
1039
1040 self.T = None
1041 self.T_iter = dict()
1042 self.i = 0
1043
1044
1045 self.H = {}
1046 self.C = {}
1047 self.rates = {}
1048
1049
1050
1052
1053 '''
1054 Reset some of the properties that depend on temperature so they can be
1055 calculated anew.
1056
1057 '''
1058
1059
1060 self.vd = None
1061 self.w = None
1062
1063
1064 self.__setT()
1065
1066
1067 self.setVdust()
1068
1069
1070
1072
1073 '''
1074 Calculate the drift velocity profile.
1075
1076 This assumes a stellar radiance profile is given, of which the
1077 wavelength grid is used to integrate Q_l * L_l, thereby interpolating
1078 the opacity.
1079
1080 The two available modes are standard and beta:
1081 - standard: Calculates the drift velocity based on the balance between
1082 the radiation pressure and the drag force.
1083 - vbeta: Follows MCP, where the terminal drift velocity is calculated
1084 from the terminal gas velocity, based on the balance between radiation
1085 pressure and drag force, with a beta law going to that max velocity.
1086
1087 Note that the vbeta mode follows the F mode for dust velocity in MCP.
1088
1089
1090 '''
1091
1092 if self.w is None:
1093
1094 self.setDensity('gas')
1095
1096
1097 kwargs = {'a': self.a, 'l': self.l, 'P': self.pars['P'],
1098 'sd': self.pars['sd'], 'opac': self.opac,
1099 'radiance': self.rad, 'alpha': self.pars['alpha'],
1100 'mu': self.gdens.getMeanMolecularWeight()}
1101
1102
1103 method = self.formatInput(self.pars['w_mode'])
1104 if method[0] == 'vbeta2D':
1105
1106
1107
1108
1109 kwargs['v'] = max(self.v.eval())
1110 kwargs['mdot'] = self.mdot.eval(self.r0)
1111 kwargs['w_thermal'] = 'none'
1112 wmax = Velocity.driftRPDF(r=self.r0,**kwargs)[0]
1113
1114
1115
1116
1117 v0 = method[1].get('v0',self.v.eval(self.r0))
1118 r0 = method[1].get('r0',self.r0)
1119 beta = method[1].get('beta',1.0)
1120 wmax = method[1].get('vinf',wmax)
1121 self.w = Velocity.Drift(r=self.r,a=self.a,\
1122 func=Velocity.vbeta2D,\
1123 r0=r0,v0=v0,vinf=wmax,beta=beta)
1124
1125 else:
1126
1127
1128 kwargs['w_thermal'] = self.pars['w_thermal']
1129 kwargs['v'] = self.v
1130 kwargs['r'] = self.r
1131 kwargs['mdot'] = self.mdot
1132 kwargs['func'] = Velocity.driftRPDF
1133
1134
1135 vtherm_types = ['kwok','mean','rms','prob','epstein']
1136 if self.pars['w_thermal'].lower() in vtherm_types:
1137
1138 kwargs['T'] = self.T
1139
1140 self.w = Velocity.Drift(**kwargs)
1141
1142
1143
1145
1146 '''
1147 Set the dust velocity profile. The drift is averaged over grain size.
1148
1149 '''
1150
1151 if self.vd is None:
1152 self.setDrift()
1153 kwargs = {'r': self.r, 'func': Velocity.vdust,
1154 'w': self.w, 'v': self.v, 'norm_type': 'standard'}
1155 self.vd = Velocity.Velocity(**kwargs)
1156
1157
1158
1160
1161 '''
1162 Calculate the density profile for gas or dust.
1163
1164 The dust density profile is not dependent on grain size, since the drift
1165 averaged over grain size.
1166
1167 @param dtype: The density profile type, being 'gas' or 'dust'.
1168 @type dtype: str
1169
1170 '''
1171
1172
1173
1174 if dtype.lower() == 'gas':
1175 if self.gdens is None:
1176 kwargs = {'r': self.r,'func':Density.dens_mdot,
1177 'mdot': self.mdot, 'v': self.v}
1178 self.gdens = Density.Density(**kwargs)
1179 kwargs = {'fH': self.pars['fH'], 'fHe': self.pars['fHe']}
1180 self.gdens.calcNumberDensity(**kwargs)
1181
1182
1183
1184 self.gdens.mu = self.mu
1185
1186 if dtype.lower() == 'dust':
1187
1188 if self.ddens is None:
1189
1190
1191 self.setVdust()
1192 kwargs = {'r': self.r,'func':Density.dens_mdot, 'dust': 1,
1193 'mdot': self.mdot_dust, 'v': self.vd}
1194 self.ddens = Density.Density(**kwargs)
1195
1196
1197 if self.nd is None and self.a.size > 1:
1198
1199
1200 kwargs = {'r': self.r, 'a': self.a, 'sd': self.pars['sd'],
1201 'func': getattr(Grainsize,self.pars['GSD']),
1202 'w': self.w,
1203 'v': self.v, 'mdot_dust': self.mdot_dust}
1204 self.nd = Grainsize.Distribution(**kwargs)
1205
1206 elif self.nd is None:
1207
1208
1209 self.ddens.calcNumberDensity(sd=self.pars['sd'],a=self.a)
1210
1211 self.nd = self.ddens
1212
1213
1214
1216
1217 '''
1218 Update the level populations.
1219
1220 This reads the level populations from the same file as listed upon
1221 creation of the EnergyBalance and assumes those level populations are
1222 appropriate for the temperature profile of the iteration number self.i.
1223
1224 This is done per molecule.
1225
1226 @param m: The molecule name from the input molecules list.
1227 @type m: str
1228
1229 @keyword fn: The filename of a populations file, in case it changes or
1230 is added anew after default populations have been used.
1231 Ignored if the file does not exist.
1232
1233 (default: '')
1234 @type fn: str
1235 @keyword updateLC: The flag that moves the self.ipop to the current (or
1236 next) iteration, so that calcClc knows a new line
1237 cooling term must be updated. This is always set to 1
1238 if the populations are updated. Setting this to 1
1239 upon function call, tells calcClc the LC term must be
1240 calculated anew regardless of reading new pops (and
1241 thus uses the old pops with the new T profile of this
1242 iteration). This key can be set in the inputfile.
1243
1244 (default: 0)
1245 @type updateLC: bool
1246
1247 '''
1248
1249
1250 imol = self.molecules.index(m)
1251
1252
1253 if os.path.isfile(str(fn)):
1254 self.pars['pop'][imol] = fn
1255
1256
1257 if self.pars['pop'][imol]:
1258
1259 npop = self.popread(self.pars['pop'][imol])
1260 if False in [np.array_equal(npop.getPop(i),self.pop[m].getPop(i))
1261 for i in npop.getLI()]:
1262 self.pop[m] = npop
1263
1264
1265 self.setTexc(m)
1266 self.pop[m].setInterp(itype='spline',k=3,ext=3)
1267
1268
1269 updateLC = 1
1270
1271
1272 if updateLC or self.pars.get('updateLC',0):
1273
1274
1275
1276 if self.C['lc_{}'.format(m)].has_key(self.i):
1277 self.ipop[m] = self.i + 1
1278 else:
1279 self.ipop[m] = self.i
1280
1281
1282
1284
1285 '''
1286 Set the current temperature profile for this iteration. When i == 0,
1287 this is the initial guess.
1288
1289 This is done explicitly, because upon initialisation the state of the
1290 object depends on self.T being None or not.
1291
1292 '''
1293
1294 self.T = self.T_iter[self.i]
1295
1296
1297
1298 - def iterT(self,conv=0.01,imax=50,step_size=0.,dTmax=0.10,warn=1,\
1299 *args,**kwargs):
1300
1301 '''
1302 Iterate the temperature profile until convergence criterion is reached.
1303
1304 Extra arguments are passed on to the spline1d interpolation of the
1305 total cooling and heating terms through the calcT() call.
1306
1307 @keyword conv: The maximum relative allowed change in T for convergence.
1308
1309 (default: 0.01)
1310 @type conv: float
1311 @keyword imax: Maximum number of allowed iterations. Code stops when
1312 this number is reached, but not before dTmax = 1.
1313
1314 (default: 50)
1315 @type imax: int
1316 @keyword step_size: The minimum step size in maximum allowed relative
1317 temperature change. The code decides dynamically
1318 based on how close the iteration is to convergence
1319 which multiple of this step size the next iteration
1320 allows. If T change percentage grows to fast,
1321 decrease this number. Default is 0, in which case
1322 the maximum allowed T change is kept constant at
1323 dTmax.
1324
1325 (default: 0)
1326 @type step_size: float
1327 @keyword dTmax: The starting value for the maximum allowed relative
1328 temperature change between iterations. This maximum
1329 increases as the code reaches convergences, through a
1330 set multiple of step_size. If step_size is 0, dTmax
1331 stays constant
1332
1333 (default: 0.10)
1334 @type dTmax: float
1335 @keyword warn: Warn when extrapolation occurs.
1336
1337 (default: 1)
1338 @type warn: bool
1339
1340 '''
1341
1342 print('-- Iterating T(r) now.')
1343 dTnsteps = (1.-dTmax)/step_size if step_size else 0.
1344 steps = 0.
1345
1346
1347
1348 print('Iteration {} for T(r)...'.format(self.i+1))
1349 self.calcT(dTmax,warn=warn,*args,**kwargs)
1350
1351
1352
1353
1354 while True:
1355 dTdiff = np.abs(1.-self.T.eval(warn=not self.inner)\
1356 /self.T_iter[self.i-1].eval(warn=not self.inner))
1357 if (np.all(dTdiff<conv) and steps == dTnsteps) or self.i == imax:
1358 break
1359 if np.all(dTdiff<(dTnsteps-steps)*conv):
1360 if dTnsteps-steps > 8.: steps += 4.
1361 elif dTnsteps-steps > 4.: steps += 2.
1362 else: steps += 1.
1363 print('Iteration {} for T(r)...'.format(self.i+1))
1364 self.calcT(dTmax+step_size*steps,warn=warn,*args,**kwargs)
1365 if not np.all(np.isfinite(self.T.eval(inner_eps=self.inner_eps,\
1366 warn=not self.inner))):
1367 print('NaNs found in T-profile. Breaking off iteration.')
1368 break
1369
1370
1371
1372 - def calcT(self,dTmax=1,warn=1,ode_kwargs={},*args,**kwargs):
1373
1374 '''
1375 Calculate the temperature profile based on the differential equation
1376 given by Goldreich & Scoville (1976). The function is defined in dTdr.
1377
1378 The initial temperature is taken to be the second argument of the
1379 initial temperature profile given by T in inputEnergyBalance.dat.
1380 This is typically the condensation temperature.
1381
1382 Additionals arguments are passed on to the spline1d interpolation of the
1383 total cooling and heating terms, e.g. k=3, ext=0 are defaults.
1384
1385 @keyword dTmax: The maximum allowed relative temperature change for this
1386 T calculation. Set to 100% by default.
1387
1388 (default: 1)
1389 @type dTmax: float
1390 @keyword warn: Warn when extrapolation occurs.
1391
1392 (default: 1)
1393 @type warn: bool
1394 @keyword ode_kwargs: Extra arguments for the ODE solver. They are added
1395 to the dict ode_args made by the function, and
1396 hence overwrites any defaults. In principle, the
1397 defaults are fine, but can be overwritten if needed
1398
1399 (default: {})
1400 @type ode_kwargs: dict
1401
1402 '''
1403
1404
1405
1406
1407
1408
1409
1410 self.i += 1
1411
1412
1413 self.setDensity('gas')
1414
1415
1416
1417
1418
1419 for term in self.pars['hterms']:
1420 getattr(self,'H'+term)()
1421 for term in self.pars['cterms']:
1422 getattr(self,'C'+term)()
1423
1424
1425
1426 nh2 = self.gdens.eval(dtype='nh2')
1427
1428
1429
1430 ascale = self.gdens.fH + 1. + self.gdens.fHe * (self.gdens.fH + 2.)
1431
1432
1433
1434 dens_term = nh2 * k_b * ascale
1435
1436
1437 yrates = sum([self.H[k][self.i] for k in self.H.keys()]) \
1438 - sum([self.C[k][self.i] for k in self.C.keys() if k != 'ad'])
1439
1440
1441 self.rates[self.i] = yrates/dens_term
1442
1443
1444 if self.r.size == Data.arrayify(yrates).size:
1445 rp = Profiler.Profiler(self.r,spline1d(self.r,self.rates[self.i],\
1446 *args,**kwargs))
1447 else:
1448 rp = None
1449
1450
1451 ode_args = {'func': dTdr, 'y0': self.T0, 't': self.r,
1452 'args': (self.v,self.gamma,rp,warn)}
1453 ode_args.update(ode_kwargs)
1454 Tr = odeint(**ode_args)[:,0]
1455
1456
1457
1458
1459 if self.i < 0:
1460 dTmax = self.pars['dTmax']
1461 print('Changing T by {:.1f}%.'.format(dTmax*100.))
1462 Ti = self.T.eval(inner_eps=self.inner_eps,warn=not self.inner)
1463 Tr = np.where(Tr<=0.,np.ones_like(Tr),Tr)
1464 Tr = Ti + dTmax*(Tr-Ti)
1465
1466
1467
1468
1469 Tinterp = spline1d(self.r,Tr,k=3,ext=3)
1470 keys = {'inner':self.inner,'inner_eps':self.inner_eps,'r0':self.r0,
1471 'T0':self.T0}
1472 self.T_iter[self.i] = Temperature.Temperature(self.r,Tinterp,**keys)
1473
1474
1475
1476 self.__next_iter()
1477
1478
1479
1480 self.Cad()
1481
1482
1483
1485
1486 '''
1487 Calculate the adiabatic cooling rate. This is not used by the solver of
1488 the dTdr differential equation, and is only for plotting purposes.
1489
1490 Uses the latest calculation of the T-profile.
1491
1492 Equation derived from Decin et al. 2006 and Danilovich 2016.
1493
1494 '''
1495
1496
1497 if self.C['ad'].has_key(self.i): return
1498
1499
1500
1501 gamma = self.gamma.eval(self.T.eval(inner_eps=self.inner_eps,\
1502 warn=not self.inner),warn=0)
1503
1504
1505
1506 factor1 = 2-2*gamma
1507 factor2 = gamma-1
1508
1509
1510
1511
1512 vi = self.v.eval()
1513 dvi = self.v.diff()
1514
1515
1516
1517 self.setDensity('gas')
1518 nh2 = self.gdens.eval(dtype='nh2')
1519 ascale = self.gdens.fH + 1. + self.gdens.fHe * (self.gdens.fH + 2.)
1520 conversion = nh2 * k_b * vi * ascale / factor2
1521
1522
1523 main_term = (1./self.r + 0.5*1./vi*dvi) \
1524 * self.T.eval(inner_eps=self.inner_eps,\
1525 warn=not self.inner)
1526
1527
1528
1529 self.C['ad'][self.i] = -1. * conversion * factor1 * main_term
1530
1531
1532
1534
1535 '''
1536 Calculate the heating rate by dust-gas collisions.
1537
1538 Note that this term evaluates to zero for the inner wind at r<r0, if the
1539 dust density is 0 there. This can be enforced by choosing an mdot_step
1540 function.
1541
1542 Sputtering is applied if a grain size distribution is used. For a
1543 constant grain size, this is not done, since it's not realistic to
1544 remove all dust if the drift becomes too large.
1545
1546 The equation is derived from Goldreich & Scoville 1976, based on Schoier
1547 et al 2001, and Decin et al. 2006:
1548
1549 General case:
1550 Hdg = n_d sigma_d w x 1/2 rho_g w^2
1551 =>
1552 Hdg = pi/2 n_H2 m_H (fH+2) (1+4fHe) (1-P)^(2/3) Int(n_d w^3 a^2 da)
1553
1554 MCP/ALI case (average grain size a):
1555 Hdg = pi n_H2 m_H n_d w^3 a^2
1556
1557 GASTRoNOoM case (MRN):
1558 Hdg = pi/2 A n_H2^2 m_H (fH+2)^2 (1+4fHe) Int(w^3 a^-1.5 da)
1559
1560 '''
1561
1562
1563 if self.H['dg'].has_key(self.i): return
1564
1565
1566
1567
1568 if not self.T:
1569 self.__setT()
1570
1571
1572 self.setDrift()
1573 self.setDensity('gas')
1574 self.setDensity('dust')
1575 nh2 = self.gdens.eval(dtype='nh2')
1576 fH = self.pars['fH']
1577 fHe = self.pars['fHe']
1578 P = self.pars['P']
1579 mode = self.pars['heatmode'].lower()
1580
1581
1582 cfac = np.pi*nh2*mh*(fH+2.)*(1.+4.*fHe)*(1.-P)**(-2./3.)
1583
1584
1585 if mode == 'gs2014':
1586
1587 vT = 4./3.*np.sqrt(8.*k_b*self.T.eval(inner_eps=self.inner_eps,\
1588 warn=not self.inner)\
1589 /(self.gdens.mu*np.pi*mh))
1590
1591
1592
1593 else:
1594 cfac *= 0.5
1595 vT = 0.
1596
1597
1598
1599
1600 if self.a.size > 1:
1601
1602
1603 vT = np.transpose([vT])
1604 nd = self.nd.eval(w=self.w.eval(),w_sputter=self.pars['w_sputter'])
1605 w = self.w.eval()
1606 afac = nd*self.a**2.*w**2.*(vT**2.+w**2.)**0.5
1607 gsfac = trapz(x=self.a,y=afac,axis=1)
1608 else:
1609
1610
1611
1612 nd = self.nd.eval(dtype='ndust')
1613 w = np.squeeze(self.w.eval())
1614 gsfac = nd*self.a**2*w**2.*(vT**2.+w**2.)**0.5
1615
1616
1617 self.H['dg'][self.i] = cfac*gsfac
1618
1619
1620
1622
1623 '''
1624 Calculate the heating rate by dust-gas heat exchange.
1625
1626 The equation is derived from Burke & Hollenbach 1983, based on
1627 Groenewegen 1994, Schoier et al. 2001, and Decin et al. 2006:
1628
1629 Note that this term evaluates to zero for the inner wind at r<r0, if the
1630 dust density is 0 there. This can be enforced by choosing an mdot_step
1631 function.
1632
1633 Sputtering is applied if a grain size distribution is used. For a
1634 constant grain size, this is not done, since it's not realistic to
1635 remove all dust if the drift becomes too large.
1636
1637 The older implementations used by MCP/ALI and GASTRoNOoM follow Burke &
1638 Hollenbach 1983 and Groenwegen 1994. The general-case implementation
1639 follows the more recent work of Gail & Sedlmayr, adding in proper vT and
1640 drift terms. The GS2014 is not yet fully implemented, but the Hdt term
1641 is already available here.
1642
1643 General case (following Gail & Sedlmayr 2014, see Eq 15.19):
1644 Hdt = alpha pi k_b n_h2 (fH+2.)(1.-P)^(-2./3.) Int(n_d a^2 da) (T_d - T)
1645 sqrt(8*vT^2+drift^2) (1/(gamma-1))
1646
1647 MCP/ALI case (n_d: dust number density, average grain size a):
1648 Hdt = 2 alpha pi k_b n_h2 (n_d a^2) (T_d - T) vT
1649
1650 GASTRoNOoM case (n_d: distribution following MRN):
1651 Hdt = 2 alpha pi k_b n_h2 (fH+2.) Int(n_d a^2 da) (T_d - T) vT
1652
1653 In all cases, alpha is the accommodation coefficient (from Groenewegen
1654 1994):
1655 alpha = 0.35 exp(-sqrt(0.002*(T_d + T)))+0.1
1656
1657 and vT is the thermal velocity:
1658 vT = sqrt(8 k_b t/(mu pi m_H))
1659
1660 '''
1661
1662
1663 if self.H['dt'].has_key(self.i): return
1664
1665
1666 self.setDrift()
1667 self.setDensity('gas')
1668 self.setDensity('dust')
1669 nh2 = self.gdens.eval(dtype='nh2')
1670 fH = self.pars['fH']
1671 P = self.pars['P']
1672 mode = self.pars['heatmode']
1673
1674
1675 td = self.Td.eval()
1676 t = self.T.eval(inner_eps=self.inner_eps,warn=not self.inner)
1677
1678
1679 accom = 0.35*np.exp(-np.sqrt((td+t)*0.002))+0.1
1680
1681
1682 cfac = np.pi*k_b*nh2*(fH+2.)*(1.-P)**(-2./3.)
1683
1684
1685 vT = np.sqrt(8*k_b*t/(self.gdens.mu*np.pi*mh))
1686
1687
1688 tdiff = (td-t)
1689
1690
1691 if self.a.size > 1:
1692
1693
1694 nd = self.nd.eval(w=self.w.eval(),w_sputter=self.pars['w_sputter'])
1695 gsfac = trapz(x=self.a,y=nd*self.a**2,axis=1)
1696 else:
1697
1698
1699
1700 nd = self.nd.eval(dtype='ndust')
1701 gsfac = nd*self.a**2
1702
1703
1704
1705
1706 if mode.lower() == 'gs2014':
1707
1708
1709
1710
1711
1712 drift = self.w.avgDrift(norm_type='collisional',nd=self.nd)
1713
1714
1715 fg = 1./(self.gamma.eval(self.T.eval(inner_eps=self.inner_eps,\
1716 warn=not self.inner),\
1717 warn=0)-1.)
1718
1719
1720 vfac = (8.*vT**2+drift**2)**0.5
1721 self.H['dt'][self.i] = accom * cfac * fg * tdiff * gsfac * vfac
1722
1723 else:
1724
1725 self.H['dt'][self.i] = 2. * accom * cfac * tdiff * vT * gsfac
1726
1727
1728
1730
1731 '''
1732 Calculate the heating rate by the photoelectric effect.
1733
1734 Two methods are available at present:
1735 1) Following Draine 1978 and Huggins et al. 1988, as used by MCP
1736 (see also Crosas & Menten 1997)
1737 2) Following Bakes & Tielens 1994., as implemented by Decin et al.
1738 2006 in GASTRoNOoM.
1739
1740 No options to tweak these methods has been implemented yet, but a lot of
1741 consistency checks should be done, and some of the assumptions can be
1742 improved on with a consistent calculation (e.g. Av in method 2).
1743
1744 Photoelectric heating as derived by Bakes & Tielens is maybe a good
1745 basis for further development of the heating term, but see also Woitke
1746 2015 (2015EPJWC.10200011W) for recent developments.
1747
1748 The derivation in method 2 makes a lot of assumptions and are applied
1749 specifically to the case of GASTRoNOoM (e.g. fixed grain size
1750 distribution). Hence, the implementation is considered appropriate for
1751 the default settings in the inputEnergyBalance_gastronoom.dat file. Any
1752 deviations from that must be treated carefully.
1753
1754 No sputtering is applied. Small grains are the most relevant for Hpe,
1755 and any sputtering would reduce the size of existing dust grains, thus
1756 increasing the amount of small grains. Since we cannot have Hpe directly
1757 depend on the grain size distribution (and instead work with a scaling
1758 factor amin_scale), we do not apply sputtering to the photoelectric
1759 heating.
1760
1761 '''
1762
1763
1764 if self.H['pe'].has_key(self.i): return
1765
1766
1767 self.setDensity('gas')
1768 self.setDensity('dust')
1769
1770
1771
1772 method = self.formatInput(self.pars['pe_method'])
1773 if method[0].lower() == 'draine':
1774 nh2 = self.gdens.eval(dtype='nh2')
1775
1776
1777 Kpe = method[1].get('Kpe',1e-26)
1778 self.H['pe'][self.i] = Kpe * nh2
1779
1780
1781
1782
1783 elif method[0].lower() == 'bakes':
1784 T = self.T.eval(inner_eps=self.inner_eps,warn=not self.inner)
1785 G0 = method[1].get('G0',1.)
1786 amin_scale = method[1].get('amin_scale',0.2)
1787 d2g = self.ddens.eval()/self.gdens.eval()
1788 nhtot = self.gdens.eval(dtype='nhtot')
1789
1790
1791
1792
1793
1794
1795
1796
1797 aco = self.abun['12C16O'].eval()
1798 xco = aco/aco[0]
1799 abun_c = method[1].get('abun_c')
1800 abun_o = method[1].get('abun_o')
1801 if abun_c > abun_o:
1802 ne = nhtot * abun_c * (1-xco*abun_o/abun_c)
1803
1804
1805
1806
1807
1808
1809
1810 else:
1811 ne = nhtot * abun_c * (1-xco)
1812
1813
1814 Hpehat = amin_scale*1e-24*nhtot
1815 Hpehat[ne>0] *= 0.03/(1+2e-4*G0*np.sqrt(T[ne>0])/ne[ne>0])
1816 Hpehat[ne==0] = 0.
1817
1818
1819
1820
1821
1822 Nh = cumtrapz(nhtot[::-1],x=(self.r[-1]-self.r)[::-1],\
1823 initial=0.)[::-1]
1824
1825
1826 Av = 1.6e-22*Nh/0.01*d2g
1827 self.H['pe'][self.i] = Hpehat*G0*np.exp(-1.8*Av)
1828
1829
1830
1832
1833 '''
1834 Calculate the heating rate by cosmic rays.
1835
1836 The rate is taken from Goldsmith & Langer 1978, and is also used by
1837 Groenwegen 1994 and Decin et al 2006. This is a very approximate formula
1838 and will need updating in the future.
1839
1840 Two modes are available: The standard one, using n(H2), and the one used
1841 by Groenewegen 1994 and Decin et al. 2006 (where all gas mass is placed
1842 into H2, even if fH or fHe are non-zero). Controlled through the keyword
1843 cr_method == 'groenewegen' or cr_method == 'standard' in the inputfile.
1844
1845 '''
1846
1847
1848 if self.H['cr'].has_key(self.i): return
1849
1850
1851 self.setDensity('gas')
1852
1853
1854
1855
1856
1857
1858
1859 nh2 = self.gdens.eval(dtype='nh2')
1860 fH = self.gdens.fH
1861 fHe = self.gdens.fHe
1862
1863
1864 if self.pars['cr_method'].lower() == 'groenewegen':
1865 self.H['cr'][self.i] = 6.4e-28 * nh2 * (fH/2.+1.)*(1.+4.*fHe)
1866 else:
1867 self.H['cr'][self.i] = 6.4e-28 * nh2
1868
1869
1870
1872
1873 '''
1874 Calculate the line cooling rate by vibrational excitation of H_2.
1875
1876 Two modes are available (used by MCP/ALI and GASTRoNOoM, respectively):
1877 1) Following Groenewegen 1994, based on Hartquist et al 1980. Based
1878 on fitting of tabulated data of H2 cooling under LTE conditions
1879 2) Following Decin et al 2006, based on GS1976, Hollenbach & McKee
1880 1979 and Hollenbach & McKee 1989.
1881
1882 The keyword h2_method (groenewegen, or decin) determines which of the
1883 two is used.
1884
1885 '''
1886
1887
1888 if self.C['h2'].has_key(self.i): return
1889
1890
1891 self.setDensity('gas')
1892 nh2 = self.gdens.eval(dtype='nh2')
1893 T = self.T.eval(inner_eps=self.inner_eps,warn=not self.inner)
1894
1895
1896 if self.pars['h2_method'].lower() == 'groenewegen':
1897 self.C['h2'][self.i] = 2.6111e-21*nh2*(T/1000.)**4.74
1898
1899
1900 else:
1901
1902
1903 nh = self.gdens.eval(dtype='nh')
1904
1905
1906
1907 A10 = 3e-7
1908
1909
1910 hnu10 = 9.61305939e-13
1911
1912
1913 coll_deex_h = 1.0e-12*np.sqrt(T)*np.exp(-1000./T)
1914 coll_deex_h2 = 1.4e-12*np.sqrt(T)*np.exp(-18100./(T+1200.))
1915 alpha = nh*coll_deex_h + nh2*coll_deex_h2
1916
1917
1918 nomin = alpha*np.exp(-hnu10/k_b/T)
1919 denomin = alpha*(1+np.exp(-hnu10/k_b/T))+A10
1920
1921
1922 n1 = nh2*nomin/denomin
1923 self.C['h2'][self.i] = A10 * hnu10 * n1
1924
1925
1926
1927 - def Clc(self,m,update_pop=1):
1928
1929 '''
1930 Calculate the radiative line cooling rate for a molecule.
1931
1932 For GS2014, no correction term yet for the excitation temperature per
1933 level.
1934
1935 Follows Sahai 1990.
1936
1937 Cubic spline interpolation for the level populations. Linear
1938 interpolation and extrapolation for the collision rates (as for
1939 GASTRoNOoM).
1940
1941 Currently not yet implemented to use a sqrt(T/T0) extrapolation at lower
1942 boundary as is done by MCP/ALI.
1943
1944 Note that EnergyBalance will internally call this function with the
1945 molecule tag tacked onto the Clc function name.
1946
1947 @param m: The molecule name from the input molecules list.
1948 @type m: str
1949 @keyword update_pop: Check if the level populations have been updated
1950
1951 (default: 1)
1952 @type update_pop: bool
1953
1954 '''
1955
1956 def calcLevelLC(llow,r,T):
1957
1958 '''
1959 Calculate the line cooling contribution for a single transition
1960 index, with a fixed lower level i and for which j > i, for the
1961 entire radial grid.
1962
1963 Keep in mind, the goal is to include all transitions from every
1964 level to every level. It doesn't actually matter if the energy is
1965 lower or higher: The Sahai + Einstein equations change the sign if
1966 the lower level is really the upper level in terms of energy.
1967
1968 We can use the structure of the collision rate files, where all
1969 possible collisional transitions are included. By returning all
1970 transitions to a given lower level, we already have j > i.
1971
1972 A note must be made here. Normally one would want to work with
1973 all levels that have higher energy than Elow. However, for CO
1974 this leads to issues because some v=1 levels have lower energy
1975 than some v=0 levels, while they are still sorted going v=0 to
1976 jmax, then v=1 to jmax, ie not sorted by energy. The collision
1977 rates however assume that they are sorted by energy. Meaning
1978 that for some collisional transitions Eup-Elow becomes < 0
1979 because of how the CO spectroscopy is sorted. This is not
1980 necessarily a problem, hence why we assume Eup-Elow must be > 0
1981 in what follows, and force it to be through abs(Eup-Elow).
1982 We assume the collision rate files are sorted properly, thus
1983 retrieve all "higher energy levels" by simply passing the llow
1984 index to the getTI method. This leads to results that are
1985 identical with GASTRoNOoM CO cooling rates.
1986
1987
1988 @param llow: index of the transition lower level.
1989 @type llow: int
1990 @param r: The radial grid in cm
1991 @type r: array
1992 @param T: The temperature in K for which to calculate the cooling
1993 @type T: array
1994
1995 @return: The contribution to the line cooling for a given lower
1996 level i (in ergs * cm^3 / s).
1997 @rtype: float
1998
1999 '''
2000
2001
2002 Elow = self.mol[m].getLEnergy(index=llow,unit='erg')
2003 glow = self.mol[m].getLWeight(index=llow)
2004 poplow = self.pop[m].getInterp(llow)(r)
2005
2006
2007
2008 indices = self.collis[m].getTI(itype='coll_trans',llow=(llow,))
2009
2010
2011
2012
2013 if not indices.size:
2014 return 0.
2015
2016
2017
2018 lups = self.collis[m].getTUpper(index=indices,itype='coll_trans')
2019 Eups = self.mol[m].getLEnergy(lups,unit='erg')
2020 popups = np.array([self.pop[m].getInterp(lup)(r) for lup in lups])
2021 gups = self.mol[m].getLWeight(index=lups)
2022
2023
2024 gups.shape = (gups.size,1)
2025 Eups.shape = (Eups.size,1)
2026
2027
2028
2029 Culs = np.array([self.collis[m].getInterp(i)(T) for i in indices])
2030
2031
2032
2033 expfac = np.exp(np.outer(-1*abs(Elow-Eups),1./(k_b*T)))
2034 Clus = Culs*np.multiply(expfac,gups/glow)
2035
2036
2037
2038 return np.sum((Clus*poplow-Culs*popups)*abs(Eups-Elow),axis=0)
2039
2040
2041
2042 if self.C['lc_{}'.format(m)].has_key(self.i):
2043 return
2044
2045
2046 self.setDensity('gas')
2047
2048
2049 if update_pop: self.updatePop(m)
2050
2051
2052
2053
2054
2055 print "Passing through Clc at iteration %i"%self.i
2056 if self.i > self.ipop[m]:
2057 print "Taking previous Clc for iteration %i"%self.ipop[m]
2058 j = self.ipop[m]
2059 self.C['lc_{}'.format(m)][self.i] = self.C['lc_{}'.format(m)][j]
2060 return
2061
2062 print "Calculating new Clc for iteration %i"%self.i
2063
2064 nh2 = self.gdens.eval(dtype='nh2')
2065 amol = self.abun[m].eval(warn=0)
2066
2067
2068 llows = self.pop[m].getLI()
2069 LCterm = np.empty(shape=(len(self.r),len(llows)))
2070 for i in llows:
2071 LCterm[:,i-1] = calcLevelLC(i,self.r,\
2072 self.T.eval(inner_eps=self.inner_eps,\
2073 warn=not self.inner))
2074 LCtotal = nh2*nh2*amol*np.sum(LCterm,axis=1)
2075
2076
2077 self.C['lc_{}'.format(m)][self.i] = LCtotal
2078
2079
2080
2081 - def plotRateIterations(self,iterations=[],dTsign='C',mechanism='ad',\
2082 scale=1,fn=None,cfg=None,**kwargs):
2083
2084 '''
2085 Plot the heating or cooling rates for several iterations of a given
2086 mechanism and heat exchange sign (cooling or heating).
2087
2088 @keyword iterations: The iteration indices to be plotted. If default,
2089 all iterations are plotted
2090
2091 (default: [])
2092 @type iterations: list
2093 @keyword dTsign: The dT type: heating (H) or cooling (C).
2094
2095 (default: 'C')
2096 @type dTsign: str
2097 @keyword mechanism: The cooling/heating mechanism to be plotted.
2098
2099 (default: 'ad')
2100 @type mechanism: type
2101 @keyword scale: Scale the heating and cooling rates with r^4/1e14cm.
2102
2103 (default: 1)
2104 @type scale: bool
2105 @keyword fn: The filename and path of the plot (without extension). If
2106 default, a filename can be given in a cfg file, and if that
2107 is not the case, the plot is simply shown but not saved.
2108
2109 (default: None)
2110 @type fn: str
2111 @keyword cfg: The filename to the cfg file for this plot with extra
2112 settings.
2113
2114 (default: None)
2115 @type cfg: str
2116
2117 @keyword kwargs: Additional keywords passed to the plotting method.
2118 Overwrites any keys given in the cfg file.
2119
2120 (default: {})
2121 @type kwargs: dict
2122
2123 '''
2124
2125
2126 cfg_dict = Plotting2.readCfg(cfg)
2127 if cfg_dict.has_key('filename'):
2128 fn = cfg_dict.pop('filename')
2129
2130
2131 if not iterations:
2132 iterations = range(1,self.i)
2133
2134
2135 iterations = Data.arrayify(iterations)
2136
2137 dTsign = dTsign[0].upper()
2138 if not fn is None:
2139 fn += '_{}{}'.format(dTsign,mechanism)
2140 fn += '_iter{}-{}'.format(iterations[0],iterations[-1])
2141 ddict['filename'] = fn
2142
2143
2144 pars = {'xlogscale': 1, 'ylogscale': 1, 'xaxis': 'r (cm)',
2145 'extension': 'pdf'}
2146 pars.update(cfg_dict)
2147 pars.update(kwargs)
2148
2149
2150 pmech = mechanism.replace('_','\_')
2151 ddict = {'yaxis': dTsign+pmech+'-rate (ergs s$^{-1}$ cm$^{-3}$)',
2152 'keytags': [str(i) for i in iterations], 'x': [], 'y': []}
2153 if scale:
2154 ddict['yaxis'] += r' $\times$ (r/10$^{14}$ cm)$^4$'
2155
2156
2157 mech_types = {'ad': 'C', 'dg': 'H', 'dt': 'H', 'lc': 'C',
2158 'pe': 'H', 'h2': 'C', 'cr': 'H' }
2159 mtype = mech_types[mechanism[:2]]
2160
2161
2162 Csigns = {'ad': 1, 'dg': -1, 'dt': -1, 'lc': 1,
2163 'pe': -1, 'h2': 1, 'cr': -1 }
2164 sign = Csigns[mechanism[:2]]
2165 if dTsign == 'H':
2166 sign = -1 * sign
2167
2168
2169 for i in iterations:
2170 isel = getattr(self,mtype)[mechanism][i]*sign > 0
2171 ix = self.r[isel]
2172 ddict['x'].append(ix)
2173 iy = getattr(self,mtype)[mechanism][i][isel]*sign
2174 if scale: iy = iy*(ix*1e-14)**4
2175 ddict['y'].append(iy)
2176
2177
2178 if abs(min([min(yi) for yi in ddict['y']])) < 1e-20:
2179 ddict['ymin'] = 1e-20
2180 ddict.update(pars)
2181 fn = Plotting2.plotCols(**ddict)
2182
2183 if not fn is None:
2184 print('Heating and cooling rates plotted at:')
2185 print(fn+'.pdf')
2186
2187
2188
2189 - def plotRates(self,scale=1,fn=None,iteration=None,cfg=None,join=0,**kwargs):
2190
2191 '''
2192 Plot the heating and cooling rates for a single iteration.
2193
2194 @keyword scale: Scale the heating and cooling rates with r^4/1e14cm.
2195
2196 (default: 1)
2197 @type scale: bool
2198 @keyword fn: The filename and path of the plot (without extension). If
2199 default, a filename can be given in a cfg file, and if that
2200 is not the case, the plot is simply shown but not saved.
2201
2202 (default: None)
2203 @type fn: str
2204 @keyword iteration: The iteration for which to plot the rates. Default
2205 is the last iteration.
2206
2207 (default: None)
2208 @type iteration: int
2209 @keyword cfg: The filename to the cfg file for this plot with extra
2210 settings.
2211
2212 (default: None)
2213 @type cfg: str
2214 @keyword join: Join together the plot for heating and cooling terms.
2215
2216 (default: 0)
2217 @type join: bool
2218
2219 @keyword kwargs: Additional keywords passed to the plotting method.
2220 Overwrites any keys given in the cfg file.
2221
2222 (default: {})
2223 @type kwargs: dict
2224
2225 '''
2226
2227
2228 cfg_dict = Plotting2.readCfg(cfg)
2229 if cfg_dict.has_key('filename'):
2230 fn = cfg_dict.pop('filename')
2231
2232
2233 if iteration is None or iteration > self.i: iteration = self.i
2234
2235
2236 if fn and iteration != self.i: fn += '_iter{}'.format(self.i)
2237
2238
2239 lts = {'ad': '-.g', 'dg': '-.b', 'dt': '--r',
2240 'pe': '--g', 'h2': '-.k', 'cr': '-k' }
2241 mkeys = [k for k in self.C.keys() if k[:2] == 'lc']
2242 lts_extra = ['-r', '--b', '--k', '-m', '--m', '-y', '--y', '-g']
2243 for mk in mkeys:
2244 lts[mk] = lts_extra.pop(0)
2245
2246
2247 pars = {'xlogscale': 1, 'ylogscale': 1, 'xaxis': 'r (cm)',
2248 'extension': 'pdf'}
2249 pars.update(cfg_dict)
2250 pars.update(kwargs)
2251
2252
2253 fns = []
2254 for sign in ['H','C']:
2255 ddict = {'yaxis': sign+'-rate (ergs s$^{-1}$ cm$^{-3}$)',
2256 'keytags': [], 'x': [], 'y': [], 'line_types': []}
2257
2258 if scale:
2259 ddict['yaxis'] += r' $\times$ (r/10$^{14}$ cm)$^4$'
2260
2261
2262 if fn:
2263 ddict['filename'] = '{}_{}'.format(fn,sign)
2264
2265
2266 for term in sorted(getattr(self,sign).keys()):
2267 sterm = '$_\\mathrm{{{s}}}$'.format(s=term.replace('_','\_'))
2268 ddict['keytags'].append(sign+sterm)
2269 isel = getattr(self,sign)[term][iteration] > 0
2270 ix = self.r[isel]
2271 ddict['x'].append(ix)
2272 iy = getattr(self,sign)[term][iteration][isel]
2273 if scale: iy = iy*(ix*1e-14)**4
2274 ddict['y'].append(iy)
2275 ddict['line_types'].append(lts[term])
2276
2277
2278 negsign = 'C' if sign == 'H' else 'H'
2279 for term in sorted(getattr(self,negsign).keys()):
2280 isel = getattr(self,negsign)[term][iteration] < 0
2281 if not isel.any(): continue
2282 sterm = '$_\\mathrm{{{s}}}$'.format(s=term.replace('_','\_'))
2283 ddict['keytags'].append(sign+sterm)
2284 ix = self.r[isel]
2285 ddict['x'].append(ix)
2286 iy = -1.*getattr(self,negsign)[term][iteration][isel]
2287 if scale: iy = iy*(ix*1e-14)**4
2288 ddict['y'].append(iy)
2289 ddict['line_types'].append(lts[term])
2290
2291
2292 if abs(min([min(yi) for yi in ddict['y'] if yi.size])) < 1e-20:
2293 ddict['ymin'] = 1e-20
2294 ddict.update(pars)
2295 ifn = Plotting2.plotCols(**ddict)
2296 fns.append(ifn)
2297
2298
2299 if join and fns[-1] and fns[-1][-3:] == 'pdf':
2300 DataIO.joinPdf(old=fns,new=fn+'.pdf',del_old=1)
2301 print('Heating and cooling rates plotted at:')
2302 print(fn+'.pdf')
2303 elif fns[-1]:
2304 print('Heating and cooling rates plotted at:')
2305 print('\n'.join(fns))
2306
2307
2308
2309 - def plotT(self,fn=None,iterations=[],cfg=None,**kwargs):
2310
2311 '''
2312 Plot the temperature profiles of the different iterations.
2313
2314 @keyword fn: The filename and path of the plot (without extension). If
2315 default, a filename can be given in a cfg file, and if that
2316 is not the case, the plot is simply shown but not saved.
2317
2318 (default: None)
2319 @type fn: str
2320 @keyword iterations: The iteration indices to be plotted. If default,
2321 all iterations are plotted. The first and last
2322 iterations are always plotted.
2323
2324 (default: [])
2325 @type iterations: list
2326 @keyword cfg: The filename to the cfg file for this plot with extra
2327 settings.
2328
2329 (default: None)
2330 @type cfg: str
2331 @keyword kwargs: Additional keywords passed to the plotting method.
2332 Overwrites any keys given in the cfg file.
2333
2334 (default: {})
2335 @type kwargs: dict
2336
2337 '''
2338
2339
2340 cfg_dict = Plotting2.readCfg(cfg)
2341 if cfg_dict.has_key('filename'):
2342 fn = cfg_dict.pop('filename')
2343
2344
2345 if iterations:
2346 iterations = [i for i in iterations if i < self.i and i > 0]
2347 elif not iterations:
2348 iterations = range(1,self.i)
2349
2350
2351 iterations = [0] + iterations + [self.i]
2352
2353
2354 pars = {'xlogscale': 1, 'ylogscale': 1, 'yaxis': 'T (K)',
2355 'xaxis': 'r (cm)', 'filename': fn,
2356 'keytags': [r'T$_\mathrm{d}$']
2357 +['Iteration {}'.format(i) for i in iterations],
2358 'extension': 'pdf'}
2359 pars.update(cfg_dict)
2360 pars.update(kwargs)
2361
2362
2363 y = [self.Td.eval()]+[self.T_iter[i].eval(inner_eps=self.inner_eps,\
2364 warn=not self.inner)
2365 for i in iterations]
2366 pfn = Plotting2.plotCols(x=self.r,y=y,**pars)
2367
2368
2369 if pfn:
2370 print('The temperature profiles are plotted at:')
2371 print(pfn)
2372
2373
2374
2375 - def plotHCTerm(self,fn=None,iterations=[],dTsign='C',cfg=None,**kwargs):
2376
2377 '''
2378 Plot the total heating and cooling term (excluding adiabatic cooling)
2379 calculated by calcT before the differential equation is solved.
2380
2381 This includes the density factor that enters, and so is essentially
2382 (H - C)/rho. The velocity does not enter here, since that is calculated
2383 explicitly in dTdr. Hence the y-axis units K/s.
2384
2385 @keyword fn: The filename and path of the plot (without extension). If
2386 default, a filename can be given in a cfg file, and if that
2387 is not the case, the plot is simply shown but not saved.
2388
2389 (default: None)
2390 @type fn: str
2391 @keyword iterations: The iteration indices to be plotted. If default,
2392 all iterations are plotted. The first and last
2393 iteration is always plotted.
2394
2395 (default: [])
2396 @type iterations: list
2397 @keyword dTsign: The dT type: heating (H) or cooling (C). Either the
2398 positive (C) or the negative (H) sum is plotted.
2399
2400 (default: 'C')
2401 @type dTsign: str
2402 @keyword cfg: The filename to the cfg file for this plot with extra
2403 settings.
2404
2405 (default: None)
2406 @type cfg: str
2407 @keyword kwargs: Additional keywords passed to the plotting method.
2408 Overwrites any keys given in the cfg file.
2409
2410 (default: {})
2411 @type kwargs: dict
2412
2413 '''
2414
2415
2416 cfg_dict = Plotting2.readCfg(cfg)
2417 if cfg_dict.has_key('filename'):
2418 fn = cfg_dict.pop('filename')
2419
2420
2421 if iterations:
2422 iterations = [i for i in iterations if i < self.i and i > 1]
2423 elif not iterations:
2424 iterations = range(2,self.i)
2425
2426
2427 iterations = [1] + iterations + [self.i]
2428
2429
2430 pars = {'xlogscale': 1, 'ylogscale': 1,
2431 'yaxis': 'H - C (K s$^{-1}$)',
2432 'xaxis': 'r (cm)', 'filename': fn,
2433 'keytags': ['Iteration {}'.format(i) for i in iterations],
2434 'extension': 'pdf'}
2435 pars.update(cfg_dict)
2436 pars.update(kwargs)
2437
2438
2439 dTsign = -1 if dTsign.upper() == 'H' else 1
2440 y = [self.rates[i]*dTsign for i in iterations]
2441 pfn = Plotting2.plotCols(x=self.r,y=y,**pars)
2442
2443
2444 if pfn:
2445 print('The H-C terms are plotted at:')
2446 print(pfn)
2447
2448
2449
2450 - def plotTexc(self,m,indices=None,llow=None,lup=None,fn=None,cfg=None,\
2451 **kwargs):
2452
2453 '''
2454 Plot the excitation temperature as a function of impact parameter for a
2455 selection of radiative transitions, given by either the transition
2456 indices or the upper and/or lower level indices.
2457
2458 Retrieves the excitation temperature from self.Texc. They are
2459 appropriate for the level populations used by the current iteration, and
2460 are updated when the level populations are updated.
2461
2462 At this time, plotting Texc for other iterations is not possible.
2463
2464 @param m: The molecule tag
2465 @type m: str
2466
2467 @keyword indices: The transition indices. Can be indirectly given
2468 through upper and/or lower level indices. If default
2469 and no llow/lup are given, all Texc are returned for
2470 this molecule.
2471
2472 (default: None)
2473 @type indices: list/array
2474 @keyword lup: The upper level indices used to extract the transition
2475 indices. If this or llow are defined, the keyword indices
2476 is overwritten.
2477
2478 (default: None)
2479 @type lup: list/array
2480 @keyword llow: The upper level indices used to extract the transition
2481 indices. If this or lup are defined, the keyword indices
2482 is overwritten.
2483
2484 (default: None)
2485 @type llow: list/array
2486 @keyword fn: The filename and path of the plot (without extension). If
2487 default, a filename can be given in a cfg file, and if that
2488 is not the case, the plot is simply shown but not saved.
2489
2490 (default: None)
2491 @type fn: str
2492 @keyword cfg: The filename to the cfg file for this plot with extra
2493 settings.
2494
2495 (default: None)
2496 @type cfg: str
2497 @keyword kwargs: Additional keywords passed to the plotting method.
2498 Overwrites any keys given in the cfg file.
2499
2500 (default: {})
2501 @type kwargs: dict
2502
2503
2504
2505 '''
2506
2507
2508 cfg_dict = Plotting2.readCfg(cfg)
2509 if cfg_dict.has_key('filename'):
2510 fn = cfg_dict.pop('filename')
2511
2512
2513 if iteration is None or iteration > self.i: iteration = self.i
2514
2515
2516 if fn and iteration != self.i: fn += '_iter{}'.format(self.i)
2517
2518
2519 if not (lup is None and llow is None):
2520 if not lup is None: lup = Data.arrayify(lup)
2521 if not llow is None: llow = Data.arrayify(llow)
2522 indices = self.mol[m].getTI(lup=lup,llow=llow)
2523
2524
2525
2526 lup = self.mol[m].getTUpper(indices)
2527 llow = self.mol[m].getTLower(indices)
2528
2529
2530 p = self.pop[m].getP()
2531
2532
2533 T = self.T.eval(p,inner_eps=self.inner_eps,warn=not self.inner)
2534 Texc = self.getTexc(m=m,indices=indices)
2535
2536
2537 pars = {'xlogscale': 1, 'ylogscale': 0,
2538 'yaxis': 'T$_\mathrm{exc}$ (K)',
2539 'xaxis': 'p (cm)', 'filename': fn,
2540 'extension': 'pdf',
2541 'keytags': ['T$_\mathrm{kin}$']+
2542 ['llup = {}, llow = {}'.format(nui,nli)
2543 for nui,nli in zip(lup,llow)]}
2544 pars.update(cfg_dict)
2545 pars.update(kwargs)
2546
2547
2548 y = [T]+[Texc[i,:] for i in range(Texc.shape[0])]
2549 pfn = Plotting2.plotCols(x=p,y=y,**pars)
2550