classiq 0.61.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 (83) 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 +13 -2
  7. classiq/applications/combinatorial_helpers/pyomo_utils.py +143 -13
  8. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
  9. classiq/applications/combinatorial_optimization/combinatorial_problem.py +58 -23
  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 +12 -10
  14. classiq/interface/_version.py +1 -1
  15. classiq/interface/backend/backend_preferences.py +26 -5
  16. classiq/interface/backend/pydantic_backend.py +1 -1
  17. classiq/interface/backend/quantum_backend_providers.py +3 -1
  18. classiq/interface/chemistry/operator.py +0 -204
  19. classiq/interface/execution/primitives.py +1 -0
  20. classiq/interface/generator/compiler_keywords.py +4 -0
  21. classiq/interface/generator/copy.py +47 -0
  22. classiq/interface/generator/function_param_list_without_self_reference.py +2 -0
  23. classiq/interface/generator/functions/type_name.py +6 -0
  24. classiq/interface/generator/generated_circuit_data.py +22 -7
  25. classiq/interface/generator/model/model.py +3 -0
  26. classiq/interface/generator/model/preferences/preferences.py +14 -1
  27. classiq/interface/generator/quantum_function_call.py +4 -2
  28. classiq/interface/generator/types/compilation_metadata.py +2 -1
  29. classiq/interface/model/handle_binding.py +50 -5
  30. classiq/interface/model/quantum_type.py +16 -0
  31. classiq/interface/server/routes.py +1 -3
  32. classiq/model_expansions/capturing/captured_vars.py +114 -28
  33. classiq/model_expansions/closure.py +25 -65
  34. classiq/model_expansions/function_builder.py +19 -9
  35. classiq/model_expansions/generative_functions.py +16 -2
  36. classiq/model_expansions/interpreter.py +110 -66
  37. classiq/model_expansions/model_tables.py +4 -0
  38. classiq/model_expansions/quantum_operations/call_emitter.py +83 -20
  39. classiq/model_expansions/quantum_operations/classicalif.py +1 -1
  40. classiq/model_expansions/quantum_operations/control.py +3 -10
  41. classiq/model_expansions/quantum_operations/emitter.py +3 -4
  42. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -2
  43. classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -1
  44. classiq/model_expansions/quantum_operations/repeat.py +4 -3
  45. classiq/model_expansions/quantum_operations/shallow_emitter.py +9 -3
  46. classiq/model_expansions/scope.py +9 -13
  47. classiq/model_expansions/scope_initialization.py +34 -25
  48. classiq/model_expansions/transformers/var_splitter.py +57 -7
  49. classiq/open_library/__init__.py +4 -0
  50. classiq/open_library/functions/__init__.py +130 -0
  51. classiq/{qmod/builtins → open_library}/functions/amplitude_estimation.py +2 -2
  52. classiq/{qmod/builtins → open_library}/functions/discrete_sine_cosine_transform.py +6 -4
  53. classiq/{qmod/builtins → open_library}/functions/grover.py +2 -2
  54. classiq/{qmod/builtins → open_library}/functions/linear_pauli_rotation.py +1 -1
  55. classiq/{qmod/builtins → open_library}/functions/modular_exponentiation.py +2 -2
  56. classiq/{qmod/builtins → open_library}/functions/qpe.py +2 -2
  57. classiq/{qmod/builtins → open_library}/functions/state_preparation.py +6 -149
  58. classiq/{qmod/builtins → open_library}/functions/swap_test.py +1 -1
  59. classiq/open_library/functions/utility_functions.py +81 -0
  60. classiq/{qmod/builtins → open_library}/functions/variational.py +1 -1
  61. classiq/qmod/builtins/functions/__init__.py +4 -130
  62. classiq/qmod/builtins/functions/allocation.py +150 -0
  63. classiq/qmod/builtins/functions/arithmetic.py +0 -34
  64. classiq/qmod/builtins/functions/operators.py +0 -6
  65. classiq/qmod/builtins/operations.py +19 -80
  66. classiq/qmod/create_model_function.py +8 -162
  67. classiq/qmod/generative.py +0 -16
  68. classiq/qmod/model_state_container.py +7 -0
  69. classiq/qmod/native/pretty_printer.py +10 -11
  70. classiq/qmod/pretty_print/pretty_printer.py +1 -1
  71. classiq/qmod/python_classical_type.py +1 -5
  72. classiq/qmod/qfunc.py +11 -12
  73. classiq/qmod/qmod_variable.py +1 -3
  74. classiq/qmod/quantum_expandable.py +23 -1
  75. classiq/qmod/quantum_function.py +69 -7
  76. {classiq-0.61.0.dist-info → classiq-0.63.0.dist-info}/METADATA +2 -1
  77. {classiq-0.61.0.dist-info → classiq-0.63.0.dist-info}/RECORD +82 -78
  78. classiq/qmod/builtins/functions/utility_functions.py +0 -43
  79. /classiq/{qmod/builtins → open_library}/functions/hea.py +0 -0
  80. /classiq/{qmod/builtins → open_library}/functions/qaoa_penalty.py +0 -0
  81. /classiq/{qmod/builtins → open_library}/functions/qft_functions.py +0 -0
  82. /classiq/{qmod/builtins → open_library}/functions/qsvt.py +0 -0
  83. {classiq-0.61.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:
@@ -112,7 +109,15 @@ class ExecutionJob:
112
109
  )
