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.
Files changed (75) hide show
  1. classiq/__init__.py +3 -0
  2. classiq/_internals/api_wrapper.py +6 -26
  3. classiq/_internals/client.py +1 -9
  4. classiq/applications/chemistry/chemistry_model_constructor.py +1 -1
  5. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +26 -8
  6. classiq/applications/combinatorial_helpers/optimization_model.py +5 -1
  7. classiq/applications/combinatorial_helpers/pyomo_utils.py +106 -27
  8. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
  9. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -2
  10. classiq/applications/grover/grover_model_constructor.py +1 -1
  11. classiq/applications/libraries/qmci_library.py +2 -1
  12. classiq/execution/execution_session.py +66 -96
  13. classiq/execution/jobs.py +3 -9
  14. classiq/interface/_version.py +1 -1
  15. classiq/interface/backend/backend_preferences.py +8 -5
  16. classiq/interface/backend/pydantic_backend.py +1 -1
  17. classiq/interface/chemistry/operator.py +0 -204
  18. classiq/interface/execution/primitives.py +1 -0
  19. classiq/interface/generator/compiler_keywords.py +4 -0
  20. classiq/interface/generator/functions/type_name.py +6 -0
  21. classiq/interface/generator/generated_circuit_data.py +22 -7
  22. classiq/interface/generator/model/model.py +3 -0
  23. classiq/interface/generator/model/preferences/preferences.py +13 -0
  24. classiq/interface/generator/quantum_function_call.py +4 -2
  25. classiq/interface/model/handle_binding.py +50 -5
  26. classiq/interface/model/quantum_type.py +16 -0
  27. classiq/interface/server/routes.py +1 -3
  28. classiq/model_expansions/capturing/captured_vars.py +102 -19
  29. classiq/model_expansions/closure.py +19 -56
  30. classiq/model_expansions/function_builder.py +13 -8
  31. classiq/model_expansions/generative_functions.py +15 -1
  32. classiq/model_expansions/interpreter.py +94 -32
  33. classiq/model_expansions/model_tables.py +4 -0
  34. classiq/model_expansions/quantum_operations/call_emitter.py +61 -2
  35. classiq/model_expansions/quantum_operations/classicalif.py +1 -1
  36. classiq/model_expansions/quantum_operations/control.py +3 -10
  37. classiq/model_expansions/quantum_operations/emitter.py +1 -1
  38. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -2
  39. classiq/model_expansions/quantum_operations/repeat.py +4 -3
  40. classiq/model_expansions/scope.py +7 -1
  41. classiq/model_expansions/scope_initialization.py +34 -25
  42. classiq/model_expansions/transformers/var_splitter.py +57 -7
  43. classiq/open_library/__init__.py +4 -0
  44. classiq/open_library/functions/__init__.py +130 -0
  45. classiq/{qmod/builtins → open_library}/functions/amplitude_estimation.py +2 -2
  46. classiq/{qmod/builtins → open_library}/functions/discrete_sine_cosine_transform.py +6 -4
  47. classiq/{qmod/builtins → open_library}/functions/grover.py +2 -2
  48. classiq/{qmod/builtins → open_library}/functions/linear_pauli_rotation.py +1 -1
  49. classiq/{qmod/builtins → open_library}/functions/modular_exponentiation.py +2 -2
  50. classiq/{qmod/builtins → open_library}/functions/qpe.py +2 -2
  51. classiq/{qmod/builtins → open_library}/functions/state_preparation.py +6 -149
  52. classiq/{qmod/builtins → open_library}/functions/swap_test.py +1 -1
  53. classiq/open_library/functions/utility_functions.py +81 -0
  54. classiq/{qmod/builtins → open_library}/functions/variational.py +1 -1
  55. classiq/qmod/builtins/functions/__init__.py +4 -130
  56. classiq/qmod/builtins/functions/allocation.py +150 -0
  57. classiq/qmod/builtins/functions/arithmetic.py +0 -34
  58. classiq/qmod/builtins/functions/operators.py +0 -6
  59. classiq/qmod/create_model_function.py +8 -162
  60. classiq/qmod/generative.py +0 -16
  61. classiq/qmod/model_state_container.py +7 -0
  62. classiq/qmod/native/pretty_printer.py +10 -11
  63. classiq/qmod/pretty_print/pretty_printer.py +1 -1
  64. classiq/qmod/qfunc.py +11 -12
  65. classiq/qmod/qmod_variable.py +1 -3
  66. classiq/qmod/quantum_expandable.py +21 -0
  67. classiq/qmod/quantum_function.py +65 -3
  68. {classiq-0.62.0.dist-info → classiq-0.63.0.dist-info}/METADATA +1 -1
  69. {classiq-0.62.0.dist-info → classiq-0.63.0.dist-info}/RECORD +74 -71
  70. classiq/qmod/builtins/functions/utility_functions.py +0 -43
  71. /classiq/{qmod/builtins → open_library}/functions/hea.py +0 -0
  72. /classiq/{qmod/builtins → open_library}/functions/qaoa_penalty.py +0 -0
  73. /classiq/{qmod/builtins → open_library}/functions/qft_functions.py +0 -0
  74. /classiq/{qmod/builtins → open_library}/functions/qsvt.py +0 -0
  75. {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 Any, Callable, Optional, Union, cast
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
- # When the primitives are called, we always override the
168
- # classical_execution_code, and we don't want the conversion route to fail
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
- self, classical_execution_code: str, primitives_input: PrimitivesInput
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["classical_execution_code"] = classical_execution_code
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(classical_execution_code, execution_primitives_input)
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(classical_execution_code, execution_primitives_input)
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(classical_execution_code, execution_primitives_input)
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(classical_execution_code, execution_primitives_input)
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 EXECUTION_JOBS_NON_VERSIONED_FULL_PATH
22
+ from classiq.interface.server.routes import EXECUTION_JOBS_FULL_PATH
23
23
 
24
- from classiq._internals.api_wrapper import CLASSIQ_ACCEPT_HEADER, ApiWrapper
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=EXECUTION_JOBS_NON_VERSIONED_FULL_PATH,
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,
@@ -3,5 +3,5 @@ from packaging.version import Version
3
3
  # This file was generated automatically
4
4
  # Please don't track in version control (DONTTRACK)
5
5
 
6
- SEMVER_VERSION = '0.62.0'
6
+ SEMVER_VERSION = '0.63.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -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[pydantic_backend.PydanticAwsRoleArn] = pydantic.Field(
214
- description="ARN of the role to be assumed for execution on your Braket account."
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
- s3_bucket_name: Optional[str] = pydantic.Field(description="S3 Bucket Name")
217
- s3_folder: Optional[pydantic_backend.PydanticS3BucketKey] = pydantic.Field(
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)
@@ -2,3 +2,7 @@ EXPANDED_KEYWORD = "expanded__"
2
2
  CAPTURE_SUFFIX = "_captured__"
3
3
  LAMBDA_KEYWORD = "lambda__"
4
4
  INPLACE_ARITH_AUX_VAR_PREFIX = "result__temp__"
5
+
6
+
7
+ def generate_original_function_name(name: str) -> str:
8
+ return name.split(f"_{EXPANDED_KEYWORD}")[0]
@@ -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.model_copy(
201
- update=dict(
202
- absolute_qubits=_get_absolute_from_relative(
203
- self.absolute_qubits, child.relative_qubits
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)",
@@ -68,3 +68,6 @@ class ExecutionModel(ClassiqBaseModel):
68
68
  description="Mapping between a measured register name and its qmod type",
69
69
  default=dict(),
70
70
  )
71
+ register_filter_bitstrings: dict[str, list[str]] = pydantic.Field(
72
+ default_factory=dict,
73
+ )