classiq 0.84.0__py3-none-any.whl → 0.86.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 (87) hide show
  1. classiq/applications/combinatorial_optimization/combinatorial_problem.py +24 -45
  2. classiq/evaluators/classical_expression.py +32 -15
  3. classiq/evaluators/qmod_annotated_expression.py +207 -0
  4. classiq/evaluators/qmod_expression_visitors/__init__.py +0 -0
  5. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +134 -0
  6. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +232 -0
  7. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +44 -0
  8. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +308 -0
  9. classiq/evaluators/qmod_node_evaluators/__init__.py +0 -0
  10. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +112 -0
  11. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +132 -0
  12. classiq/evaluators/qmod_node_evaluators/bool_op_evaluation.py +70 -0
  13. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +311 -0
  14. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +107 -0
  15. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +67 -0
  16. classiq/evaluators/qmod_node_evaluators/list_evaluation.py +107 -0
  17. classiq/evaluators/qmod_node_evaluators/measurement_evaluation.py +25 -0
  18. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +50 -0
  19. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +66 -0
  20. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +225 -0
  21. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +58 -0
  22. classiq/evaluators/qmod_node_evaluators/utils.py +80 -0
  23. classiq/execution/execution_session.py +53 -6
  24. classiq/interface/_version.py +1 -1
  25. classiq/interface/analyzer/analysis_params.py +1 -1
  26. classiq/interface/analyzer/result.py +1 -1
  27. classiq/interface/debug_info/debug_info.py +0 -4
  28. classiq/interface/executor/quantum_code.py +2 -2
  29. classiq/interface/generator/arith/arithmetic_expression_validator.py +5 -1
  30. classiq/interface/generator/arith/binary_ops.py +43 -51
  31. classiq/interface/generator/arith/number_utils.py +3 -2
  32. classiq/interface/generator/arith/register_user_input.py +15 -0
  33. classiq/interface/generator/arith/unary_ops.py +32 -28
  34. classiq/interface/generator/expressions/atomic_expression_functions.py +5 -0
  35. classiq/interface/generator/expressions/expression_types.py +2 -2
  36. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  37. classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
  38. classiq/interface/generator/functions/classical_function_declaration.py +0 -4
  39. classiq/interface/generator/functions/classical_type.py +0 -32
  40. classiq/interface/generator/functions/concrete_types.py +20 -0
  41. classiq/interface/generator/generated_circuit_data.py +7 -10
  42. classiq/interface/generator/quantum_program.py +6 -1
  43. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +29 -0
  44. classiq/interface/ide/operation_registry.py +45 -0
  45. classiq/interface/ide/visual_model.py +84 -2
  46. classiq/interface/model/bounds.py +12 -2
  47. classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
  48. classiq/interface/model/quantum_type.py +67 -33
  49. classiq/interface/model/variable_declaration_statement.py +33 -6
  50. classiq/model_expansions/arithmetic.py +115 -0
  51. classiq/model_expansions/arithmetic_compute_result_attrs.py +71 -0
  52. classiq/model_expansions/atomic_expression_functions_defs.py +10 -6
  53. classiq/model_expansions/function_builder.py +4 -1
  54. classiq/model_expansions/generative_functions.py +15 -2
  55. classiq/model_expansions/interpreters/base_interpreter.py +7 -0
  56. classiq/model_expansions/interpreters/generative_interpreter.py +18 -1
  57. classiq/model_expansions/quantum_operations/assignment_result_processor.py +63 -21
  58. classiq/model_expansions/quantum_operations/bounds.py +7 -1
  59. classiq/model_expansions/quantum_operations/call_emitter.py +5 -2
  60. classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
  61. classiq/model_expansions/quantum_operations/variable_decleration.py +30 -10
  62. classiq/model_expansions/scope.py +7 -0
  63. classiq/model_expansions/scope_initialization.py +2 -0
  64. classiq/model_expansions/sympy_conversion/sympy_to_python.py +1 -1
  65. classiq/model_expansions/transformers/type_modifier_inference.py +5 -0
  66. classiq/model_expansions/transformers/var_splitter.py +1 -1
  67. classiq/model_expansions/visitors/boolean_expression_transformers.py +1 -1
  68. classiq/open_library/functions/__init__.py +0 -2
  69. classiq/open_library/functions/qaoa_penalty.py +8 -1
  70. classiq/open_library/functions/state_preparation.py +1 -32
  71. classiq/qmod/__init__.py +2 -0
  72. classiq/qmod/builtins/operations.py +66 -2
  73. classiq/qmod/classical_variable.py +74 -0
  74. classiq/qmod/declaration_inferrer.py +5 -3
  75. classiq/qmod/native/pretty_printer.py +18 -14
  76. classiq/qmod/pretty_print/pretty_printer.py +34 -15
  77. classiq/qmod/qfunc.py +2 -19
  78. classiq/qmod/qmod_variable.py +5 -8
  79. classiq/qmod/quantum_expandable.py +1 -1
  80. classiq/qmod/quantum_function.py +42 -2
  81. classiq/qmod/symbolic_type.py +2 -1
  82. classiq/qmod/write_qmod.py +3 -1
  83. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/METADATA +1 -1
  84. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/RECORD +86 -62
  85. classiq/interface/model/quantum_variable_declaration.py +0 -7
  86. /classiq/{model_expansions/sympy_conversion/arithmetics.py → evaluators/qmod_expression_visitors/sympy_wrappers.py} +0 -0
  87. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/WHEEL +0 -0
