-
Notifications
You must be signed in to change notification settings - Fork 0
/
op_separate.py
3954 lines (3327 loc) · 188 KB
/
op_separate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# ==============================================================================
# Module includes all the numerical functions VULCAN.
# Copyright (C) 2016 Shang-Min Tsai (Shami)
# ==============================================================================
# ReadRate() reads in the chemical network and construct the rate constants based
# on the T-P sturcture.
# Integration() is the backbone of integrating for one time step
# ODESolver() contains the functions for solving system of ODEs (e.g. dy/dt, Jacobian, etc.)
# ==============================================================================
import numpy as np
import scipy
from scipy import sparse
from scipy import interpolate
import matplotlib.pyplot as plt
import matplotlib.legend as lg
import time, os, pickle
import csv, ast
# TEST numba
# from numba import njit, jit
#from builtins import input
#from collections import defaultdict
# TODO :test the TODO buldle
import vulcan_cfg
try: from PIL import Image
except ImportError:
try: import Image
except: vulcan_cfg.use_PIL = False
import build_atm
import chem_funs
from chem_funs import ni, nr # number of species and reactions in the network
from phy_const import kb, Navo, hc, ag0 # hc is used to convert to the actinic flux
from vulcan_cfg import nz
# imported functions
chemdf = chem_funs.chemdf
neg_achemjac = chem_funs.neg_symjac
compo = build_atm.compo
compo_row = build_atm.compo_row
species = chem_funs.spec_list
class ReadRate(object):
"""
to read in rate constants from the network file and compute the reaction rates for the corresponding Tco and pco
"""
def __init__(self):
self.i = 1
# flag of trimolecular reaction
self.re_tri, self.re_tri_k0 = False, False
self.list_tri = []
def read_rate(self, var, atm):
Rf, Rindx, a, n, E, a_inf, n_inf, E_inf, k, k_fun, k_inf, kinf_fun, k_fun_new, pho_rate_index = \
var.Rf, var.Rindx, var.a, var.n, var.E, var.a_inf, var.n_inf, var.E_inf, var.k, var.k_fun, var.k_inf, var.kinf_fun, var.k_fun_new,\
var.pho_rate_index
ion_rate_index = var.ion_rate_index
i = self.i
re_tri, re_tri_k0 = self.re_tri, self.re_tri_k0
list_tri = self.list_tri
Tco = atm.Tco.copy()
M = atm.M.copy()
special_re = False
conden_re = False
recomb_re = False
photo_re = False
ion_re = False
#end_re = False
#br_read = False
#ion_br_read = False
rainout_re = False
photo_sp = []
ion_sp = []
with open(vulcan_cfg.network) as f:
all_lines = f.readlines()
for line_indx, line in enumerate(all_lines):
# switch to 3-body and dissociation reations
if line.startswith("# 3-body"):
re_tri = True
if line.startswith("# 3-body reactions without high-pressure rates"):
re_tri_k0 = True
elif line.startswith("# special"):
re_tri = False
re_tri_k0 = False
special_re = True # switch to reactions with special forms (hard coded)
rainout_re = False
elif line.startswith("# condensation"):
re_tri = False
re_tri_k0 = False
special_re = False
conden_re = True
rainout_re = False
var.conden_indx = i
elif line.startswith("# radiative"):
re_tri = False
re_tri_k0 = False
special_re = False
conden_re = False
recomb_re = True
rainout_re = False
var.recomb_indx = i
elif line.startswith("# photo"):
re_tri = False
re_tri_k0 = False
special_re = False # turn off reading in the special form
conden_re = False
recomb_re = False
photo_re = True
rainout_re = False
var.photo_indx = i
elif line.startswith("# ionisation"):
re_tri = False
re_tri_k0 = False
special_re = False # turn off reading in the special form
conden_re = False
recomb_re = False
photo_re = False
ion_re = True
rainout_re = False
var.ion_indx = i
elif line.startswith("# reverse stops"):
var.stop_rev_indx = i
elif line.startswith('# rainout'):
re_tri = False
re_tri_k0 = False
special_re = False # turn off reading in the special form
conden_re = False
recomb_re = False
photo_re = False
ion_re = False
rainout_re = True
# skip common lines and blank lines
# ========================================================================================
if not line.startswith("#") and line.strip() and special_re == False and conden_re == False and photo_re == False and ion_re == False and rainout_re == False: # if not starts
Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
li = line.partition(']')[-1].strip()
columns = li.split()
Rindx[i] = int(line.partition('[')[0].strip())
a[i] = float(columns[0])
n[i] = float(columns[1])
E[i] = float(columns[2])
# switching to trimolecular reactions (len(columns) > 3 for those with high-P limit rates)
if re_tri == True and re_tri_k0 == False:
a_inf[i] = float(columns[3])
n_inf[i] = float(columns[4])
E_inf[i] = float(columns[5])
list_tri.append(i)
if columns[-1].strip() == 'He': re_He = i
elif columns[-1].strip() == 'ex1': re_CH3OH = i
# Note: make the defaut i=i
k_fun[i] = lambda temp, mm, i=i: a[i] *temp**n[i] * np.exp(-E[i]/temp)
if re_tri == False:
k[i] = k_fun[i](Tco, M)
# for 3-body reactions, also calculating k_inf
elif re_tri == True and len(columns)>=6:
kinf_fun[i] = lambda temp, i=i: a_inf[i] *temp**n_inf[i] * np.exp(-E_inf[i]/temp)
k_fun_new[i] = lambda temp, mm, i=i: (a[i] *temp**n[i] * np.exp(-E[i]/temp))/(1 + (a[i] *temp**n[i] * np.exp(-E[i]/temp))*mm/(a_inf[i] *temp**n_inf[i] * np.exp(-E_inf[i]/temp)) )
#k[i] = k_fun_new[i](Tco, M)
k_inf = a_inf[i] *Tco**n_inf[i] * np.exp(-E_inf[i]/Tco)
k[i] = k_fun[i](Tco, M)
k[i] = k[i]/(1 + k[i]*M/k_inf )
else: # for 3-body reactions without high-pressure rates
k[i] = k_fun[i](Tco, M)
i += 2
# end if not
# ========================================================================================
elif special_re == True and line.strip() and not line.startswith("#"):
Rindx[i] = int(line.partition('[')[0].strip())
Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
if Rf[i] == 'OH + CH3 + M -> CH3OH + M':
print ('Using special form for the reaction: ' + Rf[i])
k[i] = 1.932E3*Tco**-9.88 *np.exp(-7544./Tco) + 5.109E-11*Tco**-6.25 *np.exp(-1433./Tco)
k_inf = 1.031E-10 * Tco**-0.018 *np.exp(16.74/Tco)
# the pressure dependence from Jasper 2017
Fc = 0.1855*np.exp(-Tco/155.8)+0.8145*np.exp(-Tco/1675.)+np.exp(-4531./Tco)
nn = 0.75 - 1.27*np.log(Fc)
ff = np.exp( np.log(Fc)/(1.+ (np.log(k[i]*M/k_inf)/nn**2)**2 ) )
k[i] = k[i]/(1 + k[i]*M/k_inf ) *ff
k_fun[i] = lambda temp, mm, i=i: 1.932E3 *temp**-9.88 *np.exp(-7544./temp) + 5.109E-11*temp**-6.25 *np.exp(-1433./temp)
kinf_fun[i] = lambda temp, mm, i=i: 1.031E-10 * temp**-0.018 *np.exp(16.74/temp)
k_fun_new[i] = lambda temp, mm, i=i: (1.932E3 *temp**-9.88 *np.exp(-7544./temp) + 5.109E-11*temp**-6.25 *np.exp(-1433./temp))/\
(1 + (1.932E3 *temp**-9.88 *np.exp(-7544./temp) + 5.109E-11*temp**-6.25 *np.exp(-1433./temp)) * mm / (1.031E-10 * temp**-0.018 *np.exp(16.74/temp)) )
# elif Rf[i] == 'C2H2 + M -> soot':
# print ('Using fake C2H2 -> soot: ' + Rf[i])
# k[i] = np.ones(nz) * 1e-10
i += 2
# Testing condensation
elif conden_re == True and line.strip() and not line.startswith("#"):
Rindx[i] = int(line.partition('[')[0].strip())
Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
var.conden_re_list.append(i)
k[i] = np.zeros(nz)
k[i+1] = np.zeros(nz)
i += 2
# setting photo dissociation reactions to zeros
elif photo_re == True and line.strip() and not line.startswith("#"):
k[i] = np.zeros(nz)
Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
# adding the photo species
photo_sp.append(Rf[i].split()[0])
li = line.partition(']')[-1].strip()
columns = li.split()
Rindx[i] = int(line.partition('[')[0].strip())
# columns[0]: the species being dissocited; branch index: columns[1]
pho_rate_index[(columns[0],int(columns[1]))] = Rindx[i]
# store the number of branches
var.n_branch[columns[0]] = int(columns[1])
i += 2
# setting photo ionization reactions to zeros
elif ion_re == True and line.strip() and not line.startswith("#"): # and end_re == False
k[i] = np.zeros(nz)
Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
# chekcing if it already existed in the photo species
#if Rf[i].split()[0] not in photo_sp: print (str(Rf[i].split()[0]) + ' not present in the photo disccoiation but only in ionization!')
ion_sp.append(Rf[i].split()[0])
li = line.partition(']')[-1].strip()
columns = li.split()
Rindx[i] = int(line.partition('[')[0].strip())
# columns[0]: the species being dissocited; branch index: columns[1]
ion_rate_index[(columns[0],int(columns[1]))] = Rindx[i]
# store the number of branches
var.ion_branch[columns[0]] = int(columns[1])
i += 2
elif rainout_re == True and line.strip() and not line.startswith("#"):
Rindx[i] = int(line.partition('[')[0].strip())
Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
var.rainout_re_list.append(i)
k[i] = np.zeros(nz)
k[i+1] = np.zeros(nz)
i += 2
k_fun.update(k_fun_new)
# store k into data_var
# remeber k_fun has not removed reactions from remove_list
var.k = k
var.k_fun = k_fun
var.kinf_fun = kinf_fun
var.photo_sp = set(photo_sp)
if vulcan_cfg.use_ion == True: var.ion_sp = set(ion_sp)
return var
def rev_rate(self, var, atm):
rev_list = range(2, var.stop_rev_indx, 2)
# setting the rest reversal zeros
for i in range(var.stop_rev_indx+1, nr+1,2):
var.k[i] = np.zeros(nz)
Tco = atm.Tco.copy()
# reversing rates and storing into data_var
print ('Reverse rates from R1 to R' + str(var.stop_rev_indx-2))
print ('Rates greater than 1e-6:')
for i in rev_list:
if i in vulcan_cfg.remove_list:
var.k[i] = np.repeat(0.,nz)
else:
var.k_fun[i] = lambda temp, mm, i=i: var.k_fun[i-1](temp, mm)/chem_funs.Gibbs(i-1,temp)
var.k[i] = var.k[i-1]/chem_funs.Gibbs(i-1,Tco)
if np.any(var.k[i] > 1.e-6): print ('R' + str(i) + " " + var.Rf[i-1] +' : ' + str(np.amax(var.k[i])) )
if np.any(var.k[i-1] > 1.e-6): print ('R' + str(i-1) + " " + var.Rf[i-1] + ' : ' + str(np.amax(var.k[i-1])) )
return var
def remove_rate(self, var):
for i in vulcan_cfg.remove_list:
var.k[i] = np.repeat(0.,nz)
var.k_fun[i] = lambda temp, mm, i=i: np.repeat(0.,nz)
return var
def lim_lowT_rates(self, var, atm): # for setting up the lower limit of rate coefficients for low T
for i in range(1,nr,2):
if var.Rf[i] == 'H + CH3 + M -> CH4 + M':
T_mask = atm.Tco <= 277.5
k0 = 6e-29; kinf = 2.06E-10 *atm.Tco**-0.4 # from Moses+2005
lowT_lim = k0 / (1. + k0*atm.M/kinf)
print ("using the low temperature limit for CH3 + H + M -> CH4 + M")
print ("capping "); print (var.k[i][T_mask]); print ("at "); print (lowT_lim[T_mask])
var.k[i][T_mask] = lowT_lim[T_mask]
elif var.Rf[i] == 'H + C2H4 + M -> C2H5 + M':
T_mask = atm.Tco <= 300
print ("using the low temperature limit for H + C2H4 + M -> C2H5 + M")
print ("capping "); print (var.k[i][T_mask]); print ("at "); print (3.7E-30)
var.k[i][T_mask] = 3.7E-30 # from Moses+2005
elif var.Rf[i] == 'H + C2H5 + M -> C2H6 + M':
T_mask = atm.Tco <= 200
print ("using the low temperature limit for H + C2H5 + M -> C2H6 + M")
print ("capping "); print (var.k[i][T_mask]); print ("at "); print (2.49E-27)
var.k[i][T_mask] = 2.49E-27 # from Moses+2005
return var
# def read_rateFun(self, var):
# '''
# Reading in the reaction network and returning only the functions (k_fun)
# Used for pathway analysis
# '''
#
# Rf, Rindx, a, n, E, a_inf, n_inf, E_inf, k_fun, kinf_fun, k_fun_new = \
# var.Rf, var.Rindx, var.a, var.n, var.E, var.a_inf, var.n_inf, var.E_inf, var.k_fun, var.kinf_fun, var.k_fun_new
#
# i = self.i
# re_tri, re_tri_k0 = self.re_tri, self.re_tri_k0
# list_tri = self.list_tri
#
# special_re = False
# conden_re = False
# photo_re = False
# end_re = False
# br_read = False
#
# photo_sp = []
#
# with open(vulcan_cfg.network) as f:
# for line in f.readlines():
#
# # switch to 3-body and dissociation reations
# if line.startswith("# 3-body"):
# re_tri = True
#
# if line.startswith("# 3-body reactions without high-pressure rates"):
# re_tri_k0 = True
#
# elif line.startswith("# special"):
# special_re = True # switch to reactions with special forms (hard coded)
#
# elif line.startswith("# photo"):
# special_re = False # turn off reading in the special form
# photo_re = True
# var.photo_indx = i
#
# elif line.startswith("# re_end"):
# end_re = True
#
# elif line.startswith("# braching info start"):
# br_read = True
#
# elif line.startswith("# braching info end"):
# br_read = False
#
# # skip common lines and blank lines
# # ========================================================================================
# if not line.startswith("#") and line.strip() and special_re == False and photo_re == False and end_re == False: # if not starts
#
# Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
# li = line.partition(']')[-1].strip()
# columns = li.split()
# Rindx[i] = int(line.partition('[')[0].strip())
#
# a[i] = float(columns[0])
# n[i] = float(columns[1])
# E[i] = float(columns[2])
#
# # switching to trimolecular reactions (len(columns) > 3 for those with high-P limit rates)
# if re_tri == True and re_tri_k0 == False:
# a_inf[i] = float(columns[3])
# n_inf[i] = float(columns[4])
# E_inf[i] = float(columns[5])
# list_tri.append(i)
#
# if columns[-1].strip() == 'He': re_He = i
# elif columns[-1].strip() == 'ex1': re_CH3OH = i
#
# # Note: make the defaut i=i
# k_fun[i] = lambda temp, mm, i=i: a[i] *temp**n[i] * np.exp(-E[i]/temp)
#
#
# # for 3-body reactions, also calculating k_inf
# if re_tri == True and len(columns)>=6:
#
# kinf_fun[i] = lambda temp, i=i: a_inf[i] *temp**n_inf[i] * np.exp(-E_inf[i]/temp)
# k_fun_new[i] = lambda temp, mm, i=i: (a[i] *temp**n[i] * np.exp(-E[i]/temp))/(1 + (a[i] *temp**n[i] * np.exp(-E[i]/temp))*mm/(a_inf[i] *temp**n_inf[i] * np.exp(-E_inf[i]/temp)) )
#
# i += 2
# # end if not
# # ========================================================================================
# elif special_re == True and line.strip() and not line.startswith("#") and end_re == False:
#
# Rindx[i] = int(line.partition('[')[0].strip())
# Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
#
# if Rf[i] == 'OH + CH3 + M -> CH3OH + M':
# print ('Using special form for the reaction: ' + Rf[i])
# k_fun[i] = lambda temp, mm, i=i: 1.932E3 *temp**-9.88 *np.exp(-7544./temp) + 5.109E-11*temp**-6.25 *np.exp(-1433./temp)
# kinf_fun[i] = lambda temp, mm, i=i: 1.031E-10 * temp**-0.018 *np.exp(16.74/temp)
# k_fun_new[i] = lambda temp, mm, i=i: (1.932E3 *temp**-9.88 *np.exp(-7544./temp) + 5.109E-11*temp**-6.25 *np.exp(-1433./temp))/\
# (1 + (1.932E3 *temp**-9.88 *np.exp(-7544./temp) + 5.109E-11*temp**-6.25 *np.exp(-1433./temp)) * mm / (1.031E-10 * temp**-0.018 *np.exp(16.74/temp)) )
#
# i += 2
#
# # setting photo dissociation reactions to zeros
# elif photo_re == True and line.strip() and not line.startswith("#") and end_re == False:
#
# #k[i] = np.zeros(nz)
# Rf[i] = line.partition('[')[-1].rpartition(']')[0].strip()
#
# # adding the photo species
# photo_sp.append(Rf[i].split()[0])
#
# li = line.partition(']')[-1].strip()
# columns = li.split()
# Rindx[i] = int(line.partition('[')[0].strip())
# # columns[0]: the species being dissocited; branch index: columns[1]
# pho_rate_index[(columns[0],int(columns[1]))] = Rindx[i]
#
# # store the number of branches
# var.n_branch[columns[0]] = int(columns[1])
#
# i += 2
#
# # end_re == True
# elif br_read == True and not line.startswith("#"):
# # read in the quantum yields of photolysis reactions
# sp_list = line.partition(':')
# sp = sp_list[0]
# lists = sp_list[-1]
# wavelen_yield = lists.partition(';')
# # wavelen_yield is tuple of string in wavelength seitch, ;, Q yield e.g. ('[165.]', ';', '[(1.,0),(0,1.)]')
# var.wavelen[sp] = ast.literal_eval(wavelen_yield[0].strip())
# var.br_ratio[sp] = ast.literal_eval(wavelen_yield[-1].strip())
#
# k_fun.update(k_fun_new)
#
# # store k_fun into data_var
# var.k_fun = k_fun
# var.kinf_fun = kinf_fun
#
# return var
#
# def rev_rateFun(self, var):
# '''
# Revarsing only the functions of forward rates (k_fun) and the T, P values (at the examined level)
# Used for pathway analysis
# '''
#
# rev_list = range(2,nr+1,2)
#
# # reversing rates and storing into data_var
# for i in rev_list:
# var.k_fun[i] = lambda temp, mm, i=i: var.k_fun[i-1](temp, mm)/chem_funs.Gibbs(i-1,temp)
#
# return var
def make_bins_read_cross(self,var,atm):
'''
determining the bin range and only use the min and max wavelength that the molecules absorb
to avoid photons with w0=1 (pure scatteing) in certain wavelengths
var.cross stores the total absorption cross sections of each species, e.g. var.cross['H2O']
var.cross stores the IDIVIDUAL photodissociation cross sections for each bracnh, e.g. var.cross_J[('H2O',1)], which is equvilent to var.cross['H2O'] times the branching ratio of branch 1
'''
photo_sp = list(var.photo_sp)
ion_sp = list(var.ion_sp)
absp_sp = photo_sp + ion_sp
sp0 = photo_sp[0]
cross_raw, scat_raw = {}, {}
ratio_raw, ion_ratio_raw = {}, {}
cross_T_raw = {}
# In the end, we do not need photons beyond the longest-wavelength threshold from all species (different from absorption)
sp_label = np.genfromtxt(vulcan_cfg.cross_folder+'thresholds.txt',dtype=str, usecols=0) # taking the first column as labels
lmd_data = np.genfromtxt(vulcan_cfg.cross_folder+'thresholds.txt', skip_header = 1)[:,1] # discarding the fist column
# for setting up the wavelength coverage
threshold = {label: row for label, row in zip(sp_label, lmd_data) if label in species} # only include the species in the current network
var.threshold = threshold
# reading in cross sections into dictionaries
for n, sp in enumerate(absp_sp):
if vulcan_cfg.use_ion == True:
try: cross_raw[sp] = np.genfromtxt(vulcan_cfg.cross_folder+sp+'/'+sp+'_cross.csv',dtype=float,delimiter=',',skip_header=1, names = ['lambda','cross','disso','ion'])
except: print ('\nMissing the cross section from ' + sp); raise
if sp in ion_sp:
try: ion_ratio_raw[sp] = np.genfromtxt(vulcan_cfg.cross_folder+sp+'/'+sp+'_ion_branch.csv',dtype=float,delimiter=',',skip_header=1, names = True)
except: print ('\nMissing the ion branching ratio from ' + sp); raise
else:
try: cross_raw[sp] = np.genfromtxt(vulcan_cfg.cross_folder+sp+'/'+sp+'_cross.csv',dtype=float,delimiter=',',skip_header=1, names = ['lambda','cross','disso'])
except: print ('\nMissing the cross section from ' + sp); raise
# reading in the branching ratios
# for i in range(1,var.n_branch[sp]+1): # branch index should start from 1
if sp in photo_sp: # excluding ion_sp
try: ratio_raw[sp] = np.genfromtxt(vulcan_cfg.cross_folder+sp+'/'+sp+'_branch.csv',dtype=float,delimiter=',',skip_header=1, names = True)
except: print ('\nMissing the branching ratio from ' + sp); raise
# reading in temperature dependent cross sections
if sp in vulcan_cfg.T_cross_sp:
T_list = []
for temp_file in os.listdir("thermo/photo_cross/" + sp + "/"):
if temp_file.startswith(sp) and temp_file.endswith("K.csv"):
temp = temp_file
temp = temp.replace(sp,''); temp = temp.replace('_cross_',''); temp = temp.replace('K.csv','')
T_list.append(int(temp) )
var.cross_T_sp_list[sp] = T_list
for tt in T_list:
if vulcan_cfg.use_ion == True: # usually the T-dependent cross sections are only measured in the photodissociation-relavent wavelengths so cross_tot = cross_diss
cross_T_raw[(sp, tt)] = np.genfromtxt(vulcan_cfg.cross_folder+sp+'/'+sp+'_cross_'+str(tt)+'K.csv',dtype=float,delimiter=',',skip_header=1, names = ['lambda','cross','disso','ion'])
else: cross_T_raw[(sp, tt)] = np.genfromtxt(vulcan_cfg.cross_folder+sp+'/'+sp+'_cross_'+str(tt)+'K.csv',dtype=float,delimiter=',',skip_header=1, names = ['lambda','cross','disso'])
# room-T cross section
cross_T_raw[(sp, 300)] = cross_raw[sp]
var.cross_T_sp_list[sp].append(300)
if cross_raw[sp]['cross'][0] == 0 or cross_raw[sp]['cross'][-1] ==0:
raise IOError ('\n Please remove the zeros in the cross file of ' + sp)
if n==0: # the first species
bin_min = cross_raw[sp]['lambda'][0]
bin_max = cross_raw[sp]['lambda'][-1]
# photolysis threshold
try: diss_max = threshold[sp]
except: print (sp + " not in threshol.txt"); raise
else:
sp_min, sp_max = cross_raw[sp]['lambda'][0], cross_raw[sp]['lambda'][-1]
if sp_min < bin_min: bin_min = sp_min
if sp_max > bin_max: bin_max = sp_max
try:
if threshold[sp] > diss_max:
diss_max = threshold[sp]
except: print (sp + " not in threshol.txt"); raise
# constraining the bin_min and bin_max by the default values defined in store.py
bin_min = max(bin_min, var.def_bin_min)
bin_max = min(bin_max, var.def_bin_max, diss_max)
print ("Input stellar spectrum from " + "{:.1f}".format(var.def_bin_min) + " to " + "{:.1f}".format(var.def_bin_max) )
print ("Photodissociation threshold: " + "{:.1f}".format(diss_max) )
print ("Using wavelength bins from " + "{:.1f}".format(bin_min) + " to " + str(bin_max) )
var.dbin1 = vulcan_cfg.dbin1
var.dbin2 = vulcan_cfg.dbin2
if vulcan_cfg.dbin_12trans >= bin_min and vulcan_cfg.dbin_12trans <= bin_max:
bins = np.concatenate(( np.arange(bin_min,vulcan_cfg.dbin_12trans, var.dbin1), np.arange(vulcan_cfg.dbin_12trans,bin_max, var.dbin2) ))
else: bins = np.arange(bin_min,bin_max, var.dbin1)
var.bins = bins
var.nbin = len(bins)
# all variables that depend on the size of nbins
# the direct beam (staggered)
var.sflux = np.zeros( (nz+1, var.nbin) )
# the diffusive flux (staggered)
var.dflux_u, var.dflux_d = np.zeros( (nz+1, var.nbin) ), np.zeros( (nz+1, var.nbin) )
# the total actinic flux (non-staggered)
var.aflux = np.zeros( (nz, var.nbin) )
# the total actinic flux from the previous calculation
prev_aflux = np.zeros( (nz, var.nbin) )
# staggered
var.tau = np.zeros( (nz+1, var.nbin) )
# the stellar flux at TOA
var.sflux_top = np.zeros(var.nbin)
# read_cross
# creat a dict of cross section with key=sp and values=bins in data_var
var.cross = dict([(sp, np.zeros(var.nbin)) for sp in absp_sp ]) # including photo_sp and ion_sp
# read cross of disscoiation
var.cross_J = dict([((sp,i), np.zeros(var.nbin)) for sp in photo_sp for i in range(1,var.n_branch[sp]+1)])
var.cross_scat = dict([(sp, np.zeros(var.nbin)) for sp in vulcan_cfg.scat_sp])
# for temperature-dependent cross sections
var.cross_T = dict([(sp, np.zeros((nz, var.nbin) )) for sp in vulcan_cfg.T_cross_sp ])
var.cross_J_T = dict([((sp,i), np.zeros((nz, var.nbin) )) for sp in vulcan_cfg.T_cross_sp for i in range(1,var.n_branch[sp]+1) ])
#read cross of ionisation
if vulcan_cfg.use_ion == True: var.cross_Jion = dict([((sp,i), np.zeros(var.nbin)) for sp in ion_sp for i in range(1,var.ion_branch[sp]+1)])
for sp in photo_sp: # photodissociation only; photoionization takes a separate branch ratio file
# for values outside the boundary => fill_value = 0
inter_cross = interpolate.interp1d(cross_raw[sp]['lambda'], cross_raw[sp]['cross'], bounds_error=False, fill_value=0)
inter_cross_J = interpolate.interp1d(cross_raw[sp]['lambda'], cross_raw[sp]['disso'], bounds_error=False, fill_value=0)
inter_ratio = {} # excluding ionization branches
for i in range(1,var.n_branch[sp]+1): # fill_value extends the first and last elements for branching ratios
br_key = 'br_ratio_' + str(i)
try:
inter_ratio[i] = interpolate.interp1d(ratio_raw[sp]['lambda'], ratio_raw[sp][br_key], bounds_error=False, fill_value=(ratio_raw[sp][br_key][0],ratio_raw[sp][br_key][-1]))
except: print("The branches in the network file does not match the branchong ratio file for " + str(sp))
# using a loop instead of an array because it's easier to handle the branching ratios
for n, ld in enumerate(bins):
var.cross[sp][n] = inter_cross(ld)
# using the branching ratio (from the files) to construct the individual cross section of each branch
for i in range(1,var.n_branch[sp]+1):
var.cross_J[(sp,i)][n] = inter_cross_J(ld) * inter_ratio[i](ld)
# make var.cross_T[(sp,i)] and var.cross_J_T[(sp,i)] here in 2D array: nz * bins (same shape as tau)
# T-dependent cross sections are usually only measured in the photodissociation-relavent wavelengths so cross_tot = cross_diss
if sp in vulcan_cfg.T_cross_sp:
# T list of species sp that have T-depedent cross sections (inclduing 300 K for inter_cross)
T_list = np.array(var.cross_T_sp_list[sp])
max_T_sp = np.amax(T_list)
min_T_sp = np.amin(T_list)
for lev, Tz in enumerate(atm.Tco): # looping z
Tz_between = False # flag for Tz in between any two elements in T_list
# define the interpolating T range
if list(T_list[T_list <= Tz]) and list(T_list[T_list > Tz]):
Tlow = T_list[T_list <= Tz].max() # closest T in T_list smaller than Tz
Thigh = T_list[T_list > Tz].min() # closest T in T_list larger than Tz
Tz_between = True
# find the wavelength range that are included in both cross_T_raw[(sp,Tlow)] and cross_T_raw[(sp,Thigh)]
ld_min = max( cross_T_raw[(sp,Tlow)]['lambda'][0], cross_T_raw[(sp,Thigh)]['lambda'][0] )
ld_max = min( cross_T_raw[(sp,Tlow)]['lambda'][-1], cross_T_raw[(sp,Thigh)]['lambda'][-1] )
inter_cross_lowT = interpolate.interp1d(cross_T_raw[(sp,Tlow)]['lambda'], cross_T_raw[(sp,Tlow)]['cross'], bounds_error=False, fill_value=0)
inter_cross_highT = interpolate.interp1d(cross_T_raw[(sp,Thigh)]['lambda'], cross_T_raw[(sp,Thigh)]['cross'], bounds_error=False, fill_value=0)
inter_cross_J_lowT = interpolate.interp1d(cross_T_raw[(sp,Tlow)]['lambda'], cross_T_raw[(sp,Tlow)]['disso'], bounds_error=False, fill_value=0)
inter_cross_J_highT = interpolate.interp1d(cross_T_raw[(sp,Thigh)]['lambda'], cross_T_raw[(sp,Thigh)]['disso'], bounds_error=False, fill_value=0)
for n, ld in enumerate(bins): # looping bins
# not within the T-cross wavelength range
if ld < ld_min or ld > ld_max:
var.cross_T[sp][lev, n] = var.cross[sp][n]
# don't forget the cross_J_T branches
for i in range(1,var.n_branch[sp]+1):
var.cross_J_T[(sp,i)][lev, n] = var.cross_J[(sp,i)][n]
else:
# update: inerpolation in log10 for cross sections and linearly between Tlow and Thigh
log_lowT = np.log10(inter_cross_lowT(ld))
log_highT = np.log10(inter_cross_highT(ld))
if np.isinf(log_lowT ): log_lowT = -100. # replacing -inf with -100
if np.isinf(log_highT ): log_highT = -100.
inter_T = interpolate.interp1d([Tlow,Thigh], [log_lowT,log_highT], axis=0) # at wavelength ld, interpolating between Tlow and Thigh in log10
if inter_T(Tz) == -100: var.cross_T[sp][lev, n] == 0.
else: var.cross_T[sp][lev, n] = 10**(inter_T(Tz))
# update: inerpolation in log10 for cross sections and linearly between Tlow and Thigh
# using the branching ratio (from the files) to construct the individual cross section of each branch
for i in range(1,var.n_branch[sp]+1):
J_log_lowT = np.log10(inter_cross_J_lowT(ld))
J_log_highT = np.log10(inter_cross_J_highT(ld))
if np.isinf(J_log_lowT): J_log_lowT = -100. # replacing -inf with -100
if np.isinf(J_log_highT): J_log_highT = -100.
inter_cross_J_T = interpolate.interp1d([Tlow,Thigh], [J_log_lowT,J_log_highT], axis=0)
if inter_cross_J_T(Tz) == -100: var.cross_J_T[(sp,i)][lev, n] = 0.
else: var.cross_J_T[(sp,i)][lev, n] = 10**(inter_cross_J_T(Tz)) * inter_ratio[i](ld) # same inter_ratio[i](ld) as the standard one above
elif not list(T_list[T_list < Tz]): # Tz equal or smaller than all T in T_list including 300K (empty list)
if min_T_sp == 300:
var.cross_T[sp][lev] = var.cross[sp] # using the cross section at room T
for i in range(1,var.n_branch[sp]+1):
var.cross_J_T[(sp,i)][lev] = var.cross_J[(sp,i)]
else: # min_T_sp != 300; T-cross lower than room temperature
# the wavelength range of cross_T_raw at T = min_T_sp
ld_min, ld_max = cross_T_raw[(sp,min_T_sp)]['lambda'][0], cross_T_raw[(sp,min_T_sp)]['lambda'][-1]
inter_cross_lowT = interpolate.interp1d(cross_T_raw[(sp,min_T_sp)]['lambda'], cross_T_raw[(sp,min_T_sp)]['cross'], bounds_error=False, fill_value=0)
inter_cross_J_lowT = interpolate.interp1d(cross_T_raw[(sp,min_T_sp)]['lambda'], cross_T_raw[(sp,min_T_sp)]['disso'], bounds_error=False, fill_value=0)
for n, ld in enumerate(bins): # looping bins
# not within the T-cross wavelength range
if ld < ld_min or ld > ld_max:
var.cross_T[sp][lev, n] = var.cross[sp][n]
# don't forget the cross_J_T branches
for i in range(1,var.n_branch[sp]+1):
var.cross_J_T[(sp,i)][lev, n] = var.cross_J[(sp,i)][n]
else:
var.cross_T[sp][lev, n] = inter_cross_lowT(ld)
# using the branching ratio (from the files) to construct the individual cross section of each branch
for i in range(1,var.n_branch[sp]+1):
var.cross_J_T[(sp,i)][lev, n] = inter_cross_J_lowT(ld) * inter_ratio[i](ld) # same inter_ratio[i](ld) as the standard one above
else: # Tz equal or larger than all T in T_list (empty list)
# the wavelength range of cross_T_raw[(sp,Thigh)]
if max_T_sp == 300:
var.cross_T[sp][lev] = var.cross[sp] # using the cross section at room T
for i in range(1,var.n_branch[sp]+1):
var.cross_J_T[(sp,i)][lev] = var.cross_J[(sp,i)]
else: # the wavelength range of cross_T_raw at T = max_T_sp
ld_min, ld_max = cross_T_raw[(sp,max_T_sp)]['lambda'][0], cross_T_raw[(sp,max_T_sp)]['lambda'][-1]
inter_cross_highT = interpolate.interp1d(cross_T_raw[(sp,max_T_sp)]['lambda'], cross_T_raw[(sp,max_T_sp)]['cross'], bounds_error=False, fill_value=0)
inter_cross_J_highT = interpolate.interp1d(cross_T_raw[(sp,max_T_sp)]['lambda'], cross_T_raw[(sp,max_T_sp)]['disso'], bounds_error=False, fill_value=0)
for n, ld in enumerate(bins): # looping bins
# not within the T-cross wavelength range
if ld < ld_min or ld > ld_max:
var.cross_T[sp][lev, n] = var.cross[sp][n]
# don't forget the cross_J_T branches
for i in range(1,var.n_branch[sp]+1):
var.cross_J_T[(sp,i)][lev, n] = var.cross_J[(sp,i)][n]
else:
var.cross_T[sp][lev, n] = inter_cross_highT(ld)
# using the branching ratio (from the files) to construct the individual cross section of each branch
for i in range(1,var.n_branch[sp]+1):
var.cross_J_T[(sp,i)][lev, n] = inter_cross_J_highT(ld) * inter_ratio[i](ld) # same inter_ratio[i](ld) as the standard one above
if vulcan_cfg.use_ion == True:
for sp in ion_sp:
if sp not in photo_sp:
inter_cross = interpolate.interp1d(cross_raw[sp]['lambda'], cross_raw[sp]['cross'], bounds_error=False, fill_value=0)
inter_cross_Jion = interpolate.interp1d(cross_raw[sp]['lambda'], cross_raw[sp]['ion'], bounds_error=False, fill_value=0)
ion_inter_ratio = {} # For ionization branches
for i in range(1,var.ion_branch[sp]+1): # fill_value extends the first and last elements for branching ratios
br_key = 'br_ratio_' + str(i)
try:
ion_inter_ratio[i] = interpolate.interp1d(ion_ratio_raw[sp]['lambda'], ion_ratio_raw[sp][br_key], bounds_error=False, fill_value=(ion_ratio_raw[sp][br_key][0],ion_ratio_raw[sp][br_key][-1]))
except: print("The ionic branches in the network file does not match the branchong ratio file for " + str(sp))
for n, ld in enumerate(bins):
# for species noe appeared in photodissociation but only in photoionization, like H
if sp not in photo_sp: var.cross[sp][n] = inter_cross(ld)
for i in range(1,var.ion_branch[sp]+1):
var.cross_Jion[(sp,i)][n] = inter_cross_Jion(ld) * ion_inter_ratio[i](ld)
# end of if vulcan_cfg.use_ion == True:
# reading in cross sections of Rayleigh Scattering
for sp in vulcan_cfg.scat_sp:
scat_raw[sp] = np.genfromtxt(vulcan_cfg.cross_folder + 'rayleigh/' + sp+'_scat.txt',dtype=float,\
skip_header=1, names = ['lambda','cross'])
# for values outside the boundary => fill_value = 0
inter_scat = interpolate.interp1d(scat_raw[sp]['lambda'], scat_raw[sp]['cross'], bounds_error=False, fill_value=0)
for n, ld in enumerate(bins):
var.cross_scat[sp][n] = inter_scat(ld)
class Integration(object):
"""
time-stepping until the stopping criteria (steady-state) is satisfied
#all the operators required in the continuity equation: dn/dt + dphi/dz = P - L
#or class incorporating the esential numerical operations?
"""
def __init__(self, odesolver, output):
self.mtol = vulcan_cfg.mtol
self.atol = vulcan_cfg.atol
self.output = output
self.odesolver = odesolver
self.non_gas_sp = vulcan_cfg.non_gas_sp
self.use_settling = vulcan_cfg.use_settling
# including photoionisation
if vulcan_cfg.use_photo == True: self.update_photo_frq = vulcan_cfg.ini_update_photo_frq
if vulcan_cfg.use_condense == True:
self.non_gas_sp_index = [species.index(sp) for sp in self.non_gas_sp]
self.condense_sp_index = [species.index(sp) for sp in vulcan_cfg.condense_sp]
def __call__(self, var, atm, para, make_atm):
use_print_prog, use_live_plot = vulcan_cfg.use_print_prog, vulcan_cfg.use_live_plot
while not self.stop(var, para, atm): # Looping until the stop condition is satisfied
var = self.backup(var)
# updating tau, flux, and the photolosys rate
# swtiching to final_update_photo_frq
if vulcan_cfg.use_photo == True and var.longdy < vulcan_cfg.yconv_min*10. and var.longdydt < 1.e-6:
self.update_photo_frq = vulcan_cfg.final_update_photo_frq
if para.switch_final_photo_frq == False:
print ('update_photo_frq changed to ' + str(vulcan_cfg.final_update_photo_frq) +'\n')
para.switch_final_photo_frq = True
if vulcan_cfg.use_photo == True and para.count % self.update_photo_frq == 0:
self.odesolver.compute_tau(var, atm)
self.odesolver.compute_flux(var, atm)
self.odesolver.compute_J(var, atm)
if vulcan_cfg.use_ion == True: # photoionisation rate
self.odesolver.compute_Jion(var, atm)
# integrating one step
var, para = self.odesolver.one_step(var, atm, para)
# Condensation (needs to be after solver.one_step)
if vulcan_cfg.use_condense == True and var.t >= vulcan_cfg.start_conden_time and para.fix_species_start == False:
# updating the condensation rates
var = self.conden(var,atm)
# similarly updating the rainout rates
if vulcan_cfg.use_rainout == True:
var = self.rainout(var, atm)
if vulcan_cfg.fix_species and var.t > vulcan_cfg.stop_conden_time:
if para.fix_species_start == False: # switch to run for the first time
para.fix_species_start = True
vulcan_cfg.rtol = vulcan_cfg.post_conden_rtol
print ("rtol changed to " + str(vulcan_cfg.rtol) + " after fixing the condensaed species.")
atm.vs *= 0
print ("Turn off the settling velocity of all species")
# updated 2023
var.fix_y = {}
for sp in vulcan_cfg.fix_species:
var.fix_y[sp] = np.copy(var.y[:,species.index(sp)])
# record the cold trap levels
if vulcan_cfg.fix_species_from_coldtrap_lev == True:
if sp == 'H2O_l_s' or sp == 'H2SO4_l' or sp == 'NH3_l_s' or sp == 'S8_l_s': atm.conden_min_lev[sp] = nz-1 # fix condensates through the whole amtosphere
# updated 2023
else:
sat_rho = atm.n_0 * atm.sat_mix[sp]
conden_status = var.y[:,species.index(sp)] >= sat_rho
if list(var.y[conden_status,species.index(sp)]): # if it condenses
min_sat = np.amin(atm.sat_mix[sp][conden_status]) # the mininum value of the saturation p within the saturation region
conden_min_lev = np.where(atm.sat_mix[sp] == min_sat)[0][0]
atm.conden_min_lev[sp] = conden_min_lev
print (sp + " is now fixed from " + "{:.2f}".format(atm.pco[atm.conden_min_lev[sp]]/1e6) + " bar." )
else:
print (sp + " not condensed.")
atm.conden_min_lev[sp] = 0
else: pass # do nothing after fix_species has started
# this is inside the fix_species_start == False loop
if vulcan_cfg.use_relax:
if 'H2O' in vulcan_cfg.use_relax:
var = self.h2o_conden_evap_relax(var,atm)
if 'NH3' in vulcan_cfg.use_relax:
var = self.nh3_conden_evap_relax(var,atm)
if para.count % vulcan_cfg.update_frq == 0: # updating mu and dz (dzi) due to diffusion
atm = self.update_mu_dz(var, atm, make_atm)
atm = self.update_phi_esc(var, atm) # updating the diffusion-limited flux
# MAINTAINING HYDROSTATIC BALANCE
if vulcan_cfg.use_condense == True:
#var.v_ratio = np.sum(var.y[:,atm.gas_indx], axis=1) / atm.n_0
var.y[:,atm.gas_indx] = np.vstack(atm.n_0)*var.ymix[:,atm.gas_indx]
else:
#var.v_ratio = np.sum(var.y, axis=1) / atm.n_0 # how the volumn has changed while the P and number density are fixed
var.y = np.vstack(atm.n_0)*var.ymix
# calculating the changing of y
var = self.f_dy(var, para)
# save values of the current step
var, para = self.save_step(var, para)
# adjusting the step-size
var = self.odesolver.step_size(var, para)
if use_print_prog == True and para.count % vulcan_cfg.print_prog_num==0:
self.output.print_prog(var,para)
if vulcan_cfg.use_live_flux == True and vulcan_cfg.use_photo == True and para.count % vulcan_cfg.live_plot_frq ==0:
#plt.figure('flux')
self.output.plot_flux_update(var, atm, para)
if use_live_plot == True and para.count % vulcan_cfg.live_plot_frq ==0:
#plt.figure('mix')
self.output.plot_update(var, atm, para)
def backup(self, var):
var.y_prev = np.copy(var.y)
var.dy_prev = np.copy(var.dy)
var.atom_loss_prev = var.atom_loss.copy()
return var
def update_mu_dz(self, var, atm, make_atm): #y, ni, spec, Tco, pco
# gravity
gz = atm.g
pref_indx = atm.pref_indx
Tco, pico = atm.Tco.copy(), atm.pico.copy()
# calculating mu (mean molecular weight)
atm = make_atm.mean_mass(var, atm, ni)
Hp = atm.Hp
for i in range(pref_indx,nz):
if i == pref_indx:
atm.g[i] = atm.gs
Hp[i] = kb*Tco[i]/(atm.mu[i]/Navo*atm.gs)
else:
atm.g[i] = atm.gs * (vulcan_cfg.Rp/(vulcan_cfg.Rp+ atm.zco[i]))**2
Hp[i] = kb*Tco[i]/(atm.mu[i]/Navo*atm.g[i])
atm.dz[i] = Hp[i] * np.log(pico[i]/pico[i+1]) # pico[i+1] has a lower P than pico[i] (higer height)
atm.zco[i+1] = atm.zco[i] + atm.dz[i] # zco is set zero at 1bar for gas giants
# for pref_indx != zero
if not pref_indx == 0:
for i in range(pref_indx-1,-1,-1):
atm.g[i] = atm.gs * (vulcan_cfg.Rp/(vulcan_cfg.Rp + atm.zco[i+1]))**2
Hp[i] = kb*Tco[i]/(atm.mu[i]/Navo*atm.g[i])
atm.dz[i] = Hp[i] * np.log(pico[i]/pico[i+1])
atm.zco[i] = atm.zco[i+1] - atm.dz[i] # from i+1 propogating down to i
zmco = 0.5*(atm.zco + np.roll(atm.zco,-1))
atm.zmco = zmco[:-1]
dzi = 0.5*(atm.dz + np.roll(atm.dz,1))
atm.dzi = dzi[1:]
# for the molecular diffsuion
if vulcan_cfg.use_moldiff == True:
Ti = 0.5*(Tco + np.roll(Tco,-1))
atm.Ti = Ti[:-1]
Hpi = 0.5*(Hp + np.roll(Hp,-1))
atm.Hpi = Hpi[:-1]
return atm
def update_phi_esc(self, var, atm): # updating diffusion-mimited escape
# Diffusion limited escape
for sp in vulcan_cfg.diff_esc:
#atm.top_flux[species.index(sp)] = - atm.Dzz[-1,species.index(sp)] *var.y[-1,species.index(sp)] /atm.Hp[-1]
atm.top_flux[species.index(sp)] = - atm.Dzz[-1,species.index(sp)]*var.y[-1,species.index(sp)]*( 1./atm.Hp[-1] -atm.ms[species.index(sp)]* atm.g[-1]/(Navo*kb*atm.Tco[-1]) )
atm.top_flux[species.index(sp)] = max(atm.top_flux[species.index(sp)], vulcan_cfg.max_flux*(-1))