Peano
Loading...
Searching...
No Matches
AirglowInitialization.py
Go to the documentation of this file.
2 return """
3 /***********
4 * OH model *
5 ************/
6 // states v = 9, ..., 0
7 for (int vibrationalState = 9; vibrationalState >= 0; --vibrationalState) {
8 double productionOfOHState = 0.0;
9 double lossOfOHState = 0.0;
10 const int stateIndex = Shortcuts::OH0 + vibrationalState;
11 // PRODUCTIONS
12 // k5 production
13 productionOfOHState += ProductionRates::k5(T, vibrationalState) * H * O3;
14 if (vibrationalState < 9) {
15 // production via Einstein coefficients by quenching from higher states within bandwidth
16 for (int higherVibrationalState = std::min(vibrationalState + BANDWIDTH, 9); higherVibrationalState > vibrationalState; --higherVibrationalState) {
17 productionOfOHState += EINSTEIN_COEFFICIENTS(higherVibrationalState, vibrationalState) * Q[Shortcuts::OH0 + higherVibrationalState];
18 }
19
20 // production via k8 by quenching from all higher states
21 for (int higherVibrationalState = 9; higherVibrationalState > vibrationalState; --higherVibrationalState) {
22 productionOfOHState += ProductionRates::k8(higherVibrationalState, vibrationalState) * O2 * Q[Shortcuts::OH0 + higherVibrationalState];
23 }
24
25 // production via k9 by quenching from next higher vibrational state
26 productionOfOHState += ProductionRates::k9(vibrationalState + 1) * N2 * Q[stateIndex + 1];
27 }
28
29 // LOSSES
30 // loss via k7 by quenching into hydrogen and molecular oxygen
31 lossOfOHState += ProductionRates::k7(vibrationalState) * O;
32 if (vibrationalState > 0) {
33 // loss via k9 by quenching into next lower vibrational state
34 lossOfOHState += ProductionRates::k9(vibrationalState) * N2;
35
36 // loss via k8 by quenching into all lower states
37 for (int quenchedState = vibrationalState - 1; quenchedState >= 0; --quenchedState) {
38 lossOfOHState += ProductionRates::k8(vibrationalState, quenchedState) * O2;
39 }
40
41 // loss via Einstein coefficients by quenching into lower states within bandwidth
42 for (int quenchedState = vibrationalState - 1; quenchedState >= std::max(0, vibrationalState - BANDWIDTH); --quenchedState) {
43 lossOfOHState += EINSTEIN_COEFFICIENTS(vibrationalState, quenchedState);
44 }
45 }
46
47 if (productionOfOHState == 0.0 and lossOfOHState == 0.0) {
48 Q[stateIndex] = 0.0;
49 } else {
50 Q[stateIndex] = productionOfOHState / lossOfOHState;
51 }
52 }
53
54 /***********
55 * OI model *
56 ***********/
57
58 // Singlet molecular oxygen in odd, non-symmetric sigma state
59 const double productionOfO2cu = (ProductionRates::ZETA * ProductionRates::k1(T) * O * O * M);
60 const double lossOfO2cu = (ProductionRates::k2() * O2 + ProductionRates::k3() * (1 + ProductionRates::DELTA) * O + ProductionRates::A1());
61 Q[Shortcuts::O2cu] = productionOfO2cu / lossOfO2cu;
62
63 // Singlet atomic oxygen in excited state S
64 const double productionOfOS = (ProductionRates::DELTA * ProductionRates::k3() * O * Q[Shortcuts::O2cu]);
65 const double lossOfOS = (ProductionRates::k4(T) * O2 + ProductionRates::A2() + ProductionRates::A5577());
66 Q[Shortcuts::OS] = productionOfOS / lossOfOS;"""
67
68
70 csv_file_path: str,
71 csv_separator=";",
72 csv_hydrogen_key="Hden(cm-3)",
73 csv_height_key="Heit(km)",
74 plot_path: str = "",
75 transparent_background: bool = False,
76):
77 """!
78 csv_file_path:
79 Path to MSISE CSV file containing hydrogen profile.
80
81 csv_separator:
82 Delimiter used in CSV file. Default is ";".
83
84 csv_hydrogen_key:
85 Key for hydrogen profile used in CSV file. Default is "Hden(cm-3)".
86
87 csv_height_key:
88 Key for height in km used in CSV file. Default is "Heit(km)".
89
90 plot_path:
91 If the exponential fit shall be plotted and saved, pass over a path here. Default is "" meaning no plotting.
92
93 transparent_background:
94 Transparent background for plot if True. Does nothing if plot_path is "". Default is False.
95
96 This function can be used to create an ozone initial state,
97 which depends on an exponential fit of the hydrogen density
98 instead of the actual hydrogen profile.
99
100 This is due to the hydrogen tending to 0 shortly after
101 the OH peak altitude of ca. 83km. Ozone would thus tend to infinity
102 for these lower altitudes. However, its actual minimum should be lower
103 than where the hydrogen density reaches 0 (ca. 75km-80km).
104
105 Therefore, the hydrogen density is fit to an exponential curve allowing
106 the ozone to decay for altitudes less than 80km. This additional ozone
107 is not in steady state and thus needs further correction in the source term.
108 """
109
110 # calculate ozone based on vertically exponentially decaying fit of hydrogen profile
111 import pandas as pd
112 from scipy.optimize import curve_fit
113 import numpy as np
114
115 # DATA PREPROCESSING
116
117 # read in MSISE profile from CSV
118 dataframe = pd.read_csv(csv_file_path, delimiter=csv_separator)
119 # filter columns; we only need height and hydrogen
120 dataframe = dataframe[[csv_height_key, csv_hydrogen_key]]
121 # km to m
122 dataframe["Heit(m)"] = dataframe[csv_height_key].mul(1e3)
123 # sort by height descending, makes it easier later to find sign switch in derivative
124 dataframe = dataframe.sort_values(by="Heit(m)", ascending=False)
125
126 # HYDROGEN DERIVATIVE BY HEIGHT
127
128 # calculate dH/dy
129 hydrogen = dataframe[csv_hydrogen_key].to_numpy()
130 derivative_of_hydrogen = np.diff(hydrogen)
131
132 # identify index at which sign flips from + to - indicating last value taken for exponential fit
133 sign_switch_index = 0
134 for i in range(0, derivative_of_hydrogen.size):
135 # i marks turning point and it is also the last turning point
136 if derivative_of_hydrogen[i] < 0 and np.all(derivative_of_hydrogen[i:] <= 0):
137 sign_switch_index = i
138 break
139 else:
140 # no sign flip detected in derivative => no exponential fit possible
141 raise ValueError(
142 "Expected decrease in hydrogen in lower atmosphere. Exponential fit for ozone not possible"
143 )
144
145 # EXPONENTIAL FIT
146
147 # extract values from numpy array / dataframe that are used to fit exponential function
148 # revert sorting by height by going in -1 stride
149 hydrogen_values_to_fit = hydrogen[sign_switch_index::-1]
150 height_values_to_fit = dataframe["Heit(m)"].to_numpy()[sign_switch_index::-1]
151 hydrogen_log = np.log(hydrogen_values_to_fit)
152
153 # Define the exponential function to fit
154 def exp_func(x, a, b, c):
155 return a * np.exp(b * x) + c
156
157 def lin_func(x, a, b):
158 return a + b * x
159
160 # fit linear function of logarithmic values as initial guess
161 (a, b), _ = curve_fit(lin_func, height_values_to_fit, hydrogen_log)
162 a = np.exp(a)
163 # fit exponential function using fit of logarithmic function as initial guess
164 (a, b, c), _ = curve_fit(
165 exp_func,
166 height_values_to_fit,
167 hydrogen_values_to_fit,
168 p0=[a, b, np.max(hydrogen_values_to_fit)],
169 )
170
171 if plot_path != "":
172 print("a:", a, ", b:", b, ", c:", c)
173 import matplotlib.pyplot as plt
174 from os.path import splitext
175
176 # Generate fitted curve
177 hydrogen_fit = exp_func(dataframe["Heit(m)"], a, b, c)
178
179 plt.plot(hydrogen, dataframe["Heit(m)"], label="MSISE")
180 plt.plot(
181 hydrogen_fit, dataframe["Heit(m)"], color="red", label="Exponential Fit"
182 )
183
184 # scales
185 plt.xscale("log")
186
187 # labels
188 plt.ylabel("Height (km)")
189 plt.xlabel(r"[H] (cm$^{-3}$)")
190 plt.title("Exponential Fit for Hydrogen Profile")
191 plt.ylim(bottom=0, top=dataframe["Heit(m)"].max())
192 plt.legend(loc="upper left")
193 current_yticks = plt.yticks()
194 plt.yticks(
195 ticks=current_yticks[0], labels=((current_yticks[0] / 1e3).astype(np.int64))
196 )
197 plt.tight_layout()
198 plt.savefig(plot_path, transparent=transparent_background)
199
200 plt.clf()
201
202 # Generate fitted curve for subsection
203 hydrogen_fit = exp_func(height_values_to_fit, a, b, c)
204
205 plt.plot(hydrogen_values_to_fit, height_values_to_fit, label="MSISE")
206 plt.plot(
207 hydrogen_fit, height_values_to_fit, color="red", label="Exponential Fit"
208 )
209
210 # scales
211 plt.xscale("log")
212 plt.ylim(height_values_to_fit[0], height_values_to_fit[-1])
213
214 # labels
215 plt.ylabel("Height (km)")
216 plt.xlabel(r"[H] (cm$^{-3}$)")
217
218 height_min_km = height_values_to_fit[0] / 1e3
219 height_max_km = height_values_to_fit[-1] / 1e3
220
221 height_min_km = (
222 height_min_km if not height_min_km.is_integer() else int(height_min_km)
223 )
224 height_max_km = (
225 height_max_km if not height_max_km.is_integer() else int(height_max_km)
226 )
227
228 plt.title(
229 f"Exponential Fit for Hydrogen Profile from {height_min_km}km to {height_max_km}km"
230 )
231 plt.legend()
232 current_yticks = plt.yticks()
233 plt.yticks(
234 ticks=current_yticks[0], labels=((current_yticks[0] / 1e3).astype(np.int64))
235 )
236 plt.tight_layout()
237 plt.savefig(
238 "_subsection".join(splitext(plot_path)), transparent=transparent_background
239 )
240
241 # OZONE FUNCTION
242
243 return (
244 """{
245 auto hydrogenFit = [](const double altitude) {
246 return """
247 + str(a)
248 + """ * tarch::la::pow(tarch::la::E, """
249 + str(b)
250 + """ * altitude) + """
251 + str(c)
252 + """;
253 };
254 const double hydrogenPrime = hydrogenFit(x(1));
255 double lossOfO3 = 0.0;
256 for (int vibrationalState = 0; vibrationalState < 10; ++vibrationalState) {
257 lossOfO3 += ProductionRates::k5(T, vibrationalState) * hydrogenPrime;
258 }
259
260 // k6 production of ozone
261 const double productionOfO3 = (ProductionRates::k6(T) * M * O * O2);
262 Q[Shortcuts::O3] = productionOfO3 / lossOfO3;
263 }
264 const {{COMPUTE_PRECISION}} O3 = Q[Shortcuts::O3];"""
265 )
get_steady_state_for_ozone_MSISE(str csv_file_path, csv_separator=";", csv_hydrogen_key="Hden(cm-3)", csv_height_key="Heit(km)", str plot_path="", bool transparent_background=False)
csv_file_path: Path to MSISE CSV file containing hydrogen profile.