1
1
#!/usr/bin/env python
2
2
# -*- coding: utf-8 -*-
3
3
from __future__ import division
4
- from math import floor , sqrt
4
+ from math import floor
5
5
import numpy as np
6
6
import pandas as pd
7
- from sympy import symbols , solve , Eq
8
7
from beam_carbon .temperature import DICETemperature , LinearTemperature
9
8
10
9
11
- __version__ = '0.2 '
10
+ __version__ = '0.3 '
12
11
13
12
14
13
class BEAMCarbon (object ):
@@ -18,11 +17,13 @@ def __init__(self, emissions=None, time_step=1, intervals=10):
18
17
"""BEAMCarbon init
19
18
20
19
Args:
21
- :param emissions: Array of annual emissions in GtC, beginning in 2005
20
+ :param emissions: Array of annual emissions in GtC, beginning
21
+ in 2005
22
22
:type emissions: list
23
23
:param time_step: Time between emissions values in years
24
24
:type time_step: float
25
- :param intervals: Nuber of times to calculate BEAM carbon per timestep
25
+ :param intervals: Number of times to calculate BEAM carbon
26
+ per timestep
26
27
:type intervals: int
27
28
"""
28
29
self ._temperature_dependent = True
@@ -40,6 +41,10 @@ def __init__(self, emissions=None, time_step=1, intervals=10):
40
41
self ._k_h = 1.23e3
41
42
self ._A = None
42
43
self ._B = None
44
+ self ._Alk = 767.
45
+
46
+ self ._initial_carbon = np .array ([808.9 , 725. , 35641. ])
47
+ self ._carbon_mass = None
43
48
44
49
self ._linear_temperature = False
45
50
@@ -48,7 +53,21 @@ def initial_carbon(self):
48
53
"""Values for initial carbon in atmosphere, upper and lower oceans
49
54
in GtC. Default values are from 2005.
50
55
"""
51
- return np .array ([808.9 , 725. , 35641. ])
56
+ return self ._initial_carbon
57
+
58
+ @initial_carbon .setter
59
+ def initial_carbon (self , value ):
60
+ self ._initial_carbon = value
61
+
62
+ @property
63
+ def carbon_mass (self ):
64
+ if self ._carbon_mass is None :
65
+ self ._carbon_mass = self .initial_carbon .copy ()
66
+ return self ._carbon_mass
67
+
68
+ @carbon_mass .setter
69
+ def carbon_mass (self , value ):
70
+ self ._carbon_mass = value
52
71
53
72
@property
54
73
def transfer_matrix (self ):
@@ -121,6 +140,8 @@ def delta(self):
121
140
def k_h (self ):
122
141
"""CO2 solubility.
123
142
"""
143
+ if self ._k_h is None :
144
+ self ._k_h = self .get_kh (self .temperature .initial_temp [1 ])
124
145
return self ._k_h
125
146
126
147
@k_h .setter
@@ -129,8 +150,10 @@ def k_h(self, value):
129
150
130
151
@property
131
152
def k_1 (self ):
132
- """First dissacoiation constant.
153
+ """First dissociation constant.
133
154
"""
155
+ if self ._k_1 is None :
156
+ self ._k_1 = self .get_k1 (self .temperature .initial_temp [1 ])
134
157
return self ._k_1
135
158
136
159
@k_1 .setter
@@ -139,8 +162,10 @@ def k_1(self, value):
139
162
140
163
@property
141
164
def k_2 (self ):
142
- """Second dissacoiation constant.
165
+ """Second dissociation constant.
143
166
"""
167
+ if self ._k_2 is None :
168
+ self ._k_2 = self .get_k2 (self .temperature .initial_temp [1 ])
144
169
return self ._k_2
145
170
146
171
@k_2 .setter
@@ -163,7 +188,11 @@ def OM(self):
163
188
def Alk (self ):
164
189
"""Alkalinity in GtC.
165
190
"""
166
- return 767.
191
+ return self ._Alk
192
+
193
+ @Alk .setter
194
+ def Alk (self , value ):
195
+ self ._Alk = value
167
196
168
197
@property
169
198
def A (self ):
@@ -200,7 +229,7 @@ def salinity(self):
200
229
201
230
@property
202
231
def temperature_dependent (self ):
203
- """Switch for calculating temperature-dependent paramters k_1,
232
+ """Switch for calculating temperature-dependent parameters k_1,
204
233
k_2, and k_h.
205
234
"""
206
235
return self ._temperature_dependent
@@ -212,9 +241,11 @@ def linear_temperature(self):
212
241
@linear_temperature .setter
213
242
def linear_temperature (self , value ):
214
243
if value :
215
- self .temperature = LinearTemperature (self .time_step , self .intervals , self .n )
244
+ self .temperature = LinearTemperature (self .time_step ,
245
+ self .intervals , self .n )
216
246
else :
217
- self .temperature = DICETemperature (self .time_step , self .intervals , self .n )
247
+ self .temperature = DICETemperature (self .time_step ,
248
+ self .intervals , self .n )
218
249
self ._linear_temperature = value
219
250
220
251
@temperature_dependent .setter
@@ -251,7 +282,7 @@ def get_A(self):
251
282
"""
252
283
return self .k_h * self .AM / (self .OM / (self .delta + 1 ))
253
284
254
- def get_H (self , mass_upper , re_solve = False ):
285
+ def get_H (self , mass_upper ):
255
286
"""Solve for H+, the concentration of hydrogen ions
256
287
(the (pH) of seawater).
257
288
@@ -260,21 +291,10 @@ def get_H(self, mass_upper, re_solve=False):
260
291
:return: H
261
292
:rtype: float
262
293
"""
263
- if re_solve :
264
- h = symbols ('h' )
265
- a = mass_upper / self .Alk
266
- f = Eq (
267
- (h ** 2 + self .k_1 * h + self .k_1 * self .k_2 ) / self .k_1 ,
268
- a * (h + 2 * self .k_2 )
269
- )
270
- return max (solve (f , h ))
271
- return (
272
- - self .k_1 * (self .Alk - mass_upper ) / (2 * self .Alk ) +
273
- sqrt (self .k_1 * (self .Alk ** 2 * self .k_1 -
274
- 4 * self .Alk ** 2 * self .k_2 -
275
- 2 * self .Alk * self .k_1 * mass_upper +
276
- 8 * self .Alk * self .k_2 * mass_upper +
277
- self .k_1 * mass_upper ** 2 )) / (2 * self .Alk ))
294
+ p0 = 1
295
+ p1 = (self .k_1 - mass_upper * self .k_1 / self .Alk )
296
+ p2 = (1 - 2 * mass_upper / self .Alk ) * self .k_1 * self .k_2
297
+ return max (np .roots ([p0 , p1 , p2 ]))
278
298
279
299
def get_kh (self , temp_ocean ):
280
300
"""Calculate temperature dependent k_h
@@ -293,6 +313,18 @@ def get_kh(self, temp_ocean):
293
313
self .A = kh * self .AM / (self .OM / (self .delta + 1. ))
294
314
return kh
295
315
316
+ def get_pk1 (self , t ):
317
+ return (
318
+ - 13.721 + 0.031334 * t + 3235.76 / t + 1.3e-5 * self .salinity * t -
319
+ 0.1031 * self .salinity ** 0.5 )
320
+
321
+ def get_pk2 (self , t ):
322
+ return (
323
+ 5371.96 + 1.671221 * t + 0.22913 * self .salinity +
324
+ 18.3802 * np .log10 (self .salinity )) - (128375.28 / t +
325
+ 2194.30 * np .log10 (t ) + 8.0944e-4 * self .salinity * t +
326
+ 5617.11 * np .log10 (self .salinity ) / t ) + 2.136 * self .salinity / t
327
+
296
328
def get_k1 (self , temp_ocean ):
297
329
"""Calculate temperature dependent k_1
298
330
@@ -301,11 +333,7 @@ def get_k1(self, temp_ocean):
301
333
:return: k_1
302
334
:rtype: float
303
335
"""
304
- t = 283.15 + temp_ocean
305
- pk1 = (
306
- - 13.721 + 0.031334 * t + 3235.76 / t + 1.3e-5 * self .salinity * t -
307
- 0.1031 * self .salinity ** 0.5 )
308
- return 10 ** - pk1
336
+ return 10 ** - self .get_pk1 (283.15 + temp_ocean )
309
337
310
338
def get_k2 (self , temp_ocean ):
311
339
"""Calculate temperature dependent k_2
@@ -315,72 +343,71 @@ def get_k2(self, temp_ocean):
315
343
:return: k_2
316
344
:rtype: float
317
345
"""
318
- t = 283.15 + temp_ocean
319
- pk2 = (
320
- 5371.96 + 1.671221 * t + 0.22913 * self .salinity +
321
- 18.3802 * np .log10 (self .salinity )) - (128375.28 / t +
322
- 2194.30 * np .log10 (t ) + 8.0944e-4 * self .salinity * t +
323
- 5617.11 * np .log10 (self .salinity ) / t ) + 2.136 * self .salinity / t
324
- return 10 ** - pk2
346
+ return 10 ** - self .get_pk2 (283.15 + temp_ocean )
325
347
326
348
def run (self ):
327
349
N = self .n * self .intervals
350
+ self .carbon_mass = self .initial_carbon .copy ()
351
+ total_carbon = 0
352
+ emissions = np .zeros (3 )
353
+ temp_atmosphere , temp_ocean = self .temperature .initial_temp
354
+
328
355
output = np .tile (np .concatenate ((
329
356
self .initial_carbon ,
330
357
self .temperature .initial_temp ,
331
358
np .array ([
332
359
self .transfer_matrix [0 ][1 ], self .transfer_matrix [1 ][1 ]]),
333
360
np .zeros (3 ),
334
- )).reshape ((10 , 1 )).copy (), self .n + 1 )
361
+ )).reshape ((10 , 1 )).copy (), ( self .n + 1 , ) )
335
362
output = pd .DataFrame (
336
363
output ,
337
364
index = ['mass_atmosphere' , 'mass_upper' , 'mass_lower' ,
338
365
'temp_atmosphere' , 'temp_ocean' , 'phi12' , 'phi22' ,
339
366
'cumulative' , 'A' , 'B' ],
340
367
columns = np .arange (self .n + 1 ) * self .time_step ,
341
368
)
342
- carbon_mass = self .initial_carbon .copy ()
343
- total_carbon = 0
344
- emissions = np .zeros (3 )
345
- temp_atmosphere , temp_ocean = self .temperature .initial_temp
346
369
347
370
for i in xrange (N ):
348
371
349
372
_i = int (floor (i / self .intervals )) # time_step
350
373
351
- if i % self .intervals == 0 and self .temperature_dependent : # First interval in time step
374
+ if i % self .intervals == 0 and self .temperature_dependent :
352
375
self .temp_calibrate (temp_ocean )
353
376
354
- h = self .get_H (carbon_mass [1 ], re_solve = False )
377
+ h = self .get_H (self . carbon_mass [1 ])
355
378
self .B = self .get_B (h )
356
379
357
- if i % self .intervals == 0 : # First interval in time step
380
+ emissions [0 ] = self .emissions [_i ] * self .time_step / self .intervals
381
+ total_carbon += emissions [0 ]
382
+
383
+ self .carbon_mass += (
384
+ (self .transfer_matrix *
385
+ np .divide (self .carbon_mass , self .intervals )).sum (axis = 1 ) +
386
+ emissions )
387
+
388
+ if (i + 1 ) % self .intervals == 0 :
358
389
359
390
emissions [0 ] = self .emissions [_i ] * self .time_step
360
391
total_carbon += emissions [0 ]
361
392
362
393
ta = temp_atmosphere
363
394
temp_atmosphere = self .temperature .temp_atmosphere (
364
395
index = _i , temp_atmosphere = ta ,
365
- temp_ocean = temp_ocean , mass_atmosphere = carbon_mass [0 ],
396
+ temp_ocean = temp_ocean , mass_atmosphere = self . carbon_mass [0 ],
366
397
carbon = total_carbon , initial_carbon = self .initial_carbon ,
367
398
phi11 = self .transfer_matrix [0 ][0 ],
368
399
phi21 = self .transfer_matrix [1 ][0 ])
369
400
temp_ocean = self .temperature .temp_ocean (
370
401
ta , temp_ocean )
371
402
372
- carbon_mass += (((self .transfer_matrix * carbon_mass ) /
373
- self .intervals ).sum (axis = 1 ) +
374
- emissions / self .intervals )
375
-
376
- if (i + 1 ) % self .intervals == 0 :
377
403
output .iloc [:, _i + 1 ] = (
378
- np .concatenate ((carbon_mass .copy (),
404
+ np .concatenate ((self . carbon_mass .copy (),
379
405
np .array ([temp_atmosphere , temp_ocean ]),
380
406
np .array ([self .transfer_matrix [0 ][1 ],
381
407
self .transfer_matrix [1 ][1 ],
382
408
total_carbon ,
383
409
self .A , self .B ]))))
410
+
384
411
return output
385
412
386
413
@@ -441,9 +468,16 @@ def write_beam(output, csv=None):
441
468
if __name__ == '__main__' :
442
469
b = BEAMCarbon ()
443
470
b .time_step = 10.
444
- b .intervals = 20
471
+ b .intervals = 200
445
472
N = 100
446
- b .emissions = np .concatenate ((10. * np .exp (- np .arange (N ) / 40 ), np .zeros (100 - N )))
473
+ b .emissions = np .array ([
474
+ # 7.10, 7.97,
475
+ 9.58 , 12.25 , 14.72 , 16.07 , 17.43 , 19.16 , 20.89 , 23.22 , 26.15 , 29.09
476
+ ])
477
+ # df = pd.DataFrame.from_csv('webDICE-CSV.csv', header=-1, index_col=0)
478
+ # b.emissions = np.array(df.ix['emissions_total', :])
479
+ # b.emissions = np.concatenate((10. * np.exp(-np.arange(N) / 40), np.zeros(100-N)))
447
480
b .temperature_dependent = False
448
481
b .linear_temperature = False
449
- r = b .run ()
482
+ r = b .run ()
483
+ print (r )
0 commit comments