source: JEM-EUSO/esaf_cc_at_lal/packages/common/atmosphere/src/LowtranAerosol.cc @ 114

Last change on this file since 114 was 114, checked in by moretto, 11 years ago

actual version of ESAF at CCin2p3

File size: 36.3 KB
Line 
1// $Id: LowtranAerosol.cc 2922 2011-06-12 14:21:23Z mabl $
2// Author: Sylvain Moreggia   2006/04/24
3
4/*****************************************************************************
5 * ESAF: Euso Simulation and Analysis Framework                              *
6 *                                                                           *
7 *  Id: LowtranAerosol                                                           *
8 *  Package: <packagename>                                                   *
9 *  Coordinator: <coordinator>                                               *
10 *                                                                           *
11 *****************************************************************************/
12
13//_____________________________________________________________________________
14//
15// LowtranAerosol
16//
17// <extensive class description>
18//
19//   Config file parameters
20//   ======================
21//
22//   WARNING : ground altitude is not handled internally. It must be checked in the other parts of the code, from where herebelow methods are called
23//
24
25#include "LowtranAerosol.hh"
26#include "EarthVector.hh"
27#include "Config.hh"
28#include "EConst.hh"
29#include "EsafRandom.hh"
30#include "NumbersFileParser.hh"
31#include "ConfigFileParser.hh"
32
33using EConst::EarthRadius;
34using namespace sou;
35using namespace TMath;
36
37ClassImp(LowtranAerosol)
38
39//_____________________________________________________________________________
40LowtranAerosol::LowtranAerosol(string name) : Aerosol(name) {
41    //
42    // Constructor
43    //
44
45    Configure();
46    Build();
47}
48
49//_____________________________________________________________________________
50LowtranAerosol::~LowtranAerosol() {
51    //
52    // Destructor
53    //
54}
55
56
57//______________________________________________________________________________
58void LowtranAerosol::Configure() {
59    //
60    // read config files
61    //
62   
63    // init
64    fWlrange[0] = 200*nm;
65    fWlrange[1] = 300*nm;
66    fWlrange[2] = 337.1*nm;
67    fWlrange[3] = 550*nm;
68    fWlrange[4] = 694.3*nm;
69    fWlrange[5] = 1060*nm;
70    fWlrange[6] = 1536*nm;
71   
72    // aerosol model
73    fType = Conf()->GetStr("LowtranAerosol.fType");
74}
75
76//______________________________________________________________________________
77void LowtranAerosol::Build() {
78    //
79    // build the object according to config files
80    // Properties at 70% humidity are tabulated : extinction, absorption, DHG coeff. for phase function
81    // DHG coeff. come from fits on tabulated lowtran phase functions
82    // All are wavelength dependent (7 values tabulated)
83    // For Altitude profile, 3 values tabulated at 0, 1 and 2km
84    //
85           
86    // build aerosol models
87    if(fType == "rural23") {
88        fSz[0] = 0.158/km;
89        fSz[1] = 0.0991/km;
90        fSz[2] = 0.0621/km;
91       
92        // values for 70% rel. humidity
93        fKwl_ext[0] =  2.09544;  // 200 nm
94        fKwl_ext[1] = 1.74165;  // 300 nm
95        fKwl_ext[2] = 1.59981;  // 337.1 nm
96        fKwl_ext[3] = 1.;       // 550 nm
97        fKwl_ext[4] = 0.75316;  // 694.3 nm
98        fKwl_ext[5] = 0.42171;  // 1060 nm
99        fKwl_ext[6] = 0.24323;  // 1536 nm
100        //DEBUG : to test w.r.t lowtran, 0% humidity values
101        //fKwl_ext[0] = 2.09291;  // 200 nm
102        //fKwl_ext[1] = 1.74582;  // 300 nm
103        //fKwl_ext[2] = 1.60500;  // 337.1 nm
104        //fKwl_ext[3] = 1.;       // 550 nm
105        //fKwl_ext[4] = 0.75203;  // 694.3 nm
106        //fKwl_ext[5] = 0.41943;  // 1060 nm
107        //fKwl_ext[6] = 0.24070;  // 1536 nm
108       
109        // values for 70% rel. humidity
110        fKwl_abs[0] = 0.62968;  // 200 nm
111        fKwl_abs[1] = 0.10816;  // 300 nm
112        fKwl_abs[2] = 0.07671;  // 337.1 nm
113        fKwl_abs[3] = 0.05380;  // 550 nm
114        fKwl_abs[4] = 0.04684;  // 694.3 nm
115        fKwl_abs[5] = 0.05335;  // 1060 nm
116        fKwl_abs[6] = 0.04614;  // 1536 nm
117       
118        // values for 70% rel. humidity
119        fG1[0] = 0.7632;
120        fG1[1] = 0.6928;
121        fG1[2] = 0.6885;  // not available in lowtran, linear interpolation used here
122        fG1[3] = 0.6638;
123        fG1[4] = 0.6638;
124        fG1[5] = 0.6638;
125        fG1[6] = 0.6590;
126        fG2[0] = -0.6488;
127        fG2[1] = -0.6202;
128        fG2[2] = -0.6076;  // not available in lowtran, linear interpolation used here
129        fG2[3] = -0.5351;
130        fG2[4] = -0.5351;
131        fG2[5] = -0.5351;
132        fG2[6] = -0.3744;
133        fF[0] = 0.7185;
134        fF[1] = 0.985;
135        fF[2] = 0.9837;  // not available in lowtran, linear interpolation used here
136        fF[3] = 0.9765;
137        fF[4] = 0.9765;
138        fF[5] = 0.9765;
139        fF[6] = 0.9622;
140    }
141    else if(fType == "rural5") {
142        fSz[0] = 0.77/km;
143        fSz[1] = 0.77/km;
144        fSz[2] = 0.0621/km;
145       
146        // values for 70% rel. humidity
147        fKwl_ext[0] =  2.09544;  // 200 nm
148        fKwl_ext[1] = 1.74165;  // 300 nm
149        fKwl_ext[2] = 1.59981;  // 337.1 nm
150        fKwl_ext[3] = 1.;       // 550 nm
151        fKwl_ext[4] = 0.75316;  // 694.3 nm
152        fKwl_ext[5] = 0.42171;  // 1060 nm
153        fKwl_ext[6] = 0.24323;  // 1536 nm
154        //DEBUG : to test w.r.t lowtran, 0% humidity values
155        //fKwl_ext[0] = 2.09291;  // 200 nm
156        //fKwl_ext[1] = 1.74582;  // 300 nm
157        //fKwl_ext[2] = 1.60500;  // 337.1 nm
158        //fKwl_ext[3] = 1.;       // 550 nm
159        //fKwl_ext[4] = 0.75203;  // 694.3 nm
160        //fKwl_ext[5] = 0.41943;  // 1060 nm
161        //fKwl_ext[6] = 0.24070;  // 1536 nm
162       
163        // values for 70% rel. humidity
164        fKwl_abs[0] = 0.62968;  // 200 nm
165        fKwl_abs[1] = 0.10816;  // 300 nm
166        fKwl_abs[2] = 0.07671;  // 337.1 nm
167        fKwl_abs[3] = 0.05380;  // 550 nm
168        fKwl_abs[4] = 0.04684;  // 694.3 nm
169        fKwl_abs[5] = 0.05335;  // 1060 nm
170        fKwl_abs[6] = 0.04614;  // 1536 nm
171       
172        // values for 70% rel. humidity
173        fG1[0] = 0.7632;
174        fG1[1] = 0.6928;
175        fG1[2] = 0.6885;  // not available in lowtran, linear interpolation used here
176        fG1[3] = 0.6638;
177        fG1[4] = 0.6638;
178        fG1[5] = 0.6638;
179        fG1[6] = 0.6590;
180        fG2[0] = -0.6488;
181        fG2[1] = -0.6202;
182        fG2[2] = -0.6076;  // not available in lowtran, linear interpolation used here
183        fG2[3] = -0.5351;
184        fG2[4] = -0.5351;
185        fG2[5] = -0.5351;
186        fG2[6] = -0.3744;
187        fF[0] = 0.7185;
188        fF[1] = 0.985;
189        fF[2] = 0.9837;  // not available in lowtran, linear interpolation used here
190        fF[3] = 0.9765;
191        fF[4] = 0.9765;
192        fF[5] = 0.9765;
193        fF[6] = 0.9622;
194    }
195    else if(fType == "urban5") {
196        fSz[0] = 0.77/km;
197        fSz[1] = 0.77/km;
198        fSz[2] = 0.0621/km;
199       
200        // values for 70% rel. humidity
201        fKwl_ext[0] = 1.95582;  // 200 nm
202        fKwl_ext[1] = 1.64994;  // 300 nm
203        fKwl_ext[2] = 1.53070;  // 337.1 nm
204        fKwl_ext[3] = 1.;       // 550 nm
205        fKwl_ext[4] = 0.77614;  // 694.3 nm
206        fKwl_ext[5] = 0.46639;  // 1060 nm
207        fKwl_ext[6] = 0.29487;  // 1536 nm
208        //DEBUG : to test w.r.t lowtran, 0% humidity values
209        //fKwl_ext[0] = 1.88816;  // 200 nm
210        //fKwl_ext[1] = 1.63316;  // 300 nm
211        //fKwl_ext[2] = 1.51867;  // 337.1 nm
212        //fKwl_ext[3] = 1.;       // 550 nm
213        //fKwl_ext[4] = 0.77785;  // 694.3 nm
214        //fKwl_ext[5] = 0.47095;  // 1060 nm
215        //fKwl_ext[6] = 0.30006;  // 1536 nm
216       
217        // values for 70% rel. humidity
218        fKwl_abs[0] = 0.69032;  // 200 nm
219        fKwl_abs[1] = 0.49367;  // 300 nm
220        fKwl_abs[2] = 0.45165;  // 337.1 nm
221        fKwl_abs[3] = 0.29741;  // 550 nm
222        fKwl_abs[4] = 0.24070;  // 694.3 nm
223        fKwl_abs[5] = 0.17399;  // 1060 nm
224        fKwl_abs[6] = 0.13146;  // 1536 nm
225       
226//TOFIX : there is a pb in the tabulated phase function coeff. for urban5 model
227// so far, they have been replaced by rural5 coeff.
228        fG1[0] = 0.7632;
229        fG1[1] = 0.6928;
230        fG1[2] = 0.6885;  // not available in lowtran, linear interpolation used here
231        fG1[3] = 0.6638;
232        fG1[4] = 0.6638;
233        fG1[5] = 0.6638;
234        fG1[6] = 0.6590;
235        fG2[0] = -0.6488;
236        fG2[1] = -0.6202;
237        fG2[2] = -0.6076;  // not available in lowtran, linear interpolation used here
238        fG2[3] = -0.5351;
239        fG2[4] = -0.5351;
240        fG2[5] = -0.5351;
241        fG2[6] = -0.3744;
242        fF[0] = 0.7185;
243        fF[1] = 0.985;
244        fF[2] = 0.9837;  // not available in lowtran, linear interpolation used here
245        fF[3] = 0.9765;
246        fF[4] = 0.9765;
247        fF[5] = 0.9765;
248        fF[6] = 0.9622;
249
250/*
251        // values for 70% rel. humidity (urban5 coeff)
252        fG1[0] = 0.7906;
253        fG1[1] = 0.7632;
254        fG1[2] = 0.7478;  // not available in lowtran, linear interpolation used here
255        fG1[3] = 0.6592;
256        fG1[4] = 0.6592;
257        fG1[5] = 0.6536;
258        fG1[6] = 0.6590;
259        fG2[0] = -0.0;
260        fG2[1] = -0.6488;
261        fG2[2] = -0.6797;  // not available in lowtran, linear interpolation used here
262        fG2[3] = -0.8568;
263        fG2[4] = -0.8568;
264        fG2[5] = 0.5032;
265        fG2[6] = -0.3744;
266        fF[0] = 1.;
267        fF[1] = 0.7185;
268        fF[2] = 0.7602;  // not available in lowtran, linear interpolation used here
269        fF[3] = 0.9998;
270        fF[4] = 0.9998;
271        fF[5] = 0.7134;
272        fF[6] = 0.9622;
273*/
274    }
275    else if(fType == "maritime23") {
276        fSz[0] = 0.158/km;
277        fSz[1] = 0.0991/km;
278        fSz[2] = 0.0621/km;
279       
280        // values for 70% rel. humidity
281        fKwl_ext[0] = 1.36924;  // 200 nm
282        fKwl_ext[1] = 1.25443;  // 300 nm
283        fKwl_ext[2] = 1.20835;  // 337.1 nm
284        fKwl_ext[3] = 1.;       // 550 nm
285        fKwl_ext[4] = 0.91367;  // 694.3 nm
286        fKwl_ext[5] = 0.77089;  // 1060 nm
287        fKwl_ext[6] = 0.64987;  // 1536 nm
288        //DEBUG : to test w.r.t lowtran, 0% humidity values
289        //fKwl_ext[0] = 1.47576;  // 200 nm
290        //fKwl_ext[1] = 1.32614;  // 300 nm
291        //fKwl_ext[2] = 1.26171;  // 337.1 nm
292        //fKwl_ext[3] = 1.;       // 550 nm
293        //fKwl_ext[4] = 0.88133;  // 694.3 nm
294        //fKwl_ext[5] = 0.70297;  // 1060 nm
295        //fKwl_ext[6] = 0.56487;  // 1536 nm
296       
297        // values for 70% rel. humidity
298        fKwl_abs[0] = 0.23367;  // 200 nm
299        fKwl_abs[1] = 0.03127;  // 300 nm
300        fKwl_abs[2] = 0.02070;  // 337.1 nm
301        fKwl_abs[3] = 0.01297;  // 550 nm
302        fKwl_abs[4] = 0.01063;  // 694.3 nm
303        fKwl_abs[5] = 0.01285;  // 1060 nm
304        fKwl_abs[6] = 0.01190;  // 1536 nm
305       
306        // values for 70% rel. humidity
307        fG1[0] = 0.8;
308        fG1[1] = 0.75;
309        fG1[2] = 0.7458;  // not available in lowtran, linear interpolation used here
310        fG1[3] = 0.7214;
311        fG1[4] = 0.75;
312        fG1[5] = 0.75;
313        fG1[6] = 0.7445;
314        fG2[0] = -0.5396;
315        fG2[1] = -0.75;
316        fG2[2] = -0.7302;  // not available in lowtran, linear interpolation used here
317        fG2[3] = -0.6163;
318        fG2[4] = -0.5840;
319        fG2[5] = -0.5840;
320        fG2[6] = -0.6509;
321        fF[0] = 0.9492;
322        fF[1] = 0.9662;
323        fF[2] = 0.9644;  // not available in lowtran, linear interpolation used here
324        fF[3] = 0.9539;
325        fF[4] = 0.9643;
326        fF[5] = 0.9643;
327        fF[6] = 0.9926;
328    }
329    else Msg(EsafMsg::Panic) << "<Build> Wrong type of LowtranAerosol model = "<<fType << MsgDispatch;
330   
331    Msg(EsafMsg::Info) << "*** LowtranAerosol built ***"<< MsgDispatch;
332}
333
334//______________________________________________________________________________
335Double_t LowtranAerosol::Kwl(Double_t wl, Bool_t abs) const {
336    //
337    // interpolate fKwl coeff.
338    // if abs=kTRUE, return value for absorption, else return value for extinction
339    // Linear interpolation as Lowtran does
340    //
341   
342    Double_t rtn(0.), k1(0.), k2(0.), wl1(0.), wl2(0.);
343       
344    CheckWlRange(wl);
345   
346    // extinction coeff.
347    if(!abs) {
348        for(Int_t i=0; i < 6; i++) {
349            if(wl >= fWlrange[i] && wl <= fWlrange[i+1]) {
350                k1 = fKwl_ext[i];
351                k2 = fKwl_ext[i+1];
352                wl1 = fWlrange[i];
353                wl2 = fWlrange[i+1];
354                break;
355            }
356        }
357    }
358    // absorption coeff.
359    else {
360        for(Int_t i=0; i < 6; i++) {
361            if(wl >= fWlrange[i] && wl <= fWlrange[i+1]) {
362                k1 = fKwl_abs[i];
363                k2 = fKwl_abs[i+1];
364                wl1 = fWlrange[i];
365                wl2 = fWlrange[i+1];
366                break;
367            }
368        }
369    }
370   
371    rtn = (k2 - k1) / (wl2 - wl1) * (wl - wl2) + k2;
372   
373    return rtn;
374}
375
376//______________________________________________________________________________
377Bool_t LowtranAerosol::IsInAerosol(const EarthVector& pos) const {
378    //
379    // true if position is within aerosol, false otherwise
380    //
381       
382    return IsInAerosolZone(pos,ALL);
383}
384
385//______________________________________________________________________________
386Bool_t LowtranAerosol::IsInAerosolZone(const EarthVector& pos, AerZone zone) const {
387    //
388    // true if position is within aerosol, false otherwise
389    // deals with aerosol zones
390    //
391   
392    // init
393    Bool_t rtn = false;
394    Double_t alt_low = 0*km;
395    Double_t alt_high = 2*km;
396    if(pos.IsUnderSeaLevel()) return false;
397   
398    // depends on aerosol zone
399    if(zone == BOUND1) alt_high = 1*km;
400    else if(zone == BOUND2) alt_low = 1*km;
401    alt_low  -= kAltitudeTolerance;
402    alt_high += kAltitudeTolerance;
403    if( (pos.Zv() < alt_high) && (pos.Zv() > alt_low ) ) rtn = true;
404   
405    return rtn;
406}
407
408//______________________________________________________________________________
409Bool_t LowtranAerosol::CheckWlRange(Double_t wl) const {
410    //
411    // true if wl within [200,1536]nm
412    //
413   
414    // init
415    Bool_t rtn = kTRUE;
416    if(wl < 200*nm) {
417        wl = 200*nm;
418        rtn = kFALSE;
419    }
420    else if(wl > 1536*nm) {
421        wl = 1536*nm;
422        rtn = kFALSE;
423    }
424    if(!rtn) {
425        Msg(EsafMsg::Warning) << "<CheckWlRange> Required wavelength is out of range [200,1536nm] : wl = "<<wl << MsgDispatch;
426        Msg(EsafMsg::Warning) << "---> thus set to = "<<wl << MsgDispatch;
427    }
428   
429    return rtn;
430}
431
432//______________________________________________________________________________
433Double_t LowtranAerosol::Trans(const EarthVector& startpos,const EarthVector& finalposi,Double_t wl, Double_t& scat) const {
434    //
435    // Returns the total transmission along the track defined by startpos and finalpos
436    // Aerosol zones handled
437    // Altitude profile is exponential, excepted for low visibility where profile is uniform between [0-1km]
438    // Local earth-sphericity not taken into account for transmission calculation, to fasten simulation (but used for geometry (impact..))
439    // Negligeable anyway as boundary layers are pretty thin (< 2km)
440    //
441   
442    // init
443    EarthVector exit(0.,0.,0.), exit1(0.,0.,0.), enter(0.,0.,0.), enter1(0.,0.,0.), direc(0.,0.,0.);
444    EarthVector finalpos(finalposi);
445    Double_t exponext(0.), exponscat(0.), mag(0.), magmiddle(0.);
446    Double_t alt1(0.), alt2(0.), altmiddle(0.), h0(0.);
447    direc = (finalpos - startpos).Unit();
448    if(direc.Mag() == 0) return 1.;
449    Int_t split = -1;
450
451    // 1. check if startpos is a valid position
452    if(startpos.IsUnderSeaLevel()) {
453        Msg(EsafMsg::Warning) << "<Trans> Starting position is underground, zero returned" << MsgDispatch;
454        return 0.;
455    }
456   
457    // 2. get entering position
458    enter = GetImpact(startpos,direc);
459    // if no aerosol along the track
460    if(enter.Z() == HUGE) {
461        scat = 1.;
462        return 1.;
463    }
464    alt1 = enter.Zv();
465    // 2.1. get outgoing position for AerZone=ALL
466    exit = GetZoneImpact(startpos,direc,ALL,"outgoing");
467    if(exit.Z() == HUGE) Msg(EsafMsg::Warning) << "if enter exists, exit should as well" << MsgDispatch;
468    alt2 = exit.Zv();
469   
470   
471    // 3. check if finalpos is a valid position
472    if(finalpos.IsUnderSeaLevel()) {
473        Msg(EsafMsg::Warning) << "<Trans> Final position is underground, set to exit position" << MsgDispatch;
474        finalpos = exit;
475    }
476   
477    // 4. look if needs to split aerosol layer in two parts (profile is not exponential in [0-1km] for low visibility)
478    if(fType == "urban5" || fType == "rural5") {
479        if(startpos.Zv() >= 1*km && finalpos.Zv() <= 1*km) split = 1;       // finalpos is inside BOUND1
480        else if(finalpos.Zv() >= 1*km && startpos.Zv() <= 1*km) split = 2;  // startpos is inside BOUND1
481        else if(finalpos.Zv() <= 1*km && startpos.Zv() <= 1*km) split = 3;  // both pos are inside BOUND1
482        else split = 0;                                                     // both positions are outside BOUND1
483    }
484
485   
486    // 5. get wl dependent factor
487    Double_t kwlext = Kwl(wl);
488    Double_t kwlabs = Kwl(wl,kTRUE);
489
490   
491    // 6. define path length and related positions
492    mag = (exit - enter).Mag();
493    if(mag > (finalpos - enter).Mag()) {
494        exit = finalpos;
495        mag = (exit - enter).Mag();
496        alt2 = exit.Zv();
497    }
498   
499    // 7. if needed, get intermediate enter/exit position on top of AerZone=BOUND1
500    if(split > 0) {
501        if(split == 1) {
502            enter1 = GetZoneImpact(startpos,direc,BOUND1);
503            if(enter1.Z() == HUGE) Msg(EsafMsg::Warning) << "<Trans> In this case (split==1), enter position should exist" << MsgDispatch;
504            altmiddle = enter1.Zv();
505            magmiddle = (enter1 - enter).Mag();
506        }
507        else if(split == 2) {
508            exit1 = GetZoneImpact(startpos,direc,BOUND1,"outgoing");
509            if(exit1.Z() == HUGE) Msg(EsafMsg::Warning) << "<Trans> In this case (split==2), exit position should exist" << MsgDispatch;
510            altmiddle = exit1.Zv();
511            magmiddle = (exit1 - enter).Mag();
512        }
513    }
514   
515   
516    // 8. find other parameters for integration : scale height, local zenith angle
517    h0 = 2.14378*km;                // for high visibility (>= 23km)
518    if(split >= 0) h0 = 0.3972*km;  // for visibility = 5km
519    Double_t angle;
520    EarthVector temp(startpos);
521    temp(2) += EarthRadius();     // pos expressed in earth-centered frame -> gives local vertical direction (expressed in MES frame)
522    angle = direc.Angle(temp);    // angle between dir and local vertical
523   
524   
525    // 9.  integrate BETAext*dl along the track
526        // 9.1. if horizontal track
527    if(fabs(angle - PiOver2()) < kTolerance) {
528        if(split == 3) {
529            exponscat = fSz[0] * (kwlext - kwlabs) * mag;
530            exponext  = fSz[0] * kwlext * mag;
531        }
532        else { // should occur rarely
533            exponscat = fSz[0] * (kwlext - kwlabs) * mag * Exp(- 0.5*(alt1 + alt2)/h0);
534            exponext  = fSz[0] * kwlext * mag * Exp(- 0.5*(alt1 + alt2)/h0);
535        }
536    }
537        // 9.2. else integrate exponential
538    else {
539        if(angle > PiOver2()) {
540            EarthVector dirinvert = -direc;
541            angle = dirinvert.Angle(temp);
542        }
543        if(split == 0) {
544            exponscat  = fSz[1] * (kwlext - kwlabs) * h0 / Cos(angle) * Exp(1*km/h0) * fabs( Exp(-alt1/h0) - Exp(-alt2/h0) );
545            exponext   = fSz[1] * kwlext * h0 / Cos(angle) * Exp(1*km/h0) * fabs( Exp(-alt1/h0) - Exp(-alt2/h0) );
546        }
547        else if(split == 1) {
548            exponscat  = fSz[1] * (kwlext - kwlabs) * h0 / Cos(angle) * Exp(1*km/h0) * fabs( Exp(-alt1/h0) - Exp(-altmiddle/h0) );
549            exponext   = fSz[1] * kwlext * h0 / Cos(angle) * Exp(1*km/h0) * fabs( Exp(-alt1/h0) - Exp(-altmiddle/h0) );
550            exponscat += fSz[0] * (kwlext - kwlabs) * (mag - magmiddle);
551            exponext  += fSz[0] * kwlext * (mag - magmiddle);
552        }
553        else if(split == 2) {
554            exponscat  = fSz[0] * (kwlext - kwlabs) * magmiddle;
555            exponext   = fSz[0] * kwlext * magmiddle;
556            exponscat += fSz[1] * (kwlext - kwlabs) * h0 / Cos(angle) * Exp(1*km/h0) * fabs( Exp(-altmiddle/h0) - Exp(-alt2/h0) );
557            exponext  += fSz[1] * kwlext * h0 / Cos(angle) * Exp(1*km/h0) * fabs( Exp(-altmiddle/h0) - Exp(-alt2/h0) );
558        }
559        // for low visibility and both positions inside BOUND1 : aerosol density is constant
560        else if(split == 3) {
561            exponscat = fSz[0] * (kwlext - kwlabs) * mag;
562            exponext  = fSz[0] * kwlext * mag;
563        }
564        else {
565            exponscat = fSz[0] * (kwlext - kwlabs) * h0 / Cos(angle) * fabs( Exp(-alt1/h0) - Exp(-alt2/h0) );
566            exponext  = fSz[0] * kwlext * h0 / Cos(angle) * fabs( Exp(-alt1/h0) - Exp(-alt2/h0) );
567        }
568    }
569   
570    scat = Exp(-exponscat);
571    return Exp(-exponext);
572}
573
574//______________________________________________________________________________
575Bool_t LowtranAerosol::RandomScatPos(const EarthVector& posi,const EarthVector& dir,Double_t wl,EarthVector& scatpos) const {
576    //
577    // return kFALSE if absorpion, kTRUE if scattering
578    // get a scattering position along a given track
579    // handle aerosols zones
580    // Altitude profile is exponential, excepted for low visibility where profile is uniform between [0-1km]
581    // Local earth-sphericity not taken into account for transmission calculation, to fasten simulation (but used for geometry (impact..))
582    // Negligeable anyway as boundary layers are pretty thin (< 2km)
583    //
584    // - if scatpos is outside aerosol layer   :   (0,0,HUGE)  returned
585    // - if scatpos is underground             :   (0,0,HUGE)  returned
586    //
587
588
589    // init
590    Bool_t rtn(kFALSE);
591    TRandom* rndm = EsafRandom::Get();
592    EarthVector exit1(0.,0.,0.), enter(0.,0.,0.), enter1(0.,0.,0.), outgopos(0.,0.,0.);
593    EarthVector pos(posi);
594    Double_t mag(0.), magmiddle(0.);
595    Double_t alt1(0.), alt2(0.), altmiddle(0.), h0(0.);
596    EarthVector direc = dir.Unit();
597    Int_t split = -1;  // to deal with zones
598   
599    // check if pos is a valid position
600    if(pos.IsUnderSeaLevel()) {
601        Msg(EsafMsg::Warning) << "<RandomScatPos> Starting position is underground, HUGE vector returned" << MsgDispatch;
602        scatpos.SetXYZ(0,0,HUGE);
603        return kTRUE;
604    }
605   
606    // check if pos is in aerosol layer (it must be)
607    // if not, pos is set to enter position
608    if(!IsInAerosol(pos)) {
609        Msg(EsafMsg::Warning) << "<RandomScatPos()> Starting position SHOULD be in aerosol layers, pos is set to entering position in layer" << MsgDispatch;
610        // get entering position
611        enter = GetImpact(pos,direc);
612        // if no aerosol along the track
613        if(enter.Z() == HUGE) {
614            Msg(EsafMsg::Warning) << "<RandomScatPos()> STRANGE CASE : randomscatpos asked, but track does not seem to intersect aerosol layer" << MsgDispatch;
615            scatpos.SetXYZ(0,0,HUGE);
616            return kTRUE;
617        }
618        pos = enter;
619    }
620   
621    // 0. look if needs to split aerosol layer in two parts (profile is not exponential in [0-1km] for low visibility)
622    outgopos = GetImpact(pos,dir,"outgoing");
623    if(fType == "urban5" || fType == "rural5") {
624        if(pos.Zv() >= 1*km && outgopos.Zv() <= 1*km) split = 1;       // outgopos is inside BOUND1
625        else if(outgopos.Zv() >= 1*km && pos.Zv() <= 1*km) split = 2;  // startpos is inside BOUND1
626        else if(outgopos.Zv() <= 1*km && pos.Zv() <= 1*km) split = 3;  // both pos are inside BOUND1
627        else split = 0;                                                // both positions are outside BOUND1
628    }
629
630   
631    // 1. this change of variable comes from the old version of the code, which was bugged
632    Double_t K = Kwl(wl);  // = extinction coeff. !!
633    // 1(bis). tell the kind of interaction : scatt. or abs.
634    Double_t kwlabs = Kwl(wl,kTRUE);
635    Double_t abs = kwlabs / K;
636    Double_t scat = 1 - abs;
637    if(scat > rndm->Rndm()) rtn = kTRUE;
638   
639   
640    // 2. if needed, get intermediate enter/exit position on top of AerZone=BOUND1
641    if(split > 0) {
642        if(split == 1) {
643            enter1 = GetZoneImpact(pos,direc,BOUND1);
644            if(enter1.Z() == HUGE) Msg(EsafMsg::Warning) << "<RandomScatPos> In this case (split==1), enter position should exist" << MsgDispatch;
645            altmiddle = enter1.Zv();
646            magmiddle = (enter1 - pos).Mag();
647        }
648        else if(split == 2) {
649            exit1 = GetZoneImpact(pos,direc,BOUND1,"outgoing");
650            if(exit1.Z() == HUGE) {
651                Msg(EsafMsg::Warning) << "<RandomScatPos> In this case (split==2), exit position should exist" << MsgDispatch;
652                Msg(EsafMsg::Warning) << "pos = "<<pos << MsgDispatch;
653                Msg(EsafMsg::Warning) << "direc = "<<direc << MsgDispatch;
654                // find local zenith angle
655                Double_t anglee;
656                EarthVector tempp(pos);
657                tempp(2) += EarthRadius();      // pos expressed in earth-centered frame -> gives local vertical direction (expressed in MES frame)
658                anglee = direc.Angle(tempp);    // angle between dir and local vertical
659                Msg(EsafMsg::Warning) << "local zenith angle/halfpi = "<<anglee/PiOver2()<<" rad, angle = "<<anglee*RadToDeg()<<" deg" << MsgDispatch;
660                Msg(EsafMsg::Warning) << "<RandomScatPos> Fake aerosol scat position returned" << MsgDispatch;
661                scatpos.SetXYZ(0,0,HUGE);
662                return rtn;
663            }
664            altmiddle = exit1.Zv();
665            magmiddle = (exit1 - pos).Mag();
666        }
667    }
668   
669   
670    // 3. find other parameters for integration
671    // scale height, local zenith angle, pos altitude
672    h0 = 2.14378*km;                // for high visibility (>= 23km)
673    if(split >= 0) h0 = 0.3972*km;  // for visibility = 5km
674    Double_t angle;
675    EarthVector temp(pos);
676    temp(2) += EarthRadius();     // pos expressed in earth-centered frame -> gives local vertical direction (expressed in MES frame)
677    angle = direc.Angle(temp);    // angle between dir and local vertical
678    alt1 = pos.Zv();
679   
680   
681    // 4. generate random number to sample distribution function : Integral(Beta(s).ds) = -ln(1 - RNDM)
682    Double_t RNDM = -Log(1 - rndm->Rndm());
683   
684   
685    // 5.  find path length, thus determining position of interaction
686    Bool_t upward = kTRUE;
687        // 5.1. if horizontal track
688    if(fabs(angle - PiOver2()) < kTolerance) {
689        if(split == 3) mag = RNDM / (fSz[0] * K);
690        else mag = RNDM / (fSz[0] * K * Exp(-alt1/h0));  // should occur rarely
691    }
692        // 5.2. else deals with zones
693    else {
694        if(angle > PiOver2()) {
695            EarthVector dirinvert = -direc;
696            angle = dirinvert.Angle(temp);
697            upward = kFALSE;
698        }
699        if(split == 0) {
700            if(upward) {
701                if(RNDM > (fSz[1] * K * h0 / Cos(angle) * Exp(1*km/h0) * Exp(-alt1/h0))) {
702                    // means no valid position found
703                    scatpos.SetXYZ(0,0,HUGE);
704                    return rtn;
705                }
706                mag = -h0 / Cos(angle) * Log(1 - RNDM / (fSz[1] * K * h0 / Cos(angle) * Exp(1*km/h0) * Exp(-alt1/h0)));
707            }
708            else mag = h0 / Cos(angle) * Log(1 + RNDM / (fSz[1] * K * h0 / Cos(angle) * Exp(1*km/h0) * Exp(-alt1/h0)));
709        }
710        else if(split == 1) { // upward is false
711            // get a position assuming everything occurs in BOUND2
712            mag = h0 / Cos(angle) * Log(1 + RNDM / (fSz[1] * K * h0 / Cos(angle) * Exp(1*km/h0) * Exp(-alt1/h0)));
713            // if found position altitude is below altmiddle, needs to consider BOUND1 too
714            alt2 = alt1 - mag * Cos(angle);
715            if(alt2 < altmiddle) {
716                // substract first part of integration until altmiddle (in BOUND2)
717                RNDM -= fSz[1] * K * h0 / Cos(angle) * Exp(1*km/h0) * (Exp(-altmiddle/h0) - Exp(-alt1/h0));
718                mag   = magmiddle;
719                mag  += RNDM / (fSz[0] * K);
720            }
721        }
722        else if(split == 2) { // upward is true
723            // get a position assuming everything occurs in BOUND1
724            mag   = RNDM / (fSz[0] * K);
725            // if found position altitude is above altmiddle, needs to consider BOUND2 too
726            alt2 = mag * Cos(angle) + alt1;
727            if(alt2 > altmiddle) {
728                // substract first part of integration (in BOUND1)
729                RNDM -= fSz[0] * K * magmiddle;
730                mag   = magmiddle;
731                mag  += -h0 / Cos(angle) * Log(1 - RNDM / (fSz[1] * K * h0 / Cos(angle) * Exp(1*km/h0) * Exp(-altmiddle/h0)));
732            }
733        }
734        // for low visibility and both positions inside BOUND1 : aerosol density is constant
735        else if(split == 3) mag  = RNDM / (fSz[0] * K);
736        else {
737            if(upward) {
738                if(RNDM > (fSz[0] * K * h0 / Cos(angle) * Exp(-alt1/h0))) {
739                    // means no valid position found
740                    scatpos.SetXYZ(0,0,HUGE);
741                    return rtn;
742                }
743                mag = -h0 / Cos(angle) * Log(1 - RNDM / (fSz[0] * K * h0 / Cos(angle) * Exp(-alt1/h0)));
744            }
745            else mag = h0 / Cos(angle) * Log(1 + RNDM / (fSz[0] * K * h0 / Cos(angle) * Exp(-alt1/h0)));
746        }
747    }
748   
749    // 6. set position of interaction and check if it a valid position
750    scatpos = pos + mag*direc;
751    if(!IsInAerosol(scatpos)) scatpos.SetXYZ(0,0,HUGE);  // handle "under sea level" as well
752   
753    return rtn;
754}
755
756//______________________________________________________________________________
757Double_t LowtranAerosol::PhaseFunction(Double_t theta,Double_t wl) const {
758    //
759    // Mie scattering phase function
760    // Normalized when integrated over full solid angle
761    //
762   
763    // check if wl is valid
764    CheckWlRange(wl);
765   
766    // find bounds for interpolation
767    Double_t G1(0.), G2(0.), F(0.);
768    Int_t i=0;
769    for(i=0; i < 6; i++) {
770        if(wl >= fWlrange[i] && wl <= fWlrange[i+1]) break;
771    }
772    G1 = (fG1[i+1] - fG1[i]) / (fWlrange[i+1] - fWlrange[i]) * (wl - fWlrange[i]) + fG1[i];
773    G2 = (fG2[i+1] - fG2[i]) / (fWlrange[i+1] - fWlrange[i]) * (wl - fWlrange[i]) + fG2[i];
774    F  = (fF[i+1] - fF[i]) / (fWlrange[i+1] - fWlrange[i]) * (wl - fWlrange[i]) + fF[i];
775   
776    // calculate value
777    Double_t rtn(0.);
778    Double_t HG1 = (1 - G1*G1) / pow(1 - 2*G1*Cos(theta) + G1*G1,1.5);
779    Double_t HG2 = (1 - G2*G2) / pow(1 - 2*G2*Cos(theta) + G2*G2,1.5);
780    rtn = 1./(4*Pi()) * (F*HG1 + (1 - F)*HG2);
781   
782    return rtn;
783}
784
785//______________________________________________________________________________
786Double_t LowtranAerosol::PhaseFunction(const EarthVector& incom_dir,const EarthVector& outgo_dir,Double_t wl) const {
787    //
788    // Mie scattering phase function
789    // Normalized when integrated over full solid angle
790    //
791    Double_t theta = outgo_dir.Angle(incom_dir);
792   
793    return PhaseFunction(theta,wl);
794}
795
796//______________________________________________________________________________
797Double_t LowtranAerosol::GetMaxPhaseFunction(Double_t wlmin,Double_t wlmax) const {
798    //
799    // returns maximum value of currently used scattering phase function
800    // (phase function is normalized when integrated over full solid angle)
801    //
802   
803    Double_t rtn(0.), temp(0.);
804    for(Int_t i=0; i < 7; i++) {
805        temp = PhaseFunction(0.,fWlrange[i]);
806        if(temp > rtn && fWlrange[i] > wlmin && fWlrange[i] < wlmax) rtn = temp;
807    }
808    temp = PhaseFunction(0.,wlmin);
809    if(temp > rtn) rtn = temp;
810    temp = PhaseFunction(0.,wlmax);
811    if(temp > rtn) rtn = temp;
812   
813    return rtn;
814}
815
816//______________________________________________________________________________
817void LowtranAerosol::RandomDir(EarthVector& dir,Double_t wl) const {
818    //
819    // 'dir' is the incoming direction -> this method set dir to the scattering outgoing direction
820    // get a random direction, sampling clouds scattering phase function
821    //
822   
823    Double_t theta(0);
824    Double_t u1(0.), u2(0.);
825    TRandom* rndm = EsafRandom::Get();
826   
827    // check if wl is valid
828    CheckWlRange(wl);
829   
830    // find bounds for interpolation
831    Double_t G1(0.), G2(0.), F(0.);
832    Int_t i=0;
833    for(i=0; i < 6; i++) {
834        if(wl >= fWlrange[i] && wl <= fWlrange[i+1]) break;
835    }
836    G1 = (fG1[i+1] - fG1[i]) / (fWlrange[i+1] - fWlrange[i]) * (wl - fWlrange[i]) + fG1[i];
837    G2 = (fG2[i+1] - fG2[i]) / (fWlrange[i+1] - fWlrange[i]) * (wl - fWlrange[i]) + fG2[i];
838    F  = (fF[i+1] - fF[i]) / (fWlrange[i+1] - fWlrange[i]) * (wl - fWlrange[i]) + fF[i];
839   
840   
841    // get random theta, assuming isotropic distribution in phi
842    // theta is the angle between incoming and outgoing directions
843    // von neumann sampling -> much faster than using TF1
844    Double_t HG1 = 0.;
845    Double_t HG2 = 0.;
846    Double_t pfvalue = 0.;
847    do {
848        u1 = -1 + rndm->Rndm()*2;
849        HG1 = (1 + G1) / (1 - G1) / (1 - G1);
850        HG2 = (1 + G2) / (1 - G2) / (1 - G2);
851        u2 = rndm->Rndm() * 0.5 * (F*HG1 + (1 - F)*HG2);
852        HG1 = (1 - G1*G1) / pow(1 - 2*G1*u1 + G1*G1,1.5);
853        HG2 = (1 - G2*G2) / pow(1 - 2*G2*u1 + G2*G2,1.5);
854        pfvalue = 0.5 * (F*HG1 + (1 - F)*HG2);
855    } while(u2 >= pfvalue);
856   
857    theta = acos(u1);
858   
859    // then get a random direction
860    EarthVector v = dir.Orthogonal();
861    v.Rotate(rndm->Rndm()*2*Pi(),dir);
862    EarthVector axis = v.Cross(dir);
863    dir.Rotate(theta,axis);
864}
865
866//_____________________________________________________________________________________________________
867EarthVector LowtranAerosol::GetZoneImpact(const EarthVector& pos, const EarthVector& dir, AerZone zone, string opt) const {
868    //
869    // Impact on clouds top surface form a given (position,direction) pair in atmosphere
870    // if no impact, (0,0,HUGE) is returned
871    // upward direction is handled
872    //
873    // Aerosol considered geometry is defined using "zone" argument
874    //
875    // if opt == "default"         -> incoming impact returned
876    //                                if pos is within clouds, pos is returned
877    // if opt == "outgoing"        -> outgoing impact returned
878    //
879
880    // init
881    EarthVector rtn(0,0,HUGE);
882    Bool_t isinaerosol, downward;
883    Double_t Altimpact(0);
884    EarthVector direc = dir.Unit();
885    Double_t topaltitude = 2*km;
886    Double_t bottomaltitude = 0.;
887    if(zone == BOUND1) topaltitude = 1*km;
888    if(zone == BOUND2) bottomaltitude = 1*km;
889     
890    // says if dir is going up/downward
891    downward = GoingDownward(pos,direc);
892   
893    // says if pos is within boundary layers (altitude round-off effects < 1mm are handled)
894    isinaerosol = IsInAerosolZone(pos,zone);
895   
896    // set the impact surface altitude according to photon present situation (up/downward, above/below, enter/exit aerosol)
897    if(isinaerosol) {
898        if(opt == "default") return pos;
899        else if(opt == "outgoing") {
900            if(!downward) Altimpact = topaltitude;
901            else Altimpact = bottomaltitude;
902        }
903        else Msg(EsafMsg::Panic) << "<GetZoneImpact()> wrong argument (internal pos)" << MsgDispatch;
904    }
905    else {
906        // if not going toward aerosol layer
907        if(pos.Zv() < bottomaltitude && downward) return rtn;
908        if(pos.Zv() > topaltitude && !downward) return rtn;
909       
910        if((opt == "outgoing" && !downward) || (opt == "default" && downward))
911                Altimpact = topaltitude;
912        else if((opt == "outgoing" && downward) || (opt == "default" && !downward))
913                Altimpact = bottomaltitude;
914        else
915                Msg(EsafMsg::Panic) << "<GetZoneImpact()> wrong argument (external pos)" << MsgDispatch;
916    }
917
918    // now calculate impact
919    Double_t mag(0);
920
921/*  flat earth
922if(fabs(direc.Z()) < kTolerance) rtn.SetXYZ(0,0,HUGE);
923else {
924    mag = (Altimpact - pos.Z()) / direc.Z();
925    if(mag < -kAltitudeTolerance) rtn.SetXYZ(0,0,HUGE);
926    else rtn = pos + mag*direc;
927}
928if(mag < -kAltitudeTolerance) Msg(EsafMsg::Warning) << "PB in impact calculation GetCloudImpact(), mag = ["<<mag<< MsgDispatch;
929*/
930
931    Double_t b = pos*direc + direc.Z()*EarthRadius();
932    Double_t c = pos.Mag2() + 2*EarthRadius()*(pos.Z() - Altimpact) - pow(Altimpact,2);
933    pair<Int_t,Double_t*>& p = findRoots(1.,2*b,c);
934    if(p.first == 0) {
935        // if direction is nearly horizontal, try the other layer bound
936        Double_t anglee;
937        EarthVector tempp(pos);
938        tempp(2) += EarthRadius();
939        anglee = dir.Angle(tempp);
940        if(fabs(anglee - PiOver2()) < 0.1) {
941            if(Altimpact == topaltitude) Altimpact = bottomaltitude;
942            else Altimpact = topaltitude;
943            mag = 0;
944            b = pos*direc + direc.Z()*EarthRadius();
945            c = pos.Mag2() + 2*EarthRadius()*(pos.Z() - Altimpact) - pow(Altimpact,2);
946            p = findRoots(1.,2*b,c);
947        }
948        else return rtn;
949    }
950    if(p.first == 0) return rtn;
951    else if(p.first == 1) mag = p.second[0];
952    else if(p.first ==2) {
953        if((p.second[0]+kAltitudeTolerance) * (p.second[1]+kAltitudeTolerance) < 0) mag = max(p.second[0],p.second[1]);
954        else mag = min(p.second[0],p.second[1]);
955    }
956    if(mag < -kAltitudeTolerance) Msg(EsafMsg::Warning) << "PB in impact calculation GetZoneImpact(), [min,max] = ["<<mag<<", "<< max(p.second[0],p.second[1])<<"]" << MsgDispatch;
957    rtn = pos + mag*direc;   
958
959
960    return rtn;
961}
962
963//_____________________________________________________________________________________________________
964EarthVector LowtranAerosol::GetImpact(const EarthVector& pos, const EarthVector& dir, string opt) const {
965    //
966    // Impact on aerosol top surface form a given (position,direction) pair in atmosphere
967    // if no impact, (0,0,HUGE) is returned
968    // upward direction is handled
969    //
970    // if opt == "default"         -> incoming impact returned
971    //                                if pos is within clouds, pos is returned
972    // if opt == "outgoing"        -> outgoing impact returned
973    //
974
975    return GetZoneImpact(pos,dir,ALL,opt);
976}
977
978//_____________________________________________________________________________
979Bool_t LowtranAerosol::GoingDownward(const EarthVector& pos,const EarthVector& direc) const {
980    //
981    // to know if locally track is going up/downward
982    // locally means at pos nadir
983    // if perfectly horizontal (i.e. local angle of halfpi) returns false (i.e. upward)
984    //
985   
986    Bool_t rtn;
987
988/*   flat earth
989if(direc.Z() > 0) return false;
990else return true;
991*/
992
993    EarthVector dir = direc.Unit();
994    // find local zenith angle
995    Double_t angle;
996    EarthVector temp(pos);
997    temp(2) += EarthRadius();   // pos expressed in earth-centered frame -> gives local vertical direction (expressed in MES frame)
998    angle = dir.Angle(temp);    // angle between dir and local vertical
999    // returns accordingly
1000    if(angle <= PiOver2()) rtn = false;
1001    else  rtn = true;
1002
1003    return rtn;
1004}
1005
1006//_____________________________________________________________________________
1007void LowtranAerosol::Reset() {
1008    //
1009    // reset clodus features
1010    //
1011   
1012    // reset current model tables
1013    for(Int_t i=0; i < 7; i++) {
1014        if(i < 3) fSz[i] = 0;
1015        fKwl_ext[i] = 0;
1016        fKwl_abs[i] = 0;
1017        fG1[i] = 0;
1018        fG2[i] = 0;
1019        fF[i] = 0;
1020    }
1021   
1022    // build new model tables
1023    Build();
1024}
1025
Note: See TracBrowser for help on using the repository browser.