classiq 0.86.0__py3-none-any.whl → 0.87.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 (96) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/applications/chemistry/hartree_fock.py +5 -1
  3. classiq/applications/chemistry/op_utils.py +2 -2
  4. classiq/applications/combinatorial_helpers/encoding_mapping.py +11 -15
  5. classiq/applications/combinatorial_helpers/encoding_utils.py +6 -6
  6. classiq/applications/combinatorial_helpers/memory.py +4 -4
  7. classiq/applications/combinatorial_helpers/optimization_model.py +5 -5
  8. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +6 -10
  9. classiq/applications/combinatorial_helpers/pyomo_utils.py +27 -26
  10. classiq/applications/combinatorial_helpers/sympy_utils.py +2 -2
  11. classiq/applications/combinatorial_helpers/transformations/encoding.py +4 -6
  12. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +4 -4
  13. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +2 -2
  14. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +3 -3
  15. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -0
  16. classiq/applications/hamiltonian/pauli_decomposition.py +33 -1
  17. classiq/evaluators/argument_types.py +15 -6
  18. classiq/evaluators/parameter_types.py +43 -39
  19. classiq/evaluators/qmod_annotated_expression.py +88 -11
  20. classiq/evaluators/qmod_expression_visitors/out_of_place_node_transformer.py +19 -0
  21. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +54 -11
  22. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +40 -25
  23. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +29 -59
  24. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +12 -5
  25. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +175 -28
  26. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +21 -14
  27. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +9 -5
  28. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +20 -1
  29. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +97 -0
  30. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +11 -26
  31. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +56 -0
  32. classiq/evaluators/qmod_node_evaluators/piecewise_evaluation.py +40 -0
  33. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +2 -3
  34. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +48 -21
  35. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +53 -9
  36. classiq/evaluators/qmod_node_evaluators/utils.py +27 -5
  37. classiq/evaluators/qmod_type_inference/classical_type_inference.py +188 -0
  38. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +292 -0
  39. classiq/evaluators/quantum_type_utils.py +0 -131
  40. classiq/execution/execution_session.py +1 -1
  41. classiq/execution/qnn.py +4 -1
  42. classiq/execution/user_budgets.py +1 -1
  43. classiq/interface/_version.py +1 -1
  44. classiq/interface/backend/backend_preferences.py +10 -30
  45. classiq/interface/backend/quantum_backend_providers.py +63 -52
  46. classiq/interface/generator/arith/binary_ops.py +107 -115
  47. classiq/interface/generator/arith/extremum_operations.py +33 -45
  48. classiq/interface/generator/arith/number_utils.py +4 -1
  49. classiq/interface/generator/circuit_code/types_and_constants.py +0 -9
  50. classiq/interface/generator/compiler_keywords.py +2 -0
  51. classiq/interface/generator/function_param_list.py +133 -5
  52. classiq/interface/generator/functions/classical_type.py +59 -2
  53. classiq/interface/generator/functions/qmod_python_interface.py +15 -0
  54. classiq/interface/generator/functions/type_name.py +6 -0
  55. classiq/interface/generator/model/preferences/preferences.py +1 -17
  56. classiq/interface/generator/quantum_program.py +1 -13
  57. classiq/interface/helpers/model_normalizer.py +2 -2
  58. classiq/interface/helpers/text_utils.py +7 -2
  59. classiq/interface/interface_version.py +1 -1
  60. classiq/interface/model/classical_if.py +40 -0
  61. classiq/interface/model/handle_binding.py +28 -16
  62. classiq/interface/model/quantum_type.py +61 -2
  63. classiq/interface/pretty_print/expression_to_qmod.py +24 -11
  64. classiq/interface/pyomo_extension/__init__.py +0 -4
  65. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +2 -2
  66. classiq/model_expansions/arithmetic.py +43 -1
  67. classiq/model_expansions/arithmetic_compute_result_attrs.py +255 -0
  68. classiq/model_expansions/capturing/captured_vars.py +2 -5
  69. classiq/model_expansions/quantum_operations/allocate.py +22 -15
  70. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -3
  71. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -71
  72. classiq/model_expansions/quantum_operations/bind.py +15 -7
  73. classiq/model_expansions/quantum_operations/call_emitter.py +2 -10
  74. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -0
  75. classiq/open_library/functions/__init__.py +3 -0
  76. classiq/open_library/functions/lcu.py +117 -0
  77. classiq/qmod/builtins/enums.py +2 -2
  78. classiq/qmod/builtins/structs.py +33 -18
  79. classiq/qmod/pretty_print/expression_to_python.py +7 -9
  80. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/METADATA +3 -3
  81. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/RECORD +83 -89
  82. classiq/interface/generator/amplitude_estimation.py +0 -34
  83. classiq/interface/generator/function_param_list_without_self_reference.py +0 -160
  84. classiq/interface/generator/grover_diffuser.py +0 -93
  85. classiq/interface/generator/grover_operator.py +0 -106
  86. classiq/interface/generator/oracles/__init__.py +0 -3
  87. classiq/interface/generator/oracles/arithmetic_oracle.py +0 -82
  88. classiq/interface/generator/oracles/custom_oracle.py +0 -65
  89. classiq/interface/generator/oracles/oracle_abc.py +0 -76
  90. classiq/interface/generator/oracles/oracle_function_param_list.py +0 -6
  91. classiq/interface/generator/piecewise_linear_amplitude_loading.py +0 -165
  92. classiq/interface/generator/qpe.py +0 -169
  93. classiq/interface/grover/grover_modelling_params.py +0 -13
  94. classiq/model_expansions/transformers/var_splitter.py +0 -224
  95. /classiq/{interface/grover → evaluators/qmod_type_inference}/__init__.py +0 -0
  96. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/WHEEL +0 -0
