diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..7e86dc2 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,28 @@ +pub use rulett::{ + model::{Model, ModelDecl}, + netgen::NetGenerator, + ob_tm::ObTm, + surface, + theory::{Signature, SignatureDecl}, + ty::Ty, +}; + +pub use expect_test::expect; + +/// Merges multiple signatures. +pub fn merge_signatures(sigs: &[Signature]) -> Signature { + let mut merged = Signature::new(); + for sig in sigs { + for sort in sig.sorts() { + if !merged.sorts().any(|s| s == sort) { + merged.add_sort(sort).unwrap(); + } + } + for (op, dom, cod) in sig.operations() { + if merged.interface(&op).is_none() { + merged.add_operation(op, dom.clone(), cod.clone()).unwrap(); + } + } + } + merged +} diff --git a/tests/dependencies.rs b/tests/dependencies.rs new file mode 100644 index 0000000..0f088b5 --- /dev/null +++ b/tests/dependencies.rs @@ -0,0 +1,292 @@ +//! Certain rules have implicit dependencies. For example, a phosphorylation +//! rule can only fire if ATP is present, gene expression can only fire if +//! transcription-translation machinery (RNA-polymerase, ribosomes, etc.) +//! are present, etc. +//! +//! We call the totalty of dependencies an "environment". The required environment for the rule can be +//! made explicit by putting it on either side of a rule. We can use product species in the signature to +//! define environments as shown +//! [here](https://q.uiver.app/#q=WzAsOCxbMSwwLCJUeU1vbGVjdWxlIl0sWzAsMSwiVHlBIl0sWzEsMSwiVHlCIl0sWzIsMSwiVHlDIl0sWzAsMiwiVHlBQiJdLFsxLDIsIlR5QUMiXSxbMiwyLCJUeUJDIl0sWzEsMywiVHlBQkMiXSxbMSwwXSxbMiwwXSxbMywwXSxbNCwxXSxbNSwxXSxbNCwyXSxbNiwyXSxbNSwzXSxbNiwzXSxbNyw0XSxbNyw1XSxbNyw2XV0=) +//! or, using more suggestive labelling, +//! [here](https://q.uiver.app/#q=WzAsOCxbMSwwLCJcXG1hdGhybXtUeU1vbGVjdWxlfSJdLFswLDEsIlxcbWF0aHJte1R5QX0iXSxbMSwxLCJcXG1hdGhybXtUeUJ9Il0sWzIsMSwiXFxtYXRocm17VHlDfSJdLFswLDIsIlxcbWF0aHJte0NlbGxYfSJdLFsxLDIsIlxcbWF0aHJte0NlbGxZfSJdLFsyLDIsIlxcbWF0aHJte0NlbGxafSJdLFsxLDMsIlxcbWF0aHJte0NlbGxPbW5pcG90ZW50fSJdLFsxLDBdLFsyLDBdLFszLDBdLFs0LDFdLFs1LDFdLFs0LDJdLFs2LDJdLFs1LDNdLFs2LDNdLFs3LDRdLFs3LDVdLFs3LDZdXQ==) +//! +//! I.e. an environment is a capability that combines the capabilities of its underlying species. +//! +//! Environments are "made available" in the same way as species: by providing +//! "grounding signatures", that is, morphisms out of `[]`. +//! +//! Note: this requires product species (issue #9), which is currently not a priority. +//! The example below illustrates the phoshporylation of CDC25, which requires either a +//! CDK1.CCNB1 or CDK1.CCNB2 complex. We show that this reaction fires in a cell that expresses only +//! - CDK1 and CCNB1 +//! - CDK1 and CCNB2 +//! +//! but does not fire in a cell that expresses only +//! - CCNB1 and CCNB2 +//! +//! The signature can be illustrated as follows: +//! https://q.uiver.app/#q=WzAsMTQsWzEsMCwiXFxtYXRocm17VHlNb2xlY3VsZX0iXSxbMCwxLCJcXG1hdGhybXtUeUNESzF9Il0sWzEsMSwiXFxtYXRocm17VHlDQ05CMX0iXSxbMiwxLCJcXG1hdGhybXtUeUNDTkIyfSJdLFswLDIsIlxcdGV4dHtDZWxsLWxpbmV9XzEiXSxbMSwyLCJcXHRleHR7Q2VsbC1saW5lfV8yIl0sWzIsMiwiXFx0ZXh0e0NlbGwtbGluZX1fMyJdLFsxLDMsIlxcdGV4dHtDZWxsfV9cXHRleHR7b21uaXBvdGVudH0iXSxbNCwyLCJcXHRleHR7Q2VsbC1saW5lfV8xIl0sWzUsMiwiXFx0ZXh0e0NlbGwtbGluZX1fMiJdLFs2LDIsIlxcdGV4dHtDZWxsLWxpbmV9XzMiXSxbNSwzLCJcXHRleHR7Q2VsbH1fXFx0ZXh0e29tbmlwb3RlbnR9Il0sWzUsMCwiXFxtYXRocm17VHlFbnZ9Il0sWzQsMSwiXFx0ZXh0e0NlbGwtbGluZX1fezEyfSJdLFsxLDBdLFs0LDFdLFs0LDJdLFs1LDFdLFs1LDNdLFs2LDJdLFs2LDNdLFs3LDRdLFs3LDVdLFs3LDZdLFszLDBdLFsyLDBdLFsxMSw4XSxbMTEsOV0sWzEwLDEyXSxbMTEsMTBdLFsxMywxMl0sWzgsMTNdLFs5LDEzXV0= +//! +//! Note that we do not model complex formation explicitly. The purpose for providing an environment is +//! to allow modelers to optionally abstract away such detailed reactions. + +mod common; +use common::*; + +/// Base signature. +fn base_signature() -> Signature { + Signature::parse([ + SignatureDecl::sort("TyMolecule"), + SignatureDecl::sort("TyCDK1"), + SignatureDecl::sort("TyCCNB1"), + SignatureDecl::sort("TyCCNB2"), + SignatureDecl::sort("Res"), + SignatureDecl::operation("iota_cdk1", [Ty::sort("TyCDK1")], Ty::sort("TyMolecule")), + SignatureDecl::operation("iota_ccnb1", [Ty::sort("TyCCNB1")], Ty::sort("TyMolecule")), + SignatureDecl::operation("iota_ccnb2", [Ty::sort("TyCCNB2")], Ty::sort("TyMolecule")), + ]) + .unwrap() +} + +/// Signature for the model environment. +fn environment_signature() -> Signature { + Signature::parse([ + // Define different cell lines in terms of their components (note that this has currently no effect, i.e. can be considered annotation). + SignatureDecl::sort("TyCDK1"), + SignatureDecl::sort("TyCCNB1"), + SignatureDecl::sort("TyCCNB2"), + SignatureDecl::sort("CellLine1"), + SignatureDecl::sort("CellLine2"), + SignatureDecl::sort("CellLine3"), + SignatureDecl::sort("CellOmnipotent"), + SignatureDecl::operation("iota_cl1_cdk1", [Ty::sort("CellLine1")], Ty::sort("TyCDK1")), + SignatureDecl::operation("iota_cl1_ccnb1", [Ty::sort("CellLine1")], Ty::sort("TyCCNB1")), + SignatureDecl::operation("iota_cl2_cdk1", [Ty::sort("CellLine2")], Ty::sort("TyCDK1")), + SignatureDecl::operation("iota_cl2_ccnb2", [Ty::sort("CellLine2")], Ty::sort("TyCCNB2")), + SignatureDecl::operation("iota_cl3_ccnb1", [Ty::sort("CellLine3")], Ty::sort("TyCCNB1")), + SignatureDecl::operation("iota_cl3_ccnb2", [Ty::sort("CellLine3")], Ty::sort("TyCCNB2")), + SignatureDecl::operation( + "iota_omni_1", + [Ty::sort("CellOmnipotent")], + Ty::sort("CellLine1"), + ), + SignatureDecl::operation( + "iota_omni_2", + [Ty::sort("CellOmnipotent")], + Ty::sort("CellLine2"), + ), + SignatureDecl::operation( + "iota_omni_3", + [Ty::sort("CellOmnipotent")], + Ty::sort("CellLine3"), + ), + // Define CellLine12 as coproduct of CellLine1 and CellLine2 + SignatureDecl::sort("CellLine12"), + SignatureDecl::operation("iota_cl1in12", [Ty::sort("CellLine1")], Ty::sort("CellLine12")), + SignatureDecl::operation("iota_cl2in12", [Ty::sort("CellLine2")], Ty::sort("CellLine12")), + SignatureDecl::sort("TyEnv"), + SignatureDecl::operation("iota_cl12", [Ty::sort("CellLine12")], Ty::sort("TyEnv")), + SignatureDecl::operation("iota_cl3", [Ty::sort("CellLine3")], Ty::sort("TyEnv")), + ]) + .unwrap() +} + +/// Signature to ground the base in `[]``. +fn ground_base() -> Signature { + Signature::parse([ + SignatureDecl::sort("Res"), + SignatureDecl::operation("u", [], Ty::sort("Res")), + SignatureDecl::operation("p", [], Ty::sort("Res")), + ]) + .unwrap() +} + +/// Signature to ground the environment in `[]`. +fn ground_environment(n: i32) -> Signature { + Signature::parse([ + SignatureDecl::sort(format!("CellLine{n}")), + SignatureDecl::operation(format!("!env{n}"), [], Ty::sort(format!("CellLine{n}"))), + ]) + .unwrap() +} + +/// Full signature. +fn signature(n: i32) -> Signature { + let sig1 = base_signature(); + let sig2 = environment_signature(); + let sig3 = ground_base(); + let sig4 = ground_environment(n); + merge_signatures(&[sig1, sig2, sig3, sig4]) +} + +// Declares Model. +fn model_decl() -> [ModelDecl; 3] { + use crate::surface::*; + [ + ModelDecl::agent("CDC25", [ObTm::var("r")], [Ty::sort("Res")]), + ModelDecl::agent("Env", [ObTm::var("e")], [Ty::sort("TyEnv")]), + // TODO: perhaps we could add a field `dep` to rules for dependencies that occur on both lhs and rhs? + ModelDecl::rule( + "phosphorylation", + [ObTm::var("e")], + [Ty::sort("CellLine12")], + PatTm::tensor([ + PatTm::res("Env", [MorTm::var("e")]), + PatTm::res("CDC25", [MorTm::app("u", [])]), + ]), + PatTm::tensor([ + PatTm::res("Env", [MorTm::var("e")]), + PatTm::res("CDC25", [MorTm::app("p", [])]), + ]), + ), + ] +} + +// Generates Model. +fn model(n: i32) -> Model { + let decls = model_decl(); + Model::parse(signature(n), decls).unwrap() +} + +#[test] +fn parse_signature() { + let expected = expect![[r#" + #/ sorts: + TyMolecule + TyCDK1 + TyCCNB1 + TyCCNB2 + Res + CellLine1 + CellLine2 + CellLine3 + CellOmnipotent + CellLine12 + TyEnv + #/ operations: + iota_cdk1 : [TyCDK1] → TyMolecule + iota_ccnb1 : [TyCCNB1] → TyMolecule + iota_ccnb2 : [TyCCNB2] → TyMolecule + iota_cl1_cdk1 : [CellLine1] → TyCDK1 + iota_cl1_ccnb1 : [CellLine1] → TyCCNB1 + iota_cl2_cdk1 : [CellLine2] → TyCDK1 + iota_cl2_ccnb2 : [CellLine2] → TyCCNB2 + iota_cl3_ccnb1 : [CellLine3] → TyCCNB1 + iota_cl3_ccnb2 : [CellLine3] → TyCCNB2 + iota_omni_1 : [CellOmnipotent] → CellLine1 + iota_omni_2 : [CellOmnipotent] → CellLine2 + iota_omni_3 : [CellOmnipotent] → CellLine3 + iota_cl1in12 : [CellLine1] → CellLine12 + iota_cl2in12 : [CellLine2] → CellLine12 + iota_cl12 : [CellLine12] → TyEnv + iota_cl3 : [CellLine3] → TyEnv + u : [] → Res + p : [] → Res + !env1 : [] → CellLine1 + "#]]; + expected.assert_eq(&signature(1).to_string()); +} + +#[test] +fn parse_model() { + let expected = expect![[r#" + #/ sorts: + TyMolecule + TyCDK1 + TyCCNB1 + TyCCNB2 + Res + CellLine1 + CellLine2 + CellLine3 + CellOmnipotent + CellLine12 + TyEnv + #/ operations: + iota_cdk1 : [TyCDK1] → TyMolecule + iota_ccnb1 : [TyCCNB1] → TyMolecule + iota_ccnb2 : [TyCCNB2] → TyMolecule + iota_cl1_cdk1 : [CellLine1] → TyCDK1 + iota_cl1_ccnb1 : [CellLine1] → TyCCNB1 + iota_cl2_cdk1 : [CellLine2] → TyCDK1 + iota_cl2_ccnb2 : [CellLine2] → TyCCNB2 + iota_cl3_ccnb1 : [CellLine3] → TyCCNB1 + iota_cl3_ccnb2 : [CellLine3] → TyCCNB2 + iota_omni_1 : [CellOmnipotent] → CellLine1 + iota_omni_2 : [CellOmnipotent] → CellLine2 + iota_omni_3 : [CellOmnipotent] → CellLine3 + iota_cl1in12 : [CellLine1] → CellLine12 + iota_cl2in12 : [CellLine2] → CellLine12 + iota_cl12 : [CellLine12] → TyEnv + iota_cl3 : [CellLine3] → TyEnv + u : [] → Res + p : [] → Res + !env1 : [] → CellLine1 + #/ agents: + [r] : [Res] ⊢ CDC25 [r] + [e] : [TyEnv] ⊢ Env [e] + #/ rules: + [e] : [CellLine12] ⊢ + phosphorylation [e] : (Env [e], CDC25 [u []]) → (Env [e], CDC25 [p []]) + "#]]; + expected.assert_eq(&model(1).to_string()); +} + +// Test that reaction fires in CellLine1. +#[test] +fn generate_network_1() { + use itertools::Itertools; + let model = model(1); + let generator = NetGenerator::new(&model); + + let species = expect![[r#" + CDC25 [u []] + CDC25 [p []] + Env [iota_cl12 [iota_cl1in12 [!env1 []]]]"#]]; + species.assert_eq(&generator.species(2).join("\n")); + + let transitions = expect![[r#" + phosphorylation [iota_cl1in12 [!env1 []]] + : (Env [iota_cl1in12 [!env1 []]], CDC25 [u []]) + → (Env [iota_cl1in12 [!env1 []]], CDC25 [p []])"#]]; + transitions.assert_eq(&generator.transitions(2).join("\n")); +} + +// Test that reaction fires in CellLine2. +#[test] +fn generate_network_2() { + use itertools::Itertools; + let model = model(2); + let generator = NetGenerator::new(&model); + + let species = expect![[r#" + CDC25 [u []] + CDC25 [p []] + Env [iota_cl12 [iota_cl2in12 [!env2 []]]]"#]]; + species.assert_eq(&generator.species(2).join("\n")); + + let transitions = expect![[r#" + phosphorylation [iota_cl2in12 [!env2 []]] + : (Env [iota_cl2in12 [!env2 []]], CDC25 [u []]) + → (Env [iota_cl2in12 [!env2 []]], CDC25 [p []])"#]]; + transitions.assert_eq(&generator.transitions(2).join("\n")); +} + +// Test that reaction does not fire in CellLine3. +#[test] +fn generate_network_3() { + use itertools::Itertools; + let model = model(3); + let generator = NetGenerator::new(&model); + + let species = expect![[r#" + CDC25 [u []] + CDC25 [p []] + Env [iota_cl3 [!env3 []]]"#]]; + species.assert_eq(&generator.species(2).join("\n")); + + let transitions = expect![""]; + transitions.assert_eq(&generator.transitions(2).join("\n")); +} + +// TODO: Create example for CellOmnipotent once we can handel product species (issue #9) diff --git a/tests/localization.rs b/tests/localization.rs new file mode 100644 index 0000000..b02bb9a --- /dev/null +++ b/tests/localization.rs @@ -0,0 +1,282 @@ +//! Eucaryotic cells contain membrane-enclosed compartments. +//! The concentrations of chemical species may differ between compartments. +//! To describe this circumstance, we can introduce a signature repesenting a +//! preorder of compartments. + +mod common; +use common::*; + +/// Signature for a toy model (variant 2). +fn toy_signature_v2() -> Signature { + Signature::parse([ + SignatureDecl::sort("Res"), + SignatureDecl::operation("unphos", [], Ty::sort("Res")), + SignatureDecl::operation("phos", [], Ty::sort("Res")), + SignatureDecl::sort("SiteA"), + SignatureDecl::sort("SiteB"), + SignatureDecl::operation("emptyA", [], Ty::sort("SiteA")), + SignatureDecl::operation("emptyB", [], Ty::sort("SiteB")), + SignatureDecl::operation("bond", [], Ty::tensor([Ty::sort("SiteA"), Ty::sort("SiteB")])), + ]) + .unwrap() +} + +/// Signature for location. +fn signature_location() -> Signature { + Signature::parse([ + SignatureDecl::sort("LocCell"), + SignatureDecl::sort("LocCyt"), // Cytoplasm + SignatureDecl::sort("LocNuc"), // Nucleus + SignatureDecl::operation("cyt", [Ty::sort("LocCyt")], Ty::sort("LocCell")), + SignatureDecl::operation("nuc", [Ty::sort("LocNuc")], Ty::sort("LocCell")), + ]) + .unwrap() +} + +/// Grounds location signature in `[]`. +fn ground_localization() -> Signature { + Signature::parse([ + SignatureDecl::sort("LocCyt"), // Cytoplasm + SignatureDecl::sort("LocNuc"), // Nucleus + SignatureDecl::operation("!cyt", [], Ty::sort("LocCyt")), // Maybe it would be a good convention to use `!` or some other indication to label morphisms from I. + SignatureDecl::operation("!nuc", [], Ty::sort("LocNuc")), + ]) + .unwrap() +} + +/// Declares Model. +fn model_decl() -> [ModelDecl; 6] { + use crate::surface::*; + // TODO: make location mandatory for this setting (perhaps by adding a `loctm` and `locty` fields to agents?) + [ + ModelDecl::agent( + "A", + [ObTm::var("r"), ObTm::var("s"), ObTm::var("l")], + [Ty::sort("Res"), Ty::sort("SiteA"), Ty::sort("LocCell")], + ), + ModelDecl::agent( + "B", + [ObTm::var("s"), ObTm::var("l")], + [Ty::sort("SiteB"), Ty::sort("LocCell")], + ), + ModelDecl::agent("K", [ObTm::var("l")], [Ty::sort("LocCell")]), + // Nuclear import of phosphorylated A + ModelDecl::rule( + "nuclear_import_phospho_A", + [ObTm::var("s")], + [Ty::sort("SiteA")], + PatTm::res("A", [MorTm::var("p"), MorTm::var("s"), MorTm::app("!cyt", [])]), + PatTm::res("A", [MorTm::var("p"), MorTm::var("s"), MorTm::app("!nuc", [])]), + ), + // Non-transport rules are location agnostic, but require all reactants to be in the same location + ModelDecl::rule( + "bondAB", + [ObTm::var("r"), ObTm::var("l")], + [Ty::sort("Res"), Ty::sort("LocCell")], + PatTm::tensor([ + PatTm::res("A", [MorTm::var("r"), MorTm::app("emptyA", []), MorTm::var("l")]), + PatTm::res("B", [MorTm::app("emptyB", []), MorTm::var("l")]), + ]), + PatTm::let_( + ObTm::tensor([ObTm::var("s1"), ObTm::var("s2")]), + MorTm::app("bond", []), + PatTm::tensor([ + PatTm::res("A", [MorTm::var("r"), MorTm::var("s1"), MorTm::var("l")]), + PatTm::res("B", [MorTm::var("s2"), MorTm::var("l")]), + ]), + ), + ), + // @Evan: note that `l` appears multiple times here, cause K cannot phosphorylate A if they are in different compartments. + ModelDecl::rule( + "phosphorylate", + [ObTm::var("s"), ObTm::var("l")], + [Ty::sort("SiteA"), Ty::sort("LocCell")], + PatTm::tensor([ + PatTm::res("A", [MorTm::app("unphos", []), MorTm::var("s"), MorTm::var("l")]), + PatTm::res("K", [MorTm::var("l")]), + ]), + PatTm::tensor([ + PatTm::res("A", [MorTm::app("phos", []), MorTm::var("s"), MorTm::var("l")]), + PatTm::res("K", [MorTm::var("l")]), + ]), + ), + ] +} + +/// Generates Model. +fn model() -> Model { + let decls = model_decl(); + Model::parse(signature(), decls).unwrap() +} + +/// Signature for toy_model_v2 with localization. +fn signature() -> Signature { + let sig1 = toy_signature_v2(); + let sig2 = signature_location(); + let sig3 = ground_localization(); + merge_signatures(&[sig1, sig2, sig3]) +} + +#[test] +fn parse_signature() { + let expected = expect![[r#" + #/ sorts: + Res + SiteA + SiteB + LocCell + LocCyt + LocNuc + #/ operations: + unphos : [] → Res + phos : [] → Res + emptyA : [] → SiteA + emptyB : [] → SiteB + bond : [] → ⊗ [SiteA, SiteB] + cyt : [LocCyt] → LocCell + nuc : [LocNuc] → LocCell + !cyt : [] → LocCyt + !nuc : [] → LocNuc + "#]]; + expected.assert_eq(&signature().to_string()); +} + +#[test] +fn parse_model() { + let expected = expect![[r#" + #/ sorts: + Res + SiteA + SiteB + LocCell + LocCyt + LocNuc + #/ operations: + unphos : [] → Res + phos : [] → Res + emptyA : [] → SiteA + emptyB : [] → SiteB + bond : [] → ⊗ [SiteA, SiteB] + cyt : [LocCyt] → LocCell + nuc : [LocNuc] → LocCell + !cyt : [] → LocCyt + !nuc : [] → LocNuc + #/ agents: + [r, s, l] : [Res, SiteA, LocCell] ⊢ A [r, s, l] + [s, l] : [SiteB, LocCell] ⊢ B [s, l] + [l] : [LocCell] ⊢ K [l] + #/ rules: + [s] : [SiteA] ⊢ + nuclear_import_phospho_A [s] : A [p, s, !cyt []] → A [p, s, !nuc []] + [r, l] : [Res, LocCell] ⊢ + bondAB [r, l] + : (A [r, emptyA [], l], B [emptyB [], l]) + → let bond [] in (A [r, 0.0, l], B [0.1, l]) + [s, l] : [SiteA, LocCell] ⊢ + phosphorylate [s, l] + : (A [unphos [], s, l], K [l]) + → (A [phos [], s, l], K [l]) + "#]]; + expected.assert_eq(&model().to_string()); +} + +#[test] +fn generate_network() { + use itertools::Itertools; + let model = model(); + let generator = NetGenerator::new(&model); + + // TODO: Think about whether you want to have multi-compartment species here (e.g.: let bond [] in (A [unphos [], 0.0, cyt [!cyt []]], B [0.1, nuc [!nuc []]])). + let species = expect![[r#" + A [unphos [], emptyA [], cyt [!cyt []]] + A [unphos [], emptyA [], nuc [!nuc []]] + A [phos [], emptyA [], cyt [!cyt []]] + A [phos [], emptyA [], nuc [!nuc []]] + B [emptyB [], cyt [!cyt []]] + B [emptyB [], nuc [!nuc []]] + K [cyt [!cyt []]] + K [nuc [!nuc []]] + let bond [] in (A [unphos [], 0.0, cyt [!cyt []]], B [0.1, cyt [!cyt []]]) + let bond [] in (A [unphos [], 0.0, cyt [!cyt []]], B [0.1, nuc [!nuc []]]) + let bond [] in (A [unphos [], 0.0, nuc [!nuc []]], B [0.1, cyt [!cyt []]]) + let bond [] in (A [unphos [], 0.0, nuc [!nuc []]], B [0.1, nuc [!nuc []]]) + let bond [] in (A [phos [], 0.0, cyt [!cyt []]], B [0.1, cyt [!cyt []]]) + let bond [] in (A [phos [], 0.0, cyt [!cyt []]], B [0.1, nuc [!nuc []]]) + let bond [] in (A [phos [], 0.0, nuc [!nuc []]], B [0.1, cyt [!cyt []]]) + let bond [] in (A [phos [], 0.0, nuc [!nuc []]], B [0.1, nuc [!nuc []]])"#]]; + species.assert_eq(&generator.species(2).join("\n")); + + let transitions = expect![[r#" + nuclear_import_phospho_A [emptyA []] + : A [p, emptyA [], !cyt []] + → A [p, emptyA [], !nuc []] + bondAB [unphos [], cyt [!cyt []]] + : (A [unphos [], emptyA [], cyt [!cyt []]], B [emptyB [], cyt [!cyt []]]) + → let bond [] in (A [unphos [], 0.0, cyt [!cyt []]], B [0.1, cyt [!cyt []]]) + bondAB [unphos [], nuc [!nuc []]] + : (A [unphos [], emptyA [], nuc [!nuc []]], B [emptyB [], nuc [!nuc []]]) + → let bond [] in (A [unphos [], 0.0, nuc [!nuc []]], B [0.1, nuc [!nuc []]]) + bondAB [phos [], cyt [!cyt []]] + : (A [phos [], emptyA [], cyt [!cyt []]], B [emptyB [], cyt [!cyt []]]) + → let bond [] in (A [phos [], 0.0, cyt [!cyt []]], B [0.1, cyt [!cyt []]]) + bondAB [phos [], nuc [!nuc []]] + : (A [phos [], emptyA [], nuc [!nuc []]], B [emptyB [], nuc [!nuc []]]) + → let bond [] in (A [phos [], 0.0, nuc [!nuc []]], B [0.1, nuc [!nuc []]]) + phosphorylate [emptyA [], cyt [!cyt []]] + : (A [unphos [], emptyA [], cyt [!cyt []]], K [cyt [!cyt []]]) + → (A [phos [], emptyA [], cyt [!cyt []]], K [cyt [!cyt []]]) + phosphorylate [emptyA [], nuc [!nuc []]] + : (A [unphos [], emptyA [], nuc [!nuc []]], K [nuc [!nuc []]]) + → (A [phos [], emptyA [], nuc [!nuc []]], K [nuc [!nuc []]]) + let bond [] in (B [0.1, cyt [!cyt []]], nuclear_import_phospho_A [0.0]) + : let bond [] in (B [0.1, cyt [!cyt []]], A [p, 0.0, !cyt []]) + → let bond [] in (B [0.1, cyt [!cyt []]], A [p, 0.0, !nuc []]) + let bond [] in (B [0.1, nuc [!nuc []]], nuclear_import_phospho_A [0.0]) + : let bond [] in (B [0.1, nuc [!nuc []]], A [p, 0.0, !cyt []]) + → let bond [] in (B [0.1, nuc [!nuc []]], A [p, 0.0, !nuc []]) + let bond [] in (B [0.1, cyt [!cyt []]], phosphorylate [0.0, cyt [!cyt []]]) + : let bond [] in + ( + B [0.1, cyt [!cyt []]], + (A [unphos [], 0.0, cyt [!cyt []]], K [cyt [!cyt []]]) + ) + → let bond [] in + ( + B [0.1, cyt [!cyt []]], + (A [phos [], 0.0, cyt [!cyt []]], K [cyt [!cyt []]]) + ) + let bond [] in (B [0.1, cyt [!cyt []]], phosphorylate [0.0, nuc [!nuc []]]) + : let bond [] in + ( + B [0.1, cyt [!cyt []]], + (A [unphos [], 0.0, nuc [!nuc []]], K [nuc [!nuc []]]) + ) + → let bond [] in + ( + B [0.1, cyt [!cyt []]], + (A [phos [], 0.0, nuc [!nuc []]], K [nuc [!nuc []]]) + ) + let bond [] in (B [0.1, nuc [!nuc []]], phosphorylate [0.0, cyt [!cyt []]]) + : let bond [] in + ( + B [0.1, nuc [!nuc []]], + (A [unphos [], 0.0, cyt [!cyt []]], K [cyt [!cyt []]]) + ) + → let bond [] in + ( + B [0.1, nuc [!nuc []]], + (A [phos [], 0.0, cyt [!cyt []]], K [cyt [!cyt []]]) + ) + let bond [] in (B [0.1, nuc [!nuc []]], phosphorylate [0.0, nuc [!nuc []]]) + : let bond [] in + ( + B [0.1, nuc [!nuc []]], + (A [unphos [], 0.0, nuc [!nuc []]], K [nuc [!nuc []]]) + ) + → let bond [] in + ( + B [0.1, nuc [!nuc []]], + (A [phos [], 0.0, nuc [!nuc []]], K [nuc [!nuc []]]) + )"#]]; + transitions.assert_eq(&generator.transitions(2).join("\n")); +}