rscm_components/components/
carbon_cycle.rs1use crate::constants::GTC_PER_PPM;
7use ode_solvers::Vector3;
8use rscm_core::component::{
9 Component, GridType, InputState, OutputState, RequirementDefinition, RequirementType,
10 ScalarWindow,
11};
12use rscm_core::errors::RSCMResult;
13use rscm_core::ivp::{get_last_step, IVPBuilder, IVP};
14use rscm_core::state::StateValue;
15use rscm_core::timeseries::{FloatValue, Time};
16use rscm_core::ComponentIO;
17use serde::{Deserialize, Serialize};
18use std::sync::Arc;
19
20type ModelState = Vector3<FloatValue>;
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct CarbonCycleParameters {
25 pub tau: FloatValue,
28 pub conc_pi: FloatValue,
31 pub alpha_temperature: FloatValue,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct SolverOptions {
39 pub step_size: FloatValue,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, ComponentIO)]
60#[component(tags = ["carbon-cycle", "simple", "stable"], category = "Carbon Cycle")]
61#[inputs(
62 emissions { name = "Emissions|CO2|Anthropogenic", unit = "GtC / yr" },
63 temperature { name = "Surface Temperature", unit = "K" },
64)]
65#[states(
66 concentration { name = "Atmospheric Concentration|CO2", unit = "ppm" },
67 cumulative_emissions { name = "Cumulative Emissions|CO2", unit = "Gt C" },
68 cumulative_uptake { name = "Cumulative Land Uptake", unit = "Gt C" },
69)]
70pub struct CarbonCycle {
71 parameters: CarbonCycleParameters,
72 solver_options: SolverOptions,
73}
74
75impl CarbonCycle {
76 pub fn from_parameters(parameters: CarbonCycleParameters) -> Self {
78 Self {
79 parameters,
80 solver_options: SolverOptions { step_size: 0.1 },
81 }
82 }
83
84 pub fn with_solver_options(self, solver_options: SolverOptions) -> Self {
86 Self {
87 parameters: self.parameters,
88 solver_options,
89 }
90 }
91}
92
93#[typetag::serde]
94impl Component for CarbonCycle {
95 fn definitions(&self) -> Vec<RequirementDefinition> {
96 Self::generated_definitions()
97 }
98
99 fn solve(
100 &self,
101 t_current: Time,
102 t_next: Time,
103 input_state: &InputState,
104 ) -> RSCMResult<OutputState> {
105 let inputs = CarbonCycleInputs::from_input_state(input_state);
106
107 let y0 = ModelState::new(
108 inputs.concentration.at_start(),
109 inputs.cumulative_uptake.at_start(),
110 inputs.cumulative_emissions.at_start(),
111 );
112
113 let solver = IVPBuilder::new(Arc::new(self.to_owned()), input_state, y0);
114
115 let mut solver = solver.to_rk4(t_current, t_next, self.solver_options.step_size);
116 solver.integrate().expect("Failed solving");
117
118 let results = get_last_step(solver.results(), t_next);
119
120 let outputs = CarbonCycleOutputs {
121 concentration: results[0],
122 cumulative_uptake: results[1],
123 cumulative_emissions: results[2],
124 };
125
126 Ok(outputs.into())
127 }
128}
129
130impl IVP<Time, ModelState> for CarbonCycle {
131 fn calculate_dy_dt(
132 &self,
133 _t: Time,
134 input_state: &InputState,
135 y: &Vector3<FloatValue>,
136 dy_dt: &mut Vector3<FloatValue>,
137 ) {
138 let inputs = CarbonCycleInputs::from_input_state(input_state);
139
140 let emissions = inputs.emissions.at_start();
142 let temperature = inputs.temperature.at_start();
143
144 let conc = y[0];
146
147 let lifetime =
149 self.parameters.tau * (self.parameters.alpha_temperature * temperature).exp();
150 let uptake = (conc - self.parameters.conc_pi) / lifetime; dy_dt[0] = emissions / GTC_PER_PPM - uptake; dy_dt[1] = uptake * GTC_PER_PPM; dy_dt[2] = emissions }
156}