@@ -209,23 +209,6 @@ class AwsBackendPreferences(BackendPreferences):
209
209
  )
210
210
 
211
211
 
212
- class IBMBackendProvider(BaseModel):
213
- """
214
-
215
- Represents the provider specs for identifying an IBM Quantum backend.
216
-
217
- Attributes:
218
- hub (str): hub parameter of IBM Quantum provider. Defaults to `"ibm-q"`.
219
- group (str): group parameter of IBM Quantum provider. Defaults to `"open"`.
220
- project (str): project parameter of IBM Quantum provider. Defaults to `"main"`.
221
-
222
- """
223
-
224
- hub: str = "ibm-q"
225
- group: str = "open"
226
- project: str = "main"
227
-
228
-
229
212
  class IBMBackendPreferences(BackendPreferences):
230
213
  """
231
214
  Represents the backend preferences specific to IBM Quantum services.
@@ -234,30 +217,28 @@ class IBMBackendPreferences(BackendPreferences):
234
217
  specific to IBM Quantum backends.
235
218
 
236
219
  Attributes:
237
- backend_service_provider (ProviderTypeVendor.IBM_QUANTUM): Indicates the backend service provider as IBM Quantum.
238
- access_token (Optional[str]): The IBM Quantum access token to be used with IBM Quantum hosted backends. Defaults to `None`.
239
- provider (IBMBackendProvider): Specifications for identifying a single IBM Quantum provider. Defaults to a new `IBMBackendProvider`.
240
- qctrl_api_key (Optional[str]): QCTRL API key to access QCTRL optimization abilities.
220
+ backend_service_provider (ProviderTypeVendor.IBM_CLOUD): Indicates the backend service provider as IBM Cloud.
221
+ access_token (Optional[str]): The IBM Cloud access token to be used with IBM Quantum hosted backends. Defaults to `None`.
222
+ channel (str): Channel to use for IBM cloud backends. Defaults to `"ibm_cloud"`.
223
+ instance_crn (str): The IBM Cloud instance CRN (Cloud Resource Name) for the IBM Quantum service.
241
224
  run_through_classiq (bool): Run through Classiq's credentials. Defaults to `False`.
242
225
 
243
226
  See examples in the [IBM Quantum Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/ibm-backends/?h=).
244
227
  """
245
228
 
