1 from . import BaseAgent

2 from ..util import recursively_check_required_kwargs

3 import numpy as np

4

5 class ConcreteAgent(BaseAgent):

6 """One exposed square meter of carbonating concrete

7

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 ====================== ============== ===============

14

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

23

24 default_attributes = {

25 'carbonation_rate': 0, # Current kmoles/h rate

26 'carbonation': 0, # Cumulative kmoles

27 }

28

29 required_kwargs = {

30 'flows': {'in': {'co2': 0, 'caoh2': 0},

31 'out': {'caco3': 0, 'moisture': 0}},

32 'capacity': {'caoh2': 0, 'caco3': 0, 'moisture': 0}}

33

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)

39

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}

56

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

62

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)