Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions packages/catlog/src/stdlib/analyses/ode/lotka_volterra_next.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Lotka-Volterra ODE semantics.

use super::ode_builder::PolynomialODESystemBuilder;
use crate::dbl::{
model::{DiscreteDblModel, FpDblModel, ModalDblModel, MutDblModel},
theory::NonUnital,
};
use crate::one::{Path, QualifiedPath};
use crate::zero::{QualifiedName, name, name_seg};

/// Lotka-Volterra ODE analysis intended for signed graphs.
pub struct LotkaVolterraAnalysis {
/// Object type for variables.
pub var_ob_type: QualifiedName,
/// Morphism type for positive links.
pub pos_link_type: QualifiedPath,
/// Morphism type for negative links.
pub neg_link_type: QualifiedPath,
}

impl Default for LotkaVolterraAnalysis {
fn default() -> Self {
let ob_type = name("Object");
Self {
var_ob_type: ob_type.clone(),
pos_link_type: Path::Id(ob_type),
neg_link_type: Path::single(name("Negative")),
}
}
}

impl LotkaVolterraAnalysis {
/// Builds a polynomial ODE system.
pub fn build_ode_system(&self, model: &DiscreteDblModel) -> ModalDblModel<NonUnital> {
let mut builder = PolynomialODESystemBuilder::new();

for var in model.ob_generators_with_type(&self.var_ob_type) {
builder.add_variable(var.clone());

// Arbitrarily signed contribution for growth or decay.
let id = var.cons(name_seg("Growth"));
builder.add_contribution(id, var.clone(), [var]);
}

// FIXME: Should be *positively signed* contributions.
for mor in model.mor_generators_with_type(&self.pos_link_type) {
let (Some(dom), Some(cod)) = (model.get_dom(&mor), model.get_cod(&mor)) else {
continue;
};
let id = mor.cons(name_seg("Influence"));
builder.add_contribution(id, dom.clone(), [dom.clone(), cod.clone()]);
}

// FIXME: Should be *negatively signed* contributions.
for mor in model.mor_generators_with_type(&self.neg_link_type) {
let (Some(dom), Some(cod)) = (model.get_dom(&mor), model.get_cod(&mor)) else {
continue;
};
let id = mor.cons(name_seg("Influence"));
builder.add_contribution(id, dom.clone(), [dom.clone(), cod.clone()]);
}

builder.model()
}
}
2 changes: 2 additions & 0 deletions packages/catlog/src/stdlib/analyses/ode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ impl<Sys> ODEAnalysis<Sys> {
pub mod kuramoto;
pub mod linear_ode;
pub mod lotka_volterra;
pub mod lotka_volterra_next;
pub mod mass_action;
pub mod ode_builder;
pub mod polynomial_ode;
pub mod signed_coefficients;

Expand Down
62 changes: 62 additions & 0 deletions packages/catlog/src/stdlib/analyses/ode/ode_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! Convenient interface to build ODE systems.

use crate::dbl::{modal::*, model::MutDblModel, theory::NonUnital};
use crate::stdlib::theories::th_polynomial_ode_system;
use crate::zero::{QualifiedName, name};

/// Builder for polynomial ODE systems.
///
/// This struct is just a convenient interface to construct a model of the
/// [theory of polynomial ODE systems](th_polynomial_ode_system). Being an
/// ordinary mutable Rust struct, it does *not* constitute a declarative
/// language to define ODE semantics for models of other theories. However, the
/// idea is that it should be used in a style that can mechanically translated
/// to a future declarative language for model migration.
///
/// Since an ODE semantics often has contributions of several types, a useful
/// pattern is to use qualified names with an initial segment indicating the
/// type of contribution. This corresponds to a model migration in which the
/// contributions arise as a coproduct of several queries.
pub struct PolynomialODESystemBuilder {
model: ModalDblModel<NonUnital>,
}

impl Default for PolynomialODESystemBuilder {
fn default() -> Self {
let th = th_polynomial_ode_system();
Self { model: ModalDblModel::new(th.into()) }
}
}

impl PolynomialODESystemBuilder {
/// Constructs an empty ODE system.
pub fn new() -> Self {
Self::default()
}

/// Returns a model of the theory of polynomial ODE systems.
pub fn model(self) -> ModalDblModel<NonUnital> {
self.model
}

/// Adds a state variable to the ODE system.
pub fn add_variable(&mut self, var: QualifiedName) {
self.model.add_ob(var, ModeApp::new(name("State")));
}

/// Adds a contribution to the ODE system.
pub fn add_contribution(
&mut self,
id: QualifiedName,
var: QualifiedName,
monomial: impl IntoIterator<Item = QualifiedName>,
) {
let monomial = monomial.into_iter().map(ModalOb::Generator).collect();
self.model.add_mor(
id,
ModalOb::List(List::Symmetric, monomial),
ModalOb::Generator(var),
ModeApp::new(name("Contribution")).into(),
)
}
}
9 changes: 8 additions & 1 deletion packages/catlog/src/zero/qualified.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,14 @@ impl QualifiedName {
}
}

/// Add another segment onto the end.
/// Prepends a name segment.
pub fn cons(&self, segment: NameSegment) -> Self {
let mut segments = self.0.clone();
segments.insert(0, segment);
Self(segments)
}

/// Adds another segment onto the end.
pub fn snoc(&self, segment: NameSegment) -> Self {
let mut segments = self.0.clone();
segments.push(segment);
Expand Down
Loading