Source code for ionworks.parameterized_model

"""Parameterized model client for managing parameterized battery models.

This module provides the :class:`ParameterizedModelClient` for creating,
reading, and updating parameterized models, which combine a base model with
specific parameter values for a given cell specification.
"""

from __future__ import annotations

from typing import Any

from ionworks.errors import IonworksError

from ._project_id import resolve_project_id
from ._simulation_settings import normalize_simulation_settings
from .models import (
    PaginatedList,
    ParameterizedModel,
    _build_endpoint,
    _parse_list_response,
)


[docs] class ParameterizedModelClient: """Client for managing parameterized models. Parameterized models combine a base model (e.g. SPM, DFN) with specific parameter values for a given cell specification. """
[docs] def __init__(self, client: Any) -> None: """Initialize the ParameterizedModelClient. Parameters ---------- client : Any The HTTP client instance for making API requests. """ self.client = client
[docs] def get(self, parameterized_model_id: str) -> ParameterizedModel: """Get a specific parameterized model by ID. Uses the non-cell-scoped endpoint so callers don't need the cell spec ID just to fetch an existing model. Parameters ---------- parameterized_model_id : str The ID of the parameterized model to retrieve. Returns ------- ParameterizedModel The requested parameterized model object. """ endpoint = f"/parameterized_models/{parameterized_model_id}" response_data = self.client.get(endpoint) return ParameterizedModel(**response_data)
[docs] def list_by_cell_specification( self, cell_spec_id: str, limit: int | None = None, offset: int | None = None, ) -> PaginatedList[ParameterizedModel]: """List parameterized models for a cell specification. Parameters ---------- cell_spec_id : str The ID of the cell specification to list models for. limit : int | None, optional Maximum number of models to return per page. offset : int | None, optional Number of models to skip for pagination. Returns ------- PaginatedList[ParameterizedModel] A paginated list of parameterized model objects. """ endpoint = _build_endpoint( f"/cells/{cell_spec_id}/parameterized_models", {"limit": limit, "offset": offset}, ) response_data = self.client.get(endpoint) return _parse_list_response(response_data, ParameterizedModel)
[docs] def list_by_project( self, project_id: str | None = None, limit: int | None = None, offset: int | None = None, *, cell_spec_id: str | None = None, ) -> PaginatedList[ParameterizedModel]: """List parameterized models across all cell specs in a project. Unlike :meth:`list`, which scopes to a single cell specification, this returns every parameterized model linked to any cell specification in the project. Parameters ---------- project_id : str | None, optional The ID of the project to list models for. Defaults to the project_id set on the Ionworks client. limit : int | None, optional Maximum number of models to return per page. offset : int | None, optional Number of models to skip for pagination. cell_spec_id : str | None, optional If provided, restrict results to a single cell specification within the project. Defaults to None (all cell specs in the project). Returns ------- PaginatedList[ParameterizedModel] A paginated list of parameterized model objects. """ project_id = resolve_project_id(self.client, project_id) endpoint = _build_endpoint( "/parameterized_models", { "project_id": project_id, "cell_spec_id": cell_spec_id, "limit": limit, "offset": offset, }, ) response_data = self.client.get(endpoint) return _parse_list_response(response_data, ParameterizedModel)
[docs] def create(self, cell_spec_id: str, data: dict[str, Any]) -> ParameterizedModel: """Create a new parameterized model for a cell specification. Parameters ---------- cell_spec_id : str The ID of the cell specification to create the parameterized model for. data : dict[str, Any] Dictionary containing the parameterized model data. Required fields: ``name``, ``model_id``. Optional fields: ``description``, ``parameters``, ``simulation_settings``. ``simulation_settings`` is a persistent bag of pybamm simulation kwargs (``var_pts`` / ``submesh_types`` / ``spatial_methods`` / ``solver``) that takes precedence over the base model's settings when this parameterized model is simulated. Pass an ``iws.models.SimulationSettings(...)`` object (serialized for you) or its config dict (see :meth:`ModelClient.create`). Returns ------- ParameterizedModel The newly created parameterized model object. """ endpoint = f"/cells/{cell_spec_id}/parameterized_models" response_data = self.client.post(endpoint, normalize_simulation_settings(data)) return ParameterizedModel(**response_data)
[docs] def create_or_get( self, cell_spec_id: str, data: dict[str, Any] ) -> ParameterizedModel: """Create a new parameterized model or get an existing one. Creates a new parameterized model if one with the same name does not already exist for the cell specification, otherwise returns the existing one. This makes setup scripts safely re-runnable, matching the ``create_or_get`` behaviour of the cell spec, instance, and measurement clients. Parameters ---------- cell_spec_id : str The ID of the cell specification the parameterized model belongs to. data : dict[str, Any] Dictionary containing the parameterized model data, same as :meth:`create`. Returns ------- ParameterizedModel The parameterized model object (newly created or existing). """ try: return self.create(cell_spec_id, data) except IonworksError as e: if e.error_code == "CONFLICT" or e.status_code == 409: # Prefer the existing ID surfaced in the conflict error detail if e.data is not None: detail = e.data.get("detail", {}) existing_id = ( detail.get("existing_id") if isinstance(detail, dict) else None ) if existing_id: return self.get(existing_id) # Fall back to listing and matching by name model_name = data.get("name") if model_name: for model in self.list_by_cell_specification(cell_spec_id): if model.name == model_name: return model raise IonworksError( f"Parameterized model '{model_name}' reported as " "duplicate but could not be found" ) from e raise
[docs] def update( self, cell_spec_id: str, parameterized_model_id: str, data: dict[str, Any], ) -> ParameterizedModel: """Update a parameterized model's name, description, or simulation settings. Parameters ---------- cell_spec_id : str The ID of the cell specification the model belongs to. parameterized_model_id : str The ID of the parameterized model to update. data : dict[str, Any] Dictionary containing the fields to update. Supports ``name``, ``description``, and ``simulation_settings`` (a ``SimulationSettings`` object or its config dict; send ``null`` to clear the persisted settings and fall back to the base model / defaults). Returns ------- ParameterizedModel The updated parameterized model object. """ endpoint = ( f"/cells/{cell_spec_id}/parameterized_models/{parameterized_model_id}" ) response_data = self.client.patch(endpoint, normalize_simulation_settings(data)) return ParameterizedModel(**response_data)
[docs] def get_parameter_values(self, parameterized_model_id: str) -> dict[str, Any]: """Get all parameter values from a parameterized model as JSON. Returns the complete parameter set, suitable for use as baseline parameters in DataFit, parameterization, or optimization workflows. Version and citation metadata are excluded. Parameters ---------- parameterized_model_id : str The ID of the parameterized model to fetch parameters from. Returns ------- dict[str, Any] All parameterized model parameter values as JSON. """ endpoint = f"/parameterized_models/{parameterized_model_id}/parameter-values" return self.client.get(endpoint)
[docs] def get_variable_names(self, parameterized_model_id: str) -> list[str]: """Get scalar variable names from a parameterized model. Returns the names of all variables where the domain is empty or "current collector" (i.e. they are functions of time only). Parameters ---------- parameterized_model_id : str The ID of the parameterized model to fetch variables from. Returns ------- list[str] List of variable names with empty or current collector domain. """ endpoint = f"/parameterized_models/{parameterized_model_id}/variable-names" return self.client.get(endpoint)