classiq 0.62.0__py3-none-any.whl → 0.63.0__py3-none-any.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.
- classiq/__init__.py +3 -0
- classiq/_internals/api_wrapper.py +6 -26
- classiq/_internals/client.py +1 -9
- classiq/applications/chemistry/chemistry_model_constructor.py +1 -1
- classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +26 -8
- classiq/applications/combinatorial_helpers/optimization_model.py +5 -1
- classiq/applications/combinatorial_helpers/pyomo_utils.py +106 -27
- classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
- classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -2
- classiq/applications/grover/grover_model_constructor.py +1 -1
- classiq/applications/libraries/qmci_library.py +2 -1
- classiq/execution/execution_session.py +66 -96
- classiq/execution/jobs.py +3 -9
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/backend_preferences.py +8 -5
- classiq/interface/backend/pydantic_backend.py +1 -1
- classiq/interface/chemistry/operator.py +0 -204
- classiq/interface/execution/primitives.py +1 -0
- classiq/interface/generator/compiler_keywords.py +4 -0
- classiq/interface/generator/functions/type_name.py +6 -0
- classiq/interface/generator/generated_circuit_data.py +22 -7
- classiq/interface/generator/model/model.py +3 -0
- classiq/interface/generator/model/preferences/preferences.py +13 -0
- classiq/interface/generator/quantum_function_call.py +4 -2
- classiq/interface/model/handle_binding.py +50 -5
- classiq/interface/model/quantum_type.py +16 -0
- classiq/interface/server/routes.py +1 -3
- classiq/model_expansions/capturing/captured_vars.py +102 -19
- classiq/model_expansions/closure.py +19 -56
- classiq/model_expansions/function_builder.py +13 -8
- classiq/model_expansions/generative_functions.py +15 -1
- classiq/model_expansions/interpreter.py +94 -32
- classiq/model_expansions/model_tables.py +4 -0
- classiq/model_expansions/quantum_operations/call_emitter.py +61 -2
- classiq/model_expansions/quantum_operations/classicalif.py +1 -1
- classiq/model_expansions/quantum_operations/control.py +3 -10
- classiq/model_expansions/quantum_operations/emitter.py +1 -1
- classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -2
- classiq/model_expansions/quantum_operations/repeat.py +4 -3
- classiq/model_expansions/scope.py +7 -1
- classiq/model_expansions/scope_initialization.py +34 -25
- classiq/model_expansions/transformers/var_splitter.py +57 -7
- classiq/open_library/__init__.py +4 -0
- classiq/open_library/functions/__init__.py +130 -0
- classiq/{qmod/builtins → open_library}/functions/amplitude_estimation.py +2 -2
- classiq/{qmod/builtins → open_library}/functions/discrete_sine_cosine_transform.py +6 -4
- classiq/{qmod/builtins → open_library}/functions/grover.py +2 -2
- classiq/{qmod/builtins → open_library}/functions/linear_pauli_rotation.py +1 -1
- classiq/{qmod/builtins → open_library}/functions/modular_exponentiation.py +2 -2
- classiq/{qmod/builtins → open_library}/functions/qpe.py +2 -2
- classiq/{qmod/builtins → open_library}/functions/state_preparation.py +6 -149
- classiq/{qmod/builtins → open_library}/functions/swap_test.py +1 -1
- classiq/open_library/functions/utility_functions.py +81 -0
- classiq/{qmod/builtins → open_library}/functions/variational.py +1 -1
- classiq/qmod/builtins/functions/__init__.py +4 -130
- classiq/qmod/builtins/functions/allocation.py +150 -0
- classiq/qmod/builtins/functions/arithmetic.py +0 -34
- classiq/qmod/builtins/functions/operators.py +0 -6
- classiq/qmod/create_model_function.py +8 -162
- classiq/qmod/generative.py +0 -16
- classiq/qmod/model_state_container.py +7 -0
- classiq/qmod/native/pretty_printer.py +10 -11
- classiq/qmod/pretty_print/pretty_printer.py +1 -1
- classiq/qmod/qfunc.py +11 -12
- classiq/qmod/qmod_variable.py +1 -3
- classiq/qmod/quantum_expandable.py +21 -0
- classiq/qmod/quantum_function.py +65 -3
- {classiq-0.62.0.dist-info → classiq-0.63.0.dist-info}/METADATA +1 -1
- {classiq-0.62.0.dist-info → classiq-0.63.0.dist-info}/RECORD +74 -71
- classiq/qmod/builtins/functions/utility_functions.py +0 -43
- /classiq/{qmod/builtins → open_library}/functions/hea.py +0 -0
- /classiq/{qmod/builtins → open_library}/functions/qaoa_penalty.py +0 -0
- /classiq/{qmod/builtins → open_library}/functions/qft_functions.py +0 -0
- /classiq/{qmod/builtins → open_library}/functions/qsvt.py +0 -0
- {classiq-0.62.0.dist-info → classiq-0.63.0.dist-info}/WHEEL +0 -0
@@ -2,12 +2,12 @@ import json
|
|
2
2
|
import random
|
3
3
|
from functools import cached_property
|
4
4
|
from types import TracebackType
|
5
|
-
from typing import
|
5
|
+
from typing import Callable, Optional, Union, cast
|
6
6
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
9
|
from classiq.interface.chemistry.operator import PauliOperator, pauli_integers_to_str
|
10
|
-
from classiq.interface.exceptions import ClassiqValueError
|
10
|
+
from classiq.interface.exceptions import ClassiqError, ClassiqValueError
|
11
11
|
from classiq.interface.execution.primitives import EstimateInput, PrimitivesInput
|
12
12
|
from classiq.interface.executor.execution_preferences import ExecutionPreferences
|
13
13
|
from classiq.interface.executor.result import (
|
@@ -15,15 +15,16 @@ from classiq.interface.executor.result import (
|
|
15
15
|
ExecutionDetails,
|
16
16
|
ParsedState,
|
17
17
|
)
|
18
|
+
from classiq.interface.generator.arith import number_utils
|
18
19
|
from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
|
19
20
|
from classiq.interface.generator.quantum_program import QuantumProgram
|
21
|
+
from classiq.interface.model.quantum_type import QuantumBit, QuantumNumeric
|
20
22
|
|
21
23
|
from classiq._internals import async_utils
|
22
24
|
from classiq._internals.api_wrapper import ApiWrapper
|
23
25
|
from classiq._internals.client import client
|
24
26
|
from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import (
|
25
27
|
_pauli_dict_to_pauli_terms,
|
26
|
-
_pauli_terms_to_qmod,
|
27
28
|
)
|
28
29
|
from classiq.execution.jobs import ExecutionJob
|
29
30
|
from classiq.qmod.builtins import PauliTerm
|
@@ -42,16 +43,6 @@ ParsedExecutionParameters = Optional[
|
|
42
43
|
]
|
43
44
|
|
44
45
|
|
45
|
-
SAVE_RESULT = "\nsave({'result': result})\n"
|
46
|
-
|
47
|
-
|
48
|
-
class SupportedPrimitives:
|
49
|
-
SAMPLE = "sample"
|
50
|
-
BATCH_SAMPLE = "batch_sample"
|
51
|
-
ESTIMATE = "estimate"
|
52
|
-
BATCH_ESTIMATE = "batch_estimate"
|
53
|
-
|
54
|
-
|
55
46
|
def _deserialize_program(program: Program) -> QuantumProgram:
|
56
47
|
return (
|
57
48
|
program
|
@@ -67,10 +58,6 @@ def hamiltonian_to_pauli_terms(hamiltonian: Hamiltonian) -> list[PauliTerm]:
|
|
67
58
|
return _pauli_dict_to_pauli_terms(cast(list[QmodPyStruct], hamiltonian))
|
68
59
|
|
69
60
|
|
70
|
-
def to_hamiltonian_str(hamiltonian: Hamiltonian) -> str:
|
71
|
-
return _pauli_terms_to_qmod(hamiltonian_to_pauli_terms(hamiltonian))
|
72
|
-
|
73
|
-
|
74
61
|
def _hamiltonian_to_pauli_operator(hamiltonian: Hamiltonian) -> PauliOperator:
|
75
62
|
pauli_list = [
|
76
63
|
(
|
@@ -82,14 +69,6 @@ def _hamiltonian_to_pauli_operator(hamiltonian: Hamiltonian) -> PauliOperator:
|
|
82
69
|
return PauliOperator(pauli_list=pauli_list)
|
83
70
|
|
84
71
|
|
85
|
-
def serialize(
|
86
|
-
item: Union[float, int, tuple[int, ...], tuple[float, ...]]
|
87
|
-
) -> Union[str, list]:
|
88
|
-
if isinstance(item, tuple):
|
89
|
-
return list(item)
|
90
|
-
return str(item)
|
91
|
-
|
92
|
-
|
93
72
|
def parse_params(params: ExecutionParams) -> ParsedExecutionParams:
|
94
73
|
result = {}
|
95
74
|
for key, values in params.items():
|
@@ -104,48 +83,6 @@ def parse_params(params: ExecutionParams) -> ParsedExecutionParams:
|
|
104
83
|
return result
|
105
84
|
|
106
85
|
|
107
|
-
def format_parameters(execution_params: ExecutionParameters) -> str:
|
108
|
-
parsed_parameters: ParsedExecutionParameters = None
|
109
|
-
if execution_params is None:
|
110
|
-
return ""
|
111
|
-
if isinstance(execution_params, dict):
|
112
|
-
parsed_parameters = parse_params(execution_params)
|
113
|
-
|
114
|
-
elif isinstance(execution_params, list):
|
115
|
-
parsed_parameters = [
|
116
|
-
parse_params(ep) if isinstance(ep, dict) else ep for ep in execution_params
|
117
|
-
]
|
118
|
-
|
119
|
-
execution_params = cast(ExecutionParams, parsed_parameters)
|
120
|
-
return json.dumps(execution_params, default=serialize, indent=2)
|
121
|
-
|
122
|
-
|
123
|
-
def create_estimate_execution_code(operation: str, **kwargs: Any) -> str:
|
124
|
-
hamiltonian = kwargs.get("hamiltonian", "")
|
125
|
-
parameters = kwargs.get("parameters", "")
|
126
|
-
return f"\nresult = {operation}([{hamiltonian}], {parameters})" + SAVE_RESULT
|
127
|
-
|
128
|
-
|
129
|
-
def create_sample_execution_code(operation: str, **kwargs: Any) -> str:
|
130
|
-
parameters = kwargs.get("parameters", "")
|
131
|
-
return f"\nresult = {operation}({parameters})" + SAVE_RESULT
|
132
|
-
|
133
|
-
|
134
|
-
operation_handlers: dict[str, Callable[[str], str]] = {
|
135
|
-
"estimate": create_estimate_execution_code,
|
136
|
-
"batch_estimate": create_estimate_execution_code,
|
137
|
-
"sample": create_sample_execution_code,
|
138
|
-
"batch_sample": create_sample_execution_code,
|
139
|
-
}
|
140
|
-
|
141
|
-
|
142
|
-
def generate_code_snippet(operation: str, **kwargs: Any) -> str:
|
143
|
-
handler = operation_handlers.get(operation)
|
144
|
-
if handler:
|
145
|
-
return handler(operation, **kwargs)
|
146
|
-
raise ClassiqValueError(f"Unsupported operation type: {operation}")
|
147
|
-
|
148
|
-
|
149
86
|
class ExecutionSession:
|
150
87
|
"""
|
151
88
|
A session for executing a quantum program.
|
@@ -164,9 +101,8 @@ class ExecutionSession:
|
|
164
101
|
):
|
165
102
|
self.program: QuantumProgram = _deserialize_program(quantum_program)
|
166
103
|
self.update_execution_preferences(execution_preferences)
|
167
|
-
#
|
168
|
-
#
|
169
|
-
# because cmain is expected in some cases
|
104
|
+
# We never use classical_execution_code in ExecutionSession, and we don't want
|
105
|
+
# the conversion route to fail because cmain is expected in some cases
|
170
106
|
self.program.model.classical_execution_code = "dummy"
|
171
107
|
|
172
108
|
self._random_seed = self.program.model.execution_preferences.random_seed
|
@@ -197,13 +133,10 @@ class ExecutionSession:
|
|
197
133
|
ApiWrapper.call_convert_quantum_program(self.program, self._async_client)
|
198
134
|
)
|
199
135
|
|
200
|
-
def _execute(
|
201
|
-
|
202
|
-
) -> ExecutionJob:
|
203
|
-
execution_input = self._execution_input.copy()
|
204
|
-
execution_input["execution_preferences"]["random_seed"] = self._random_seed
|
136
|
+
def _execute(self, primitives_input: PrimitivesInput) -> ExecutionJob:
|
137
|
+
primitives_input.random_seed = self._random_seed
|
205
138
|
self._random_seed = self._rng.randint(0, 2**32 - 1)
|
206
|
-
execution_input
|
139
|
+
execution_input = self._execution_input.copy()
|
207
140
|
# The use of `model_dump_json` is necessary for complex numbers serialization
|
208
141
|
execution_input["primitives_input"] = json.loads(
|
209
142
|
primitives_input.model_dump_json()
|
@@ -256,13 +189,10 @@ class ExecutionSession:
|
|
256
189
|
Returns:
|
257
190
|
The execution job.
|
258
191
|
"""
|
259
|
-
classical_execution_code = generate_code_snippet(
|
260
|
-
SupportedPrimitives.SAMPLE, parameters=format_parameters(parameters)
|
261
|
-
)
|
262
192
|
execution_primitives_input = PrimitivesInput(
|
263
193
|
sample=[parse_params(parameters)] if parameters is not None else [{}]
|
264
194
|
)
|
265
|
-
return self._execute(
|
195
|
+
return self._execute(execution_primitives_input)
|
266
196
|
|
267
197
|
def batch_sample(self, parameters: list[ExecutionParams]) -> list[ExecutionDetails]:
|
268
198
|
"""
|
@@ -290,13 +220,10 @@ class ExecutionSession:
|
|
290
220
|
Returns:
|
291
221
|
The execution job.
|
292
222
|
"""
|
293
|
-
classical_execution_code = generate_code_snippet(
|
294
|
-
SupportedPrimitives.BATCH_SAMPLE, parameters=format_parameters(parameters)
|
295
|
-
)
|
296
223
|
execution_primitives_input = PrimitivesInput(
|
297
224
|
sample=[parse_params(params) for params in parameters]
|
298
225
|
)
|
299
|
-
return self._execute(
|
226
|
+
return self._execute(execution_primitives_input)
|
300
227
|
|
301
228
|
def estimate(
|
302
229
|
self, hamiltonian: Hamiltonian, parameters: Optional[ExecutionParams] = None
|
@@ -330,11 +257,6 @@ class ExecutionSession:
|
|
330
257
|
Returns:
|
331
258
|
The execution job.
|
332
259
|
"""
|
333
|
-
classical_execution_code = generate_code_snippet(
|
334
|
-
SupportedPrimitives.ESTIMATE,
|
335
|
-
parameters=format_parameters(parameters),
|
336
|
-
hamiltonian=to_hamiltonian_str(hamiltonian),
|
337
|
-
)
|
338
260
|
execution_primitives_input = PrimitivesInput(
|
339
261
|
estimate=EstimateInput(
|
340
262
|
hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
|
@@ -343,7 +265,7 @@ class ExecutionSession:
|
|
343
265
|
),
|
344
266
|
)
|
345
267
|
)
|
346
|
-
return self._execute(
|
268
|
+
return self._execute(execution_primitives_input)
|
347
269
|
|
348
270
|
def batch_estimate(
|
349
271
|
self, hamiltonian: Hamiltonian, parameters: list[ExecutionParams]
|
@@ -377,18 +299,13 @@ class ExecutionSession:
|
|
377
299
|
Returns:
|
378
300
|
The execution job.
|
379
301
|
"""
|
380
|
-
classical_execution_code = generate_code_snippet(
|
381
|
-
SupportedPrimitives.BATCH_ESTIMATE,
|
382
|
-
parameters=format_parameters(parameters),
|
383
|
-
hamiltonian=to_hamiltonian_str(hamiltonian),
|
384
|
-
)
|
385
302
|
execution_primitives_input = PrimitivesInput(
|
386
303
|
estimate=EstimateInput(
|
387
304
|
hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
|
388
305
|
parameters=[parse_params(params) for params in parameters],
|
389
306
|
)
|
390
307
|
)
|
391
|
-
return self._execute(
|
308
|
+
return self._execute(execution_primitives_input)
|
392
309
|
|
393
310
|
def estimate_cost(
|
394
311
|
self,
|
@@ -427,3 +344,56 @@ class ExecutionSession:
|
|
427
344
|
if costs.size == 0:
|
428
345
|
return np.nan
|
429
346
|
return float(np.average(costs))
|
347
|
+
|
348
|
+
def set_measured_state_filter(
|
349
|
+
self,
|
350
|
+
output_name: str,
|
351
|
+
condition: Callable,
|
352
|
+
) -> None:
|
353
|
+
"""
|
354
|
+
EXPERIMENTAL
|
355
|
+
|
356
|
+
When simulating on a statevector simulator, emulate the behavior of postprocessing
|
357
|
+
by discarding amplitudes for which their states are "undesirable".
|
358
|
+
|
359
|
+
Args:
|
360
|
+
output_name: The name of the register to filter
|
361
|
+
condition: Filter out values of the statevector for which this callable is False
|
362
|
+
"""
|
363
|
+
if "_execution_input" in self.__dict__:
|
364
|
+
raise ClassiqError(
|
365
|
+
"set_measured_state_filter must be called before use of the first primitive (sample, estimate...)"
|
366
|
+
)
|
367
|
+
|
368
|
+
if output_name not in self.program.model.circuit_outputs:
|
369
|
+
raise ClassiqValueError(f"{output_name} is not an output of the model")
|
370
|
+
output_type = self.program.model.circuit_output_types[output_name].quantum_types
|
371
|
+
|
372
|
+
legal_bitstrings = []
|
373
|
+
|
374
|
+
if isinstance(output_type, QuantumBit):
|
375
|
+
if condition(0):
|
376
|
+
legal_bitstrings.append("0")
|
377
|
+
if condition(1):
|
378
|
+
legal_bitstrings.append("1")
|
379
|
+
elif isinstance(output_type, QuantumNumeric):
|
380
|
+
size = output_type.size_in_bits
|
381
|
+
for i in range(2**size):
|
382
|
+
number_string = f"{i:0{size}b}"
|
383
|
+
val = number_utils.binary_to_float_or_int(
|
384
|
+
number_string,
|
385
|
+
output_type.fraction_digits_value,
|
386
|
+
output_type.sign_value,
|
387
|
+
)
|
388
|
+
if condition(val):
|
389
|
+
legal_bitstrings.append(number_string)
|
390
|
+
if len(legal_bitstrings) > 1:
|
391
|
+
raise NotImplementedError(
|
392
|
+
"Filtering is only supported on a single value per model output"
|
393
|
+
)
|
394
|
+
else:
|
395
|
+
raise NotImplementedError(
|
396
|
+
"Filtering is only supported on QuantumBit and QuantumNumeric"
|
397
|
+
)
|
398
|
+
|
399
|
+
self.program.model.register_filter_bitstrings[output_name] = legal_bitstrings
|
classiq/execution/jobs.py
CHANGED
@@ -19,18 +19,15 @@ from classiq.interface.executor.result import (
|
|
19
19
|
MultipleExecutionDetails,
|
20
20
|
)
|
21
21
|
from classiq.interface.jobs import JobStatus, JSONObject
|
22
|
-
from classiq.interface.server.routes import
|
22
|
+
from classiq.interface.server.routes import EXECUTION_JOBS_FULL_PATH
|
23
23
|
|
24
|
-
from classiq._internals.api_wrapper import
|
24
|
+
from classiq._internals.api_wrapper import ApiWrapper
|
25
25
|
from classiq._internals.async_utils import syncify_function
|
26
26
|
from classiq._internals.client import client
|
27
27
|
from classiq._internals.jobs import JobID, JobPoller
|
28
28
|
|
29
29
|
_JobDetails = Union[ExecutionJobDetails, ExecutionJobDetailsV1]
|
30
30
|
|
31
|
-
_JOB_DETAILS_VERSION = "v1"
|
32
|
-
_JOB_RESULT_VERSION = "v1"
|
33
|
-
|
34
31
|
|
35
32
|
class ClassiqExecutionResultError(ClassiqError):
|
36
33
|
def __init__(self, primitive: str) -> None:
|
@@ -142,7 +139,6 @@ class ExecutionJob:
|
|
142
139
|
self._result = (
|
143
140
|
await ApiWrapper.call_get_execution_job_result(
|
144
141
|
job_id=self._job_id,
|
145
|
-
version=_JOB_RESULT_VERSION,
|
146
142
|
http_client=_http_client,
|
147
143
|
)
|
148
144
|
).results
|
@@ -273,9 +269,7 @@ class ExecutionJob:
|
|
273
269
|
return None
|
274
270
|
|
275
271
|
poller = JobPoller(
|
276
|
-
base_url=
|
277
|
-
use_versioned_url=False,
|
278
|
-
additional_headers={CLASSIQ_ACCEPT_HEADER: _JOB_DETAILS_VERSION},
|
272
|
+
base_url=EXECUTION_JOBS_FULL_PATH,
|
279
273
|
)
|
280
274
|
await poller.poll(
|
281
275
|
job_id=self._job_id,
|
classiq/interface/_version.py
CHANGED
@@ -210,12 +210,15 @@ class AwsBackendPreferences(BackendPreferences):
|
|
210
210
|
backend_service_provider: ProviderTypeVendor.AMAZON_BRAKET = pydantic.Field(
|
211
211
|
default=ProviderVendor.AMAZON_BRAKET
|
212
212
|
)
|
213
|
-
aws_role_arn: Optional[
|
214
|
-
|
213
|
+
aws_role_arn: Optional[str] = pydantic.Field(
|
214
|
+
default=None,
|
215
|
+
description="ARN of the role to be assumed for execution on your Braket account.",
|
216
|
+
)
|
217
|
+
s3_bucket_name: Optional[str] = pydantic.Field(
|
218
|
+
default=None, description="S3 Bucket Name"
|
215
219
|
)
|
216
|
-
|
217
|
-
|
218
|
-
description="S3 Folder Path Within The S3 Bucket"
|
220
|
+
s3_folder: Optional[str] = pydantic.Field(
|
221
|
+
default=None, description="S3 Folder Path Within The S3 Bucket"
|
219
222
|
)
|
220
223
|
run_through_classiq: bool = pydantic.Field(
|
221
224
|
default=False,
|
@@ -8,7 +8,7 @@ _IONQ_API_KEY_LENGTH: int = 32
|
|
8
8
|
_ALICE_BOB_API_KEY_LENGTH: int = 72
|
9
9
|
INVALID_API_KEY: str = _IONQ_API_KEY_LENGTH * "a"
|
10
10
|
INVALID_EMAIL_OQC: str = "aa@aa.aa"
|
11
|
-
INVALID_PASSWORD_OQC: str = "Aa1!Aa1!"
|
11
|
+
INVALID_PASSWORD_OQC: str = "Aa1!Aa1!" # noqa: S105
|
12
12
|
|
13
13
|
EXECUTION_PARAMETER_PATTERN = "[_a-z][_a-z0-9]*"
|
14
14
|
|
@@ -260,210 +260,6 @@ class PauliOperator(HashablePydanticBaseModel, VersionedModel):
|
|
260
260
|
model_config = ConfigDict(frozen=True)
|
261
261
|
|
262
262
|
|
263
|
-
class PauliOperatorV1(HashablePydanticBaseModel):
|
264
|
-
"""
|
265
|
-
Specification of a Pauli sum operator.
|
266
|
-
"""
|
267
|
-
|
268
|
-
pauli_list: PydanticPauliList = pydantic.Field(
|
269
|
-
description="A list of tuples each containing a pauli string comprised of I,X,Y,Z characters and a complex coefficient; for example [('IZ', 0.1), ('XY', 0.2)].",
|
270
|
-
)
|
271
|
-
is_hermitian: bool = pydantic.Field(default=False)
|
272
|
-
has_complex_coefficients: bool = pydantic.Field(default=True)
|
273
|
-
|
274
|
-
def show(self) -> str:
|
275
|
-
if self.is_hermitian:
|
276
|
-
# If the operator is hermitian then the coefficients must be numeric
|
277
|
-
return "\n".join(
|
278
|
-
f"{summand[1].real:+.3f} * {summand[0]}" for summand in self.pauli_list # type: ignore[union-attr]
|
279
|
-
)
|
280
|
-
return "\n".join(
|
281
|
-
f"+({summand[1]:+.3f}) * {summand[0]}" for summand in self.pauli_list
|
282
|
-
)
|
283
|
-
|
284
|
-
@pydantic.field_validator("pauli_list", mode="before")
|
285
|
-
@classmethod
|
286
|
-
def _validate_pauli_monomials(
|
287
|
-
cls, pauli_list: PydanticPauliList
|
288
|
-
) -> PydanticPauliList:
|
289
|
-
validated_pauli_list = []
|
290
|
-
for monomial in pauli_list:
|
291
|
-
# Validate the length
|
292
|
-
_PauliMonomialLengthValidator(monomial=monomial) # type: ignore[call-arg]
|
293
|
-
coeff = cls._validate_monomial_coefficient(monomial[1])
|
294
|
-
parsed_monomial = _PauliMonomialParser(string=monomial[0], coeff=coeff)
|
295
|
-
validated_pauli_list.append((parsed_monomial.string, parsed_monomial.coeff))
|
296
|
-
return validated_pauli_list
|
297
|
-
|
298
|
-
@staticmethod
|
299
|
-
def _validate_monomial_coefficient(
|
300
|
-
coeff: Union[sympy.Expr, ParameterComplexType]
|
301
|
-
) -> ParameterComplexType:
|
302
|
-
if isinstance(coeff, str):
|
303
|
-
validate_expression_str(coeff)
|
304
|
-
elif isinstance(coeff, sympy.Expr):
|
305
|
-
coeff = str(coeff)
|
306
|
-
return coeff
|
307
|
-
|
308
|
-
@pydantic.field_validator("pauli_list", mode="after")
|
309
|
-
@classmethod
|
310
|
-
def _validate_pauli_list(cls, pauli_list: PydanticPauliList) -> PydanticPauliList:
|
311
|
-
if not all_equal(len(summand[0]) for summand in pauli_list):
|
312
|
-
raise ClassiqValueError("Pauli strings have incompatible lengths.")
|
313
|
-
return pauli_list
|
314
|
-
|
315
|
-
@pydantic.model_validator(mode="before")
|
316
|
-
@classmethod
|
317
|
-
def _validate_hermitianity(cls, v: dict[str, Any]) -> dict[str, Any]:
|
318
|
-
pauli_list = cast(PydanticPauliList, v.get("pauli_list"))
|
319
|
-
if all(isinstance(summand[1], complex) for summand in pauli_list):
|
320
|
-
v["is_hermitian"] = all(
|
321
|
-
np.isclose(complex(summand[1]).real, summand[1])
|
322
|
-
for summand in pauli_list
|
323
|
-
)
|
324
|
-
if v["is_hermitian"]:
|
325
|
-
v["has_complex_coefficients"] = False
|
326
|
-
v["pauli_list"] = [
|
327
|
-
(summand[0], complex(summand[1]).real) for summand in pauli_list
|
328
|
-
]
|
329
|
-
else:
|
330
|
-
v["has_complex_coefficients"] = not all(
|
331
|
-
np.isclose(complex(summand[1]).real, summand[1])
|
332
|
-
for summand in pauli_list
|
333
|
-
if isinstance(summand[1], complex)
|
334
|
-
)
|
335
|
-
return v
|
336
|
-
|
337
|
-
def __mul__(self, coefficient: complex) -> "PauliOperatorV1":
|
338
|
-
multiplied_ising = [
|
339
|
-
(monomial[0], self._multiply_monomial_coefficient(monomial[1], coefficient))
|
340
|
-
for monomial in self.pauli_list
|
341
|
-
]
|
342
|
-
return self.__class__(pauli_list=multiplied_ising)
|
343
|
-
|
344
|
-
@staticmethod
|
345
|
-
def _multiply_monomial_coefficient(
|
346
|
-
monomial_coefficient: ParameterComplexType, coefficient: complex
|
347
|
-
) -> ParameterComplexType:
|
348
|
-
if isinstance(monomial_coefficient, ParameterType):
|
349
|
-
return str(sympy.sympify(monomial_coefficient) * coefficient)
|
350
|
-
return monomial_coefficient * coefficient
|
351
|
-
|
352
|
-
@property
|
353
|
-
def is_commutative(self) -> bool:
|
354
|
-
return all(
|
355
|
-
self._is_sub_pauli_commutative(
|
356
|
-
[summand[0][qubit_num] for summand in self.pauli_list]
|
357
|
-
)
|
358
|
-
for qubit_num in range(self.num_qubits)
|
359
|
-
)
|
360
|
-
|
361
|
-
@staticmethod
|
362
|
-
def _is_sub_pauli_commutative(qubit_pauli_string: Union[list[str], str]) -> bool:
|
363
|
-
unique_paulis = set(qubit_pauli_string) - {"I"}
|
364
|
-
return len(unique_paulis) <= 1
|
365
|
-
|
366
|
-
@property
|
367
|
-
def num_qubits(self) -> int:
|
368
|
-
return len(self.pauli_list[0][0])
|
369
|
-
|
370
|
-
def to_matrix(self) -> np.ndarray:
|
371
|
-
if not all(isinstance(summand[1], complex) for summand in self.pauli_list):
|
372
|
-
raise ClassiqValueError(
|
373
|
-
"Supporting only Hamiltonian with numeric coefficients."
|
374
|
-
)
|
375
|
-
return sum(
|
376
|
-
cast(complex, summand[1]) * to_pauli_matrix(summand[0])
|
377
|
-
for summand in self.pauli_list
|
378
|
-
) # type: ignore[return-value]
|
379
|
-
|
380
|
-
@staticmethod
|
381
|
-
def _extend_pauli_string(
|
382
|
-
pauli_string: PydanticPauliMonomialStr, num_extra_qubits: int
|
383
|
-
) -> PydanticPauliMonomialStr:
|
384
|
-
return "I" * num_extra_qubits + pauli_string
|
385
|
-
|
386
|
-
def extend(self, num_extra_qubits: int) -> "PauliOperatorV1":
|
387
|
-
new_pauli_list = [
|
388
|
-
(self._extend_pauli_string(pauli_string, num_extra_qubits), coeff)
|
389
|
-
for (pauli_string, coeff) in self.pauli_list
|
390
|
-
]
|
391
|
-
return self.model_copy(update={"pauli_list": new_pauli_list}, deep=True)
|
392
|
-
|
393
|
-
@staticmethod
|
394
|
-
def _reorder_pauli_string(
|
395
|
-
pauli_string: PydanticPauliMonomialStr,
|
396
|
-
order: Collection[int],
|
397
|
-
new_num_qubits: int,
|
398
|
-
) -> PydanticPauliMonomialStr:
|
399
|
-
reversed_pauli_string = pauli_string[::-1]
|
400
|
-
reversed_new_pauli_string = ["I"] * new_num_qubits
|
401
|
-
|
402
|
-
for logical_pos, actual_pos in enumerate(order):
|
403
|
-
reversed_new_pauli_string[actual_pos] = reversed_pauli_string[logical_pos]
|
404
|
-
|
405
|
-
return "".join(reversed(reversed_new_pauli_string))
|
406
|
-
|
407
|
-
@staticmethod
|
408
|
-
def _validate_reorder(
|
409
|
-
order: Collection[int],
|
410
|
-
num_qubits: int,
|
411
|
-
num_extra_qubits: int,
|
412
|
-
) -> None:
|
413
|
-
if num_extra_qubits < 0:
|
414
|
-
raise ClassiqValueError("Number of extra qubits cannot be negative")
|
415
|
-
|
416
|
-
if len(order) != num_qubits:
|
417
|
-
raise ClassiqValueError("The qubits order doesn't match the Pauli operator")
|
418
|
-
|
419
|
-
if len(order) != len(set(order)):
|
420
|
-
raise ClassiqValueError("The qubits order is not one-to-one")
|
421
|
-
|
422
|
-
if not all(pos < num_qubits + num_extra_qubits for pos in order):
|
423
|
-
raise ClassiqValueError(
|
424
|
-
"The qubits order contains qubits which do no exist"
|
425
|
-
)
|
426
|
-
|
427
|
-
@classmethod
|
428
|
-
def reorder(
|
429
|
-
cls,
|
430
|
-
operator: "PauliOperatorV1",
|
431
|
-
order: Collection[int],
|
432
|
-
num_extra_qubits: int = 0,
|
433
|
-
) -> "PauliOperatorV1":
|
434
|
-
cls._validate_reorder(order, operator.num_qubits, num_extra_qubits)
|
435
|
-
|
436
|
-
new_num_qubits = operator.num_qubits + num_extra_qubits
|
437
|
-
new_pauli_list = [
|
438
|
-
(cls._reorder_pauli_string(pauli_string, order, new_num_qubits), coeff)
|
439
|
-
for pauli_string, coeff in operator.pauli_list
|
440
|
-
]
|
441
|
-
return cls(pauli_list=new_pauli_list)
|
442
|
-
|
443
|
-
@classmethod
|
444
|
-
def from_unzipped_lists(
|
445
|
-
cls,
|
446
|
-
operators: list[list["Pauli"]],
|
447
|
-
coefficients: Optional[list[complex]] = None,
|
448
|
-
) -> "PauliOperatorV1":
|
449
|
-
if coefficients is None:
|
450
|
-
coefficients = [1] * len(operators)
|
451
|
-
|
452
|
-
if len(operators) != len(coefficients):
|
453
|
-
raise ClassiqValueError(
|
454
|
-
f"The number of coefficients ({len(coefficients)}) must be equal to the number of pauli operators ({len(operators)})"
|
455
|
-
)
|
456
|
-
|
457
|
-
return cls(
|
458
|
-
pauli_list=[
|
459
|
-
(pauli_integers_to_str(op), coeff)
|
460
|
-
for op, coeff in zip(operators, coefficients)
|
461
|
-
]
|
462
|
-
)
|
463
|
-
|
464
|
-
model_config = ConfigDict(frozen=True)
|
465
|
-
|
466
|
-
|
467
263
|
# This class validates the length of a monomial.
|
468
264
|
@pydantic.dataclasses.dataclass
|
469
265
|
class _PauliMonomialLengthValidator:
|
@@ -15,3 +15,4 @@ class EstimateInput(BaseModel, json_encoders=CUSTOM_ENCODERS):
|
|
15
15
|
class PrimitivesInput(BaseModel, json_encoders=CUSTOM_ENCODERS):
|
16
16
|
sample: Optional[list[Arguments]] = Field(default=None)
|
17
17
|
estimate: Optional[EstimateInput] = Field(default=None)
|
18
|
+
random_seed: Optional[int] = Field(default=None)
|
@@ -69,6 +69,12 @@ class TypeName(ClassicalType, QuantumType):
|
|
69
69
|
def set_fields(self, fields: Mapping[str, "ConcreteQuantumType"]) -> None:
|
70
70
|
self._assigned_fields = fields
|
71
71
|
|
72
|
+
@property
|
73
|
+
def is_instantiated(self) -> bool:
|
74
|
+
return self.has_fields and all(
|
75
|
+
field_type.is_instantiated for field_type in self.fields.values()
|
76
|
+
)
|
77
|
+
|
72
78
|
|
73
79
|
class Enum(TypeName):
|
74
80
|
pass
|
@@ -197,14 +197,16 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
197
197
|
|
198
198
|
updated_children: list[FunctionDebugInfoInterface] = []
|
199
199
|
for child in self.children:
|
200
|
-
updated_child = child.
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
200
|
+
updated_child = child.white_new_absolute_qubits(self.absolute_qubits)
|
201
|
+
if updated_child.override_debug_info is None:
|
202
|
+
updated_child = updated_child.propagate_absolute_qubits()
|
203
|
+
else:
|
204
|
+
updated_child.override_debug_info = (
|
205
|
+
updated_child.override_debug_info.white_new_absolute_qubits(
|
206
|
+
absolute_qubits=self.absolute_qubits
|
207
|
+
).propagate_absolute_qubits()
|
205
208
|
)
|
206
|
-
)
|
207
|
-
updated_children.append(updated_child.propagate_absolute_qubits())
|
209
|
+
updated_children.append(updated_child)
|
208
210
|
|
209
211
|
return self.model_copy(
|
210
212
|
update=dict(
|
@@ -213,6 +215,17 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
213
215
|
)
|
214
216
|
)
|
215
217
|
|
218
|
+
def white_new_absolute_qubits(
|
219
|
+
self, absolute_qubits: tuple[int, ...]
|
220
|
+
) -> "FunctionDebugInfoInterface":
|
221
|
+
return self.model_copy(
|
222
|
+
update=dict(
|
223
|
+
absolute_qubits=_get_absolute_from_relative(
|
224
|
+
absolute_qubits, self.relative_qubits
|
225
|
+
)
|
226
|
+
)
|
227
|
+
)
|
228
|
+
|
216
229
|
def inverse(self) -> "FunctionDebugInfoInterface":
|
217
230
|
inverted_children = [child.inverse() for child in self.children[::-1]]
|
218
231
|
return self.model_copy(
|
@@ -226,6 +239,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
226
239
|
def _get_absolute_from_relative(
|
227
240
|
absolute_qubits: tuple[int, ...], relative_qubits: tuple[int, ...]
|
228
241
|
) -> tuple[int, ...]:
|
242
|
+
if len(relative_qubits) == 0:
|
243
|
+
return tuple()
|
229
244
|
if max(relative_qubits) >= len(absolute_qubits):
|
230
245
|
_logger.warning(
|
231
246
|
"Invalid qubit computation (relative qubits: %s, absolute qubits: %s)",
|