Source code for qilisdk.functionals.variational_program

# Copyright 2025 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 __future__ import annotations

import functools
import operator
from typing import TYPE_CHECKING

from qilisdk.core.variables import BaseVariable, Parameter
from qilisdk.functionals.functional import Functional, PrimitiveFunctional
from qilisdk.yaml import yaml

if TYPE_CHECKING:
    from qilisdk.core.variables import ComparisonTerm
    from qilisdk.cost_functions.cost_function import CostFunction
    from qilisdk.optimizers.optimizer import Optimizer


@yaml.register_class
[docs] class VariationalProgram(Functional): """ Bundle a parameterized functional, optimizer, and cost function into a variational loop. Example: .. code-block:: python program = VariationalProgram(functional, optimizer, cost_function) """ def __init__( self, functional: PrimitiveFunctional, optimizer: Optimizer, cost_function: CostFunction, store_intermediate_results: bool = False, parameter_constraints: list[ComparisonTerm] | None = None, ) -> None: """ Args: functional (PrimitiveFunctional): Parameterized functional to optimize. optimizer (Optimizer): Optimization routine controlling parameter updates. cost_function (CostFunction): Metric used to evaluate functional executions. store_intermediate_results (bool): Persist intermediate executions if requested by the optimizer. Defaults to False. parameter_constraints (list[ComparisonTerm] | None): Optional constraints on parameter values that are enforced before optimizer updates are applied. Defaults to None. Raises: ValueError: if the user applies constraints on parameters that are not present in the variational program. Or the constraints contain Objects that are not parameters. """ self._functional = functional self._optimizer = optimizer self._cost_function = cost_function self._store_intermediate_results = store_intermediate_results parameter_constraints = parameter_constraints or [] functional_params = self._functional.get_parameters() for p in parameter_constraints: if not p.lhs.is_parameterized_term() or not p.rhs.is_parameterized_term(): raise ValueError("Only parameters are allowed to be constrained.") variables = p.variables() for v in variables: if v.label not in functional_params: raise ValueError( f"Writing a constraint on the parameter ({v}) that is not present in the variational program " ) self._parameter_constraints = parameter_constraints @property
[docs] def functional(self) -> PrimitiveFunctional: """Parameterized functional that will be optimised.""" return self._functional
@property
[docs] def optimizer(self) -> Optimizer: """Optimizer responsible for parameter updates.""" return self._optimizer
@property
[docs] def cost_function(self) -> CostFunction: """Cost function applied to functional results.""" return self._cost_function
@property
[docs] def store_intermediate_results(self) -> bool: """Whether intermediate execution data should be stored.""" return self._store_intermediate_results
[docs] def get_constraints(self) -> list[ComparisonTerm]: """Return variational-program-level constraints plus those from the underlying functional. Returns: list[ComparisonTerm]: Combined list of constraints from the program and the wrapped functional. """ return self._parameter_constraints + self._functional.get_constraints()
def _check_constraints(self, parameters: dict[str, float]) -> list[bool]: """Evaluate each constraint with a proposed parameter set. Args: parameters (dict[str, float]): Proposed parameter values keyed by label. Returns: list[bool]: List of booleans indicating whether each constraint is satisfied. Raises: ValueError: If a parameter label is not defined in the underlying functional or if a constraint references a non-parameter variable. """ params: list[BaseVariable] = functools.reduce( operator.iadd, (con.variables() for con in self.get_constraints()), [] ) params = list(set(params)) if any(not isinstance(p, Parameter) for p in params): raise ValueError("Only Parameters are allowed.") params_dict = {p.label: p for p in params} evaluate_dict: dict[BaseVariable, float] = {} functional_params = self._functional.get_parameters() for label, value in parameters.items(): if label not in functional_params: raise ValueError(f"Parameter {label} is not defined in the functional.") if label in params_dict: evaluate_dict[params_dict[label]] = value constraints = self.get_constraints() return [con.evaluate(evaluate_dict) for con in constraints]
[docs] def check_parameter_constraints(self, parameters: dict[str, float]) -> int: """Return a penalty score indicating how many constraints are violated. Each violated constraint adds 100 to the returned score; a fully valid parameter set returns 0. Args: parameters (dict[str, float]): Proposed parameter values keyed by label. Returns: int: Cumulative penalty (0 when all constraints are satisfied). """ const_list = self._check_constraints(parameters) return sum((100 if not con else 0) for con in const_list)