Basic Tutorial

1 Human preset

Start by loading the simplest preset, 1 human. To run a preset configuration: 1. Import the AgentModel class and the load_preset_configuration helper function 2. Load the preset configuration by name - in this case, 1h for ‘one human’ 3. Initialize the model using the from_config method, and use the double star operator to feed all keyward arguments from the configuration. 4. Run the simulation until termination using the run method 5. Export records from the simulation using the get_records method

[1]:
# Ignore this
import sys
sys.path.append('../../src')

from simoc_abm.agent_model import AgentModel
from simoc_abm.util import load_preset_configuration

config = load_preset_configuration('1h')
model = AgentModel.from_config(**config)
model.run()
records = model.get_records()

We now have a records object, which is a multi-level dict of all the relevant fields from the model. Let’s take a quick look inside.

[2]:
print('Records base:', list(records.keys()))
print('\nAgents:', list(records['agents'].keys()))
print('\nHuman Records:', list(records['agents']['human'].keys()))
Records base: ['time', 'step_num', 'agents']

Agents: ['human', 'solar_pv_array_mars', 'crew_habitat_small', 'ration_storage', 'water_storage', 'nutrient_storage', 'power_storage', 'solid_waste_aerobic_bioreactor', 'multifiltration_purifier_post_treatment', 'oxygen_generation_SFWE', 'urine_recycling_processor_VCD', 'co2_removal_SAWD', 'co2_makeup_valve', 'co2_storage', 'co2_reduction_sabatier', 'ch4_removal_agent', 'dehumidifier']

Human Records: ['active', 'cause_of_death', 'attributes', 'flows']

SIMOC-ABM includes the helper function plot_agent to easily plot agent records using matplotlib. Let’s take a look at human flows.

[3]:
from simoc_abm.viz import plot_agent

plot_agent(records, 'human', 'flows');
../_images/tutorials_basic_tutorial_5_0.png

The plot_agent function uses matplotlib and includes an ax kwarg, so we have the option of passing it an axis. The script below creates a horizontal 3-panel plot, and passes each panel to a different plot_agent function so we can view all three categories of human data side-by-side.

[4]:
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 3, figsize=(12, 3))
plot_agent(records, 'human', 'active', ax=axs[0]);
plot_agent(records, 'human', 'flows', ax=axs[1]);
plot_agent(records, 'human', 'attributes', ax=axs[2]);
../_images/tutorials_basic_tutorial_7_0.png

Let’s try increasing the number of humans while leaving everything else the same and see what happens. We can start by looking inside the config object to see what it looks like.

[5]:
config = load_preset_configuration('1h')
print('Config keys:', list(config.keys()))
print('\nConfig Agents keys:', list(config['agents'].keys()))
print('\nConfig Human Agent keys:', list(config['agents']['human'].keys()))
Config keys: ['agents', 'termination', 'seed', 'location', 'priorities']

Config Agents keys: ['human', 'solar_pv_array_mars', 'crew_habitat_small', 'ration_storage', 'water_storage', 'nutrient_storage', 'power_storage', 'solid_waste_aerobic_bioreactor', 'multifiltration_purifier_post_treatment', 'oxygen_generation_SFWE', 'urine_recycling_processor_VCD', 'co2_removal_SAWD', 'co2_makeup_valve', 'co2_storage', 'co2_reduction_sabatier', 'ch4_removal_agent', 'dehumidifier']

Config Human Agent keys: ['amount']

Let’s increase the number of humans to 3, run the model and use the same function as above to plot the results.

[6]:
config['agents']['human']['amount'] = 3
model = AgentModel.from_config(**config)
model.run()
records = model.get_records()

fig, axs = plt.subplots(1, 3, figsize=(12, 3))
plot_agent(records, 'human', 'active', ax=axs[0]);
plot_agent(records, 'human', 'flows', ax=axs[1]);
plot_agent(records, 'human', 'attributes', ax=axs[2]);
../_images/tutorials_basic_tutorial_11_0.png

It looks like all our humans died right before the end of the simulation. We can inspect the cause_of_death field to find out why:

[7]:
model.agents['human'].cause_of_death
[7]:
'human passed co2 threshold'

Looks like CO2 levels go too high. An easy way to address this is to increase the number of co2 scrubbers, which for this simulation is the co2_removal_SAWD agent. Let’s try doing that, run it again and see if our humans survive.

