From 2df67be69d707859e2f9039c20b88897574cb6d4 Mon Sep 17 00:00:00 2001 From: user <59329744+dilpath@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:15:21 +0200 Subject: [PATCH 1/2] add constants file --- petab_sciml/__init__.py | 1 + petab_sciml/constants.py | 101 +++++++++++++++++++++++++++++ petab_sciml/standard/array_data.py | 20 ++---- petab_sciml/standard/nn_model.py | 4 +- 4 files changed, 111 insertions(+), 15 deletions(-) create mode 100644 petab_sciml/constants.py diff --git a/petab_sciml/__init__.py b/petab_sciml/__init__.py index 39a66225..f5775c3a 100644 --- a/petab_sciml/__init__.py +++ b/petab_sciml/__init__.py @@ -1,2 +1,3 @@ +from .constants import * from .problem_utils import * from .standard import * diff --git a/petab_sciml/constants.py b/petab_sciml/constants.py new file mode 100644 index 00000000..186db4a7 --- /dev/null +++ b/petab_sciml/constants.py @@ -0,0 +1,101 @@ +"""Constants for reserved keywords or useful lists.""" + +from __future__ import annotations + +__all__ = [ + "METADATA", + "DATA", + "CONDITION_IDS", + "INPUTS", + "PARAMETERS", + "ALL_CONDITION_IDS", + "ARRAY", + "SUPPORTED_LAYERS", + "SUPPORTED_ACTIVATIONS", + "SUPPORTED_TENSOR_OPS", +] + + +# --- Array data file (HDF5) ------------------------------------------------- + +#: Metadata group. +METADATA = "metadata" +#: Name of a dataset. +DATA = "data" +#: Condition IDs field. +CONDITION_IDS = "conditionIds" +#: Inputs group. +INPUTS = "inputs" +#: Parameters group. +PARAMETERS = "parameters" +#: Condition IDs field value when the array data applies to all conditions. +ALL_CONDITION_IDS = "0" + + +# --- Reserved keywords ------------------------------------------------------ + +#: Indicate that a variable value is an array. +ARRAY = "array" + + +# --- NN model YAML format --------------------------------------------------- + +#: Supported layers. +SUPPORTED_LAYERS = [ + "Linear", + "Bilinear", + "Flatten", + "Dropout", + "Dropout1d", + "Dropout2d", + "Dropout3d", + "AlphaDropout", + "Conv1d", + "Conv2d", + "Conv3d", + "ConvTranspose1d", + "ConvTranspose2d", + "ConvTranspose3d", + "MaxPool1d", + "MaxPool2d", + "MaxPool3d", + "AvgPool1d", + "AvgPool2d", + "AvgPool3d", + "LPPool1d", + "LPPool2d", + "LPPool3d", + "AdaptiveMaxPool1d", + "AdaptiveMaxPool2d", + "AdaptiveMaxPool3d", + "AdaptiveAvgPool1d", + "AdaptiveAvgPool2d", + "AdaptiveAvgPool3d", +] + +#: Supported activation functions. +SUPPORTED_ACTIVATIONS = [ + "relu", + "relu6", + "hardtanh", + "hardswish", + "selu", + "leaky_relu", + "gelu", + "tanhshrink", + "softsign", + "softplus", + "tanh", + "sigmoid", + "hardsigmoid", + "silu", + "mish", + "elu", + "celu", + "softmax", + "log_softmax", +] + +#: These are handled by ``torch`` rather than ``torch.nn`` in the pytorch +#: compatibility code. +SUPPORTED_TENSOR_OPS = frozenset({"flatten", "cat"}) diff --git a/petab_sciml/standard/array_data.py b/petab_sciml/standard/array_data.py index ddd5bc83..b0ad9afe 100644 --- a/petab_sciml/standard/array_data.py +++ b/petab_sciml/standard/array_data.py @@ -10,6 +10,12 @@ from pydantic import BaseModel, field_validator from ruamel.yaml import YAML +from petab_sciml.constants import ( + ALL_CONDITION_IDS, + INPUTS, + METADATA, + PARAMETERS, +) from petab_sciml.standard.nn_model import NNModelStandard if TYPE_CHECKING: @@ -20,25 +26,11 @@ "Metadata", "ArrayData", "ArrayDataStandard", - "METADATA", - "DATA", - "CONDITION_IDS", - "INPUTS", - "PARAMETERS", - "ALL_CONDITION_IDS", "extract_torch_parameters", "add_array_files_to_yaml", ] -METADATA = "metadata" -DATA = "data" -CONDITION_IDS = "conditionIds" -INPUTS = "inputs" -PARAMETERS = "parameters" -ALL_CONDITION_IDS = "0" - - Array = get_array_type() diff --git a/petab_sciml/standard/nn_model.py b/petab_sciml/standard/nn_model.py index 47726963..b1050208 100644 --- a/petab_sciml/standard/nn_model.py +++ b/petab_sciml/standard/nn_model.py @@ -8,6 +8,8 @@ from mkstd import YamlStandard +from petab_sciml.constants import SUPPORTED_TENSOR_OPS + # For PyTorch import/export support try: import torch.fx @@ -318,7 +320,7 @@ def __init__(self) -> None: case "placeholder": state[node.name] = graph.placeholder(node.target) case "call_function": - if node.target in ["flatten", "cat"]: + if node.target in SUPPORTED_TENSOR_OPS: function = getattr(torch, node.target) else: function = getattr(nn.functional, node.target) From 84d6c98bec9b510f6248a0e5e2f3c4b997826cb3 Mon Sep 17 00:00:00 2001 From: user <59329744+dilpath@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:35:01 +0200 Subject: [PATCH 2/2] use enums; include regex for NN model patterns --- petab_sciml/constants.py | 179 ++++++++++++++++++++----------- petab_sciml/standard/nn_model.py | 18 ++-- 2 files changed, 128 insertions(+), 69 deletions(-) diff --git a/petab_sciml/constants.py b/petab_sciml/constants.py index 186db4a7..57657ac3 100644 --- a/petab_sciml/constants.py +++ b/petab_sciml/constants.py @@ -2,6 +2,9 @@ from __future__ import annotations +import re +from enum import StrEnum + __all__ = [ "METADATA", "DATA", @@ -10,9 +13,12 @@ "PARAMETERS", "ALL_CONDITION_IDS", "ARRAY", - "SUPPORTED_LAYERS", - "SUPPORTED_ACTIVATIONS", - "SUPPORTED_TENSOR_OPS", + "Layers", + "ActivationFunctions", + "TensorOps", + "Op", + "NN_ENTITY_PATTERN", + "NN_PARAMETER_PATTERN", ] @@ -40,62 +46,115 @@ # --- NN model YAML format --------------------------------------------------- -#: Supported layers. -SUPPORTED_LAYERS = [ - "Linear", - "Bilinear", - "Flatten", - "Dropout", - "Dropout1d", - "Dropout2d", - "Dropout3d", - "AlphaDropout", - "Conv1d", - "Conv2d", - "Conv3d", - "ConvTranspose1d", - "ConvTranspose2d", - "ConvTranspose3d", - "MaxPool1d", - "MaxPool2d", - "MaxPool3d", - "AvgPool1d", - "AvgPool2d", - "AvgPool3d", - "LPPool1d", - "LPPool2d", - "LPPool3d", - "AdaptiveMaxPool1d", - "AdaptiveMaxPool2d", - "AdaptiveMaxPool3d", - "AdaptiveAvgPool1d", - "AdaptiveAvgPool2d", - "AdaptiveAvgPool3d", -] -#: Supported activation functions. -SUPPORTED_ACTIVATIONS = [ - "relu", - "relu6", - "hardtanh", - "hardswish", - "selu", - "leaky_relu", - "gelu", - "tanhshrink", - "softsign", - "softplus", - "tanh", - "sigmoid", - "hardsigmoid", - "silu", - "mish", - "elu", - "celu", - "softmax", - "log_softmax", -] +class Layers(StrEnum): + """Neural network layers supported by the NN model YAML format. + + Names follow the PyTorch (``torch.nn``) naming scheme. + See https://petab-sciml.readthedocs.io/latest/layers.html + """ + + Linear = "Linear" + Bilinear = "Bilinear" + Flatten = "Flatten" + Dropout = "Dropout" + Dropout1d = "Dropout1d" + Dropout2d = "Dropout2d" + Dropout3d = "Dropout3d" + AlphaDropout = "AlphaDropout" + Conv1d = "Conv1d" + Conv2d = "Conv2d" + Conv3d = "Conv3d" + ConvTranspose1d = "ConvTranspose1d" + ConvTranspose2d = "ConvTranspose2d" + ConvTranspose3d = "ConvTranspose3d" + MaxPool1d = "MaxPool1d" + MaxPool2d = "MaxPool2d" + MaxPool3d = "MaxPool3d" + AvgPool1d = "AvgPool1d" + AvgPool2d = "AvgPool2d" + AvgPool3d = "AvgPool3d" + LPPool1d = "LPPool1d" + LPPool2d = "LPPool2d" + LPPool3d = "LPPool3d" + AdaptiveMaxPool1d = "AdaptiveMaxPool1d" + AdaptiveMaxPool2d = "AdaptiveMaxPool2d" + AdaptiveMaxPool3d = "AdaptiveMaxPool3d" + AdaptiveAvgPool1d = "AdaptiveAvgPool1d" + AdaptiveAvgPool2d = "AdaptiveAvgPool2d" + AdaptiveAvgPool3d = "AdaptiveAvgPool3d" + + +class ActivationFunctions(StrEnum): + """Activation functions supported by the NN model YAML format. + + Names follow the PyTorch (``torch.nn.functional``) naming scheme. + See https://petab-sciml.readthedocs.io/latest/layers.html + """ + + relu = "relu" + relu6 = "relu6" + hardtanh = "hardtanh" + hardswish = "hardswish" + selu = "selu" + leaky_relu = "leaky_relu" + gelu = "gelu" + tanhshrink = "tanhshrink" + softsign = "softsign" + softplus = "softplus" + tanh = "tanh" + sigmoid = "sigmoid" + hardsigmoid = "hardsigmoid" + silu = "silu" + mish = "mish" + elu = "elu" + celu = "celu" + softmax = "softmax" + log_softmax = "log_softmax" + + +class TensorOps(StrEnum): + """Non-activation tensor operations supported in a forward pass. + + Unlike layers and activation functions, these are in ``torch`` + rather than ``torch.nn``/``torch.nn.functional``. + """ + + flatten = "flatten" + cat = "cat" + + +class Op(StrEnum): + """PyTorch ``torch.fx`` opcodes for the ``op`` field of a node in a + neural network's forward graph. + + See https://pytorch.org/docs/stable/fx.html#torch.fx.Node + """ + + #: A forward-graph input (function argument). + PLACEHOLDER = "placeholder" + #: A call to a free function (e.g. a ``torch.nn.functional`` activation). + CALL_FUNCTION = "call_function" + #: A call to a method on a value (e.g. ``x.tanh()``). + CALL_METHOD = "call_method" + #: A call to a submodule/layer (e.g. a ``torch.nn`` layer). + CALL_MODULE = "call_module" + #: The forward-graph output. + OUTPUT = "output" + + +# --- Mapping-table modelEntityId syntax ------------------------------------- + +#: Matches a mapping-table ``modelEntityId`` that refers to a neural network +#: input, output, or parameter, e.g. ``net1.inputs[0][1]`` or +#: ``net1.parameters[layer1]``. Named groups: ``nn_id``, ``entity_type``. +NN_ENTITY_PATTERN = re.compile( + r"^(?P[^.\[\]\s]+)\.(?Pinputs|outputs|parameters)\b" +) -#: These are handled by ``torch`` rather than ``torch.nn`` in the pytorch -#: compatibility code. -SUPPORTED_TENSOR_OPS = frozenset({"flatten", "cat"}) +#: Matches a mapping-table ``modelEntityId`` referring to neural network +#: parameters, optionally for a specific layer, e.g. ``net1.parameters`` or +#: ``net1.parameters[layer1]``. Named groups: ``nn_id``, ``layer``. +NN_PARAMETER_PATTERN = re.compile( + r"^(?P[^.\[\]\s]+)\.parameters(?:\[(?P[^\]]+)\])?" +) diff --git a/petab_sciml/standard/nn_model.py b/petab_sciml/standard/nn_model.py index b1050208..890d368d 100644 --- a/petab_sciml/standard/nn_model.py +++ b/petab_sciml/standard/nn_model.py @@ -8,7 +8,7 @@ from mkstd import YamlStandard -from petab_sciml.constants import SUPPORTED_TENSOR_OPS +from petab_sciml.constants import Op, TensorOps # For PyTorch import/export support try: @@ -254,9 +254,9 @@ def from_pytorch_module( for pytorch_node in pytorch_nodes: op = pytorch_node.op target = pytorch_node.target - if op == "call_function": + if op == Op.CALL_FUNCTION: target = pytorch_node.target.__name__ - if op == "placeholder" and generate_inputs: + if op == Op.PLACEHOLDER and generate_inputs: inputs.append(Input(input_id=pytorch_node.target)) # Convert module args to strings @@ -317,25 +317,25 @@ def __init__(self) -> None: for k, v in node.kwargs.items() } match node.op: - case "placeholder": + case Op.PLACEHOLDER: state[node.name] = graph.placeholder(node.target) - case "call_function": - if node.target in SUPPORTED_TENSOR_OPS: + case Op.CALL_FUNCTION: + if node.target in TensorOps: function = getattr(torch, node.target) else: function = getattr(nn.functional, node.target) state[node.name] = graph.call_function( function, args, kwargs ) - case "call_method": + case Op.CALL_METHOD: state[node.name] = graph.call_method( node.target, args, kwargs ) - case "call_module": + case Op.CALL_MODULE: state[node.name] = graph.call_module( node.target, args, kwargs ) - case "output": + case Op.OUTPUT: graph.output(args[0]) return torch.fx.GraphModule(_PytorchModule(), graph)