use crate::constants::GTC_PER_PPM;
use ode_solvers::Vector3;
use rscm_core::component::{
Component, GridType, InputState, OutputState, RequirementDefinition, RequirementType,
TimeseriesWindow,
};
use rscm_core::errors::RSCMResult;
use rscm_core::ivp::{get_last_step, IVPBuilder, IVP};
use rscm_core::state::StateValue;
use rscm_core::timeseries::{FloatValue, Time};
use rscm_core::ComponentIO;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
type ModelState = Vector3<FloatValue>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CarbonCycleParameters {
pub tau: FloatValue,
pub conc_pi: FloatValue,
pub alpha_temperature: FloatValue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverOptions {
pub step_size: FloatValue,
}
#[derive(Debug, Clone, Serialize, Deserialize, ComponentIO)]
#[component(tags = ["carbon-cycle", "simple", "stable"], category = "Carbon Cycle")]
#[inputs(
emissions { name = "Emissions|CO2|Anthropogenic", unit = "GtC / yr" },
temperature { name = "Surface Temperature", unit = "K" },
)]
#[states(
concentration { name = "Atmospheric Concentration|CO2", unit = "ppm" },
cumulative_emissions { name = "Cumulative Emissions|CO2", unit = "Gt C" },
cumulative_uptake { name = "Cumulative Land Uptake", unit = "Gt C" },
)]
pub struct CarbonCycle {
parameters: CarbonCycleParameters,
solver_options: SolverOptions,
}
impl CarbonCycle {
pub fn from_parameters(parameters: CarbonCycleParameters) -> Self {
Self {
parameters,
solver_options: SolverOptions { step_size: 0.1 },
}
}
pub fn with_solver_options(self, solver_options: SolverOptions) -> Self {
Self {
parameters: self.parameters,
solver_options,
}
}
}
#[typetag::serde]
impl Component for CarbonCycle {
fn definitions(&self) -> Vec<RequirementDefinition> {
Self::generated_definitions()
}
fn solve(
&self,
t_current: Time,
t_next: Time,
input_state: &InputState,
) -> RSCMResult<OutputState> {
let inputs = CarbonCycleInputs::from_input_state(input_state);
let y0 = ModelState::new(
inputs.concentration.current(),
inputs.cumulative_uptake.current(),
inputs.cumulative_emissions.current(),
);
let solver = IVPBuilder::new(Arc::new(self.to_owned()), input_state, y0);
let mut solver = solver.to_rk4(t_current, t_next, self.solver_options.step_size);
solver.integrate().expect("Failed solving");
let results = get_last_step(solver.results(), t_next);
let outputs = CarbonCycleOutputs {
concentration: results[0],
cumulative_uptake: results[1],
cumulative_emissions: results[2],
};
Ok(outputs.into())
}
}
impl IVP<Time, ModelState> for CarbonCycle {
fn calculate_dy_dt(
&self,
_t: Time,
input_state: &InputState,
y: &Vector3<FloatValue>,
dy_dt: &mut Vector3<FloatValue>,
) {
let inputs = CarbonCycleInputs::from_input_state(input_state);
let emissions = inputs.emissions.current();
let temperature = inputs.temperature.current();
let conc = y[0];
let lifetime =
self.parameters.tau * (self.parameters.alpha_temperature * temperature).exp();
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 }
}