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 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)