Source code for sparho.adapters.callable

"""Wrap an arbitrary ``(problem, hp) -> SolverResult`` callable as a Solver."""

from __future__ import annotations

import inspect
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any

from ..core.types import Array, Hyperparam
from ..problem import Problem
from ..state import SolverResult

_SolverFn = Callable[..., SolverResult]


def _accepts_kw(fn: Callable[..., Any], name: str) -> bool:
    """Return ``True`` if ``fn``'s signature accepts a keyword argument ``name``."""
    try:
        sig = inspect.signature(fn)
    except (TypeError, ValueError):
        return False
    for param in sig.parameters.values():
        if param.kind == inspect.Parameter.VAR_KEYWORD:
            return True
        if param.name == name and param.kind in (
            inspect.Parameter.KEYWORD_ONLY,
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
        ):
            return True
    return False


@dataclass(frozen=True, slots=True)
class _CallableSolver:
    fn: _SolverFn
    name: str
    _accepts_x0: bool = field(default=False, compare=False, repr=False, hash=False)
    _accepts_tol: bool = field(default=False, compare=False, repr=False, hash=False)

    def __call__(
        self,
        problem: Problem,
        hyperparam: Hyperparam,
        /,
        *,
        x0: Array | None = None,
        tol: float | None = None,
    ) -> SolverResult:
        kwargs: dict[str, Any] = {}
        if self._accepts_x0 and x0 is not None:
            kwargs["x0"] = x0
        if self._accepts_tol and tol is not None:
            kwargs["tol"] = tol
        return self.fn(problem, hyperparam, **kwargs)


[docs] def as_solver(fn: _SolverFn, *, name: str = "<callable>") -> _CallableSolver: """Wrap ``fn`` as a Solver. The wrapper is frozen so it can be hashed and reprd. Optional Solver-protocol kwargs (``x0`` for warm-start, ``tol`` for an inner-tolerance override) are forwarded only when ``fn`` declares them in its signature. A plain ``(problem, hp) -> SolverResult`` callable keeps working — the wrapper silently drops kwargs the function doesn't accept. """ return _CallableSolver( fn=fn, name=name, _accepts_x0=_accepts_kw(fn, "x0"), _accepts_tol=_accepts_kw(fn, "tol"), )