luna-quantum 1.0.0__cp311-cp311-macosx_10_12_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of luna-quantum might be problematic. Click here for more details.
- luna_quantum/__init__.py +96 -0
- luna_quantum/__init__.pyi +68 -0
- luna_quantum/_core.cpython-311-darwin.so +0 -0
- luna_quantum/_core.pyi +3017 -0
- luna_quantum/aqm_overwrites/__init__.py +3 -0
- luna_quantum/aqm_overwrites/model.py +184 -0
- luna_quantum/client/__init__.py +0 -0
- luna_quantum/client/controllers/__init__.py +4 -0
- luna_quantum/client/controllers/luna_http_client.py +37 -0
- luna_quantum/client/controllers/luna_platform_client.py +153 -0
- luna_quantum/client/controllers/luna_q.py +62 -0
- luna_quantum/client/controllers/luna_solve.py +125 -0
- luna_quantum/client/error/__init__.py +0 -0
- luna_quantum/client/error/luna_api_key_invalid_error.py +10 -0
- luna_quantum/client/error/luna_api_key_missing_error.py +10 -0
- luna_quantum/client/error/luna_error.py +2 -0
- luna_quantum/client/error/luna_server_error.py +20 -0
- luna_quantum/client/error/timeout_error.py +12 -0
- luna_quantum/client/error/transformation_error.py +18 -0
- luna_quantum/client/error/utils/__init__.py +0 -0
- luna_quantum/client/error/utils/http_error_utils.py +112 -0
- luna_quantum/client/interfaces/__init__.py +4 -0
- luna_quantum/client/interfaces/clients/__init__.py +25 -0
- luna_quantum/client/interfaces/clients/circuit_rest_client_i.py +68 -0
- luna_quantum/client/interfaces/clients/info_rest_client_i.py +53 -0
- luna_quantum/client/interfaces/clients/model_rest_client_i.py +139 -0
- luna_quantum/client/interfaces/clients/qpu_token_rest_client_i.py +364 -0
- luna_quantum/client/interfaces/clients/rest_client_i.py +21 -0
- luna_quantum/client/interfaces/clients/solve_job_rest_client_i.py +201 -0
- luna_quantum/client/interfaces/clients/users_rest_client_i.py +29 -0
- luna_quantum/client/interfaces/services/__init__.py +0 -0
- luna_quantum/client/interfaces/services/luna_q_i.py +34 -0
- luna_quantum/client/interfaces/services/luna_solve_i.py +72 -0
- luna_quantum/client/interfaces/services/service_i.py +36 -0
- luna_quantum/client/rest_client/__init__.py +15 -0
- luna_quantum/client/rest_client/circuit_rest_client.py +107 -0
- luna_quantum/client/rest_client/info_rest_client.py +76 -0
- luna_quantum/client/rest_client/model_rest_client.py +216 -0
- luna_quantum/client/rest_client/qpu_token_rest_client.py +504 -0
- luna_quantum/client/rest_client/solve_job_rest_client.py +286 -0
- luna_quantum/client/rest_client/users_rest_client.py +35 -0
- luna_quantum/client/schemas/__init__.py +26 -0
- luna_quantum/client/schemas/circuit.py +49 -0
- luna_quantum/client/schemas/create/__init__.py +6 -0
- luna_quantum/client/schemas/create/circuit.py +31 -0
- luna_quantum/client/schemas/create/optimization.py +39 -0
- luna_quantum/client/schemas/create/qpu_token.py +25 -0
- luna_quantum/client/schemas/create/qpu_token_time_quota.py +29 -0
- luna_quantum/client/schemas/create/qubo.py +19 -0
- luna_quantum/client/schemas/create/solve_job_create.py +43 -0
- luna_quantum/client/schemas/enums/__init__.py +0 -0
- luna_quantum/client/schemas/enums/call_style.py +13 -0
- luna_quantum/client/schemas/enums/circuit.py +42 -0
- luna_quantum/client/schemas/enums/model_format.py +11 -0
- luna_quantum/client/schemas/enums/problem.py +50 -0
- luna_quantum/client/schemas/enums/qpu_token_type.py +20 -0
- luna_quantum/client/schemas/enums/sense.py +8 -0
- luna_quantum/client/schemas/enums/status.py +40 -0
- luna_quantum/client/schemas/enums/timeframe.py +11 -0
- luna_quantum/client/schemas/error_message.py +14 -0
- luna_quantum/client/schemas/model_metadata.py +35 -0
- luna_quantum/client/schemas/qpu_token/__init__.py +0 -0
- luna_quantum/client/schemas/qpu_token/qpu_token.py +161 -0
- luna_quantum/client/schemas/qpu_token/qpu_token_source.py +19 -0
- luna_quantum/client/schemas/qpu_token/qpu_token_time_quota.py +28 -0
- luna_quantum/client/schemas/qpu_token/token_provider.py +135 -0
- luna_quantum/client/schemas/representation.py +19 -0
- luna_quantum/client/schemas/solution.py +106 -0
- luna_quantum/client/schemas/solve_job.py +47 -0
- luna_quantum/client/schemas/solver_info.py +11 -0
- luna_quantum/client/schemas/user.py +11 -0
- luna_quantum/client/schemas/wrappers/__init__.py +5 -0
- luna_quantum/client/schemas/wrappers/datetime_wrapper.py +32 -0
- luna_quantum/client/utils/__init__.py +0 -0
- luna_quantum/client/utils/qpu_token_utils.py +147 -0
- luna_quantum/errors.py +1 -0
- luna_quantum/errors.pyi +202 -0
- luna_quantum/exceptions/__init__.py +0 -0
- luna_quantum/exceptions/base_luna_quantum_error.py +2 -0
- luna_quantum/exceptions/patch_class_field_exists_error.py +10 -0
- luna_quantum/factories/__init__.py +4 -0
- luna_quantum/factories/luna_solve_client_factory.py +75 -0
- luna_quantum/factories/usecase_factory.py +457 -0
- luna_quantum/py.typed +0 -0
- luna_quantum/solve/__init__.py +13 -0
- luna_quantum/solve/default_token.py +304 -0
- luna_quantum/solve/domain/__init__.py +0 -0
- luna_quantum/solve/domain/abstract/__init__.py +4 -0
- luna_quantum/solve/domain/abstract/luna_algorithm.py +203 -0
- luna_quantum/solve/domain/abstract/qpu_token_backend.py +34 -0
- luna_quantum/solve/domain/model_metadata.py +54 -0
- luna_quantum/solve/domain/solve_job.py +187 -0
- luna_quantum/solve/errors/__init__.py +0 -0
- luna_quantum/solve/errors/incompatible_backend_error.py +15 -0
- luna_quantum/solve/errors/model_metadata_missing_error.py +11 -0
- luna_quantum/solve/errors/solve_base_error.py +5 -0
- luna_quantum/solve/errors/token_missing_error.py +11 -0
- luna_quantum/solve/interfaces/__init__.py +0 -0
- luna_quantum/solve/interfaces/algorithm_i.py +47 -0
- luna_quantum/solve/interfaces/backend_i.py +28 -0
- luna_quantum/solve/interfaces/usecases/__init__.py +55 -0
- luna_quantum/solve/interfaces/usecases/model_delete_usecase_i.py +27 -0
- luna_quantum/solve/interfaces/usecases/model_fetch_metadata_usecase_i.py +33 -0
- luna_quantum/solve/interfaces/usecases/model_get_solutions_usecase_i.py +33 -0
- luna_quantum/solve/interfaces/usecases/model_get_solve_jobs_usecase_i.py +33 -0
- luna_quantum/solve/interfaces/usecases/model_load_by_id_usecase_i.py +32 -0
- luna_quantum/solve/interfaces/usecases/model_load_by_metadata_usecase_i.py +37 -0
- luna_quantum/solve/interfaces/usecases/model_load_metadata_by_hash_usecase_i.py +38 -0
- luna_quantum/solve/interfaces/usecases/model_save_usecase_i.py +36 -0
- luna_quantum/solve/interfaces/usecases/solve_job_cancel_usecase_i.py +33 -0
- luna_quantum/solve/interfaces/usecases/solve_job_create_usecase_i.py +44 -0
- luna_quantum/solve/interfaces/usecases/solve_job_delete_usecase_i.py +32 -0
- luna_quantum/solve/interfaces/usecases/solve_job_fetch_updates_usecase_i.py +38 -0
- luna_quantum/solve/interfaces/usecases/solve_job_get_result_usecase_i.py +63 -0
- luna_quantum/solve/parameters/__init__.py +0 -0
- luna_quantum/solve/parameters/algorithms/__init__.py +49 -0
- luna_quantum/solve/parameters/algorithms/base_params/__init__.py +24 -0
- luna_quantum/solve/parameters/algorithms/base_params/decomposer.py +57 -0
- luna_quantum/solve/parameters/algorithms/base_params/qaoa_circuit_params.py +95 -0
- luna_quantum/solve/parameters/algorithms/base_params/quantum_annealing_params.py +78 -0
- luna_quantum/solve/parameters/algorithms/base_params/scipy_optimizer.py +120 -0
- luna_quantum/solve/parameters/algorithms/base_params/simulated_annealing_params.py +106 -0
- luna_quantum/solve/parameters/algorithms/base_params/tabu_kerberos_params.py +39 -0
- luna_quantum/solve/parameters/algorithms/base_params/tabu_search_params.py +129 -0
- luna_quantum/solve/parameters/algorithms/flexible_parameter_algorithm.py +59 -0
- luna_quantum/solve/parameters/algorithms/genetic_algorithms/__init__.py +4 -0
- luna_quantum/solve/parameters/algorithms/genetic_algorithms/qaga.py +131 -0
- luna_quantum/solve/parameters/algorithms/genetic_algorithms/saga.py +139 -0
- luna_quantum/solve/parameters/algorithms/optimization_solvers/__init__.py +3 -0
- luna_quantum/solve/parameters/algorithms/optimization_solvers/scip.py +51 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/__init__.py +19 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/kerberos.py +149 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/leap_hybrid_bqm.py +75 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/leap_hybrid_cqm.py +75 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/parallel_tempering_qpu.py +139 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/population_annealing_qpu.py +109 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/qbsolv_like_qpu.py +111 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/quantum_annealing.py +105 -0
- luna_quantum/solve/parameters/algorithms/quantum_annealing/repeated_reverse_quantum_annealing.py +174 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/__init__.py +6 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/flex_qaoa/__init__.py +26 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/flex_qaoa/config.py +80 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/flex_qaoa/flex_qaoa.py +226 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/flex_qaoa/optimizers.py +97 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/flex_qaoa/pipeline.py +87 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/qaoa.py +104 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/qaoa_fo.py +69 -0
- luna_quantum/solve/parameters/algorithms/quantum_gate/vqe.py +109 -0
- luna_quantum/solve/parameters/algorithms/search_algorithms/__init__.py +5 -0
- luna_quantum/solve/parameters/algorithms/search_algorithms/dialectic_search.py +152 -0
- luna_quantum/solve/parameters/algorithms/search_algorithms/qbsolv_like_tabu_search.py +117 -0
- luna_quantum/solve/parameters/algorithms/search_algorithms/tabu_search.py +126 -0
- luna_quantum/solve/parameters/algorithms/simulated_annealing/__init__.py +13 -0
- luna_quantum/solve/parameters/algorithms/simulated_annealing/parallel_tempering.py +131 -0
- luna_quantum/solve/parameters/algorithms/simulated_annealing/population_annealing.py +95 -0
- luna_quantum/solve/parameters/algorithms/simulated_annealing/qbsolv_like_simulated_annealing.py +141 -0
- luna_quantum/solve/parameters/algorithms/simulated_annealing/repeated_reverse_simulated_annealing.py +172 -0
- luna_quantum/solve/parameters/algorithms/simulated_annealing/simulated_annealing.py +126 -0
- luna_quantum/solve/parameters/backends/__init__.py +20 -0
- luna_quantum/solve/parameters/backends/aqarios.py +17 -0
- luna_quantum/solve/parameters/backends/aws/__init__.py +11 -0
- luna_quantum/solve/parameters/backends/aws/aws.py +36 -0
- luna_quantum/solve/parameters/backends/aws/aws_backend_base.py +74 -0
- luna_quantum/solve/parameters/backends/aws/ionq.py +43 -0
- luna_quantum/solve/parameters/backends/aws/iqm.py +31 -0
- luna_quantum/solve/parameters/backends/aws/rigetti.py +31 -0
- luna_quantum/solve/parameters/backends/dwave.py +17 -0
- luna_quantum/solve/parameters/backends/dwave_qpu.py +164 -0
- luna_quantum/solve/parameters/backends/ibm.py +132 -0
- luna_quantum/solve/parameters/backends/qctrl.py +130 -0
- luna_quantum/solve/parameters/backends/zib.py +17 -0
- luna_quantum/solve/parameters/constants.py +11 -0
- luna_quantum/solve/parameters/mixins/__init__.py +0 -0
- luna_quantum/solve/parameters/mixins/fujitsu_common_params_mixin.py +239 -0
- luna_quantum/solve/parameters/mixins/fujitsu_v2_mixin.py +70 -0
- luna_quantum/solve/parameters/mixins/qbsolv_like_mixin.py +60 -0
- luna_quantum/solve/use_cases/__init__.py +119 -0
- luna_quantum/solve/use_cases/arbitrage_edge_based.py +50 -0
- luna_quantum/solve/use_cases/arbitrage_node_based.py +55 -0
- luna_quantum/solve/use_cases/base.py +7 -0
- luna_quantum/solve/use_cases/binary_integer_linear_programming.py +54 -0
- luna_quantum/solve/use_cases/binary_paint_shop_problem.py +37 -0
- luna_quantum/solve/use_cases/credit_scoring_feature_selection.py +40 -0
- luna_quantum/solve/use_cases/dynamic_portfolio_optimization.py +64 -0
- luna_quantum/solve/use_cases/exact_cover.py +51 -0
- luna_quantum/solve/use_cases/flight_gate_assignment.py +79 -0
- luna_quantum/solve/use_cases/graph_coloring.py +42 -0
- luna_quantum/solve/use_cases/graph_isomorphism.py +52 -0
- luna_quantum/solve/use_cases/graph_partitioning.py +46 -0
- luna_quantum/solve/use_cases/hamiltonian_cycle.py +49 -0
- luna_quantum/solve/use_cases/induced_subgraph_isomorphism.py +50 -0
- luna_quantum/solve/use_cases/job_shop_scheduling.py +44 -0
- luna_quantum/solve/use_cases/k_medoids_clustering.py +49 -0
- luna_quantum/solve/use_cases/knapsack_integer_weights.py +56 -0
- luna_quantum/solve/use_cases/linear_regression.py +60 -0
- luna_quantum/solve/use_cases/lmwcs.py +84 -0
- luna_quantum/solve/use_cases/longest_path.py +50 -0
- luna_quantum/solve/use_cases/market_graph_clustering.py +61 -0
- luna_quantum/solve/use_cases/max2sat.py +54 -0
- luna_quantum/solve/use_cases/max3sat.py +55 -0
- luna_quantum/solve/use_cases/max_clique.py +60 -0
- luna_quantum/solve/use_cases/max_cut.py +48 -0
- luna_quantum/solve/use_cases/max_independent_set.py +37 -0
- luna_quantum/solve/use_cases/minimal_maximal_matching.py +54 -0
- luna_quantum/solve/use_cases/minimal_spanning_tree.py +90 -0
- luna_quantum/solve/use_cases/minimum_vertex_cover.py +45 -0
- luna_quantum/solve/use_cases/number_partitioning.py +32 -0
- luna_quantum/solve/use_cases/portfolio_optimization.py +46 -0
- luna_quantum/solve/use_cases/portfolio_optimization_ib_tv.py +63 -0
- luna_quantum/solve/use_cases/quadratic_assignment.py +49 -0
- luna_quantum/solve/use_cases/quadratic_knapsack.py +48 -0
- luna_quantum/solve/use_cases/satellite_scheduling.py +73 -0
- luna_quantum/solve/use_cases/sensor_placement.py +58 -0
- luna_quantum/solve/use_cases/set_cover.py +56 -0
- luna_quantum/solve/use_cases/set_packing.py +54 -0
- luna_quantum/solve/use_cases/set_partitioning.py +52 -0
- luna_quantum/solve/use_cases/subgraph_isomorphism.py +55 -0
- luna_quantum/solve/use_cases/subset_sum.py +37 -0
- luna_quantum/solve/use_cases/support_vector_machine.py +64 -0
- luna_quantum/solve/use_cases/traffic_flow.py +35 -0
- luna_quantum/solve/use_cases/travelling_salesman_problem.py +53 -0
- luna_quantum/solve/use_cases/type_aliases.py +9 -0
- luna_quantum/solve/use_cases/weighted_max_cut.py +37 -0
- luna_quantum/solve/usecases/__init__.py +45 -0
- luna_quantum/solve/usecases/model_delete_usecase.py +49 -0
- luna_quantum/solve/usecases/model_fetch_metadata_usecase.py +50 -0
- luna_quantum/solve/usecases/model_get_solution_usecase.py +56 -0
- luna_quantum/solve/usecases/model_get_solve_jobs_usecase.py +62 -0
- luna_quantum/solve/usecases/model_load_by_id_usecase.py +47 -0
- luna_quantum/solve/usecases/model_load_by_metadata_usecase.py +52 -0
- luna_quantum/solve/usecases/model_load_metadata_by_hash_usecase.py +51 -0
- luna_quantum/solve/usecases/model_save_usecase.py +63 -0
- luna_quantum/solve/usecases/solve_job_cancel_usecase.py +51 -0
- luna_quantum/solve/usecases/solve_job_create_usecase.py +112 -0
- luna_quantum/solve/usecases/solve_job_delete_usecase.py +38 -0
- luna_quantum/solve/usecases/solve_job_fetch_updates_usecase.py +49 -0
- luna_quantum/solve/usecases/solve_job_get_result_usecase.py +97 -0
- luna_quantum/translator.py +1 -0
- luna_quantum/translator.pyi +833 -0
- luna_quantum/util/__init__.py +0 -0
- luna_quantum/util/active_waiting.py +79 -0
- luna_quantum/util/class_patcher.py +164 -0
- luna_quantum/util/log_utils.py +167 -0
- luna_quantum/util/pretty_base.py +67 -0
- luna_quantum/util/pydantic_utils.py +38 -0
- luna_quantum/utils.py +54 -0
- luna_quantum/utils.pyi +35 -0
- luna_quantum-1.0.0.dist-info/METADATA +37 -0
- luna_quantum-1.0.0.dist-info/RECORD +252 -0
- luna_quantum-1.0.0.dist-info/WHEEL +4 -0
- luna_quantum-1.0.0.dist-info/licenses/LICENSE +176 -0
- luna_quantum-1.0.0.dist-info/licenses/NOTICE +13 -0
luna_quantum/_core.pyi
ADDED
|
@@ -0,0 +1,3017 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any, overload
|
|
4
|
+
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
from . import exceptions, translator
|
|
8
|
+
from .client.interfaces.services.luna_solve_i import ILunaSolve
|
|
9
|
+
from .solve import ModelMetadata
|
|
10
|
+
from .solve.domain.solve_job import SolveJob
|
|
11
|
+
|
|
12
|
+
# _variable.pyi
|
|
13
|
+
class Vtype(Enum):
|
|
14
|
+
"""
|
|
15
|
+
Enumeration of variable types supported by the optimization system.
|
|
16
|
+
|
|
17
|
+
This enum defines the type of a variable used in a model. The type influences
|
|
18
|
+
the domain and behavior of the variable during optimization. It is often passed
|
|
19
|
+
when defining variables to specify how they should behave.
|
|
20
|
+
|
|
21
|
+
Attributes
|
|
22
|
+
----------
|
|
23
|
+
Real : Vtype
|
|
24
|
+
Continuous real-valued variable. Can take any value within given bounds.
|
|
25
|
+
Integer : Vtype
|
|
26
|
+
Discrete integer-valued variable. Takes integer values within bounds.
|
|
27
|
+
Binary : Vtype
|
|
28
|
+
Binary variable. Can only take values 0 or 1.
|
|
29
|
+
Spin : Vtype
|
|
30
|
+
Spin variable. Can only take values -1 or +1.
|
|
31
|
+
|
|
32
|
+
Examples
|
|
33
|
+
--------
|
|
34
|
+
>>> from luna_quantum import Vtype
|
|
35
|
+
>>> Vtype.Real
|
|
36
|
+
Real
|
|
37
|
+
|
|
38
|
+
>>> str(Vtype.Binary)
|
|
39
|
+
'Binary'
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
Real = ...
|
|
43
|
+
"""Continuous real-valued variable. Can take any value within given bounds."""
|
|
44
|
+
Integer = ...
|
|
45
|
+
"""Discrete integer-valued variable. Takes integer values within bounds."""
|
|
46
|
+
Binary = ...
|
|
47
|
+
"""Binary variable. Can only take values 0 or 1."""
|
|
48
|
+
|
|
49
|
+
Spin = ...
|
|
50
|
+
"""Spin variable. Can only take values -1 or +1."""
|
|
51
|
+
|
|
52
|
+
class Unbounded: ...
|
|
53
|
+
|
|
54
|
+
class Bounds:
|
|
55
|
+
"""
|
|
56
|
+
Represents bounds for a variable (only supported for real and integer variables).
|
|
57
|
+
|
|
58
|
+
A `Bounds` object defines the valid interval for a variable. Bounds are inclusive,
|
|
59
|
+
and can be partially specified by providing only a lower or upper limit. If neither
|
|
60
|
+
is specified, the variable is considered unbounded.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
lower : float, optional
|
|
65
|
+
Lower bound of the variable. Defaults to negative infinity if not specified.
|
|
66
|
+
upper : float, optional
|
|
67
|
+
Upper bound of the variable. Defaults to positive infinity if not specified.
|
|
68
|
+
|
|
69
|
+
Examples
|
|
70
|
+
--------
|
|
71
|
+
>>> from luna_quantum import Bounds
|
|
72
|
+
>>> Bounds(-1.0, 1.0)
|
|
73
|
+
Bounds { lower: -1, upper: 1 }
|
|
74
|
+
|
|
75
|
+
>>> Bounds(lower=0.0)
|
|
76
|
+
Bounds { lower: -1, upper: unlimited }
|
|
77
|
+
|
|
78
|
+
>>> Bounds(upper=10.0)
|
|
79
|
+
Bounds { lower: unlimited, upper: 1 }
|
|
80
|
+
|
|
81
|
+
Notes
|
|
82
|
+
-----
|
|
83
|
+
- Bounds are only meaningful for variables of type `Vtype.Real` or `Vtype.Integer`.
|
|
84
|
+
- If both bounds are omitted, the variable is unbounded.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
@overload
|
|
88
|
+
def __init__(self, /, *, lower: float | Unbounded) -> None: ...
|
|
89
|
+
@overload
|
|
90
|
+
def __init__(self, /, *, upper: float | type[Unbounded]) -> None: ...
|
|
91
|
+
@overload
|
|
92
|
+
def __init__(
|
|
93
|
+
self, /, lower: float | type[Unbounded], upper: float | type[Unbounded]
|
|
94
|
+
) -> None: ...
|
|
95
|
+
@overload
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
/,
|
|
99
|
+
lower: float | type[Unbounded] | None = ...,
|
|
100
|
+
upper: float | type[Unbounded] | None = ...,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Create bounds for a variable.
|
|
104
|
+
|
|
105
|
+
See class-level docstring for full documentation.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def lower(self, /) -> float | Unbounded | None:
|
|
110
|
+
"""Get the lower bound"""
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def upper(self, /) -> float | Unbounded | None:
|
|
114
|
+
"""Get the upper bound"""
|
|
115
|
+
|
|
116
|
+
class Variable:
|
|
117
|
+
"""
|
|
118
|
+
Represents a symbolic variable within an optimization environment.
|
|
119
|
+
|
|
120
|
+
A `Variable` is the fundamental building block of algebraic expressions
|
|
121
|
+
used in optimization models. Each variable is tied to an `Environment`
|
|
122
|
+
which scopes its lifecycle and expression context. Variables can be
|
|
123
|
+
typed and optionally bounded.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
name : str
|
|
128
|
+
The name of the variable.
|
|
129
|
+
vtype : Vtype, optional
|
|
130
|
+
The variable type (e.g., `Vtype.Real`, `Vtype.Integer`, etc.).
|
|
131
|
+
Defaults to `Vtype.Binary`.
|
|
132
|
+
bounds : Bounds, optional
|
|
133
|
+
Bounds restricting the range of the variable. Only applicable for
|
|
134
|
+
`Real` and `Integer` variables.
|
|
135
|
+
env : Environment, optional
|
|
136
|
+
The environment in which this variable is created. If not provided,
|
|
137
|
+
the current environment from the context manager is used.
|
|
138
|
+
|
|
139
|
+
Examples
|
|
140
|
+
--------
|
|
141
|
+
>>> from luna_quantum import Variable, Environment, Vtype, Bounds
|
|
142
|
+
>>> with Environment():
|
|
143
|
+
... x = Variable("x")
|
|
144
|
+
... y = Variable("y", vtype=Vtype.Integer, bounds=Bounds(0, 5))
|
|
145
|
+
... expr = 2 * x + y - 1
|
|
146
|
+
|
|
147
|
+
Arithmetic Overloads
|
|
148
|
+
--------------------
|
|
149
|
+
Variables support standard arithmetic operations:
|
|
150
|
+
|
|
151
|
+
- Addition: `x + y`, `x + 2`, `2 + x`
|
|
152
|
+
- Subtraction: `x - y`, `3 - x`
|
|
153
|
+
- Multiplication: `x * y`, `2 * x`, `x * 2`
|
|
154
|
+
|
|
155
|
+
All expressions return `Expression` objects and preserve symbolic structure.
|
|
156
|
+
|
|
157
|
+
Notes
|
|
158
|
+
-----
|
|
159
|
+
- A `Variable` is bound to a specific `Environment` instance.
|
|
160
|
+
- Variables are immutable; all operations yield new `Expression` objects.
|
|
161
|
+
- Variables carry their environment, but the environment does not own the variable.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
@overload
|
|
165
|
+
def __init__(self, /, name: str) -> None: ...
|
|
166
|
+
@overload
|
|
167
|
+
def __init__(self, /, name: str, *, env: Environment) -> None: ...
|
|
168
|
+
@overload
|
|
169
|
+
def __init__(self, /, name: str, *, vtype: Vtype) -> None: ...
|
|
170
|
+
@overload
|
|
171
|
+
def __init__(self, /, name: str, *, vtype: Vtype, bounds: Bounds) -> None: ...
|
|
172
|
+
@overload
|
|
173
|
+
def __init__(
|
|
174
|
+
self, /, name: str, *, vtype: Vtype, bounds: Bounds, env: Environment
|
|
175
|
+
) -> None: ...
|
|
176
|
+
def __init__(
|
|
177
|
+
self,
|
|
178
|
+
/,
|
|
179
|
+
name: str,
|
|
180
|
+
*,
|
|
181
|
+
vtype: Vtype | None = ...,
|
|
182
|
+
bounds: Bounds | None = ...,
|
|
183
|
+
env: Environment | None = ...,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Initialize a new Variable.
|
|
187
|
+
|
|
188
|
+
See class-level docstring for full usage.
|
|
189
|
+
|
|
190
|
+
Raises
|
|
191
|
+
------
|
|
192
|
+
NoActiveEnvironmentFoundError
|
|
193
|
+
If no active environment is found and none is explicitly provided.
|
|
194
|
+
VariableExistsError
|
|
195
|
+
If a variable with the same name already exists in the environment.
|
|
196
|
+
VariableCreationError
|
|
197
|
+
If the variable is tried to be created with incompatible bounds.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def name(self, /) -> str:
|
|
202
|
+
"""Get the name of the variable."""
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def bounds(self, /) -> Bounds:
|
|
206
|
+
"""Get the bounds of the variable."""
|
|
207
|
+
|
|
208
|
+
@overload
|
|
209
|
+
def __add__(self, other: int, /) -> Expression: ...
|
|
210
|
+
@overload
|
|
211
|
+
def __add__(self, other: float, /) -> Expression: ...
|
|
212
|
+
@overload
|
|
213
|
+
def __add__(self, other: Variable, /) -> Expression: ...
|
|
214
|
+
@overload
|
|
215
|
+
def __add__(self, other: Expression, /) -> Expression: ...
|
|
216
|
+
def __add__(self, other: float | Variable | Expression, /) -> Expression:
|
|
217
|
+
"""
|
|
218
|
+
Add this variable to another value.
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
other : int, float, Variable or Expression.
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
Expression
|
|
227
|
+
The resulting symbolic expression.
|
|
228
|
+
|
|
229
|
+
Raises
|
|
230
|
+
------
|
|
231
|
+
VariablesFromDifferentEnvsError
|
|
232
|
+
If the operands belong to different environments.
|
|
233
|
+
TypeError
|
|
234
|
+
If the operand type is unsupported.
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
@overload
|
|
238
|
+
def __radd__(self, other: int, /) -> Expression: ...
|
|
239
|
+
@overload
|
|
240
|
+
def __radd__(self, other: float, /) -> Expression: ...
|
|
241
|
+
@overload
|
|
242
|
+
def __radd__(self, other: Variable, /) -> Expression: ...
|
|
243
|
+
@overload
|
|
244
|
+
def __radd__(self, other: Expression, /) -> Expression: ...
|
|
245
|
+
def __radd__(self, other: float | Variable | Expression, /) -> Expression:
|
|
246
|
+
"""
|
|
247
|
+
Right-hand addition.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
other : int, float, Variable or Expression.
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
Expression
|
|
256
|
+
The resulting symbolic expression.
|
|
257
|
+
|
|
258
|
+
Raises
|
|
259
|
+
------
|
|
260
|
+
TypeError
|
|
261
|
+
If the operand type is unsupported.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
@overload
|
|
265
|
+
def __sub__(self, other: int, /) -> Expression: ...
|
|
266
|
+
@overload
|
|
267
|
+
def __sub__(self, other: float, /) -> Expression: ...
|
|
268
|
+
@overload
|
|
269
|
+
def __sub__(self, other: Variable, /) -> Expression: ...
|
|
270
|
+
@overload
|
|
271
|
+
def __sub__(self, other: Expression, /) -> Expression: ...
|
|
272
|
+
def __sub__(self, other: float | Variable | Expression, /) -> Expression:
|
|
273
|
+
"""
|
|
274
|
+
Subtract a value from this variable.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
other : int, float, Variable or Expression.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
Expression
|
|
283
|
+
The resulting symbolic expression.
|
|
284
|
+
|
|
285
|
+
Raises
|
|
286
|
+
------
|
|
287
|
+
VariablesFromDifferentEnvsError
|
|
288
|
+
If the operands belong to different environments.
|
|
289
|
+
TypeError
|
|
290
|
+
If the operand type is unsupported.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
@overload
|
|
294
|
+
def __rsub__(self, other: int, /) -> Expression: ...
|
|
295
|
+
@overload
|
|
296
|
+
def __rsub__(self, other: float, /) -> Expression: ...
|
|
297
|
+
def __rsub__(self, other: float, /) -> Expression:
|
|
298
|
+
"""
|
|
299
|
+
Subtract this variable from a scalar (right-hand subtraction).
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
other : int or float
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
Expression
|
|
308
|
+
The resulting symbolic expression.
|
|
309
|
+
|
|
310
|
+
Raises
|
|
311
|
+
------
|
|
312
|
+
TypeError
|
|
313
|
+
If `other` is not a scalar.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
@overload
|
|
317
|
+
def __mul__(self, other: int, /) -> Expression: ...
|
|
318
|
+
@overload
|
|
319
|
+
def __mul__(self, other: float, /) -> Expression: ...
|
|
320
|
+
@overload
|
|
321
|
+
def __mul__(self, other: Variable, /) -> Expression: ...
|
|
322
|
+
@overload
|
|
323
|
+
def __mul__(self, other: Expression, /) -> Expression: ...
|
|
324
|
+
def __mul__(self, other: float | Variable | Expression, /) -> Expression:
|
|
325
|
+
"""
|
|
326
|
+
Multiply this variable by another value.
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
other : Variable, Expression, int, or float
|
|
331
|
+
|
|
332
|
+
Returns
|
|
333
|
+
-------
|
|
334
|
+
Expression
|
|
335
|
+
The resulting symbolic expression.
|
|
336
|
+
|
|
337
|
+
Raises
|
|
338
|
+
------
|
|
339
|
+
VariablesFromDifferentEnvsError
|
|
340
|
+
If the operands belong to different environments.
|
|
341
|
+
TypeError
|
|
342
|
+
If the operand type is unsupported.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
@overload
|
|
346
|
+
def __rmul__(self, other: int, /) -> Expression: ...
|
|
347
|
+
@overload
|
|
348
|
+
def __rmul__(self, other: float, /) -> Expression: ...
|
|
349
|
+
@overload
|
|
350
|
+
def __rmul__(self, other: Variable, /) -> Expression: ...
|
|
351
|
+
@overload
|
|
352
|
+
def __rmul__(self, other: Expression, /) -> Expression: ...
|
|
353
|
+
def __rmul__(self, other: float | Variable | Expression, /) -> Expression:
|
|
354
|
+
"""
|
|
355
|
+
Right-hand multiplication for scalars.
|
|
356
|
+
|
|
357
|
+
Parameters
|
|
358
|
+
----------
|
|
359
|
+
other : int or float
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
Expression
|
|
364
|
+
The resulting symbolic expression.
|
|
365
|
+
|
|
366
|
+
Raises
|
|
367
|
+
------
|
|
368
|
+
TypeError
|
|
369
|
+
If the operand type is unsupported.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
def __pow__(self, other: int, /) -> Expression:
|
|
373
|
+
"""
|
|
374
|
+
Raise the variable to the power specified by `other`.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
other : int
|
|
379
|
+
|
|
380
|
+
Returns
|
|
381
|
+
-------
|
|
382
|
+
Expression
|
|
383
|
+
|
|
384
|
+
Raises
|
|
385
|
+
------
|
|
386
|
+
RuntimeError
|
|
387
|
+
If the param `modulo` usually supported for `__pow__` is specified.
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
@overload
|
|
391
|
+
def __eq__(self, rhs: int, /) -> Constraint: ...
|
|
392
|
+
@overload
|
|
393
|
+
def __eq__(self, rhs: float, /) -> Constraint: ...
|
|
394
|
+
@overload
|
|
395
|
+
def __eq__(self, rhs: Expression, /) -> Constraint: ...
|
|
396
|
+
@overload
|
|
397
|
+
def __eq__(self, rhs: Variable, /) -> bool:
|
|
398
|
+
"""
|
|
399
|
+
Check equality of two variables.
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
rhs : Variable
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
bool
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
def __eq__(self, rhs: float | Expression, /) -> Constraint: # type: ignore
|
|
411
|
+
"""
|
|
412
|
+
Create a constraint: Variable == float | int | Expression.
|
|
413
|
+
|
|
414
|
+
If `rhs` is of type `Variable` or `Expression` it is moved to the `lhs` in the
|
|
415
|
+
constraint, resulting in the following constraint:
|
|
416
|
+
|
|
417
|
+
self - rhs == 0
|
|
418
|
+
|
|
419
|
+
Parameters
|
|
420
|
+
----------
|
|
421
|
+
rhs : float, int or Expression
|
|
422
|
+
|
|
423
|
+
Returns
|
|
424
|
+
-------
|
|
425
|
+
Constraint
|
|
426
|
+
|
|
427
|
+
Raises
|
|
428
|
+
------
|
|
429
|
+
TypeError
|
|
430
|
+
If the right-hand side is not of type float, int or Expression.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
@overload
|
|
434
|
+
def __le__(self, rhs: int, /) -> Constraint: ...
|
|
435
|
+
@overload
|
|
436
|
+
def __le__(self, rhs: float, /) -> Constraint: ...
|
|
437
|
+
@overload
|
|
438
|
+
def __le__(self, rhs: Variable, /) -> Constraint: ...
|
|
439
|
+
@overload
|
|
440
|
+
def __le__(self, rhs: Expression, /) -> Constraint: ...
|
|
441
|
+
def __le__(self, rhs: float | Variable | Expression, /) -> Constraint: # type: ignore
|
|
442
|
+
"""
|
|
443
|
+
Create a constraint: Variable <= scalar.
|
|
444
|
+
|
|
445
|
+
If `rhs` is of type `Variable` or `Expression` it is moved to the `lhs` in the
|
|
446
|
+
constraint, resulting in the following constraint:
|
|
447
|
+
|
|
448
|
+
self - rhs <= 0
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
rhs : float, int, Variable or Expression
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
Constraint
|
|
457
|
+
|
|
458
|
+
Raises
|
|
459
|
+
------
|
|
460
|
+
TypeError
|
|
461
|
+
If the right-hand side is not of type float, int, Variable or Expression.
|
|
462
|
+
"""
|
|
463
|
+
|
|
464
|
+
@overload
|
|
465
|
+
def __ge__(self, rhs: int, /) -> Constraint: ...
|
|
466
|
+
@overload
|
|
467
|
+
def __ge__(self, rhs: float, /) -> Constraint: ...
|
|
468
|
+
@overload
|
|
469
|
+
def __ge__(self, rhs: Variable, /) -> Constraint: ...
|
|
470
|
+
@overload
|
|
471
|
+
def __ge__(self, rhs: Expression, /) -> Constraint: ...
|
|
472
|
+
def __ge__(self, rhs: float | Variable | Expression, /) -> Constraint:
|
|
473
|
+
"""
|
|
474
|
+
Create a constraint: Variable >= scalar.
|
|
475
|
+
|
|
476
|
+
If `rhs` is of type `Variable` or `Expression` it is moved to the `lhs` in the
|
|
477
|
+
constraint, resulting in the following constraint:
|
|
478
|
+
|
|
479
|
+
self - rhs >= 0
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
rhs : float, int, Variable or Expression
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
Constraint
|
|
488
|
+
|
|
489
|
+
Raises
|
|
490
|
+
------
|
|
491
|
+
TypeError
|
|
492
|
+
If the right-hand side is not of type float, int, Variable or Expression.
|
|
493
|
+
"""
|
|
494
|
+
|
|
495
|
+
def __neg__(self, /) -> Expression:
|
|
496
|
+
"""
|
|
497
|
+
Negate the variable, i.e., multiply it by `-1`.
|
|
498
|
+
|
|
499
|
+
Returns
|
|
500
|
+
-------
|
|
501
|
+
Expression
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
def __hash__(self, /) -> int: ...
|
|
505
|
+
|
|
506
|
+
# _timing.pyi
|
|
507
|
+
class Timing:
|
|
508
|
+
"""
|
|
509
|
+
The object that holds information about an algorithm's runtime.
|
|
510
|
+
|
|
511
|
+
This class can only be constructed using a `Timer`. This ensures that a
|
|
512
|
+
`Timing` object always contains a start as well as an end time.
|
|
513
|
+
|
|
514
|
+
The `qpu` field of this class can only be set after constructing it with a timer.
|
|
515
|
+
|
|
516
|
+
Examples
|
|
517
|
+
--------
|
|
518
|
+
>>> from dwave.samplers.tree.solve import BinaryQuadraticModel
|
|
519
|
+
>>> from luna_quantum import Model, Timer, Timing
|
|
520
|
+
>>> model = ... # third-party model
|
|
521
|
+
>>> algorithm = ... # third-party algorithm
|
|
522
|
+
>>> timer = Timer.start()
|
|
523
|
+
>>> sol = algorithm.run(model)
|
|
524
|
+
>>> timing: Timing = timer.stop()
|
|
525
|
+
>>> timing.qpu = sol.qpu_time
|
|
526
|
+
>>> timing.total_seconds
|
|
527
|
+
1.2999193
|
|
528
|
+
>>> timing.qpu
|
|
529
|
+
0.02491934
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
@property
|
|
533
|
+
def start(self, /) -> datetime:
|
|
534
|
+
"""The starting time of the algorithm."""
|
|
535
|
+
|
|
536
|
+
@property
|
|
537
|
+
def end(self, /) -> datetime:
|
|
538
|
+
"""The end, or finishing, time of the algorithm."""
|
|
539
|
+
|
|
540
|
+
@property
|
|
541
|
+
def total(self, /) -> timedelta:
|
|
542
|
+
"""
|
|
543
|
+
The difference of the end and start time.
|
|
544
|
+
|
|
545
|
+
Raises
|
|
546
|
+
------
|
|
547
|
+
RuntimeError
|
|
548
|
+
If total cannot be computed due to an inconsistent start or end time.
|
|
549
|
+
"""
|
|
550
|
+
|
|
551
|
+
@property
|
|
552
|
+
def total_seconds(self, /) -> float:
|
|
553
|
+
"""
|
|
554
|
+
The total time in seconds an algorithm needed to run. Computed as the
|
|
555
|
+
difference of end and start time.
|
|
556
|
+
|
|
557
|
+
Raises
|
|
558
|
+
------
|
|
559
|
+
RuntimeError
|
|
560
|
+
If total_seconds cannot be computed due to an inconsistent start or end time.
|
|
561
|
+
"""
|
|
562
|
+
|
|
563
|
+
@property
|
|
564
|
+
def qpu(self, /) -> float | None:
|
|
565
|
+
"""The qpu usage time of the algorithm this timing object was created for."""
|
|
566
|
+
|
|
567
|
+
@qpu.setter
|
|
568
|
+
def qpu(self, /, value: float | None):
|
|
569
|
+
"""
|
|
570
|
+
Set the qpu usage time.
|
|
571
|
+
|
|
572
|
+
Raises
|
|
573
|
+
------
|
|
574
|
+
ValueError
|
|
575
|
+
If `value` is negative.
|
|
576
|
+
"""
|
|
577
|
+
|
|
578
|
+
def add_qpu(self, /, value: float):
|
|
579
|
+
"""
|
|
580
|
+
Add qpu usage time to the qpu usage time already present. If the current value
|
|
581
|
+
is None, this method acts like a setter.
|
|
582
|
+
|
|
583
|
+
Parameters
|
|
584
|
+
----------
|
|
585
|
+
value : float
|
|
586
|
+
The value to add to the already present qpu value.
|
|
587
|
+
|
|
588
|
+
Raises
|
|
589
|
+
------
|
|
590
|
+
ValueError
|
|
591
|
+
If `value` is negative.
|
|
592
|
+
"""
|
|
593
|
+
|
|
594
|
+
class Timer:
|
|
595
|
+
"""
|
|
596
|
+
Used to measure the computation time of an algorithm.
|
|
597
|
+
|
|
598
|
+
The sole purpose of the `Timer` class is to create a `Timing` object in a safe
|
|
599
|
+
way, i.e., to ensure that the `Timing` object always holds a starting and
|
|
600
|
+
finishing time.
|
|
601
|
+
|
|
602
|
+
Examples
|
|
603
|
+
--------
|
|
604
|
+
Basic usage:
|
|
605
|
+
>>> from luna_quantum import Timer
|
|
606
|
+
>>> timer = Timer.start()
|
|
607
|
+
>>> solution = ... # create a solution by running an algorithm.
|
|
608
|
+
>>> timing = timer.stop()
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
@staticmethod
|
|
612
|
+
def start() -> Timer:
|
|
613
|
+
"""
|
|
614
|
+
Create a timer that starts counting immediately.
|
|
615
|
+
|
|
616
|
+
Returns
|
|
617
|
+
-------
|
|
618
|
+
Timer
|
|
619
|
+
The timer.
|
|
620
|
+
"""
|
|
621
|
+
|
|
622
|
+
def stop(self, /) -> Timing:
|
|
623
|
+
"""
|
|
624
|
+
Stop the timer, and get the resulting `Timing` object.
|
|
625
|
+
|
|
626
|
+
Returns
|
|
627
|
+
-------
|
|
628
|
+
Timing
|
|
629
|
+
The timing object that holds the start and end time.
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
# _solution.pyi
|
|
633
|
+
class Solution:
|
|
634
|
+
"""
|
|
635
|
+
The solution object that is obtained by running an algorihtm.
|
|
636
|
+
|
|
637
|
+
The `Solution` class represents a summary of all data obtained from solving a
|
|
638
|
+
model. It contains samples, i.e., assignments of values to each model variable as
|
|
639
|
+
returned by the algorithm, metadata about the solution quality, e.g., the objective
|
|
640
|
+
value, and the runtime of the algorithm.
|
|
641
|
+
|
|
642
|
+
A `Solution` can be constructed explicitly using `from_dict` or by obtaining a solution
|
|
643
|
+
from an algorithm or by converting a different solution format with one of the available
|
|
644
|
+
translators. Note that the latter requires the environment the model was created in.
|
|
645
|
+
|
|
646
|
+
Examples
|
|
647
|
+
--------
|
|
648
|
+
Basic usage, assuming that the algorithm already returns a `Solution`:
|
|
649
|
+
|
|
650
|
+
>>> from luna_quantum import Model, Solution
|
|
651
|
+
>>> model: Model = ...
|
|
652
|
+
>>> algorithm = ...
|
|
653
|
+
>>> solution: Solution = algorithm.run(model)
|
|
654
|
+
>>> solution.samples
|
|
655
|
+
[[1, 0, 1], [0, 0, 1]]
|
|
656
|
+
|
|
657
|
+
When you have a `dimod.Sampleset` as the raw solution format:
|
|
658
|
+
|
|
659
|
+
>>> from luna_quantum.translator import BqmTranslator
|
|
660
|
+
>>> from luna_quantum import Model, Solution, DwaveTranslator
|
|
661
|
+
>>> from dimod import SimulatedAnnealingSampler
|
|
662
|
+
>>> model: Model = ...
|
|
663
|
+
>>> bqm = BqmTranslator.from_aq(model)
|
|
664
|
+
>>> sampleset = SimulatedAnnealingSampler().sample(bqm)
|
|
665
|
+
>>> solution = DwaveTranslator.from_dimod_sample_set(sampleset)
|
|
666
|
+
>>> solution.samples
|
|
667
|
+
[[1, 0, 1], [0, 0, 1]]
|
|
668
|
+
|
|
669
|
+
Serialization:
|
|
670
|
+
|
|
671
|
+
>>> blob = solution.encode()
|
|
672
|
+
>>> restored = Solution.decode(blob)
|
|
673
|
+
>>> restored.samples
|
|
674
|
+
[[1, 0, 1], [0, 0, 1]]
|
|
675
|
+
|
|
676
|
+
Notes
|
|
677
|
+
-----
|
|
678
|
+
- To ensure metadata like objective values or feasibility, use `model.evaluate(solution)`.
|
|
679
|
+
- Use `encode()` and `decode()` to serialize and recover solutions.
|
|
680
|
+
"""
|
|
681
|
+
|
|
682
|
+
def __len__(self, /) -> int: ...
|
|
683
|
+
def __iter__(self, /) -> ResultIterator:
|
|
684
|
+
"""
|
|
685
|
+
Extract a result view from the `Solution` object.
|
|
686
|
+
|
|
687
|
+
Returns
|
|
688
|
+
-------
|
|
689
|
+
ResultView
|
|
690
|
+
|
|
691
|
+
Raises
|
|
692
|
+
------
|
|
693
|
+
TypeError
|
|
694
|
+
If `item` has the wrong type.
|
|
695
|
+
IndexError
|
|
696
|
+
If the row index is out of bounds for the variable environment.
|
|
697
|
+
"""
|
|
698
|
+
|
|
699
|
+
def __getitem__(self, item: int, /) -> ResultView:
|
|
700
|
+
"""
|
|
701
|
+
Extract a result view from the `Solution` object.
|
|
702
|
+
|
|
703
|
+
Returns
|
|
704
|
+
-------
|
|
705
|
+
ResultView
|
|
706
|
+
|
|
707
|
+
Raises
|
|
708
|
+
------
|
|
709
|
+
TypeError
|
|
710
|
+
If `item` has the wrong type.
|
|
711
|
+
IndexError
|
|
712
|
+
If the row index is out of bounds for the variable environment.
|
|
713
|
+
"""
|
|
714
|
+
|
|
715
|
+
def __eq__(self, other: Solution, /) -> bool: # type: ignore
|
|
716
|
+
"""
|
|
717
|
+
Check whether this solution is equal to `other`.
|
|
718
|
+
|
|
719
|
+
Parameters
|
|
720
|
+
----------
|
|
721
|
+
other : Model
|
|
722
|
+
|
|
723
|
+
Returns
|
|
724
|
+
-------
|
|
725
|
+
bool
|
|
726
|
+
"""
|
|
727
|
+
|
|
728
|
+
def best(self, /) -> ResultView | None:
|
|
729
|
+
"""
|
|
730
|
+
Get the best result of the solution if it exists.
|
|
731
|
+
|
|
732
|
+
A best solution is defined as the result with the lowest (in case of Sense.Min)
|
|
733
|
+
or the highest (in case of Sense.Max) objective value that is feasible.
|
|
734
|
+
|
|
735
|
+
Returns
|
|
736
|
+
-------
|
|
737
|
+
ResultView
|
|
738
|
+
The best result of the solution as a view.
|
|
739
|
+
"""
|
|
740
|
+
|
|
741
|
+
@property
|
|
742
|
+
def results(self, /) -> ResultIterator:
|
|
743
|
+
"""Get an iterator over the single results of the solution."""
|
|
744
|
+
|
|
745
|
+
@property
|
|
746
|
+
def samples(self, /) -> Samples:
|
|
747
|
+
"""Get a view into the samples of the solution."""
|
|
748
|
+
|
|
749
|
+
@property
|
|
750
|
+
def obj_values(self, /) -> NDArray:
|
|
751
|
+
"""
|
|
752
|
+
Get the objective values of the single samples as a ndarray. A value will be
|
|
753
|
+
None if the sample hasn't yet been evaluated.
|
|
754
|
+
"""
|
|
755
|
+
|
|
756
|
+
@property
|
|
757
|
+
def raw_energies(self, /) -> NDArray:
|
|
758
|
+
"""
|
|
759
|
+
Get the raw energy values of the single samples as returned by the solver /
|
|
760
|
+
algorithm. Will be None if the solver / algorithm did not provide a value.
|
|
761
|
+
"""
|
|
762
|
+
|
|
763
|
+
@property
|
|
764
|
+
def counts(self, /) -> NDArray:
|
|
765
|
+
"""Return how often each sample occurred in the solution."""
|
|
766
|
+
|
|
767
|
+
@property
|
|
768
|
+
def runtime(self, /) -> Timing | None:
|
|
769
|
+
"""Get the solver / algorithm runtime."""
|
|
770
|
+
|
|
771
|
+
@property
|
|
772
|
+
def best_sample_idx(self, /) -> int | None:
|
|
773
|
+
"""Get the index of the sample with the best objective value."""
|
|
774
|
+
|
|
775
|
+
@property
|
|
776
|
+
def variable_names(self, /) -> list[str]:
|
|
777
|
+
"""Get the names of all variables in the solution."""
|
|
778
|
+
|
|
779
|
+
def expectation_value(self, /) -> float:
|
|
780
|
+
"""
|
|
781
|
+
Compute the expectation value of the solution.
|
|
782
|
+
|
|
783
|
+
Returns
|
|
784
|
+
-------
|
|
785
|
+
float
|
|
786
|
+
The expectation value.
|
|
787
|
+
|
|
788
|
+
Raises
|
|
789
|
+
------
|
|
790
|
+
ComputationError
|
|
791
|
+
If the computation fails for any reason.
|
|
792
|
+
"""
|
|
793
|
+
|
|
794
|
+
@overload
|
|
795
|
+
def encode(self, /) -> bytes: ...
|
|
796
|
+
@overload
|
|
797
|
+
def encode(self, /, *, compress: bool) -> bytes: ...
|
|
798
|
+
@overload
|
|
799
|
+
def encode(self, /, *, level: int) -> bytes: ...
|
|
800
|
+
@overload
|
|
801
|
+
def encode(self, /, *, compress: bool, level: int) -> bytes:
|
|
802
|
+
"""
|
|
803
|
+
Serialize the solution into a compact binary format.
|
|
804
|
+
|
|
805
|
+
Parameters
|
|
806
|
+
----------
|
|
807
|
+
compress : bool, optional
|
|
808
|
+
Whether to compress the binary output. Default is True.
|
|
809
|
+
level : int, optional
|
|
810
|
+
Compression level (0–9). Default is 3.
|
|
811
|
+
|
|
812
|
+
Returns
|
|
813
|
+
-------
|
|
814
|
+
bytes
|
|
815
|
+
Encoded model representation.
|
|
816
|
+
|
|
817
|
+
Raises
|
|
818
|
+
------
|
|
819
|
+
IOError
|
|
820
|
+
If serialization fails.
|
|
821
|
+
"""
|
|
822
|
+
|
|
823
|
+
@overload
|
|
824
|
+
def serialize(self, /) -> bytes: ...
|
|
825
|
+
@overload
|
|
826
|
+
def serialize(self, /, *, compress: bool) -> bytes: ...
|
|
827
|
+
@overload
|
|
828
|
+
def serialize(self, /, *, level: int) -> bytes: ...
|
|
829
|
+
@overload
|
|
830
|
+
def serialize(self, /, compress: bool, level: int) -> bytes: ...
|
|
831
|
+
def serialize(
|
|
832
|
+
self, /, compress: bool | None = ..., level: int | None = ...
|
|
833
|
+
) -> bytes:
|
|
834
|
+
"""
|
|
835
|
+
Alias for `encode()`.
|
|
836
|
+
|
|
837
|
+
See `encode()` for details.
|
|
838
|
+
"""
|
|
839
|
+
|
|
840
|
+
@classmethod
|
|
841
|
+
def decode(cls, data: bytes) -> Solution:
|
|
842
|
+
"""
|
|
843
|
+
Reconstruct a solution object from binary data.
|
|
844
|
+
|
|
845
|
+
Parameters
|
|
846
|
+
----------
|
|
847
|
+
data : bytes
|
|
848
|
+
Serialized model blob created by `encode()`.
|
|
849
|
+
|
|
850
|
+
Returns
|
|
851
|
+
-------
|
|
852
|
+
Solution
|
|
853
|
+
The reconstructed solution.
|
|
854
|
+
|
|
855
|
+
Raises
|
|
856
|
+
------
|
|
857
|
+
DecodeError
|
|
858
|
+
If decoding fails due to corruption or incompatibility.
|
|
859
|
+
"""
|
|
860
|
+
|
|
861
|
+
@classmethod
|
|
862
|
+
def deserialize(cls, data: bytes) -> Solution:
|
|
863
|
+
"""Alias for `decode()`."""
|
|
864
|
+
|
|
865
|
+
@staticmethod
|
|
866
|
+
def build(
|
|
867
|
+
component_types: list[Vtype],
|
|
868
|
+
*,
|
|
869
|
+
variable_names: list[str] | None = ...,
|
|
870
|
+
binary_cols: list[list[int]] | None = ...,
|
|
871
|
+
spin_cols: list[list[int]] | None = ...,
|
|
872
|
+
int_cols: list[list[int]] | None = ...,
|
|
873
|
+
real_cols: list[list[float]] | None = ...,
|
|
874
|
+
raw_energies: list[float | None] | None = ...,
|
|
875
|
+
timing: Timing | None = ...,
|
|
876
|
+
counts: list[int] | None = ...,
|
|
877
|
+
) -> Solution:
|
|
878
|
+
"""
|
|
879
|
+
Build a `Solution` based on the provided input data. The solution is constructed
|
|
880
|
+
based on a column layout of the solution. Let's take the following sample-set with three
|
|
881
|
+
samples as an example:
|
|
882
|
+
|
|
883
|
+
[ 0 1 -1 3 2.2 1 ]
|
|
884
|
+
[ 1 0 -1 6 3.8 0 ]
|
|
885
|
+
[ 1 1 +1 2 2.4 0 ]
|
|
886
|
+
|
|
887
|
+
Each row encodes a single sample. However, the variable types vary, the first, second, and
|
|
888
|
+
last columns all represent a Binary variable (index 0, 1, 5). The third column represents a
|
|
889
|
+
variable of type Spin (index 2). The fourth column (index 3), a variable of type Integer and
|
|
890
|
+
the fifth column (index 4), a real-valued variable.
|
|
891
|
+
|
|
892
|
+
Thus, the `component_types` list is:
|
|
893
|
+
|
|
894
|
+
>>> component_types = [Vtype.Binary, Vtype.Binary, Vtype.Spin, Vtype.Integer, Vtype.Real, Vtype.Binary]
|
|
895
|
+
|
|
896
|
+
Now we can extract all columns for a binary-valued variable and append them to a new list:
|
|
897
|
+
|
|
898
|
+
>>> binary_cols = [[0, 1, 1], [1, 0, 1], [1, 0, 0]]
|
|
899
|
+
|
|
900
|
+
where the first element in the list represents the first column, the second element the\
|
|
901
|
+
second column and the third element the fifth column.
|
|
902
|
+
We do the same for the remaining variable types:
|
|
903
|
+
|
|
904
|
+
>>> spin_cols = [[-1, -1, +1]]
|
|
905
|
+
>>> int_cols = [[3, 6, 2]]
|
|
906
|
+
>>> real_cols = [[2.2, 3.8, 2.4]]
|
|
907
|
+
|
|
908
|
+
If we know the raw energies, we can construct them as well:
|
|
909
|
+
|
|
910
|
+
>>> raw_energies = [-200, -100, +300]
|
|
911
|
+
|
|
912
|
+
And finally call the `build` function:
|
|
913
|
+
|
|
914
|
+
>>> sol = Solution.build(
|
|
915
|
+
... component_types,
|
|
916
|
+
... binary_cols,
|
|
917
|
+
... spin_cols,
|
|
918
|
+
... int_cols,
|
|
919
|
+
... real_cols,
|
|
920
|
+
... raw_energies,
|
|
921
|
+
... timing,
|
|
922
|
+
... counts=[1, 1, 1]
|
|
923
|
+
... )
|
|
924
|
+
>>> sol
|
|
925
|
+
|
|
926
|
+
In this example, we could also neglect the `counts` as it defaults to `1`
|
|
927
|
+
for all samples if not set:
|
|
928
|
+
|
|
929
|
+
>>> sol = Solution.build(
|
|
930
|
+
... component_types,
|
|
931
|
+
... binary_cols,
|
|
932
|
+
... spin_cols,
|
|
933
|
+
... int_cols,
|
|
934
|
+
... real_cols,
|
|
935
|
+
... raw_energies,
|
|
936
|
+
... timing
|
|
937
|
+
... )
|
|
938
|
+
>>> sol
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
Parameters
|
|
942
|
+
----------
|
|
943
|
+
component_types : list[Vtype]
|
|
944
|
+
The variable type each element in a sample encodes.
|
|
945
|
+
variable_names : list[Vtype], optional
|
|
946
|
+
The name of each variable in the solution.
|
|
947
|
+
binary_cols : list[list[int]], optional
|
|
948
|
+
The data of all binary valued columns. Each inner list encodes a single binary-valued
|
|
949
|
+
column. Required if any element in the `component_types` is `Vtype.Binary`.
|
|
950
|
+
spin_cols : list[list[int]], optional
|
|
951
|
+
The data of all spin-valued columns. Each inner list encodes a single spin-valued
|
|
952
|
+
column. Required if any element in the `component_types` is `Vtype.Spin`.
|
|
953
|
+
int_cols : list[list[int]], optional
|
|
954
|
+
The data of all integer-valued columns. Each inner list encodes a single integer valued
|
|
955
|
+
column. Required if any element in the `component_types` is `Vtype.Integer`.
|
|
956
|
+
real_cols : list[list[float]], optional
|
|
957
|
+
The data of all real-valued columns. Each inner list encodes a single real-valued
|
|
958
|
+
column. Required if any element in the `component_types` is `Vtype.Real`.
|
|
959
|
+
raw_energies : list[float, optional], optional
|
|
960
|
+
The data of all real valued columns. Each inner list encodes a single real-valued
|
|
961
|
+
column.
|
|
962
|
+
timing : Timing, optional
|
|
963
|
+
The timing data.
|
|
964
|
+
counts : list[int], optional
|
|
965
|
+
The number how often each sample in the solution has occurred. By default, 1 for all
|
|
966
|
+
samples.
|
|
967
|
+
|
|
968
|
+
Returns
|
|
969
|
+
-------
|
|
970
|
+
Solution
|
|
971
|
+
The constructed solution
|
|
972
|
+
|
|
973
|
+
Raises
|
|
974
|
+
------
|
|
975
|
+
RuntimeError
|
|
976
|
+
If a sample column has an incorrect number of samples or if `counts` has
|
|
977
|
+
a length different from the number of samples given.
|
|
978
|
+
"""
|
|
979
|
+
|
|
980
|
+
@overload
|
|
981
|
+
@staticmethod
|
|
982
|
+
def from_dict(data: dict[Variable, int]) -> Solution: ...
|
|
983
|
+
@overload
|
|
984
|
+
@staticmethod
|
|
985
|
+
def from_dict(data: dict[Variable, float]) -> Solution: ...
|
|
986
|
+
@overload
|
|
987
|
+
@staticmethod
|
|
988
|
+
def from_dict(data: dict[str, int]) -> Solution: ...
|
|
989
|
+
@overload
|
|
990
|
+
@staticmethod
|
|
991
|
+
def from_dict(data: dict[str, float]) -> Solution: ...
|
|
992
|
+
@overload
|
|
993
|
+
@staticmethod
|
|
994
|
+
def from_dict(data: dict[Variable | str, int | float]) -> Solution: ...
|
|
995
|
+
@overload
|
|
996
|
+
@staticmethod
|
|
997
|
+
def from_dict(data: dict[Variable, int], *, env: Environment) -> Solution: ...
|
|
998
|
+
@overload
|
|
999
|
+
@staticmethod
|
|
1000
|
+
def from_dict(data: dict[Variable, float], *, env: Environment) -> Solution: ...
|
|
1001
|
+
@overload
|
|
1002
|
+
@staticmethod
|
|
1003
|
+
def from_dict(data: dict[str, int], *, env: Environment) -> Solution: ...
|
|
1004
|
+
@overload
|
|
1005
|
+
@staticmethod
|
|
1006
|
+
def from_dict(data: dict[str, float], *, env: Environment) -> Solution: ...
|
|
1007
|
+
@overload
|
|
1008
|
+
@staticmethod
|
|
1009
|
+
def from_dict(
|
|
1010
|
+
data: dict[Variable | str, int | float], *, env: Environment
|
|
1011
|
+
) -> Solution: ...
|
|
1012
|
+
@overload
|
|
1013
|
+
@staticmethod
|
|
1014
|
+
def from_dict(data: dict[Variable, int], *, model: Model) -> Solution: ...
|
|
1015
|
+
@overload
|
|
1016
|
+
@staticmethod
|
|
1017
|
+
def from_dict(data: dict[Variable, float], *, model: Model) -> Solution: ...
|
|
1018
|
+
@overload
|
|
1019
|
+
@staticmethod
|
|
1020
|
+
def from_dict(data: dict[str, int], *, model: Model) -> Solution: ...
|
|
1021
|
+
@overload
|
|
1022
|
+
@staticmethod
|
|
1023
|
+
def from_dict(data: dict[str, float], *, model: Model) -> Solution: ...
|
|
1024
|
+
@overload
|
|
1025
|
+
@staticmethod
|
|
1026
|
+
def from_dict(
|
|
1027
|
+
data: dict[Variable | str, int | float], *, model: Model
|
|
1028
|
+
) -> Solution: ...
|
|
1029
|
+
@staticmethod
|
|
1030
|
+
def from_dict(
|
|
1031
|
+
data: dict[Variable | str, int | float],
|
|
1032
|
+
*,
|
|
1033
|
+
env: Environment | None = ...,
|
|
1034
|
+
model: Model | None = ...,
|
|
1035
|
+
timing: Timing | None = ...,
|
|
1036
|
+
) -> Solution:
|
|
1037
|
+
"""
|
|
1038
|
+
Create a `Solution` from a dict that maps variables or variable names to their
|
|
1039
|
+
assigned values.
|
|
1040
|
+
|
|
1041
|
+
If a Model is passed, the solution will be evaluated immediately. Otherwise,
|
|
1042
|
+
there has to be an environment present to determine the correct variable types.
|
|
1043
|
+
|
|
1044
|
+
Parameters
|
|
1045
|
+
----------
|
|
1046
|
+
data : dict[Variable | str, int | float]
|
|
1047
|
+
The sample that shall be part of the solution.
|
|
1048
|
+
env : Environment, optional
|
|
1049
|
+
The environment the variable types shall be determined from.
|
|
1050
|
+
model : Model, optional
|
|
1051
|
+
A model to evaluate the sample with.
|
|
1052
|
+
|
|
1053
|
+
Returns
|
|
1054
|
+
-------
|
|
1055
|
+
Solution
|
|
1056
|
+
The solution object created from the sample dict.
|
|
1057
|
+
|
|
1058
|
+
Raises
|
|
1059
|
+
------
|
|
1060
|
+
NoActiveEnvironmentFoundError
|
|
1061
|
+
If no environment or model is passed to the method or available from the
|
|
1062
|
+
context.
|
|
1063
|
+
ValueError
|
|
1064
|
+
If `env` and `model` are both present. When this is the case, the user's
|
|
1065
|
+
intention is unclear as the model itself already contains an environment.
|
|
1066
|
+
SolutionTranslationError
|
|
1067
|
+
Generally if the sample translation fails. Might be specified by one of the
|
|
1068
|
+
three following errors.
|
|
1069
|
+
SampleIncorrectLengthErr
|
|
1070
|
+
If a sample has a different number of variables than the environment.
|
|
1071
|
+
SampleUnexpectedVariableError
|
|
1072
|
+
If a sample has a variable that is not present in the environment.
|
|
1073
|
+
ModelVtypeError
|
|
1074
|
+
If the result's variable types are incompatible with the model environment's
|
|
1075
|
+
variable types.
|
|
1076
|
+
"""
|
|
1077
|
+
|
|
1078
|
+
@overload
|
|
1079
|
+
@staticmethod
|
|
1080
|
+
def from_dicts(data: list[dict[Variable, int]]) -> Solution: ...
|
|
1081
|
+
@overload
|
|
1082
|
+
@staticmethod
|
|
1083
|
+
def from_dicts(data: list[dict[Variable, float]]) -> Solution: ...
|
|
1084
|
+
@overload
|
|
1085
|
+
@staticmethod
|
|
1086
|
+
def from_dicts(data: list[dict[str, int]]) -> Solution: ...
|
|
1087
|
+
@overload
|
|
1088
|
+
@staticmethod
|
|
1089
|
+
def from_dicts(data: list[dict[str, float]]) -> Solution: ...
|
|
1090
|
+
@overload
|
|
1091
|
+
@staticmethod
|
|
1092
|
+
def from_dicts(data: list[dict[Variable | str, int | float]]) -> Solution: ...
|
|
1093
|
+
@overload
|
|
1094
|
+
@staticmethod
|
|
1095
|
+
def from_dicts(
|
|
1096
|
+
data: list[dict[Variable, int]], *, env: Environment
|
|
1097
|
+
) -> Solution: ...
|
|
1098
|
+
@overload
|
|
1099
|
+
@staticmethod
|
|
1100
|
+
def from_dicts(
|
|
1101
|
+
data: list[dict[Variable, float]], *, env: Environment
|
|
1102
|
+
) -> Solution: ...
|
|
1103
|
+
@overload
|
|
1104
|
+
@staticmethod
|
|
1105
|
+
def from_dicts(data: list[dict[str, int]], *, env: Environment) -> Solution: ...
|
|
1106
|
+
@overload
|
|
1107
|
+
@staticmethod
|
|
1108
|
+
def from_dicts(data: list[dict[str, float]], *, env: Environment) -> Solution: ...
|
|
1109
|
+
@overload
|
|
1110
|
+
@staticmethod
|
|
1111
|
+
def from_dicts(
|
|
1112
|
+
data: list[dict[Variable | str, int | float]], *, env: Environment
|
|
1113
|
+
) -> Solution: ...
|
|
1114
|
+
@overload
|
|
1115
|
+
@staticmethod
|
|
1116
|
+
def from_dicts(data: list[dict[Variable, int]], *, model: Model) -> Solution: ...
|
|
1117
|
+
@overload
|
|
1118
|
+
@staticmethod
|
|
1119
|
+
def from_dicts(data: list[dict[Variable, float]], *, model: Model) -> Solution: ...
|
|
1120
|
+
@overload
|
|
1121
|
+
@staticmethod
|
|
1122
|
+
def from_dicts(data: list[dict[str, int]], *, model: Model) -> Solution: ...
|
|
1123
|
+
@overload
|
|
1124
|
+
@staticmethod
|
|
1125
|
+
def from_dicts(data: list[dict[str, float]], *, model: Model) -> Solution: ...
|
|
1126
|
+
@overload
|
|
1127
|
+
@staticmethod
|
|
1128
|
+
def from_dicts(
|
|
1129
|
+
data: list[dict[Variable | str, int | float]], *, model: Model
|
|
1130
|
+
) -> Solution: ...
|
|
1131
|
+
@staticmethod
|
|
1132
|
+
def from_dicts(
|
|
1133
|
+
data: list[dict[Variable | str, int | float]],
|
|
1134
|
+
*,
|
|
1135
|
+
env: Environment | None = ...,
|
|
1136
|
+
model: Model | None = ...,
|
|
1137
|
+
timing: Timing | None = ...,
|
|
1138
|
+
) -> Solution:
|
|
1139
|
+
"""
|
|
1140
|
+
Create a `Solution` from multiple dicts that map variables or variable names to their
|
|
1141
|
+
assigned values. Duplicate samples contained in the `data` list are aggregated to a single
|
|
1142
|
+
sample.
|
|
1143
|
+
|
|
1144
|
+
If a Model is passed, the solution will be evaluated immediately. Otherwise,
|
|
1145
|
+
there has to be an environment present to determine the correct variable types.
|
|
1146
|
+
|
|
1147
|
+
Parameters
|
|
1148
|
+
----------
|
|
1149
|
+
data : list[dict[Variable | str, int | float]]
|
|
1150
|
+
The samples that shall be part of the solution.
|
|
1151
|
+
env : Environment, optional
|
|
1152
|
+
The environment the variable types shall be determined from.
|
|
1153
|
+
model : Model, optional
|
|
1154
|
+
A model to evaluate the sample with.
|
|
1155
|
+
|
|
1156
|
+
Returns
|
|
1157
|
+
-------
|
|
1158
|
+
Solution
|
|
1159
|
+
The solution object created from the sample dict.
|
|
1160
|
+
|
|
1161
|
+
Raises
|
|
1162
|
+
------
|
|
1163
|
+
NoActiveEnvironmentFoundError
|
|
1164
|
+
If no environment or model is passed to the method or available from the
|
|
1165
|
+
context.
|
|
1166
|
+
ValueError
|
|
1167
|
+
If `env` and `model` are both present. When this is the case, the user's
|
|
1168
|
+
intention is unclear as the model itself already contains an environment.
|
|
1169
|
+
SolutionTranslationError
|
|
1170
|
+
Generally if the sample translation fails. Might be specified by one of the
|
|
1171
|
+
three following errors.
|
|
1172
|
+
SampleIncorrectLengthErr
|
|
1173
|
+
If a sample has a different number of variables than the environment.
|
|
1174
|
+
SampleUnexpectedVariableError
|
|
1175
|
+
If a sample has a variable that is not present in the environment.
|
|
1176
|
+
ModelVtypeError
|
|
1177
|
+
If the result's variable types are incompatible with the model environment's
|
|
1178
|
+
variable types.
|
|
1179
|
+
"""
|
|
1180
|
+
|
|
1181
|
+
# _sample.pyi
|
|
1182
|
+
class SamplesIterator:
|
|
1183
|
+
"""
|
|
1184
|
+
An iterator over a solution's samples.
|
|
1185
|
+
|
|
1186
|
+
Examples
|
|
1187
|
+
--------
|
|
1188
|
+
>>> from luna_quantum import Solution
|
|
1189
|
+
>>> solution: Solution = ...
|
|
1190
|
+
|
|
1191
|
+
Note: ``solution.samples`` is automatically converted into a ``SamplesIterator``.
|
|
1192
|
+
|
|
1193
|
+
>>> for sample in solution.samples:
|
|
1194
|
+
... sample
|
|
1195
|
+
[0, -5, 0.28]
|
|
1196
|
+
[1, -4, -0.42]
|
|
1197
|
+
"""
|
|
1198
|
+
|
|
1199
|
+
def __iter__(self, /) -> SamplesIterator: ...
|
|
1200
|
+
def __next__(self, /) -> Sample: ...
|
|
1201
|
+
|
|
1202
|
+
class SampleIterator:
|
|
1203
|
+
"""
|
|
1204
|
+
An iterator over the variable assignments of a solution's sample.
|
|
1205
|
+
|
|
1206
|
+
Examples
|
|
1207
|
+
--------
|
|
1208
|
+
>>> from luna_quantum import Solution
|
|
1209
|
+
>>> solution: Solution = ...
|
|
1210
|
+
>>> sample = solution.samples[0]
|
|
1211
|
+
|
|
1212
|
+
Note: ``sample`` is automatically converted into a ``SampleIterator``.
|
|
1213
|
+
|
|
1214
|
+
>>> for var in sample:
|
|
1215
|
+
... var
|
|
1216
|
+
0
|
|
1217
|
+
-5
|
|
1218
|
+
0.28
|
|
1219
|
+
"""
|
|
1220
|
+
|
|
1221
|
+
def __iter__(self, /) -> SampleIterator: ...
|
|
1222
|
+
def __next__(self, /) -> int | float: ...
|
|
1223
|
+
|
|
1224
|
+
class Samples:
|
|
1225
|
+
"""
|
|
1226
|
+
A samples object is simply a set-like object that contains every different sample
|
|
1227
|
+
of a solution.
|
|
1228
|
+
|
|
1229
|
+
The ``Samples`` class is readonly as it's merely a helper class for looking into a
|
|
1230
|
+
solution's different samples.
|
|
1231
|
+
|
|
1232
|
+
Examples
|
|
1233
|
+
--------
|
|
1234
|
+
>>> from luna_quantum import Model, Sample, Solution
|
|
1235
|
+
>>> model: Model = ...
|
|
1236
|
+
>>> solution: Solution = ...
|
|
1237
|
+
>>> samples: Samples = solution.samples
|
|
1238
|
+
>>> samples
|
|
1239
|
+
[0, -5, 0.28]
|
|
1240
|
+
[1, -4, -0.42]
|
|
1241
|
+
"""
|
|
1242
|
+
|
|
1243
|
+
@overload
|
|
1244
|
+
def __getitem__(self, item: int, /) -> Sample: ...
|
|
1245
|
+
@overload
|
|
1246
|
+
def __getitem__(self, item: tuple[int, int], /) -> int | float:
|
|
1247
|
+
"""
|
|
1248
|
+
Extract a sample or variable assignment from the ``Samples`` object.
|
|
1249
|
+
If ``item`` is an int, returns the sample in this row. If ``item`` is a tuple
|
|
1250
|
+
of ints `(i, j)`, returns the variable assignment in row `i` and column `j`.
|
|
1251
|
+
|
|
1252
|
+
Returns
|
|
1253
|
+
-------
|
|
1254
|
+
Sample or int or float
|
|
1255
|
+
|
|
1256
|
+
Raises
|
|
1257
|
+
------
|
|
1258
|
+
TypeError
|
|
1259
|
+
If ``item`` has the wrong type.
|
|
1260
|
+
IndexError
|
|
1261
|
+
If the row or column index is out of bounds for the variable environment.
|
|
1262
|
+
"""
|
|
1263
|
+
|
|
1264
|
+
def __len__(self, /) -> int:
|
|
1265
|
+
"""
|
|
1266
|
+
Get the number of samples present in this sample set.
|
|
1267
|
+
|
|
1268
|
+
Returns
|
|
1269
|
+
-------
|
|
1270
|
+
int
|
|
1271
|
+
"""
|
|
1272
|
+
|
|
1273
|
+
def __iter__(self, /) -> SamplesIterator:
|
|
1274
|
+
"""
|
|
1275
|
+
Iterate over all samples of this sample set.
|
|
1276
|
+
|
|
1277
|
+
Returns
|
|
1278
|
+
-------
|
|
1279
|
+
SamplesIterator
|
|
1280
|
+
"""
|
|
1281
|
+
|
|
1282
|
+
def tolist(self, /) -> list[list[int | float]]:
|
|
1283
|
+
"""
|
|
1284
|
+
Convert the sample into a 2-dimensional list where a row constitutes a single
|
|
1285
|
+
sample, and a column constitutes all assignments for a single variable.
|
|
1286
|
+
|
|
1287
|
+
Returns
|
|
1288
|
+
-------
|
|
1289
|
+
list[list[int | float]]
|
|
1290
|
+
The samples object as a 2-dimensional list.
|
|
1291
|
+
"""
|
|
1292
|
+
|
|
1293
|
+
class Sample:
|
|
1294
|
+
"""
|
|
1295
|
+
A sample object is an assignment of an actual value to each of the models'
|
|
1296
|
+
variables.
|
|
1297
|
+
|
|
1298
|
+
The ``Sample`` class is readonly as it's merely a helper class for looking into a
|
|
1299
|
+
single sample of a solution.
|
|
1300
|
+
|
|
1301
|
+
Note: a ``Sample`` can be converted to ``list[int | float]`` simply by calling
|
|
1302
|
+
``list(sample)``.
|
|
1303
|
+
|
|
1304
|
+
Examples
|
|
1305
|
+
--------
|
|
1306
|
+
>>> from luna_quantum import Model, Sample, Solution
|
|
1307
|
+
>>> model: Model = ...
|
|
1308
|
+
>>> solution: Solution = ...
|
|
1309
|
+
>>> sample: Sample = solution.samples[0]
|
|
1310
|
+
>>> sample
|
|
1311
|
+
[0, -5, 0.28]
|
|
1312
|
+
"""
|
|
1313
|
+
|
|
1314
|
+
@overload
|
|
1315
|
+
def __getitem__(self, item: int, /) -> int | float: ...
|
|
1316
|
+
@overload
|
|
1317
|
+
def __getitem__(self, item: Variable, /) -> int | float: ...
|
|
1318
|
+
def __getitem__(self, item: int | Variable, /) -> int | float:
|
|
1319
|
+
"""
|
|
1320
|
+
Extract a variable assignment from the ``Sample`` object.
|
|
1321
|
+
|
|
1322
|
+
Returns
|
|
1323
|
+
-------
|
|
1324
|
+
Sample or int or float
|
|
1325
|
+
|
|
1326
|
+
Raises
|
|
1327
|
+
------
|
|
1328
|
+
TypeError
|
|
1329
|
+
If ``item`` has the wrong type.
|
|
1330
|
+
IndexError
|
|
1331
|
+
If the row or column index is out of bounds for the variable environment.
|
|
1332
|
+
"""
|
|
1333
|
+
|
|
1334
|
+
def __len__(self, /) -> int:
|
|
1335
|
+
"""
|
|
1336
|
+
Get the number of variables present in this sample.
|
|
1337
|
+
|
|
1338
|
+
Returns
|
|
1339
|
+
-------
|
|
1340
|
+
int
|
|
1341
|
+
"""
|
|
1342
|
+
|
|
1343
|
+
def __iter__(self, /) -> SampleIterator:
|
|
1344
|
+
"""
|
|
1345
|
+
Iterate over all variable assignments of this sample.
|
|
1346
|
+
|
|
1347
|
+
Returns
|
|
1348
|
+
-------
|
|
1349
|
+
SampleIterator
|
|
1350
|
+
"""
|
|
1351
|
+
|
|
1352
|
+
# _result.pyi
|
|
1353
|
+
class ResultIterator:
|
|
1354
|
+
"""
|
|
1355
|
+
An iterator over a solution's results.
|
|
1356
|
+
|
|
1357
|
+
Examples
|
|
1358
|
+
--------
|
|
1359
|
+
>>> from luna_quantum import ResultIterator, Solution
|
|
1360
|
+
>>> solution: Solution = ...
|
|
1361
|
+
>>> results: ResultIterator = solution.results
|
|
1362
|
+
>>> for result in results:
|
|
1363
|
+
... result.sample
|
|
1364
|
+
[0, -5, 0.28]
|
|
1365
|
+
[1, -4, -0.42]
|
|
1366
|
+
"""
|
|
1367
|
+
|
|
1368
|
+
def __iter__(self, /) -> ResultIterator: ...
|
|
1369
|
+
def __next__(self, /) -> ResultView: ...
|
|
1370
|
+
|
|
1371
|
+
class Result:
|
|
1372
|
+
"""
|
|
1373
|
+
A result object can be understood as a solution with only one sample.
|
|
1374
|
+
|
|
1375
|
+
It can be obtained by calling `model.evaluate_sample` for a single sample.
|
|
1376
|
+
|
|
1377
|
+
Most properties available for the solution object are also available for a result,
|
|
1378
|
+
but in the singular form. For example, you can call `solution.obj_values`, but
|
|
1379
|
+
`result.obj_value`.
|
|
1380
|
+
|
|
1381
|
+
Examples
|
|
1382
|
+
--------
|
|
1383
|
+
>>> from luna_quantum import Model, Result, Solution
|
|
1384
|
+
>>> model: Model = ...
|
|
1385
|
+
>>> solution: Solution = ...
|
|
1386
|
+
>>> sample = solution.samples[0]
|
|
1387
|
+
>>> result = model.evaluate_sample(sample)
|
|
1388
|
+
>>> result.obj_value
|
|
1389
|
+
-109.42
|
|
1390
|
+
>>> result.sample
|
|
1391
|
+
[0, -5, 0.28]
|
|
1392
|
+
>>> result.constraints
|
|
1393
|
+
[True, False]
|
|
1394
|
+
>>> result.feasible
|
|
1395
|
+
False
|
|
1396
|
+
"""
|
|
1397
|
+
|
|
1398
|
+
@property
|
|
1399
|
+
def sample(self, /) -> Sample:
|
|
1400
|
+
"""Get the sample of the result."""
|
|
1401
|
+
|
|
1402
|
+
@property
|
|
1403
|
+
def obj_value(self, /) -> float | None:
|
|
1404
|
+
"""Get the objective value of the result."""
|
|
1405
|
+
|
|
1406
|
+
@property
|
|
1407
|
+
def constraints(self, /) -> NDArray | None:
|
|
1408
|
+
"""
|
|
1409
|
+
Get this result's feasibility values of all constraints. Note that
|
|
1410
|
+
`results.constraints[i]` iff. `model.constraints[i]` is feasible for
|
|
1411
|
+
this result.
|
|
1412
|
+
"""
|
|
1413
|
+
|
|
1414
|
+
@property
|
|
1415
|
+
def variable_bounds(self, /) -> NDArray | None:
|
|
1416
|
+
"""
|
|
1417
|
+
Get this result's feasibility values of all variable bounds.
|
|
1418
|
+
"""
|
|
1419
|
+
|
|
1420
|
+
@property
|
|
1421
|
+
def feasible(self, /) -> bool | None:
|
|
1422
|
+
"""Return whether all constraint results are feasible for this result."""
|
|
1423
|
+
|
|
1424
|
+
class ResultView:
|
|
1425
|
+
"""
|
|
1426
|
+
A result view object serves as a view into one row of a solution object.
|
|
1427
|
+
|
|
1428
|
+
The `Result` class is readonly as it's merely a helper class for looking into a
|
|
1429
|
+
solution's row, i.e., a single sample and this sample's metadata.
|
|
1430
|
+
|
|
1431
|
+
Most properties available for the solution object are also available for a result,
|
|
1432
|
+
but in the singular form. For example, you can call `solution.obj_values`, but
|
|
1433
|
+
`result.obj_value`.
|
|
1434
|
+
|
|
1435
|
+
Examples
|
|
1436
|
+
--------
|
|
1437
|
+
>>> from luna_quantum import ResultView, Solution
|
|
1438
|
+
>>> solution: Solution = ...
|
|
1439
|
+
>>> result: ResultView = solution[0]
|
|
1440
|
+
>>> result.obj_value
|
|
1441
|
+
-109.42
|
|
1442
|
+
>>> result.sample
|
|
1443
|
+
[0, -5, 0.28]
|
|
1444
|
+
>>> result.constraints
|
|
1445
|
+
[True, False]
|
|
1446
|
+
>>> result.feasible
|
|
1447
|
+
False
|
|
1448
|
+
"""
|
|
1449
|
+
|
|
1450
|
+
@property
|
|
1451
|
+
def sample(self, /) -> Sample:
|
|
1452
|
+
"""Get the sample of the result."""
|
|
1453
|
+
|
|
1454
|
+
@property
|
|
1455
|
+
def counts(self, /) -> int:
|
|
1456
|
+
"""Return how often this result appears in the solution."""
|
|
1457
|
+
|
|
1458
|
+
@property
|
|
1459
|
+
def obj_value(self, /) -> float | None:
|
|
1460
|
+
"""
|
|
1461
|
+
Get the objective value of this sample if present. This is the value computed
|
|
1462
|
+
by the corresponding AqModel.
|
|
1463
|
+
"""
|
|
1464
|
+
|
|
1465
|
+
@property
|
|
1466
|
+
def raw_energy(self, /) -> float | None:
|
|
1467
|
+
"""
|
|
1468
|
+
Get the raw energy returned by the algorithm if present. This value is not
|
|
1469
|
+
guaranteed to be accurate under consideration of the corresponding AqModel.
|
|
1470
|
+
"""
|
|
1471
|
+
|
|
1472
|
+
@property
|
|
1473
|
+
def constraints(self, /) -> NDArray | None:
|
|
1474
|
+
"""
|
|
1475
|
+
Get this result's feasibility values of all constraints. Note that
|
|
1476
|
+
`results.constraints[i]` iff. `model.constraints[i]` is feasible for
|
|
1477
|
+
this result.
|
|
1478
|
+
"""
|
|
1479
|
+
|
|
1480
|
+
@property
|
|
1481
|
+
def variable_bounds(self, /) -> NDArray | None:
|
|
1482
|
+
"""
|
|
1483
|
+
Get this result's feasibility values of all variable bounds.
|
|
1484
|
+
"""
|
|
1485
|
+
|
|
1486
|
+
@property
|
|
1487
|
+
def feasible(self, /) -> bool | None:
|
|
1488
|
+
"""Return whether all constraint results are feasible for this result."""
|
|
1489
|
+
|
|
1490
|
+
def __eq__(self, other: ResultView, /) -> bool: ... # type: ignore
|
|
1491
|
+
|
|
1492
|
+
# _model.pyi
|
|
1493
|
+
class Sense(Enum):
|
|
1494
|
+
"""
|
|
1495
|
+
Enumeration of optimization senses supported by the optimization system.
|
|
1496
|
+
|
|
1497
|
+
This enum defines the type of optimization used for a model. The type influences
|
|
1498
|
+
the domain and behavior of the model during optimization.
|
|
1499
|
+
"""
|
|
1500
|
+
|
|
1501
|
+
Min = ...
|
|
1502
|
+
"""Indicate the objective function to be minimized."""
|
|
1503
|
+
|
|
1504
|
+
Max = ...
|
|
1505
|
+
"""Indicate the objective function to be maximized."""
|
|
1506
|
+
|
|
1507
|
+
class Model:
|
|
1508
|
+
"""
|
|
1509
|
+
A symbolic optimization model consisting of an objective and constraints.
|
|
1510
|
+
|
|
1511
|
+
The `Model` class represents a structured symbolic optimization problem. It
|
|
1512
|
+
combines a scalar objective `Expression`, a collection of `Constraints`, and
|
|
1513
|
+
a shared `Environment` that scopes all variables used in the model.
|
|
1514
|
+
|
|
1515
|
+
Models can be constructed explicitly by passing an environment, or implicitly
|
|
1516
|
+
by allowing the model to create its own private environment. If constructed
|
|
1517
|
+
inside an active `Environment` context (via `with Environment()`), that context
|
|
1518
|
+
is used automatically.
|
|
1519
|
+
|
|
1520
|
+
Parameters
|
|
1521
|
+
----------
|
|
1522
|
+
env : Environment, optional
|
|
1523
|
+
The environment in which variables and expressions are created. If not
|
|
1524
|
+
provided, the model will either use the current context (if active), or
|
|
1525
|
+
create a new private environment.
|
|
1526
|
+
name : str, optional
|
|
1527
|
+
An optional name assigned to the model.
|
|
1528
|
+
|
|
1529
|
+
Examples
|
|
1530
|
+
--------
|
|
1531
|
+
Basic usage:
|
|
1532
|
+
|
|
1533
|
+
>>> from luna_quantum import Model, Variable
|
|
1534
|
+
>>> model = Model("MyModel")
|
|
1535
|
+
>>> with model.environment:
|
|
1536
|
+
... x = Variable("x")
|
|
1537
|
+
... y = Variable("y")
|
|
1538
|
+
>>> model.objective = x * y + x
|
|
1539
|
+
>>> model.constraints += x >= 0
|
|
1540
|
+
>>> model.constraints += y <= 5
|
|
1541
|
+
|
|
1542
|
+
With explicit environment:
|
|
1543
|
+
|
|
1544
|
+
>>> from luna_quantum import Environment
|
|
1545
|
+
>>> env = Environment()
|
|
1546
|
+
>>> model = Model("ScopedModel", env)
|
|
1547
|
+
>>> with env:
|
|
1548
|
+
... x = Variable("x")
|
|
1549
|
+
... model.objective = x * x
|
|
1550
|
+
|
|
1551
|
+
Serialization:
|
|
1552
|
+
|
|
1553
|
+
>>> blob = model.encode()
|
|
1554
|
+
>>> restored = Model.decode(blob)
|
|
1555
|
+
>>> restored.name
|
|
1556
|
+
'MyModel'
|
|
1557
|
+
|
|
1558
|
+
Notes
|
|
1559
|
+
-----
|
|
1560
|
+
- The `Model` class does not solve the optimization problem.
|
|
1561
|
+
- Use `.objective`, `.constraints`, and `.environment` to access the symbolic content.
|
|
1562
|
+
- Use `encode()` and `decode()` to serialize and recover models.
|
|
1563
|
+
"""
|
|
1564
|
+
|
|
1565
|
+
metadata: ModelMetadata | None = ...
|
|
1566
|
+
|
|
1567
|
+
@staticmethod
|
|
1568
|
+
def load_luna(model_id: str, client: ILunaSolve | str | None = None) -> Model: ...
|
|
1569
|
+
def save_luna(self, client: ILunaSolve | str | None = None) -> None: ...
|
|
1570
|
+
def delete_luna(self, client: ILunaSolve | str | None = None) -> None: ...
|
|
1571
|
+
def load_solutions(
|
|
1572
|
+
self, client: ILunaSolve | str | None = None
|
|
1573
|
+
) -> list[Solution]: ...
|
|
1574
|
+
def load_solve_jobs(
|
|
1575
|
+
self, client: ILunaSolve | str | None = None
|
|
1576
|
+
) -> list[SolveJob]: ...
|
|
1577
|
+
@overload
|
|
1578
|
+
def __init__(self, /) -> None: ...
|
|
1579
|
+
@overload
|
|
1580
|
+
def __init__(self, /, name: str) -> None: ...
|
|
1581
|
+
@overload
|
|
1582
|
+
def __init__(self, /, name: str, *, sense: Sense) -> None: ...
|
|
1583
|
+
@overload
|
|
1584
|
+
def __init__(self, /, name: str, *, env: Environment) -> None: ...
|
|
1585
|
+
@overload
|
|
1586
|
+
def __init__(self, /, *, sense: Sense) -> None: ...
|
|
1587
|
+
@overload
|
|
1588
|
+
def __init__(self, /, *, env: Environment) -> None: ...
|
|
1589
|
+
@overload
|
|
1590
|
+
def __init__(self, /, *, sense: Sense, env: Environment) -> None: ...
|
|
1591
|
+
@overload
|
|
1592
|
+
def __init__(self, /, name: str, *, sense: Sense, env: Environment) -> None: ...
|
|
1593
|
+
def __init__(
|
|
1594
|
+
self,
|
|
1595
|
+
/,
|
|
1596
|
+
name: str | None = ...,
|
|
1597
|
+
*,
|
|
1598
|
+
sense: Sense | None = ...,
|
|
1599
|
+
env: Environment | None = ...,
|
|
1600
|
+
) -> None:
|
|
1601
|
+
"""
|
|
1602
|
+
Initialize a new symbolic model.
|
|
1603
|
+
|
|
1604
|
+
Parameters
|
|
1605
|
+
----------
|
|
1606
|
+
name : str, optional
|
|
1607
|
+
An optional name for the model.
|
|
1608
|
+
env : Environment, optional
|
|
1609
|
+
The environment in which the model operates. If not provided, a new
|
|
1610
|
+
environment will be created or inferred from context.
|
|
1611
|
+
"""
|
|
1612
|
+
|
|
1613
|
+
def set_sense(self, /, sense: Sense) -> None:
|
|
1614
|
+
"""
|
|
1615
|
+
Set the optimization sense of a model.
|
|
1616
|
+
|
|
1617
|
+
Parameters
|
|
1618
|
+
----------
|
|
1619
|
+
sense : Sense
|
|
1620
|
+
The sense of the model (minimization, maximization)
|
|
1621
|
+
"""
|
|
1622
|
+
|
|
1623
|
+
@property
|
|
1624
|
+
def name(self, /) -> str:
|
|
1625
|
+
"""Return the name of the model."""
|
|
1626
|
+
|
|
1627
|
+
@property
|
|
1628
|
+
def sense(self, /) -> Sense:
|
|
1629
|
+
"""
|
|
1630
|
+
Get the sense of the model
|
|
1631
|
+
|
|
1632
|
+
Returns
|
|
1633
|
+
-------
|
|
1634
|
+
Sense
|
|
1635
|
+
The sense of the model (Min or Max).
|
|
1636
|
+
"""
|
|
1637
|
+
|
|
1638
|
+
@property
|
|
1639
|
+
def objective(self, /) -> Expression:
|
|
1640
|
+
"""Get the objective expression of the model."""
|
|
1641
|
+
|
|
1642
|
+
@objective.setter
|
|
1643
|
+
def objective(self, value: Expression, /):
|
|
1644
|
+
"""Set the objective expression of the model."""
|
|
1645
|
+
|
|
1646
|
+
@property
|
|
1647
|
+
def constraints(self, /) -> Constraints:
|
|
1648
|
+
"""Access the set of constraints associated with the model."""
|
|
1649
|
+
|
|
1650
|
+
@constraints.setter
|
|
1651
|
+
def constraints(self, value: Constraints, /):
|
|
1652
|
+
"""Replace the model's constraints with a new set."""
|
|
1653
|
+
|
|
1654
|
+
@property
|
|
1655
|
+
def environment(self, /) -> Environment:
|
|
1656
|
+
"""Get the environment in which this model is defined."""
|
|
1657
|
+
|
|
1658
|
+
@overload
|
|
1659
|
+
def variables(self, /) -> list[Variable]: ...
|
|
1660
|
+
@overload
|
|
1661
|
+
def variables(self, /, *, active: bool) -> list[Variable]: ...
|
|
1662
|
+
def variables(self, /, active: bool | None = ...) -> list[Variable]:
|
|
1663
|
+
"""
|
|
1664
|
+
Get all variables that are part of this model.
|
|
1665
|
+
|
|
1666
|
+
Parameters
|
|
1667
|
+
----------
|
|
1668
|
+
active : bool, optional
|
|
1669
|
+
Instead of all variables from the environment, return only those that are
|
|
1670
|
+
actually present in the model's objective.
|
|
1671
|
+
|
|
1672
|
+
Returns
|
|
1673
|
+
-------
|
|
1674
|
+
The model's variables as a list.
|
|
1675
|
+
"""
|
|
1676
|
+
|
|
1677
|
+
@overload
|
|
1678
|
+
def add_constraint(self, /, constraint: Constraint): ...
|
|
1679
|
+
@overload
|
|
1680
|
+
def add_constraint(self, /, constraint: Constraint, name: str): ...
|
|
1681
|
+
def add_constraint(self, /, constraint: Constraint, name: str | None = ...):
|
|
1682
|
+
"""
|
|
1683
|
+
Add a constraint to the model's constraint collection.
|
|
1684
|
+
|
|
1685
|
+
Parameters
|
|
1686
|
+
----------
|
|
1687
|
+
constraint : Constraint
|
|
1688
|
+
The constraint to be added.
|
|
1689
|
+
name : str, optional
|
|
1690
|
+
The name of the constraint to be added.
|
|
1691
|
+
"""
|
|
1692
|
+
|
|
1693
|
+
@overload
|
|
1694
|
+
def set_objective(self, /, expression: Expression): ...
|
|
1695
|
+
@overload
|
|
1696
|
+
def set_objective(self, /, expression: Expression, *, sense: Sense): ...
|
|
1697
|
+
def set_objective(self, /, expression: Expression, *, sense: Sense | None = ...):
|
|
1698
|
+
"""
|
|
1699
|
+
Set the model's objective to this expression.
|
|
1700
|
+
|
|
1701
|
+
Parameters
|
|
1702
|
+
----------
|
|
1703
|
+
expression : Expression
|
|
1704
|
+
The expression assigned to the model's objective.
|
|
1705
|
+
sense : Sense, optional
|
|
1706
|
+
The sense of the model for this objective, by default Sense.Min.
|
|
1707
|
+
"""
|
|
1708
|
+
|
|
1709
|
+
@property
|
|
1710
|
+
def num_constraints(self, /) -> int:
|
|
1711
|
+
"""
|
|
1712
|
+
Return the number of constraints defined in the model.
|
|
1713
|
+
|
|
1714
|
+
Returns
|
|
1715
|
+
-------
|
|
1716
|
+
int
|
|
1717
|
+
Total number of constraints.
|
|
1718
|
+
"""
|
|
1719
|
+
|
|
1720
|
+
def evaluate(self, /, solution: Solution) -> Solution:
|
|
1721
|
+
"""
|
|
1722
|
+
Evaluate the model given a solution.
|
|
1723
|
+
|
|
1724
|
+
Parameters
|
|
1725
|
+
----------
|
|
1726
|
+
solution : Solution
|
|
1727
|
+
The solution used to evaluate the model with.
|
|
1728
|
+
|
|
1729
|
+
Returns
|
|
1730
|
+
-------
|
|
1731
|
+
Solution
|
|
1732
|
+
A new solution object with filled-out information.
|
|
1733
|
+
"""
|
|
1734
|
+
|
|
1735
|
+
def evaluate_sample(self, /, sample: Sample) -> Result:
|
|
1736
|
+
"""
|
|
1737
|
+
Evaluate the model given a single sample.
|
|
1738
|
+
|
|
1739
|
+
Parameters
|
|
1740
|
+
----------
|
|
1741
|
+
sample : Sample
|
|
1742
|
+
The sample used to evaluate the model with.
|
|
1743
|
+
|
|
1744
|
+
Returns
|
|
1745
|
+
-------
|
|
1746
|
+
Result
|
|
1747
|
+
A result object containing the information from the evaluation process.
|
|
1748
|
+
"""
|
|
1749
|
+
|
|
1750
|
+
@overload
|
|
1751
|
+
def encode(self, /) -> bytes: ...
|
|
1752
|
+
@overload
|
|
1753
|
+
def encode(self, /, *, compress: bool) -> bytes: ...
|
|
1754
|
+
@overload
|
|
1755
|
+
def encode(self, /, *, level: int) -> bytes: ...
|
|
1756
|
+
@overload
|
|
1757
|
+
def encode(self, /, compress: bool, level: int) -> bytes: ...
|
|
1758
|
+
def encode(self, /, compress: bool | None = ..., level: int | None = ...) -> bytes:
|
|
1759
|
+
"""
|
|
1760
|
+
Serialize the model into a compact binary format.
|
|
1761
|
+
|
|
1762
|
+
Parameters
|
|
1763
|
+
----------
|
|
1764
|
+
compress : bool, optional
|
|
1765
|
+
Whether to compress the binary output. Default is True.
|
|
1766
|
+
level : int, optional
|
|
1767
|
+
Compression level (0–9). Default is 3.
|
|
1768
|
+
|
|
1769
|
+
Returns
|
|
1770
|
+
-------
|
|
1771
|
+
bytes
|
|
1772
|
+
Encoded model representation.
|
|
1773
|
+
|
|
1774
|
+
Raises
|
|
1775
|
+
------
|
|
1776
|
+
IOError
|
|
1777
|
+
If serialization fails.
|
|
1778
|
+
"""
|
|
1779
|
+
|
|
1780
|
+
@overload
|
|
1781
|
+
def serialize(self, /) -> bytes: ...
|
|
1782
|
+
@overload
|
|
1783
|
+
def serialize(self, /, *, compress: bool) -> bytes: ...
|
|
1784
|
+
@overload
|
|
1785
|
+
def serialize(self, /, *, level: int) -> bytes: ...
|
|
1786
|
+
@overload
|
|
1787
|
+
def serialize(self, /, compress: bool, level: int) -> bytes: ...
|
|
1788
|
+
def serialize(
|
|
1789
|
+
self, /, compress: bool | None = ..., level: int | None = ...
|
|
1790
|
+
) -> bytes:
|
|
1791
|
+
"""
|
|
1792
|
+
Alias for `encode()`.
|
|
1793
|
+
|
|
1794
|
+
See `encode()` for full documentation.
|
|
1795
|
+
"""
|
|
1796
|
+
|
|
1797
|
+
@classmethod
|
|
1798
|
+
def decode(cls, data: bytes) -> Model:
|
|
1799
|
+
"""
|
|
1800
|
+
Reconstruct a symbolic model from binary data.
|
|
1801
|
+
|
|
1802
|
+
Parameters
|
|
1803
|
+
----------
|
|
1804
|
+
data : bytes
|
|
1805
|
+
Serialized model blob created by `encode()`.
|
|
1806
|
+
|
|
1807
|
+
Returns
|
|
1808
|
+
-------
|
|
1809
|
+
Model
|
|
1810
|
+
The reconstructed model.
|
|
1811
|
+
|
|
1812
|
+
Raises
|
|
1813
|
+
------
|
|
1814
|
+
DecodeError
|
|
1815
|
+
If decoding fails due to corruption or incompatibility.
|
|
1816
|
+
"""
|
|
1817
|
+
|
|
1818
|
+
@classmethod
|
|
1819
|
+
def deserialize(cls, data: bytes) -> Model:
|
|
1820
|
+
"""
|
|
1821
|
+
Alias for `decode()`.
|
|
1822
|
+
|
|
1823
|
+
See `decode()` for full documentation.
|
|
1824
|
+
"""
|
|
1825
|
+
|
|
1826
|
+
def __eq__(self, other: Model, /) -> bool: # type: ignore
|
|
1827
|
+
"""
|
|
1828
|
+
Check whether this model is equal to `other`.
|
|
1829
|
+
|
|
1830
|
+
Parameters
|
|
1831
|
+
----------
|
|
1832
|
+
other : Model
|
|
1833
|
+
|
|
1834
|
+
Returns
|
|
1835
|
+
-------
|
|
1836
|
+
bool
|
|
1837
|
+
"""
|
|
1838
|
+
|
|
1839
|
+
def __hash__(self, /) -> int: ...
|
|
1840
|
+
|
|
1841
|
+
# _expression.pyi
|
|
1842
|
+
class Expression:
|
|
1843
|
+
"""
|
|
1844
|
+
Polynomial expression supporting symbolic arithmetic, constraint creation, and encoding.
|
|
1845
|
+
|
|
1846
|
+
An `Expression` represents a real-valued mathematical function composed of variables,
|
|
1847
|
+
scalars, and coefficients. Expressions may include constant, linear, quadratic, and
|
|
1848
|
+
higher-order terms (cubic and beyond). They are used to build objective functions
|
|
1849
|
+
and constraints in symbolic optimization models.
|
|
1850
|
+
|
|
1851
|
+
Expressions support both regular and in-place arithmetic, including addition and
|
|
1852
|
+
multiplication with integers, floats, `Variable` instances, and other `Expression`s.
|
|
1853
|
+
|
|
1854
|
+
Parameters
|
|
1855
|
+
----------
|
|
1856
|
+
env : Environment, optional
|
|
1857
|
+
Environment used to scope the expression when explicitly instantiating it.
|
|
1858
|
+
Typically, expressions are constructed implicitly via arithmetic on variables.
|
|
1859
|
+
|
|
1860
|
+
Examples
|
|
1861
|
+
--------
|
|
1862
|
+
Constructing expressions from variables:
|
|
1863
|
+
|
|
1864
|
+
>>> from luna_quantum import Environment, Variable
|
|
1865
|
+
>>> with Environment():
|
|
1866
|
+
... x = Variable("x")
|
|
1867
|
+
... y = Variable("y")
|
|
1868
|
+
... expr = 1 + 2 * x + 3 * x * y + x * y * y
|
|
1869
|
+
|
|
1870
|
+
Inspecting terms:
|
|
1871
|
+
|
|
1872
|
+
>>> expr.get_offset()
|
|
1873
|
+
1.0
|
|
1874
|
+
>>> expr.get_linear(x)
|
|
1875
|
+
2.0
|
|
1876
|
+
>>> expr.get_quadratic(x, y)
|
|
1877
|
+
3.0
|
|
1878
|
+
>>> expr.get_higher_order((x, y, y))
|
|
1879
|
+
1.0
|
|
1880
|
+
|
|
1881
|
+
In-place arithmetic:
|
|
1882
|
+
|
|
1883
|
+
>>> expr += x
|
|
1884
|
+
>>> expr *= 2
|
|
1885
|
+
|
|
1886
|
+
Creating constraints:
|
|
1887
|
+
|
|
1888
|
+
>>> constraint = expr == 10.0
|
|
1889
|
+
>>> constraint2 = expr <= 15
|
|
1890
|
+
|
|
1891
|
+
Serialization:
|
|
1892
|
+
|
|
1893
|
+
>>> blob = expr.encode()
|
|
1894
|
+
>>> restored = Expression.decode(blob)
|
|
1895
|
+
|
|
1896
|
+
Supported Arithmetic
|
|
1897
|
+
--------------------
|
|
1898
|
+
The following operations are supported:
|
|
1899
|
+
|
|
1900
|
+
- Addition:
|
|
1901
|
+
* `expr + expr` → `Expression`
|
|
1902
|
+
* `expr + variable` → `Expression`
|
|
1903
|
+
* `expr + int | float` → `Expression`
|
|
1904
|
+
* `int | float + expr` → `Expression`
|
|
1905
|
+
|
|
1906
|
+
- In-place addition:
|
|
1907
|
+
* `expr += expr`
|
|
1908
|
+
* `expr += variable`
|
|
1909
|
+
* `expr += int | float`
|
|
1910
|
+
|
|
1911
|
+
- Multiplication:
|
|
1912
|
+
* `expr * expr`
|
|
1913
|
+
* `expr * variable`
|
|
1914
|
+
* `expr * int | float`
|
|
1915
|
+
* `int | float * expr`
|
|
1916
|
+
|
|
1917
|
+
- In-place multiplication:
|
|
1918
|
+
* `expr *= expr`
|
|
1919
|
+
* `expr *= variable`
|
|
1920
|
+
* `expr *= int | float`
|
|
1921
|
+
|
|
1922
|
+
- Constraint creation:
|
|
1923
|
+
* `expr == constant` → `Constraint`
|
|
1924
|
+
* `expr <= constant` → `Constraint`
|
|
1925
|
+
* `expr >= constant` → `Constraint`
|
|
1926
|
+
|
|
1927
|
+
Notes
|
|
1928
|
+
-----
|
|
1929
|
+
- Expressions are mutable: in-place operations (`+=`, `*=`) modify the instance.
|
|
1930
|
+
- Expressions are scoped to an environment via the variables they reference.
|
|
1931
|
+
- Comparisons like `expr == expr` return `bool`, not constraints.
|
|
1932
|
+
- Use `==`, `<=`, `>=` with numeric constants to create constraints.
|
|
1933
|
+
"""
|
|
1934
|
+
|
|
1935
|
+
@overload
|
|
1936
|
+
def __init__(self, /) -> None: ...
|
|
1937
|
+
@overload
|
|
1938
|
+
def __init__(self, /, env: Environment) -> None: ...
|
|
1939
|
+
def __init__(self, /, env: Environment | None = ...) -> None:
|
|
1940
|
+
"""
|
|
1941
|
+
Create a new empty expression scoped to an environment.
|
|
1942
|
+
|
|
1943
|
+
Parameters
|
|
1944
|
+
----------
|
|
1945
|
+
env : Environment
|
|
1946
|
+
The environment to which this expression is bound.
|
|
1947
|
+
|
|
1948
|
+
Raises
|
|
1949
|
+
------
|
|
1950
|
+
NoActiveEnvironmentFoundError
|
|
1951
|
+
If no environment is provided and none is active in the context.
|
|
1952
|
+
"""
|
|
1953
|
+
|
|
1954
|
+
def get_offset(self, /) -> float:
|
|
1955
|
+
"""
|
|
1956
|
+
Get the constant (offset) term in the expression.
|
|
1957
|
+
|
|
1958
|
+
Returns
|
|
1959
|
+
-------
|
|
1960
|
+
float
|
|
1961
|
+
The constant term.
|
|
1962
|
+
"""
|
|
1963
|
+
|
|
1964
|
+
def get_linear(self, /, variable: Variable) -> float:
|
|
1965
|
+
"""
|
|
1966
|
+
Get the coefficient of a linear term for a given variable.
|
|
1967
|
+
|
|
1968
|
+
Parameters
|
|
1969
|
+
----------
|
|
1970
|
+
variable : Variable
|
|
1971
|
+
The variable whose linear coefficient is being queried.
|
|
1972
|
+
|
|
1973
|
+
Returns
|
|
1974
|
+
-------
|
|
1975
|
+
float
|
|
1976
|
+
The coefficient, or 0.0 if the variable is not present.
|
|
1977
|
+
|
|
1978
|
+
Raises
|
|
1979
|
+
------
|
|
1980
|
+
VariableOutOfRangeError
|
|
1981
|
+
If the variable index is not valid in this expression's environment.
|
|
1982
|
+
"""
|
|
1983
|
+
|
|
1984
|
+
def get_quadratic(self, /, u: Variable, v: Variable) -> float:
|
|
1985
|
+
"""
|
|
1986
|
+
Get the coefficient for a quadratic term (u * v).
|
|
1987
|
+
|
|
1988
|
+
Parameters
|
|
1989
|
+
----------
|
|
1990
|
+
u : Variable
|
|
1991
|
+
v : Variable
|
|
1992
|
+
|
|
1993
|
+
Returns
|
|
1994
|
+
-------
|
|
1995
|
+
float
|
|
1996
|
+
The coefficient, or 0.0 if not present.
|
|
1997
|
+
|
|
1998
|
+
Raises
|
|
1999
|
+
------
|
|
2000
|
+
VariableOutOfRangeError
|
|
2001
|
+
If either variable is out of bounds for the expression's environment.
|
|
2002
|
+
"""
|
|
2003
|
+
|
|
2004
|
+
def get_higher_order(self, /, variables: tuple[Variable, ...]) -> float:
|
|
2005
|
+
"""
|
|
2006
|
+
Get the coefficient for a higher-order term (degree ≥ 3).
|
|
2007
|
+
|
|
2008
|
+
Parameters
|
|
2009
|
+
----------
|
|
2010
|
+
variables : tuple of Variable
|
|
2011
|
+
A tuple of variables specifying the term.
|
|
2012
|
+
|
|
2013
|
+
Returns
|
|
2014
|
+
-------
|
|
2015
|
+
float
|
|
2016
|
+
The coefficient, or 0.0 if not present.
|
|
2017
|
+
|
|
2018
|
+
Raises
|
|
2019
|
+
------
|
|
2020
|
+
VariableOutOfRangeError
|
|
2021
|
+
If any variable is out of bounds for the environment.
|
|
2022
|
+
"""
|
|
2023
|
+
|
|
2024
|
+
@property
|
|
2025
|
+
def num_variables(self, /) -> int:
|
|
2026
|
+
"""
|
|
2027
|
+
Return the number of distinct variables in the expression.
|
|
2028
|
+
|
|
2029
|
+
Returns
|
|
2030
|
+
-------
|
|
2031
|
+
int
|
|
2032
|
+
Number of variables with non-zero coefficients.
|
|
2033
|
+
"""
|
|
2034
|
+
|
|
2035
|
+
def is_equal(self, /, other: Expression) -> bool:
|
|
2036
|
+
"""
|
|
2037
|
+
Compare two expressions for equality.
|
|
2038
|
+
|
|
2039
|
+
Parameters
|
|
2040
|
+
----------
|
|
2041
|
+
other : Expression
|
|
2042
|
+
The expression to which `self` is compared to.
|
|
2043
|
+
|
|
2044
|
+
Returns
|
|
2045
|
+
-------
|
|
2046
|
+
bool
|
|
2047
|
+
If the two expressions are equal.
|
|
2048
|
+
"""
|
|
2049
|
+
|
|
2050
|
+
@overload
|
|
2051
|
+
def encode(self, /) -> bytes: ...
|
|
2052
|
+
@overload
|
|
2053
|
+
def encode(self, /, *, compress: bool) -> bytes: ...
|
|
2054
|
+
@overload
|
|
2055
|
+
def encode(self, /, *, level: int) -> bytes: ...
|
|
2056
|
+
@overload
|
|
2057
|
+
def encode(self, /, compress: bool, level: int) -> bytes: ...
|
|
2058
|
+
def encode(self, /, compress: bool | None = ..., level: int | None = ...) -> bytes:
|
|
2059
|
+
"""
|
|
2060
|
+
Serialize the expression into a compact binary format.
|
|
2061
|
+
|
|
2062
|
+
Parameters
|
|
2063
|
+
----------
|
|
2064
|
+
compress : bool, optional
|
|
2065
|
+
Whether to compress the data. Default is True.
|
|
2066
|
+
level : int, optional
|
|
2067
|
+
Compression level (0–9). Default is 3.
|
|
2068
|
+
|
|
2069
|
+
Returns
|
|
2070
|
+
-------
|
|
2071
|
+
bytes
|
|
2072
|
+
Encoded representation of the expression.
|
|
2073
|
+
|
|
2074
|
+
Raises
|
|
2075
|
+
------
|
|
2076
|
+
IOError
|
|
2077
|
+
If serialization fails.
|
|
2078
|
+
"""
|
|
2079
|
+
|
|
2080
|
+
@overload
|
|
2081
|
+
def serialize(self, /) -> bytes: ...
|
|
2082
|
+
@overload
|
|
2083
|
+
def serialize(self, /, *, compress: bool) -> bytes: ...
|
|
2084
|
+
@overload
|
|
2085
|
+
def serialize(self, /, *, level: int) -> bytes: ...
|
|
2086
|
+
@overload
|
|
2087
|
+
def serialize(self, /, compress: bool, level: int) -> bytes: ...
|
|
2088
|
+
def serialize(
|
|
2089
|
+
self, /, compress: bool | None = ..., level: int | None = ...
|
|
2090
|
+
) -> bytes:
|
|
2091
|
+
"""
|
|
2092
|
+
Alias for `encode()`.
|
|
2093
|
+
|
|
2094
|
+
See `encode()` for full documentation.
|
|
2095
|
+
"""
|
|
2096
|
+
|
|
2097
|
+
@classmethod
|
|
2098
|
+
def decode(cls, data: bytes) -> Expression:
|
|
2099
|
+
"""
|
|
2100
|
+
Reconstruct an expression from encoded bytes.
|
|
2101
|
+
|
|
2102
|
+
Parameters
|
|
2103
|
+
----------
|
|
2104
|
+
data : bytes
|
|
2105
|
+
Binary blob returned by `encode()`.
|
|
2106
|
+
|
|
2107
|
+
Returns
|
|
2108
|
+
-------
|
|
2109
|
+
Expression
|
|
2110
|
+
Deserialized expression object.
|
|
2111
|
+
|
|
2112
|
+
Raises
|
|
2113
|
+
------
|
|
2114
|
+
DecodeError
|
|
2115
|
+
If decoding fails due to corruption or incompatibility.
|
|
2116
|
+
"""
|
|
2117
|
+
|
|
2118
|
+
@classmethod
|
|
2119
|
+
def deserialize(cls, data: bytes) -> Expression:
|
|
2120
|
+
"""
|
|
2121
|
+
Alias for `decode()`.
|
|
2122
|
+
|
|
2123
|
+
See `decode()` for full documentation.
|
|
2124
|
+
"""
|
|
2125
|
+
|
|
2126
|
+
@overload
|
|
2127
|
+
def __add__(self, other: Expression, /) -> Expression: ...
|
|
2128
|
+
@overload
|
|
2129
|
+
def __add__(self, other: Variable, /) -> Expression: ...
|
|
2130
|
+
@overload
|
|
2131
|
+
def __add__(self, other: int, /) -> Expression: ...
|
|
2132
|
+
@overload
|
|
2133
|
+
def __add__(self, other: float, /) -> Expression: ...
|
|
2134
|
+
def __add__(self, other: Expression | Variable | float, /) -> Expression:
|
|
2135
|
+
"""
|
|
2136
|
+
Add another expression, variable, or scalar.
|
|
2137
|
+
|
|
2138
|
+
Parameters
|
|
2139
|
+
----------
|
|
2140
|
+
other : Expression, Variable, int, or float
|
|
2141
|
+
|
|
2142
|
+
Returns
|
|
2143
|
+
-------
|
|
2144
|
+
Expression
|
|
2145
|
+
|
|
2146
|
+
Raises
|
|
2147
|
+
------
|
|
2148
|
+
VariablesFromDifferentEnvsError
|
|
2149
|
+
If operands are from different environments.
|
|
2150
|
+
TypeError
|
|
2151
|
+
If the operand type is unsupported.
|
|
2152
|
+
"""
|
|
2153
|
+
|
|
2154
|
+
@overload
|
|
2155
|
+
def __radd__(self, other: Expression, /) -> Expression: ...
|
|
2156
|
+
@overload
|
|
2157
|
+
def __radd__(self, other: Variable, /) -> Expression: ...
|
|
2158
|
+
@overload
|
|
2159
|
+
def __radd__(self, other: int, /) -> Expression: ...
|
|
2160
|
+
@overload
|
|
2161
|
+
def __radd__(self, other: float, /) -> Expression: ...
|
|
2162
|
+
def __radd__(self, other: Expression | Variable | float, /) -> Expression:
|
|
2163
|
+
"""
|
|
2164
|
+
Add this expression to a scalar or variable.
|
|
2165
|
+
|
|
2166
|
+
Parameters
|
|
2167
|
+
----------
|
|
2168
|
+
other : int, float, or Variable
|
|
2169
|
+
|
|
2170
|
+
Returns
|
|
2171
|
+
-------
|
|
2172
|
+
Expression
|
|
2173
|
+
|
|
2174
|
+
Raises
|
|
2175
|
+
------
|
|
2176
|
+
TypeError
|
|
2177
|
+
If the operand type is unsupported.
|
|
2178
|
+
"""
|
|
2179
|
+
|
|
2180
|
+
@overload
|
|
2181
|
+
def __iadd__(self, other: Expression, /): ...
|
|
2182
|
+
@overload
|
|
2183
|
+
def __iadd__(self, other: Variable, /): ...
|
|
2184
|
+
@overload
|
|
2185
|
+
def __iadd__(self, other: int, /): ...
|
|
2186
|
+
@overload
|
|
2187
|
+
def __iadd__(self, other: float, /): ...
|
|
2188
|
+
def __iadd__(self, other: Expression | Variable | float, /) -> Expression:
|
|
2189
|
+
"""
|
|
2190
|
+
In-place addition.
|
|
2191
|
+
|
|
2192
|
+
Parameters
|
|
2193
|
+
----------
|
|
2194
|
+
other : Expression, Variable, int, or float
|
|
2195
|
+
|
|
2196
|
+
Returns
|
|
2197
|
+
-------
|
|
2198
|
+
Expression
|
|
2199
|
+
|
|
2200
|
+
Raises
|
|
2201
|
+
------
|
|
2202
|
+
VariablesFromDifferentEnvsError
|
|
2203
|
+
If operands are from different environments.
|
|
2204
|
+
TypeError
|
|
2205
|
+
If the operand type is unsupported.
|
|
2206
|
+
"""
|
|
2207
|
+
|
|
2208
|
+
@overload
|
|
2209
|
+
def __isub__(self, other: Expression, /): ...
|
|
2210
|
+
@overload
|
|
2211
|
+
def __isub__(self, other: Variable, /): ...
|
|
2212
|
+
@overload
|
|
2213
|
+
def __isub__(self, other: int, /): ...
|
|
2214
|
+
@overload
|
|
2215
|
+
def __isub__(self, other: float, /): ...
|
|
2216
|
+
def __isub__(self, other: Expression | Variable | float, /):
|
|
2217
|
+
"""
|
|
2218
|
+
In-place subtraction.
|
|
2219
|
+
|
|
2220
|
+
Parameters
|
|
2221
|
+
----------
|
|
2222
|
+
other : Expression, Variable, int, or float
|
|
2223
|
+
|
|
2224
|
+
Returns
|
|
2225
|
+
-------
|
|
2226
|
+
Expression
|
|
2227
|
+
|
|
2228
|
+
Raises
|
|
2229
|
+
------
|
|
2230
|
+
VariablesFromDifferentEnvsError
|
|
2231
|
+
If operands are from different environments.
|
|
2232
|
+
TypeError
|
|
2233
|
+
If the operand type is unsupported.
|
|
2234
|
+
"""
|
|
2235
|
+
|
|
2236
|
+
@overload
|
|
2237
|
+
def __sub__(self, other: Expression, /) -> Expression: ...
|
|
2238
|
+
@overload
|
|
2239
|
+
def __sub__(self, other: Variable, /) -> Expression: ...
|
|
2240
|
+
@overload
|
|
2241
|
+
def __sub__(self, other: int, /) -> Expression: ...
|
|
2242
|
+
@overload
|
|
2243
|
+
def __sub__(self, other: float, /) -> Expression: ...
|
|
2244
|
+
def __sub__(self, other: Expression | Variable | float, /) -> Expression:
|
|
2245
|
+
"""
|
|
2246
|
+
Subtract another expression, variable, or scalar.
|
|
2247
|
+
|
|
2248
|
+
Parameters
|
|
2249
|
+
----------
|
|
2250
|
+
other : Expression, Variable, int, or float
|
|
2251
|
+
|
|
2252
|
+
Returns
|
|
2253
|
+
-------
|
|
2254
|
+
Expression
|
|
2255
|
+
|
|
2256
|
+
Raises
|
|
2257
|
+
------
|
|
2258
|
+
VariablesFromDifferentEnvsError
|
|
2259
|
+
If operands are from different environments.
|
|
2260
|
+
TypeError
|
|
2261
|
+
If the operand type is unsupported.
|
|
2262
|
+
"""
|
|
2263
|
+
|
|
2264
|
+
@overload
|
|
2265
|
+
def __mul__(self, other: Expression, /) -> Expression: ...
|
|
2266
|
+
@overload
|
|
2267
|
+
def __mul__(self, other: Variable, /) -> Expression: ...
|
|
2268
|
+
@overload
|
|
2269
|
+
def __mul__(self, other: int, /) -> Expression: ...
|
|
2270
|
+
@overload
|
|
2271
|
+
def __mul__(self, other: float, /) -> Expression: ...
|
|
2272
|
+
def __mul__(self, other: Expression | Variable | float, /) -> Expression:
|
|
2273
|
+
"""
|
|
2274
|
+
Multiply this expression by another value.
|
|
2275
|
+
|
|
2276
|
+
Parameters
|
|
2277
|
+
----------
|
|
2278
|
+
other : Expression, Variable, int, or float
|
|
2279
|
+
|
|
2280
|
+
Returns
|
|
2281
|
+
-------
|
|
2282
|
+
Expression
|
|
2283
|
+
|
|
2284
|
+
Raises
|
|
2285
|
+
------
|
|
2286
|
+
VariablesFromDifferentEnvsError
|
|
2287
|
+
If operands are from different environments.
|
|
2288
|
+
TypeError
|
|
2289
|
+
If the operand type is unsupported.
|
|
2290
|
+
"""
|
|
2291
|
+
|
|
2292
|
+
@overload
|
|
2293
|
+
def __rmul__(self, other: int, /) -> Expression: ...
|
|
2294
|
+
@overload
|
|
2295
|
+
def __rmul__(self, other: float, /) -> Expression: ...
|
|
2296
|
+
def __rmul__(self, other: float, /) -> Expression:
|
|
2297
|
+
"""
|
|
2298
|
+
Right-hand multiplication.
|
|
2299
|
+
|
|
2300
|
+
Parameters
|
|
2301
|
+
----------
|
|
2302
|
+
other : int or float
|
|
2303
|
+
|
|
2304
|
+
Returns
|
|
2305
|
+
-------
|
|
2306
|
+
Expression
|
|
2307
|
+
|
|
2308
|
+
Raises
|
|
2309
|
+
------
|
|
2310
|
+
TypeError
|
|
2311
|
+
If the operand type is unsupported.
|
|
2312
|
+
"""
|
|
2313
|
+
|
|
2314
|
+
@overload
|
|
2315
|
+
def __imul__(self, other: Expression, /): ...
|
|
2316
|
+
@overload
|
|
2317
|
+
def __imul__(self, other: Variable, /): ...
|
|
2318
|
+
@overload
|
|
2319
|
+
def __imul__(self, other: int, /): ...
|
|
2320
|
+
@overload
|
|
2321
|
+
def __imul__(self, other: float, /): ...
|
|
2322
|
+
def __imul__(self, other: Expression | Variable | float, /):
|
|
2323
|
+
"""
|
|
2324
|
+
In-place multiplication.
|
|
2325
|
+
|
|
2326
|
+
Parameters
|
|
2327
|
+
----------
|
|
2328
|
+
other : Expression, Variable, int, or float
|
|
2329
|
+
|
|
2330
|
+
Returns
|
|
2331
|
+
-------
|
|
2332
|
+
Expression
|
|
2333
|
+
|
|
2334
|
+
Raises
|
|
2335
|
+
------
|
|
2336
|
+
VariablesFromDifferentEnvsError
|
|
2337
|
+
If operands are from different environments.
|
|
2338
|
+
TypeError
|
|
2339
|
+
If the operand type is unsupported.
|
|
2340
|
+
"""
|
|
2341
|
+
|
|
2342
|
+
def __pow__(self, other: int, /) -> Expression:
|
|
2343
|
+
"""
|
|
2344
|
+
Raise the expression to the power specified by `other`.
|
|
2345
|
+
|
|
2346
|
+
Parameters
|
|
2347
|
+
----------
|
|
2348
|
+
other : int
|
|
2349
|
+
|
|
2350
|
+
Returns
|
|
2351
|
+
-------
|
|
2352
|
+
Expression
|
|
2353
|
+
|
|
2354
|
+
Raises
|
|
2355
|
+
------
|
|
2356
|
+
RuntimeError
|
|
2357
|
+
If the param `modulo` usually supported for `__pow__` is specified.
|
|
2358
|
+
"""
|
|
2359
|
+
|
|
2360
|
+
@overload
|
|
2361
|
+
def __eq__(self, rhs: Expression, /) -> Constraint: ...
|
|
2362
|
+
@overload
|
|
2363
|
+
def __eq__(self, rhs: Variable, /) -> Constraint: ...
|
|
2364
|
+
@overload
|
|
2365
|
+
def __eq__(self, rhs: int, /) -> Constraint: ... # type: ignore
|
|
2366
|
+
@overload
|
|
2367
|
+
def __eq__(self, rhs: float, /) -> Constraint: ... # type: ignore
|
|
2368
|
+
def __eq__(self, rhs: Expression | Variable | float, /) -> Constraint: # type: ignore
|
|
2369
|
+
"""
|
|
2370
|
+
Compare to a different expression or create a constraint `expression == scalar`
|
|
2371
|
+
|
|
2372
|
+
If `rhs` is of type `Variable` or `Expression` it is moved to the `lhs` in the
|
|
2373
|
+
constraint, resulting in the following constraint:
|
|
2374
|
+
|
|
2375
|
+
self - rhs == 0
|
|
2376
|
+
|
|
2377
|
+
Parameters
|
|
2378
|
+
----------
|
|
2379
|
+
rhs : Expression or float, int, Variable or Expression
|
|
2380
|
+
|
|
2381
|
+
Returns
|
|
2382
|
+
-------
|
|
2383
|
+
bool or Constraint
|
|
2384
|
+
|
|
2385
|
+
Raises
|
|
2386
|
+
------
|
|
2387
|
+
TypeError
|
|
2388
|
+
If the right-hand side is not an Expression or scalar.
|
|
2389
|
+
"""
|
|
2390
|
+
|
|
2391
|
+
@overload
|
|
2392
|
+
def __le__(self, rhs: Expression, /) -> Constraint: ...
|
|
2393
|
+
@overload
|
|
2394
|
+
def __le__(self, rhs: Variable, /) -> Constraint: ...
|
|
2395
|
+
@overload
|
|
2396
|
+
def __le__(self, rhs: int, /) -> Constraint: ...
|
|
2397
|
+
@overload
|
|
2398
|
+
def __le__(self, rhs: float, /) -> Constraint: ...
|
|
2399
|
+
def __le__(self, rhs: Expression | Variable | float, /) -> Constraint:
|
|
2400
|
+
"""
|
|
2401
|
+
Create a constraint `expression <= scalar`.
|
|
2402
|
+
|
|
2403
|
+
If `rhs` is of type `Variable` or `Expression` it is moved to the `lhs` in the
|
|
2404
|
+
constraint, resulting in the following constraint:
|
|
2405
|
+
|
|
2406
|
+
self - rhs <= 0
|
|
2407
|
+
|
|
2408
|
+
Parameters
|
|
2409
|
+
----------
|
|
2410
|
+
rhs : float, int, Variable or Expression
|
|
2411
|
+
|
|
2412
|
+
Returns
|
|
2413
|
+
-------
|
|
2414
|
+
Constraint
|
|
2415
|
+
|
|
2416
|
+
Raises
|
|
2417
|
+
------
|
|
2418
|
+
TypeError
|
|
2419
|
+
If the right-hand side is not of type float, int, Variable or Expression.
|
|
2420
|
+
"""
|
|
2421
|
+
|
|
2422
|
+
@overload
|
|
2423
|
+
def __ge__(self, rhs: Expression, /) -> Constraint: ...
|
|
2424
|
+
@overload
|
|
2425
|
+
def __ge__(self, rhs: Variable, /) -> Constraint: ...
|
|
2426
|
+
@overload
|
|
2427
|
+
def __ge__(self, rhs: int, /) -> Constraint: ...
|
|
2428
|
+
@overload
|
|
2429
|
+
def __ge__(self, rhs: float, /) -> Constraint: ...
|
|
2430
|
+
def __ge__(self, rhs: Expression | Variable | float, /) -> Constraint:
|
|
2431
|
+
"""
|
|
2432
|
+
Create a constraint: expression >= scalar.
|
|
2433
|
+
|
|
2434
|
+
If `rhs` is of type `Variable` or `Expression` it is moved to the `lhs` in the
|
|
2435
|
+
constraint, resulting in the following constraint:
|
|
2436
|
+
|
|
2437
|
+
self - rhs >= 0
|
|
2438
|
+
|
|
2439
|
+
Parameters
|
|
2440
|
+
----------
|
|
2441
|
+
rhs : float, int, Variable or Expression
|
|
2442
|
+
|
|
2443
|
+
Returns
|
|
2444
|
+
-------
|
|
2445
|
+
Constraint
|
|
2446
|
+
|
|
2447
|
+
Raises
|
|
2448
|
+
------
|
|
2449
|
+
TypeError
|
|
2450
|
+
If the right-hand side is not of type float, int, Variable or Expression.
|
|
2451
|
+
"""
|
|
2452
|
+
|
|
2453
|
+
def __neg__(self, /) -> Expression:
|
|
2454
|
+
"""
|
|
2455
|
+
Negate the expression, i.e., multiply it by `-1`.
|
|
2456
|
+
|
|
2457
|
+
Returns
|
|
2458
|
+
-------
|
|
2459
|
+
Expression
|
|
2460
|
+
"""
|
|
2461
|
+
|
|
2462
|
+
# _environment.pyi
|
|
2463
|
+
class Environment:
|
|
2464
|
+
"""
|
|
2465
|
+
Execution context for variable creation and expression scoping.
|
|
2466
|
+
|
|
2467
|
+
An `Environment` provides the symbolic scope in which `Variable` objects are defined.
|
|
2468
|
+
It is required for variable construction, and ensures consistency across expressions.
|
|
2469
|
+
The environment does **not** store constraints or expressions — it only facilitates
|
|
2470
|
+
their creation by acting as a context manager and anchor for `Variable` instances.
|
|
2471
|
+
|
|
2472
|
+
Environments are best used with `with` blocks, but can also be passed manually
|
|
2473
|
+
to models or variables.
|
|
2474
|
+
|
|
2475
|
+
Examples
|
|
2476
|
+
--------
|
|
2477
|
+
Create variables inside an environment:
|
|
2478
|
+
|
|
2479
|
+
>>> from luna_quantum import Environment, Variable
|
|
2480
|
+
>>> with Environment() as env:
|
|
2481
|
+
... x = Variable("x")
|
|
2482
|
+
... y = Variable("y")
|
|
2483
|
+
|
|
2484
|
+
Serialize the environment state:
|
|
2485
|
+
|
|
2486
|
+
>>> data = env.encode()
|
|
2487
|
+
>>> expr = Environment.decode(data)
|
|
2488
|
+
|
|
2489
|
+
Notes
|
|
2490
|
+
-----
|
|
2491
|
+
- The environment is required to create `Variable` instances.
|
|
2492
|
+
- It does **not** own constraints or expressions — they merely reference variables tied to an environment.
|
|
2493
|
+
- Environments **cannot be nested**. Only one can be active at a time.
|
|
2494
|
+
- Use `encode()` / `decode()` to persist and recover expression trees.
|
|
2495
|
+
"""
|
|
2496
|
+
|
|
2497
|
+
def __init__(self, /) -> None:
|
|
2498
|
+
"""
|
|
2499
|
+
Initialize a new environment for variable construction.
|
|
2500
|
+
|
|
2501
|
+
It is recommended to use this in a `with` statement to ensure proper scoping.
|
|
2502
|
+
"""
|
|
2503
|
+
|
|
2504
|
+
def __enter__(self, /) -> Any:
|
|
2505
|
+
"""
|
|
2506
|
+
Activate this environment for variable creation.
|
|
2507
|
+
|
|
2508
|
+
Returns
|
|
2509
|
+
-------
|
|
2510
|
+
Environment
|
|
2511
|
+
The current environment (self).
|
|
2512
|
+
|
|
2513
|
+
Raises
|
|
2514
|
+
------
|
|
2515
|
+
MultipleActiveEnvironmentsError
|
|
2516
|
+
If another environment is already active.
|
|
2517
|
+
"""
|
|
2518
|
+
|
|
2519
|
+
def __exit__(self, /, exc_type, exc_value, exc_traceback) -> None:
|
|
2520
|
+
"""
|
|
2521
|
+
Deactivate this environment.
|
|
2522
|
+
|
|
2523
|
+
Called automatically at the end of a `with` block.
|
|
2524
|
+
"""
|
|
2525
|
+
|
|
2526
|
+
def get_variable(self, /, name: str) -> Variable:
|
|
2527
|
+
"""
|
|
2528
|
+
Get a variable by its label (name).
|
|
2529
|
+
|
|
2530
|
+
Parameters
|
|
2531
|
+
----------
|
|
2532
|
+
name : str
|
|
2533
|
+
The name/label of the variable
|
|
2534
|
+
|
|
2535
|
+
Returns
|
|
2536
|
+
-------
|
|
2537
|
+
Variable
|
|
2538
|
+
The variable with the specified label/name.
|
|
2539
|
+
|
|
2540
|
+
Raises
|
|
2541
|
+
------
|
|
2542
|
+
VariableNotExistingError
|
|
2543
|
+
If no variable with the specified name is registered.
|
|
2544
|
+
"""
|
|
2545
|
+
|
|
2546
|
+
@overload
|
|
2547
|
+
def encode(self, /) -> bytes: ...
|
|
2548
|
+
@overload
|
|
2549
|
+
def encode(self, /, *, compress: bool) -> bytes: ...
|
|
2550
|
+
@overload
|
|
2551
|
+
def encode(self, /, *, level: int) -> bytes: ...
|
|
2552
|
+
@overload
|
|
2553
|
+
def encode(self, /, compress: bool, level: int) -> bytes: ...
|
|
2554
|
+
def encode(self, /, compress: bool | None = ..., level: int | None = ...) -> bytes:
|
|
2555
|
+
"""
|
|
2556
|
+
Serialize the environment into a compact binary format.
|
|
2557
|
+
|
|
2558
|
+
This is the preferred method for persisting an environment's state.
|
|
2559
|
+
|
|
2560
|
+
Parameters
|
|
2561
|
+
----------
|
|
2562
|
+
compress : bool, optional
|
|
2563
|
+
Whether to compress the binary output. Default is `True`.
|
|
2564
|
+
level : int, optional
|
|
2565
|
+
Compression level (e.g., from 0 to 9). Default is `3`.
|
|
2566
|
+
|
|
2567
|
+
Returns
|
|
2568
|
+
-------
|
|
2569
|
+
bytes
|
|
2570
|
+
Encoded binary representation of the environment.
|
|
2571
|
+
|
|
2572
|
+
Raises
|
|
2573
|
+
------
|
|
2574
|
+
IOError
|
|
2575
|
+
If serialization fails.
|
|
2576
|
+
"""
|
|
2577
|
+
|
|
2578
|
+
@overload
|
|
2579
|
+
def serialize(self, /) -> bytes: ...
|
|
2580
|
+
@overload
|
|
2581
|
+
def serialize(self, /, *, compress: bool) -> bytes: ...
|
|
2582
|
+
@overload
|
|
2583
|
+
def serialize(self, /, *, level: int) -> bytes: ...
|
|
2584
|
+
@overload
|
|
2585
|
+
def serialize(self, /, compress: bool, level: int) -> bytes: ...
|
|
2586
|
+
def serialize(
|
|
2587
|
+
self, /, compress: bool | None = ..., level: int | None = ...
|
|
2588
|
+
) -> bytes:
|
|
2589
|
+
"""
|
|
2590
|
+
Alias for `encode()`.
|
|
2591
|
+
|
|
2592
|
+
See `encode()` for full usage details.
|
|
2593
|
+
"""
|
|
2594
|
+
|
|
2595
|
+
@classmethod
|
|
2596
|
+
def decode(cls, data: bytes) -> Environment:
|
|
2597
|
+
"""
|
|
2598
|
+
Reconstruct an expression from a previously encoded binary blob.
|
|
2599
|
+
|
|
2600
|
+
Parameters
|
|
2601
|
+
----------
|
|
2602
|
+
data : bytes
|
|
2603
|
+
The binary data returned from `Environment.encode()`.
|
|
2604
|
+
|
|
2605
|
+
Returns
|
|
2606
|
+
-------
|
|
2607
|
+
Expression
|
|
2608
|
+
The reconstructed symbolic expression.
|
|
2609
|
+
|
|
2610
|
+
Raises
|
|
2611
|
+
------
|
|
2612
|
+
DecodeError
|
|
2613
|
+
If decoding fails due to corruption or incompatibility.
|
|
2614
|
+
"""
|
|
2615
|
+
|
|
2616
|
+
@classmethod
|
|
2617
|
+
def deserialize(cls, data: bytes) -> Environment:
|
|
2618
|
+
"""
|
|
2619
|
+
Alias for `decode()`.
|
|
2620
|
+
|
|
2621
|
+
See `decode()` for full usage details.
|
|
2622
|
+
"""
|
|
2623
|
+
|
|
2624
|
+
def __eq__(self, other: Environment, /) -> bool: ... # type: ignore
|
|
2625
|
+
|
|
2626
|
+
# _constraints.pyi
|
|
2627
|
+
class Comparator(Enum):
|
|
2628
|
+
"""
|
|
2629
|
+
Comparison operators used to define constraints.
|
|
2630
|
+
|
|
2631
|
+
This enum represents the logical relation between the left-hand side (LHS)
|
|
2632
|
+
and the right-hand side (RHS) of a constraint.
|
|
2633
|
+
|
|
2634
|
+
Attributes
|
|
2635
|
+
----------
|
|
2636
|
+
Eq : Comparator
|
|
2637
|
+
Equality constraint (==).
|
|
2638
|
+
Le : Comparator
|
|
2639
|
+
Less-than-or-equal constraint (<=).
|
|
2640
|
+
Ge : Comparator
|
|
2641
|
+
Greater-than-or-equal constraint (>=).
|
|
2642
|
+
|
|
2643
|
+
Examples
|
|
2644
|
+
--------
|
|
2645
|
+
>>> from luna_quantum import Comparator
|
|
2646
|
+
>>> str(Comparator.Eq)
|
|
2647
|
+
'=='
|
|
2648
|
+
"""
|
|
2649
|
+
|
|
2650
|
+
Eq = ...
|
|
2651
|
+
"""Equality (==)"""
|
|
2652
|
+
|
|
2653
|
+
Le = ...
|
|
2654
|
+
"""Less-than or equal (<=)"""
|
|
2655
|
+
|
|
2656
|
+
Ge = ...
|
|
2657
|
+
"""Greater-than or equal (>=)"""
|
|
2658
|
+
|
|
2659
|
+
class Constraint:
|
|
2660
|
+
"""
|
|
2661
|
+
A symbolic constraint formed by comparing an expression to a constant.
|
|
2662
|
+
|
|
2663
|
+
A `Constraint` captures a relation of the form:
|
|
2664
|
+
`expression comparator constant`, where the comparator is one of:
|
|
2665
|
+
`==`, `<=`, or `>=`.
|
|
2666
|
+
|
|
2667
|
+
While constraints are usually created by comparing an `Expression` to a scalar
|
|
2668
|
+
(e.g., `expr == 3.0`), they can also be constructed manually using this class.
|
|
2669
|
+
|
|
2670
|
+
Parameters
|
|
2671
|
+
----------
|
|
2672
|
+
lhs : Expression
|
|
2673
|
+
The left-hand side expression.
|
|
2674
|
+
rhs : float
|
|
2675
|
+
The scalar right-hand side value.
|
|
2676
|
+
comparator : Comparator
|
|
2677
|
+
The relation between lhs and rhs (e.g., `Comparator.Eq`).
|
|
2678
|
+
|
|
2679
|
+
Examples
|
|
2680
|
+
--------
|
|
2681
|
+
>>> from luna_quantum import Environment, Variable, Constraint, Comparator
|
|
2682
|
+
>>> with Environment():
|
|
2683
|
+
... x = Variable("x")
|
|
2684
|
+
... c = Constraint(x + 2, 5.0, Comparator.Eq)
|
|
2685
|
+
|
|
2686
|
+
Or create via comparison:
|
|
2687
|
+
|
|
2688
|
+
>>> expr = 2 * x + 1
|
|
2689
|
+
>>> c2 = expr <= 10.0
|
|
2690
|
+
"""
|
|
2691
|
+
|
|
2692
|
+
@overload
|
|
2693
|
+
def __init__(
|
|
2694
|
+
self, /, lhs: Expression, rhs: Expression, comparator: Comparator
|
|
2695
|
+
) -> None: ...
|
|
2696
|
+
@overload
|
|
2697
|
+
def __init__(
|
|
2698
|
+
self, /, lhs: Expression, rhs: Variable, comparator: Comparator
|
|
2699
|
+
) -> None: ...
|
|
2700
|
+
@overload
|
|
2701
|
+
def __init__(
|
|
2702
|
+
self, /, lhs: Expression, rhs: int, comparator: Comparator
|
|
2703
|
+
) -> None: ...
|
|
2704
|
+
@overload
|
|
2705
|
+
def __init__(
|
|
2706
|
+
self, /, lhs: Expression, rhs: float, comparator: Comparator
|
|
2707
|
+
) -> None: ...
|
|
2708
|
+
@overload
|
|
2709
|
+
def __init__(
|
|
2710
|
+
self, /, lhs: Expression, rhs: Expression, comparator: Comparator, name: str
|
|
2711
|
+
) -> None: ...
|
|
2712
|
+
@overload
|
|
2713
|
+
def __init__(
|
|
2714
|
+
self, /, lhs: Expression, rhs: Variable, comparator: Comparator, name: str
|
|
2715
|
+
) -> None: ...
|
|
2716
|
+
@overload
|
|
2717
|
+
def __init__(
|
|
2718
|
+
self, /, lhs: Expression, rhs: int, comparator: Comparator, name: str
|
|
2719
|
+
) -> None: ...
|
|
2720
|
+
@overload
|
|
2721
|
+
def __init__(
|
|
2722
|
+
self, /, lhs: Expression, rhs: float, comparator: Comparator, name: str
|
|
2723
|
+
) -> None: ...
|
|
2724
|
+
@overload
|
|
2725
|
+
def __init__(
|
|
2726
|
+
self, /, lhs: Variable, rhs: Expression, comparator: Comparator
|
|
2727
|
+
) -> None: ...
|
|
2728
|
+
@overload
|
|
2729
|
+
def __init__(
|
|
2730
|
+
self, /, lhs: Variable, rhs: Variable, comparator: Comparator
|
|
2731
|
+
) -> None: ...
|
|
2732
|
+
@overload
|
|
2733
|
+
def __init__(self, /, lhs: Variable, rhs: int, comparator: Comparator) -> None: ...
|
|
2734
|
+
@overload
|
|
2735
|
+
def __init__(
|
|
2736
|
+
self, /, lhs: Variable, rhs: float, comparator: Comparator
|
|
2737
|
+
) -> None: ...
|
|
2738
|
+
@overload
|
|
2739
|
+
def __init__(
|
|
2740
|
+
self, /, lhs: Variable, rhs: Expression, comparator: Comparator, name: str
|
|
2741
|
+
) -> None: ...
|
|
2742
|
+
@overload
|
|
2743
|
+
def __init__(
|
|
2744
|
+
self, /, lhs: Variable, rhs: Variable, comparator: Comparator, name: str
|
|
2745
|
+
) -> None: ...
|
|
2746
|
+
@overload
|
|
2747
|
+
def __init__(
|
|
2748
|
+
self, /, lhs: Variable, rhs: int, comparator: Comparator, name: str
|
|
2749
|
+
) -> None: ...
|
|
2750
|
+
@overload
|
|
2751
|
+
def __init__(
|
|
2752
|
+
self, /, lhs: Variable, rhs: float, comparator: Comparator, name: str
|
|
2753
|
+
) -> None: ...
|
|
2754
|
+
def __init__(
|
|
2755
|
+
self,
|
|
2756
|
+
/,
|
|
2757
|
+
lhs: Variable | Expression,
|
|
2758
|
+
rhs: float | Expression | Variable,
|
|
2759
|
+
comparator: Comparator,
|
|
2760
|
+
name: str,
|
|
2761
|
+
) -> None:
|
|
2762
|
+
"""
|
|
2763
|
+
Construct a new symbolic constraint.
|
|
2764
|
+
|
|
2765
|
+
Parameters
|
|
2766
|
+
----------
|
|
2767
|
+
lhs : Expression | Variable
|
|
2768
|
+
Left-hand side symbolic expression or variable.
|
|
2769
|
+
rhs : int | float | Expression | Variable
|
|
2770
|
+
Scalar right-hand side constant.
|
|
2771
|
+
comparator : Comparator
|
|
2772
|
+
Relational operator (e.g., Comparator.Eq, Comparator.Le).
|
|
2773
|
+
name : str
|
|
2774
|
+
The name of the constraint
|
|
2775
|
+
|
|
2776
|
+
Raises
|
|
2777
|
+
------
|
|
2778
|
+
TypeError
|
|
2779
|
+
If lhs is not an Expression or rhs is not a scalar float.
|
|
2780
|
+
IllegalConstraintNameError
|
|
2781
|
+
If the constraint is tried to be created with an illegal name.
|
|
2782
|
+
"""
|
|
2783
|
+
|
|
2784
|
+
@property
|
|
2785
|
+
def name(self, /) -> str | None:
|
|
2786
|
+
"""
|
|
2787
|
+
Get the name of the constraint.
|
|
2788
|
+
|
|
2789
|
+
Returns
|
|
2790
|
+
-------
|
|
2791
|
+
str, optional
|
|
2792
|
+
Returns the name of the constraint as a string or None if it is unnamed.
|
|
2793
|
+
"""
|
|
2794
|
+
|
|
2795
|
+
@property
|
|
2796
|
+
def lhs(self, /) -> Expression:
|
|
2797
|
+
"""
|
|
2798
|
+
Get the left-hand side of the constraint
|
|
2799
|
+
|
|
2800
|
+
Returns
|
|
2801
|
+
-------
|
|
2802
|
+
Expression
|
|
2803
|
+
The left-hand side expression.
|
|
2804
|
+
"""
|
|
2805
|
+
|
|
2806
|
+
@property
|
|
2807
|
+
def rhs(self, /) -> float:
|
|
2808
|
+
"""
|
|
2809
|
+
Get the right-hand side of the constraint
|
|
2810
|
+
|
|
2811
|
+
Returns
|
|
2812
|
+
-------
|
|
2813
|
+
float
|
|
2814
|
+
The right-hand side expression.
|
|
2815
|
+
"""
|
|
2816
|
+
|
|
2817
|
+
@property
|
|
2818
|
+
def comparator(self, /) -> Comparator:
|
|
2819
|
+
"""
|
|
2820
|
+
Get the comparator of the constraint
|
|
2821
|
+
|
|
2822
|
+
Returns
|
|
2823
|
+
-------
|
|
2824
|
+
Comparator
|
|
2825
|
+
The comparator of the constraint.
|
|
2826
|
+
"""
|
|
2827
|
+
|
|
2828
|
+
def __eq__(self, other: Constraint, /) -> bool: ... # type: ignore
|
|
2829
|
+
|
|
2830
|
+
class Constraints:
|
|
2831
|
+
"""
|
|
2832
|
+
A collection of symbolic constraints used to define a model.
|
|
2833
|
+
|
|
2834
|
+
The `Constraints` object serves as a container for individual `Constraint`
|
|
2835
|
+
instances. It supports adding constraints programmatically and exporting
|
|
2836
|
+
them for serialization.
|
|
2837
|
+
|
|
2838
|
+
Constraints are typically added using `add_constraint()` or the `+=` operator.
|
|
2839
|
+
|
|
2840
|
+
Examples
|
|
2841
|
+
--------
|
|
2842
|
+
>>> from luna_quantum import Constraints, Constraint, Environment, Variable
|
|
2843
|
+
>>> with Environment():
|
|
2844
|
+
... x = Variable("x")
|
|
2845
|
+
... c = Constraint(x + 1, 0.0, Comparator.Le)
|
|
2846
|
+
|
|
2847
|
+
>>> cs = Constraints()
|
|
2848
|
+
>>> cs.add_constraint(c)
|
|
2849
|
+
|
|
2850
|
+
>>> cs += x >= 1.0
|
|
2851
|
+
|
|
2852
|
+
Serialization:
|
|
2853
|
+
|
|
2854
|
+
>>> blob = cs.encode()
|
|
2855
|
+
>>> expr = Constraints.decode(blob)
|
|
2856
|
+
|
|
2857
|
+
Notes
|
|
2858
|
+
-----
|
|
2859
|
+
- This class does not check feasibility or enforce satisfaction.
|
|
2860
|
+
- Use `encode()`/`decode()` to serialize constraints alongside expressions.
|
|
2861
|
+
"""
|
|
2862
|
+
|
|
2863
|
+
def __init__(self, /) -> None: ...
|
|
2864
|
+
@overload
|
|
2865
|
+
def add_constraint(self, /, constraint: Constraint):
|
|
2866
|
+
"""
|
|
2867
|
+
Add a constraint to the collection.
|
|
2868
|
+
|
|
2869
|
+
Parameters
|
|
2870
|
+
----------
|
|
2871
|
+
constraint : Constraint
|
|
2872
|
+
The constraint to be added.
|
|
2873
|
+
name : str, optional
|
|
2874
|
+
The name of the constraint to be added.
|
|
2875
|
+
"""
|
|
2876
|
+
|
|
2877
|
+
@overload
|
|
2878
|
+
def add_constraint(self, /, constraint: Constraint, name: str): ...
|
|
2879
|
+
def add_constraint(self, /, constraint: Constraint, name: str | None = ...):
|
|
2880
|
+
"""
|
|
2881
|
+
Add a constraint to the collection.
|
|
2882
|
+
|
|
2883
|
+
Parameters
|
|
2884
|
+
----------
|
|
2885
|
+
constraint : Constraint
|
|
2886
|
+
The constraint to be added.
|
|
2887
|
+
name : str, optional
|
|
2888
|
+
The name of the constraint to be added.
|
|
2889
|
+
"""
|
|
2890
|
+
|
|
2891
|
+
@overload
|
|
2892
|
+
def encode(self, /) -> bytes: ...
|
|
2893
|
+
@overload
|
|
2894
|
+
def encode(self, /, *, compress: bool) -> bytes: ...
|
|
2895
|
+
@overload
|
|
2896
|
+
def encode(self, /, *, level: int) -> bytes: ...
|
|
2897
|
+
@overload
|
|
2898
|
+
def encode(self, /, compress: bool, level: int) -> bytes: ...
|
|
2899
|
+
def encode(self, /, compress: bool | None = ..., level: int | None = ...) -> bytes:
|
|
2900
|
+
"""
|
|
2901
|
+
Serialize the constraint collection to a binary blob.
|
|
2902
|
+
|
|
2903
|
+
Parameters
|
|
2904
|
+
----------
|
|
2905
|
+
compress : bool, optional
|
|
2906
|
+
Whether to compress the result. Default is True.
|
|
2907
|
+
level : int, optional
|
|
2908
|
+
Compression level (0–9). Default is 3.
|
|
2909
|
+
|
|
2910
|
+
Returns
|
|
2911
|
+
-------
|
|
2912
|
+
bytes
|
|
2913
|
+
Encoded representation of the constraints.
|
|
2914
|
+
|
|
2915
|
+
Raises
|
|
2916
|
+
------
|
|
2917
|
+
IOError
|
|
2918
|
+
If serialization fails.
|
|
2919
|
+
"""
|
|
2920
|
+
|
|
2921
|
+
@overload
|
|
2922
|
+
def serialize(self, /) -> bytes: ...
|
|
2923
|
+
@overload
|
|
2924
|
+
def serialize(self, /, *, compress: bool) -> bytes: ...
|
|
2925
|
+
@overload
|
|
2926
|
+
def serialize(self, /, *, level: int) -> bytes: ...
|
|
2927
|
+
@overload
|
|
2928
|
+
def serialize(self, /, compress: bool, level: int) -> bytes: ...
|
|
2929
|
+
def serialize(
|
|
2930
|
+
self, /, compress: bool | None = ..., level: int | None = ...
|
|
2931
|
+
) -> bytes:
|
|
2932
|
+
"""
|
|
2933
|
+
Alias for `encode()`.
|
|
2934
|
+
|
|
2935
|
+
See `encode()` for details.
|
|
2936
|
+
"""
|
|
2937
|
+
|
|
2938
|
+
@classmethod
|
|
2939
|
+
def decode(cls, data: bytes, env: Environment) -> Expression:
|
|
2940
|
+
"""
|
|
2941
|
+
Deserialize an expression from binary constraint data.
|
|
2942
|
+
|
|
2943
|
+
Parameters
|
|
2944
|
+
----------
|
|
2945
|
+
data : bytes
|
|
2946
|
+
Encoded blob from `encode()`.
|
|
2947
|
+
|
|
2948
|
+
Returns
|
|
2949
|
+
-------
|
|
2950
|
+
Expression
|
|
2951
|
+
Expression reconstructed from the constraint context.
|
|
2952
|
+
|
|
2953
|
+
Raises
|
|
2954
|
+
------
|
|
2955
|
+
DecodeError
|
|
2956
|
+
If decoding fails due to corruption or incompatibility.
|
|
2957
|
+
"""
|
|
2958
|
+
|
|
2959
|
+
@classmethod
|
|
2960
|
+
def deserialize(cls, data: bytes, env: Environment) -> Expression:
|
|
2961
|
+
"""
|
|
2962
|
+
Alias for `decode()`.
|
|
2963
|
+
|
|
2964
|
+
See `decode()` for usage.
|
|
2965
|
+
"""
|
|
2966
|
+
|
|
2967
|
+
@overload
|
|
2968
|
+
def __iadd__(self, constraint: Constraint, /): ...
|
|
2969
|
+
@overload
|
|
2970
|
+
def __iadd__(self, constraint: tuple[Constraint, str], /): ...
|
|
2971
|
+
def __iadd__(self, constraint: Constraint | tuple[Constraint, str], /):
|
|
2972
|
+
"""
|
|
2973
|
+
In-place constraint addition using `+=`.
|
|
2974
|
+
|
|
2975
|
+
Parameters
|
|
2976
|
+
----------
|
|
2977
|
+
constraint : Constraint | tuple[Constraint, str]
|
|
2978
|
+
The constraint to add.
|
|
2979
|
+
|
|
2980
|
+
Returns
|
|
2981
|
+
-------
|
|
2982
|
+
Constraints
|
|
2983
|
+
The updated collection.
|
|
2984
|
+
|
|
2985
|
+
Raises
|
|
2986
|
+
------
|
|
2987
|
+
TypeError
|
|
2988
|
+
If the value is not a `Constraint` or valid symbolic comparison.
|
|
2989
|
+
"""
|
|
2990
|
+
|
|
2991
|
+
def __eq__(self, other: Constraints, /) -> bool: ... # type: ignore
|
|
2992
|
+
def __getitem__(self, item: int, /) -> Constraint: ...
|
|
2993
|
+
|
|
2994
|
+
__all__ = [
|
|
2995
|
+
"Bounds",
|
|
2996
|
+
"Comparator",
|
|
2997
|
+
"Constraint",
|
|
2998
|
+
"Constraints",
|
|
2999
|
+
"Environment",
|
|
3000
|
+
"Expression",
|
|
3001
|
+
"Model",
|
|
3002
|
+
"Result",
|
|
3003
|
+
"ResultIterator",
|
|
3004
|
+
"ResultView",
|
|
3005
|
+
"Sample",
|
|
3006
|
+
"SampleIterator",
|
|
3007
|
+
"Samples",
|
|
3008
|
+
"SamplesIterator",
|
|
3009
|
+
"Sense",
|
|
3010
|
+
"Solution",
|
|
3011
|
+
"Timer",
|
|
3012
|
+
"Timing",
|
|
3013
|
+
"Variable",
|
|
3014
|
+
"Vtype",
|
|
3015
|
+
"errors",
|
|
3016
|
+
"translator",
|
|
3017
|
+
]
|