113
110
  return cls(details)
114
111
 
115
- from_id = syncify_function(from_id_async)
112
+ # `syncify_function` doesn't work well for class methods, so I wrote `from_id`
113
+ # explicitly
114
+ @classmethod
115
+ def from_id(
116
+ cls,
117
+ id: str,
118
+ _http_client: Optional[httpx.AsyncClient] = None,
119
+ ) -> "ExecutionJob":
120
+ return syncify_function(cls.from_id_async)(id, _http_client=_http_client)
116
121
 
117
122
  @property
118
123
  def _job_id(self) -> JobID:
@@ -134,7 +139,6 @@ class ExecutionJob:
134
139
  self._result = (
135
140
  await ApiWrapper.call_get_execution_job_result(
136
141
  job_id=self._job_id,
137
- version=_JOB_RESULT_VERSION,
138
142
  http_client=_http_client,
139
143
  )
140
144
  ).results
@@ -265,9 +269,7 @@ class ExecutionJob:
265
269
  return None
266
270
 
267
271
  poller = JobPoller(
268
- base_url=EXECUTION_JOBS_NON_VERSIONED_FULL_PATH,
269
- use_versioned_url=False,
270
- additional_headers={CLASSIQ_ACCEPT_HEADER: _JOB_DETAILS_VERSION},
272
+ base_url=EXECUTION_JOBS_FULL_PATH,
271
273
  )
272
274
  await poller.poll(
273
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.61.0'
6
+ SEMVER_VERSION = '0.63.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -161,6 +161,20 @@ class ClassiqBackendPreferences(BackendPreferences):
161
161
  def is_nvidia_backend(self) -> bool:
162
162
  return self.backend_name in list(ClassiqNvidiaBackendNames)
163
163
 
164
+ # CAD-25390
165
+ @pydantic.field_validator("backend_name")
166
+ @classmethod
167
+ def _validate_nvidia_name_backwards_compatibility(cls, backend_name: str) -> str:
168
+ if backend_name == "nvidia_state_vector_simulator":
169
+ warnings.warn(
170
+ "The name 'nvidia_state_vector_simulator' is deprecated and "
171
+ "will be removed soon, no earlier than January 12th 2025. "
172
+ "Please use ClassiqNvidiaBackendNames.SIMULATOR instead",
173
+ ClassiqDeprecationWarning,
174
+ stacklevel=2,
175
+ )
176
+ return backend_name
177
+
164
178
 
165
179
  class AwsBackendPreferences(BackendPreferences):
166
180
  """
@@ -196,12 +210,19 @@ class AwsBackendPreferences(BackendPreferences):
196
210
  backend_service_provider: ProviderTypeVendor.AMAZON_BRAKET = pydantic.Field(
197
211
  default=ProviderVendor.AMAZON_BRAKET
198
212
  )
199
- aws_role_arn: pydantic_backend.PydanticAwsRoleArn = pydantic.Field(
200
- 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"
219
+ )
220
+ s3_folder: Optional[str] = pydantic.Field(
221
+ default=None, description="S3 Folder Path Within The S3 Bucket"
201
222
  )
202
- s3_bucket_name: str = pydantic.Field(description="S3 Bucket Name")
203
- s3_folder: pydantic_backend.PydanticS3BucketKey = pydantic.Field(
204
- description="S3 Folder Path Within The S3 Bucket"
223
+ run_through_classiq: bool = pydantic.Field(
224
+ default=False,
225
+ description="Run through Classiq's credentials while using user's allocated budget.",
205
226
  )
206
227
 
207
228
  @pydantic.field_validator("s3_bucket_name", mode="before")
@@ -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
 
@@ -170,7 +170,8 @@ class ClassiqNvidiaBackendNames(StrEnum):
170
170
  Classiq's Nvidia simulator backend names.
171
171
  """
172
172
 
173
- SIMULATOR = "nvidia_state_vector_simulator"
173
+ SIMULATOR = "nvidia_simulator"
174
+ SIMULATOR_STATEVECTOR = "nvidia_simulator_statevector"
174
175
 
175
176
 
176
177
  class IntelBackendNames(StrEnum):
@@ -186,6 +187,7 @@ class GoogleNvidiaBackendNames(StrEnum):
186
187
  """
187
188
 
188
189
  CUQUANTUM = "cuquantum"
190
+ CUQUANTUM_STATEVECTOR = "cuquantum_statevector"
189
191
 
190
192
 
191
193
  class AliceBobBackendNames(StrEnum):
@@ -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]