Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1eb9dec
add hybridization class
m-philipps May 15, 2026
1165218
add sciml problem config
m-philipps May 15, 2026
f2c4193
create Problem for sciml extension
m-philipps May 25, 2026
96858f2
Assign SciML-specific lint checks
m-philipps May 26, 2026
dd3f87b
Proble.add_hybridization method
m-philipps May 26, 2026
8c1a8a9
add hybridization df property, setter
m-philipps May 26, 2026
8c5cd2b
fix
m-philipps May 26, 2026
238829b
add a basic test for sciml
m-philipps May 26, 2026
d4d8a94
methods for adding nn, array data to problem
m-philipps May 26, 2026
653ccc7
Non-local petab_sciml import
m-philipps May 27, 2026
15c4486
add dependency
m-philipps May 27, 2026
3d8901b
Update petab_sciml dependency to use Git URL
dilpath May 27, 2026
4ef0a10
neural_nets to neural_networks
BSnelling Jun 11, 2026
58c549a
add required params check to sciml validations
BSnelling Jun 15, 2026
f1d4540
ruff format
BSnelling Jun 15, 2026
2f76d3e
implement feedback from code review
BSnelling Jun 17, 2026
81292a7
move sciml code to separate files and resolve circular imports
BSnelling Jun 17, 2026
840ae7e
fixup ruff
BSnelling Jun 17, 2026
9af9a54
fix docs build issue
BSnelling Jun 17, 2026
e342f26
Update petab/v2/core.py
BSnelling Jun 17, 2026
254a596
add to docstrings
BSnelling Jun 17, 2026
d95f09a
Revert "move sciml code to separate files and resolve circular imports"
BSnelling Jun 18, 2026
239e1ff
use problem.extensions.sciml for separation
BSnelling Jun 18, 2026
3c1a09f
revert mapping table fixes that should be implemented in the other pr
dilpath Jun 19, 2026
118aed7
fix aliased model logic - again
BSnelling Jun 22, 2026
e133f21
move sciml validation to own file
BSnelling Jun 23, 2026
81148e6
use pypi petab_sciml
BSnelling Jun 23, 2026
7759b50
cherry-pick ci fix
BSnelling Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import sys
import warnings

from pydantic import BaseModel

# -- Path setup --------------------------------------------------------------

# If extensions (or modules to document with autodoc) are in another directory,
Expand Down Expand Up @@ -126,6 +128,9 @@ def skip_some_objects(app, what, name, obj, skip, options):
"""Exclude some objects from the documentation"""
if getattr(obj, "__module__", None) == "collections":
return True
# Napoleon + Pydantic v2 bug: BaseModel itself triggers __getattr__ error
if obj is BaseModel:
return True


def setup(app):
Expand Down
2 changes: 2 additions & 0 deletions petab/v2/C.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@
MAPPING_FILES = "mapping_files"
#: Extensions key in the YAML file
EXTENSIONS = "extensions"
#: PEtab SciML extension
EXT_ID_SCIML = "sciml"


# MAPPING
Expand Down
122 changes: 112 additions & 10 deletions petab/v2/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,22 @@ def __iadd__(self, other: T) -> BaseTable[T]:
return self


# SciML extension classes — imported after BaseTable is defined to avoid
# circular imports (sciml.py does not import from core.py).
from .extensions.sciml import ( # noqa: E402
HybridizationTable,
SciMLConfig,
SciMLExt,
)


class ProblemExtensions:
"""Runtime extension state attached to a :class:`Problem`."""

def __init__(self, sciml: SciMLExt = None):
self.sciml: SciMLExt = sciml or SciMLExt()


class Observable(BaseModel):
"""Observable definition."""

Expand Down Expand Up @@ -927,6 +943,7 @@ class Parameter(BaseModel):
)
#: Nominal value.
nominal_value: Annotated[
# PEtab SciML supports arrays via "array" nominal values
float | Literal["array"] | None, BeforeValidator(_convert_nan_to_none)
Comment thread
BSnelling marked this conversation as resolved.
] = Field(alias=C.NOMINAL_VALUE, default=None)
#: Is the parameter to be estimated?
Expand Down Expand Up @@ -1134,22 +1151,33 @@ def __init__(
measurement_tables: list[MeasurementTable] = None,
parameter_tables: list[ParameterTable] = None,
mapping_tables: list[MappingTable] = None,
extensions: ProblemExtensions = None,
config: ProblemConfig = None,
):
from ..v2.lint import default_validation_tasks
from ..v2.lint import default_validation_tasks, sciml_validation_tasks

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this, import from ..v2.sciml.lint for example, for separation.