246
- backend_service_provider: ProviderTypeVendor.IBM_QUANTUM = pydantic.Field(
229
+ backend_service_provider: ProviderTypeVendor.IBM_CLOUD = pydantic.Field(
247
230
  default=ProviderVendor.IBM_QUANTUM
248
231
  )
249
232
  access_token: Optional[str] = pydantic.Field(
250
233
  default=None,
251
- description="IBM Quantum access token to be used"
234
+ description="IBM Cloud access token to be used"
252
235
  " with IBM Quantum hosted backends",
253
236
  )
254
- provider: IBMBackendProvider = pydantic.Field(
255
- default_factory=IBMBackendProvider,
256
- description="Provider specs. for identifying a single IBM Quantum provider.",
237
+ channel: Optional[str] = pydantic.Field(
238
+ default=None, description="Channel to use for IBM cloud backends."
257
239
  )
258
- qctrl_api_key: Optional[str] = pydantic.Field(
259
- default=None,
260
- description="QCTRL API key to access QCTRL optimization abilities",
240
+ instance_crn: Optional[str] = pydantic.Field(
241
+ default=None, description="IBM Cloud instance CRN."
261
242
  )
262
243
  run_through_classiq: bool = pydantic.Field(
263
244
  default=False,
@@ -544,7 +525,6 @@ __all__ = [
544
525
  "ClassiqSimulatorBackendNames",
545
526
  "GCPBackendPreferences",
546
527
  "IBMBackendPreferences",
547
- "IBMBackendProvider",
548
528
  "IQCCBackendPreferences",
549
529
  "IntelBackendNames",
550
530
  "IntelBackendPreferences",
@@ -26,7 +26,7 @@ class ProviderVendor(StrEnum):
26
26
 
27
27
  class ProviderTypeVendor:
28
28
  CLASSIQ = Literal[ProviderVendor.CLASSIQ]
29
- IBM_QUANTUM = Literal[ProviderVendor.IBM_QUANTUM]
29
+ IBM_CLOUD = Literal[ProviderVendor.IBM_QUANTUM]
30
30
  AZURE_QUANTUM = Literal[ProviderVendor.AZURE_QUANTUM]
31
31
  AMAZON_BRAKET = Literal[ProviderVendor.AMAZON_BRAKET]
32
32
  IONQ = Literal[ProviderVendor.IONQ]
@@ -108,63 +108,74 @@ class AmazonBraketBackendNames(StrEnum):
108
108
 
109
109
 
110
110
  # The IBM devices were taken from:
111
- # from qiskit.providers.fake_provider import FakeProvider
112
- # provider = FakeProvider()
111
+ # from qiskit_ibm_runtime.fake_provider import FakeProviderForBackendV2
112
+ # provider = FakeProviderForBackendV2()
113
113
  # backends_list = provider.backends()
114
- # # Using _normalize_backend_name from `ibm_hardware_graphs.py`
115
- # the_devices = [_normalize_backend_name(str(backend)) for backend in backends_list]
114
+ # the_devices = ["ibm_" + backend.name.split('_')[1] for backend in backends_list.backends()]
116
115
  class IBMQHardwareNames(StrEnum):
117
116
  """
118
117
  IBM backend names which Classiq Supports running on.
119
118
  """
120
119
 
121
- ALMADEN = "Almaden"
122
- ARMONK = "Armonk"
123
- ATHENS = "Athens"
124
- BELEM = "Belem"
125
- BOEBLINGEN = "Boeblingen"
126
- BOGOTA = "Bogota"
127
- BROOKLYN = "Brooklyn"
128
- BURLINGTON = "Burlington"
129
- CAIRO = "Cairo"
130
- CAMBRIDGE = "Cambridge"
131
- # CAMBRIDGEALTERNATIVEBASIS = "CambridgeAlternativeBasis"
132
- CASABLANCA = "Casablanca"
133
- ESSEX = "Essex"
134
- GUADALUPE = "Guadalupe"
135
- HANOI = "Hanoi"
136
- JAKARTA = "Jakarta"
137
- JOHANNESBURG = "Johannesburg"
138
- KOLKATA = "Kolkata"
139
- LAGOS = "Lagos"
140
- LIMA = "Lima"
141
- LONDON = "London"
142
- MANHATTAN = "Manhattan"
143
- MANILA = "Manila"
144
- MELBOURNE = "Melbourne"
145
- MONTREAL = "Montreal"
146
- MUMBAI = "Mumbai"
147
- NAIROBI = "Nairobi"
148
- OPENPULSE2Q = "OpenPulse_2Q"
149
- OPENPULSE3Q = "OpenPulse_3Q"
150
- OURENSE = "Ourense"
151
- PARIS = "Paris"
152
- POUGHKEEPSIE = "Poughkeepsie"
153
- QASM_SIMULATOR = "qasm_simulator"
154
- QUITO = "Quito"
155
- ROCHESTER = "Rochester"
156
- ROME = "Rome"
157
- RUESCHLIKON = "Rueschlikon"
158
- SANTIAGO = "Santiago"
159
- SINGAPORE = "Singapore"
160
- SYDNEY = "Sydney"
161
- TENERIFE = "Tenerife"
162
- TOKYO = "Tokyo"
163
- TORONTO = "Toronto"
164
- VALENCIA = "Valencia"
165
- VIGO = "Vigo"
166
- WASHINGTON = "Washington"
167
- YORKTOWN = "Yorktown"
120
+ IBM_ALGIERS = "ibm_algiers"
121
+ IBM_ALMADEN = "ibm_almaden"
122
+ IBM_ARMONK = "ibm_armonk"
123
+ IBM_ATHENS = "ibm_athens"
124
+ IBM_AUCKLAND = "ibm_auckland"
125
+ IBM_BELEM = "ibm_belem"
126
+ IBM_BOEBLINGEN = "ibm_boeblingen"
127
+ IBM_BOGOTA = "ibm_bogota"
128
+ IBM_BRISBANE = "ibm_brisbane"
129
+ IBM_BROOKLYN = "ibm_brooklyn"
130
+ IBM_BURLINGTON = "ibm_burlington"
131
+ IBM_CAIRO = "ibm_cairo"
132
+ IBM_CAMBRIDGE = "ibm_cambridge"
133
+ IBM_CASABLANCA = "ibm_casablanca"
134
+ IBM_CUSCO = "ibm_cusco"
135
+ IBM_ESSEX = "ibm_essex"
136
+ IBM_FEZ = "ibm_fez"
137
+ IBM_FRACTIONAL = "ibm_fractional"
138
+ IBM_GENEVA = "ibm_geneva"
139
+ IBM_GUADALUPE = "ibm_guadalupe"
140
+ IBM_HANOI = "ibm_hanoi"
141
+ IBM_JAKARTA = "ibm_jakarta"
142
+ IBM_JOHANNESBURG = "ibm_johannesburg"
143
+ IBM_KAWASAKI = "ibm_kawasaki"
144
+ IBM_KOLKATA = "ibm_kolkata"
145
+ IBM_KYIV = "ibm_kyiv"
146
+ IBM_KYOTO = "ibm_kyoto"
147
+ IBM_LAGOS = "ibm_lagos"
148
+ IBM_LIMA = "ibm_lima"
149
+ IBM_LONDON = "ibm_london"
150
+ IBM_MANHATTAN = "ibm_manhattan"
151
+ IBM_MANILA = "ibm_manila"
152
+ IBM_MELBOURNE = "ibm_melbourne"
153
+ IBM_MARRAKESH = "ibm_marrakesh"
154
+ IBM_MONTREAL = "ibm_montreal"
155
+ IBM_MUMBAI = "ibm_mumbai"
156
+ IBM_NAIROBI = "ibm_nairobi"
157
+ IBM_OSAKA = "ibm_osaka"
158
+ IBM_OSLO = "ibm_oslo"
159
+ IBM_OURENSE = "ibm_ourense"
160
+ IBM_PARIS = "ibm_paris"
161
+ IBM_PEEKSKILL = "ibm_peekskill"
162
+ IBM_PERTH = "ibm_perth"
163
+ IBM_PRAGUE = "ibm_prague"
164
+ IBM_POUGHKEEPSIE = "ibm_poughkeepsie"
165
+ IBM_QUEBEC = "ibm_quebec"
166
+ IBM_QUITO = "ibm_quito"
167
+ IBM_ROCHESTER = "ibm_rochester"
168
+ IBM_ROME = "ibm_rome"
169
+ IBM_SANTIAGO = "ibm_santiago"
170
+ IBM_SHERBROOKE = "ibm_sherbrooke"
171
+ IBM_SINGAPORE = "ibm_singapore"
172
+ IBM_SYDNEY = "ibm_sydney"
173
+ IBM_TORINO = "ibm_torino"
174
+ IBM_TORONTO = "ibm_toronto"
175
+ IBM_VALENCIA = "ibm_valencia"
176
+ IBM_VIGO = "ibm_vigo"
177
+ IBM_WASHINGTON = "ibm_washington"
178
+ IBM_YORKTOWN = "ibm_yorktown"
168
179
 
169
180
 
170
181
  class ClassiqNvidiaBackendNames(StrEnum):
@@ -36,6 +36,14 @@ from classiq.model_expansions.arithmetic import NumericAttributes
36
36
  from classiq.model_expansions.arithmetic_compute_result_attrs import (
37
37
  compute_result_attrs_add,
38
38
  compute_result_attrs_assign,
39
+ compute_result_attrs_bitwise_and,
40
+ compute_result_attrs_bitwise_or,
41
+ compute_result_attrs_bitwise_xor,
42
+ compute_result_attrs_lshift,
43
+ compute_result_attrs_modulo,
44
+ compute_result_attrs_multiply,
45
+ compute_result_attrs_power,
46
+ compute_result_attrs_rshift,
39
47
  compute_result_attrs_subtract,
40
48
  )
41
49
 
@@ -225,31 +233,6 @@ class BinaryOpWithIntInputs(BinaryOpParams[RegisterOrInt, RegisterOrInt]):
225
233
  raise ClassiqValueError(BOOLEAN_OP_WITH_FRACTIONS_ERROR)
226
234
  return self
227
235
 
228
- @staticmethod
229
- def _is_signed(arg: Union[int, RegisterArithmeticInfo]) -> bool:
230
- if isinstance(arg, RegisterArithmeticInfo):
231
- return arg.is_signed
232
- return arg < 0
233
-
234
- def _get_result_register(self) -> RegisterArithmeticInfo:
235
- required_size = self._aligned_inputs_max_length()
236
- is_signed = self._include_sign and (
237
- self._is_signed(self.left_arg) or self._is_signed(self.right_arg)
238
- )
239
- return RegisterArithmeticInfo(
240
- size=self.output_size or required_size, is_signed=is_signed
241
- )
242
-
243
- def _aligned_inputs_max_length(self) -> int:
244
- left_signed: bool = argument_utils.is_signed(self.left_arg)
245
- right_signed: bool = argument_utils.is_signed(self.right_arg)
246
- return max(
247
- argument_utils.integer_part_size(self.right_arg)
248
- + int(left_signed and not right_signed),
249
- argument_utils.integer_part_size(self.left_arg)
250
- + int(right_signed and not left_signed),
251
- )
252
-
253
236
 
254
237
  class BinaryOpWithFloatInputs(BinaryOpParams[RegisterOrConst, RegisterOrConst]):
255
238
  pass
@@ -258,10 +241,42 @@ class BinaryOpWithFloatInputs(BinaryOpParams[RegisterOrConst, RegisterOrConst]):
258
241
  class BitwiseAnd(BinaryOpWithIntInputs):
259
242
  output_name = "bitwise_and"
260
243
 
244
+ def _get_result_register(self) -> RegisterArithmeticInfo:
245
+ left_attrs = NumericAttributes.from_type_or_constant(
246
+ self.left_arg, self.machine_precision
247
+ )
248
+ right_attrs = NumericAttributes.from_type_or_constant(
249
+ self.right_arg, self.machine_precision
250
+ )
251
+ result_attrs = compute_result_attrs_bitwise_and(
252
+ left_attrs, right_attrs, self.machine_precision
253
+ )
254
+ return RegisterArithmeticInfo(
255
+ size=self.output_size or result_attrs.size,
256
+ is_signed=self._include_sign and result_attrs.is_signed,
257
+ fraction_places=result_attrs.fraction_digits,
258
+ )
259
+
261
260
 
262
261
  class BitwiseOr(BinaryOpWithIntInputs):
263
262
  output_name = "bitwise_or"
264
263
 
264
+ def _get_result_register(self) -> RegisterArithmeticInfo:
265
+ left_attrs = NumericAttributes.from_type_or_constant(
266
+ self.left_arg, self.machine_precision
267
+ )
268
+ right_attrs = NumericAttributes.from_type_or_constant(
269
+ self.right_arg, self.machine_precision
270
+ )
271
+ result_attrs = compute_result_attrs_bitwise_or(
272
+ left_attrs, right_attrs, self.machine_precision
273
+ )
274
+ return RegisterArithmeticInfo(
275
+ size=self.output_size or result_attrs.size,
276
+ is_signed=self._include_sign and result_attrs.is_signed,
277
+ fraction_places=result_attrs.fraction_digits,
278
+ )
279
+
265
280
 
266
281
  # TODO: fix diamond inheritance
267
282
  class BitwiseXor(
@@ -269,6 +284,22 @@ class BitwiseXor(
269
284
  ):
270
285
  output_name = "bitwise_xor"
271
286
 
287
+ def _get_result_register(self) -> RegisterArithmeticInfo:
288
+ left_attrs = NumericAttributes.from_type_or_constant(
289
+ self.left_arg, self.machine_precision
290
+ )
291
+ right_attrs = NumericAttributes.from_type_or_constant(
292
+ self.right_arg, self.machine_precision
293
+ )
294
+ result_attrs = compute_result_attrs_bitwise_xor(
295
+ left_attrs, right_attrs, self.machine_precision
296
+ )
297
+ return RegisterArithmeticInfo(
298
+ size=self.output_size or result_attrs.size,
299
+ is_signed=self._include_sign and result_attrs.is_signed,
300
+ fraction_places=result_attrs.fraction_digits,
301
+ )
302
+
272
303
 
273
304
  class Adder(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
274
305
  output_name = "sum"
@@ -467,29 +498,18 @@ class Multiplier(BinaryOpWithFloatInputs):
467
498
  argument_utils.limit_fraction_places(self.right_arg, self.machine_precision)
468
499
  )
469
500
 
470
- @staticmethod
471
- def _get_bounds(
472
- args: tuple[RegisterOrConst, RegisterOrConst], machine_precision: int
473
- ) -> tuple[float, float]:
474
- extremal_values = [
475
- left * right
476
- for left in argument_utils.bounds(args[0])
477
- for right in argument_utils.bounds(args[1])
478
- ]
479
- return (
480
- number_utils.limit_fraction_places(min(extremal_values), machine_precision),
481
- number_utils.limit_fraction_places(max(extremal_values), machine_precision),
482
- )
483
-
484
501
  def _get_result_register(self) -> RegisterArithmeticInfo:
485
- fraction_places = min(self.machine_precision, self.expected_fraction_places())
486
- left_arg = argument_utils.limit_fraction_places(
502
+ left_attrs = NumericAttributes.from_type_or_constant(
487
503
  self.left_arg, self.machine_precision
488
504
  )
489
- right_arg = argument_utils.limit_fraction_places(
505
+ right_attrs = NumericAttributes.from_type_or_constant(
490
506
  self.right_arg, self.machine_precision
491
507
  )
492
- bounds = self._get_bounds((left_arg, right_arg), self.machine_precision)
508
+ result_attrs = compute_result_attrs_multiply(
509
+ left_attrs, right_attrs, self.machine_precision
510
+ )
511
+ fraction_places = result_attrs.fraction_digits
512
+ bounds = result_attrs.bounds
493
513
  if self.output_size:
494
514
  if fraction_places:
495
515
  raise ValueError(MODULO_WITH_FRACTION_PLACES_ERROR_MSG)
@@ -498,10 +518,8 @@ class Multiplier(BinaryOpWithFloatInputs):
498
518
  )
499
519
  bounds = number_utils.bounds_cut(bounds, max_bounds)
500
520
 
501
- size = self.output_size or self._get_output_size(
502
- bounds, fraction_places, left_arg, right_arg
503
- )
504
- is_signed = self._include_sign and min(bounds) < 0
521
+ size = self.output_size or result_attrs.size
522
+ is_signed = self._include_sign and result_attrs.is_signed
505
523
  return RegisterArithmeticInfo(
506
524
  size=size,
507
525
  fraction_places=fraction_places,
@@ -514,25 +532,6 @@ class Multiplier(BinaryOpWithFloatInputs):
514
532
  ),
515
533
  )
516
534
 
517
- @staticmethod
518
- def _get_output_size(
519
- bounds: tuple[float, float],
520
- fraction_places: int,
521
- left_arg: Union[RegisterArithmeticInfo, float],
522
- right_arg: Union[RegisterArithmeticInfo, float],
523
- ) -> int:
524
- if isinstance(left_arg, float) and left_arg == 1.0:
525
- assert isinstance(right_arg, RegisterArithmeticInfo)
526
- return right_arg.size
527
- elif isinstance(right_arg, float) and right_arg == 1.0:
528
- assert isinstance(left_arg, RegisterArithmeticInfo)
529
- return left_arg.size
530
- if bounds[0] == 0 and bounds[1] == 0:
531
- return max(1, fraction_places)
532
-
533
- integer_part_size = number_utils.bounds_to_integer_part_size(*bounds)
534
- return integer_part_size + fraction_places
535
-
536
535
  def garbage_output_size(self) -> pydantic.NonNegativeInt:
537
536
  return max(
538
537
  0, self.expected_fraction_places() - self.result_register.fraction_places
@@ -592,43 +591,22 @@ class Power(BinaryOpParams[RegisterArithmeticInfo, pydantic.PositiveInt]):
592
591
  * self.right_arg
593
592
  )
594
593
 
595
- def _get_result_bounds(self) -> tuple[float, float]:
596
- bounds = [
597
- number_utils.limit_fraction_places(
598
- bound, machine_precision=self.machine_precision
599
- )
600
- for bound in self.left_arg.bounds
601
- ]
602
- if (self.right_arg % 2) or min(bounds) >= 0 or max(bounds) <= 0:
603
- return (
604
- number_utils.limit_fraction_places(
605
- bounds[0] ** self.right_arg,
606
- machine_precision=self.machine_precision,
607
- ),
608
- number_utils.limit_fraction_places(
609
- bounds[1] ** self.right_arg,
610
- machine_precision=self.machine_precision,
611
- ),
612
- )
613
- return 0.0, number_utils.limit_fraction_places(
614
- max(abs(bound) for bound in bounds) ** self.right_arg,
615
- machine_precision=self.machine_precision,
616
- )
617
-
618
594
  def _get_result_register(self) -> RegisterArithmeticInfo:
619
595
  if self.output_size:
620
596
  return RegisterArithmeticInfo(size=self.output_size)
621
597
 
622
- fraction_places = min(self.machine_precision, self.expected_fraction_places())
623
- bounds = self._get_result_bounds()
624
- size = number_utils.bounds_to_integer_part_size(*bounds) + fraction_places
625
- if bounds[0] == 0 and bounds[1] == 0:
626
- size = max(1, fraction_places)
598
+ left_attrs = NumericAttributes.from_register_arithmetic_info(
599
+ self.left_arg, self.machine_precision
600
+ )
601
+ result_attrs = compute_result_attrs_power(
602
+ left_attrs, self.right_arg, self.machine_precision
603
+ )
604
+
627
605
  return RegisterArithmeticInfo(
628
- size=size,
629
- is_signed=self.left_arg.is_signed and (self.right_arg % 2 == 1),
630
- fraction_places=fraction_places,
631
- bounds=bounds,
606
+ size=result_attrs.size,
607
+ is_signed=result_attrs.is_signed,
608
+ fraction_places=result_attrs.fraction_digits,
609
+ bounds=result_attrs.bounds,
632
610
  )
633
611
 
634
612
  def _get_inner_action_garbage_size(
@@ -709,13 +687,17 @@ class LShift(EffectiveUnaryOpParams[pydantic.NonNegativeInt]):
709
687
  return max(self.left_arg.size + extra_result_lsbs - self.output_size, 0)
710
688
 
711
689
  def _get_result_register(self) -> RegisterArithmeticInfo:
712
- new_fraction_places = max(self.left_arg.fraction_places - self.right_arg, 0)
713
- new_integer_part_size = self.left_arg.integer_part_size + self.right_arg
714
- required_size = new_integer_part_size + new_fraction_places
690
+ left_attrs = NumericAttributes.from_register_arithmetic_info(
691
+ self.left_arg, self.machine_precision
692
+ )
693
+ result_attrs = compute_result_attrs_lshift(
694
+ left_attrs, self.right_arg, self.machine_precision
695
+ )
715
696
  return RegisterArithmeticInfo(
716
- size=self.output_size or required_size,
717
- is_signed=self._include_sign and self.left_arg.is_signed,
718
- fraction_places=new_fraction_places,
697
+ size=self.output_size or result_attrs.size,
698
+ is_signed=self._include_sign and result_attrs.is_signed,
699
+ fraction_places=result_attrs.fraction_digits,
700
+ bounds=result_attrs.bounds if not self.output_size else None,
719
701
  )
720
702
 
721
703
 
@@ -755,15 +737,17 @@ class RShift(EffectiveUnaryOpParams[pydantic.NonNegativeInt]):
755
737
  )
756
738
 
757
739
  def _get_result_register(self) -> RegisterArithmeticInfo:
758
- min_size: int = max(self.left_arg.size - self.right_arg, 1)
759
- new_fraction_places = self._shifted_fraction_places(
760
- arg=self.left_arg, shift=self.right_arg
740
+ left_attrs = NumericAttributes.from_register_arithmetic_info(
741
+ self.left_arg, self.machine_precision
742
+ )
743
+ result_attrs = compute_result_attrs_rshift(
744
+ left_attrs, self.right_arg, self.machine_precision
761
745
  )
762
- required_size = max(min_size, new_fraction_places)
763
746
  return RegisterArithmeticInfo(
764
- size=self.output_size or required_size,
765
- is_signed=self._include_sign and self.left_arg.is_signed,
766
- fraction_places=new_fraction_places,
747
+ size=self.output_size or result_attrs.size,
748
+ is_signed=self._include_sign and result_attrs.is_signed,
749
+ fraction_places=result_attrs.fraction_digits,
750
+ bounds=result_attrs.bounds if not self.output_size else None,
767
751
  )
768
752
 
769
753
 
@@ -822,7 +806,15 @@ class Modulo(EffectiveUnaryOpParams[int]):
822
806
  return 2 ** (repr_qubits)
823
807
 
824
808
  def _get_result_register(self) -> RegisterArithmeticInfo:
825
- size = round(math.log2(self.right_arg))
826
- if size <= 0:
827
- raise ClassiqValueError("Cannot use a quantum expression with zero size")
828
- return RegisterArithmeticInfo(size=size, is_signed=False, fraction_places=0)
809
+ left_attrs = NumericAttributes.from_register_arithmetic_info(
810
+ self.left_arg, self.machine_precision
811
+ )
812
+ result_attrs = compute_result_attrs_modulo(
813
+ left_attrs, self.right_arg, self.machine_precision
814
+ )
815
+ return RegisterArithmeticInfo(
816
+ size=result_attrs.size,
817
+ is_signed=result_attrs.is_signed,
818
+ fraction_places=result_attrs.fraction_digits,
819
+ bounds=result_attrs.bounds,
820
+ )
@@ -18,6 +18,12 @@ from classiq.interface.generator.arith.binary_ops import (
18
18
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
19
19
  from classiq.interface.generator.function_params import get_zero_input_name
20
20
 
21
+ from classiq.model_expansions.arithmetic import NumericAttributes
22
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
23
+ compute_result_attrs_max,
24
+ compute_result_attrs_min,
25
+ )
26
+
21
27
  Numeric = (float, int)
22
28
 
23
29
 
@@ -55,11 +61,6 @@ class Extremum(ArithmeticOperationParams):
55
61
  def get_params_inplace_options(self) -> Iterable["Extremum"]:
56
62
  return ()
57
63
 
58
- @classmethod
59
- @abc.abstractmethod
60
- def _bound_calculator(cls, arg1: float, arg2: float) -> float:
61
- pass
62
-
63
64
  @staticmethod
64
65
  def _less_qubits_arg(
65
66
  arg1: RegisterOrConst, arg2: RegisterOrConst
@@ -78,40 +79,15 @@ class Extremum(ArithmeticOperationParams):
78
79
  pass
79
80
 
80
81
  def _get_result_register(self) -> RegisterArithmeticInfo:
81
- eff_left_arg = argument_utils.limit_fraction_places(
82
+ left_attrs = NumericAttributes.from_type_or_constant(
82
83
  self.left_arg, self.machine_precision
83
84
  )
84
- eff_right_arg = argument_utils.limit_fraction_places(
85
+ right_attrs = NumericAttributes.from_type_or_constant(
85
86
  self.right_arg, self.machine_precision
86
87
  )
87
- if argument_utils.arg_bounds_overlap((eff_left_arg, eff_right_arg)):
88
- return self._get_general_case_result_register(eff_left_arg, eff_right_arg)
89
- return argument_utils.as_arithmetic_info(
90
- self.preferred_arg(eff_left_arg, eff_right_arg)
91
- )
92
-
93
- def _get_general_case_result_register(
94
- self, eff_left_arg: RegisterOrConst, eff_right_arg: RegisterOrConst
95
- ) -> RegisterArithmeticInfo:
96
- integer_part_size = max(
97
- argument_utils.integer_part_size(eff_left_arg),
98
- argument_utils.integer_part_size(eff_right_arg),
99
- )
100
- fraction_places = max(
101
- argument_utils.fraction_places(eff_left_arg),
102
- argument_utils.fraction_places(eff_right_arg),
103
- )
104
- required_size = integer_part_size + fraction_places
105
- bounds = (
106
- self._bound_calculator(
107
- argument_utils.lower_bound(eff_left_arg),
108
- argument_utils.lower_bound(eff_right_arg),
109
- ),
110
- self._bound_calculator(
111
- argument_utils.upper_bound(eff_left_arg),
112
- argument_utils.upper_bound(eff_right_arg),
113
- ),
114
- )
88
+ result_attrs = self._compute_result_attrs(left_attrs, right_attrs)
89
+ bounds = result_attrs.bounds
90
+ fraction_places = result_attrs.fraction_digits
115
91
  if self.output_size:
116
92
  if fraction_places:
117
93
  raise ValueError(MODULO_WITH_FRACTION_PLACES_ERROR_MSG)
@@ -119,8 +95,8 @@ class Extremum(ArithmeticOperationParams):
119
95
  size=self.output_size, is_signed=False, fraction_places=0
120
96
  )
121
97
  bounds = number_utils.bounds_cut(bounds, max_bounds)
122
- size = self.output_size or required_size
123
- is_signed = self._include_sign and min(bounds) < 0
98
+ size = self.output_size or result_attrs.size
99
+ is_signed = self._include_sign and result_attrs.is_signed
124
100
  return RegisterArithmeticInfo(
125
101
  size=size,
126
102
  fraction_places=fraction_places,
@@ -133,14 +109,16 @@ class Extremum(ArithmeticOperationParams):
133
109
  ),
134
110
  )
135
111
 
112
+ @abc.abstractmethod
113
+ def _compute_result_attrs(
114
+ self, left_attrs: NumericAttributes, right_attrs: NumericAttributes
115
+ ) -> NumericAttributes:
116
+ pass
117
+
136
118
 
137
119
  class Min(Extremum):
138
120
  output_name = "min_value"
139
121
 
140
- @classmethod
141
- def _bound_calculator(cls, arg1: float, arg2: float) -> float:
142
- return min(arg1, arg2)
143
-
144
122
  @classmethod
145
123
  def preferred_arg(
146
124
  cls, arg1: RegisterOrConst, arg2: RegisterOrConst
@@ -152,14 +130,17 @@ class Min(Extremum):
152
130
  return arg2
153
131
  return cls._less_qubits_arg(arg1, arg2)
154
132
 
133
+ def _compute_result_attrs(
134
+ self, left_attrs: NumericAttributes, right_attrs: NumericAttributes
135
+ ) -> NumericAttributes:
136
+ return compute_result_attrs_min(
137
+ [left_attrs, right_attrs], self.machine_precision
138
+ )
139
+
155
140
 
156
141
  class Max(Extremum):
157
142
  output_name = "max_value"
158
143
 
159
- @classmethod
160
- def _bound_calculator(cls, arg1: float, arg2: float) -> float:
161
- return max(arg1, arg2)
162
-
163
144
  @classmethod
164
145
  def preferred_arg(
165
146
  cls, arg1: RegisterOrConst, arg2: RegisterOrConst
@@ -170,3 +151,10 @@ class Max(Extremum):
170
151
  if max2 > max1:
171
152
  return arg2
172
153
  return cls._less_qubits_arg(arg1, arg2)
154
+
155
+ def _compute_result_attrs(
156
+ self, left_attrs: NumericAttributes, right_attrs: NumericAttributes
157
+ ) -> NumericAttributes:
158
+ return compute_result_attrs_max(
159
+ [left_attrs, right_attrs], self.machine_precision
160
+ )
@@ -105,7 +105,10 @@ def bounds_to_attributes(
105
105
  lb: float, ub: float, fraction_places: int, machine_precision: int
106
106
  ) -> tuple[int, bool, int]:
107
107
  fraction_places = min(fraction_places, machine_precision)
108
- integers = bounds_to_integer_part_size(lb, ub)
108
+ if lb == ub == 0:
109
+ integers = 0
110
+ else:
111
+ integers = bounds_to_integer_part_size(lb, ub)
109
112
  return max(1, integers + fraction_places), lb < 0, fraction_places
110
113
 
111
114