diff --git a/mira/metamodel/schema.json b/mira/metamodel/schema.json index d1f3e947..14cfdcd2 100644 --- a/mira/metamodel/schema.json +++ b/mira/metamodel/schema.json @@ -819,6 +819,66 @@ "subject" ] }, + "GroupedNaturalConversion": { + "title": "GroupedNaturalConversion", + "description": "Specifies a process of multiple subjects and outcomes.", + "type": "object", + "properties": { + "rate_law": { + "title": "Rate Law", + "description": "The rate law for the template.", + "type": "string", + "example": "2*x" + }, + "name": { + "title": "Name", + "description": "The name of the template.", + "type": "string" + }, + "display_name": { + "title": "Display Name", + "description": "The display name of the template.", + "type": "string" + }, + "type": { + "title": "Type", + "default": "GroupedNaturalConversion", + "const": "GroupedNaturalConversion", + "enum": [ + "GroupedNaturalConversion" + ], + "type": "string" + }, + "subjects": { + "title": "Subjects", + "description": "The subject of the conversion.", + "type": "array", + "items": { + "$ref": "#/definitions/Concept" + } + }, + "outcomes": { + "title": "Outcomes", + "description": "The outcome of the conversion.", + "type": "array", + "items": { + "$ref": "#/definitions/Concept" + } + }, + "provenance": { + "title": "Provenance", + "description": "The provenance of the production.", + "type": "array", + "items": { + "$ref": "#/definitions/Provenance" + } + } + }, + "required": [ + "subjects", + "outcomes" + ] + }, "Distribution": { "title": "Distribution", "description": "A distribution of values for a parameter.", diff --git a/mira/metamodel/templates.py b/mira/metamodel/templates.py index c6a22ca9..9db4d2d5 100644 --- a/mira/metamodel/templates.py +++ b/mira/metamodel/templates.py @@ -16,6 +16,7 @@ "GroupedControlledConversion", "GroupedControlledProduction", "GroupedControlledDegradation", + "GroupedNaturalConversion", "NaturalReplication", "ControlledReplication", "StaticConcept", @@ -1933,3 +1934,52 @@ def conversion_to_deg_prod(conv_template): [conv_template.subject], rate_law=conv_template.rate_law) return deepcopy([tdeg, tprod]) + + + +class GroupedNaturalConversion(Template): + """Specifies a process of multiple subjects and outcomes.""" + + type: Literal["GroupedNaturalConversion"] = Field("GroupedNaturalConversion", const=True) + subjects: List[Concept] = Field(..., description="The subject of the conversion.") + outcomes: List[Concept] = Field(..., description="The outcome of the conversion.") + provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the production.") + + concept_keys: ClassVar[List[str]] = ["subjects", "outcomes"] + + + def get_key(self, config: Optional[Config] = None): + return ( + self.type, + *tuple( + c.get_key(config=config) + for c in sorted(self.subjects, key=lambda c: c.get_key(config=config)) + ), + *tuple( + c.get_key(config=config) + for c in sorted(self.outcomes, key=lambda c: c.get_key(config=config)) + ), + ) + + def get_concepts(self): + return self.subjects + self.outcomes + + def with_context( + self, + do_rename=False, + exclude_concepts=None, + curie_to_name_map=None, + **context + ) -> "GroupedNaturalConversion": + exclude_concepts = exclude_concepts or set() + return self.__class__( + type=self.type, + subjects=[c.with_context(do_rename, curie_to_name_map=curie_to_name_map, **context) + if c.name not in exclude_concepts else c + for c in self.subjects], + outcomes=[c.with_context(do_rename, curie_to_name_map=curie_to_name_map, **context) + if c.name not in exclude_concepts else c + for c in self.outcomes], + provenance=self.provenance, + rate_law=self.rate_law, + ) \ No newline at end of file diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index f3393ade..311c768f 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -75,6 +75,12 @@ def test_group_controlled(self): self.assertIn(self.infected, t1.controllers) self.assertIn(self.asymptomatic, t1.controllers) + def test_grouped_natural_conversion(self): + """Test grouped natural conversions.""" + template = GroupedNaturalConversion(subjects=[self.exposed], outcomes=[self.infected]) + self.assertEqual([self.infected], template.outcomes) + self.assertEqual([self.exposed], template.subjects) + def test_natural_degradation(self): t = NaturalDegradation(subject=self.infected) self.assertEqual(self.infected, t.subject)