diff --git a/mira/dkg/api.py b/mira/dkg/api.py index 97b3c9ebd..dffbdc31b 100644 --- a/mira/dkg/api.py +++ b/mira/dkg/api.py @@ -25,18 +25,18 @@ class RelationQuery(BaseModel): """A query for relations in the domain knowledge graph.""" - source_type: Optional[str] = Field(description="The source type (i.e., prefix)", example="vo") + source_type: Optional[str] = Field(None, description="The source type (i.e., prefix)", examples=["vo"]) source_curie: Optional[str] = Field( - description="The source compact URI (CURIE)", example="doid:946" + None, description="The source compact URI (CURIE)", examples=["doid:946"] ) target_type: Optional[str] = Field( - description="The target type (i.e., prefix)", example="ncbitaxon" + None, description="The target type (i.e., prefix)", examples=["ncbitaxon"] ) target_curie: Optional[str] = Field( - description="The target compact URI (CURIE)", example="ncbitaxon:10090" + None, description="The target compact URI (CURIE)", examples=["ncbitaxon:10090"] ) relations: Union[None, str, List[str]] = Field( - description="A relation string or list of relation strings", example="vo:0001243" + None, description="A relation string or list of relation strings", examples=["vo:0001243"] ) relation_direction: Literal["right", "left", "both"] = Field( "right", description="The direction of the relationship" @@ -50,7 +50,7 @@ class RelationQuery(BaseModel): ge=0, ) limit: Optional[int] = Field( - description="A limit on the number of records returned", example=50, ge=0 + None, description="A limit on the number of records returned", examples=[50], ge=0 ) full: bool = Field( False, @@ -178,12 +178,12 @@ def get_transitive_closure( class RelationResponse(BaseModel): """A triple (or multi-predicate triple) with abbreviated data.""" - subject: str = Field(description="The CURIE of the subject of the triple", example="doid:96") + subject: str = Field(description="The CURIE of the subject of the triple", examples=["doid:96"]) predicate: Union[str, List[str]] = Field( description="A predicate or list of predicates as CURIEs", - example="ro:0002452", + examples=["ro:0002452"], ) - object: str = Field(description="The CURIE of the object of the triple", example="symp:0000001") + object: str = Field(description="The CURIE of the object of the triple", examples=["symp:0000001"]) class FullRelationResponse(BaseModel): @@ -203,7 +203,7 @@ def get_relations( request: Request, relation_query: RelationQuery = Body( ..., - examples={ + examples=[{ "source type query": { "summary": "Query relations with a given source node type", "value": { @@ -285,7 +285,7 @@ def get_relations( "full": True, }, }, - }, + }], ), ): """Get relations based on the query sent. @@ -392,10 +392,10 @@ def add_resources( request: Request, resource_prefix_list: List[str] = Body( ..., - description="A of resources to add to the DKG", + description="A list of resources to add to the DKG", title="Resource Prefixes", - example=["probonto", "wikidata", "eiffel", "geonames", "ncit", - "nbcbitaxon"], + examples=[["probonto", "wikidata", "eiffel", "geonames", "ncit", + "nbcbitaxon"]], ) ): """From a list of resource prefixes, add a list of nodes and edges @@ -418,10 +418,10 @@ class IsOntChildResult(BaseModel): """Result of a query to /is_ontological_child""" child_curie: str = Field(..., - example="vo:0001113", + examples=["vo:0001113"], description="The child CURIE") parent_curie: str = Field(..., - example="obi:0000047", + examples=["obi:0000047"], description="The parent CURIE") is_child: bool = Field( ..., @@ -446,7 +446,7 @@ def is_ontological_child( request: Request, query: IsOntChildQuery = Body( ..., - example={"child_curie": "vo:0001113", "parent_curie": "obi:0000047"}, + examples=[{"child_curie": "vo:0001113", "parent_curie": "obi:0000047"}], ) ): """Check if one CURIE is an ontological child of another CURIE""" @@ -490,7 +490,7 @@ def search( labels: Optional[str] = Query( default=None, description="A comma-separated list of labels", - examples={ + examples=[{ "no label filter": { "summary": "Don't filter by label", "value": None, @@ -499,7 +499,7 @@ def search( "summary": "Search for units, which are labeled as `unit`", "value": "unit", }, - }, + }], ), wikidata_fallback: bool = Query( default=False, @@ -530,7 +530,7 @@ class ParentQuery(BaseModel): def common_parent( request: Request, query: ParentQuery = Body( - ..., example={"curie1": "ido:0000566", "curie2": "ido:0000567"} + ..., examples=[{"curie1": "ido:0000566", "curie2": "ido:0000567"}] ), ): """Get the common parent of two CURIEs""" diff --git a/mira/dkg/askemo/api.py b/mira/dkg/askemo/api.py index ade09fdad..5a9f2b67a 100644 --- a/mira/dkg/askemo/api.py +++ b/mira/dkg/askemo/api.py @@ -70,7 +70,7 @@ def get_askemo_terms() -> Mapping[str, Term]: """Load the epi ontology JSON.""" rv = {} for obj in json.loads(ONTOLOGY_PATH.read_text()): - term = Term.parse_obj(obj) + term = Term.model_validate(obj) rv[term.id] = term return rv @@ -79,7 +79,7 @@ def get_askemosw_terms() -> Mapping[str, Term]: """Load the space weather ontology JSON.""" rv = {} for obj in json.loads(SW_ONTOLOGY_PATH.read_text()): - term = Term.parse_obj(obj) + term = Term.model_validate(obj) rv[term.id] = term return rv @@ -87,14 +87,15 @@ def get_askem_climate_ontology_terms() -> Mapping[str, Term]: """Load the space weather ontology JSON.""" rv = {} for obj in json.loads(CLIMATE_ONTOLOGY_PATH.read_text()): - term = Term.parse_obj(obj) + term = Term.model_validate(obj) rv[term.id] = term return rv def write(ontology: Mapping[str, Term], path: Path) -> None: terms = [ - term.dict(exclude_unset=True, exclude_defaults=True, exclude_none=True) + term.model_dump(exclude_unset=True, exclude_defaults=True, + exclude_none=True) for _curie, term in sorted(ontology.items()) ] path.write_text( diff --git a/mira/dkg/client.py b/mira/dkg/client.py index ee9b8a2c9..972a84c7e 100644 --- a/mira/dkg/client.py +++ b/mira/dkg/client.py @@ -14,7 +14,7 @@ import pystow import requests from neo4j import GraphDatabase, Transaction, unit_of_work -from pydantic import BaseModel, Field, validator +from pydantic import BaseModel, Field, field_validator from tqdm import tqdm from typing_extensions import Literal, TypeAlias @@ -44,28 +44,28 @@ class Relation(BaseModel): """A relationship between two entities in the DKG""" source_curie: str = Field( - description="The curie of the source node", example="probonto:k0000000" + description="The curie of the source node", examples=["probonto:k0000000"] ) target_curie: str = Field( - description="The curie of the target node", example="probonto:k0000007" + description="The curie of the target node", examples=["probonto:k0000007"] ) type: str = Field( - description="The type of the relation", example="has_parameter" + description="The type of the relation", examples=["has_parameter"] ) pred: str = Field( description="The curie of the relation type", - example="probonto:c0000062" + examples=["probonto:c0000062"] ) source: str = Field( - description="The prefix of the relation curie", example="probonto" + description="The prefix of the relation curie", examples=["probonto"] ) graph: str = Field( description="The URI of the relation", - example="https://raw.githubusercontent.com/probonto" - "/ontology/master/probonto4ols.owl" + examples=["https://raw.githubusercontent.com/probonto" + "/ontology/master/probonto4ols.owl"] ) version: str = Field( - description="The version number", example="2.5" + description="The version number", examples=["2.5"] ) @@ -73,45 +73,45 @@ class Entity(BaseModel): """An entity in the domain knowledge graph.""" id: str = Field( - ..., title="Compact URI", description="The CURIE of the entity", example="ido:0000511" + ..., title="Compact URI", description="The CURIE of the entity", examples=["ido:0000511"] ) - name: Optional[str] = Field(description="The name of the entity", example="infected population") - type: EntityType = Field(..., description="The type of the entity", example="class") - obsolete: bool = Field(..., description="Is the entity marked obsolete?", example=False) + name: Optional[str] = Field(None, description="The name of the entity", examples=["infected population"]) + type: EntityType = Field(..., description="The type of the entity", examples=["class"]) + obsolete: bool = Field(..., description="Is the entity marked obsolete?", examples=[False]) description: Optional[str] = Field( - description="The description of the entity.", - example="An organism population whose members have an infection.", + None, description="The description of the entity.", + examples=["An organism population whose members have an infection."], ) synonyms: List[Synonym] = Field( - default_factory=list, description="A list of string synonyms", example=[] + default_factory=list, description="A list of string synonyms", examples=[[]] ) alts: List[str] = Field( title="Alternative Identifiers", default_factory=list, - example=[], + examples=[[]], description="A list of alternative identifiers, given as CURIE strings.", ) xrefs: List[Xref] = Field( title="Database Cross-references", default_factory=list, - example=[], + examples=[[]], description="A list of database cross-references, given as CURIE strings.", ) labels: List[str] = Field( default_factory=list, - example=["ido"], + examples=[["ido"]], description="A list of Neo4j labels assigned to the entity.", ) properties: Dict[str, List[str]] = Field( default_factory=dict, description="A mapping of properties to their values", - example={}, + examples=[{}], ) # Gets auto-populated link: Optional[str] = None - @validator("link") - def set_link(cls, value, values): + @field_validator("link") + def set_link(cls, value, validation_info): """ Set the value of the ``link`` field based on the value of the ``id`` field. This gets run as a post-init hook by Pydantic @@ -119,7 +119,7 @@ def set_link(cls, value, values): See also: https://stackoverflow.com/questions/54023782/pydantic-make-field-none-in-validator-based-on-other-fields-value """ - curie = values["id"] + curie = validation_info.data["id"] return f"{METAREGISTRY_BASE}/{curie}" @property @@ -209,7 +209,7 @@ def as_askem_entity(self): raise ValueError(f"can only call as_askem_entity() on ASKEM ontology terms") if isinstance(self, AskemEntity): return self - data = self.dict() + data = self.model_dump() return AskemEntity( **data, physical_min=self._get_single_property( @@ -235,12 +235,12 @@ class AskemEntity(Entity): """An extended entity with more ASKEM stuff loaded in.""" # TODO @ben please write descriptions for these - physical_min: Optional[float] = Field(description="") - physical_max: Optional[float] = Field(description="") - suggested_data_type: Optional[str] = Field(description="") - suggested_unit: Optional[str] = Field(description="") - typical_min: Optional[float] = Field(description="") - typical_max: Optional[float] = Field(description="") + physical_min: Optional[float] = Field(None, description="") + physical_max: Optional[float] = Field(None, description="") + suggested_data_type: Optional[str] = Field(None, description="") + suggested_unit: Optional[str] = Field(None, description="") + typical_min: Optional[float] = Field(None, description="") + typical_max: Optional[float] = Field(None, description="") class Neo4jClient: diff --git a/mira/dkg/construct.py b/mira/dkg/construct.py index 322f6c534..3b8b04ae6 100644 --- a/mira/dkg/construct.py +++ b/mira/dkg/construct.py @@ -563,7 +563,12 @@ def main( ): """Generate the node and edge files.""" if Path(use_case).is_file(): - config = DKGConfig.parse_file(use_case) + with open(use_case, 'r') as file: + file_content = file.read() + if use_case.lower().endswith(".json"): + config = DKGConfig.model_validate_json(file_content) + else: + config = DKGConfig.model_validate(file_content) use_case = config.use_case else: config = None diff --git a/mira/dkg/construct_registry.py b/mira/dkg/construct_registry.py index bde7c12bd..77a3d7605 100644 --- a/mira/dkg/construct_registry.py +++ b/mira/dkg/construct_registry.py @@ -120,7 +120,13 @@ def _construct_registry( edges_path: Optional[Path] = None, upload: bool = False, ): - config = Config.parse_file(config_path) + with config_path.open("r") as file: + file_content = file.read() + + if config_path.suffix.lower() == ".json": + config = Config.model_validate_json(file_content) + else: + config = Config.model_validate(file_content) prefixes = get_prefixes(nodes_path=nodes_path, edges_path=edges_path) manager = Manager( @@ -140,7 +146,8 @@ def _construct_registry( ) output_path.write_text( - json.dumps(new_config.dict(exclude_none=True, exclude_unset=True), indent=2) + json.dumps(new_config.model_dump(exclude_none=True, + exclude_unset=True), indent=2) ) if upload: upload_s3(output_path, use_case="epi") diff --git a/mira/dkg/grounding.py b/mira/dkg/grounding.py index eecc07b5a..e53c5da78 100644 --- a/mira/dkg/grounding.py +++ b/mira/dkg/grounding.py @@ -18,16 +18,16 @@ class GroundRequest(BaseModel): """A model representing the parameters to be passed to :func:`gilda.ground` for grounding.""" - text: str = Field(..., description="The text to be grounded", example="Infected Population") + text: str = Field(..., description="The text to be grounded", examples=["Infected Population"]) context: Optional[str] = Field( None, description="Context around the text to be grounded", - example="The infected population increased over the past month", + examples=["The infected population increased over the past month"], ) namespaces: Optional[List[str]] = Field( None, description="A list of namespaces to filter groundings to.", - example=["do", "mondo", "ido"], + examples=[["do", "mondo", "ido"]], ) @@ -37,30 +37,30 @@ class GroundResult(BaseModel): url: str = Field( ..., description="A URL that resolves the term to an external web service", - example=f"{BR_BASE}/ido:0000511", + examples=[f"{BR_BASE}/ido:0000511"], ) score: float = Field( - ..., description="The matching score calculated by Gilda", ge=0.0, le=1.0, example=0.78 + ..., description="The matching score calculated by Gilda", ge=0.0, le=1.0, examples=[0.78] ) prefix: str = Field( ..., description="The prefix corresponding to the ontology/database from which the term comes", - example="ido", + examples=["ido"], ) identifier: str = Field( ..., description="The local unique identifier for the term in the ontology/database denoted by the prefix", - example="0000511", + examples=["0000511"], ) curie: str = Field( ..., description="The compact URI that combines the prefix and local identifier.", - example="ido:0000511", + examples=["ido:0000511"], ) name: str = Field( - ..., description="The standard entity name for the term", example="infected population" + ..., description="The standard entity name for the term", examples=["infected population"] ) - status: str = Field(..., description="The match status, e.g., name, synonym", example="name") + status: str = Field(..., description="The match status, e.g., name, synonym", examples=["name"]) @classmethod def from_scored_match(cls, scored_match: ScoredMatch) -> "GroundResult": diff --git a/mira/dkg/model.py b/mira/dkg/model.py index a67e4ba2e..5af4eb6ba 100644 --- a/mira/dkg/model.py +++ b/mira/dkg/model.py @@ -110,7 +110,9 @@ standard now uses that endpoint. """.rstrip()), ) -def model_to_petri(template_model: Dict[str, Any] = Body(..., example=template_model_example)): +def model_to_petri(template_model: Dict[str, Any] = Body(..., + examples=[ + template_model_example])): """Create a PetriNet model from a TemplateModel""" tm = TemplateModel.from_json(template_model) model = Model(tm) @@ -134,7 +136,8 @@ def model_to_petri(template_model: Dict[str, Any] = Body(..., example=template_m standard now uses that endpoint. """.rstrip()), ) -def petri_to_model(petri_json: Dict[str, Any] = Body(..., example=petrinet_json)): +def petri_to_model(petri_json: Dict[str, Any] = Body(..., + examples=[petrinet_json])): """Create a TemplateModel from a PetriNet model""" return template_model_from_petri_json(petri_json) @@ -151,7 +154,8 @@ def petri_to_model(petri_json: Dict[str, Any] = Body(..., example=petrinet_json) implement this standard. """.rstrip()), ) -def model_to_amr(template_model: Dict[str, Any] = Body(..., example=template_model_example)): +def model_to_amr(template_model: Dict[str, Any] = Body(..., + examples=[template_model_example])): """Create an AMR Petri model from a TemplateModel.""" tm = TemplateModel.from_json(template_model) model = Model(tm) @@ -171,7 +175,8 @@ def model_to_amr(template_model: Dict[str, Any] = Body(..., example=template_mod extension, stratification, and comparison. """.rstrip()), ) -def amr_to_model(amr_json: Dict[str, Any] = Body(..., example=amr_petrinet_json)): +def amr_to_model(amr_json: Dict[str, Any] = Body(..., + examples=[amr_petrinet_json])): """Create a TemplateModel from an AMR model.""" return template_model_from_amr_json(amr_json) @@ -181,17 +186,17 @@ class StratificationQuery(BaseModel): template_model: Dict[str, Any] = Field( ..., description="The template model to stratify", - example=template_model_example + examples=[template_model_example] ) key: str = Field( ..., description="The (singular) name of the stratification", - example="city" + examples=["city"] ) strata: Set[str] = Field( ..., description="A list of the values for stratification", - example=["boston", "nyc"] + examples=[["boston", "nyc"]] ) strata_name_map: Union[Dict[str, str], None] = Field( None, @@ -199,9 +204,9 @@ class StratificationQuery(BaseModel): "renaming the concepts. If none given, will use the " "strata values as the names. This option only has an " "effect if ``modify_names`` is true.", - example={ + examples=[{ "geonames:4930956": "Boston", "geonames:5128581": "New York City" - }, + }], ) strata_name_lookup: bool = Field( False, @@ -209,26 +214,26 @@ class StratificationQuery(BaseModel): "strata values under the assumption that they are " "curies. This flag has no impact if ``strata_name_map`` " "is given.", - example=True + examples=[True] ) structure: Union[List[List[str]], None] = Field( None, description="An iterable of pairs corresponding to a directed network " "structure where each of the pairs has two strata. If none given, " "will assume a complete network structure.", - example=[["boston", "nyc"]], + examples=[[["boston", "nyc"]]], ) directed: bool = Field( False, description="Whether the model has directed edges or not.", - example=True + examples=[True] ) conversion_cls: Literal["natural_conversion", "controlled_conversion"] = Field( "natural_conversion", description="The template class to be used for conversions between " "strata defined by the network structure.", - example="natural_conversion", + examples=["natural_conversion"], ) cartesian_control: bool = Field( False, @@ -247,38 +252,38 @@ class StratificationQuery(BaseModel): through the perspective of the model) affect the infection of susceptible population in another city. """), - example=True + examples=[True] ) modify_names: bool = Field( True, description="If true, will modify the names of the concepts to " "include the strata (e.g., ``'S'`` becomes " "``'S_boston'``). If false, will keep the original names.", - example=True + examples=[True] ) params_to_stratify: Optional[List[str]] = Field( None, description="A list of parameters to stratify. If none given, " "will stratify all parameters.", - example=["beta"] + examples=[["beta"]] ) params_to_preserve: Optional[List[str]] = Field( None, description="A list of parameters to preserve. If none given, " "will stratify all parameters.", - example=["gamma"] + examples=[["gamma"]] ) concepts_to_stratify: Optional[List[str]] = Field( None, description="A list of concepts to stratify. If none given, " "will stratify all concepts.", - example=["susceptible", "infected"], + examples=[["susceptible", "infected"]], ) concepts_to_preserve: Optional[List[str]] = Field( None, description="A list of concepts to preserve. If none given, " "will stratify all concepts.", - example=["recovered"], + examples=[["recovered"]], ) def get_conversion_cls(self) -> Type[Template]: @@ -292,13 +297,13 @@ def model_stratification( request: Request, stratification_query: StratificationQuery = Body( ..., - example={ + examples=[{ "template_model": template_model_example, "key": "city", "strata": ["geonames:4930956", "geonames:5128581"], "strata_name_lookup": True, "params_to_stratify": ["beta"], - }, + }], ) ): """Stratify a model according to the specified stratification""" @@ -346,11 +351,11 @@ def model_stratification( def dimension_transform( query: Dict[str, Any] = Body( ..., - example={ + examples=[{ "model": sir_parameterized_init, "counts_unit": "person", "norm_factor": 1e5, - }, + }], ) ): """Convert all entity concentrations to dimensionless units""" @@ -371,11 +376,11 @@ def dimension_transform( def dimension_transform( query: Dict[str, Any] = Body( ..., - example={ + examples=[{ "model": amr_petrinet_json_units_values, "counts_units": "persons", "norm_factor": 1e5, - }, + }], ) ): """Convert all entity concentrations to dimensionless units""" @@ -406,12 +411,12 @@ def biomodels_id_to_model( model_id: str = FastPath( ..., description="The BioModels model ID to get the template model for.", - example="BIOMD0000000956", + examples=["BIOMD0000000956"], ), simplify: bool = Query( default=True, description="Whether to simplify the rate laws of the model.", - example=True, + examples=[True], ), aggregate_params: bool = Query( default=False, @@ -419,7 +424,7 @@ def biomodels_id_to_model( "a new parameter to make rate laws more mass action like" "if the actual rate law uses some function of constants" "and one or more parameters.", - example=False, + examples=[False], ) ): """Get a BioModels base template model by providing its model id""" @@ -440,7 +445,7 @@ def bilayer_to_template_model( bilayer: Dict[str, Any] = Body( ..., description="The bilayer json to transform to a template model", - example=sir_bilayer, + examples=[sir_bilayer], ) ): """Transform a bilayer json to a template model""" @@ -453,7 +458,7 @@ def template_model_to_bilayer( template_model: Dict[str, Any] = Body( ..., description="A template model to turn into a bilayer json", - example=template_model_example, + examples=[template_model_example], ) ): """Turn template model into a bilayer json""" @@ -518,7 +523,8 @@ def _graph_model( ) def model_to_viz_dot( bg_task: BackgroundTasks, - template_model: Dict[str, Any] = Body(..., example=template_model_example), + template_model: Dict[str, Any] = Body(..., examples=[ + template_model_example]), ): """Create a graphviz dot file from a TemplateModel""" tm = TemplateModel.from_json(template_model) @@ -540,7 +546,8 @@ def model_to_viz_dot( ) def model_to_graph_image( bg_task: BackgroundTasks, - template_model: Dict[str, Any] = Body(..., example=template_model_example), + template_model: Dict[str, Any] = Body(..., examples=[ + template_model_example]), ): """Create a graph image from a TemplateModel""" tm = TemplateModel.from_json(template_model) @@ -554,9 +561,9 @@ def model_to_graph_image( class TemplateModelDeltaQuery(BaseModel): - template_model1: Dict[str, Any] = Field(..., example=template_model_example) + template_model1: Dict[str, Any] = Field(..., examples=[template_model_example]) template_model2: Dict[str, Any] = Field( - ..., example=template_model_example_w_context + ..., examples=[template_model_example_w_context] ) @@ -629,7 +636,7 @@ def models_to_delta_image( class AddTranstitionQuery(BaseModel): template_model: Dict[str, Any] = Field( - ..., description="The template model to add the transition to", example=template_model_example + ..., description="The template model to add the transition to", examples=[template_model_example] ) subject_concept: Concept = Field(..., description="The subject concept") outcome_concept: Concept = Field(..., description="The outcome concept") @@ -640,13 +647,13 @@ class AddTranstitionQuery(BaseModel): def add_transition( add_transition_query: AddTranstitionQuery = Body( ..., - example={ + examples=[{ "template_model": template_model_example, "subject_concept": {"name": "infected population", "identifiers": {"ido": "0000511"}}, "outcome_concept": {"name": "dead", "identifiers": {"ncit": "C28554"}}, - }, + }], ) ): """Add a transition between two concepts in a template model""" @@ -661,14 +668,14 @@ def add_transition( class ModelComparisonQuery(BaseModel): template_models: List[Dict[str, Any]] = Field( - ..., example=[ + ..., examples=[[ template_model_example, template_model_example_w_context - ] + ]] ) class ModelComparisonResponse(BaseModel): - graph_comparison_data: Dict[str, Any] #ModelComparisonGraphdata + graph_comparison_data: Union[Dict[str, Any], ModelComparisonGraphdata] #ModelComparisonGraphdata similarity_scores: List[Dict[str, Union[List[int], float]]] = Field( ..., description="A dictionary of similarity scores between all the " "provided models." @@ -692,7 +699,7 @@ def model_comparison( template_models, refinement_func=request.app.state.refinement_closure.is_ontological_child ) resp = ModelComparisonResponse( - graph_comparison_data=graph_comparison_data.dict(), + graph_comparison_data=graph_comparison_data.model_dump(), similarity_scores=graph_comparison_data.get_similarity_scores(), ) return resp @@ -700,7 +707,7 @@ def model_comparison( class AMRComparisonQuery(BaseModel): petrinet_models: List[Dict[str, Any]] = Field( - ..., example=[amr_petrinet_json, amr_petrinet_json_2_city] + ..., examples=[[amr_petrinet_json, amr_petrinet_json_2_city]] ) @@ -725,7 +732,7 @@ def askepetrinet_model_comparison( app.state.refinement_closure.is_ontological_child ) resp = ModelComparisonResponse( - graph_comparison_data=graph_comparison_data.dict(), + graph_comparison_data=graph_comparison_data.model_dump(), similarity_scores=graph_comparison_data.get_similarity_scores(), ) return resp @@ -742,7 +749,7 @@ def askepetrinet_model_comparison( class FluxSpanQuery(BaseModel): model: Dict[str, Any] = Field( ..., - example=flux_span_query_example, + examples=[flux_span_query_example], description="The model to recover the ODE-semantics from.", ) diff --git a/mira/dkg/models.py b/mira/dkg/models.py index 58c632cc6..6a6f3e195 100644 --- a/mira/dkg/models.py +++ b/mira/dkg/models.py @@ -37,7 +37,7 @@ class Xref(BaseModel): id: str = Field(description="The CURIE of the cross reference") type: str = Field( description="The CURIE for the cross reference predicate", - example="skos:exactMatch", + examples=["skos:exactMatch"], ) @@ -47,5 +47,5 @@ class Synonym(BaseModel): value: str = Field(description="The text of the synonym") type: str = Field( description="The CURIE for the synonym predicate", - example="skos:exactMatch", + examples=["skos:exactMatch"], ) diff --git a/mira/dkg/web_client.py b/mira/dkg/web_client.py index 677630f25..1529578ac 100644 --- a/mira/dkg/web_client.py +++ b/mira/dkg/web_client.py @@ -130,7 +130,8 @@ def get_relations_web( print(relations[:5]) """ - query_json = relations_model.dict(exclude_unset=True, exclude_defaults=True) + query_json = relations_model.model_dump(exclude_unset=True, + exclude_defaults=True) res_json = web_client( endpoint="/relations", method="post", query_json=query_json, api_url=api_url ) diff --git a/mira/metamodel/comparison.py b/mira/metamodel/comparison.py index a53c75931..5fafcac5c 100644 --- a/mira/metamodel/comparison.py +++ b/mira/metamodel/comparison.py @@ -1,3 +1,6 @@ +from pydantic import Field, ConfigDict +from typing_extensions import Annotated + __all__ = ["ModelComparisonGraphdata", "TemplateModelComparison", "TemplateModelDelta", "RefinementClosure", "get_dkg_refinement_closure"] @@ -9,7 +12,7 @@ import networkx as nx import sympy -from pydantic import BaseModel, conint, Field +from pydantic import BaseModel, Field from tqdm import tqdm from .templates import Provenance, Concept, Template, SympyExprStr, IS_EQUAL, \ @@ -27,13 +30,15 @@ class DataNode(BaseModel): """A node in a ModelComparisonGraphdata""" + model_config = ConfigDict(protected_namespaces=()) node_type: Literal["template", "concept"] - model_id: conint(ge=0, strict=True) + model_id: Annotated[int, Field(ge=0, strict=True)] class TemplateNode(DataNode): """A node in a ModelComparisonGraphdata representing a Template""" + model_config = ConfigDict(protected_namespaces=()) type: str rate_law: Optional[SympyExprStr] = \ Field(default=None, description="The rate law of this template") @@ -69,15 +74,8 @@ class IntraModelEdge(DataEdge): class ModelComparisonGraphdata(BaseModel): """A data structure holding a graph representation of TemplateModel delta""" - class Config: - arbitrary_types_allowed = True - json_encoders = { - SympyExprStr: lambda e: str(e), - } - json_decoders = { - SympyExprStr: lambda e: safe_parse_expr(e), - Template: lambda t: Template.from_json(data=t), - } + + model_config = ConfigDict(arbitrary_types_allowed=True) template_models: Dict[int, TemplateModel] = Field( ..., description="A mapping of template model keys to template models" diff --git a/mira/metamodel/io.py b/mira/metamodel/io.py index 09bdaa2ef..6c30fed44 100644 --- a/mira/metamodel/io.py +++ b/mira/metamodel/io.py @@ -35,7 +35,7 @@ def model_to_json_file(model: TemplateModel, fname): A file path to dump the model into. """ with open(fname, 'w') as fh: - json.dump(json.loads(model.json()), fh, indent=1) + json.dump(json.loads(model.model_dump_json()), fh, indent=1) def expression_to_mathml(expression: sympy.Expr, *args, **kwargs) -> str: diff --git a/mira/metamodel/ops.py b/mira/metamodel/ops.py index a4b209d43..adec6c751 100644 --- a/mira/metamodel/ops.py +++ b/mira/metamodel/ops.py @@ -725,7 +725,9 @@ def counts_to_dimensionless(tm: TemplateModel, SympyExprStr(p.units.expression.args[0] / (counts_unit_symbol ** exponent)) p.value /= (norm_factor ** exponent) - + # Previously was sympy.Float object, cannot be serialized in + # Pydantic2 type enforcement + p.value = float(p.value) return tm diff --git a/mira/metamodel/schema.json b/mira/metamodel/schema.json index e15eb25df..a09b4feb0 100644 --- a/mira/metamodel/schema.json +++ b/mira/metamodel/schema.json @@ -3,1415 +3,1935 @@ "$id": "https://raw.githubusercontent.com/indralab/mira/main/mira/metamodel/schema.json", "title": "MIRA Metamodel Template Schema", "description": "MIRA metamodel templates give a high-level abstraction of modeling appropriate for many domains.", - "definitions": { - "Unit": { - "title": "Unit", - "description": "A unit of measurement.", - "type": "object", + "$defs": { + "Annotations": { + "description": "A metadata model for model-level annotations.\n\nExamples in this metadata model are taken from\nhttps://www.ebi.ac.uk/biomodels/BIOMD0000000956,\na well-annotated SIR model in the BioModels database.", "properties": { - "expression": { - "title": "Expression", - "description": "The expression for the unit.", - "type": "string", - "example": "2*x" + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "A human-readable label for the model", + "examples": [ + "SIR model of scenarios of COVID-19 spread in CA and NY" + ], + "title": "Name" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "A description of the model", + "examples": [ + "The coronavirus disease 2019 (COVID-19) pandemic has placed epidemic modeling at the forefront of worldwide public policy making. Nonetheless, modeling and forecasting the spread of COVID-19 remains a challenge. Here, we detail three regional scale models for forecasting and assessing the course of the pandemic. This work demonstrates the utility of parsimonious models for early-time data and provides an accessible framework for generating policy-relevant insights into its course. We show how these models can be connected to each other and to time series data for a particular region. Capable of measuring and forecasting the impacts of social distancing, these models highlight the dangers of relaxing nonpharmaceutical public health interventions in the absence of a vaccine or antiviral therapies." + ], + "title": "Description" + }, + "license": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Information about the licensing of the model artifact. Ideally, given as an SPDX identifier like CC0 or CC-BY-4.0. For example, models from the BioModels databases are all licensed under the CC0 public attribution license.", + "examples": [ + "CC0" + ], + "title": "License" + }, + "authors": { + "description": "A list of authors/creators of the model. This is not the same as the people who e.g., submitted the model to BioModels", + "examples": [ + [ + { + "name": "Andrea L Bertozzi" + }, + { + "name": "Elisa Franco" + }, + { + "name": "George Mohler" + }, + { + "name": "Martin B Short" + }, + { + "name": "Daniel Sledge" + } + ] + ], + "items": { + "$ref": "#/$defs/Author" + }, + "title": "Authors", + "type": "array" + }, + "references": { + "description": "A list of CURIEs (i.e., :) corresponding to literature references that describe the model. Do **not** duplicate the same publication with different CURIEs (e.g., using pubmed, pmc, and doi)", + "examples": [ + [ + "pubmed:32616574" + ] + ], + "items": { + "type": "string" + }, + "title": "References", + "type": "array" + }, + "time_scale": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The granularity of the time element of the model, typically on the scale of days, weeks, or months for epidemiology models", + "examples": [ + "day" + ], + "title": "Time Scale" + }, + "time_start": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The start time of the applicability of a model, given as a datetime. When the time scale is not so granular, leave the less granular fields as default, i.e., if the time scale is on months, give dates like YYYY-MM-01 00:00", + "title": "Time Start" + }, + "time_end": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Similar to the start time of the applicability of a model, the end time is given as a datetime. For example, the Bertozzi 2020 model is applicable between March and August 2020, so this field is annotated with August 1st, 2020.", + "title": "Time End" + }, + "locations": { + "description": "A location or list of locations where this model is applicable, ideally annotated using a CURIEs referencing a controlled vocabulary such as GeoNames, which has multiple levels of granularity including city/state/country level terms. For example,the Bertozzi 2020 model was for New York City (geonames:5128581) and California (geonames:5332921)", + "examples": [ + [ + "geonames:5128581", + "geonames:5332921" + ] + ], + "items": { + "type": "string" + }, + "title": "Locations", + "type": "array" + }, + "pathogens": { + "description": "The pathogens present in the model, given with CURIEs referencing vocabulary for taxa, ideally, NCBI Taxonomy. For example, the Bertozzi 2020 model is about SARS-CoV-2, this is ncbitaxon:2697049. Do not confuse this field with terms for annotating the disease caused by the pathogen. Note that some models may have multiple pathogens, for simulating double pandemics such as the interaction with SARS-CoV-2 and the seasonal flu.", + "examples": [ + [ + "ncbitaxon:2697049" + ] + ], + "items": { + "type": "string" + }, + "title": "Pathogens", + "type": "array" + }, + "diseases": { + "description": "The diseases caused by pathogens in the model, given with CURIEs referencing vocabulary for dieases, such as DOID, EFO, or MONDO. For example, the Bertozzi 2020 model is about SARS-CoV-2, which causes COVID-19. In the Human Disease Ontology (DOID), this is referenced by doid:0080600.", + "examples": [ + [ + "doid:0080600" + ] + ], + "items": { + "type": "string" + }, + "title": "Diseases", + "type": "array" + }, + "hosts": { + "description": "The hosts present in the model, given with CURIEs referencing vocabulary for taxa, ideally, NCBI Taxonomy. For example, the Bertozzi 2020 model is about human infection by SARS-CoV-2. Therefore, the appropriate annotation for this field would be ncbitaxon:9606. Note that some models have multiple hosts, such as Malaria models that consider humans and mosquitos.", + "examples": [ + [ + "ncbitaxon:9606" + ] + ], + "items": { + "type": "string" + }, + "title": "Hosts", + "type": "array" + }, + "model_types": { + "description": "This field describes the type(s) of the model using the Mathematical Modeling Ontology (MAMO), which has terms like 'ordinary differential equation model', 'population model', etc. These should be annotated as CURIEs in the form of mamo:. For example, the Bertozzi 2020 model is a population model (mamo:0000028) and ordinary differential equation model (mamo:0000046)", + "examples": [ + [ + "mamo:0000028", + "mamo:0000046" + ] + ], + "items": { + "type": "string" + }, + "title": "Model Types", + "type": "array" + } + }, + "title": "Annotations", + "type": "object" + }, + "Author": { + "description": "A metadata model for an author.", + "properties": { + "name": { + "description": "The name of the author", + "title": "Name", + "type": "string" } }, "required": [ - "expression" - ] + "name" + ], + "title": "Author", + "type": "object" }, "Concept": { - "title": "Concept", "description": "A concept is specified by its identifier(s), name, and - optionally -\nits context.", - "type": "object", "properties": { "name": { - "title": "Name", "description": "The name of the concept.", + "title": "Name", "type": "string" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "An optional display name for the concept. If not provided, the name can be used for display purposes.", - "type": "string" + "title": "Display Name" }, "description": { - "title": "Description", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "An optional description of the concept.", - "type": "string" + "title": "Description" }, "identifiers": { - "title": "Identifiers", - "description": "A mapping of namespaces to identifiers.", - "type": "object", "additionalProperties": { "type": "string" - } + }, + "description": "A mapping of namespaces to identifiers.", + "title": "Identifiers", + "type": "object" }, "context": { - "title": "Context", - "description": "A mapping of context keys to values.", - "type": "object", "additionalProperties": { - "type": "string" - } + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "description": "A mapping of context keys to values.", + "title": "Context", + "type": "object" }, "units": { - "title": "Units", - "description": "The units of the concept.", - "allOf": [ + "anyOf": [ + { + "$ref": "#/$defs/Unit" + }, { - "$ref": "#/definitions/Unit" + "type": "null" } - ] + ], + "default": null, + "description": "The units of the concept." } }, "required": [ "name" - ] - }, - "Template": { - "title": "Template", - "description": "The Template is a parent class for model processes", - "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" - } - } - }, - "Provenance": { - "title": "Provenance", - "type": "object", - "properties": {} + ], + "title": "Concept", + "type": "object" }, "ControlledConversion": { - "title": "ControlledConversion", "description": "Specifies a process of controlled conversion from subject to outcome,\ncontrolled by the controller.", - "type": "object", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "ControlledConversion", "const": "ControlledConversion", + "default": "ControlledConversion", "enum": [ "ControlledConversion" ], + "title": "Type", "type": "string" }, "controller": { - "title": "Controller", - "description": "The controller of the conversion.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The controller of the conversion." }, "subject": { - "title": "Subject", - "description": "The subject of the conversion.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The subject of the conversion." }, "outcome": { - "title": "Outcome", - "description": "The outcome of the conversion.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The outcome of the conversion." }, "provenance": { - "title": "Provenance", "description": "The provenance of the conversion.", - "type": "array", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ "controller", "subject", "outcome" - ] + ], + "title": "ControlledConversion", + "type": "object" }, - "GroupedControlledConversion": { - "title": "GroupedControlledConversion", - "description": "The Template is a parent class for model processes", - "type": "object", + "ControlledDegradation": { + "description": "Specifies a process of degradation controlled by one controller", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "GroupedControlledConversion", - "const": "GroupedControlledConversion", + "const": "ControlledDegradation", + "default": "ControlledDegradation", "enum": [ - "GroupedControlledConversion" + "ControlledDegradation" ], + "title": "Type", "type": "string" }, - "controllers": { - "title": "Controllers", - "description": "The controllers of the conversion.", - "type": "array", - "items": { - "$ref": "#/definitions/Concept" - } + "controller": { + "$ref": "#/$defs/Concept", + "description": "The controller of the degradation." }, "subject": { - "title": "Subject", - "description": "The subject of the conversion.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] - }, - "outcome": { - "title": "Outcome", - "description": "The outcome of the conversion.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The subject of the degradation." }, "provenance": { - "title": "Provenance", - "description": "The provenance of the conversion.", - "type": "array", + "description": "The provenance of the degradation.", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ - "controllers", - "subject", - "outcome" - ] + "controller", + "subject" + ], + "title": "ControlledDegradation", + "type": "object" }, - "GroupedControlledProduction": { - "title": "GroupedControlledProduction", - "description": "Specifies a process of production controlled by several controllers", - "type": "object", + "ControlledProduction": { + "description": "Specifies a process of production controlled by one controller", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "GroupedControlledProduction", - "const": "GroupedControlledProduction", + "const": "ControlledProduction", + "default": "ControlledProduction", "enum": [ - "GroupedControlledProduction" + "ControlledProduction" ], + "title": "Type", "type": "string" }, - "controllers": { - "title": "Controllers", - "description": "The controllers of the production.", - "type": "array", - "items": { - "$ref": "#/definitions/Concept" - } + "controller": { + "$ref": "#/$defs/Concept", + "description": "The controller of the production." }, "outcome": { - "title": "Outcome", - "description": "The outcome of the production.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The outcome of the production." }, "provenance": { - "title": "Provenance", - "description": "The provenance of the production.", - "type": "array", + "description": "Provenance of the template", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ - "controllers", + "controller", "outcome" - ] - }, - "ControlledProduction": { + ], "title": "ControlledProduction", - "description": "Specifies a process of production controlled by one controller", - "type": "object", + "type": "object" + }, + "ControlledReplication": { + "description": "Specifies a process of replication controlled by one controller", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "ControlledProduction", - "const": "ControlledProduction", + "const": "ControlledReplication", + "default": "ControlledReplication", "enum": [ - "ControlledProduction" + "ControlledReplication" ], + "title": "Type", "type": "string" }, "controller": { - "title": "Controller", - "description": "The controller of the production.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The controller of the replication." }, - "outcome": { - "title": "Outcome", - "description": "The outcome of the production.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "subject": { + "$ref": "#/$defs/Concept", + "description": "The subject of the replication." }, "provenance": { - "title": "Provenance", - "description": "Provenance of the template", - "type": "array", + "description": "The provenance of the replication.", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ "controller", - "outcome" - ] + "subject" + ], + "title": "ControlledReplication", + "type": "object" }, - "NaturalConversion": { - "title": "NaturalConversion", - "description": "Specifies a process of natural conversion from subject to outcome", - "type": "object", + "Distribution": { + "description": "A distribution of values for a parameter.", "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": { + "description": "The type of distribution as provided by ProbOnto e.g. 'StandardUniform1', 'Beta1', etc.", "title": "Type", - "default": "NaturalConversion", - "const": "NaturalConversion", - "enum": [ - "NaturalConversion" - ], "type": "string" }, - "subject": { - "title": "Subject", - "description": "The subject of the conversion.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] - }, - "outcome": { - "title": "Outcome", - "description": "The outcome of the conversion.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] - }, - "provenance": { - "title": "Provenance", - "description": "The provenance of the conversion.", - "type": "array", - "items": { - "$ref": "#/definitions/Provenance" - } + "parameters": { + "additionalProperties": { + "anyOf": [ + { + "type": "number" + }, + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + } + ] + }, + "description": "The parameters of the distribution keyed by parameter names controlled by ProbOnto and values that are either floating point values or symbolic expressions over other parameters.", + "title": "Parameters", + "type": "object" } }, "required": [ - "subject", - "outcome" - ] + "type", + "parameters" + ], + "title": "Distribution", + "type": "object" }, - "MultiConversion": { - "title": "MultiConversion", - "description": "Specifies a conversion process of multiple subjects and outcomes.", - "type": "object", + "GroupedControlledConversion": { "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "MultiConversion", - "const": "MultiConversion", + "const": "GroupedControlledConversion", + "default": "GroupedControlledConversion", "enum": [ - "MultiConversion" + "GroupedControlledConversion" ], + "title": "Type", "type": "string" }, - "subjects": { - "title": "Subjects", - "description": "The subjects of the conversion.", - "type": "array", + "controllers": { + "description": "The controllers of the conversion.", "items": { - "$ref": "#/definitions/Concept" - } + "$ref": "#/$defs/Concept" + }, + "title": "Controllers", + "type": "array" }, - "outcomes": { - "title": "Outcomes", - "description": "The outcomes of the conversion.", - "type": "array", - "items": { - "$ref": "#/definitions/Concept" - } + "subject": { + "$ref": "#/$defs/Concept", + "description": "The subject of the conversion." + }, + "outcome": { + "$ref": "#/$defs/Concept", + "description": "The outcome of the conversion." }, "provenance": { - "title": "Provenance", "description": "The provenance of the conversion.", - "type": "array", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ - "subjects", - "outcomes" - ] + "controllers", + "subject", + "outcome" + ], + "title": "GroupedControlledConversion", + "type": "object" }, - "ReversibleFlux": { - "title": "ReversibleFlux", - "description": "Specifies a reversible flux between a left and right side.", - "type": "object", + "GroupedControlledDegradation": { + "description": "Specifies a process of degradation controlled by several controllers", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "ReversibleFlux", - "const": "ReversibleFlux", + "const": "GroupedControlledDegradation", + "default": "GroupedControlledDegradation", "enum": [ - "ReversibleFlux" + "GroupedControlledDegradation" ], + "title": "Type", "type": "string" }, - "left": { - "title": "Left", - "description": "The left hand side of the flux.", - "type": "array", + "controllers": { + "description": "The controllers of the degradation.", "items": { - "$ref": "#/definitions/Concept" - } + "$ref": "#/$defs/Concept" + }, + "title": "Controllers", + "type": "array" }, - "right": { - "title": "Right", - "description": "The right hand side of the flux.", - "type": "array", - "items": { - "$ref": "#/definitions/Concept" - } + "subject": { + "$ref": "#/$defs/Concept", + "description": "The subject of the degradation." }, "provenance": { - "title": "Provenance", - "description": "The provenance of the flux.", - "type": "array", + "description": "The provenance of the degradation.", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ - "left", - "right" - ] + "controllers", + "subject" + ], + "title": "GroupedControlledDegradation", + "type": "object" }, - "NaturalProduction": { - "title": "NaturalProduction", - "description": "A template for the production of a species at a constant rate.", - "type": "object", + "GroupedControlledProduction": { + "description": "Specifies a process of production controlled by several controllers", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "NaturalProduction", - "const": "NaturalProduction", + "const": "GroupedControlledProduction", + "default": "GroupedControlledProduction", "enum": [ - "NaturalProduction" + "GroupedControlledProduction" ], + "title": "Type", "type": "string" }, + "controllers": { + "description": "The controllers of the production.", + "items": { + "$ref": "#/$defs/Concept" + }, + "title": "Controllers", + "type": "array" + }, "outcome": { - "title": "Outcome", - "description": "The outcome of the production.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The outcome of the production." }, "provenance": { - "title": "Provenance", "description": "The provenance of the production.", - "type": "array", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ + "controllers", "outcome" - ] + ], + "title": "GroupedControlledProduction", + "type": "object" }, - "NaturalDegradation": { - "title": "NaturalDegradation", - "description": "A template for the degradataion of a species at a proportional rate to its amount.", - "type": "object", + "Initial": { + "description": "Represents the initial conditions for parameters present in the\nmodel.", "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" + "concept": { + "$ref": "#/$defs/Concept", + "description": "The concept associated with the initial." }, - "type": { - "title": "Type", - "default": "NaturalDegradation", - "const": "NaturalDegradation", - "enum": [ - "NaturalDegradation" - ], + "expression": { + "anyOf": [], + "description": "The expression for the initial.", + "format": "sympy-expr", + "title": "Expression", "type": "string" - }, - "subject": { - "title": "Subject", - "description": "The subject of the degradation.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] - }, - "provenance": { - "title": "Provenance", - "description": "The provenance of the degradation.", - "type": "array", - "items": { - "$ref": "#/definitions/Provenance" - } } }, "required": [ - "subject" - ] + "concept", + "expression" + ], + "title": "Initial", + "type": "object" }, - "ControlledDegradation": { - "title": "ControlledDegradation", - "description": "Specifies a process of degradation controlled by one controller", - "type": "object", + "MultiConversion": { + "description": "Specifies a conversion process of multiple subjects and outcomes.", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "ControlledDegradation", - "const": "ControlledDegradation", + "const": "MultiConversion", + "default": "MultiConversion", "enum": [ - "ControlledDegradation" + "MultiConversion" ], + "title": "Type", "type": "string" }, - "controller": { - "title": "Controller", - "description": "The controller of the degradation.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "subjects": { + "description": "The subjects of the conversion.", + "items": { + "$ref": "#/$defs/Concept" + }, + "title": "Subjects", + "type": "array" }, - "subject": { - "title": "Subject", - "description": "The subject of the degradation.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "outcomes": { + "description": "The outcomes of the conversion.", + "items": { + "$ref": "#/$defs/Concept" + }, + "title": "Outcomes", + "type": "array" }, "provenance": { - "title": "Provenance", - "description": "The provenance of the degradation.", - "type": "array", + "description": "The provenance of the conversion.", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ - "controller", - "subject" - ] + "subjects", + "outcomes" + ], + "title": "MultiConversion", + "type": "object" }, - "GroupedControlledDegradation": { - "title": "GroupedControlledDegradation", - "description": "Specifies a process of degradation controlled by several controllers", - "type": "object", + "NaturalConversion": { + "description": "Specifies a process of natural conversion from subject to outcome", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "GroupedControlledDegradation", - "const": "GroupedControlledDegradation", + "const": "NaturalConversion", + "default": "NaturalConversion", "enum": [ - "GroupedControlledDegradation" + "NaturalConversion" ], + "title": "Type", "type": "string" }, - "controllers": { - "title": "Controllers", - "description": "The controllers of the degradation.", - "type": "array", - "items": { - "$ref": "#/definitions/Concept" - } - }, "subject": { - "title": "Subject", - "description": "The subject of the degradation.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The subject of the conversion." + }, + "outcome": { + "$ref": "#/$defs/Concept", + "description": "The outcome of the conversion." }, "provenance": { - "title": "Provenance", - "description": "The provenance of the degradation.", - "type": "array", + "description": "The provenance of the conversion.", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ - "controllers", - "subject" - ] + "subject", + "outcome" + ], + "title": "NaturalConversion", + "type": "object" }, - "NaturalReplication": { - "title": "NaturalReplication", - "description": "Specifies a process of natural replication of a subject.", - "type": "object", + "NaturalDegradation": { + "description": "A template for the degradataion of a species at a proportional rate to its amount.", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "NaturalReplication", - "const": "NaturalReplication", + "const": "NaturalDegradation", + "default": "NaturalDegradation", "enum": [ - "NaturalReplication" + "NaturalDegradation" ], + "title": "Type", "type": "string" }, "subject": { - "title": "Subject", - "description": "The subject of the replication.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The subject of the degradation." }, "provenance": { - "title": "Provenance", - "description": "The provenance of the template.", - "type": "array", + "description": "The provenance of the degradation.", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ "subject" - ] + ], + "title": "NaturalDegradation", + "type": "object" }, - "ControlledReplication": { - "title": "ControlledReplication", - "description": "Specifies a process of replication controlled by one controller", - "type": "object", + "NaturalProduction": { + "description": "A template for the production of a species at a constant rate.", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "ControlledReplication", - "const": "ControlledReplication", + "const": "NaturalProduction", + "default": "NaturalProduction", "enum": [ - "ControlledReplication" + "NaturalProduction" ], + "title": "Type", "type": "string" }, - "controller": { - "title": "Controller", - "description": "The controller of the replication.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] - }, - "subject": { - "title": "Subject", - "description": "The subject of the replication.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "outcome": { + "$ref": "#/$defs/Concept", + "description": "The outcome of the production." }, "provenance": { - "title": "Provenance", - "description": "The provenance of the replication.", - "type": "array", + "description": "The provenance of the production.", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ - "controller", - "subject" - ] + "outcome" + ], + "title": "NaturalProduction", + "type": "object" }, - "StaticConcept": { - "title": "StaticConcept", - "description": "Specifies a standalone Concept that is not part of a process.", - "type": "object", + "NaturalReplication": { + "description": "Specifies a process of natural replication of a subject.", "properties": { "rate_law": { - "title": "Rate Law", + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The rate law for the template.", - "type": "string", - "example": "2*x" + "title": "Rate Law" }, "name": { - "title": "Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The name of the template.", - "type": "string" + "title": "Name" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "The display name of the template.", - "type": "string" + "title": "Display Name" }, "type": { - "title": "Type", - "default": "StaticConcept", - "const": "StaticConcept", + "const": "NaturalReplication", + "default": "NaturalReplication", "enum": [ - "StaticConcept" + "NaturalReplication" ], + "title": "Type", "type": "string" }, "subject": { - "title": "Subject", - "description": "The subject.", - "allOf": [ - { - "$ref": "#/definitions/Concept" - } - ] + "$ref": "#/$defs/Concept", + "description": "The subject of the replication." }, "provenance": { - "title": "Provenance", - "description": "The provenance.", - "type": "array", + "description": "The provenance of the template.", "items": { - "$ref": "#/definitions/Provenance" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } }, "required": [ "subject" - ] - }, - "Distribution": { - "title": "Distribution", - "description": "A distribution of values for a parameter.", - "type": "object", - "properties": { - "type": { - "title": "Type", - "description": "The type of distribution as provided by ProbOnto e.g. 'StandardUniform1', 'Beta1', etc.", - "type": "string" - }, - "parameters": { - "title": "Parameters", - "description": "The parameters of the distribution keyed by parameter names controlled by ProbOnto and values that are either floating point values or symbolic expressions over other parameters.", - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "example": "2*x" - } - ] - } - } - }, - "required": [ - "type", - "parameters" - ] + ], + "title": "NaturalReplication", + "type": "object" }, - "Parameter": { - "title": "Parameter", - "description": "A Parameter is a special type of Concept that carries a value.", - "type": "object", + "Observable": { + "description": "An observable is a special type of Concept that carries an expression.\n\nObservables are used to define the readouts of a model, useful when a\nreadout is not defined as a state variable but is rather a function of\nstate variables.", "properties": { "name": { - "title": "Name", "description": "The name of the concept.", + "title": "Name", "type": "string" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "An optional display name for the concept. If not provided, the name can be used for display purposes.", - "type": "string" + "title": "Display Name" }, "description": { - "title": "Description", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "An optional description of the concept.", - "type": "string" + "title": "Description" }, "identifiers": { - "title": "Identifiers", - "description": "A mapping of namespaces to identifiers.", - "type": "object", "additionalProperties": { "type": "string" - } + }, + "description": "A mapping of namespaces to identifiers.", + "title": "Identifiers", + "type": "object" }, "context": { - "title": "Context", - "description": "A mapping of context keys to values.", - "type": "object", "additionalProperties": { - "type": "string" - } + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "description": "A mapping of context keys to values.", + "title": "Context", + "type": "object" }, "units": { - "title": "Units", - "description": "The units of the concept.", - "allOf": [ - { - "$ref": "#/definitions/Unit" - } - ] - }, - "value": { - "title": "Value", - "description": "Value of the parameter.", - "type": "number" - }, - "distribution": { - "title": "Distribution", - "description": "A distribution of values for the parameter.", - "allOf": [ + "anyOf": [ { - "$ref": "#/definitions/Distribution" - } - ] - } - }, - "required": [ - "name" - ] - }, - "Initial": { - "title": "Initial", - "description": "Represents the initial conditions for parameters present in the\nmodel.", - "type": "object", - "properties": { - "concept": { - "title": "Concept", - "description": "The concept associated with the initial.", - "allOf": [ + "$ref": "#/$defs/Unit" + }, { - "$ref": "#/definitions/Concept" + "type": "null" } - ] + ], + "default": null, + "description": "The units of the concept." }, "expression": { + "anyOf": [], + "description": "The expression for the observable.", + "format": "sympy-expr", "title": "Expression", - "description": "The expression for the initial.", - "type": "string", - "example": "2*x" + "type": "string" } }, "required": [ - "concept", + "name", "expression" - ] - }, - "Observable": { + ], "title": "Observable", - "description": "An observable is a special type of Concept that carries an expression.\n\nObservables are used to define the readouts of a model, useful when a\nreadout is not defined as a state variable but is rather a function of\nstate variables.", - "type": "object", + "type": "object" + }, + "Parameter": { + "description": "A Parameter is a special type of Concept that carries a value.", "properties": { "name": { - "title": "Name", "description": "The name of the concept.", + "title": "Name", "type": "string" }, "display_name": { - "title": "Display Name", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "An optional display name for the concept. If not provided, the name can be used for display purposes.", - "type": "string" + "title": "Display Name" }, "description": { - "title": "Description", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, "description": "An optional description of the concept.", - "type": "string" + "title": "Description" }, "identifiers": { - "title": "Identifiers", - "description": "A mapping of namespaces to identifiers.", - "type": "object", "additionalProperties": { "type": "string" - } + }, + "description": "A mapping of namespaces to identifiers.", + "title": "Identifiers", + "type": "object" }, "context": { - "title": "Context", - "description": "A mapping of context keys to values.", - "type": "object", "additionalProperties": { - "type": "string" - } + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "description": "A mapping of context keys to values.", + "title": "Context", + "type": "object" }, "units": { - "title": "Units", - "description": "The units of the concept.", - "allOf": [ + "anyOf": [ + { + "$ref": "#/$defs/Unit" + }, { - "$ref": "#/definitions/Unit" + "type": "null" } - ] - }, - "expression": { - "title": "Expression", - "description": "The expression for the observable.", - "type": "string", - "example": "2*x" - } - }, - "required": [ - "name", - "expression" - ] - }, - "Author": { - "title": "Author", - "description": "A metadata model for an author.", - "type": "object", - "properties": { - "name": { - "title": "Name", - "description": "The name of the author", - "type": "string" - } - }, - "required": [ - "name" - ] - }, - "Annotations": { - "title": "Annotations", - "description": "A metadata model for model-level annotations.\n\nExamples in this metadata model are taken from\nhttps://www.ebi.ac.uk/biomodels/BIOMD0000000956,\na well-annotated SIR model in the BioModels database.", - "type": "object", - "properties": { - "name": { - "title": "Name", - "description": "A human-readable label for the model", - "example": "SIR model of scenarios of COVID-19 spread in CA and NY", - "type": "string" - }, - "description": { - "title": "Description", - "description": "A description of the model", - "example": "The coronavirus disease 2019 (COVID-19) pandemic has placed epidemic modeling at the forefront of worldwide public policy making. Nonetheless, modeling and forecasting the spread of COVID-19 remains a challenge. Here, we detail three regional scale models for forecasting and assessing the course of the pandemic. This work demonstrates the utility of parsimonious models for early-time data and provides an accessible framework for generating policy-relevant insights into its course. We show how these models can be connected to each other and to time series data for a particular region. Capable of measuring and forecasting the impacts of social distancing, these models highlight the dangers of relaxing nonpharmaceutical public health interventions in the absence of a vaccine or antiviral therapies.", - "type": "string" - }, - "license": { - "title": "License", - "description": "Information about the licensing of the model artifact. Ideally, given as an SPDX identifier like CC0 or CC-BY-4.0. For example, models from the BioModels databases are all licensed under the CC0 public attribution license.", - "example": "CC0", - "type": "string" + ], + "default": null, + "description": "The units of the concept." }, - "authors": { - "title": "Authors", - "description": "A list of authors/creators of the model. This is not the same as the people who e.g., submitted the model to BioModels", - "example": [ + "value": { + "anyOf": [ { - "name": "Andrea L Bertozzi" + "type": "number" }, { - "name": "Elisa Franco" + "type": "null" + } + ], + "default": null, + "description": "Value of the parameter.", + "title": "Value" + }, + "distribution": { + "anyOf": [ + { + "$ref": "#/$defs/Distribution" }, { - "name": "George Mohler" + "type": "null" + } + ], + "default": null, + "description": "A distribution of values for the parameter." + } + }, + "required": [ + "name" + ], + "title": "Parameter", + "type": "object" + }, + "Provenance": { + "properties": {}, + "title": "Provenance", + "type": "object" + }, + "ReversibleFlux": { + "description": "Specifies a reversible flux between a left and right side.", + "properties": { + "rate_law": { + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" }, { - "name": "Martin B Short" + "type": "null" + } + ], + "default": null, + "description": "The rate law for the template.", + "title": "Rate Law" + }, + "name": { + "anyOf": [ + { + "type": "string" }, { - "name": "Daniel Sledge" + "type": "null" } ], - "type": "array", - "items": { - "$ref": "#/definitions/Author" - } + "default": null, + "description": "The name of the template.", + "title": "Name" }, - "references": { - "title": "References", - "description": "A list of CURIEs (i.e., :) corresponding to literature references that describe the model. Do **not** duplicate the same publication with different CURIEs (e.g., using pubmed, pmc, and doi)", - "example": [ - "pubmed:32616574" + "display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } ], - "type": "array", - "items": { - "type": "string" - } + "default": null, + "description": "The display name of the template.", + "title": "Display Name" }, - "time_scale": { - "title": "Time Scale", - "description": "The granularity of the time element of the model, typically on the scale of days, weeks, or months for epidemiology models", - "example": "day", + "type": { + "const": "ReversibleFlux", + "default": "ReversibleFlux", + "enum": [ + "ReversibleFlux" + ], + "title": "Type", "type": "string" }, - "time_start": { - "title": "Time Start", - "description": "The start time of the applicability of a model, given as a datetime. When the time scale is not so granular, leave the less granular fields as default, i.e., if the time scale is on months, give dates like YYYY-MM-01 00:00", - "type": "string", - "format": "date-time" - }, - "time_end": { - "title": "Time End", - "description": "Similar to the start time of the applicability of a model, the end time is given as a datetime. For example, the Bertozzi 2020 model is applicable between March and August 2020, so this field is annotated with August 1st, 2020.", - "type": "string", - "format": "date-time" + "left": { + "description": "The left hand side of the flux.", + "items": { + "$ref": "#/$defs/Concept" + }, + "title": "Left", + "type": "array" }, - "locations": { - "title": "Locations", - "description": "A location or list of locations where this model is applicable, ideally annotated using a CURIEs referencing a controlled vocabulary such as GeoNames, which has multiple levels of granularity including city/state/country level terms. For example,the Bertozzi 2020 model was for New York City (geonames:5128581) and California (geonames:5332921)", - "example": [ - "geonames:5128581", - "geonames:5332921" - ], - "type": "array", + "right": { + "description": "The right hand side of the flux.", "items": { - "type": "string" - } + "$ref": "#/$defs/Concept" + }, + "title": "Right", + "type": "array" }, - "pathogens": { - "title": "Pathogens", - "description": "The pathogens present in the model, given with CURIEs referencing vocabulary for taxa, ideally, NCBI Taxonomy. For example, the Bertozzi 2020 model is about SARS-CoV-2, this is ncbitaxon:2697049. Do not confuse this field with terms for annotating the disease caused by the pathogen. Note that some models may have multiple pathogens, for simulating double pandemics such as the interaction with SARS-CoV-2 and the seasonal flu.", - "example": [ - "ncbitaxon:2697049" - ], - "type": "array", + "provenance": { + "description": "The provenance of the flux.", "items": { - "type": "string" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" + } + }, + "required": [ + "left", + "right" + ], + "title": "ReversibleFlux", + "type": "object" + }, + "StaticConcept": { + "description": "Specifies a standalone Concept that is not part of a process.", + "properties": { + "rate_law": { + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The rate law for the template.", + "title": "Rate Law" }, - "diseases": { - "title": "Diseases", - "description": "The diseases caused by pathogens in the model, given with CURIEs referencing vocabulary for dieases, such as DOID, EFO, or MONDO. For example, the Bertozzi 2020 model is about SARS-CoV-2, which causes COVID-19. In the Human Disease Ontology (DOID), this is referenced by doid:0080600.", - "example": [ - "doid:0080600" + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } ], - "type": "array", - "items": { - "type": "string" - } + "default": null, + "description": "The name of the template.", + "title": "Name" }, - "hosts": { - "title": "Hosts", - "description": "The hosts present in the model, given with CURIEs referencing vocabulary for taxa, ideally, NCBI Taxonomy. For example, the Bertozzi 2020 model is about human infection by SARS-CoV-2. Therefore, the appropriate annotation for this field would be ncbitaxon:9606. Note that some models have multiple hosts, such as Malaria models that consider humans and mosquitos.", - "example": [ - "ncbitaxon:9606" + "display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } ], - "type": "array", - "items": { - "type": "string" - } + "default": null, + "description": "The display name of the template.", + "title": "Display Name" }, - "model_types": { - "title": "Model Types", - "description": "This field describes the type(s) of the model using the Mathematical Modeling Ontology (MAMO), which has terms like 'ordinary differential equation model', 'population model', etc. These should be annotated as CURIEs in the form of mamo:. For example, the Bertozzi 2020 model is a population model (mamo:0000028) and ordinary differential equation model (mamo:0000046)", - "example": [ - "mamo:0000028", - "mamo:0000046" + "type": { + "const": "StaticConcept", + "default": "StaticConcept", + "enum": [ + "StaticConcept" ], - "type": "array", + "title": "Type", + "type": "string" + }, + "subject": { + "$ref": "#/$defs/Concept", + "description": "The subject." + }, + "provenance": { + "description": "The provenance.", "items": { - "type": "string" - } + "$ref": "#/$defs/Provenance" + }, + "title": "Provenance", + "type": "array" } - } + }, + "required": [ + "subject" + ], + "title": "StaticConcept", + "type": "object" }, - "Time": { - "title": "Time", - "description": "A special type of Concept that represents time.", - "type": "object", + "Template": { + "description": "The Template is a parent class for model processes", "properties": { + "rate_law": { + "anyOf": [ + { + "anyOf": [], + "format": "sympy-expr", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The rate law for the template.", + "title": "Rate Law" + }, "name": { - "title": "Name", - "description": "The symbol of the time variable in the model.", - "default": "t", - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The name of the template.", + "title": "Name" }, - "units": { - "title": "Units", - "description": "The units of the time variable.", - "allOf": [ + "display_name": { + "anyOf": [ { - "$ref": "#/definitions/Unit" + "type": "string" + }, + { + "type": "null" } - ] + ], + "default": null, + "description": "The display name of the template.", + "title": "Display Name" } - } + }, + "title": "Template", + "type": "object" }, "TemplateModel": { - "title": "TemplateModel", "description": "A template model.", - "type": "object", "properties": { "templates": { - "title": "Templates", "description": "A list of any child class of Templates", - "type": "array", "items": { + "description": "Any child class of a Template", "discriminator": { - "propertyName": "type", "mapping": { - "NaturalConversion": "#/definitions/NaturalConversion", - "MultiConversion": "#/definitions/MultiConversion", - "ControlledConversion": "#/definitions/ControlledConversion", - "NaturalDegradation": "#/definitions/NaturalDegradation", - "ControlledDegradation": "#/definitions/ControlledDegradation", - "GroupedControlledDegradation": "#/definitions/GroupedControlledDegradation", - "NaturalProduction": "#/definitions/NaturalProduction", - "ControlledProduction": "#/definitions/ControlledProduction", - "GroupedControlledConversion": "#/definitions/GroupedControlledConversion", - "GroupedControlledProduction": "#/definitions/GroupedControlledProduction", - "NaturalReplication": "#/definitions/NaturalReplication", - "ControlledReplication": "#/definitions/ControlledReplication", - "StaticConcept": "#/definitions/StaticConcept", - "ReversibleFlux": "#/definitions/ReversibleFlux" - } + "ControlledConversion": "#/$defs/ControlledConversion", + "ControlledDegradation": "#/$defs/ControlledDegradation", + "ControlledProduction": "#/$defs/ControlledProduction", + "ControlledReplication": "#/$defs/ControlledReplication", + "GroupedControlledConversion": "#/$defs/GroupedControlledConversion", + "GroupedControlledDegradation": "#/$defs/GroupedControlledDegradation", + "GroupedControlledProduction": "#/$defs/GroupedControlledProduction", + "MultiConversion": "#/$defs/MultiConversion", + "NaturalConversion": "#/$defs/NaturalConversion", + "NaturalDegradation": "#/$defs/NaturalDegradation", + "NaturalProduction": "#/$defs/NaturalProduction", + "NaturalReplication": "#/$defs/NaturalReplication", + "ReversibleFlux": "#/$defs/ReversibleFlux", + "StaticConcept": "#/$defs/StaticConcept" + }, + "propertyName": "type" }, "oneOf": [ { - "$ref": "#/definitions/NaturalConversion" + "$ref": "#/$defs/NaturalConversion" }, { - "$ref": "#/definitions/MultiConversion" + "$ref": "#/$defs/MultiConversion" }, { - "$ref": "#/definitions/ControlledConversion" + "$ref": "#/$defs/ControlledConversion" }, { - "$ref": "#/definitions/NaturalDegradation" + "$ref": "#/$defs/NaturalDegradation" }, { - "$ref": "#/definitions/ControlledDegradation" + "$ref": "#/$defs/ControlledDegradation" }, { - "$ref": "#/definitions/GroupedControlledDegradation" + "$ref": "#/$defs/GroupedControlledDegradation" }, { - "$ref": "#/definitions/NaturalProduction" + "$ref": "#/$defs/NaturalProduction" }, { - "$ref": "#/definitions/ControlledProduction" + "$ref": "#/$defs/ControlledProduction" }, { - "$ref": "#/definitions/GroupedControlledConversion" + "$ref": "#/$defs/GroupedControlledConversion" }, { - "$ref": "#/definitions/GroupedControlledProduction" + "$ref": "#/$defs/GroupedControlledProduction" }, { - "$ref": "#/definitions/NaturalReplication" + "$ref": "#/$defs/NaturalReplication" }, { - "$ref": "#/definitions/ControlledReplication" + "$ref": "#/$defs/ControlledReplication" }, { - "$ref": "#/definitions/StaticConcept" + "$ref": "#/$defs/StaticConcept" }, { - "$ref": "#/definitions/ReversibleFlux" + "$ref": "#/$defs/ReversibleFlux" } ] - } + }, + "title": "Templates", + "type": "array" }, "parameters": { - "title": "Parameters", - "description": "A dict of parameter values where keys correspond to how the parameter appears in rate laws.", - "type": "object", "additionalProperties": { - "$ref": "#/definitions/Parameter" - } + "$ref": "#/$defs/Parameter" + }, + "description": "A dict of parameter values where keys correspond to how the parameter appears in rate laws.", + "title": "Parameters", + "type": "object" }, "initials": { - "title": "Initials", - "description": "A dict of initial condition values where keyscorrespond to concept names they apply to.", - "type": "object", "additionalProperties": { - "$ref": "#/definitions/Initial" - } + "$ref": "#/$defs/Initial" + }, + "description": "A dict of initial condition values where keyscorrespond to concept names they apply to.", + "title": "Initials", + "type": "object" }, "observables": { - "title": "Observables", - "description": "A list of observables that are readouts from the model.", - "type": "object", "additionalProperties": { - "$ref": "#/definitions/Observable" - } + "$ref": "#/$defs/Observable" + }, + "description": "A list of observables that are readouts from the model.", + "title": "Observables", + "type": "object" }, "annotations": { - "title": "Annotations", - "description": "A structure containing model-level annotations. Note that all annotations are optional.", - "allOf": [ + "anyOf": [ + { + "$ref": "#/$defs/Annotations" + }, { - "$ref": "#/definitions/Annotations" + "type": "null" } - ] + ], + "default": null, + "description": "A structure containing model-level annotations. Note that all annotations are optional." }, "time": { - "title": "Time", - "description": "A structure containing time-related annotations. Note that all annotations are optional.", - "allOf": [ + "anyOf": [ + { + "$ref": "#/$defs/Time" + }, { - "$ref": "#/definitions/Time" + "type": "null" } - ] + ], + "default": null, + "description": "A structure containing time-related annotations. Note that all annotations are optional." } }, "required": [ "templates" - ] + ], + "title": "TemplateModel", + "type": "object" + }, + "Time": { + "description": "A special type of Concept that represents time.", + "properties": { + "name": { + "default": "t", + "description": "The symbol of the time variable in the model.", + "title": "Name", + "type": "string" + }, + "units": { + "anyOf": [ + { + "$ref": "#/$defs/Unit" + }, + { + "type": "null" + } + ], + "default": null, + "description": "The units of the time variable." + } + }, + "title": "Time", + "type": "object" + }, + "Unit": { + "description": "A unit of measurement.", + "properties": { + "expression": { + "anyOf": [], + "description": "The expression for the unit.", + "format": "sympy-expr", + "title": "Expression", + "type": "string" + } + }, + "required": [ + "expression" + ], + "title": "Unit", + "type": "object" + } + }, + "properties": { + "Concept": { + "$ref": "#/$defs/Concept" + }, + "Template": { + "$ref": "#/$defs/Template" + }, + "ControlledConversion": { + "$ref": "#/$defs/ControlledConversion" + }, + "GroupedControlledConversion": { + "$ref": "#/$defs/GroupedControlledConversion" + }, + "GroupedControlledProduction": { + "$ref": "#/$defs/GroupedControlledProduction" + }, + "ControlledProduction": { + "$ref": "#/$defs/ControlledProduction" + }, + "NaturalConversion": { + "$ref": "#/$defs/NaturalConversion" + }, + "MultiConversion": { + "$ref": "#/$defs/MultiConversion" + }, + "ReversibleFlux": { + "$ref": "#/$defs/ReversibleFlux" + }, + "NaturalProduction": { + "$ref": "#/$defs/NaturalProduction" + }, + "NaturalDegradation": { + "$ref": "#/$defs/NaturalDegradation" + }, + "ControlledDegradation": { + "$ref": "#/$defs/ControlledDegradation" + }, + "GroupedControlledDegradation": { + "$ref": "#/$defs/GroupedControlledDegradation" + }, + "NaturalReplication": { + "$ref": "#/$defs/NaturalReplication" + }, + "ControlledReplication": { + "$ref": "#/$defs/ControlledReplication" + }, + "StaticConcept": { + "$ref": "#/$defs/StaticConcept" + }, + "TemplateModel": { + "$ref": "#/$defs/TemplateModel" } - } + }, + "required": [ + "Concept", + "Template", + "ControlledConversion", + "GroupedControlledConversion", + "GroupedControlledProduction", + "ControlledProduction", + "NaturalConversion", + "MultiConversion", + "ReversibleFlux", + "NaturalProduction", + "NaturalDegradation", + "ControlledDegradation", + "GroupedControlledDegradation", + "NaturalReplication", + "ControlledReplication", + "StaticConcept", + "TemplateModel" + ], + "type": "object" } \ No newline at end of file diff --git a/mira/metamodel/schema.py b/mira/metamodel/schema.py index 1a3dea2d1..c2a994630 100644 --- a/mira/metamodel/schema.py +++ b/mira/metamodel/schema.py @@ -6,7 +6,8 @@ from pathlib import Path import pydantic -from pydantic import BaseModel +from pydantic import BaseModel, create_model +from pydantic.json_schema import model_json_schema from . import Concept, Template, TemplateModel @@ -15,35 +16,37 @@ def get_json_schema(): - """Get the JSON schema for MIRA. - - Returns - ------- - : JSON - The JSON schema for MIRA. - """ + """Get the JSON schema for MIRA.""" rv = { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://raw.githubusercontent.com/indralab/mira/main/mira/metamodel/schema.json", + "title": "MIRA Metamodel Template Schema", + "description": "MIRA metamodel templates give a high-level abstraction of modeling appropriate for many domains.", } - rv.update( - pydantic.schema.schema( - [ - Concept, - Template, - *Template.__subclasses__(), - TemplateModel, - ], - title="MIRA Metamodel Template Schema", - description="MIRA metamodel templates give a high-level abstraction of modeling appropriate for many domains.", - ) + + models = [Concept, Template, *Template.__subclasses__(), TemplateModel] + + CombinedModel = create_model( + "CombinedModel", + **{model.__name__: (model, ...) for model in models} ) + + schema = model_json_schema( + CombinedModel, + mode='validation', + ) + + # Remove the top-level 'title' and 'description' from the generated schema + schema.pop('title', None) + schema.pop('description', None) + + rv.update(schema) return rv def _encoder(x): if isinstance(x, BaseModel): - return x.dict() + return x.model_dump() return x diff --git a/mira/metamodel/template_model.py b/mira/metamodel/template_model.py index 3ceb852b8..8570ced48 100644 --- a/mira/metamodel/template_model.py +++ b/mira/metamodel/template_model.py @@ -1,3 +1,5 @@ +from pydantic import ConfigDict + __all__ = [ "Annotations", "TemplateModel", @@ -18,7 +20,7 @@ import networkx as nx import sympy import mira.metamodel.io -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_serializer from .templates import * from .units import Unit from .utils import safe_parse_expr, SympyExprStr @@ -35,12 +37,7 @@ class Initial(BaseModel): description="The expression for the initial." ) - class Config: - arbitrary_types_allowed = True - json_encoders = { - SympyExprStr: lambda e: str(e), - } - json_decoders = {SympyExprStr: lambda e: sympy.parse_expr(e)} + model_config = ConfigDict(arbitrary_types_allowed=True) @classmethod def from_json(cls, data: Dict[str, Any], locals_dict=None) -> "Initial": @@ -68,6 +65,10 @@ def from_json(cls, data: Dict[str, Any], locals_dict=None) -> "Initial": expression = safe_parse_expr(expression_str, local_dict=locals_dict) return cls(concept=concept, expression=SympyExprStr(expression)) + @field_serializer('expression') + def serialize_expression(self, expression): + return str(expression) + def substitute_parameter(self, name, value): """ Substitute a parameter value into the initial expression. @@ -119,11 +120,11 @@ class Parameter(Concept): """A Parameter is a special type of Concept that carries a value.""" value: Optional[float] = Field( - default_factory=None, description="Value of the parameter." + default=None, description="Value of the parameter." ) distribution: Optional[Distribution] = Field( - default_factory=None, + default=None, description="A distribution of values for the parameter.", ) @@ -136,17 +137,16 @@ class Observable(Concept): state variables. """ - class Config: - arbitrary_types_allowed = True - json_encoders = { - SympyExprStr: lambda e: str(e), - } - json_decoders = {SympyExprStr: lambda e: safe_parse_expr(e)} + model_config = ConfigDict(arbitrary_types_allowed=True) expression: SympyExprStr = Field( description="The expression for the observable." ) + @field_serializer('expression') + def serialize_expression(self, expression): + return str(expression) + def substitute_parameter(self, name, value): """ Substitute a parameter value into the observable expression. @@ -185,7 +185,7 @@ class Time(BaseModel): name: str = Field( default="t", description="The symbol of the time variable in the model." ) - units: Optional[Unit] = Field(description="The units of the time variable.") + units: Optional[Unit] = Field(None, description="The units of the time variable.") class Author(BaseModel): @@ -202,9 +202,11 @@ class Annotations(BaseModel): a well-annotated SIR model in the BioModels database. """ + model_config = ConfigDict(protected_namespaces=()) + name: Optional[str] = Field( - description="A human-readable label for the model", - example="SIR model of scenarios of COVID-19 spread in CA and NY", + None, description="A human-readable label for the model", + examples=["SIR model of scenarios of COVID-19 spread in CA and NY"], ) # identifiers: Dict[str, str] = Field( # description="Structured identifiers corresponding to the model artifact " @@ -217,8 +219,8 @@ class Annotations(BaseModel): # }, # ) description: Optional[str] = Field( - description="A description of the model", - example="The coronavirus disease 2019 (COVID-19) pandemic has placed " + None, description="A description of the model", + examples=["The coronavirus disease 2019 (COVID-19) pandemic has placed " "epidemic modeling at the forefront of worldwide public policy making. " "Nonetheless, modeling and forecasting the spread of COVID-19 remains a " "challenge. Here, we detail three regional scale models for forecasting " @@ -229,48 +231,48 @@ class Annotations(BaseModel): "time series data for a particular region. Capable of measuring and " "forecasting the impacts of social distancing, these models highlight the " "dangers of relaxing nonpharmaceutical public health interventions in the " - "absence of a vaccine or antiviral therapies.", + "absence of a vaccine or antiviral therapies."], ) license: Optional[str] = Field( - description="Information about the licensing of the model artifact. " + None, description="Information about the licensing of the model artifact. " "Ideally, given as an SPDX identifier like CC0 or CC-BY-4.0. For example, " "models from the BioModels databases are all licensed under the CC0 " "public attribution license.", - example="CC0", + examples=["CC0"], ) authors: List[Author] = Field( default_factory=list, description="A list of authors/creators of the model. This is not the same " "as the people who e.g., submitted the model to BioModels", - example=[ + examples=[[ Author(name="Andrea L Bertozzi"), Author(name="Elisa Franco"), Author(name="George Mohler"), Author(name="Martin B Short"), Author(name="Daniel Sledge"), - ], + ]], ) references: List[str] = Field( default_factory=list, description="A list of CURIEs (i.e., :) corresponding " "to literature references that describe the model. Do **not** duplicate the " "same publication with different CURIEs (e.g., using pubmed, pmc, and doi)", - example=["pubmed:32616574"], + examples=[["pubmed:32616574"]], ) # TODO agree on how we annotate this one, e.g. with a timedelta time_scale: Optional[str] = Field( - description="The granularity of the time element of the model, typically on " + None, description="The granularity of the time element of the model, typically on " "the scale of days, weeks, or months for epidemiology models", - example="day", + examples=["day"], ) time_start: Optional[datetime.datetime] = Field( - description="The start time of the applicability of a model, given as a datetime. " + None, description="The start time of the applicability of a model, given as a datetime. " "When the time scale is not so granular, leave the less granular fields as default, " "i.e., if the time scale is on months, give dates like YYYY-MM-01 00:00", # example=datetime.datetime(year=2020, month=3, day=1), ) time_end: Optional[datetime.datetime] = Field( - description="Similar to the start time of the applicability of a model, the end time " + None, description="Similar to the start time of the applicability of a model, the end time " "is given as a datetime. For example, the Bertozzi 2020 model is applicable between " "March and August 2020, so this field is annotated with August 1st, 2020.", # example=datetime.datetime(year=2020, month=8, day=1), @@ -282,10 +284,10 @@ class Annotations(BaseModel): "has multiple levels of granularity including city/state/country level terms. For example," "the Bertozzi 2020 model was for New York City (geonames:5128581) and California " "(geonames:5332921)", - example=[ + examples=[[ "geonames:5128581", "geonames:5332921", - ], + ]], ) pathogens: List[str] = Field( default_factory=list, @@ -294,9 +296,9 @@ class Annotations(BaseModel): "SARS-CoV-2, this is ncbitaxon:2697049. Do not confuse this field with terms for annotating " "the disease caused by the pathogen. Note that some models may have multiple pathogens, for " "simulating double pandemics such as the interaction with SARS-CoV-2 and the seasonal flu.", - example=[ + examples=[[ "ncbitaxon:2697049", - ], + ]], ) diseases: List[str] = Field( default_factory=list, @@ -304,9 +306,9 @@ class Annotations(BaseModel): "vocabulary for dieases, such as DOID, EFO, or MONDO. For example, the Bertozzi 2020 model " "is about SARS-CoV-2, which causes COVID-19. In the Human Disease Ontology (DOID), this " "is referenced by doid:0080600.", - example=[ + examples=[[ "doid:0080600", - ], + ]], ) hosts: List[str] = Field( default_factory=list, @@ -315,9 +317,9 @@ class Annotations(BaseModel): "human infection by SARS-CoV-2. Therefore, the appropriate annotation for this field " "would be ncbitaxon:9606. Note that some models have multiple hosts, such as Malaria " "models that consider humans and mosquitos.", - example=[ + examples=[[ "ncbitaxon:9606", - ], + ]], ) model_types: List[str] = Field( default_factory=list, @@ -326,10 +328,10 @@ class Annotations(BaseModel): " model', 'population model', etc. These should be annotated as CURIEs in the form " "of mamo:. For example, the Bertozzi 2020 model is a population " "model (mamo:0000028) and ordinary differential equation model (mamo:0000046)", - example=[ + examples=[[ "mamo:0000028", "mamo:0000046", - ], + ]], ) @@ -357,23 +359,17 @@ class TemplateModel(BaseModel): ) annotations: Optional[Annotations] = Field( - default_factory=None, + default=None, description="A structure containing model-level annotations. " "Note that all annotations are optional.", ) time: Optional[Time] = Field( - default_factory=None, + default=None, description="A structure containing time-related annotations. " "Note that all annotations are optional.", ) - class Config: - json_encoders = { - SympyExprStr: lambda e: str(e), - } - json_decoders = {SympyExprStr: lambda e: safe_parse_expr(e)} - def get_parameters_from_rate_law(self, rate_law) -> Set[str]: """Given a rate law, find its elements that are model parameters. diff --git a/mira/metamodel/templates.py b/mira/metamodel/templates.py index 84f4580d9..df4778223 100644 --- a/mira/metamodel/templates.py +++ b/mira/metamodel/templates.py @@ -3,6 +3,9 @@ Regenerate the JSON schema by running ``python -m mira.metamodel.schema``. """ +from pydantic import ConfigDict +from typing import Literal + __all__ = [ "Concept", "Template", @@ -59,7 +62,7 @@ import networkx as nx import pydantic import sympy -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_serializer try: from typing import Annotated # py39+ @@ -109,7 +112,7 @@ class Concept(BaseModel): """ name: str = Field(..., description="The name of the concept.") - display_name: str = \ + display_name: Optional[str]= \ Field(None, description="An optional display name for the concept. " "If not provided, the name can be used for " "display purposes.") @@ -118,7 +121,7 @@ class Concept(BaseModel): identifiers: Mapping[str, str] = Field( default_factory=dict, description="A mapping of namespaces to identifiers." ) - context: Mapping[str, str] = Field( + context: Mapping[str, Union[str,int]] = Field( default_factory=dict, description="A mapping of context keys to values." ) units: Optional[Unit] = Field( @@ -126,14 +129,7 @@ class Concept(BaseModel): ) _base_name: str = pydantic.PrivateAttr(None) - class Config: - arbitrary_types_allowed = True - json_encoders = { - SympyExprStr: lambda e: str(e), - } - json_decoders = { - SympyExprStr: lambda e: sympy.parse_expr(e) - } + model_config = ConfigDict(arbitrary_types_allowed=True) def with_context(self, do_rename=False, curie_to_name_map=None, inplace=False, **context) -> "Concept": @@ -388,14 +384,7 @@ def from_json(cls, data) -> "Concept": class Template(BaseModel): """The Template is a parent class for model processes""" - class Config: - arbitrary_types_allowed = True - json_encoders = { - SympyExprStr: lambda e: str(e), - } - json_decoders = { - SympyExprStr: lambda e: safe_parse_expr(e) - } + model_config = ConfigDict(arbitrary_types_allowed=True) rate_law: Optional[SympyExprStr] = Field( default=None, description="The rate law for the template." @@ -456,6 +445,10 @@ def from_json(cls, data, rate_symbols=None) -> "Template": if k not in {'rate_law', 'type'}}, rate_law=rate) + @field_serializer('rate_law', when_used="unless-none") + def serialize_expression(self, rate_law): + return str(rate_law) + def is_equal_to(self, other: "Template", with_context: bool = False, config: Config = None) -> bool: """Check if this template is equal to another template @@ -769,7 +762,7 @@ def with_mass_action_rate_law(self, parameter, independent=False) -> "Template": : A copy of this template with the mass action rate law. """ - template = self.copy(deep=True) + template = self.model_copy(deep=True) template.set_mass_action_rate_law(parameter, independent=independent) return template @@ -793,7 +786,7 @@ def set_rate_law(self, rate_law: Union[str, sympy.Expr, SympyExprStr], def with_rate_law(self, rate_law: Union[str, sympy.Expr, SympyExprStr], local_dict=None) -> "Template": - template = self.copy(deep=True) + template = self.model_copy(deep=True) template.set_rate_law(rate_law, local_dict=local_dict) return template @@ -890,7 +883,7 @@ class ControlledConversion(Template): """Specifies a process of controlled conversion from subject to outcome, controlled by the controller.""" - type: Literal["ControlledConversion"] = Field("ControlledConversion", const=True) + type: Literal["ControlledConversion"] = "ControlledConversion" controller: Concept = Field(..., description="The controller of the conversion.") subject: Concept = Field(..., description="The subject of the conversion.") outcome: Concept = Field(..., description="The outcome of the conversion.") @@ -976,7 +969,7 @@ def get_key(self, config: Optional[Config] = None): class GroupedControlledConversion(Template): - type: Literal["GroupedControlledConversion"] = Field("GroupedControlledConversion", const=True) + type: Literal["GroupedControlledConversion"] = "GroupedControlledConversion" controllers: List[Concept] = Field(..., description="The controllers of the conversion.") subject: Concept = Field(..., description="The subject of the conversion.") outcome: Concept = Field(..., description="The outcome of the conversion.") @@ -1066,7 +1059,7 @@ def add_controller(self, controller: Concept) -> "GroupedControlledConversion": class GroupedControlledProduction(Template): """Specifies a process of production controlled by several controllers""" - type: Literal["GroupedControlledProduction"] = Field("GroupedControlledProduction", const=True) + type: Literal["GroupedControlledProduction"] = "GroupedControlledProduction" controllers: List[Concept] = Field(..., description="The controllers of the production.") outcome: Concept = Field(..., description="The outcome of the production.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the production.") @@ -1152,9 +1145,7 @@ def with_context( class ControlledProduction(Template): """Specifies a process of production controlled by one controller""" - type: Literal["ControlledProduction"] = Field( - "ControlledProduction", const=True - ) + type: Literal["ControlledProduction"] = "ControlledProduction" controller: Concept = Field( ..., description="The controller of the production." ) @@ -1240,7 +1231,7 @@ def with_context( class NaturalConversion(Template): """Specifies a process of natural conversion from subject to outcome""" - type: Literal["NaturalConversion"] = Field("NaturalConversion", const=True) + type: Literal["NaturalConversion"] = "NaturalConversion" subject: Concept = Field(..., description="The subject of the conversion.") outcome: Concept = Field(..., description="The outcome of the conversion.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the conversion.") @@ -1278,7 +1269,7 @@ def get_key(self, config: Optional[Config] = None): class MultiConversion(Template): """Specifies a conversion process of multiple subjects and outcomes.""" - type: Literal["MultiConversion"] = Field("MultiConversion", const=True) + type: Literal["MultiConversion"] = "MultiConversion" subjects: List[Concept] = Field(..., description="The subjects of the conversion.") outcomes: List[Concept] = Field(..., description="The outcomes of the conversion.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the conversion.") @@ -1325,7 +1316,7 @@ def with_context( class ReversibleFlux(Template): """Specifies a reversible flux between a left and right side.""" - type: Literal["ReversibleFlux"] = Field("ReversibleFlux", const=True) + type: Literal["ReversibleFlux"] = "ReversibleFlux" left: List[Concept] = Field(..., description="The left hand side of the flux.") right: List[Concept] = Field(..., description="The right hand side of the flux.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the flux.") @@ -1372,7 +1363,7 @@ def with_context( class NaturalProduction(Template): """A template for the production of a species at a constant rate.""" - type: Literal["NaturalProduction"] = Field("NaturalProduction", const=True) + type: Literal["NaturalProduction"] = "NaturalProduction" outcome: Concept = Field(..., description="The outcome of the production.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the production.") @@ -1406,7 +1397,7 @@ def with_context( class NaturalDegradation(Template): """A template for the degradataion of a species at a proportional rate to its amount.""" - type: Literal["NaturalDegradation"] = Field("NaturalDegradation", const=True) + type: Literal["NaturalDegradation"] = "NaturalDegradation" subject: Concept = Field(..., description="The subject of the degradation.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the degradation.") @@ -1439,7 +1430,7 @@ def with_context( class ControlledDegradation(Template): """Specifies a process of degradation controlled by one controller""" - type: Literal["ControlledDegradation"] = Field("ControlledDegradation", const=True) + type: Literal["ControlledDegradation"] = "ControlledDegradation" controller: Concept = Field(..., description="The controller of the degradation.") subject: Concept = Field(..., description="The subject of the degradation.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the degradation.") @@ -1518,7 +1509,7 @@ def with_context( class GroupedControlledDegradation(Template): """Specifies a process of degradation controlled by several controllers""" - type: Literal["GroupedControlledDegradation"] = Field("GroupedControlledDegradation", const=True) + type: Literal["GroupedControlledDegradation"] = "GroupedControlledDegradation" controllers: List[Concept] = Field(..., description="The controllers of the degradation.") subject: Concept = Field(..., description="The subject of the degradation.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the degradation.") @@ -1602,7 +1593,7 @@ def with_context( class NaturalReplication(Template): """Specifies a process of natural replication of a subject.""" - type: Literal["NaturalReplication"] = Field("NaturalReplication", const=True) + type: Literal["NaturalReplication"] = "NaturalReplication" subject: Concept = Field(..., description="The subject of the replication.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the template.") @@ -1635,7 +1626,7 @@ def get_key(self, config: Optional[Config] = None): class ControlledReplication(Template): """Specifies a process of replication controlled by one controller""" - type: Literal["ControlledReplication"] = Field("ControlledReplication", const=True) + type: Literal["ControlledReplication"] = "ControlledReplication" controller: Concept = Field(..., description="The controller of the replication.") subject: Concept = Field(..., description="The subject of the replication.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance of the replication.") @@ -1695,7 +1686,7 @@ def with_context( class StaticConcept(Template): """Specifies a standalone Concept that is not part of a process.""" - type: Literal["StaticConcept"] = Field("StaticConcept", const=True) + type: Literal["StaticConcept"] = "StaticConcept" subject: Concept = Field(..., description="The subject.") provenance: List[Provenance] = Field(default_factory=list, description="The provenance.") concept_keys: ClassVar[List[str]] = ["subject"] diff --git a/mira/metamodel/units.py b/mira/metamodel/units.py index 3de22139d..31c2af9fa 100644 --- a/mira/metamodel/units.py +++ b/mira/metamodel/units.py @@ -1,3 +1,5 @@ +from pydantic import ConfigDict + __all__ = [ 'Unit', 'person_units', @@ -12,7 +14,7 @@ from typing import Dict, Any import sympy -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_serializer from .utils import SympyExprStr @@ -32,14 +34,7 @@ def load_units(): class Unit(BaseModel): """A unit of measurement.""" - class Config: - arbitrary_types_allowed = True - json_encoders = { - SympyExprStr: lambda e: str(e), - } - json_decoders = { - SympyExprStr: lambda e: sympy.parse_expr(e) - } + model_config = ConfigDict(arbitrary_types_allowed=True) expression: SympyExprStr = Field( description="The expression for the unit." @@ -55,6 +50,16 @@ def from_json(cls, data: Dict[str, Any]) -> "Unit": ) return cls(**data) + @classmethod + def model_validate(cls, obj): + if isinstance(obj, dict) and 'expression' in obj: + obj['expression'] = SympyExprStr(obj['expression']) + return super().model_validate(obj) + + @field_serializer('expression') + def serialize_expression(self, expression): + return str(expression) + person_units = Unit(expression=sympy.Symbol('person')) day_units = Unit(expression=sympy.Symbol('day')) diff --git a/mira/metamodel/utils.py b/mira/metamodel/utils.py index 8d98f1e75..e8306a07c 100644 --- a/mira/metamodel/utils.py +++ b/mira/metamodel/utils.py @@ -5,6 +5,10 @@ import re import unicodedata +from typing import Any +from pydantic import GetCoreSchemaHandler +from pydantic_core import core_schema + def get_parseable_expression(s: str) -> str: """Return an expression that can be parsed using sympy.""" @@ -42,6 +46,16 @@ class SympyExprStr(sympy.Expr): def __get_validators__(cls): yield cls.validate + @classmethod + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + return handler.resolve_ref_schema(core_schema.union_schema([ + core_schema.is_instance_schema(cls), + core_schema.no_info_plain_validator_function(cls.validate) + ])) + + @classmethod def validate(cls, v): if isinstance(v, cls): @@ -53,8 +67,11 @@ def validate(cls, v): return cls(v) @classmethod - def __modify_schema__(cls, field_schema): - field_schema.update(type="string", example="2*x") + def __get_pydantic_json_schema__(cls, core_schema, handler): + json_schema = handler(core_schema) + json_schema.update(type="string", format="sympy-expr") + return json_schema + #field_schema.update(type="string", example="2*x") def __str__(self): return super().__str__()[len(self.__class__.__name__)+1:-1] diff --git a/mira/modeling/acsets/petri.py b/mira/modeling/acsets/petri.py index 07743c634..3baa9cddc 100644 --- a/mira/modeling/acsets/petri.py +++ b/mira/modeling/acsets/petri.py @@ -21,7 +21,7 @@ class State(BaseModel): sname: str - sprop: Optional[Dict] + sprop: Optional[Dict] = None #mira_ids: str #mira_context: str #mira_initial_value: Optional[float] @@ -29,8 +29,8 @@ class State(BaseModel): class Transition(BaseModel): tname: str - rate: Optional[float] - tprop: Optional[Dict] + rate: Optional[float] = None + tprop: Optional[Dict] = None #template_type: str #parameter_name: Optional[str] #parameter_value: Optional[str] @@ -94,7 +94,7 @@ def __init__(self, model: Model): 'is_observable': False, 'mira_ids': ids, 'mira_context': context, - 'mira_concept': var.concept.json(), + 'mira_concept': var.concept.model_dump_json(), } } initial_expr = var.data.get('expression') @@ -114,8 +114,7 @@ def __init__(self, model: Model): pname = f"p_petri_{idx + 1}" else: pname = transition.rate.key - - distr = transition.rate.distribution.json() \ + distr = transition.rate.distribution.model_dump_json() \ if transition.rate.distribution else None pvalue = transition.rate.value transition_dict = { @@ -125,7 +124,7 @@ def __init__(self, model: Model): 'parameter_name': pname, 'parameter_value': pvalue, 'parameter_distribution': distr, - 'mira_template': transition.template.json(), + 'mira_template': transition.template.model_dump_json(), } } transition_dict["rate"] = pvalue @@ -149,7 +148,7 @@ def __init__(self, model: Model): continue key = p.key if p.key else f"p_petri_{idx + 1}" _parameters[key] = p.value - _distributions[key] = p.distribution.dict() \ + _distributions[key] = p.distribution.model_dump() \ if p.distribution else None transition_dict["tprop"]["mira_parameters"] = \ json.dumps(_parameters, sort_keys=True) @@ -187,7 +186,7 @@ def __init__(self, model: Model): key = sanitize_parameter_name( p.key) if p.key else f"p_petri_{idx + 1}" _parameters[key] = p.value - _distributions[key] = p.distribution.dict() \ + _distributions[key] = p.distribution.model_dump() \ if p.distribution else None obs_dict = { 'concept': json.dumps(concept_data), diff --git a/mira/modeling/amr/petrinet.py b/mira/modeling/amr/petrinet.py index 55244e84e..7dc93e7f9 100644 --- a/mira/modeling/amr/petrinet.py +++ b/mira/modeling/amr/petrinet.py @@ -10,7 +10,7 @@ from copy import deepcopy from typing import Dict, List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict from mira.metamodel import expression_to_mathml, TemplateModel, SympyExprStr from mira.sources.amr import sanity_check_amr @@ -294,15 +294,15 @@ def to_pydantic(self, name=None, description=None, model_version=None) -> "Model ), properties=self.properties, model=PetriModel( - states=[State.parse_obj(s) for s in self.states], - transitions=[Transition.parse_obj(t) for t in self.transitions], + states=[State.model_validate(s) for s in self.states], + transitions=[Transition.model_validate(t) for t in self.transitions], ), semantics=Ode(ode=OdeSemantics( - rates=[Rate.parse_obj(r) for r in self.rates], - initials=[Initial.parse_obj(i) for i in self.initials], - parameters=[Parameter.parse_obj(p) for p in self.parameters], - observables=[Observable.parse_obj(o) for o in self.observables], - time=Time.parse_obj(self.time) if self.time else Time(id='t') + rates=[Rate.model_validate(r) for r in self.rates], + initials=[Initial.model_validate(i) for i in self.initials], + parameters=[Parameter.model_validate(p) for p in self.parameters], + observables=[Observable.model_validate(o) for o in self.observables], + time=Time.model_validate(self.time) if self.time else Time(id='t') )), metadata=self.metadata, ) @@ -381,8 +381,8 @@ class Initial(BaseModel): class TransitionProperties(BaseModel): - name: Optional[str] - grounding: Optional[Dict] + name: Optional[str] = None + grounding: Optional[Dict] = None class Rate(BaseModel): @@ -404,7 +404,7 @@ class Units(BaseModel): class State(BaseModel): id: str name: Optional[str] = None - grounding: Optional[Dict] + grounding: Optional[Dict] = None units: Optional[Units] = None @@ -412,15 +412,15 @@ class Transition(BaseModel): id: str input: List[str] output: List[str] - grounding: Optional[Dict] - properties: Optional[TransitionProperties] + grounding: Optional[Dict] = None + properties: Optional[TransitionProperties] = None class Parameter(BaseModel): id: str description: Optional[str] = None value: Optional[float] = None - grounding: Optional[Dict] + grounding: Optional[Dict] = None distribution: Optional[Distribution] = None units: Optional[Units] = None @@ -428,7 +428,7 @@ class Parameter(BaseModel): def from_dict(cls, d): d = deepcopy(d) d['id'] = str(d['id']) - return cls.parse_obj(d) + return cls.model_validate(d) class Time(BaseModel): @@ -438,8 +438,8 @@ class Time(BaseModel): class Observable(BaseModel): id: str - name: Optional[str] - grounding: Optional[Dict] + name: Optional[str] = None + grounding: Optional[Dict] = None expression: str expression_mathml: str @@ -453,15 +453,16 @@ class OdeSemantics(BaseModel): rates: List[Rate] initials: List[Initial] parameters: List[Parameter] - time: Optional[Time] + time: Optional[Time] = None observables: List[Observable] class Ode(BaseModel): - ode: Optional[OdeSemantics] + ode: Optional[OdeSemantics] = None class Header(BaseModel): + model_config = ConfigDict(protected_namespaces=()) name: str schema_url: str = Field(..., alias='schema') schema_name: str @@ -472,7 +473,7 @@ class Header(BaseModel): class ModelSpecification(BaseModel): """A Pydantic model corresponding to the PetriNet JSON schema.""" header: Header - properties: Optional[Dict] + properties: Optional[Dict] = None model: PetriModel - semantics: Optional[Ode] - metadata: Optional[Dict] + semantics: Optional[Ode] = None + metadata: Optional[Dict] = None diff --git a/mira/modeling/amr/regnet.py b/mira/modeling/amr/regnet.py index bf128d0eb..e35a322a1 100644 --- a/mira/modeling/amr/regnet.py +++ b/mira/modeling/amr/regnet.py @@ -11,8 +11,7 @@ from collections import defaultdict from typing import Dict, List, Optional, Union -import sympy -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict from mira.metamodel import * @@ -377,15 +376,15 @@ def to_pydantic( model_version=model_version or '0.1', ), model=RegNetModel( - vertices=[State.parse_obj(s) for s in self.states], - edges=[Transition.parse_obj(t) for t in self.transitions], + vertices=[State.model_validate(s) for s in self.states], + edges=[Transition.model_validate(t) for t in self.transitions], parameters=[Parameter.from_dict(p) for p in self.parameters], ), semantics=Ode( ode=OdeSemantics( - rates=[Rate.parse_obj(r) for r in self.rates], - observables=[Observable.parse_obj(o) for o in self.observables], - time=Time.parse_obj(self.time) if self.time else Time(id='t') + rates=[Rate.model_validate(r) for r in self.rates], + observables=[Observable.model_validate(o) for o in self.observables], + time=Time.model_validate(self.time) if self.time else Time(id='t') ) ), metadata=self.metadata, @@ -460,9 +459,9 @@ class Initial(BaseModel): class TransitionProperties(BaseModel): - name: Optional[str] - grounding: Optional[Dict] - rate: Optional[Dict] + name: Optional[str] = None + grounding: Optional[Dict] = None + rate: Optional[Dict] = None class Rate(BaseModel): @@ -485,7 +484,7 @@ class Transition(BaseModel): id: str input: List[str] output: List[str] - properties: Optional[TransitionProperties] + properties: Optional[TransitionProperties] = None class Parameter(BaseModel): @@ -498,7 +497,7 @@ class Parameter(BaseModel): def from_dict(cls, d): d = deepcopy(d) d['id'] = str(d['id']) - return cls.parse_obj(d) + return cls.model_validate(d) class RegNetModel(BaseModel): @@ -508,6 +507,7 @@ class RegNetModel(BaseModel): class Header(BaseModel): + model_config = ConfigDict(protected_namespaces=()) name: str schema_name: str schema_url: str = Field(..., alias='schema') @@ -517,18 +517,18 @@ class Header(BaseModel): class OdeSemantics(BaseModel): rates: List[Rate] - time: Optional[Time] + time: Optional[Time] = None observables: List[Observable] class Ode(BaseModel): - ode: Optional[OdeSemantics] + ode: Optional[OdeSemantics] = None class ModelSpecification(BaseModel): """A Pydantic model specification of the model.""" header: Header - properties: Optional[Dict] + properties: Optional[Dict] = None model: RegNetModel - semantics: Optional[Ode] - metadata: Optional[Dict] + semantics: Optional[Ode] = None + metadata: Optional[Dict] = None diff --git a/mira/modeling/amr/utils.py b/mira/modeling/amr/utils.py index ef975b713..162c0797c 100644 --- a/mira/modeling/amr/utils.py +++ b/mira/modeling/amr/utils.py @@ -4,7 +4,7 @@ def add_metadata_annotations(metadata, model): return annotations_subset = { k: (str(v) if k in ["time_start", "time_end"] and v is not None else v) - for k, v in model.template_model.annotations.dict().items() + for k, v in model.template_model.annotations.model_dump().items() if k not in ["name", "description"] # name and description already have a privileged place # in the petrinet schema so don't get added again diff --git a/mira/sources/acsets/stockflow.py b/mira/sources/acsets/stockflow.py index 15b10602e..8d2eefad1 100644 --- a/mira/sources/acsets/stockflow.py +++ b/mira/sources/acsets/stockflow.py @@ -107,8 +107,9 @@ def template_model_from_stockflow_ascet_json(model_json) -> TemplateModel: outputs = [] # flow_id or flow_name for template name? - flow_id = flow["_id"] # required - flow_name = flow.get("fname") + flow_id = flow["_id"] # required + flow_name = str(flow_id) + flow_display_name = flow.get("fname") inputs.append(input) outputs.append(output) @@ -123,9 +124,9 @@ def template_model_from_stockflow_ascet_json(model_json) -> TemplateModel: if (link["t"] == flow_id and link["s"] != input) ] - input_concepts = [concepts[i].copy(deep=True) for i in inputs] - output_concepts = [concepts[i].copy(deep=True) for i in outputs] - controller_concepts = [concepts[i].copy(deep=True) for i in controllers] + input_concepts = [concepts[i].model_copy(deep=True) for i in inputs] + output_concepts = [concepts[i].model_copy(deep=True) for i in outputs] + controller_concepts = [concepts[i].model_copy(deep=True) for i in controllers] expression_sympy = safe_parse_expr(expression_str, symbols) @@ -135,15 +136,15 @@ def template_model_from_stockflow_ascet_json(model_json) -> TemplateModel: output_concepts, controller_concepts, expression_sympy, - flow_id, flow_name, + flow_display_name, ) ) static_stocks = all_stocks - used_stocks for state in static_stocks: - concept = concepts[state].copy(deep=True) + concept = concepts[state].model_copy(deep=True) templates.append(StaticConcept(subject=concept)) return TemplateModel(templates=templates, parameters=mira_parameters) @@ -162,7 +163,7 @@ def stock_to_concept(stock) -> Concept: : The concept created from the stock """ - name = stock["_id"] + name = str(stock["_id"]) display_name = stock.get("sname") grounding = stock.get("grounding", {}) identifiers = grounding.get("identifiers", {}) diff --git a/mira/sources/amr/petrinet.py b/mira/sources/amr/petrinet.py index 2c4c3d244..8e9c670f2 100644 --- a/mira/sources/amr/petrinet.py +++ b/mira/sources/amr/petrinet.py @@ -128,7 +128,7 @@ def template_model_from_amr_json(model_json) -> TemplateModel: continue try: initial = Initial( - concept=concepts[initial_state['target']].copy(deep=True), + concept=concepts[initial_state['target']].model_copy(deep=True), expression=initial_expr ) initials[initial.concept.name] = initial @@ -197,9 +197,10 @@ def template_model_from_amr_json(model_json) -> TemplateModel: both = set(inputs) & set(outputs) # We can now get the appropriate concepts for each group - input_concepts = [concepts[i].copy(deep=True) for i in inputs] - output_concepts = [concepts[i].copy(deep=True) for i in outputs] - controller_concepts = [concepts[i].copy(deep=True) for i in controllers] + input_concepts = [concepts[i].model_copy(deep=True) for i in inputs] + output_concepts = [concepts[i].model_copy(deep=True) for i in outputs] + controller_concepts = [concepts[i].model_copy(deep=True) for i in + controllers] transition_id = transition['id'] rate_law = get_sympy(rate_obj, local_dict=symbols) @@ -211,7 +212,7 @@ def template_model_from_amr_json(model_json) -> TemplateModel: # Handle static states static_states = all_states - used_states for state in static_states: - concept = concepts[state].copy(deep=True) + concept = concepts[state].model_copy(deep=True) templates.append(StaticConcept(subject=concept)) # Finally, we gather some model-level annotations diff --git a/mira/sources/amr/stockflow.py b/mira/sources/amr/stockflow.py index c3ca7cb4b..fecac706f 100644 --- a/mira/sources/amr/stockflow.py +++ b/mira/sources/amr/stockflow.py @@ -67,7 +67,7 @@ def template_model_from_amr_json(model_json) -> TemplateModel: continue try: initial = Initial( - concept=concepts[initial_state['target']].copy(deep=True), + concept=concepts[initial_state['target']].model_copy(deep=True), expression=initial_expr ) initials[initial.concept.name] = initial @@ -114,9 +114,10 @@ def template_model_from_amr_json(model_json) -> TemplateModel: and link['source'] in concepts and link['source'] not in aux_expressions)] - input_concepts = [concepts[input].copy(deep=True)] if input else [] - output_concepts = [concepts[output].copy(deep=True)] if output else [] - controller_concepts = [concepts[i].copy(deep=True) for i in controllers] + input_concepts = [concepts[input].model_copy(deep=True)] if input \ + else [] + output_concepts = [concepts[output].model_copy(deep=True)] if output else [] + controller_concepts = [concepts[i].model_copy(deep=True) for i in controllers] if 'rate_expression' in flow: rate_expr = safe_parse_expr(flow['rate_expression'], @@ -133,7 +134,7 @@ def template_model_from_amr_json(model_json) -> TemplateModel: static_stocks = all_stocks - used_stocks for state in static_stocks: - concept = concepts[state].copy(deep=True) + concept = concepts[state].model_copy(deep=True) templates.append(StaticConcept(subject=concept)) # Finally, we gather some model-level annotations diff --git a/mira/sources/biomodels.py b/mira/sources/biomodels.py index 19f7cea81..67a9bd148 100644 --- a/mira/sources/biomodels.py +++ b/mira/sources/biomodels.py @@ -211,7 +211,7 @@ def main(): tqdm.write(f"[{model_id}] failed to parse: {e}") continue model_module.join(name=f"{model_id}.json").write_text( - template_model.json(indent=2) + template_model.model_dump_json(indent=2) ) # Write a petri-net type graphical representation of the model diff --git a/mira/sources/sbml/qual_processor.py b/mira/sources/sbml/qual_processor.py index ed2124bf9..fb77545b2 100644 --- a/mira/sources/sbml/qual_processor.py +++ b/mira/sources/sbml/qual_processor.py @@ -66,7 +66,8 @@ def _lookup_concepts_filtered(species_ids) -> List[Concept]: for transition_id, transition in enumerate( self.qual_model_plugin.transitions ): - transition_name = transition.id + transition_name = str(transition.id) + transition_display_name = transition.id input_names = [ qual_species.qualitative_species @@ -108,16 +109,16 @@ def _lookup_concepts_filtered(species_ids) -> List[Concept]: templates.append( NaturalDegradation( subject=input_concepts[0], - name=transition_id, - display_name=transition_name, + name=transition_name, + display_name=transition_display_name, ) ) elif len(input_concepts) == 0 and len(output_concepts) == 1: templates.append( NaturalProduction( outcome=output_concepts[0], - name=transition_id, - display_name=transition_name, + name=transition_name, + display_name=transition_display_name, ) ) elif len(input_concepts) == 1 and len(output_concepts) == 1: @@ -125,8 +126,8 @@ def _lookup_concepts_filtered(species_ids) -> List[Concept]: NaturalConversion( subject=input_concepts[0], outcome=output_concepts[0], - name=transition_id, - display_name=transition_name, + name=transition_name, + display_name=transition_display_name, ) ) else: @@ -139,8 +140,8 @@ def _lookup_concepts_filtered(species_ids) -> List[Concept]: ControlledProduction( controller=positive_controller_concepts[0], outcome=output_concepts[0], - name=transition_id, - display_name=transition_name, + name=transition_name, + display_name=transition_display_name, ) ) else: @@ -148,8 +149,8 @@ def _lookup_concepts_filtered(species_ids) -> List[Concept]: GroupedControlledProduction( controllers=positive_controller_concepts, outcome=output_concepts[0], - name=transition_id, - display_name=transition_name, + name=transition_name, + display_name=transition_display_name, ) ) elif ( @@ -161,8 +162,8 @@ def _lookup_concepts_filtered(species_ids) -> List[Concept]: ControlledDegradation( controller=negative_controller_concepts[0], subject=input_concepts[0], - name=transition_id, - display_name=transition_name, + name=transition_name, + display_name=transition_display_name, ) ) else: @@ -170,8 +171,8 @@ def _lookup_concepts_filtered(species_ids) -> List[Concept]: GroupedControlledDegradation( controllers=negative_controller_concepts, subject=input_concepts[0], - name=transition_id, - display_name=transition_name, + name=transition_name, + display_name=transition_display_name, ) ) elif ( @@ -183,8 +184,8 @@ def _lookup_concepts_filtered(species_ids) -> List[Concept]: controllers=positive_controller_concepts + negative_controller_concepts, outcome=output_concepts[0], - name=transition_id, - display_name=transition_name, + name=transition_name, + display_name=transition_display_name, ) ) diff --git a/mira/sources/system_dynamics/pysd.py b/mira/sources/system_dynamics/pysd.py index ed9281fa0..9e79f1e27 100644 --- a/mira/sources/system_dynamics/pysd.py +++ b/mira/sources/system_dynamics/pysd.py @@ -164,7 +164,7 @@ def template_model_from_pysd_model( ): if initials_map and (mapped_value := initials_map.get(state_id)): initial = Initial( - concept=concepts[state_id].copy(deep=True), + concept=concepts[state_id].model_copy(deep=True), expression=SympyExprStr(sympy.Float(mapped_value)), ) # if the state value is not a number @@ -175,12 +175,12 @@ def template_model_from_pysd_model( f"got non-numeric state value for {state_id}: {state_initial_value}" ) initial = Initial( - concept=concepts[state_id].copy(deep=True), + concept=concepts[state_id].model_copy(deep=True), expression=SympyExprStr(sympy.Float("0")), ) else: initial = Initial( - concept=concepts[state_id].copy(deep=True), + concept=concepts[state_id].model_copy(deep=True), expression=SympyExprStr(sympy.Float(state_initial_value)), ) mira_initials[initial.concept.name] = initial @@ -322,15 +322,15 @@ def template_model_from_pysd_model( templates_.extend( transition_to_templates( input_concepts=[ - concepts[input_name].copy(deep=True) + concepts[input_name].model_copy(deep=True) for input_name in transition.get("inputs") ], output_concepts=[ - concepts[output_name].copy(deep=True) + concepts[output_name].model_copy(deep=True) for output_name in transition.get("outputs") ], controller_concepts=[ - concepts[controller_name].copy(deep=True) + concepts[controller_name].model_copy(deep=True) for controller_name in transition.get("controllers") ], transition_rate=( diff --git a/notebooks/Hackathon Scenario 1.ipynb b/notebooks/Hackathon Scenario 1.ipynb index 7239d2275..3b6a2dce2 100644 --- a/notebooks/Hackathon Scenario 1.ipynb +++ b/notebooks/Hackathon Scenario 1.ipynb @@ -2628,7 +2628,7 @@ } ], "source": [ - "print(tc.model_comparison.json(indent=1))" + "print(tc.model_comparison.model_dump_json(indent=1))" ] }, { @@ -4710,7 +4710,7 @@ } ], "source": [ - "print(tc.model_comparison.json(indent=1))" + "print(tc.model_comparison.model_dump_json(indent=1))" ] }, { @@ -4721,7 +4721,7 @@ "outputs": [], "source": [ "with open('mira_comparison_threeway.json', 'w') as fh:\n", - " fh.write(tc.model_comparison.json(indent=1))" + " fh.write(tc.model_comparison.model_dump_json(indent=1))" ] }, { @@ -4779,7 +4779,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/notebooks/Hackathon Scenario 4.ipynb b/notebooks/Hackathon Scenario 4.ipynb index 3a3b9e0a0..92001f0a8 100644 --- a/notebooks/Hackathon Scenario 4.ipynb +++ b/notebooks/Hackathon Scenario 4.ipynb @@ -74,7 +74,7 @@ "outputs": [], "source": [ "with open('mira_comparison_scenario4.json', 'w') as fh:\n", - " fh.write(tc.model_comparison.json(indent=1))" + " fh.write(tc.model_comparison.model_dump_json(indent=1))" ] }, { diff --git a/notebooks/ensemble/ensemble.ipynb b/notebooks/ensemble/ensemble.ipynb index 3792b607d..70091428b 100644 --- a/notebooks/ensemble/ensemble.ipynb +++ b/notebooks/ensemble/ensemble.ipynb @@ -76,9 +76,7 @@ "cell_type": "code", "execution_count": 2, "id": "3c0e6c34", - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", @@ -786,9 +784,9 @@ "outputs": [], "source": [ "model.observables = {\n", - " 'Cases': Observable(**cases.dict(), expression=Symbol('Infected_reported')),\n", - " 'Hospitalizations': Observable(**hospitalizations.dict(), expression=Symbol('h')*Symbol('Infected_reported')),\n", - " 'Deaths': Observable(**deaths.dict(), expression=Symbol('Deceased'))\n", + " 'Cases': Observable(**cases.model_dump(), expression=Symbol('Infected_reported')),\n", + " 'Hospitalizations': Observable(**hospitalizations.model_dump(), expression=Symbol('h')*Symbol('Infected_reported')),\n", + " 'Deaths': Observable(**deaths.model_dump(), expression=Symbol('Deceased'))\n", "}" ] }, @@ -972,9 +970,9 @@ "outputs": [], "source": [ "model.observables = {\n", - " 'Cases': Observable(**cases.dict(), expression=Symbol('Diagnosed')+Symbol('Recognized')+Symbol('Threatened')),\n", - " 'Hospitalizations': Observable(**hospitalizations.dict(), expression=Symbol('Recognized')+Symbol('Threatened')),\n", - " 'Deaths': Observable(**deaths.dict(), expression=Symbol('Extinct'))\n", + " 'Cases': Observable(**cases.model_dump(), expression=Symbol('Diagnosed')+Symbol('Recognized')+Symbol('Threatened')),\n", + " 'Hospitalizations': Observable(**hospitalizations.model_dump(), expression=Symbol('Recognized')+Symbol('Threatened')),\n", + " 'Deaths': Observable(**deaths.model_dump(), expression=Symbol('Extinct'))\n", "}" ] }, @@ -1015,9 +1013,7 @@ "cell_type": "code", "execution_count": 31, "id": "677b8074", - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -1140,9 +1136,9 @@ "outputs": [], "source": [ "model.observables = {\n", - " 'Cases': Observable(**cases.dict(), expression=Symbol('Infectious')),\n", - " 'Hospitalizations': Observable(**hospitalizations.dict(), expression=Symbol('Hospitalized')),\n", - " 'Deaths': Observable(**deaths.dict(), expression=Symbol('Deceased'))\n", + " 'Cases': Observable(**cases.model_dump(), expression=Symbol('Infectious')),\n", + " 'Hospitalizations': Observable(**hospitalizations.model_dump(), expression=Symbol('Hospitalized')),\n", + " 'Deaths': Observable(**deaths.model_dump(), expression=Symbol('Deceased'))\n", "}" ] }, diff --git a/notebooks/evaluation_2023.01/Scenario2.ipynb b/notebooks/evaluation_2023.01/Scenario2.ipynb index 55aa154fb..35e781f4c 100644 --- a/notebooks/evaluation_2023.01/Scenario2.ipynb +++ b/notebooks/evaluation_2023.01/Scenario2.ipynb @@ -456,7 +456,7 @@ "outputs": [], "source": [ "with open('scenario2_model_comparison.json', 'w') as fh:\n", - " fh.write(tc.model_comparison.json(indent=1))" + " fh.write(tc.model_comparison.model_dump_json(indent=1))" ] } ], diff --git a/notebooks/evaluation_2023.01/Scenario3.ipynb b/notebooks/evaluation_2023.01/Scenario3.ipynb index e58863ad3..d0c70b703 100644 --- a/notebooks/evaluation_2023.01/Scenario3.ipynb +++ b/notebooks/evaluation_2023.01/Scenario3.ipynb @@ -285,9 +285,9 @@ " model.to_json_file(mname, indent=1)\n", "# Also dump the mira model jsons\n", "with open(\"scenario3_biomd958_mira.json\", \"w\") as f:\n", - " f.write(model_958.json(indent=1))\n", + " f.write(model_958.model_dump_json(indent=1))\n", "with open(\"scenario3_biomd960_mira.json\", \"w\") as f:\n", - " f.write(model_960.json(indent=1))" + " f.write(model_960.model_dump_json(indent=1))" ] }, { @@ -534,12 +534,12 @@ "source": [ "import json\n", "res = {\n", - " 'graph_comparison_data': json.loads(mc.model_comparison.json()),\n", + " 'graph_comparison_data': json.loads(mc.model_comparison.model_dump_json()),\n", " 'similarity_scores': mc.model_comparison.get_similarity_scores(),\n", " 'model_names': model_names\n", "}\n", "res_filtered = {\n", - " 'graph_comparison_data': json.loads(mc_filtered.model_comparison.json()),\n", + " 'graph_comparison_data': json.loads(mc_filtered.model_comparison.model_dump_json()),\n", " 'similarity_scores': mc_filtered.model_comparison.get_similarity_scores(),\n", " 'model_names': model_names_filtered\n", "}" diff --git a/notebooks/hackathon_2024.02/scenario1/epi_scenario1.ipynb b/notebooks/hackathon_2024.02/scenario1/epi_scenario1.ipynb index 9e2513426..7dcd30bd7 100644 --- a/notebooks/hackathon_2024.02/scenario1/epi_scenario1.ipynb +++ b/notebooks/hackathon_2024.02/scenario1/epi_scenario1.ipynb @@ -799,7 +799,7 @@ "# Apply country-specific data to model\n", "for country, country_data in data.items():\n", " # Create a copy of the model for this country\n", - " seird_age_strat_country = seird_base_age_strat.copy(deep=True)\n", + " seird_age_strat_country = seird_base_age_strat.model_copy(deep=True)\n", " \n", " pop_df = country_data['population']\n", " contact_matrix_df = country_data['contact_matrix']\n", @@ -1025,7 +1025,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/notebooks/metamodel_intro.ipynb b/notebooks/metamodel_intro.ipynb index 31de33d9a..6cd1546c6 100644 --- a/notebooks/metamodel_intro.ipynb +++ b/notebooks/metamodel_intro.ipynb @@ -41,7 +41,20 @@ "outputs": [ { "data": { - "text/plain": "{'rate_law': None,\n 'type': 'ControlledConversion',\n 'controller': {'name': 'infected population',\n 'identifiers': {'ido': '0000511'},\n 'context': {}},\n 'subject': {'name': 'susceptible population',\n 'identifiers': {'ido': '0000514'},\n 'context': {}},\n 'outcome': {'name': 'infected population',\n 'identifiers': {'ido': '0000511'},\n 'context': {}},\n 'provenance': []}" + "text/plain": [ + "{'rate_law': None,\n", + " 'type': 'ControlledConversion',\n", + " 'controller': {'name': 'infected population',\n", + " 'identifiers': {'ido': '0000511'},\n", + " 'context': {}},\n", + " 'subject': {'name': 'susceptible population',\n", + " 'identifiers': {'ido': '0000514'},\n", + " 'context': {}},\n", + " 'outcome': {'name': 'infected population',\n", + " 'identifiers': {'ido': '0000511'},\n", + " 'context': {}},\n", + " 'provenance': []}" + ] }, "execution_count": 16, "metadata": {}, @@ -49,7 +62,7 @@ } ], "source": [ - "t1.dict()" + "t1.model_dump()" ] }, { @@ -61,7 +74,9 @@ "outputs": [ { "data": { - "text/plain": "ControlledConversion(rate_law=None, type='ControlledConversion', controller=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), subject=Concept(name='susceptible population', identifiers={'ido': '0000514'}, context={}), outcome=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), provenance=[])" + "text/plain": [ + "ControlledConversion(rate_law=None, type='ControlledConversion', controller=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), subject=Concept(name='susceptible population', identifiers={'ido': '0000514'}, context={}), outcome=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), provenance=[])" + ] }, "execution_count": 28, "metadata": {}, @@ -69,7 +84,7 @@ } ], "source": [ - "Template.from_json(t1.dict())" + "Template.from_json(t1.model_dump())" ] }, { @@ -217,7 +232,70 @@ "outputs": [ { "data": { - "text/plain": "{'templates': [{'rate_law': None,\n 'type': 'ControlledConversion',\n 'controller': {'name': 'infected population',\n 'identifiers': {'ido': '0000511'},\n 'context': {}},\n 'subject': {'name': 'susceptible population',\n 'identifiers': {'ido': '0000514'},\n 'context': {}},\n 'outcome': {'name': 'exposed population',\n 'identifiers': {'genepio': '0001538'},\n 'context': {}},\n 'provenance': []},\n {'rate_law': None,\n 'type': 'NaturalConversion',\n 'subject': {'name': 'exposed population',\n 'identifiers': {'genepio': '0001538'},\n 'context': {}},\n 'outcome': {'name': 'infected population',\n 'identifiers': {'ido': '0000511'},\n 'context': {}},\n 'provenance': []},\n {'rate_law': None,\n 'type': 'NaturalConversion',\n 'subject': {'name': 'infected population',\n 'identifiers': {'ido': '0000511'},\n 'context': {}},\n 'outcome': {'name': 'deceased population',\n 'identifiers': {'ncit': 'C28554'},\n 'context': {}},\n 'provenance': []},\n {'rate_law': None,\n 'type': 'NaturalConversion',\n 'subject': {'name': 'infected population',\n 'identifiers': {'ido': '0000511'},\n 'context': {}},\n 'outcome': {'name': 'immune population',\n 'identifiers': {'ido': '0000592'},\n 'context': {}},\n 'provenance': []},\n {'rate_law': None,\n 'type': 'ControlledConversion',\n 'controller': {'name': 'exposed population',\n 'identifiers': {'genepio': '0001538'},\n 'context': {}},\n 'subject': {'name': 'susceptible population',\n 'identifiers': {'ido': '0000514'},\n 'context': {}},\n 'outcome': {'name': 'exposed population',\n 'identifiers': {'genepio': '0001538'},\n 'context': {}},\n 'provenance': []},\n {'rate_law': None,\n 'type': 'NaturalConversion',\n 'subject': {'name': 'immune population',\n 'identifiers': {'ido': '0000592'},\n 'context': {}},\n 'outcome': {'name': 'susceptible population',\n 'identifiers': {'ido': '0000514'},\n 'context': {}},\n 'provenance': []}],\n 'parameters': {},\n 'initials': {}}" + "text/plain": [ + "{'templates': [{'rate_law': None,\n", + " 'type': 'ControlledConversion',\n", + " 'controller': {'name': 'infected population',\n", + " 'identifiers': {'ido': '0000511'},\n", + " 'context': {}},\n", + " 'subject': {'name': 'susceptible population',\n", + " 'identifiers': {'ido': '0000514'},\n", + " 'context': {}},\n", + " 'outcome': {'name': 'exposed population',\n", + " 'identifiers': {'genepio': '0001538'},\n", + " 'context': {}},\n", + " 'provenance': []},\n", + " {'rate_law': None,\n", + " 'type': 'NaturalConversion',\n", + " 'subject': {'name': 'exposed population',\n", + " 'identifiers': {'genepio': '0001538'},\n", + " 'context': {}},\n", + " 'outcome': {'name': 'infected population',\n", + " 'identifiers': {'ido': '0000511'},\n", + " 'context': {}},\n", + " 'provenance': []},\n", + " {'rate_law': None,\n", + " 'type': 'NaturalConversion',\n", + " 'subject': {'name': 'infected population',\n", + " 'identifiers': {'ido': '0000511'},\n", + " 'context': {}},\n", + " 'outcome': {'name': 'deceased population',\n", + " 'identifiers': {'ncit': 'C28554'},\n", + " 'context': {}},\n", + " 'provenance': []},\n", + " {'rate_law': None,\n", + " 'type': 'NaturalConversion',\n", + " 'subject': {'name': 'infected population',\n", + " 'identifiers': {'ido': '0000511'},\n", + " 'context': {}},\n", + " 'outcome': {'name': 'immune population',\n", + " 'identifiers': {'ido': '0000592'},\n", + " 'context': {}},\n", + " 'provenance': []},\n", + " {'rate_law': None,\n", + " 'type': 'ControlledConversion',\n", + " 'controller': {'name': 'exposed population',\n", + " 'identifiers': {'genepio': '0001538'},\n", + " 'context': {}},\n", + " 'subject': {'name': 'susceptible population',\n", + " 'identifiers': {'ido': '0000514'},\n", + " 'context': {}},\n", + " 'outcome': {'name': 'exposed population',\n", + " 'identifiers': {'genepio': '0001538'},\n", + " 'context': {}},\n", + " 'provenance': []},\n", + " {'rate_law': None,\n", + " 'type': 'NaturalConversion',\n", + " 'subject': {'name': 'immune population',\n", + " 'identifiers': {'ido': '0000592'},\n", + " 'context': {}},\n", + " 'outcome': {'name': 'susceptible population',\n", + " 'identifiers': {'ido': '0000514'},\n", + " 'context': {}},\n", + " 'provenance': []}],\n", + " 'parameters': {},\n", + " 'initials': {}}" + ] }, "execution_count": 27, "metadata": {}, @@ -225,7 +303,7 @@ } ], "source": [ - "M4.dict()" + "M4.model_dump()" ] }, { @@ -237,7 +315,9 @@ "outputs": [ { "data": { - "text/plain": "TemplateModel(templates=[ControlledConversion(rate_law=None, type='ControlledConversion', controller=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), subject=Concept(name='susceptible population', identifiers={'ido': '0000514'}, context={}), outcome=Concept(name='exposed population', identifiers={'genepio': '0001538'}, context={}), provenance=[]), NaturalConversion(rate_law=None, type='NaturalConversion', subject=Concept(name='exposed population', identifiers={'genepio': '0001538'}, context={}), outcome=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), provenance=[]), NaturalConversion(rate_law=None, type='NaturalConversion', subject=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), outcome=Concept(name='deceased population', identifiers={'ncit': 'C28554'}, context={}), provenance=[]), NaturalConversion(rate_law=None, type='NaturalConversion', subject=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), outcome=Concept(name='immune population', identifiers={'ido': '0000592'}, context={}), provenance=[]), ControlledConversion(rate_law=None, type='ControlledConversion', controller=Concept(name='exposed population', identifiers={'genepio': '0001538'}, context={}), subject=Concept(name='susceptible population', identifiers={'ido': '0000514'}, context={}), outcome=Concept(name='exposed population', identifiers={'genepio': '0001538'}, context={}), provenance=[]), NaturalConversion(rate_law=None, type='NaturalConversion', subject=Concept(name='immune population', identifiers={'ido': '0000592'}, context={}), outcome=Concept(name='susceptible population', identifiers={'ido': '0000514'}, context={}), provenance=[])], parameters={}, initials={})" + "text/plain": [ + "TemplateModel(templates=[ControlledConversion(rate_law=None, type='ControlledConversion', controller=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), subject=Concept(name='susceptible population', identifiers={'ido': '0000514'}, context={}), outcome=Concept(name='exposed population', identifiers={'genepio': '0001538'}, context={}), provenance=[]), NaturalConversion(rate_law=None, type='NaturalConversion', subject=Concept(name='exposed population', identifiers={'genepio': '0001538'}, context={}), outcome=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), provenance=[]), NaturalConversion(rate_law=None, type='NaturalConversion', subject=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), outcome=Concept(name='deceased population', identifiers={'ncit': 'C28554'}, context={}), provenance=[]), NaturalConversion(rate_law=None, type='NaturalConversion', subject=Concept(name='infected population', identifiers={'ido': '0000511'}, context={}), outcome=Concept(name='immune population', identifiers={'ido': '0000592'}, context={}), provenance=[]), ControlledConversion(rate_law=None, type='ControlledConversion', controller=Concept(name='exposed population', identifiers={'genepio': '0001538'}, context={}), subject=Concept(name='susceptible population', identifiers={'ido': '0000514'}, context={}), outcome=Concept(name='exposed population', identifiers={'genepio': '0001538'}, context={}), provenance=[]), NaturalConversion(rate_law=None, type='NaturalConversion', subject=Concept(name='immune population', identifiers={'ido': '0000592'}, context={}), outcome=Concept(name='susceptible population', identifiers={'ido': '0000514'}, context={}), provenance=[])], parameters={}, initials={})" + ] }, "execution_count": 14, "metadata": {}, @@ -245,13 +325,13 @@ } ], "source": [ - "TemplateModel.from_json(M4.dict())" + "TemplateModel.from_json(M4.model_dump())" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -265,9 +345,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.9" + "version": "3.10.12" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/notebooks/model_api.ipynb b/notebooks/model_api.ipynb index 51041b752..f10024735 100644 --- a/notebooks/model_api.ipynb +++ b/notebooks/model_api.ipynb @@ -44,8 +44,8 @@ ")\n", "natural_conversion = NaturalConversion(subject=infected, outcome=immune)\n", "sir_template_model = TemplateModel(templates=[controlled_conversion, natural_conversion])\n", - "sir_template_model_dict = sir_template_model.dict()\n", - "print(sir_template_model.json())" + "sir_template_model_dict = sir_template_model.model_dump()\n", + "print(sir_template_model.model_dump_json())" ] }, { @@ -61,7 +61,10 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [ { @@ -106,17 +109,26 @@ }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "## Biomodels Model\n", "The `/api/biomodels/` endpoint returns a `TemplateModel` json of the biomodels model." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": 4, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [ { "name": "stdout", @@ -129,24 +141,30 @@ "source": [ "res = requests.get(rest_url + \"/api/biomodels/BIOMD0000000956\")\n", "print(res.json())" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "## Bilayer Endpoints\n", "The `/api/model_to_bilayer` and `/api/bilayer_to_model` endpoints can translate between a `TemplateModel` json and a `Bilayer`" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": 5, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [ { "name": "stdout", @@ -160,14 +178,17 @@ "# Get a bilayer json representation from a TemplateModel json\n", "res = requests.post(rest_url + \"/api/model_to_bilayer\", json=sir_template_model_dict)\n", "print(res.json())" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": 6, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [ { "name": "stdout", @@ -182,26 +203,32 @@ "bilayer_json = res.json()\n", "res = requests.post(rest_url + \"/api/bilayer_to_model\", json=bilayer_json)\n", "print(res.json())" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "## SBML XML To Model\n", "For the `/api/sbml_xml_to_model` endpoint, you can provide an SBML model in the form of a string of the XML and get back a model.\n", "\n", "First, get an XML string. Here, done by the `get_sbml_model` function from the `biomodels` submodule in MIRA:" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": 7, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [ { "name": "stdout", @@ -217,23 +244,29 @@ "from mira.sources.biomodels import get_sbml_model\n", "biomd951_xml_str = get_sbml_model(\"BIOMD0000000956\")\n", "print(biomd951_xml_str[:250])" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "Next, submit this string to the endpoint and get the corresponding model back:" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": 8, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [ { "name": "stdout", @@ -246,10 +279,7 @@ "source": [ "res = requests.post(rest_url + \"/api/sbml_xml_to_model\", json={\"xml_string\": biomd951_xml_str})\n", "print(res.json())" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", @@ -341,7 +371,9 @@ { "data": { "image/png": "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\n", - "text/plain": "" + "text/plain": [ + "" + ] }, "execution_count": 10, "metadata": {}, @@ -369,33 +401,47 @@ { "cell_type": "code", "execution_count": 11, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "# Add a location to the templates that are not NaturalConversion (Note that this has no meaning outside as an illustration in this example)\n", "templates_w_context = [templ.with_context(location=\"geonames:5128581\") if not isinstance(templ, NaturalConversion) else templ for templ in sir_template_model.templates]\n", "sir_w_context = TemplateModel(templates=templates_w_context, parameters=sir_template_model.parameters, initials=sir_template_model.initials)" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "Submit the two models to `/api/models_to_delta_image` to get an image of the delta graph:" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": 12, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAC9QAAAEFCAYAAAC46k5iAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdd3RUZf7H8fek94SahIBSpChiooCowEovEoosrojiogviz4KIuIptPWJZlVXRlRV11UVFUXYJUgQVsdCLEhVp0iEJJISEBJKQ8vz+uMyEYSYwCUkmMZ/XOfdM8sz3Pvf73BY453ufazPGGERERERERERERERERERERERERERERERE6pY9Pt7OQERERERERERERERERERERERERERERETEG1RQLyIiIiIiIiIiIiIiIiIiIiIiIiIiIiJ1kgrqRURERERERERERERERERERERERERERKRO8vN2AiLVKT8f8vKc27KywJhzx50t1tcX/P2d2319ISLCtY+wMNdYPz8IDz93nIiIiIiIiIiIiIiIiIiIiIiIiIiIiFQeFdRLjVJQYBWt25djx6zPwkLIzS0tdM/NtdqysqCoyIo7eRKOH4cTJ6x+jh2zvrPH5OR4e3QVExlpFdxHRkJAAISGQkgIBAZaBfu+vlCvXmlRflAQBAeXFuSfvn5UlPPipzuAiIiIiIiIiIiIiIiIiIiIiIiIiIjUYTZjzpxvW6TiCgshM9O5KD4rC7Kz4ehR1/bTv8/Kcj8rPFiF4WFhVqF4UFBpsXhUlFVQfnqxuT0mPNwqGLcXjkdElMacztMZ46G0zzNjS0qs5XQFBVZx/5k8nRE/J8d6EODoUSguth4QsPd5+kMDxcVWjP2hAXvM8ePWQwb2GHdCQ12L7M9c7IX49eo5t9evDz4+7vsVERERERERERERERERERERERERERGpBfaooF7OKT8f0tMhNRUOH7Z+TkmxPg8fdm5PT3dd32ZzX5BdVuH2mW2RkdU/5t8bY87+IMO5luxs1z59faFRI2uJiYHoaGjcuPTnRo0gNtZqa9TI9aEFERERERERERERERERERERERERERERL1NBfV127Bjs2wd798LBg5CW5lw4f/iw1XZmMXVoaGmhdOPGzkXTTZpAgwYqiP89OvMNA4cPw6FD7h+2OHzYehDjdPbie3fnTFwcXHABXHih9YYBERERERERERERERERERERERERERGRaqCC+t+roiKryHnvXqtoft8+2L+/tIB+3z7nQvmICKuwuVEja3bxmJjSmcdP/zk6GkJCvDcuqT1yclzfZJCebhXhn/nziROl6zVqBM2aWQX29iL703+PibHeeiAiIiIiIiIiIiIiIiIiIiIiIiIiInKeVFBfWxUWwu7dsGOHVSC/f7+17N1rLampVlE9gL+/NQN4s2bQvHlpcXKzZlax8gUXQHi4V4cjdVxmZulDH3v2lJ7P9gdAUlOhpMSKDQyEpk1Lz2H7OX3hhdC6tdXu4+PN0YiIiIiIiIiIiIiIiIiIiIiIiIiISC2hgvqa7uhR2LwZfv0Vdu0qXTZvhvx8KyYoyJpdvmVLa4mNdf79ggvAz8+74xA5H4WF1oz2qaml10BKSunvO3dCVpYVGxBgFdy3bAmXXALt25deCy1aaHZ7ERERERERERERERERERERERERERFxUEF9TZCZCdu3Oy87dlifJ05YMVFR0KaNNQN327alP7durdnlRcC6jnbsgG3bnK+jHTvg+HErJirKumbatLGuI/vPuo5EREREREREREREREREREREREREROokFdRXp5wc+Okna0lOhp9/top/jxyxvg8MLC3wtRf52gt/GzXybu4itdn+/aUPqZxedL9njzX7PVhvdWjXDjp0gMsug/h4a3b7oCCvpi4iIiIiIiIiIiIiIiIiIiIiIiIiIlVHBfVVwRjYvRs2bXIuoN+92/ouKsoq2O3QAS6+uLR4/oILwMfH29mL1B2FhdZ1aZ/RfutW67rdvNl6O4Sfn3V92gvs4+Otn+PivJ25iIiIiIiIiIiIiIiIiIiIiIiIiIhUAhXUn6/cXGu2682bYeNG+PVX+PHH0lnnY2OhY0drad8eLrnEWmw27+YtImeXkmJd0/brevNm2LKl9KGY9u1Lr2n7NR4c7O2sRURERERERERERERERERERERERESkHFRQXx75+dbs1evWWcv69bBjh1VgGxlpzTh/+kzWl14KoaHezlpEKsvRo9bbJk5/88TmzZCXZ81mf8klcOWV0KULdO5sFdz7+Xk7axERERERERERERERERERERERERERKYMK6s9m+3ZYvbq0gD45GQoLoX59q2j2yivh8sut4vkWLbydrYh4Q3Gxda/46SfYsMG6V2zcCMePWw/UXHFF6f2ia1eIi/N2xiIiIiIiIiIiIiIiIiIiIiIiIiIicooK6u2Ki2HrVli5ElasgG+/hX37wN8fWreGbt2sYtiOHa1ZqG02b2cs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fOJx31I3hYXBjBnw+efw3XfezsbZc889R6tWrbj99tv5/PPPyc7OJjMzk5kzZ/LUU08xbdq0cr/doTblUtXbvOKKK9i+fTv79+9n9erV7Nq1i+7du5crvxYtWjBx4kQWLlxITk4O27dvZ9SoUaSmpjJ9+nSio6MrnF+lyv4VvhsOcYnwh/9ZszP/jt1555389NNPFBQUcPjwYV544QWMMfQ645VG/fr1IyUlhX/+85/k5uayc+dO7rvvvjJnYPekX/t5cf/997No0SJycnI4cOAAd911F6mpqY6C+oqc35GRkTzyyCOsXr2a48ePs2HDBm655RYCAgKYPn26U+z5nN+hoaHcc889rF279pzbcacyr11Px1yR67E8+7O858qZfH196dGjB2lpabz44otkZGSQl5fH8uXLeeONN9yuU5FjWKvuSzVF1GXQdwXkH4bvb4CSQm9nJCIiIiIiIiIiIiIiIiIiIuLMuHHihDFxccZMmODuWxFnjz76qPHz8zMHDx4877569+5tAgMDKyErZ8uXLzeAuffee12+e/LJJw1g5s6dW2PjjTGma9euZv369Z4N2Bhz4sQJx89xcXHG19fXo/Wuu+46c8cdd5j333/fAGbq1Kllxt5www2madOmprCw0OO8pO7q3duYbt2qZ1urV682gMvizpEjR8ykSZNMy5Ytjb+/v2nUqJHp16+f+fLLLx0x8+bNc+nr5ptvNsYY079/f5fvXnzxRZe2Rx991BhjXNoHDRpUrlzcje3RRx8169evd2l/7rnnzPfff+/S/re//a1Stnmu8WzdutV0797dhIaGmmbNmpnXX3+93McyIyPDTJw40bRo0cL4+/ubyMhI079/f7Ns2bKzHh/ArF692vMN7dtnTEXvZYU5xsxvZcwX3Ywpzq9YH7XIpk2bzPjx483FF19sQkJCTP369c1VV11l3nrrLVNSUuIUm5WVZcaOHWtiY2NNcHCw6datm1m/fr3p2LGj4zg99NBD5e73zPMiNjbWjBw50mzfvt0pzpPz2y4+Pt7ExcWZX3/91fTv39+Eh4eb4OBgc+2115oVK1a4xJd1fpd1vzj9vhAXF2fWrVtnevbsacLCwtxu52z3kfKOrSzlHbMn12NF+/b0XDnbfklPTzfjx483zZo1M/7+/iY6OtqMGTPGPPzww47Yjh07VvgYlmc/VPS++buW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4L/Rqmq9zgoc66uK+K6Pqar3ro+BW3Owdqma5/IgOX8efv8djhwpvj1xAgoKwN4eAgJU+Ll3b/D3h8aNVTV6C6mFLSqBTqfeX/oRD4zl50N8vPq95dgx9V79z3/UKAjG79cWLaB5czW1aKEuFHxI6LTyjDUmhBBCCCEeeJqm/mZbtw769avq3gghoPizuH591fZDlM7NzY309HSuXbtmMuQ1wCuvvMJnn33GJ598wiuvvAKoymuBgYHs3bvXpG2rVq04c+YMqampJsvLan+rfd9JH3v37s3mzZu5cOGCyfDKXbt25fvvv+f69es4ODgYlnfo0IHff/+da9eumewzIyODq1ev4uJieiI2ODiYX3/9lcTEROrWrVtmv4UQQgghhBBCCFF9LFsGERGQlla+9qmpqXh6eposCw4OLvMC+9dff51169aZnBtJTU1lypQpfPPNN6SkpODh4UH37t2pXbs277zzjtk2T5w4wbBhw/j111/x8PBg4sSJvP7664btpaenM378eLZu3UpaWhrBwcEsXLiQkSNHcujQIQAiIyPp3bs3ISEhZn0sGasob/8GDBjAhAkTTB47efJkZs2aZbLsl19+4cMPP+THH38kJSUFOzs7AgICGDhwICNHjsTGxsak/eXLl5k1axabN28mKSkJBwcH2rRpQ0REhCF8HhcXZ/Zc9PvWlQj49ujRg6+//tpkWUREBPPnz+f333+nefPmZscE4MqVK8yePZtNmzaRmJiIm5sbQUFBTJgwgbCwsDL7Udox/f3331m6dCl79uwhISEBOzs7mjRpwpAhQxgyZAg6nY5NmzYZKtHrvfTSS3z++ef37LjcTJcu4OsLpeTEH1phYWHs3bu3wheuVBsFBSoUeehQ8XTwoKo87OICjz8OwcFq6tABGjSo6h4LIUSxonzISiwO2etvr/23uGq9jbtpyN41QAXvnRpWbd+rq2vX4MABiI2FuDg1r/8dtn590xBy8+YqPG9pWbV9FuJWcnPh6FEVsNdPhw+ryvag3ttPPqlGV2jTRlW5t7Wt2j5XjjMSqBdCCCGEEIAE6oWojvr1U6P69eypRl175BH192rNmlXdM5Gbm4udnR12dnalDuU9YcIEoqOjmTdvHhEREcDdC9Tfat930kd9oD47Oxs7o+GGn3nmGXbs2GE2THenTp04ePAgmZmZJvu8lV27dvHU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\n", - "text/plain": "" + "text/plain": [ + "" + ] }, "execution_count": 12, "metadata": {}, @@ -403,29 +449,35 @@ } ], "source": [ - "res = requests.post(rest_url + \"/api/models_to_delta_image\", json={\"template_model1\": sir_template_model_dict, \"template_model2\": sir_w_context.dict()})\n", + "res = requests.post(rest_url + \"/api/models_to_delta_image\", json={\"template_model1\": sir_template_model_dict, \"template_model2\": sir_w_context.model_dump()})\n", "with open(\"./delta_graph.png\", \"wb\") as f:\n", " f.write(res.content)\n", "Image(filename=\"./delta_graph.png\")" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "From the image, it can be seen that the NaturalConversion of the two models are equals, while the ControlledConversion in one model is a refinement of the other, in this case because the context was added.\n", "\n", "The other endpoint provides a way to download the graph seen in the image as a node-link data json:" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": 13, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [ { "name": "stdout", @@ -436,17 +488,14 @@ } ], "source": [ - "res = requests.post(rest_url + \"/api/models_to_delta_graph\", json={\"template_model1\": sir_template_model_dict, \"template_model2\": sir_w_context.dict()})\n", + "res = requests.post(rest_url + \"/api/models_to_delta_graph\", json={\"template_model1\": sir_template_model_dict, \"template_model2\": sir_w_context.model_dump()})\n", "print(res.json())" - ], - "metadata": { - "collapsed": false - } + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -460,9 +509,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.9" + "version": "3.10.12" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/notebooks/scenarios_2024.08/eval_202301_scenario2.ipynb b/notebooks/scenarios_2024.08/eval_202301_scenario2.ipynb index 97ff219b9..f577fe3ba 100644 --- a/notebooks/scenarios_2024.08/eval_202301_scenario2.ipynb +++ b/notebooks/scenarios_2024.08/eval_202301_scenario2.ipynb @@ -431,7 +431,7 @@ "outputs": [], "source": [ "with open('eval_202301_scenario2_sidarthe_v_model_comparison.json', 'w') as fh:\n", - " fh.write(tc.model_comparison.json(indent=1))" + " fh.write(tc.model_comparison.model_dump_json(indent=1))" ] } ], @@ -451,7 +451,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/setup.cfg b/setup.cfg index ef7f299aa..44b659a30 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,7 @@ license_files = [options] install_requires = - pydantic>=1.10,<2.0.0 + pydantic>=2.0.0 sympy typing_extensions networkx @@ -68,7 +68,8 @@ metaregistry = bioregistry[web] more_click web = - fastapi<0.87.0 + fastapi>=0.100.0 + httpx flask flasgger bootstrap-flask diff --git a/tests/test_amr_source.py b/tests/test_amr_source.py index 0b290f5a7..fdfce2d55 100644 --- a/tests/test_amr_source.py +++ b/tests/test_amr_source.py @@ -196,9 +196,9 @@ def test_annotation_serialization_ingestion(): petrinet_tm = petrinet.template_model_from_amr_json(amrs[1]) stockflow_tm = stockflow.template_model_from_amr_json(amrs[2]) - zipped_annotations = zip(regnet_tm.annotations.dict().values(), - petrinet_tm.annotations.dict().values(), - stockflow_tm.annotations.dict().values()) + zipped_annotations = zip(regnet_tm.annotations.model_dump().values(), + petrinet_tm.annotations.model_dump().values(), + stockflow_tm.annotations.model_dump().values()) for annotation_attribute_tuple in zipped_annotations: assert annotation_attribute_tuple[0] diff --git a/tests/test_metamodel.py b/tests/test_metamodel.py index accdf19be..5ca5923fc 100644 --- a/tests/test_metamodel.py +++ b/tests/test_metamodel.py @@ -235,8 +235,8 @@ def test_from_askenet_petri_mathml(): tm = template_model_from_amr_json(model_json) # Check equality - mathml_str = sorted_json_str(mathml_tm.dict()) - org_str = sorted_json_str(tm.dict()) + mathml_str = sorted_json_str(mathml_tm.model_dump()) + org_str = sorted_json_str(tm.model_dump()) assert mathml_str == org_str diff --git a/tests/test_model_api.py b/tests/test_model_api.py index 65e8b45a6..b8a396110 100644 --- a/tests/test_model_api.py +++ b/tests/test_model_api.py @@ -96,13 +96,13 @@ def query_relations( relations=relation_type, ) res = get_relations_web(relations_model=rq) - return [r.dict(exclude_unset=True) for r in res] + return [r.model_dump(exclude_unset=True) for r in res] @staticmethod def get_entity(curie: str): try: res = get_entity_web(curie=curie) - return res.dict(exclude_unset=True) + return res.model_dump(exclude_unset=True) except requests.exceptions.HTTPError: return None @@ -143,17 +143,18 @@ def test_petri(self): """Test the petrinet endpoint.""" sir_model_templ = _get_sir_templatemodel() response = self.client.post( - "/api/to_petrinet_acsets", json=sir_model_templ.dict() + "/api/to_petrinet_acsets", json=sir_model_templ.model_dump() ) self.assertEqual(response.status_code, 200, msg=response.content) - response_petri_net = PetriNetResponse.parse_obj(response.json()) + response_petri_net = PetriNetResponse.model_validate(response.json()) model = Model(sir_model_templ) petri_net = PetriNetModel(model) self.assertEqual(petri_net.to_pydantic(), response_petri_net) def test_petri_parameterized(self): response = self.client.post( - "/api/to_petrinet_acsets", json=json.loads(sir_parameterized.json()) + "/api/to_petrinet_acsets", json=json.loads( + sir_parameterized.model_dump_json()) ) self.assertEqual(200, response.status_code, msg=response.content) @@ -163,12 +164,14 @@ def test_petri_distribution(self): parameters={'minimum': 0.01, 'maximum': 0.5}) sir_distribution.parameters['beta'].distribution = distr response = self.client.post( - "/api/to_petrinet_acsets", json=json.loads(sir_distribution.json()) + "/api/to_petrinet_acsets", json=json.loads( + sir_distribution.model_dump_json()) ) pm = response.json() - assert pm['T'][0]['tprop']['parameter_distribution'] == distr.json() + assert (pm['T'][0]['tprop']['parameter_distribution'] == + distr.model_dump_json()) assert json.loads(pm['T'][0]['tprop']['mira_parameter_distributions']) == \ - {'beta': distr.dict()} + {'beta': distr.model_dump()} self.assertEqual(200, response.status_code, msg=response.content) def test_petri_to_template_model(self): @@ -177,7 +180,7 @@ def test_petri_to_template_model(self): response = self.client.post("/api/from_petrinet_acsets", json=petrinet_json) self.assertEqual(200, response.status_code, msg=response.content) resp_json_str = sorted_json_str(response.json()) - tm_json_str = sorted_json_str(tm.dict()) + tm_json_str = sorted_json_str(tm.model_dump()) self.assertEqual(resp_json_str, tm_json_str) def test_petri_to_template_model_parameterized(self): @@ -186,7 +189,7 @@ def test_petri_to_template_model_parameterized(self): response = self.client.post("/api/from_petrinet_acsets", json=petrinet_json) self.assertEqual(200, response.status_code, msg=response.content) resp_json_str = sorted_json_str(response.json()) - tm_json_str = sorted_json_str(tm.dict()) + tm_json_str = sorted_json_str(tm.model_dump()) self.assertEqual(resp_json_str, tm_json_str) def test_askenet_to_template_model(self): @@ -208,7 +211,8 @@ def test_askenet_to_template_model_no_sympy(self): self.assertIsInstance(template_model, TemplateModel) def test_askenet_from_template_model(self): - response = self.client.post("/api/to_petrinet", json=json.loads(sir_parameterized.json())) + response = self.client.post("/api/to_petrinet", json=json.loads( + sir_parameterized.model_dump_json())) self.assertEqual(200, response.status_code, msg=response.content) template_model = template_model_from_amr_json(response.json()) self.assertIsInstance(template_model, TemplateModel) @@ -223,7 +227,7 @@ def test_stratify(self): "geonames:4930956": "Boston", } query_json = { - "template_model": sir_templ_model.dict(), + "template_model": sir_templ_model.model_dump(), "key": key, "strata": strata, "strata_name_map": strata_name_map, @@ -238,13 +242,13 @@ def test_stratify(self): strata=strata, strata_curie_to_name=strata_name_map, ) - strat_str = sorted_json_str(strat_templ_model.dict()) + strat_str = sorted_json_str(strat_templ_model.model_dump()) self.assertEqual(strat_str, resp_json_str) # Test directed True, also skip the name map query_json = { - "template_model": sir_templ_model.dict(), + "template_model": sir_templ_model.model_dump(), "key": key, "strata": strata, "directed": True, @@ -259,7 +263,7 @@ def test_stratify(self): strata=set(strata), directed=query_json["directed"], ) - strat_str = sorted_json_str(strat_templ_model.dict()) + strat_str = sorted_json_str(strat_templ_model.model_dump()) self.assertEqual(strat_str, resp_json_str) @@ -268,7 +272,7 @@ def test_stratify(self): def test_stratify_observable_api(self): from mira.examples.sir import sir_parameterized - tm = sir_parameterized.copy(deep=True) + tm = sir_parameterized.model_copy(deep=True) symbols = set(tm.get_concepts_name_map().keys()) expr = sympy.Add(*[sympy.Symbol(s) for s in symbols]) tm.observables = {'half_population': Observable( @@ -281,7 +285,7 @@ def test_stratify_observable_api(self): structure=[], cartesian_control=True) - query_json = {"template_model": json.loads(tm.json())} + query_json = {"template_model": json.loads(tm.model_dump_json())} query_json.update(strata_options) response = self.client.post("/api/stratify", json=query_json) @@ -291,7 +295,7 @@ def test_stratify_observable_api(self): def test_to_dot_file(self): sir_templ_model = _get_sir_templatemodel() response = self.client.post( - "/api/viz/to_dot_file", json=sir_templ_model.dict() + "/api/viz/to_dot_file", json=sir_templ_model.model_dump() ) self.assertEqual(200, response.status_code) self.assertIn( @@ -309,7 +313,7 @@ def test_to_dot_file(self): def test_to_graph_image(self): sir_templ_model = _get_sir_templatemodel() response = self.client.post( - "/api/viz/to_image", json=sir_templ_model.dict() + "/api/viz/to_image", json=sir_templ_model.model_dump() ) self.assertEqual(200, response.status_code) self.assertIn( @@ -343,7 +347,8 @@ def test_biomodels_id_to_template_model(self): assert tm == local self.assertEqual( - sorted_json_str(tm.dict()), sorted_json_str(local.dict()) + sorted_json_str(tm.model_dump()), sorted_json_str( + local.model_dump()) ) def test_workflow(self): @@ -379,7 +384,7 @@ def test_template_model_to_bilayer_json(self): bj = BilayerModel(Model(tm)).bilayer response = self.client.post("/api/model_to_bilayer", - json=json.loads(tm.json())) + json=json.loads(tm.model_dump_json())) self.assertEqual(response.status_code, 200) bj_res = response.json() @@ -400,7 +405,8 @@ def test_xml_str_to_template_model(self): # less restrictive than the string comparison below assert tm_res == local self.assertEqual( - sorted_json_str(tm_res.dict()), sorted_json_str(local.dict()) + sorted_json_str(tm_res.model_dump()), sorted_json_str( + local.model_dump()) ) def test_models_to_templatemodel_delta_graph_json(self): @@ -415,8 +421,8 @@ def test_models_to_templatemodel_delta_graph_json(self): response = self.client.post( "/api/models_to_delta_graph", json={ - "template_model1": sir_templ_model.dict(), - "template_model2": sir_templ_model_ctx.dict(), + "template_model1": sir_templ_model.model_dump(), + "template_model2": sir_templ_model_ctx.model_dump(), }, ) self.assertEqual(200, response.status_code) @@ -445,8 +451,8 @@ def test_models_to_templatemodel_delta_graph_image(self): response = self.client.post( "/api/models_to_delta_image", json={ - "template_model1": sir_templ_model.dict(), - "template_model2": sir_templ_model_ctx.dict(), + "template_model1": sir_templ_model.model_dump(), + "template_model2": sir_templ_model_ctx.model_dump(), }, ) self.assertEqual(200, response.status_code) @@ -477,7 +483,7 @@ def test_add_transition(self): response = self.client.post( "/api/add_transition", json={ - "template_model": sir_templ_model.dict(), + "template_model": sir_templ_model.model_dump(), "subject_concept": s, "outcome_concept": x, "parameter": {'name': 's_to_x', 'value': 0.1}} @@ -496,7 +502,7 @@ def test_n_way_comparison(self): response = self.client.post( "/api/model_comparison", - json={"template_models": [m.dict() for m in mmts]}, + json={"template_models": [m.model_dump() for m in mmts]}, ) self.assertEqual(200, response.status_code) @@ -514,8 +520,8 @@ def test_n_way_comparison(self): similarity_scores=model_comparson_graph_data.get_similarity_scores(), ) self.assertEqual( - sorted_json_str(local_response.dict()), - sorted_json_str(resp_model.dict()), + sorted_json_str(local_response.model_dump()), + sorted_json_str(resp_model.model_dump()), ) def test_n_way_comparison_askenet(self): @@ -531,17 +537,18 @@ def test_n_way_comparison_askenet(self): # Copy parameters, annotations, initials and observables from the # original model sir_parameterized_ctx.parameters = { - k: v.copy(deep=True) + k: v.model_copy(deep=True) for k, v in sir_templ_model.parameters.items() } sir_parameterized_ctx.annotations = \ - sir_templ_model.annotations.copy(deep=True) + sir_templ_model.annotations.model_copy(deep=True) sir_parameterized_ctx.observables = { - k: v.copy(deep=True) + k: v.model_copy(deep=True) for k, v in sir_templ_model.observables.items() } sir_parameterized_ctx.initials = { - k: v.copy(deep=True) for k, v in sir_templ_model.initials.items() + k: v.model_copy(deep=True) for k, v in + sir_templ_model.initials.items() } sir_parameterized_ctx.time = copy.deepcopy(sir_templ_model.time) askenet_list = [] @@ -582,15 +589,20 @@ def test_n_way_comparison_askenet(self): "exclude_defaults": True, "exclude_unset": True, "exclude_none": True, - "skip_defaults": True, } # Compare the ModelComparisonResponse models - assert local_response == resp_model # If assertion fails the diff is printed + # If assertion fails the diff is printed + # Use model dump as Pydantic objects have an attribute + # "model_fields_set" that contain explicitly set attributes even if + # are None + assert local_response.model_dump() == resp_model.model_dump() local_sorted_str = sorted_json_str( - json.loads(local_response.json(**dict_options)), skip_empty=True + json.loads(local_response.model_dump_json(**dict_options)), + skip_empty=True ) resp_sorted_str = sorted_json_str( - json.loads(resp_model.json(**dict_options)), skip_empty=True + json.loads(resp_model.model_dump_json(**dict_options)), + skip_empty=True ) self.assertEqual(local_sorted_str, resp_sorted_str) @@ -601,7 +613,7 @@ def test_counts_to_dimensionless_mira(self): response = self.client.post( "/api/counts_to_dimensionless_mira", json={ - "model": json.loads(sir_parameterized_init.json()), + "model": json.loads(sir_parameterized_init.model_dump_json()), "counts_unit": "person", "norm_factor": sir_init_val_norm, }, diff --git a/tests/test_modeling/test_amr_ops.py b/tests/test_modeling/test_amr_ops.py index a3d525e94..e194ed278 100644 --- a/tests/test_modeling/test_amr_ops.py +++ b/tests/test_modeling/test_amr_ops.py @@ -297,7 +297,7 @@ def test_add_parameter(self): description = 'TEST_DESCRIPTION' value = 0.35 distribution = {'type': 'test_distribution', - 'parameters': {'test_dist': 5}} + 'parameters': {'test_dist': 5.0}} new_amr = add_parameter(amr, parameter_id=parameter_id, name=name, description=description, value=value, distribution=distribution) param_dict = {} diff --git a/tests/test_modeling/test_petri.py b/tests/test_modeling/test_petri.py index 7f43d427a..002fb0bbd 100644 --- a/tests/test_modeling/test_petri.py +++ b/tests/test_modeling/test_petri.py @@ -45,4 +45,4 @@ def test_petri_parameterized(): assert js assert js['S'][0]['concentration'] == 1 assert js['T'][0]['rate'] == 0.1 - assert js['T'][0]['tprop']['parameter_distribution'] == distr.json() + assert js['T'][0]['tprop']['parameter_distribution'] == distr.model_dump_json() diff --git a/tests/test_ops.py b/tests/test_ops.py index 7276e131c..b46c3ea40 100644 --- a/tests/test_ops.py +++ b/tests/test_ops.py @@ -159,19 +159,33 @@ def test_stratify_full(self): self.assertEqual(tm_stratified.parameters, actual.parameters) self.assertTrue(actual.initials['infected_population_vaccinated'].expression.equals( tm_stratified.initials['infected_population_vaccinated'].expression)) - self.assertEqual( + + def one_to_one_concept_mapping(strat_concepts, actual_concepts): + matched_concept_idxs = set() + for strat_concept in strat_concepts: + matched = False + for concept_idx, actual_concept in enumerate(actual_concepts): + if (strat_concept.is_equal_to(actual_concept) and + concept_idx not in matched_concept_idxs): + matched_concept_idxs.add(concept_idx) + matched = True + break + if not matched: + return False + return True + + self.assertTrue(one_to_one_concept_mapping( [t.subject for t in tm_stratified.templates], - [t.subject for t in actual.templates], - ) - self.assertEqual( + [t.subject for t in actual.templates])) + + self.assertTrue(one_to_one_concept_mapping( [t.outcome for t in tm_stratified.templates], - [t.outcome for t in actual.templates], - ) - self.assertEqual( + [t.outcome for t in actual.templates])) + + self.assertTrue(one_to_one_concept_mapping( [t.controller for t in tm_stratified.templates], - [t.controller for t in actual.templates], - ) - self.assertEqual(tm_stratified.templates, actual.templates) + [t.controller for t in actual.templates])) + def test_stratify(self): """Test stratifying a template model by labels.""" diff --git a/tests/test_template.py b/tests/test_template.py index aafe7fda3..975477018 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -295,7 +295,7 @@ def test_get_curie_custom(): def test_rate_json(): t = NaturalDegradation(subject=Concept(name='x'), rate_law=sympy.Mul(2, sympy.Symbol('x'))) - jj = json.loads(t.json()) + jj = json.loads(t.model_dump_json()) assert jj.get('rate_law') == '2*x', jj t2 = Template.from_json(jj) assert isinstance(t2, NaturalDegradation) diff --git a/tox.ini b/tox.ini index cd9e4e850..6dbbbce7c 100644 --- a/tox.ini +++ b/tox.ini @@ -35,7 +35,6 @@ commands = [testenv:mypy] deps = mypy - pydantic skip_install = true commands = mypy --install-types --non-interactive --ignore-missing-imports mira/ description = Run the mypy tool to check static typing on the project.