@@ -1,10 +1,15 @@
1
1
  import inspect
2
2
  import random
3
+ import warnings
3
4
  from types import TracebackType
4
- from typing import Callable, Optional, Union, cast
5
+ from typing import Any, Callable, Optional, Union, cast
5
6
 
6
7
  from classiq.interface.chemistry.operator import PauliOperator, pauli_integers_to_str
7
- from classiq.interface.exceptions import ClassiqError, ClassiqValueError
8
+ from classiq.interface.exceptions import (
9
+ ClassiqDeprecationWarning,
10
+ ClassiqError,
11
+ ClassiqValueError,
12
+ )
8
13
  from classiq.interface.execution.primitives import (
9
14
  EstimateInput,
10
15
  MinimizeClassicalCostInput,
@@ -71,6 +76,20 @@ def parse_params(params: ExecutionParams) -> ParsedExecutionParams:
71
76
  return result
72
77
 
73
78
 
79
+ def _hamiltonian_deprecation_warning(hamiltonian: Any) -> None:
80
+ if isinstance(hamiltonian, list):
81
+ warnings.warn(
82
+ (
83
+ "Parameter type list[PauliTerm] to 'ExecutionSession' methods is "
84
+ "deprecated and will no longer be supported starting on 21/7/2025 "
85
+ "at the earliest. Instead, send a 'SparsePauliOp' (see "
86
+ "https://docs.classiq.io/latest/qmod-reference/language-reference/classical-types/#hamiltonians)."
87
+ ),
88
+ ClassiqDeprecationWarning,
89
+ stacklevel=3,
90
+ )
91
+
92
+
74
93
  class ExecutionSession:
75
94
  """
76
95
  A session for executing a quantum program.
@@ -228,11 +247,18 @@ class ExecutionSession:
228
247
  Returns:
229
248
  EstimationResult: The result of the estimation.
230
249
  """
231
- job = self.submit_estimate(hamiltonian=hamiltonian, parameters=parameters)
250
+ _hamiltonian_deprecation_warning(hamiltonian)
251
+ job = self.submit_estimate(
252
+ hamiltonian=hamiltonian, parameters=parameters, _check_deprecation=False
253
+ )
232
254
  return job.get_estimate_result(_http_client=self._async_client)
233
255
 
234
256
  def submit_estimate(
235
- self, hamiltonian: Hamiltonian, parameters: Optional[ExecutionParams] = None
257
+ self,
258
+ hamiltonian: Hamiltonian,
259
+ parameters: Optional[ExecutionParams] = None,
260
+ *,
261
+ _check_deprecation: bool = True,
236
262
  ) -> ExecutionJob:
237
263
  """
238
264
  Initiates an execution job with the `estimate` primitive.
@@ -247,6 +273,8 @@ class ExecutionSession:
247
273
  Returns:
248
274
  The execution job.
249
275
  """
276
+ if _check_deprecation:
277
+ _hamiltonian_deprecation_warning(hamiltonian)
250
278
  execution_primitives_input = PrimitivesInput(
251
279
  estimate=EstimateInput(
252
280
  hamiltonian=self._hamiltonian_to_pauli_operator(hamiltonian),
@@ -270,11 +298,18 @@ class ExecutionSession:
270
298
  Returns:
271
299
  List[EstimationResult]: The results of all the estimation iterations.
272
300
  """
273
- job = self.submit_batch_estimate(hamiltonian=hamiltonian, parameters=parameters)
301
+ _hamiltonian_deprecation_warning(hamiltonian)
302
+ job = self.submit_batch_estimate(
303
+ hamiltonian=hamiltonian, parameters=parameters, _check_deprecation=False
304
+ )
274
305
  return job.get_batch_estimate_result(_http_client=self._async_client)
275
306
 
276
307
  def submit_batch_estimate(
277
- self, hamiltonian: Hamiltonian, parameters: list[ExecutionParams]
308
+ self,
309
+ hamiltonian: Hamiltonian,
310
+ parameters: list[ExecutionParams],
311
+ *,
312
+ _check_deprecation: bool = True,
278
313
  ) -> ExecutionJob:
279
314
  """
280
315
  Initiates an execution job with the `batch_estimate` primitive.
@@ -289,6 +324,8 @@ class ExecutionSession:
289
324
  Returns:
290
325
  The execution job.
291
326
  """
327
+ if _check_deprecation:
328
+ _hamiltonian_deprecation_warning(hamiltonian)
292
329
  execution_primitives_input = PrimitivesInput(
293
330
  estimate=EstimateInput(
294
331
  hamiltonian=self._hamiltonian_to_pauli_operator(hamiltonian),
@@ -322,11 +359,13 @@ class ExecutionSession:
322
359
  A list of tuples, each containing the estimated cost and the corresponding parameters for that iteration.
323
360
  `cost` is a float, and `parameters` is a dictionary matching the execution parameter format.
324
361
  """
362
+ _hamiltonian_deprecation_warning(cost_function)
325
363
  job = self.submit_minimize(
326
364
  cost_function=cost_function,
327
365
  initial_params=initial_params,
328
366
  max_iteration=max_iteration,
329
367
  quantile=quantile,
368
+ _check_deprecation=False,
330
369
  )
331
370
  result = job.get_minimization_result(_http_client=self._async_client)
332
371
 
@@ -340,6 +379,8 @@ class ExecutionSession:
340
379
  initial_params: ExecutionParams,
341
380
  max_iteration: int,
342
381
  quantile: float = 1.0,
382
+ *,
383
+ _check_deprecation: bool = True,
343
384
  ) -> ExecutionJob:
344
385
  """
345
386
  Initiates an execution job with the `minimize` primitive.
@@ -363,6 +404,8 @@ class ExecutionSession:
363
404
  Returns:
364
405
  The execution job.
365
406
  """
407
+ if _check_deprecation:
408
+ _hamiltonian_deprecation_warning(cost_function)
366
409
  if len(initial_params) != 1:
367
410
  raise ClassiqValueError(
368
411
  "The initial parameters must be a dictionary with a single key-value pair."
@@ -461,6 +504,10 @@ class ExecutionSession:
461
504
  raise NotImplementedError(
462
505
  "Filtering is only supported on a single value per model output"
463
506
  )
507
+ if len(legal_bitstrings) == 0:
508
+ raise ClassiqValueError(
509
+ f"The condition was false for every possible value of {output_name}"
510
+ )
464
511
  else:
465
512
  raise NotImplementedError(
466
513
  "Filtering is only supported on QuantumBit and QuantumNumeric"
@@ -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.84.0'
6
+ SEMVER_VERSION = '0.86.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -14,7 +14,7 @@ from classiq.interface.generator.model.preferences.preferences import (
14
14
  from classiq.interface.hardware import Provider
15
15
  from classiq.interface.helpers.custom_pydantic_types import PydanticNonEmptyString
16
16
 
17
- MAX_KB_OF_FILE = 500
17
+ MAX_KB_OF_FILE = 50000
18
18
  MAX_FILE_LENGTH = MAX_KB_OF_FILE * 1024
19
19
 
20
20
  MAX_QUBITS = 100
@@ -37,7 +37,7 @@ class DataID(pydantic.BaseModel):
37
37
 
38
38
 
39
39
  class QasmCode(pydantic.BaseModel):
40
- code: str = Field(..., max_length=MAX_FILE_LENGTH * 100)
40
+ code: str = Field(..., max_length=MAX_FILE_LENGTH)
41
41
 
42
42
 
43
43
  class AnalysisStatus(StrEnum):
@@ -9,7 +9,6 @@ from classiq.interface.generator.generated_circuit_data import (
9
9
  FunctionDebugInfoInterface,
10
10
  StatementType,
11
11
  )
12
- from classiq.interface.model.block import Block
13
12
  from classiq.interface.model.handle_binding import ConcreteHandleBinding
14
13
  from classiq.interface.model.port_declaration import PortDeclaration
15
14
  from classiq.interface.model.quantum_function_call import ArgValue
@@ -82,9 +81,6 @@ def get_back_refs(
82
81
  ) -> list[ConcreteQuantumStatement]:
83
82
  back_refs: list[ConcreteQuantumStatement] = []
84
83
  while (node := debug_info.node) is not None:
85
- # For backwards compatibility, we make sure that the back_ref is not a block
86
- # Remove this check when we start saving blocks in the debug info collection.
87
- assert not isinstance(node, Block)
88
84
  if len(back_refs) > 0 and node.back_ref == back_refs[0].back_ref:
89
85
  break
90
86
  back_refs.insert(0, node)
@@ -95,8 +95,8 @@ class QuantumCode(QuantumBaseCode):
95
95
  ) -> Optional[ExecutionData]:
96
96
  if (
97
97
  synthesis_execution_data is not None
98
- and values.data.get("syntax") is not QuantumInstructionSet.QASM
99
- ):
98
+ and synthesis_execution_data.function_execution
99
+ ) and values.data.get("syntax") is not QuantumInstructionSet.QASM:
100
100
  raise ClassiqValueError("Only QASM supports the requested configuration")
101
101
 
102
102
  return synthesis_execution_data
@@ -63,6 +63,10 @@ def is_constant(expr: Union[str, Expr]) -> bool:
63
63
  return False
64
64
 
65
65
 
66
+ def is_variable(expr: str) -> bool:
67
+ return IDENITIFIER_REGEX.fullmatch(expr) is not None
68
+
69
+
66
70
  class ExpressionValidator(ast.NodeVisitor):
67
71
  def __init__(
68
72
  self,
@@ -95,7 +99,7 @@ class ExpressionValidator(ast.NodeVisitor):
95
99
  @staticmethod
96
100
  def _get_adjusted_expression(expression: str) -> str:
97
101
  # This works around the simplification of the trivial expressions such as a + 0, 1 * a, etc.
98
- if IDENITIFIER_REGEX.fullmatch(expression) or is_constant(expression):
102
+ if is_variable(expression) or is_constant(expression):
99
103
  return f"0 + {expression}"
100
104
  return expression
101
105
 
@@ -20,7 +20,6 @@ from classiq.interface.exceptions import ClassiqValueError
20
20
  from classiq.interface.generator.arith import argument_utils, number_utils
21
21
  from classiq.interface.generator.arith.argument_utils import (
22
22
  RegisterOrConst,
23
- as_arithmetic_info,
24
23
  )
25
24
  from classiq.interface.generator.arith.arithmetic_operations import (
26
25
  MODULO_WITH_FRACTION_PLACES_ERROR_MSG,
@@ -33,6 +32,13 @@ from classiq.interface.generator.arith.register_user_input import RegisterArithm
33
32
  from classiq.interface.generator.arith.unary_ops import Negation
34
33
  from classiq.interface.generator.function_params import get_zero_input_name
35
34
 
35
+ from classiq.model_expansions.arithmetic import NumericAttributes
36
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
37
+ compute_result_attrs_add,
38
+ compute_result_attrs_assign,
39
+ compute_result_attrs_subtract,
40
+ )
41
+
36
42
  LeftDataT = TypeVar("LeftDataT")
37
43
  RightDataT = TypeVar("RightDataT")
38
44
  _NumericArgumentInplaceErrorMessage: str = "Cannot inplace the numeric argument {}"
@@ -268,40 +274,35 @@ class Adder(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
268
274
  output_name = "sum"
269
275
 
270
276
  def _get_result_register(self) -> RegisterArithmeticInfo:
271
- left_arg = argument_utils.limit_fraction_places(
277
+ left_attrs = NumericAttributes.from_type_or_constant(
272
278
  self.left_arg, self.machine_precision
273
279
  )
274
- right_arg = argument_utils.limit_fraction_places(
280
+ right_attrs = NumericAttributes.from_type_or_constant(
275
281
  self.right_arg, self.machine_precision
276
282
  )
277
- lb = argument_utils.lower_bound(left_arg) + argument_utils.lower_bound(
278
- right_arg
279
- )
280
- ub = argument_utils.upper_bound(left_arg) + argument_utils.upper_bound(
281
- right_arg
282
- )
283
- fraction_places = max(
284
- argument_utils.fraction_places(left_arg),
285
- argument_utils.fraction_places(right_arg),
286
- )
283
+
284
+ if self._is_zero(self.left_arg) or self._is_zero(self.right_arg):
285
+ other_arg_attrs = (
286
+ right_attrs if self._is_zero(self.left_arg) else left_attrs
287
+ )
288
+ result_attrs = compute_result_attrs_assign(
289
+ other_arg_attrs, self.machine_precision
290
+ )
291
+ else:
292
+ result_attrs = compute_result_attrs_add(
293
+ left_attrs, right_attrs, self.machine_precision
294
+ )
295
+
287
296
  return RegisterArithmeticInfo(
288
- size=self.output_size or self._get_output_size(ub, lb, fraction_places),
289
- fraction_places=fraction_places,
290
- is_signed=self._include_sign and lb < 0,
291
- bounds=(lb, ub) if self._include_sign else None,
297
+ size=self.output_size or result_attrs.size,
298
+ fraction_places=result_attrs.fraction_digits,
299
+ is_signed=self._include_sign and result_attrs.is_signed,
300
+ bounds=result_attrs.bounds if self._include_sign else None,
292
301
  )
293
302
 
294
- def _get_output_size(self, ub: float, lb: float, fraction_places: int) -> int:
295
- if isinstance(self.left_arg, float) and self.left_arg == 0.0:
296
- if isinstance(self.right_arg, RegisterArithmeticInfo):
297
- return self.right_arg.size
298
- return as_arithmetic_info(self.right_arg).size
299
- elif isinstance(self.right_arg, float) and self.right_arg == 0.0:
300
- assert isinstance(self.left_arg, RegisterArithmeticInfo)
301
- return self.left_arg.size
302
-
303
- integer_part_size = number_utils.bounds_to_integer_part_size(lb, ub)
304
- return integer_part_size + fraction_places
303
+ @staticmethod
304
+ def _is_zero(arg: RegisterOrConst) -> bool:
305
+ return isinstance(arg, float) and arg == 0
305
306
 
306
307
 
307
308
  class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
@@ -337,41 +338,32 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
337
338
  return self._is_arg_trimmed_register(self.right_arg, self.machine_precision)
338
339
 
339
340
  def _get_result_register(self) -> RegisterArithmeticInfo:
340
- bounds = (
341
- argument_utils.lower_bound(self.effective_left_arg)
342
- - argument_utils.upper_bound(self.effective_right_arg),
343
- argument_utils.upper_bound(self.effective_left_arg)
344
- - argument_utils.lower_bound(self.effective_right_arg),
341
+ left_attrs = NumericAttributes.from_type_or_constant(
342
+ self.left_arg, self.machine_precision
345
343
  )
346
- fraction_places = max(
347
- argument_utils.fraction_places(self.effective_left_arg),
348
- argument_utils.fraction_places(self.effective_right_arg),
344
+ right_attrs = NumericAttributes.from_type_or_constant(
345
+ self.right_arg, self.machine_precision
346
+ )
347
+ result_attrs = compute_result_attrs_subtract(
348
+ left_attrs, right_attrs, self.machine_precision
349
349
  )
350
350
 
351
- size = self.output_size or self._get_output_size(bounds, fraction_places)
352
- is_signed = self._include_sign and min(bounds) < 0
351
+ size = self.output_size or result_attrs.size
352
+ is_signed = self._include_sign and result_attrs.is_signed
353
353
  return RegisterArithmeticInfo(
354
354
  size=size,
355
- fraction_places=fraction_places,
355
+ fraction_places=result_attrs.fraction_digits,
356
356
  is_signed=is_signed,
357
357
  bounds=self._legal_bounds(
358
- bounds,
358
+ result_attrs.bounds,
359
359
  RegisterArithmeticInfo.get_maximal_bounds(
360
- size=size, is_signed=is_signed, fraction_places=fraction_places
360
+ size=size,
361
+ is_signed=is_signed,
362
+ fraction_places=result_attrs.fraction_digits,
361
363
  ),
362
364
  ),
363
365
  )
364
366
 
365
- def _get_output_size(
366
- self, bounds: tuple[float, float], fraction_places: int
367
- ) -> int:
368
- if isinstance(self.right_arg, float) and self.effective_right_arg == 0:
369
- assert isinstance(self.effective_left_arg, RegisterArithmeticInfo)
370
- return self.effective_left_arg.size
371
- integer_part_size = number_utils.bounds_to_integer_part_size(*bounds)
372
- size_needed = integer_part_size + fraction_places
373
- return size_needed
374
-
375
367
  def garbage_output_size(self) -> pydantic.NonNegativeInt:
376
368
  if (
377
369
  not self.left_arg_is_trimmed_register
@@ -79,10 +79,11 @@ def size(float_value: float) -> int:
79
79
 
80
80
 
81
81
  def _is_extra_sign_bit_needed(*, lb: float, ub: float) -> bool:
82
- integer_lb = lb * 2 ** fraction_places(lb)
82
+ fractions = max(fraction_places(lb), fraction_places(ub))
83
+ integer_lb = lb * 2**fractions
83
84
  max_represented_number = (
84
85
  2 ** (len(binary_string(integer_lb)) - 1) - 1
85
- ) / 2 ** fraction_places(lb)
86
+ ) / 2**fractions
86
87
  return ub > max_represented_number
87
88
 
88
89
 
@@ -11,6 +11,8 @@ from classiq.interface.helpers.hashable_pydantic_base_model import (
11
11
  HashablePydanticBaseModel,
12
12
  )
13
13
 
14
+ MAX_REGISTER_SIZE = 10000
15
+
14
16
 
15
17
  class RegisterArithmeticInfo(HashablePydanticBaseModel):
16
18
  size: pydantic.PositiveInt = pydantic.Field(default=1)
@@ -56,6 +58,19 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
56
58
  fraction_places = info.data.get("fraction_places", 0)
57
59
  if not isinstance(size, int):
58
60
  raise ClassiqValueError("RegisterArithmeticInfo must have an integer size")
61
+ if not isinstance(fraction_places, int):
62
+ raise ClassiqValueError(
63
+ "RegisterArithmeticInfo must have an integer fraction_places"
64
+ )
65
+
66
+ if size > MAX_REGISTER_SIZE:
67
+ raise ValueError(
68
+ f"Register size {size} exceeds maximum size {MAX_REGISTER_SIZE}."
69
+ )
70
+ if fraction_places > size:
71
+ raise ValueError(
72
+ f"Number of fraction places {fraction_places} exceeds register size {size}."
73
+ )
59
74
 
60
75
  maximal_bounds = cls.get_maximal_bounds(
61
76
  size=size, is_signed=is_signed, fraction_places=fraction_places
@@ -1,11 +1,11 @@
1
1
  from collections.abc import Iterable
2
- from typing import TYPE_CHECKING, Final, Optional
2
+ from typing import Final, Optional
3
3
 
4
4
  import pydantic
5
5
  from pydantic import ConfigDict
6
6
 
7
7
  from classiq.interface.exceptions import ClassiqValueError
8
- from classiq.interface.generator.arith import argument_utils, number_utils
8
+ from classiq.interface.generator.arith import number_utils
9
9
  from classiq.interface.generator.arith.arithmetic_operations import (
10
10
  MODULO_WITH_FRACTION_PLACES_ERROR_MSG,
11
11
  ArithmeticOperationParams,
@@ -13,6 +13,12 @@ from classiq.interface.generator.arith.arithmetic_operations import (
13
13
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
14
14
  from classiq.interface.generator.function_params import get_zero_input_name
15
15
 
16
+ from classiq.model_expansions.arithmetic import NumericAttributes
17
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
18
+ compute_result_attrs_bitwise_invert,
19
+ compute_result_attrs_negate,
20
+ )
21
+
16
22
  UNARY_ARG_NAME: Final[str] = "arg"
17
23
 
18
24
 
@@ -65,13 +71,17 @@ class BitwiseInvert(UnaryOpParams):
65
71
  output_name = "inverted"
66
72
 
67
73
  def _get_result_register(self) -> RegisterArithmeticInfo:
68
- eff_arg = argument_utils.limit_fraction_places(self.arg, self.machine_precision)
69
- if TYPE_CHECKING:
70
- assert isinstance(eff_arg, RegisterArithmeticInfo)
74
+ arg_attrs = NumericAttributes.from_register_arithmetic_info(
75
+ self.arg, self.machine_precision
76
+ )
77
+ result_attrs = compute_result_attrs_bitwise_invert(
78
+ arg_attrs, self.machine_precision
79
+ )
80
+
71
81
  return RegisterArithmeticInfo(
72
- size=self.output_size or eff_arg.size,
73
- fraction_places=eff_arg.fraction_places,
74
- is_signed=eff_arg.is_signed and self._include_sign,
82
+ size=self.output_size or result_attrs.size,
83
+ fraction_places=result_attrs.fraction_digits,
84
+ is_signed=result_attrs.is_signed and self._include_sign,
75
85
  )
76
86
 
77
87
 
@@ -81,40 +91,34 @@ class Negation(UnaryOpParams):
81
91
  ) # True for efficient subtraction
82
92
  output_name = "negated"
83
93
 
84
- @staticmethod
85
- def _expected_result_size(arg: RegisterArithmeticInfo) -> pydantic.PositiveInt:
86
- if arg.size == 1:
87
- return 1
88
- return arg.fraction_places + number_utils.bounds_to_integer_part_size(
89
- *(-bound for bound in arg.bounds)
90
- )
91
-
92
94
  def _get_result_register(self) -> RegisterArithmeticInfo:
93
- eff_arg = self.arg.limit_fraction_places(self.machine_precision)
94
- is_signed = max(eff_arg.bounds) > 0 and self._include_sign
95
- bounds = (-max(eff_arg.bounds), -min(eff_arg.bounds))
95
+ arg_attrs = NumericAttributes.from_register_arithmetic_info(
96
+ self.arg, self.machine_precision
97
+ )
98
+ result_attrs = compute_result_attrs_negate(arg_attrs, self.machine_precision)
99
+ is_signed = result_attrs.is_signed and self._include_sign
100
+ bounds = result_attrs.bounds
96
101
  if self.output_size and not self.bypass_bounds_validation:
97
- if eff_arg.fraction_places:
102
+ if result_attrs.fraction_digits:
98
103
  raise ValueError(MODULO_WITH_FRACTION_PLACES_ERROR_MSG)
99
104
  max_bounds = RegisterArithmeticInfo.get_maximal_bounds(
100
105
  size=self.output_size, is_signed=False, fraction_places=0
101
106
  )
102
107
  bounds = number_utils.bounds_cut(bounds, max_bounds)
103
108
  return RegisterArithmeticInfo(
104
- size=self.output_size or self._expected_result_size(eff_arg),
105
- fraction_places=eff_arg.fraction_places,
109
+ size=self.output_size or result_attrs.size,
110
+ fraction_places=result_attrs.fraction_digits,
106
111
  is_signed=is_signed,
107
112
  bypass_bounds_validation=self.bypass_bounds_validation,
108
113
  bounds=bounds,
109
114
  )
110
115
 
111
116
  def zero_input_for_extension(self) -> pydantic.NonNegativeInt:
112
- eff_arg = self.arg.limit_fraction_places(self.machine_precision)
113
- if (self.output_size or eff_arg.size) == 1:
114
- return 0
115
- return (
116
- self.output_size or self._expected_result_size(self.arg)
117
- ) - self.arg.size
117
+ arg_integers = self.arg.size - self.arg.fraction_places
118
+ result_integers = (
119
+ self.result_register.size - self.result_register.fraction_places
120
+ )
121
+ return result_integers - arg_integers
118
122
 
119
123
 
120
124
  class Sign(UnaryOpParams):
@@ -20,9 +20,14 @@ CLASSIQ_EXPR_FUNCTIONS = {
20
20
  "get_field",
21
21
  }
22
22
 
23
+ MEASUREMENT_FUNCTIONS = {
24
+ "measure",
25
+ }
26
+
23
27
  SUPPORTED_CLASSIQ_BUILTIN_FUNCTIONS = {
24
28
  *CLASSIQ_BUILTIN_CLASSICAL_FUNCTIONS,
25
29
  *CLASSIQ_EXPR_FUNCTIONS,
30
+ *MEASUREMENT_FUNCTIONS,
26
31
  }
27
32
 
28
33
  SUPPORTED_CLASSIQ_SYMPY_WRAPPERS = {
@@ -1,6 +1,6 @@
1
1
  from typing import Union
2
2
 
3
- from sympy import Expr
3
+ from sympy import Basic
4
4
  from sympy.logic.boolalg import Boolean
5
5
 
6
6
  from classiq.interface.generator.expressions.handle_identifier import HandleIdentifier
@@ -31,5 +31,5 @@ Proxies = Union[
31
31
  QmodSizedProxy,
32
32
  ClassicalProxy,
33
33
  ]
34
- RuntimeExpression = Union[AnyClassicalValue, Expr, Boolean]
34
+ RuntimeExpression = Union[AnyClassicalValue, Basic, Boolean]
35
35
  ExpressionValue = Union[RuntimeConstant, Proxies, RuntimeExpression]
@@ -41,3 +41,10 @@ class QmodStructInstance:
41
41
  for field_name, field_value in self._fields.items()
42
42
  )
43
43
  return f"{self.struct_declaration.name}({fields})"
44
+
45
+ def __eq__(self, other: Any) -> bool:
46
+ return (
47
+ isinstance(other, QmodStructInstance)
48
+ and self.struct_declaration.name == other.struct_declaration.name
49
+ and self._fields == other._fields
50
+ )
@@ -6,6 +6,7 @@ CLASSICAL_IF_OPERATOR_NAME = "classical_if"
6
6
  POWER_OPERATOR_NAME = "power"
7
7
  UNCOMPUTE_OPERATOR_NAME = "uncompute"
8
8
  WITHIN_APPLY_NAME = "within_apply"
9
+ BLOCK_OPERATOR_NAME = "block"
9
10
 
10
11
  All_BUILTINS_OPERATORS = {
11
12
  CONTROL_OPERATOR_NAME,
@@ -14,4 +15,5 @@ All_BUILTINS_OPERATORS = {
14
15
  POWER_OPERATOR_NAME,
15
16
  UNCOMPUTE_OPERATOR_NAME,
16
17
  WITHIN_APPLY_NAME,
18
+ BLOCK_OPERATOR_NAME,
17
19
  }
@@ -28,10 +28,6 @@ class ClassicalFunctionDeclaration(FunctionDeclaration):
28
28
  default=None,
29
29
  )
30
30
 
31
- BUILTIN_FUNCTION_DECLARATIONS: ClassVar[
32
- dict[str, "ClassicalFunctionDeclaration"]
33
- ] = {}
34
-
35
31
  FOREIGN_FUNCTION_DECLARATIONS: ClassVar[
36
32
  dict[str, "ClassicalFunctionDeclaration"]
37
33
  ] = {}
@@ -113,7 +113,6 @@ class StructMetaType(ClassicalType):
113
113
  class ClassicalArray(ClassicalType):
114
114
  kind: Literal["array"]
115
115
  element_type: "ConcreteClassicalType"
116
- size: Optional[int] = pydantic.Field(exclude=True, default=None)
117
116
  length: Optional[Expression] = None
118
117
 
119
118
  @pydantic.model_validator(mode="before")
@@ -121,37 +120,6 @@ class ClassicalArray(ClassicalType):
121
120
  def _set_kind(cls, values: Any) -> dict[str, Any]:
122
121
  return values_with_discriminator(values, "kind", "array")
123
122
 
124
- @pydantic.model_validator(mode="before")
125
- @classmethod
126
- def _set_length(cls, values: Any) -> Any:
127
- if isinstance(values, dict):
128
- size = values.get("size")
129
- length = values.get("length")
130
- else:
131
- size = values.size
132
- length = values.length
133
- if size is not None:
134
- if isinstance(values, dict):
135
- values["length"] = Expression(expr=str(size))
136
- else:
137
- values.length = Expression(expr=str(size))
138
- elif length is not None:
139
- if isinstance(length, dict):
140
- expr = length["expr"]
141
- else:
142
- expr = length.expr
143
- expr_size: Optional[int] = None
144
- try: # noqa: SIM105
145
- expr_size = int(expr)
146
- except ValueError:
147
- pass
148
- if expr_size is not None:
149
- if isinstance(values, dict):
150
- values["size"] = expr_size
151
- else:
152
- values.size = expr_size
153
- return values
154
-
155
123
  @property
156
124
  def has_length(self) -> bool:
157
125
  return self.length is not None and self.length.is_evaluated()
@@ -53,3 +53,23 @@ QuantumBitvector.model_rebuild()
53
53
  TypeName.model_rebuild()
54
54
  QStructDeclaration.model_rebuild()
55
55
  RegisterQuantumType.model_rebuild()
56
+
57
+ ConcreteType = Annotated[
58
+ Union[
59
+ Integer,
60
+ Real,
61
+ Bool,
62
+ StructMetaType,
63
+ TypeName,
64
+ ClassicalArray,
65
+ ClassicalTuple,
66
+ VQEResult,
67
+ Histogram,
68
+ Estimation,
69
+ IQAERes,
70
+ QuantumBit,
71
+ QuantumBitvector,
72
+ QuantumNumeric,
73
+ ],
74
+ Field(discriminator="kind"),
75
+ ]