Coverage for agent_model/agents/concrete.py: 100%
40 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
1from . import BaseAgent
2from ..util import recursively_check_required_kwargs
3import numpy as np
5class ConcreteAgent(BaseAgent):
6 """One exposed square meter of carbonating concrete
8 ====================== ============== ===============
9 Attribute Type Description
10 ====================== ============== ===============
11 ``carbonation_rate`` float Current co2-dependent carbonation rate, in kmoles
12 ``carbonation`` float Total lifetime carbonation, in kmoles
13 ====================== ============== ===============
15 This agent's exchange values are equal to the molar mass (g/mol) of the respective
16 compounds, so when multiplied by the above attributes, the results are in kg.
17 """
18 diffusion_rate = .000018 # Tune manually. Match Table 2 total kmoles
19 saturation_when_measured = 0.3 # Tune manually. Lit suggests up to 20yr of carb.
20 rate_scale = [12.7, 12.7 + 35 / saturation_when_measured] # Figure 2 integrals
21 ppm_range = [350, 3000] # External and enclosed ppms
22 density = 1.21 / 1000 # Table 2, 'structural concrete', convert grams to kg
24 default_attributes = {
25 'carbonation_rate': 0, # Current kmoles/h rate
26 'carbonation': 0, # Cumulative kmoles
27 }
29 required_kwargs = {
30 'flows': {'in': {'co2': 0, 'caoh2': 0},
31 'out': {'caco3': 0, 'moisture': 0}},
32 'capacity': {'caoh2': 0, 'caco3': 0, 'moisture': 0}}
34 def __init__(self, *args, attributes=None, **kwargs):
35 recursively_check_required_kwargs(kwargs, self.required_kwargs)
36 attributes = {} if attributes is None else attributes
37 attributes = {**self.default_attributes, **attributes}
38 super().__init__(*args, attributes=attributes, **kwargs)
40 # Set internal caoh2 to the maximum amount of carbonation at the highest ppm level
41 caoh2_flow = self.flows['in']['caoh2']['value']
42 caco3_flow = self.flows['out']['caco3']['value']
43 moisture_flow = self.flows['out']['moisture']['value']
44 initial_storage = {
45 'caoh2': self.calc_max_carbonation(3000) * caoh2_flow * self.active,
46 'caco3': 0, # Byproduct, accumulates internally
47 'moisture': 0, # Byproduct, accumulates internally
48 }
49 # If carbonation has already occured (Mission 2), update storages accordingly
50 carbonation = self.attributes['carbonation']
51 if carbonation > 0:
52 initial_storage['caoh2'] -= carbonation * caoh2_flow * self.active
53 initial_storage['caco3'] += carbonation * caco3_flow * self.active
54 initial_storage['moisture'] += carbonation * moisture_flow * self.active
55 self.storage = {**self.storage, **initial_storage}
57 @classmethod
58 def calc_max_carbonation(cls, ppm):
59 """Return max kmoles CO2 uptake by structural concrete"""
60 saturation_point_kmoles = np.interp(ppm, cls.ppm_range, cls.rate_scale)
61 return saturation_point_kmoles * cls.density
63 def step(self, dT=1):
64 """Set the carbonation rate, which is used to weight exchanges"""
65 # Calculate ppm of CO2 in atmosphere
66 ref_agent_name = self.flows['in']['co2']['connections'][0]
67 ref_agent = self.model.agents[ref_agent_name]
68 ref_atm = ref_agent.view('atmosphere')
69 ppm = ref_atm['co2'] / sum(ref_atm.values()) * 1e6
70 # Calculate carbonation rate
71 max_carbonation = self.calc_max_carbonation(ppm)
72 gradient = max(0, max_carbonation - self.attributes['carbonation'])
73 self.attributes['carbonation_rate'] = gradient * self.diffusion_rate
74 self.attributes['carbonation'] += self.attributes['carbonation_rate']
75 super().step(dT)