[8]:
config = load_preset_configuration('1h')
config['agents']['human']['amount'] = 3
config['agents']['co2_removal_SAWD']['amount'] = 3
model = AgentModel.from_config(**config)
model.run()
records = model.get_records()

fig, axs = plt.subplots(1, 3, figsize=(12, 3))
plot_agent(records, 'human', 'active', ax=axs[0]);
plot_agent(records, 'human', 'flows', ax=axs[1]);
plot_agent(records, 'human', 'attributes', ax=axs[2]);
../_images/tutorials_basic_tutorial_15_0.png

1 Human Radish

Next, let’s look at a more complex agent. Load and run the 1 human + radish preset, and plot the attributes for the radish agent.

[9]:
config = load_preset_configuration('1hrad')
model = AgentModel.from_config(**config)
model.run()
records = model.get_records()
plot_agent(records, 'radish', 'attributes');
../_images/tutorials_basic_tutorial_17_0.png

It’s difficult to see too much from this plot because there are so many fields. We can narrow-down the fields using either the exclude or include kwargs. Let’s remove the attributes with values outside the ~0-1 range: age and deprive.

[10]:
exclude = ['age', 'in_co2_deprive', 'in_potable_deprive', 'in_fertilizer_deprive']
plot_agent(records, 'radish', 'attributes', exclude=exclude);
../_images/tutorials_basic_tutorial_19_0.png

Next, let’s take a look at the CO2. Plot CO2 storage in the greenhouse and crew quarters side-by-side.

[11]:
fig, axs = plt.subplots(1, 2, figsize=(12, 3))
plot_agent(records, 'crew_habitat_small', 'storage', include=['co2'], ax=axs[0]);
plot_agent(records, 'greenhouse_small', 'storage', include=['co2'], ax=axs[1]);
../_images/tutorials_basic_tutorial_21_0.png

You can see the behavior changes over time. If we check the radish charts above, it looks like the CO2 levels drop when the radish plant matures, and then go up again after it’s harvested. Let’s plot radish CO2 flows and the CO2 scrubber flows side-by-side to see how they line up with the levels in the atmosphere.

[12]:
fig, axs = plt.subplots(1, 2, figsize=(12, 3))
plot_agent(records, 'radish', 'flows', include=['co2'], ax=axs[0]);
plot_agent(records, 'co2_removal_SAWD', 'flows', include=['in_co2'], ax=axs[1]);
../_images/tutorials_basic_tutorial_23_0.png

We can see that the CO2 scrubber activates less frequently when the radish is mature, because the radish is absorbing most of the CO2 created by the humans.

What would happen if we maintained a lower CO2 level? Let’s try adjusting the activation point of the CO2 scrubber to a lower level. To see its current level, we can look at the ‘flows’ variable inside the CO2 scrubber agent:

[13]:
co2_removal_SAWD = model.agents['co2_removal_SAWD']
co2_removal_SAWD.flows
[13]:
{'in': {'co2': {'value': 0.085,
   'flow_rate': {'unit': 'kg', 'time': 'hour'},
   'criteria': {'in_co2_ratio': {'limit': '>', 'value': 0.001, 'buffer': 8}},
   'connections': ['greenhouse_small', 'crew_habitat_small']},
  'kwh': {'value': 0.65,
   'requires': ['co2'],
   'flow_rate': {'unit': 'kWh', 'time': 'hour'},
   'connections': ['power_storage']}},
 'out': {'co2': {'value': 0.085,
   'requires': ['co2', 'kwh'],
   'flow_rate': {'unit': 'kg', 'time': 'hour'},
   'connections': ['co2_storage']}}}

Line 4, the in-flow co2 criteria value, is set to 0.001. We often discuss CO2 in terms of ‘parts per million’ or ppm, and this corresponds to 1000 ppm - a reasonable level for a greenhouse. Average levels for outdoor growing are closer to 450 parts per million.

Let’s create a new model, but modify the configuration file to change the scrubber activation point to 450 ppm instead of 1000. First, load the preset again, and inspect the scrubber agent.

[14]:
config2 = load_preset_configuration('1hrad')
config2['agents']['co2_removal_SAWD']
[14]:
{'amount': 1}

The only field we see is ‘amount’. When using the from_config API, this dict will be merged with the default agent description. We can load the default agent and inspect it using the get_default_agent_data utility function.