self.config = config
self.models: list[Model] = models or []
self.validation_tasks: list[ValidationTask] = (
default_validation_tasks.copy()
)
if (
config
and config.extensions
and config.extensions.get(C.EXT_ID_SCIML)
):
self.validation_tasks: list[ValidationTask] = (
sciml_validation_tasks.copy()
)
else:
self.validation_tasks: list[ValidationTask] = (
default_validation_tasks.copy()
)

self.observable_tables = observable_tables or [ObservableTable()]
self.condition_tables = condition_tables or [ConditionTable()]
self.experiment_tables = experiment_tables or [ExperimentTable()]
self.measurement_tables = measurement_tables or [MeasurementTable()]
self.mapping_tables = mapping_tables or [MappingTable()]
self.parameter_tables = parameter_tables or [ParameterTable()]
self.extensions = extensions or ProblemExtensions()

def __repr__(self):
return f"<{self.__class__.__name__} id={self.id!r}>"
Expand Down Expand Up @@ -1322,6 +1350,45 @@ def from_yaml(
else None
)

extensions = ProblemExtensions()
if config.extensions and config.extensions.get(C.EXT_ID_SCIML):
from petab_sciml import ArrayDataStandard, NNModel, NNModelStandard

# Neural network classes are constructed via pytorch for now to get
# the proper inputs
neural_networks = [
NNModel.from_pytorch_module(
NNModelStandard.load_data(
_generate_path(
file_path=nn_config.location,
base_path=base_path,
)
).to_pytorch_module(),
nn_model_id=nn_id,
)
for nn_id, nn_config in (
config.extensions[C.EXT_ID_SCIML].neural_networks or {}
).items()
]

hybridization_tables = [
HybridizationTable.from_tsv(f, base_path)
for f in config.extensions[C.EXT_ID_SCIML].hybridization_files
]

array_data_files = [
ArrayDataStandard.load_data(_generate_path(f, base_path))
for f in config.extensions[C.EXT_ID_SCIML].array_files
]

extensions = ProblemExtensions(
sciml=SciMLExt(
neural_networks=neural_networks,
hybridization_tables=hybridization_tables,
array_data_files=array_data_files,
)
)

return Problem(
config=config,
models=models,
Expand All @@ -1331,6 +1398,7 @@ def from_yaml(
measurement_tables=measurement_tables,
parameter_tables=parameter_tables,
mapping_tables=mapping_tables,
extensions=extensions,
)

@staticmethod
Expand Down Expand Up @@ -1941,14 +2009,21 @@ def validate(

validation_results = ValidationResultList()

if self.config and self.config.extensions:
extensions = ",".join(self.config.extensions.keys())
supported_extensions = {C.EXT_ID_SCIML}
if (
self.config
and self.config.extensions
and (self.config.extensions.keys() - supported_extensions)
):
extensions_without_support = ",".join(
self.config.extensions.keys() - supported_extensions
)
validation_results.append(
ValidationIssue(
ValidationIssueSeverity.WARNING,
"Validation of PEtab extensions is not yet implemented, "
"but the given problem uses the following extensions: "
f"{extensions}",
"The given problem uses the following extensions for "
"which validation is not yet implemented: "
f"{extensions_without_support}",
)
)

Expand Down Expand Up @@ -2506,6 +2581,23 @@ class ProblemConfig(BaseModel):
validate_assignment=True,
)

@field_validator("extensions", mode="before")
@classmethod
def _parse_extensions(cls, v):
"""Parse extensions dict and convert known extensions to their specific
config classes."""
if isinstance(v, dict):
parsed_extensions = {}
for ext_name, ext_config in v.items():
if ext_name == C.EXT_ID_SCIML:
# Convert sciml extension to SciMLConfig
parsed_extensions[ext_name] = SciMLConfig(**ext_config)
else:
# Keep other extensions as ExtensionConfig
parsed_extensions[ext_name] = ExtensionConfig(**ext_config)
return parsed_extensions
return v

# convert parameter_file to list
@field_validator(
"parameter_files",
Expand Down Expand Up @@ -2543,12 +2635,22 @@ def to_yaml(self, filename: str | Path):

for model_id in data.get("model_files", {}):
data["model_files"][model_id][C.MODEL_LOCATION] = str(
data["model_files"][model_id]["location"]
data["model_files"][model_id][C.MODEL_LOCATION]
)
if data["id"] is None:
# The schema requires a valid id or no id field at all.
del data["id"]

for ext_id, d_ext in data[C.EXTENSIONS].items():
if ext_id == C.EXT_ID_SCIML:
# convert Paths to strings
for key in ("array_files", "hybridization_files"):
d_ext[key] = list(map(str, d_ext[key]))
for nn in d_ext["neural_networks"]:
d_ext["neural_networks"][nn][C.MODEL_LOCATION] = str(
d_ext["neural_networks"][nn][C.MODEL_LOCATION]
)

write_yaml(data, filename)

@property
Expand Down
Empty file.
Loading
Loading