Spaces:
Sleeping
Sleeping
| """Model adapter β the single contract between a user's surrogate and SIP. | |
| SIP has NO native support for any model framework. The user wraps their model | |
| (ONNX, PyTorch, sklearn, custom, remote endpoint) in a ``ModelAdapter`` subclass | |
| implementing one method: ``predict(inputs) -> outputs``. This is the sprawl | |
| containment β SIP never imports torch/onnx/sklearn; the user's adapter does. | |
| ``predict`` receives the benchmark inputs (an ``N x D`` array of evaluation | |
| points) and returns a mapping ``{quantity_of_interest: array_of_N_predictions}`` | |
| so SIP can compute per-QoI residuals against the reference set. | |
| """ | |
| from __future__ import annotations | |
| import importlib | |
| import importlib.util | |
| from abc import ABC, abstractmethod | |
| from pathlib import Path | |
| from typing import Any | |
| class ModelAdapter(ABC): | |
| """One contract: turn benchmark inputs into per-QoI surrogate outputs. | |
| Subclasses load their own model (in ``__init__`` or lazily) using whatever | |
| framework they like. SIP only ever calls ``predict``. | |
| """ | |
| def predict(self, inputs: Any) -> dict[str, Any]: | |
| """Return ``{qoi_name: predictions}`` for the given ``inputs``. | |
| ``inputs`` is an ``N x D`` array-like of evaluation points; | |
| ``predictions`` is a length-``N`` array-like per quantity of interest. | |
| """ | |
| raise NotImplementedError | |
| def load_adapter(ref: str) -> ModelAdapter: | |
| """Resolve and instantiate a ``ModelAdapter`` from a reference string. | |
| Accepted forms: | |
| - ``"package.module.ClassName"`` β imported via ``importlib``. | |
| - ``"/path/to/file.py:ClassName"`` β loaded from a source file. | |
| The class is instantiated with no arguments (the adapter loads its own | |
| model). Raises ``ValueError`` if the target is not a ``ModelAdapter`` | |
| subclass, ``ModuleNotFoundError``/``AttributeError`` if it can't be found. | |
| """ | |
| cls = _resolve_class(ref) | |
| if not (isinstance(cls, type) and issubclass(cls, ModelAdapter)): | |
| raise ValueError( | |
| f"{ref!r} does not resolve to a ModelAdapter subclass " | |
| f"(got {cls!r}). Subclass uofa_cli.interrogate.adapter.ModelAdapter." | |
| ) | |
| return cls() | |
| def _resolve_class(ref: str): | |
| if ":" in ref and (ref.endswith(".py") or "/" in ref.split(":", 1)[0]): | |
| file_part, _, class_name = ref.partition(":") | |
| path = Path(file_part).expanduser().resolve() | |
| if not path.is_file(): | |
| raise FileNotFoundError(f"Adapter file not found: {path}") | |
| spec = importlib.util.spec_from_file_location(path.stem, path) | |
| if spec is None or spec.loader is None: | |
| raise ImportError(f"Could not load adapter module from {path}") | |
| module = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(module) | |
| return getattr(module, class_name) | |
| module_path, _, class_name = ref.rpartition(".") | |
| if not module_path: | |
| raise ValueError( | |
| f"Adapter ref {ref!r} must be 'pkg.module.ClassName' or " | |
| f"'/path/to/file.py:ClassName'." | |
| ) | |
| module = importlib.import_module(module_path) | |
| return getattr(module, class_name) | |