[15]:
from simoc_abm.util import get_default_agent_data
default_sawd = get_default_agent_data('co2_removal_SAWD')
default_sawd
[15]:
{'amount': 1,
 'properties': {'mass': {'value': 137.35, 'unit': 'kg'},
  'volume': {'value': 0.31, 'unit': 'm^3'}},
 'flows': {'in': {'co2': {'value': 0.085,
    'flow_rate': {'unit': 'kg', 'time': 'hour'},
    'criteria': {'in_co2_ratio': {'limit': '>', 'value': 0.001, 'buffer': 8}},
    'connections': ['greenhouse', 'habitat']},
   'kwh': {'value': 0.65,
    'requires': ['co2'],
    'flow_rate': {'unit': 'kWh', 'time': 'hour'},
    'connections': ['power_storage']}},
  'out': {'co2': {'value': 0.085,
    'requires': ['co2', 'kwh'],
    'flow_rate': {'unit': 'kg', 'time': 'hour'},
    'connections': ['co2_storage']}}},
 'description': 'Moves carbon dioxide from the habitat atmosphere to co2_storage.',
 'agent_class': 'eclss'}

To override default values, we can update the specific field we want, and include that in our configuration file.

[16]:
stub = {'in': {'co2': {'criteria': {'in_co2_ratio': {'value': 0.00045}}}}}
config2['agents']['co2_removal_SAWD']['flows'] = stub

We can verify that it worked by instantiating a new model with our updated config, and inspecting the scrubber agent the same way:

[17]:
model2 = AgentModel.from_config(**config2)
model2.agents['co2_removal_SAWD'].flows
[17]:
{'in': {'co2': {'value': 0.085,
   'flow_rate': {'unit': 'kg', 'time': 'hour'},
   'criteria': {'in_co2_ratio': {'limit': '>', 'value': 0.00045, 'buffer': 8}},
   'connections': ['greenhouse_small', 'crew_habitat_small']},
  'kwh': {'value': 0.65,
   'requires': ['co2'],
   'flow_rate': {'unit': 'kWh', 'time': 'hour'},
   'connections': ['power_storage']}},
 'out': {'co2': {'value': 0.085,
   'requires': ['co2', 'kwh'],
   'flow_rate': {'unit': 'kg', 'time': 'hour'},
   'connections': ['co2_storage']}}}

It worked! Let’s run the model and plot the relevant agents.

[18]:
model2.run()
records2 = model2.get_records()

fig, axs = plt.subplots(2, 2, figsize=(12, 6))
plot_agent(records2, 'crew_habitat_small', 'storage', include=['co2'], ax=axs[0][0]);
plot_agent(records2, 'greenhouse_small', 'storage', include=['co2'], ax=axs[0][1]);
plot_agent(records2, 'radish', 'flows', include=['in_co2'], ax=axs[1][0]);
plot_agent(records2, 'co2_removal_SAWD', 'flows', include=['in_co2'], ax=axs[1][1]);
../_images/tutorials_basic_tutorial_35_0.png

We can see that CO2 levels in the habitat and storage are indeed lower and tje scrubber had to work a lot harder, but our radish CO2 consumption has also changed. Let’s take a look at the radish attributes, specifically the growth factors, to idenfity what happened.

[19]:
plot_agent(records2, 'radish', 'attributes', include=['cu_factor', 'te_factor', 'par_factor', 'daily_growth_factor']);
../_images/tutorials_basic_tutorial_37_0.png

We can see that the cu_factor and te_factor often fall below 1. To try to figure out why, let’s zoom in on a smaller date range, and line it up with the greenhouse co2 level. I’ll focus on 4 days in the middle of the simulation by setting i=300 and j=396.

[20]:
fig, axs = plt.subplots(2, 1, figsize=(10, 5))
plot_agent(records2, 'radish', 'attributes', include=['te_factor', 'cu_factor'], i=300, j=396, ax=axs[0]);
plot_agent(records2, 'greenhouse_small', 'storage', include=['co2'], i=300, j=396, ax=axs[1]);
../_images/tutorials_basic_tutorial_39_0.png

We can see clearly that, when ambient CO2 drops below some level (in the case of SIMOC, 700ppm), it negatively affects crop growth. Let’s compare the internal biomass in the two scenarios to see the magnitude of the impact. We can plot both on one chart just by calling plot_agent twice. We should be able to guess which is which.

[21]:
plot_agent(records, 'radish', 'storage');
plot_agent(records2, 'radish', 'storage');
../_images/tutorials_basic_tutorial_41_0.png
[ ]: