Código fuente para qilisdk.utils.classical_solvers.scipy_solver

# Copyright 2026 Qilimanjaro Quantum Tech
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Callable

from qilisdk.core import Model
from qilisdk.core.variables import BaseVariable, BinaryVariable, Domain, RealNumber, Variable
from qilisdk.optimizers import SciPyOptimizer

from .base_solver import ClassicalSolver, ClassicalSolverResult, _assert_real, _variable_bounds


def _decode_value(variable: BaseVariable, parameter: float) -> RealNumber:
    """
    Decode a SciPy parameter back to the corresponding QiliSDK variable value.

    Args:
        variable: The QiliSDK variable.
        parameter: The SciPy parameter value.

    Returns:
        The corresponding QiliSDK variable value.
    """
    lower, upper = _variable_bounds(variable)
    parameter = min(max(parameter, lower), upper)
    if variable.domain in {Domain.INTEGER, Domain.POSITIVE_INTEGER, Domain.BINARY}:
        return round(parameter)
    if variable.domain is Domain.SPIN:
        return 1 if parameter >= 0 else -1
    return parameter


[documentos] class ScipySolver(ClassicalSolver): """Classical solver that uses SciPy to minimize the model's objective. This uses the existing QiliSDK SciPyOptimizer (the one used for variational algorithms) to optimize the model function. Example: .. code-block:: python from qilisdk.core import Model from qilisdk.utils.classical_solvers import ScipySolver model = Model.knapsack(values=[5, 4], weights=[3, 2], max_weight=3) result = ScipySolver(method="l-bfgs-b").solve(model) """ def __init__( self, method: str | Callable | None = None, **kwargs: dict[str, Any], ) -> None: """Create a new SciPy based classical solver instance. Args: method (str | Callable | None, optional): The SciPy optimizer to use. See :class:`SciPyOptimizer` for the full list of supported methods. If not given, SciPy chooses a default local minimizer. Extra Args: Any argument supported by ``scipy.optimize.minimize`` (or the corresponding global optimizer) can be passed and is forwarded to the underlying :class:`SciPyOptimizer`. """
[documentos] self.method = method
[documentos] self.extra_arguments = kwargs
[documentos] def solve(self, model: Model) -> ClassicalSolverResult: """Solve the given model by minimizing its objective with SciPy. Args: model: The ``Model`` instance to solve. Returns: ClassicalSolverResult: the results of the optimization, including the objective value and best solution. Raises: ValueError: if the model contains a variable that is neither a BinaryVariable nor a Variable. """ # Get the list of variables from the model variables = model.variables() # Make sure all variables are either BinaryVariable or Variable for v in variables: if not isinstance(v, (BinaryVariable, Variable)): raise ValueError(f"SciPy solving is not supported for variable {v} of domain {v.domain}.") # Get the bounds for each variable bounds = [_variable_bounds(v) for v in variables] # Convert from SciPy parameters to a sample dict mapping variables to their values def build_sample(parameters: list[float]) -> dict[BaseVariable, RealNumber]: return {v: _decode_value(v, p) for v, p in zip(variables, parameters)} # Evaluate the model for a given set of SciPy parameters def cost_function(parameters: list[float]) -> float: results = model.evaluate(build_sample(parameters)) objective_value = _assert_real(results[model.objective.label]) penalty = sum(_assert_real(results[c.label]) for c in model.constraints) return objective_value + penalty # Run the optimizer optimizer = SciPyOptimizer(method=self.method, **self.extra_arguments) result = optimizer.optimize( cost_function=cost_function, init_parameters=[(lower + upper) / 2 for lower, upper in bounds], bounds=bounds, ) # Return the best results best_sample = build_sample(result.optimal_parameters) return ClassicalSolverResult.from_model(model, best_sample)