meclib 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
meclib-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: meclib
3
+ Version: 0.1.1
4
+ Summary: Code for Modeling Earth's Climate course
5
+ Author-email: Steven Neshyba <sneshyba@gmail.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: numpy
9
+ Requires-Dist: matplotlib
10
+ Requires-Dist: pandas
11
+ Requires-Dist: pint
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ # ### Computational Guided Inquiry for Modeling Earth's Climate (Neshyba, 2025)
5
+ #
6
+ # # Cambio Comparisons
7
+ # This code takes as input a user-generated emission scenario and runs four versions of Cambio:
8
+ #
9
+ # - Cambio 1.0 has the five basic equations of motion, with $CO_2$ fertilization but no other feedbacks
10
+ # - Cambio 2.0 adds in Henry's Law feedbacks
11
+ # - Cambio 3.0 adds in ice-albedo feedback
12
+ # - Cambio 4.0 adds in terrestrial sequestion feedback that reduces $CO_2$ fertilization
13
+ #
14
+ # The code then runs the four simulations, and compares the output of a selected two of them.
15
+
16
+ # In[2]:
17
+
18
+
19
+ import numpy as np
20
+ import matplotlib.pyplot as plt
21
+ import meclib as CL
22
+
23
+
24
+ # In[3]:
25
+
26
+
27
+ # get_ipython().run_line_magic('matplotlib', 'inline')
28
+
29
+
30
+ # ### Loading your emission scenario
31
+ # In the cell below, load in your scheduled flows file. It'll be most convenient if you use the following naming convention:
32
+ #
33
+ # time, eps, epsdictionary_from_file = CL.LoadMyScenario('...')
34
+ #
35
+ # (but of course supplying the name of a particular emission scenario).
36
+
37
+ # In[5]:
38
+
39
+
40
+ filename = "Peaks_in_2040_LTE_default.pkl"
41
+ time, eps, epsdictionary_from_file = CL.LoadMyScenario(filename, verbose=True)
42
+
43
+
44
+ # ### Creating a dictionary for climate parameters consistent with this emission scenario
45
+ # In the cell below, we use the CreateClimateParams function to create a dictionary of climate parameters.
46
+
47
+ # In[7]:
48
+
49
+
50
+ ClimateParams = CL.CreateClimateParams(epsdictionary_from_file)
51
+
52
+
53
+ # ### Running Cambio
54
+ # The cell below runs Cambio versions 1-4.
55
+
56
+ # In[9]:
57
+
58
+
59
+ CS_Cambio1_list = CL.run_Cambio(
60
+ CL.PropagateClimateState_Cambio1, ClimateParams, time, eps
61
+ )
62
+ CS_Cambio2_list = CL.run_Cambio(
63
+ CL.PropagateClimateState_Cambio2, ClimateParams, time, eps
64
+ )
65
+ CS_Cambio3_list = CL.run_Cambio(
66
+ CL.PropagateClimateState_Cambio3, ClimateParams, time, eps
67
+ )
68
+ CS_Cambio4_list = CL.run_Cambio(
69
+ CL.PropagateClimateState_Cambio4, ClimateParams, time, eps
70
+ )
71
+
72
+
73
+ # ### Individual reports using display
74
+
75
+ # In[11]:
76
+
77
+
78
+ print("Starting state:")
79
+ print(CS_Cambio1_list[0])
80
+ print("Ending state:")
81
+ print(CS_Cambio1_list[-1])
82
+
83
+
84
+ # ### Individual reports using CS_list_plots
85
+
86
+ # In[13]:
87
+
88
+
89
+ items_to_plot = [
90
+ ["C_atm", "C_ocean"],
91
+ ["F_ha", "F_ocean_net", "F_land_net"],
92
+ "T_anomaly",
93
+ ]
94
+ CL.CS_list_plots(CS_Cambio1_list, "Cambio1.0", items_to_plot)
95
+
96
+
97
+ # ### Comparison plots using CS_list_compare
98
+
99
+ # In[15]:
100
+
101
+
102
+ items_to_plot = [
103
+ ["C_atm", "C_ocean"],
104
+ ["F_ha", "F_ocean_net", "F_land_net"],
105
+ "T_anomaly",
106
+ "OceanSurfacepH",
107
+ ]
108
+ CL.CS_list_compare(
109
+ [CS_Cambio1_list, CS_Cambio4_list],
110
+ ["Cambio1.0", "Cambio4.0"],
111
+ items_to_plot,
112
+ )
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ # ### Computational Guided Inquiry for Modeling Earth's Climate (Neshyba, 2025)
5
+ #
6
+ # # Make an Emission Scenario
7
+ # The idea of this module is to create an emissions scenario -- a _schedule_ -- that describes how much carbon humans have released to the atmosphere in the past, and that makes projections about future emissions. We've done this before (ScheduledFlows), but this will allow us to specify long-term emissions.
8
+
9
+ # In[16]:
10
+
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ import matplotlib.pyplot as plt
15
+ import meclib as CL
16
+
17
+ # In[17]:
18
+
19
+
20
+ # get_ipython().run_line_magic('matplotlib', 'inline')
21
+
22
+
23
+ # ### Using ScheduledFlowsWithLTE
24
+ # In the cell below, we calculate and plot an emissions scenario, $\epsilon (t)$, using MakeEmissionsScenarioLTE (which includes long-term emissions).
25
+
26
+ # In[19]:
27
+
28
+
29
+ # Parameters
30
+ t_start = 1750
31
+ t_stop = 2150
32
+ nsteps = 600
33
+ t = np.linspace(t_start, t_stop, nsteps)
34
+ t_peak = 2040
35
+ t_decarb = 15
36
+ k = 0.0166
37
+ t_0 = 2003
38
+ eps_0 = 9
39
+ epslongterm = 2
40
+ t_decarb_ppf_factor = 0.5
41
+
42
+ # Create the scenario
43
+ t, myeps = CL.MakeEmissionsScenarioLTE(
44
+ t_start,
45
+ t_stop,
46
+ nsteps,
47
+ k,
48
+ eps_0,
49
+ t_0,
50
+ t_peak,
51
+ t_decarb,
52
+ epslongterm=epslongterm,
53
+ t_decarb_ppf_factor=t_decarb_ppf_factor,
54
+ )
55
+
56
+ # Plot the scenario
57
+ plt.figure()
58
+ plt.plot(t, myeps)
59
+ plt.grid(True)
60
+ plt.title("Emission scenario (GtC/year)")
61
+ plt.xlabel("year")
62
+ plt.ylabel("GtC/year")
63
+ plt.show()
64
+
65
+ # ### Saving your emissions scenario
66
+ # In the cell below we create the dictionary, etc., and save it to a file.
67
+
68
+ # In[21]:
69
+
70
+
71
+ # Create an empty dictionary
72
+ epsdictionary = dict()
73
+
74
+ # Create an empty dataframe
75
+ epsdf = pd.DataFrame()
76
+
77
+ # Insert the time and emissions columns into the dataframe
78
+ epsdf.insert(loc=0, column="time", value=t)
79
+ epsdf.insert(loc=1, column="emissions", value=myeps)
80
+
81
+ # Add the dataframe to the dictionary
82
+ epsdictionary["dataframe"] = epsdf
83
+
84
+ # Add metadata
85
+ epsdictionary["t_0"] = t_0
86
+ epsdictionary["eps_0"] = eps_0
87
+ epsdictionary["t_peak"] = t_peak
88
+ epsdictionary["t_decarb"] = t_decarb
89
+ epsdictionary["t_decarb"] = t_decarb_ppf_factor
90
+ epsdictionary["k"] = k
91
+ epsdictionary["description"] = (
92
+ "This is an emission scenario that peaks in the year 2040, with long-term emissions continuing after that"
93
+ )
94
+
95
+ # Report the contents of the dictionary
96
+ print(epsdictionary)
97
+
98
+
99
+ # ### Saving your emissions scenario
100
+ # Use the cell below to save your emissions scenario (the entire dictionary -- data and metadata) as a pickle file.
101
+
102
+ # In[23]:
103
+
104
+
105
+ # Assign a name for the file, and save it
106
+ filename = "Peaks_in_2040_LTE_default.pkl"
107
+ CL.SaveMyScenario(epsdictionary, filename)
@@ -0,0 +1 @@
1
+ from .cl import *
@@ -0,0 +1,733 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from copy import deepcopy as makeacopy
4
+ import pandas as pd
5
+ import pickle
6
+
7
+ def SaveMyScenario(my_dict, filename):
8
+ """Stores the stored scheduled flow dictionary"""
9
+
10
+ # Save dictionary to a file
11
+ with open(filename, 'wb') as file: pickle.dump(my_dict, file)
12
+
13
+ # Return
14
+ return
15
+
16
+ def LoadMyScenario(filename,verbose=False):
17
+ """Loads the stored scheduled flow dictionary, with an option to plot"""
18
+
19
+ # Load dictionary from a file
20
+ with open(filename, 'rb') as file: my_dict_loaded = pickle.load(file)
21
+
22
+ # This extracts the dataframe from the dictionary
23
+ epsdf = my_dict_loaded['dataframe']
24
+
25
+ # This extracts the time and emissions from the dataframe
26
+ time = np.array(epsdf['time'])
27
+ eps = np.array(epsdf['emissions'])
28
+
29
+ # Graph the time series (if flagged)
30
+ if verbose:
31
+ linewidth = 3
32
+ print(my_dict_loaded)
33
+ plt.figure()
34
+ plt.plot(time,eps,'k',linewidth=linewidth)
35
+ plt.grid(True)
36
+ plt.title(filename)
37
+ plt.xlabel('year')
38
+ plt.ylabel('GtC/year')
39
+
40
+ # Return
41
+ return time, eps, my_dict_loaded
42
+
43
+ def CreateClimateParams(epsdictionary,k_la=120):
44
+ """Returns a structured dictionary containing climate parameters"""
45
+
46
+ # Start with an empty dictionary
47
+ ClimateParams = {}
48
+
49
+ # Amount in the atmosphere (pre-industrial, and at the time of the NASA figure, 2003)
50
+ C_atm_preind = 590
51
+ C_atm_2003 = 780
52
+
53
+ # The starting time
54
+ epsdf = epsdictionary['dataframe']
55
+ time_preind = np.array(epsdf['time'])[0]
56
+
57
+ # Land-to-atmosphere flux constant and fluxes, according to NASA's figure
58
+ F_la_preind = k_la
59
+ F_la_2003 = F_la_preind
60
+ F_al_2003 = F_la_preind + 3
61
+
62
+ # Atm-to-land flux constants (Eq. 4 of Cambio1.0, using 2003 fluxes
63
+ # and reservoir amounts in pre-industrial time and 2003)
64
+ k_al1 = (F_al_2003-F_la_preind)/(C_atm_2003-C_atm_preind)
65
+ k_al0 = F_la_preind-k_al1*C_atm_preind
66
+
67
+ # Atmosphere-to-ocean (Eq. 6 of Cambio1.0, using 2003 flux and
68
+ # atmospheric reservoir amount)
69
+ F_ao_2003 = 92
70
+ k_ao = F_ao_2003/C_atm_2003
71
+ F_oa_2003 = 90
72
+
73
+ # NASA's observed ratio of net ocean sequestration to net land sequestration
74
+ r = 2/3
75
+
76
+ # Ocean-to-atmosphere flux constant based on Cambio1.0 equations
77
+ k = epsdictionary['k']
78
+ k_oa = k*(k_ao/(r*k_al1)-1) # constraining to satisfy the ratio r
79
+
80
+ # Atmosphere-to-land feedback parameters
81
+ k_al1_Tstar = 1.43
82
+ k_al1_deltaT = 0.471
83
+ fractional_k_al1_floor = 0.684
84
+
85
+ # Albedo parameters
86
+ preindustrial_albedo = 0.3
87
+ kappa = 0.61
88
+ S0 = 1367
89
+ sigma = 5.67e-8
90
+ AS_term1 = -1/(4*(1-preindustrial_albedo))
91
+ AS_term2 = (S0*(1-preindustrial_albedo)/(4*kappa*sigma))**.25
92
+ AS = AS_term1 * AS_term2; #print('Calculated AS = ', AS_test)
93
+ T_test = (S0/4*(1-preindustrial_albedo)/kappa/sigma)**.25; #print('Calculated T_test =', T_test)
94
+ #AS = -102.8
95
+
96
+ # Attach to the dictionary
97
+ ClimateParams['k_la'] = k_la
98
+ ClimateParams['k_al0'] = k_al0
99
+ ClimateParams['k_al1'] = k_al1
100
+ ClimateParams['k_oa'] = k_oa
101
+ ClimateParams['k_ao'] = k_ao
102
+ ClimateParams['k'] = k
103
+ ClimateParams['DC'] = 0.0321
104
+ ClimateParams['climate sensitivity'] = 3/C_atm_preind
105
+ ClimateParams['C_atm 2003'] = C_atm_2003
106
+ ClimateParams['F_ao 2003'] = F_ao_2003
107
+ ClimateParams['F_oa 2003'] = F_oa_2003
108
+ ClimateParams['preindustrial C_atm'] = C_atm_preind
109
+ ClimateParams['albedo sensitivity'] = AS
110
+ ClimateParams['preindustrial albedo'] = preindustrial_albedo
111
+ ClimateParams['preindustrial pH'] = 8.2
112
+ ClimateParams['starting time'] = time_preind
113
+ ClimateParams['k_al1_Tstar'] = k_al1_Tstar
114
+ ClimateParams['k_al1_deltaT'] = k_al1_deltaT
115
+ ClimateParams['fractional_k_al1_floor'] = fractional_k_al1_floor
116
+
117
+ # Albedo feedback parameters (see Wunderling et al for the origin of 0.43 here)
118
+ Delta_alpha_ASI = 0.43/AS; #print(Delta_alpha_ASI)
119
+ fractional_albedo_floor = (preindustrial_albedo+Delta_alpha_ASI)/preindustrial_albedo
120
+ ClimateParams['albedo_Tstar'] = 2
121
+ ClimateParams['albedo_delta_T'] = 1
122
+ ClimateParams['fractional_albedo_floor'] = fractional_albedo_floor
123
+
124
+ # Return
125
+ return ClimateParams
126
+
127
+ def CreateClimateState(ClimateParams):
128
+ """Creates a new climate state with default values (preindustrial)"""
129
+
130
+ # Create an empty climate state
131
+ ClimateState = {}
132
+
133
+ # Fill in some default (preindustrial) values
134
+ ClimateState['C_atm'] = ClimateParams['preindustrial C_atm']
135
+
136
+ # Pre-industrial ocean carbon reservoir needed to satisfy flux ratios in 2003
137
+ F_ao_preind = ClimateParams['k_ao'] * ClimateParams['preindustrial C_atm']
138
+ F_oa_preind = F_ao_preind
139
+ ClimateState['C_ocean'] = F_oa_preind/ClimateParams['k_oa']
140
+
141
+ # Other useful parameters
142
+ ClimateState['albedo'] = ClimateParams['preindustrial albedo']
143
+
144
+ # The temperature anomaly in preindustrial times is defined to be zero
145
+ ClimateState['T_anomaly'] = 0
146
+
147
+ # And the pH
148
+ ClimateState['pH'] = ClimateParams['preindustrial pH']
149
+
150
+ # Starting year
151
+ ClimateState['time'] = ClimateParams['starting time']
152
+
153
+ # Return the climate
154
+ return ClimateState
155
+
156
+ def sigmafloor(x,x_trans=0,x_interval=1,sigma_floor_infinity=0):
157
+ """Generates a sigmoid (smooth step-down) function with a floor"""
158
+ temp = 1 - 1/(1 + np.exp(-(x-x_trans)*3/x_interval))
159
+ return temp*(1-sigma_floor_infinity)+sigma_floor_infinity
160
+
161
+ def sigmoid(x,x_thresh=0,x_trans=1,sigma_x_infinity=0):
162
+ """Generates a sigmoid (smooth step-down) function with a floor"""
163
+ return sigmafloor(x,x_thresh,x_trans,sigma_x_infinity)
164
+
165
+ def sigmaup(t,transitiontime,transitiontimeinterval):
166
+ """Generates a sigmoid (smooth step-up) function"""
167
+ return 1 / (1 + np.exp(-(t-transitiontime)*3/transitiontimeinterval))
168
+
169
+ def sigmadown(t,transitiontime,transitiontimeinterval):
170
+ """Generates a sigmoid (smooth step-down) function"""
171
+ return 1 - sigmaup(t,transitiontime,transitiontimeinterval)
172
+
173
+ def CollectClimateTimeSeries(ClimateState_list,whatIwant):
174
+ """Collects elements from a list of dictionaries"""
175
+ array = np.empty(0)
176
+ for ClimateState in ClimateState_list:
177
+ array = np.append(array,ClimateState[whatIwant])
178
+ return array
179
+
180
+ def Diagnose_OceanSurfacepH(C_atm,ClimateParams):
181
+ """Returns ocean pH from the carbon amount in the atmosphere"""
182
+ """and the ClimateParams dictionary (preindust_pH and preindust_C_atm)"""
183
+
184
+ # Extract the preindustrial pH from ClimateParms
185
+ preindust_pH = ClimateParams['preindustrial pH']
186
+
187
+ # Extract the preindustrial carbon in the atmosphere from ClimateParams
188
+ preindust_C_atm = ClimateParams['preindustrial C_atm']
189
+
190
+ # Calculate the pH according to our algorithm
191
+ pH = -np.log10(C_atm/preindust_C_atm)+preindust_pH
192
+
193
+ # Return the diagnosed pH
194
+ return(pH)
195
+
196
+ def Diagnose_T_anomaly(C_atm, alpha, ClimateParams):
197
+ """Returns a temperature anomaly based on the C_atm, Earth's albedo,"""
198
+ """and ClimateParams parameters related to climate_sensitivity and albedo sensitivity"""
199
+
200
+ CS = ClimateParams['climate sensitivity']
201
+ preindust_C_atm = ClimateParams['preindustrial C_atm']
202
+ AS = ClimateParams['albedo sensitivity']
203
+ preindust_alpha = ClimateParams['preindustrial albedo']
204
+
205
+ # Calculate the temperature anomaly according to our algorithm
206
+ T_anomaly = CS*(C_atm-preindust_C_atm) + AS*(alpha-preindust_alpha)
207
+
208
+ # Return the temperature anomaly
209
+ return(T_anomaly)
210
+
211
+ def Diagnose_ppm(C_atm):
212
+ """Computes atmospheric carbon amount in ppm, from GtC"""
213
+ C_atm_ppm = C_atm/2.12
214
+ return(C_atm_ppm)
215
+
216
+ def Diagnose_actual_temperature(T_anomaly):
217
+ """Computes degrees C from a temperature anomaly"""
218
+ T_C = T_anomaly+14
219
+ return(T_C)
220
+
221
+ def Diagnose_degreesF(T_C):
222
+ """Converts temperature from C to F"""
223
+
224
+ # Do the conversion to F
225
+ T_F = T_C*9/5+32### END SOLUTION
226
+
227
+ # Return the diagnosed temperature in F
228
+ return(T_F)
229
+
230
+ def Diagnose_albedo_with_constraint(T_anomaly, ClimateParams, previousalbedo=0, dtime=0):
231
+ """
232
+ Returns the albedo as a function of temperature, constrained so the change can't
233
+ exceed a certain amount per year, if so flagged
234
+ """
235
+
236
+ # Find the albedo without constraint
237
+ albedo = Diagnose_albedo(T_anomaly, ClimateParams)
238
+
239
+ # Applying a constraint, if called for
240
+ if (previousalbedo !=0) & (dtime != 0):
241
+ albedo_change = albedo-previousalbedo
242
+ max_albedo_change = ClimateParams['max_albedo_change_rate']*dtime
243
+ if np.abs(albedo_change) > max_albedo_change:
244
+ this_albedo_change = np.sign(albedo_change)*max_albedo_change
245
+ albedo = previousalbedo + this_albedo_change
246
+
247
+ # Return the albedo
248
+ return albedo
249
+
250
+ def Diagnose_albedo(T_anomaly, ClimateParams):
251
+ """
252
+ Returns the albedo as a function of temperature anomaly
253
+ """
254
+
255
+ # Extract the fractional albedo floor from ClimateParams
256
+ fractional_albedo_floor = ClimateParams['fractional_albedo_floor']
257
+
258
+ # Extract the transition temperature
259
+ transitionT = ClimateParams['albedo_Tstar']
260
+
261
+ # Extract the transition temperature interval
262
+ transitionTinterval = ClimateParams['albedo_delta_T']
263
+
264
+ # Extract the preindustrial albedo
265
+ preindust_albedo = ClimateParams['preindustrial albedo']
266
+
267
+ # Compute the albedo
268
+ albedo = sigmafloor(T_anomaly,transitionT,transitionTinterval,fractional_albedo_floor)*preindust_albedo
269
+
270
+ # Return the diagnosed albedo
271
+ return albedo
272
+
273
+
274
+ def Diagnose_Delta_T_from_albedo(albedo,ClimateParams):
275
+ """
276
+ Computes additional planetary temperature increase resulting from a lower albedo
277
+ Based on the idea of radiative balance, ASR = OLR
278
+ """
279
+
280
+ # Extract parameters we need and make the diagnosis
281
+ AS = ClimateParams['albedo_sensitivity']
282
+ preindust_albedo = ClimateParams['preindust_albedo']
283
+ Delta_T_from_albedo = (albedo-preindust_albedo)*AS
284
+ return Delta_T_from_albedo
285
+
286
+ def Diagnose_Stochastic_C_atm(C_atm,ClimateParams):
287
+ """Returns a noisy version of T"""
288
+
289
+ # Extract parameters we need and make the diagnosis
290
+ Stochastic_C_atm_std_dev = ClimateParams['Stochastic_C_atm_std_dev']
291
+ C_atm_new = np.random.normal(C_atm, Stochastic_C_atm_std_dev)
292
+ return C_atm_new
293
+
294
+ def MakeEmissionsScenario(t_start,t_stop,nsteps,k,eps_0,t_0,t_peak,t_decarb):
295
+ """Returns an emissions scenario"""
296
+ t = np.linspace(t_start,t_stop,nsteps)
297
+ tp1 = t_peak + (t_decarb/3)*np.log(3/(k*t_decarb)-1)
298
+ myeps = np.exp(k*t) * eps_0*np.exp(-k*t_0) * np.exp(3/t_decarb*(tp1-t)) / (1+np.exp(3/t_decarb*(tp1-t)))
299
+ return t, myeps
300
+
301
+ def MakeEmissionsScenarioLTE(t_start,t_stop,nsteps,k,eps_0,t_0,t_peak,t_decarb,epslongterm,t_decarb_ppf_factor=1):
302
+ """Returns an emissions scenario that has a long-term, post-peak emission"""
303
+ t_decarb_ppf = t_decarb*t_decarb_ppf_factor
304
+ t = np.linspace(t_start,t_stop,nsteps)
305
+ time, eps = MakeEmissionsScenario(t_start,t_stop,nsteps,k,eps_0,t_0,t_peak,t_decarb)
306
+ neweps = PostPeakFlattener(time,eps,t_decarb_ppf,epslongterm)
307
+ return time, neweps
308
+
309
+ def MakeEmissionsScenario1(t_start,t_stop,nsteps,k,eps_0,t_0,t_trans,delta_t_trans):
310
+ """Returns an emissions scenario"""
311
+ time = np.linspace(t_start,t_stop,nsteps)
312
+ myexp = np.exp(k*time)
313
+ myN = eps_0/(np.exp(k*t_0)*sigmadown(t_0,t_trans,delta_t_trans))
314
+ mysigmadown = sigmadown(time,t_trans,delta_t_trans)
315
+ eps = myN*myexp*mysigmadown
316
+ return time, eps
317
+
318
+ def MakeEmissionsScenario2(t_start,t_stop,nsteps,k,eps_0,t_0,t_peak,delta_t_trans):
319
+ """Returns an emissions scenario parameterized by the year of peak emissions"""
320
+ t_trans = t_peak + delta_t_trans/3*np.log(3/(k*delta_t_trans)-1)
321
+ return MakeEmissionsScenario(t_start,t_stop,nsteps,k,eps_0,t_0,t_trans,delta_t_trans)
322
+
323
+ def PostPeakFlattener(time,eps,transitiontimeinterval,epslongterm):
324
+ ipeak = np.where(eps==np.max(eps))[0][0]; print('peak',eps[ipeak],ipeak)
325
+ b = eps[ipeak]
326
+ a = epslongterm
327
+ neweps = makeacopy(eps)
328
+ for i in range(ipeak,len(eps)):
329
+ ipostpeak = i-ipeak
330
+ neweps[i] = a + np.exp(-(time[i]-time[ipeak])**2/transitiontimeinterval**2)*(b-a)
331
+ return neweps
332
+
333
+ def MakeEmissionsScenario1LTE(t_start,t_stop,nsteps,k,eps_0,t_0,t_trans,delta_t_trans,epslongterm):
334
+ time, eps = MakeEmissionsScenario(t_start,t_stop,nsteps,k,eps_0,t_0,t_trans,delta_t_trans)
335
+ neweps = PostPeakFlattener(time,eps,delta_t_trans,epslongterm)
336
+ return time, neweps
337
+
338
+ def MakeEmissionsScenario2LTE(t_start,t_stop,nsteps,k,eps_0,t_0,t_peak,delta_t_trans,epslongterm):
339
+ time, eps = MakeEmissionsScenario2(t_start,t_stop,nsteps,k,eps_0,t_0,t_peak,delta_t_trans)
340
+ neweps = PostPeakFlattener(time,eps,delta_t_trans,epslongterm)
341
+ return time, neweps
342
+
343
+ def MakeHybridEmissionScenario(\
344
+ t_start,t_stop,EnROADS_time,EnROADS_eps,filename,k=0.0166, nsteps=1000,reportflag=False,plotflag=False):
345
+
346
+ # Extracting the first emission entry of the baseline scenario, for pegging the cambio scenario
347
+ eps_0=EnROADS_eps[0]; print(eps_0)
348
+ time_0=EnROADS_time[0]; print(time_0)
349
+
350
+ # Creating a new cambio scenario compatible pegged to the baseline scenario
351
+ delta_t_trans = 20
352
+ t_peak = time_0+1
353
+ epslongterm = 0
354
+ intermediate_filename = 'ignore_this_file.txt'
355
+ time_cambio, eps_cambio = MakeEmissionsScenario2LTE(t_start,t_stop,nsteps,k,eps_0,time_0,t_peak,delta_t_trans,epslongterm)
356
+
357
+ # Create a set of emissions equal to EnROADS, but interpolated to the cambio times
358
+ dt = time_cambio[1]-time_cambio[0]
359
+ times_to_interpolate = np.arange(time_0+dt,t_stop,dt)
360
+ eps_interpolated = np.interp(times_to_interpolate,EnROADS_time,EnROADS_eps)
361
+
362
+ # Create the hybrid emission scenario
363
+ i_pegged,val = min(enumerate(time_cambio), key=lambda x: abs(x[1]-(time_0+dt))); print(i_pegged,val)
364
+ eps_hybrid = np.append(eps_cambio[0:i_pegged],eps_interpolated); print(len(eps_hybrid))
365
+
366
+ # Create an empty dictionary
367
+ epsdictionary = dict()
368
+
369
+ # Create an empty dataframe
370
+ epsdf = pd.DataFrame()
371
+
372
+ # Insert the time and emissions columns into the dataframe
373
+ epsdf.insert(loc=0, column='time', value=time_cambio)
374
+ epsdf.insert(loc=1, column='emissions', value=eps_hybrid)
375
+
376
+ # Add the dataframe to the dictionary
377
+ epsdictionary['dataframe'] = epsdf
378
+
379
+ # This saves it, if we change the flag to True
380
+ h5io.write_hdf5(filename, epsdictionary, overwrite=True)
381
+
382
+ return
383
+
384
+ def run_Cambio(PropagateClimateState, ClimateParams, epstime, eps):
385
+
386
+ # Make the starting state the preindustrial
387
+ ClimateState = CreateClimateState(ClimateParams)
388
+
389
+ # Initialize our list of climate states
390
+ ClimateState_list = []
391
+
392
+ # The time interval
393
+ dt = epstime[1]-epstime[0]
394
+
395
+ # Loop over all the times after the initial one in the scheduled flow
396
+ for i in range(1,len(epstime)):
397
+
398
+ # Propagate
399
+ ClimateState = PropagateClimateState(ClimateState,ClimateParams,dt,eps[i])
400
+
401
+ # Add to our list of climate states
402
+ ClimateState_list.append(ClimateState)
403
+
404
+ return ClimateState_list
405
+
406
+
407
+ def PropagateClimateState_Cambio4(previousClimateState, ClimateParams, dt, F_ha):
408
+ """Propagates the state of the climate, with a specified anthropogenic carbon flux"""
409
+ """Returns a new climate state"""
410
+
411
+ # Extract constants from ClimateParams
412
+ k_la = ClimateParams['k_la']
413
+ k_al0 = ClimateParams['k_al0']
414
+ k_al1 = ClimateParams['k_al1']
415
+ k_oa = ClimateParams['k_oa']
416
+ k_ao = ClimateParams['k_ao']
417
+ DC = ClimateParams['DC']
418
+ preindustrial_albedo = ClimateParams['preindustrial albedo']
419
+ fractional_albedo_floor = ClimateParams['fractional_albedo_floor']
420
+ albedo_Tstar = ClimateParams['albedo_Tstar']
421
+ albedo_delta_T = ClimateParams['albedo_delta_T']
422
+ k_al1_Tstar = ClimateParams['k_al1_Tstar']
423
+ k_al1_deltaT = ClimateParams['k_al1_deltaT']
424
+ fractional_k_al1_floor = ClimateParams['fractional_k_al1_floor']
425
+
426
+ # Extract concentrations, albedo, etc, from the previous climate state
427
+ C_atm = previousClimateState['C_atm']
428
+ C_ocean = previousClimateState['C_ocean']
429
+ albedo = previousClimateState['albedo']
430
+ time = previousClimateState['time']
431
+
432
+ # Get the temperature implied by the carbon in the atmosphere and the updated albedo, and ocean pH
433
+ T_anomaly = Diagnose_T_anomaly(C_atm, albedo, ClimateParams)
434
+ actual_temperature = Diagnose_actual_temperature(T_anomaly)
435
+ OceanSurfacepH = Diagnose_OceanSurfacepH(C_atm,ClimateParams)
436
+
437
+ # Get the albedo implied by the temperature anomaly
438
+ albedo = Diagnose_albedo(T_anomaly, ClimateParams)
439
+
440
+ # Get new fluxes (including the effect of temperature anomaly on the ocean-to-atmosphere flux)
441
+ F_la = k_la
442
+ F_al = k_al0 + k_al1*C_atm*sigmafloor(T_anomaly,k_al1_Tstar,k_al1_deltaT,fractional_k_al1_floor)
443
+ F_oa = k_oa*C_ocean*(1+DC*T_anomaly)
444
+ F_ao = k_ao*C_atm
445
+
446
+ # Get new concentrations of carbon that depend on the fluxes
447
+ C_atm += (F_la + F_oa - F_ao - F_al + F_ha)*dt
448
+ C_ocean += (F_ao - F_oa)*dt
449
+ time += dt
450
+
451
+ # Ocean surface diagnostic
452
+ OceanSurfacepH = Diagnose_OceanSurfacepH(C_atm,ClimateParams)
453
+
454
+ # Create a new climate state with these updates
455
+ ClimateState = makeacopy(previousClimateState)
456
+ ClimateState['C_atm'] = C_atm
457
+ ClimateState['C_ocean'] = C_ocean
458
+ ClimateState['F_al'] = F_al
459
+ ClimateState['F_la'] = F_la
460
+ ClimateState['F_ao'] = F_ao
461
+ ClimateState['F_oa'] = F_oa
462
+ ClimateState['F_ha'] = F_ha
463
+ ClimateState['F_ocean_net'] = F_oa-F_ao
464
+ ClimateState['F_land_net'] = F_la-F_al
465
+ ClimateState['time'] = time
466
+ ClimateState['T_anomaly'] = T_anomaly
467
+ ClimateState['actual temperature'] = actual_temperature
468
+ ClimateState['OceanSurfacepH'] = OceanSurfacepH
469
+ ClimateState['albedo'] = albedo
470
+
471
+ # Return the new climate state
472
+ return ClimateState
473
+
474
+ def PropagateClimateState_Cambio3(previousClimateState, ClimateParams, dt, F_ha):
475
+ """Propagates the state of the climate including Henry feedback and ice-albedo feedback"""
476
+
477
+ # Extract constants from ClimateParams
478
+ k_la = ClimateParams['k_la']
479
+ k_al0 = ClimateParams['k_al0']
480
+ k_al1 = ClimateParams['k_al1']
481
+ k_oa = ClimateParams['k_oa']
482
+ k_ao = ClimateParams['k_ao']
483
+ DC = ClimateParams['DC']
484
+ preindustrial_albedo = ClimateParams['preindustrial albedo']
485
+ fractional_albedo_floor = ClimateParams['fractional_albedo_floor']
486
+ albedo_Tstar = ClimateParams['albedo_Tstar']
487
+ albedo_delta_T = ClimateParams['albedo_delta_T']
488
+
489
+ # Extract concentrations, albedo, etc, from the previous climate state
490
+ C_atm = previousClimateState['C_atm']
491
+ C_ocean = previousClimateState['C_ocean']
492
+ albedo = previousClimateState['albedo']
493
+ time = previousClimateState['time']
494
+
495
+ # Get the temperature implied by the carbon in the atmosphere and the updated albedo, and ocean pH
496
+ T_anomaly = Diagnose_T_anomaly(C_atm, albedo, ClimateParams)
497
+ actual_temperature = Diagnose_actual_temperature(T_anomaly)
498
+ OceanSurfacepH = Diagnose_OceanSurfacepH(C_atm,ClimateParams)
499
+
500
+ # Get the albedo implied by the temperature anomaly
501
+ albedo = Diagnose_albedo(T_anomaly, ClimateParams)
502
+
503
+ # Get new fluxes (including the effect of temperature anomaly on the ocean-to-atmosphere flux)
504
+ F_la = k_la
505
+ F_al = k_al0 + k_al1*C_atm
506
+ F_oa = k_oa*C_ocean*(1+DC*T_anomaly)
507
+ F_ao = k_ao*C_atm
508
+
509
+ # Get new concentrations of carbon that depend on the fluxes
510
+ C_atm += (F_la + F_oa - F_ao - F_al + F_ha)*dt
511
+ C_ocean += (F_ao - F_oa)*dt
512
+ time += dt
513
+
514
+ # Ocean surface diagnostic
515
+ OceanSurfacepH = Diagnose_OceanSurfacepH(C_atm,ClimateParams)
516
+
517
+ # Create a new climate state with these updates
518
+ ClimateState = makeacopy(previousClimateState)
519
+ ClimateState['C_atm'] = C_atm
520
+ ClimateState['C_ocean'] = C_ocean
521
+ ClimateState['F_al'] = F_al
522
+ ClimateState['F_la'] = F_la
523
+ ClimateState['F_ao'] = F_ao
524
+ ClimateState['F_oa'] = F_oa
525
+ ClimateState['F_ha'] = F_ha
526
+ ClimateState['F_ocean_net'] = F_oa-F_ao
527
+ ClimateState['F_land_net'] = F_la-F_al
528
+ ClimateState['time'] = time
529
+ ClimateState['T_anomaly'] = T_anomaly
530
+ ClimateState['actual temperature'] = actual_temperature
531
+ ClimateState['OceanSurfacepH'] = OceanSurfacepH
532
+ ClimateState['albedo'] = albedo
533
+
534
+ # Return the new climate state
535
+ return ClimateState
536
+
537
+ def PropagateClimateState_Cambio2(previousClimateState, ClimateParams, dt, F_ha):
538
+ """Propagates the state of the climate including Henry feedback"""
539
+
540
+ # Extract constants from ClimateParams
541
+ k_la = ClimateParams['k_la']
542
+ k_al0 = ClimateParams['k_al0']
543
+ k_al1 = ClimateParams['k_al1']
544
+ k_oa = ClimateParams['k_oa']
545
+ k_ao = ClimateParams['k_ao']
546
+ DC = ClimateParams['DC']
547
+ preindustrial_albedo = ClimateParams['preindustrial albedo']
548
+ fractional_albedo_floor = ClimateParams['fractional_albedo_floor']
549
+ albedo_Tstar = ClimateParams['albedo_Tstar']
550
+ albedo_delta_T = ClimateParams['albedo_delta_T']
551
+
552
+ # Extract concentrations, albedo, etc, from the previous climate state
553
+ C_atm = previousClimateState['C_atm']
554
+ C_ocean = previousClimateState['C_ocean']
555
+ albedo = previousClimateState['albedo']
556
+ time = previousClimateState['time']
557
+
558
+ # Get the temperature anomaly implied by the carbon in the atmosphere and the preindustrial albedo
559
+ T_anomaly = Diagnose_T_anomaly(C_atm, preindustrial_albedo, ClimateParams)
560
+ actual_temperature = Diagnose_actual_temperature(T_anomaly)
561
+ OceanSurfacepH = Diagnose_OceanSurfacepH(C_atm,ClimateParams)
562
+
563
+ # Get new fluxes (including the effect of temperature anomaly on the ocean-to-atmosphere flux)
564
+ F_la = k_la
565
+ F_al = k_al0 + k_al1*C_atm
566
+ F_oa = k_oa*C_ocean*(1+DC*T_anomaly)
567
+ F_ao = k_ao*C_atm
568
+
569
+ # Get new concentrations of carbon that depend on the fluxes
570
+ C_atm += (F_la + F_oa - F_ao - F_al + F_ha)*dt
571
+ C_ocean += (F_ao - F_oa)*dt
572
+ time += dt
573
+
574
+ # Create a new climate state with these updates
575
+ ClimateState = makeacopy(previousClimateState)
576
+ ClimateState['C_atm'] = C_atm
577
+ ClimateState['C_ocean'] = C_ocean
578
+ ClimateState['F_al'] = F_al
579
+ ClimateState['F_la'] = F_la
580
+ ClimateState['F_ao'] = F_ao
581
+ ClimateState['F_oa'] = F_oa
582
+ ClimateState['F_ha'] = F_ha
583
+ ClimateState['F_ocean_net'] = F_oa-F_ao
584
+ ClimateState['F_land_net'] = F_la-F_al
585
+ ClimateState['time'] = time
586
+ ClimateState['T_anomaly'] = T_anomaly
587
+ ClimateState['actual temperature'] = actual_temperature
588
+ ClimateState['OceanSurfacepH'] = OceanSurfacepH
589
+ ClimateState['albedo'] = albedo
590
+
591
+ # Return the new climate state
592
+ return ClimateState
593
+
594
+ def PropagateClimateState_Cambio1(previousClimateState, ClimateParams, dt, F_ha):
595
+ """Propagates the state of the climate with no feedback"""
596
+
597
+ # Extract constants from ClimateParams
598
+ k_la = ClimateParams['k_la']
599
+ k_al0 = ClimateParams['k_al0']
600
+ k_al1 = ClimateParams['k_al1']
601
+ k_oa = ClimateParams['k_oa']
602
+ k_ao = ClimateParams['k_ao']
603
+ DC = ClimateParams['DC']
604
+
605
+ # Extract concentrations, albedo, etc, from the previous climate state
606
+ C_atm = previousClimateState['C_atm']
607
+ C_ocean = previousClimateState['C_ocean']
608
+ albedo = previousClimateState['albedo']
609
+ time = previousClimateState['time']
610
+
611
+ # Get the temperature anomaly implied by the carbon in the atmosphere and the albedo
612
+ T_anomaly = Diagnose_T_anomaly(C_atm, albedo, ClimateParams)
613
+ actual_temperature = Diagnose_actual_temperature(T_anomaly)
614
+ OceanSurfacepH = Diagnose_OceanSurfacepH(C_atm,ClimateParams)
615
+
616
+ # Get new fluxes (including the effect of temperature anomaly on the ocean-to-atmosphere flux)
617
+ F_la = k_la
618
+ F_al = k_al0 + k_al1*C_atm
619
+ F_oa = k_oa*C_ocean
620
+ F_ao = k_ao*C_atm
621
+
622
+ # Get new concentrations of carbon that depend on the fluxes
623
+ C_atm += (F_la + F_oa - F_ao - F_al + F_ha)*dt
624
+ C_ocean += (F_ao - F_oa)*dt
625
+ time += dt
626
+
627
+ # Create a new climate state with these updates
628
+ ClimateState = makeacopy(previousClimateState)
629
+ ClimateState['C_atm'] = C_atm
630
+ ClimateState['C_ocean'] = C_ocean
631
+ ClimateState['F_al'] = F_al
632
+ ClimateState['F_la'] = F_la
633
+ ClimateState['F_ao'] = F_ao
634
+ ClimateState['F_oa'] = F_oa
635
+ ClimateState['F_ha'] = F_ha
636
+ ClimateState['F_ocean_net'] = F_oa-F_ao
637
+ ClimateState['F_land_net'] = F_la-F_al
638
+ ClimateState['time'] = time
639
+ ClimateState['T_anomaly'] = T_anomaly
640
+ ClimateState['actual temperature'] = actual_temperature
641
+ ClimateState['OceanSurfacepH'] = OceanSurfacepH
642
+ ClimateState['albedo'] = albedo
643
+
644
+ # Return the new climate state
645
+ return ClimateState
646
+
647
+ def printmaxmin(t,y,label=''):
648
+ imax = np.argmax(y)
649
+ print('Max of '+label+' = ',y[imax], 'at time ', t[imax])
650
+ imin = np.argmin(y)
651
+ print('Min of '+label+' = ',y[imin], 'at time ', t[imin])
652
+
653
+ def CS_list_plots(ClimateState_list,plot_title,items_to_plot,colorlist=['k','b','g','r']):
654
+ linewidth = 3
655
+ time_array = CollectClimateTimeSeries(ClimateState_list,'time')
656
+ for item in items_to_plot:
657
+ plt.figure()
658
+ plt.title(plot_title)
659
+ plt.grid(True)
660
+ plt.xlabel('time (years)')
661
+
662
+ if np.size(item) == 1:
663
+ item_array = CollectClimateTimeSeries(ClimateState_list,item)
664
+ if len(plot_title) != 0:
665
+ plotlabel = plot_title+' ('+item+')'
666
+ else:
667
+ plotlabel = item
668
+ printmaxmin(time_array,item_array,label=plotlabel)
669
+ plt.plot(time_array,item_array,label=item,color=colorlist[0],linewidth=linewidth)
670
+ else:
671
+ icolor = 0
672
+ for subitem in item:
673
+ subitem_array = CollectClimateTimeSeries(ClimateState_list,subitem)
674
+ printmaxmin(time_array,subitem_array,label=plot_title+' ('+subitem+')')
675
+ if len(plot_title) != 0:
676
+ plotlabel = plot_title+' ('+subitem+')'
677
+ else:
678
+ plotlabel = subitem
679
+ plt.plot(time_array,subitem_array,label=plotlabel,color=colorlist[icolor],linewidth=linewidth)
680
+ icolor += 1
681
+ plt.legend()
682
+ plt.show()
683
+ return
684
+
685
+ def CS_list_compare(CS_Cambio_lists,plot_titles,items_to_plot,colorlist=['k','b','g','r']):
686
+ linewidth = 3
687
+ alpha = 0.3
688
+ CS_CambioA_list, CS_CambioB_list = CS_Cambio_lists
689
+ plot_titleA, plot_titleB = plot_titles
690
+ plot_title = plot_titleA+' and '+plot_titleB
691
+ time_arrayA = CollectClimateTimeSeries(CS_CambioA_list,'time')
692
+ time_arrayB = CollectClimateTimeSeries(CS_CambioB_list,'time')
693
+ for item in items_to_plot:
694
+ plt.figure()
695
+ plt.title(plot_title)
696
+ plt.grid(True)
697
+ plt.xlabel('time (years)')
698
+
699
+ if np.size(item) == 1:
700
+ item_arrayA = CollectClimateTimeSeries(CS_CambioA_list,item)
701
+ item_arrayB = CollectClimateTimeSeries(CS_CambioB_list,item)
702
+ if len(plot_titleA) != 0:
703
+ plotlabelA = plot_titleA+' ('+item+')'
704
+ else:
705
+ plotlabelA = item
706
+ if len(plot_titleB) != 0:
707
+ plotlabelB = plot_titleB+' ('+item+')'
708
+ else:
709
+ plotlabelB = item
710
+ plt.plot(time_arrayA,item_arrayA,label=plotlabelA,color=colorlist[0],linewidth=linewidth,alpha=alpha)
711
+ plt.plot(time_arrayB,item_arrayB,label=plotlabelB,color=colorlist[0],linewidth=linewidth)
712
+ else:
713
+ icolor = 0
714
+ for subitem in item:
715
+ subitem_arrayA = CollectClimateTimeSeries(CS_CambioA_list,subitem)
716
+ subitem_arrayB = CollectClimateTimeSeries(CS_CambioB_list,subitem)
717
+ printmaxmin(time_arrayA,subitem_arrayA,label=plot_titleA+' ('+subitem+')')
718
+ printmaxmin(time_arrayB,subitem_arrayB,label=plot_titleB+' ('+subitem+')')
719
+ if len(plot_titleA) != 0:
720
+ plotlabelA = plot_titleA+' ('+subitem+')'
721
+ else:
722
+ plotlabelA = subitem
723
+ if len(plot_titleB) != 0:
724
+ plotlabelB = plot_titleB+' ('+subitem+')'
725
+ else:
726
+ plotlabelB = subitem
727
+ plt.plot(time_arrayA,subitem_arrayA,label=plotlabelA,color=colorlist[icolor],linewidth=linewidth,alpha=alpha)
728
+ plt.plot(time_arrayB,subitem_arrayB,label=plotlabelB,color=colorlist[icolor],linewidth=linewidth)
729
+ icolor += 1
730
+ plt.legend()
731
+ plt.show()
732
+ return
733
+
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: meclib
3
+ Version: 0.1.1
4
+ Summary: Code for Modeling Earth's Climate course
5
+ Author-email: Steven Neshyba <sneshyba@gmail.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: numpy
9
+ Requires-Dist: matplotlib
10
+ Requires-Dist: pandas
11
+ Requires-Dist: pint
@@ -0,0 +1,10 @@
1
+ pyproject.toml
2
+ examples/CambioComparisons.py
3
+ examples/MakeEmissionScenario.py
4
+ meclib/__init__.py
5
+ meclib/cl.py
6
+ meclib.egg-info/PKG-INFO
7
+ meclib.egg-info/SOURCES.txt
8
+ meclib.egg-info/dependency_links.txt
9
+ meclib.egg-info/requires.txt
10
+ meclib.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ numpy
2
+ matplotlib
3
+ pandas
4
+ pint
@@ -0,0 +1,3 @@
1
+ dist
2
+ examples
3
+ meclib
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "meclib"
3
+ version = "0.1.1"
4
+ description = "Code for Modeling Earth's Climate course"
5
+ authors = [{ name = "Steven Neshyba", email = "sneshyba@gmail.com" }]
6
+ readme = "README.md"
7
+ requires-python = ">=3.8"
8
+ dependencies = ['numpy', 'matplotlib', 'pandas', 'pint']
9
+
10
+ [build-system]
11
+ requires = ["setuptools>=61.0", "wheel"]
12
+ build-backend = "setuptools.build_meta"
13
+
14
+ [tool.setuptools.packages.find]
15
+ where = ["."]
meclib-0.1.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+