classiq 0.37.1__py3-none-any.whl → 0.39.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 (280) hide show
  1. classiq/__init__.py +23 -24
  2. classiq/_analyzer_extras/_ipywidgets_async_extension.py +1 -1
  3. classiq/_analyzer_extras/interactive_hardware.py +3 -3
  4. classiq/_internals/api_wrapper.py +37 -17
  5. classiq/_internals/async_utils.py +1 -74
  6. classiq/_internals/authentication/device.py +9 -4
  7. classiq/_internals/authentication/password_manager.py +25 -10
  8. classiq/_internals/authentication/token_manager.py +2 -2
  9. classiq/_internals/client.py +24 -6
  10. classiq/_internals/jobs.py +10 -7
  11. classiq/analyzer/analyzer.py +29 -29
  12. classiq/analyzer/analyzer_utilities.py +5 -5
  13. classiq/analyzer/rb.py +4 -5
  14. classiq/analyzer/show_interactive_hack.py +6 -6
  15. classiq/applications/__init__.py +1 -8
  16. classiq/applications/chemistry/__init__.py +6 -0
  17. classiq/{applications_model_constructors → applications/chemistry}/chemistry_model_constructor.py +9 -16
  18. classiq/applications/combinatorial_helpers/allowed_constraints.py +20 -0
  19. classiq/applications/combinatorial_helpers/arithmetic/arithmetic_expression.py +35 -0
  20. classiq/applications/combinatorial_helpers/arithmetic/isolation.py +42 -0
  21. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +150 -0
  22. classiq/applications/combinatorial_helpers/encoding_mapping.py +107 -0
  23. classiq/applications/combinatorial_helpers/encoding_utils.py +122 -0
  24. classiq/applications/combinatorial_helpers/memory.py +77 -0
  25. classiq/applications/combinatorial_helpers/optimization_model.py +162 -0
  26. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_sparsing.py +31 -0
  27. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +75 -0
  28. classiq/applications/combinatorial_helpers/py.typed +0 -0
  29. classiq/applications/combinatorial_helpers/pyomo_utils.py +245 -0
  30. classiq/applications/combinatorial_helpers/solvers/__init__.py +0 -0
  31. classiq/applications/combinatorial_helpers/sympy_utils.py +22 -0
  32. classiq/applications/combinatorial_helpers/transformations/__init__.py +0 -0
  33. classiq/applications/combinatorial_helpers/transformations/encoding.py +187 -0
  34. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +142 -0
  35. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +122 -0
  36. classiq/applications/combinatorial_helpers/transformations/penalty.py +32 -0
  37. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +37 -0
  38. classiq/applications/combinatorial_helpers/transformations/sign_seperation.py +75 -0
  39. classiq/applications/combinatorial_helpers/transformations/slack_variables.py +88 -0
  40. classiq/applications/combinatorial_optimization/__init__.py +13 -2
  41. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +134 -0
  42. classiq/applications/finance/__init__.py +3 -2
  43. classiq/{applications_model_constructors → applications/finance}/finance_model_constructor.py +27 -30
  44. classiq/applications/grover/__init__.py +11 -0
  45. classiq/{applications_model_constructors → applications/grover}/grover_model_constructor.py +20 -91
  46. classiq/applications/libraries/__init__.py +0 -0
  47. classiq/applications/libraries/qmci_library.py +35 -0
  48. classiq/applications/qnn/circuit_utils.py +2 -2
  49. classiq/applications/qnn/gradients/quantum_gradient.py +2 -2
  50. classiq/applications/qnn/types.py +2 -2
  51. classiq/applications/qsvm/__init__.py +5 -1
  52. classiq/applications/qsvm/qsvm.py +4 -7
  53. classiq/applications/qsvm/qsvm_data_generation.py +2 -5
  54. classiq/exceptions.py +43 -1
  55. classiq/execution/all_hardware_devices.py +13 -0
  56. classiq/executor.py +12 -10
  57. classiq/interface/_version.py +1 -1
  58. classiq/interface/analyzer/analysis_params.py +6 -3
  59. classiq/interface/analyzer/result.py +12 -8
  60. classiq/interface/applications/qsvm.py +17 -3
  61. classiq/interface/ast_node.py +23 -0
  62. classiq/interface/backend/backend_preferences.py +4 -2
  63. classiq/interface/backend/pydantic_backend.py +3 -1
  64. classiq/interface/backend/quantum_backend_providers.py +1 -0
  65. classiq/interface/chemistry/fermionic_operator.py +15 -13
  66. classiq/interface/chemistry/ground_state_problem.py +18 -3
  67. classiq/interface/chemistry/molecule.py +8 -6
  68. classiq/interface/chemistry/operator.py +20 -14
  69. classiq/interface/combinatorial_optimization/examples/ascending_sequence.py +1 -1
  70. classiq/interface/combinatorial_optimization/examples/greater_than_ilp.py +1 -1
  71. classiq/interface/combinatorial_optimization/examples/ilp.py +2 -1
  72. classiq/interface/combinatorial_optimization/examples/integer_portfolio_optimization.py +2 -2
  73. classiq/interface/combinatorial_optimization/examples/mds.py +2 -1
  74. classiq/interface/combinatorial_optimization/examples/mht.py +8 -3
  75. classiq/interface/combinatorial_optimization/examples/mis.py +4 -1
  76. classiq/interface/combinatorial_optimization/examples/mvc.py +2 -1
  77. classiq/interface/combinatorial_optimization/examples/set_cover.py +2 -1
  78. classiq/interface/combinatorial_optimization/examples/tsp.py +4 -3
  79. classiq/interface/combinatorial_optimization/examples/tsp_digraph.py +6 -2
  80. classiq/interface/combinatorial_optimization/mht_qaoa_input.py +9 -3
  81. classiq/interface/executor/aws_execution_cost.py +4 -3
  82. classiq/interface/executor/estimation.py +2 -2
  83. classiq/interface/executor/execution_preferences.py +5 -34
  84. classiq/interface/executor/execution_request.py +15 -48
  85. classiq/interface/executor/optimizer_preferences.py +22 -13
  86. classiq/interface/executor/{quantum_program.py → quantum_code.py} +21 -15
  87. classiq/interface/executor/quantum_instruction_set.py +2 -1
  88. classiq/interface/executor/register_initialization.py +1 -3
  89. classiq/interface/executor/result.py +41 -10
  90. classiq/interface/executor/vqe_result.py +2 -2
  91. classiq/interface/finance/function_input.py +17 -4
  92. classiq/interface/finance/gaussian_model_input.py +3 -1
  93. classiq/interface/finance/log_normal_model_input.py +3 -1
  94. classiq/interface/finance/model_input.py +2 -0
  95. classiq/interface/generator/amplitude_loading.py +6 -3
  96. classiq/interface/generator/application_apis/__init__.py +1 -0
  97. classiq/interface/generator/application_apis/arithmetic_declarations.py +14 -0
  98. classiq/interface/generator/arith/argument_utils.py +14 -4
  99. classiq/interface/generator/arith/arithmetic.py +3 -1
  100. classiq/interface/generator/arith/arithmetic_arg_type_validator.py +12 -13
  101. classiq/interface/generator/arith/arithmetic_expression_abc.py +4 -1
  102. classiq/interface/generator/arith/arithmetic_expression_parser.py +8 -2
  103. classiq/interface/generator/arith/arithmetic_expression_validator.py +16 -2
  104. classiq/interface/generator/arith/arithmetic_operations.py +5 -10
  105. classiq/interface/generator/arith/ast_node_rewrite.py +1 -1
  106. classiq/interface/generator/arith/binary_ops.py +202 -54
  107. classiq/interface/generator/arith/extremum_operations.py +5 -3
  108. classiq/interface/generator/arith/logical_ops.py +4 -2
  109. classiq/interface/generator/arith/machine_precision.py +3 -0
  110. classiq/interface/generator/arith/number_utils.py +34 -44
  111. classiq/interface/generator/arith/register_user_input.py +21 -1
  112. classiq/interface/generator/arith/unary_ops.py +16 -25
  113. classiq/interface/generator/builtin_api_builder.py +0 -5
  114. classiq/interface/generator/chemistry_function_params.py +4 -4
  115. classiq/interface/generator/commuting_pauli_exponentiation.py +3 -1
  116. classiq/interface/generator/compiler_keywords.py +4 -0
  117. classiq/interface/generator/complex_type.py +3 -10
  118. classiq/interface/generator/constant.py +2 -3
  119. classiq/interface/generator/control_state.py +5 -3
  120. classiq/interface/generator/credit_risk_example/linear_gci.py +10 -3
  121. classiq/interface/generator/credit_risk_example/weighted_adder.py +14 -4
  122. classiq/interface/generator/expressions/atomic_expression_functions.py +5 -3
  123. classiq/interface/generator/expressions/evaluated_expression.py +18 -4
  124. classiq/interface/generator/expressions/expression.py +3 -5
  125. classiq/interface/generator/expressions/qmod_qscalar_proxy.py +33 -0
  126. classiq/interface/generator/expressions/sympy_supported_expressions.py +2 -1
  127. classiq/interface/generator/finance.py +1 -1
  128. classiq/interface/generator/function_params.py +7 -6
  129. classiq/interface/generator/functions/__init__.py +2 -2
  130. classiq/interface/generator/functions/builtins/__init__.py +15 -0
  131. classiq/interface/generator/functions/builtins/core_library/__init__.py +14 -0
  132. classiq/interface/generator/functions/builtins/core_library/chemistry_functions.py +0 -0
  133. classiq/interface/generator/functions/builtins/internal_operators.py +62 -0
  134. classiq/interface/generator/functions/{core_lib_declarations/quantum_functions/std_lib_functions.py → builtins/open_lib_functions.py} +612 -219
  135. classiq/interface/generator/functions/builtins/quantum_operators.py +37 -0
  136. classiq/interface/generator/functions/classical_type.py +2 -4
  137. classiq/interface/generator/functions/foreign_function_definition.py +12 -4
  138. classiq/interface/generator/functions/function_declaration.py +2 -2
  139. classiq/interface/generator/functions/function_implementation.py +8 -4
  140. classiq/interface/generator/functions/native_function_definition.py +4 -2
  141. classiq/interface/generator/functions/register.py +4 -2
  142. classiq/interface/generator/functions/register_mapping_data.py +14 -10
  143. classiq/interface/generator/generated_circuit_data.py +2 -2
  144. classiq/interface/generator/grover_operator.py +5 -3
  145. classiq/interface/generator/hamiltonian_evolution/suzuki_trotter.py +5 -1
  146. classiq/interface/generator/hardware/hardware_data.py +6 -4
  147. classiq/interface/generator/hardware_efficient_ansatz.py +25 -8
  148. classiq/interface/generator/hartree_fock.py +13 -3
  149. classiq/interface/generator/linear_pauli_rotations.py +3 -1
  150. classiq/interface/generator/mcu.py +5 -3
  151. classiq/interface/generator/mcx.py +7 -5
  152. classiq/interface/generator/model/classical_main_validator.py +1 -1
  153. classiq/interface/generator/model/constraints.py +2 -1
  154. classiq/interface/generator/model/model.py +12 -20
  155. classiq/interface/generator/model/preferences/preferences.py +4 -3
  156. classiq/interface/generator/oracles/custom_oracle.py +4 -2
  157. classiq/interface/generator/oracles/oracle_abc.py +2 -2
  158. classiq/interface/generator/qpe.py +6 -4
  159. classiq/interface/generator/qsvm.py +5 -8
  160. classiq/interface/generator/quantum_function_call.py +21 -16
  161. classiq/interface/generator/{generated_circuit.py → quantum_program.py} +10 -14
  162. classiq/interface/generator/range_types.py +3 -1
  163. classiq/interface/generator/slice_parsing_utils.py +8 -3
  164. classiq/interface/generator/standard_gates/controlled_standard_gates.py +4 -2
  165. classiq/interface/generator/state_preparation/metrics.py +2 -1
  166. classiq/interface/generator/state_preparation/state_preparation.py +7 -5
  167. classiq/interface/generator/state_propagator.py +16 -5
  168. classiq/interface/generator/types/builtin_struct_declarations/__init__.py +0 -1
  169. classiq/interface/generator/types/struct_declaration.py +10 -7
  170. classiq/interface/generator/ucc.py +6 -4
  171. classiq/interface/generator/unitary_gate.py +7 -3
  172. classiq/interface/generator/validations/flow_graph.py +6 -4
  173. classiq/interface/generator/validations/validator_functions.py +6 -4
  174. classiq/interface/hardware.py +2 -2
  175. classiq/interface/helpers/custom_encoders.py +3 -0
  176. classiq/interface/helpers/pydantic_model_helpers.py +0 -6
  177. classiq/interface/helpers/validation_helpers.py +1 -1
  178. classiq/interface/helpers/versioned_model.py +4 -1
  179. classiq/interface/ide/show.py +2 -2
  180. classiq/interface/jobs.py +72 -3
  181. classiq/interface/model/bind_operation.py +18 -11
  182. classiq/interface/model/call_synthesis_data.py +68 -0
  183. classiq/interface/model/classical_if.py +13 -0
  184. classiq/interface/model/classical_parameter_declaration.py +2 -3
  185. classiq/interface/model/control.py +16 -0
  186. classiq/interface/model/handle_binding.py +3 -2
  187. classiq/interface/model/inplace_binary_operation.py +2 -2
  188. classiq/interface/model/invert.py +10 -0
  189. classiq/interface/model/model.py +29 -22
  190. classiq/interface/model/native_function_definition.py +3 -5
  191. classiq/interface/model/power.py +12 -0
  192. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +9 -4
  193. classiq/interface/model/quantum_expressions/control_state.py +2 -2
  194. classiq/interface/model/quantum_function_call.py +33 -142
  195. classiq/interface/model/quantum_function_declaration.py +8 -0
  196. classiq/interface/model/quantum_if_operation.py +4 -5
  197. classiq/interface/model/quantum_lambda_function.py +58 -0
  198. classiq/{quantum_register.py → interface/model/quantum_register.py} +17 -9
  199. classiq/interface/model/quantum_statement.py +3 -2
  200. classiq/interface/model/quantum_type.py +58 -59
  201. classiq/interface/model/quantum_variable_declaration.py +3 -3
  202. classiq/interface/model/repeat.py +13 -0
  203. classiq/interface/model/resolvers/function_call_resolver.py +26 -0
  204. classiq/interface/model/statement_block.py +49 -0
  205. classiq/interface/model/validations/handles_validator.py +16 -18
  206. classiq/interface/model/within_apply_operation.py +11 -0
  207. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +4 -1
  208. classiq/interface/server/routes.py +5 -4
  209. classiq/qmod/__init__.py +13 -6
  210. classiq/qmod/builtins/classical_execution_primitives.py +27 -36
  211. classiq/qmod/builtins/classical_functions.py +22 -12
  212. classiq/qmod/builtins/functions.py +272 -328
  213. classiq/qmod/builtins/operations.py +171 -35
  214. classiq/qmod/builtins/structs.py +15 -15
  215. classiq/qmod/cfunc.py +42 -0
  216. classiq/qmod/classical_function.py +6 -14
  217. classiq/qmod/declaration_inferrer.py +12 -21
  218. classiq/qmod/expression_query.py +23 -0
  219. classiq/qmod/model_state_container.py +2 -0
  220. classiq/qmod/native/__init__.py +0 -0
  221. classiq/qmod/native/expression_to_qmod.py +189 -0
  222. classiq/qmod/native/pretty_printer.py +340 -0
  223. classiq/qmod/qfunc.py +27 -0
  224. classiq/qmod/qmod_constant.py +100 -0
  225. classiq/qmod/qmod_parameter.py +36 -13
  226. classiq/qmod/qmod_struct.py +3 -3
  227. classiq/qmod/qmod_variable.py +148 -31
  228. classiq/qmod/quantum_callable.py +1 -0
  229. classiq/qmod/quantum_expandable.py +18 -19
  230. classiq/qmod/quantum_function.py +41 -8
  231. classiq/qmod/symbolic.py +48 -5
  232. classiq/qmod/symbolic_expr.py +9 -0
  233. classiq/qmod/utilities.py +13 -0
  234. classiq/qmod/write_qmod.py +39 -0
  235. {classiq-0.37.1.dist-info → classiq-0.39.0.dist-info}/METADATA +2 -1
  236. {classiq-0.37.1.dist-info → classiq-0.39.0.dist-info}/RECORD +244 -225
  237. {classiq-0.37.1.dist-info → classiq-0.39.0.dist-info}/WHEEL +1 -1
  238. classiq/applications/benchmarking/__init__.py +0 -9
  239. classiq/applications/benchmarking/mirror_benchmarking.py +0 -67
  240. classiq/applications/numpy_utils.py +0 -37
  241. classiq/applications_model_constructors/__init__.py +0 -17
  242. classiq/applications_model_constructors/combinatorial_optimization_model_constructor.py +0 -178
  243. classiq/applications_model_constructors/libraries/qmci_library.py +0 -109
  244. classiq/builtin_functions/__init__.py +0 -43
  245. classiq/builtin_functions/amplitude_loading.py +0 -3
  246. classiq/builtin_functions/binary_ops.py +0 -1
  247. classiq/builtin_functions/exponentiation.py +0 -5
  248. classiq/builtin_functions/qpe.py +0 -4
  249. classiq/builtin_functions/qsvm.py +0 -7
  250. classiq/builtin_functions/range_types.py +0 -5
  251. classiq/builtin_functions/standard_gates.py +0 -1
  252. classiq/builtin_functions/state_preparation.py +0 -6
  253. classiq/builtin_functions/suzuki_trotter.py +0 -3
  254. classiq/interface/generator/expressions/qmod_qnum_proxy.py +0 -22
  255. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/__init__.py +0 -18
  256. classiq/interface/generator/functions/core_lib_declarations/quantum_operators.py +0 -169
  257. classiq/interface/generator/types/builtin_struct_declarations/qaoa_declarations.py +0 -23
  258. classiq/interface/generator/types/combinatorial_problem.py +0 -26
  259. classiq/interface/model/numeric_reinterpretation.py +0 -25
  260. classiq/interface/model/operator_synthesis_data.py +0 -48
  261. classiq/model/__init__.py +0 -14
  262. classiq/model/composite_function_generator.py +0 -33
  263. classiq/model/function_handler.py +0 -466
  264. classiq/model/function_handler.pyi +0 -152
  265. classiq/model/logic_flow.py +0 -149
  266. classiq/model/logic_flow_change_handler.py +0 -71
  267. classiq/model/model.py +0 -246
  268. classiq/quantum_functions/__init__.py +0 -17
  269. classiq/quantum_functions/annotation_parser.py +0 -207
  270. classiq/quantum_functions/decorators.py +0 -22
  271. classiq/quantum_functions/function_library.py +0 -181
  272. classiq/quantum_functions/function_parser.py +0 -74
  273. classiq/quantum_functions/quantum_function.py +0 -236
  274. /classiq/{applications_model_constructors/libraries → applications/combinatorial_helpers}/__init__.py +0 -0
  275. /classiq/{interface/generator/functions/core_lib_declarations → applications/combinatorial_helpers/arithmetic}/__init__.py +0 -0
  276. /classiq/{interface/generator/functions/core_lib_declarations/quantum_functions/chemistry_functions.py → applications/combinatorial_helpers/pauli_helpers/__init__.py} +0 -0
  277. /classiq/{applications_model_constructors → applications}/libraries/ampltitude_estimation_library.py +0 -0
  278. /classiq/{applications_model_constructors → applications/qsvm}/qsvm_model_constructor.py +0 -0
  279. /classiq/interface/generator/functions/{core_lib_declarations/quantum_functions → builtins/core_library}/atomic_quantum_functions.py +0 -0
  280. /classiq/interface/generator/functions/{core_lib_declarations/quantum_functions → builtins/core_library}/exponentiation_functions.py +0 -0
@@ -8,6 +8,7 @@ from typing import (
8
8
  Literal,
9
9
  Optional,
10
10
  Tuple,
11
+ Type,
11
12
  TypeVar,
12
13
  Union,
13
14
  )
@@ -28,6 +29,7 @@ from classiq.interface.generator.arith.unary_ops import Negation
28
29
  from classiq.interface.generator.function_params import get_zero_input_name
29
30
 
30
31
  from classiq._internals.enum_utils import StrEnum
32
+ from classiq.exceptions import ClassiqValueError
31
33
 
32
34
  LeftDataT = TypeVar("LeftDataT")
33
35
  RightDataT = TypeVar("RightDataT")
@@ -61,23 +63,36 @@ class BinaryOpParams(
61
63
  left_arg = values.get("left_arg")
62
64
  right_arg = values.get("right_arg")
63
65
  if isinstance(left_arg, Numeric) and isinstance(right_arg, Numeric):
64
- raise ValueError("One argument must be a register")
66
+ raise ClassiqValueError("One argument must be a register")
65
67
  if left_arg is right_arg and isinstance(left_arg, pydantic.BaseModel):
66
68
  # In case both arguments refer to the same object, copy it.
67
69
  # This prevents changes performed on one argument to affect the other.
68
70
  values["right_arg"] = left_arg.copy(deep=True)
69
71
  return values
70
72
 
73
+ def garbage_output_size(self) -> pydantic.NonNegativeInt:
74
+ return 0
75
+
71
76
  def _create_ios(self) -> None:
72
77
  self._inputs = dict()
73
78
  if isinstance(self.left_arg, RegisterArithmeticInfo):
74
79
  self._inputs[self.left_arg_name] = self.left_arg
75
80
  if isinstance(self.right_arg, RegisterArithmeticInfo):
76
81
  self._inputs[self.right_arg_name] = self.right_arg
77
- zero_input_name = get_zero_input_name(self.output_name)
78
- self._zero_inputs = {zero_input_name: self.result_register}
79
82
  self._outputs = {**self._inputs, self.output_name: self.result_register}
80
83
 
84
+ garbage_size = self.garbage_output_size()
85
+ if garbage_size > 0:
86
+ self._outputs[self.garbage_output_name] = RegisterArithmeticInfo(
87
+ size=garbage_size
88
+ )
89
+
90
+ zero_input_name = get_zero_input_name(self.output_name)
91
+ zero_input_size = self.result_register.size + garbage_size
92
+ self._zero_inputs = {
93
+ zero_input_name: RegisterArithmeticInfo(size=zero_input_size)
94
+ }
95
+
81
96
  def is_inplaced(self) -> bool:
82
97
  return False
83
98
 
@@ -96,18 +111,17 @@ class InplacableBinaryOpParams(
96
111
  right_arg = values.get("right_arg")
97
112
  inplace_arg: Optional[ArgToInplace] = values.get("inplace_arg")
98
113
  if inplace_arg == ArgToInplace.RIGHT and isinstance(right_arg, Numeric):
99
- raise ValueError(_NumericArgumentInplaceErrorMessage.format(right_arg))
114
+ raise ClassiqValueError(
115
+ _NumericArgumentInplaceErrorMessage.format(right_arg)
116
+ )
100
117
  elif inplace_arg == ArgToInplace.LEFT and isinstance(left_arg, Numeric):
101
- raise ValueError(_NumericArgumentInplaceErrorMessage.format(left_arg))
118
+ raise ClassiqValueError(
119
+ _NumericArgumentInplaceErrorMessage.format(left_arg)
120
+ )
102
121
  return values
103
122
 
104
123
  def _create_ios(self) -> None:
105
124
  BinaryOpParams._create_ios(self)
106
- garbage_size = self.garbage_output_size()
107
- if garbage_size > 0:
108
- self._outputs[self.garbage_output_name] = RegisterArithmeticInfo(
109
- size=garbage_size
110
- )
111
125
  if self.inplace_arg is None:
112
126
  return
113
127
  inplace_arg_name = (
@@ -117,7 +131,7 @@ class InplacableBinaryOpParams(
117
131
  )
118
132
  self._outputs.pop(inplace_arg_name)
119
133
 
120
- self._set_inplace_zero_inputs(inplace_arg_name, garbage_size)
134
+ self._set_inplace_zero_inputs(inplace_arg_name, self.garbage_output_size())
121
135
 
122
136
  def _set_inplace_zero_inputs(
123
137
  self, inplace_arg_name: str, garbage_size: int
@@ -193,7 +207,7 @@ class BinaryOpWithIntInputs(BinaryOpParams[RegisterOrInt, RegisterOrInt]):
193
207
  and right_arg.fraction_places > 0
194
208
  )
195
209
  if is_left_arg_float_register or is_right_arg_float_register:
196
- raise ValueError(BOOLEAN_OP_WITH_FRACTIONS_ERROR)
210
+ raise ClassiqValueError(BOOLEAN_OP_WITH_FRACTIONS_ERROR)
197
211
  return values
198
212
 
199
213
  @staticmethod
@@ -251,19 +265,28 @@ class Adder(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
251
265
  ub = argument_utils.upper_bound(self.left_arg) + argument_utils.upper_bound(
252
266
  self.right_arg
253
267
  )
254
- integer_part_size = number_utils.bounds_to_integer_part_size(lb, ub)
255
268
  fraction_places = max(
256
- self._compute_fraction_places(self.left_arg),
257
- self._compute_fraction_places(self.right_arg),
269
+ argument_utils.fraction_places(self.left_arg),
270
+ argument_utils.fraction_places(self.right_arg),
258
271
  )
259
- size_needed = integer_part_size + fraction_places
260
272
  return RegisterArithmeticInfo(
261
- size=self.output_size or size_needed,
273
+ size=self.output_size or self._get_output_size(ub, lb, fraction_places),
262
274
  fraction_places=fraction_places,
263
275
  is_signed=self._include_sign and lb < 0,
264
276
  bounds=(lb, ub) if self._include_sign else None,
265
277
  )
266
278
 
279
+ def _get_output_size(self, ub: float, lb: float, fraction_places: int) -> int:
280
+ if isinstance(self.left_arg, float) and self.left_arg == 0.0:
281
+ assert isinstance(self.right_arg, RegisterArithmeticInfo)
282
+ return self.right_arg.size
283
+ elif isinstance(self.right_arg, float) and self.right_arg == 0.0:
284
+ assert isinstance(self.left_arg, RegisterArithmeticInfo)
285
+ return self.left_arg.size
286
+
287
+ integer_part_size = number_utils.bounds_to_integer_part_size(lb, ub)
288
+ return integer_part_size + fraction_places
289
+
267
290
 
268
291
  class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
269
292
  output_name = "difference"
@@ -275,19 +298,28 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
275
298
  argument_utils.upper_bound(self.left_arg)
276
299
  - argument_utils.lower_bound(self.right_arg),
277
300
  )
278
- integer_part_size = number_utils.bounds_to_integer_part_size(*bounds)
279
301
  fraction_places = max(
280
- self._compute_fraction_places(self.left_arg),
281
- self._compute_fraction_places(self.right_arg),
302
+ argument_utils.fraction_places(self.left_arg),
303
+ argument_utils.fraction_places(self.right_arg),
282
304
  )
283
- size_needed = integer_part_size + fraction_places
305
+
284
306
  return RegisterArithmeticInfo(
285
- size=self.output_size or size_needed,
307
+ size=self.output_size or self._get_output_size(bounds, fraction_places),
286
308
  fraction_places=fraction_places,
287
309
  is_signed=self._include_sign and min(bounds) < 0,
288
310
  bounds=self._legal_bounds(bounds),
289
311
  )
290
312
 
313
+ def _get_output_size(
314
+ self, bounds: Tuple[float, float], fraction_places: int
315
+ ) -> int:
316
+ if isinstance(self.right_arg, float) and self.right_arg == 0:
317
+ assert isinstance(self.left_arg, RegisterArithmeticInfo)
318
+ return self.left_arg.size
319
+ integer_part_size = number_utils.bounds_to_integer_part_size(*bounds)
320
+ size_needed = integer_part_size + fraction_places
321
+ return size_needed
322
+
291
323
  def garbage_output_size(self) -> pydantic.NonNegativeInt:
292
324
  if not isinstance(self.right_arg, RegisterArithmeticInfo):
293
325
  adder_params = Adder(
@@ -327,7 +359,7 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
327
359
  return self.inplace_arg == ArgToInplace.LEFT
328
360
 
329
361
  def _expected_negation_output_size(self) -> int:
330
- return self._compute_fraction_places(self.right_arg) + min(
362
+ return argument_utils.fraction_places(self.right_arg) + min(
331
363
  self.result_register.integer_part_size,
332
364
  number_utils.bounds_to_integer_part_size(
333
365
  *(-bound for bound in argument_utils.bounds(self.right_arg))
@@ -356,31 +388,80 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
356
388
  class Multiplier(BinaryOpWithFloatInputs):
357
389
  output_name = "product"
358
390
 
359
- def _get_result_register(self) -> RegisterArithmeticInfo:
360
- fraction_places = self._compute_fraction_places(
361
- self.left_arg
362
- ) + self._compute_fraction_places(self.right_arg)
391
+ def expected_fraction_places(self) -> int:
392
+ return argument_utils.fraction_places(
393
+ argument_utils.limit_fraction_places(
394
+ self.left_arg, machine_precision=self.machine_precision
395
+ )
396
+ ) + argument_utils.fraction_places(
397
+ argument_utils.limit_fraction_places(
398
+ self.right_arg, machine_precision=self.machine_precision
399
+ )
400
+ )
401
+
402
+ @staticmethod
403
+ def _get_bounds(
404
+ args: Tuple[RegisterOrConst, RegisterOrConst], machine_precision: int
405
+ ) -> Tuple[float, float]:
363
406
  extremal_values = [
364
407
  left * right
365
- for left in argument_utils.bounds(self.left_arg)
366
- for right in argument_utils.bounds(self.right_arg)
408
+ for left in argument_utils.bounds(args[0])
409
+ for right in argument_utils.bounds(args[1])
367
410
  ]
368
- bounds = (min(extremal_values), max(extremal_values))
369
- largest_bound = max(bounds, key=abs)
370
- integer_places = int(largest_bound).bit_length() + int(largest_bound < 0)
371
- extra_sign_bit = int(
372
- argument_utils.is_signed(self.left_arg)
373
- and argument_utils.is_signed(self.right_arg)
374
- and largest_bound > 0
411
+ return (
412
+ number_utils.limit_fraction_places(
413
+ min(extremal_values), machine_precision=machine_precision
414
+ ),
415
+ number_utils.limit_fraction_places(
416
+ max(extremal_values), machine_precision=machine_precision
417
+ ),
418
+ )
419
+
420
+ def _get_result_register(self) -> RegisterArithmeticInfo:
421
+ fraction_places = min(self.machine_precision, self.expected_fraction_places())
422
+ left_arg = argument_utils.limit_fraction_places(
423
+ self.left_arg, machine_precision=self.machine_precision
375
424
  )
425
+ right_arg = argument_utils.limit_fraction_places(
426
+ self.right_arg, machine_precision=self.machine_precision
427
+ )
428
+ bounds = self._get_bounds((left_arg, right_arg), self.machine_precision)
429
+
376
430
  return RegisterArithmeticInfo(
377
431
  size=self.output_size
378
- or max(1, integer_places + fraction_places + extra_sign_bit),
432
+ or self._get_output_size(bounds, fraction_places, left_arg, right_arg),
379
433
  fraction_places=fraction_places,
380
434
  is_signed=self._include_sign and min(bounds) < 0,
381
435
  bounds=self._legal_bounds(bounds),
382
436
  )
383
437
 
438
+ @staticmethod
439
+ def _get_output_size(
440
+ bounds: Tuple[float, float],
441
+ fraction_places: int,
442
+ left_arg: Union[RegisterArithmeticInfo, float],
443
+ right_arg: Union[RegisterArithmeticInfo, float],
444
+ ) -> int:
445
+ if isinstance(left_arg, float) and left_arg == 1.0:
446
+ assert isinstance(right_arg, RegisterArithmeticInfo)
447
+ return right_arg.size
448
+ elif isinstance(right_arg, float) and right_arg == 1.0:
449
+ assert isinstance(left_arg, RegisterArithmeticInfo)
450
+ return left_arg.size
451
+ largest_bound = max(bounds, key=abs)
452
+ integer_places = int(largest_bound).bit_length() + int(largest_bound < 0)
453
+ extra_sign_bit = int(
454
+ argument_utils.is_signed(left_arg)
455
+ and argument_utils.is_signed(right_arg)
456
+ and largest_bound > 0
457
+ )
458
+ return max(1, integer_places + fraction_places + extra_sign_bit)
459
+
460
+ def garbage_output_size(self) -> pydantic.NonNegativeInt:
461
+ return max(
462
+ 0, self.expected_fraction_places() - self.result_register.fraction_places
463
+ )
464
+
384
465
 
385
466
  class Comparator(BinaryOpWithFloatInputs):
386
467
  output_size: Literal[1] = 1
@@ -419,26 +500,53 @@ class Power(BinaryOpParams[RegisterArithmeticInfo, pydantic.PositiveInt]):
419
500
  @pydantic.validator("right_arg", pre=True)
420
501
  def _validate_legal_power(cls, right_arg: Any) -> pydantic.PositiveInt:
421
502
  if not float(right_arg).is_integer():
422
- raise ValueError("Power must be an integer")
503
+ raise ClassiqValueError("Power must be an integer")
423
504
  if right_arg <= 0:
424
- raise ValueError("Power must be greater than one")
505
+ raise ClassiqValueError("Power must be greater than one")
425
506
  return int(right_arg)
426
507
 
508
+ def expected_fraction_places(self) -> int:
509
+ return (
510
+ argument_utils.fraction_places(
511
+ argument_utils.limit_fraction_places(
512
+ self.left_arg, machine_precision=self.machine_precision
513
+ )
514
+ )
515
+ * self.right_arg
516
+ )
517
+
427
518
  def _get_result_bounds(self) -> Tuple[float, float]:
428
- if (self.right_arg % 2) or min(self.left_arg.bounds) >= 0:
519
+ bounds = [
520
+ number_utils.limit_fraction_places(
521
+ bound, machine_precision=self.machine_precision
522
+ )
523
+ for bound in self.left_arg.bounds
524
+ ]
525
+ if (self.right_arg % 2) or min(bounds) >= 0:
429
526
  return (
430
- self.left_arg.bounds[0] ** self.right_arg,
431
- self.left_arg.bounds[1] ** self.right_arg,
527
+ number_utils.limit_fraction_places(
528
+ bounds[0] ** self.right_arg,
529
+ machine_precision=self.machine_precision,
530
+ ),
531
+ number_utils.limit_fraction_places(
532
+ bounds[1] ** self.right_arg,
533
+ machine_precision=self.machine_precision,
534
+ ),
432
535
  )
433
- return 0.0, max(abs(bound) for bound in self.left_arg.bounds) ** self.right_arg
536
+ return 0.0, number_utils.limit_fraction_places(
537
+ max(abs(bound) for bound in bounds) ** self.right_arg,
538
+ machine_precision=self.machine_precision,
539
+ )
434
540
 
435
541
  def _get_result_register(self) -> RegisterArithmeticInfo:
436
542
  if self.output_size:
437
543
  return RegisterArithmeticInfo(size=self.output_size)
438
544
 
439
- fraction_places: int = self.left_arg.fraction_places * self.right_arg
545
+ fraction_places = min(self.machine_precision, self.expected_fraction_places())
440
546
  bounds = self._get_result_bounds()
441
547
  size = number_utils.bounds_to_integer_part_size(*bounds) + fraction_places
548
+ if bounds[0] == bounds[1]:
549
+ size = 1
442
550
  return RegisterArithmeticInfo(
443
551
  size=size,
444
552
  is_signed=self.left_arg.is_signed and (self.right_arg % 2 == 1),
@@ -446,6 +554,50 @@ class Power(BinaryOpParams[RegisterArithmeticInfo, pydantic.PositiveInt]):
446
554
  bounds=bounds,
447
555
  )
448
556
 
557
+ def _get_inner_action_garbage_size(
558
+ self,
559
+ action_type: Union[Type["Power"], Type[Multiplier]],
560
+ *,
561
+ arg: RegisterArithmeticInfo,
562
+ action_right_arg: RegisterOrConst,
563
+ compute_power: int
564
+ ) -> pydantic.NonNegativeInt:
565
+ inner_compute_power_params = Power(
566
+ left_arg=arg,
567
+ right_arg=compute_power,
568
+ output_size=self.output_size,
569
+ machine_precision=self.machine_precision,
570
+ )
571
+ return action_type(
572
+ left_arg=inner_compute_power_params.result_register,
573
+ right_arg=action_right_arg,
574
+ output_size=self.output_size,
575
+ machine_precision=self.machine_precision,
576
+ ).garbage_output_size()
577
+
578
+ def garbage_output_size(self) -> pydantic.NonNegativeInt:
579
+ arg = self.left_arg
580
+ power = self.right_arg
581
+ if power == 1:
582
+ return 0
583
+ if (
584
+ power == 2
585
+ or (arg.size == 1 and arg.fraction_places == 0)
586
+ or self.output_size == 1
587
+ ):
588
+ return max(
589
+ 0,
590
+ self.expected_fraction_places() - self.result_register.fraction_places,
591
+ )
592
+
593
+ if power % 2 == 0:
594
+ return self._get_inner_action_garbage_size(
595
+ Power, arg=arg, action_right_arg=power // 2, compute_power=2
596
+ )
597
+ return self._get_inner_action_garbage_size(
598
+ Multiplier, arg=arg, action_right_arg=arg, compute_power=power - 1
599
+ )
600
+
449
601
 
450
602
  class EffectiveUnaryOpParams(
451
603
  InplacableBinaryOpParams[RegisterArithmeticInfo, RightDataT], Generic[RightDataT]
@@ -465,9 +617,9 @@ class LShift(EffectiveUnaryOpParams[pydantic.NonNegativeInt]):
465
617
  arg = values.get("left_arg")
466
618
  shift = values.get("right_arg")
467
619
  if not isinstance(arg, RegisterArithmeticInfo):
468
- raise ValueError("left arg must be a RegisterArithmeticInfo")
620
+ raise ClassiqValueError("left arg must be a RegisterArithmeticInfo")
469
621
  if not isinstance(shift, int):
470
- raise ValueError("Shift must be an integer")
622
+ raise ClassiqValueError("Shift must be an integer")
471
623
  assert arg.fraction_places - shift <= 0, _FLOATING_POINT_MODULO_ERROR_MESSAGE
472
624
  return values
473
625
 
@@ -506,9 +658,9 @@ class RShift(EffectiveUnaryOpParams[pydantic.NonNegativeInt]):
506
658
  arg = values.get("left_arg")
507
659
  shift = values.get("right_arg")
508
660
  if not isinstance(arg, RegisterArithmeticInfo):
509
- raise ValueError("left arg must be a RegisterArithmeticInfo")
661
+ raise ClassiqValueError("left arg must be a RegisterArithmeticInfo")
510
662
  if not isinstance(shift, int):
511
- raise ValueError("Shift must be an integer")
663
+ raise ClassiqValueError("Shift must be an integer")
512
664
  assert (
513
665
  cls._shifted_fraction_places(arg=arg, shift=shift) == 0
514
666
  ), _FLOATING_POINT_MODULO_ERROR_MESSAGE
@@ -549,7 +701,7 @@ class CyclicShift(EffectiveUnaryOpParams[int]):
549
701
  return values
550
702
  arg = values.get("left_arg")
551
703
  if not isinstance(arg, RegisterArithmeticInfo):
552
- raise ValueError("left arg must be a RegisterArithmeticInfo")
704
+ raise ClassiqValueError("left arg must be a RegisterArithmeticInfo")
553
705
  assert arg.fraction_places == 0, _FLOATING_POINT_MODULO_ERROR_MESSAGE
554
706
  return values
555
707
 
@@ -590,11 +742,7 @@ class Modulo(EffectiveUnaryOpParams[int]):
590
742
  values["output_size"] = None
591
743
  return 2 ** (repr_qubits)
592
744
 
593
- @property
594
- def result_size(self) -> int:
595
- return round(math.log2(self.right_arg))
596
-
597
745
  def _get_result_register(self) -> RegisterArithmeticInfo:
598
746
  return RegisterArithmeticInfo(
599
- size=self.result_size, is_signed=False, fraction_places=0
747
+ size=round(math.log2(self.right_arg)), is_signed=False, fraction_places=0
600
748
  )
@@ -15,6 +15,8 @@ from classiq.interface.generator.arith.binary_ops import (
15
15
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
16
16
  from classiq.interface.generator.function_params import get_zero_input_name
17
17
 
18
+ from classiq.exceptions import ClassiqValueError
19
+
18
20
  Numeric = (float, int)
19
21
 
20
22
 
@@ -27,7 +29,7 @@ class Extremum(ArithmeticOperationParams):
27
29
  left_arg = values.get("left_arg")
28
30
  right_arg = values.get("right_arg")
29
31
  if isinstance(left_arg, Numeric) and isinstance(right_arg, Numeric):
30
- raise ValueError("One argument must be a register")
32
+ raise ClassiqValueError("One argument must be a register")
31
33
  if left_arg is right_arg and isinstance(left_arg, pydantic.BaseModel):
32
34
  # In case both arguments refer to the same object, copy it.
33
35
  # This prevents changes performed on one argument from affecting the other.
@@ -61,8 +63,8 @@ class Extremum(ArithmeticOperationParams):
61
63
  argument_utils.integer_part_size(self.right_arg),
62
64
  )
63
65
  fraction_places = max(
64
- self._compute_fraction_places(self.left_arg),
65
- self._compute_fraction_places(self.right_arg),
66
+ argument_utils.fraction_places(self.left_arg),
67
+ argument_utils.fraction_places(self.right_arg),
66
68
  )
67
69
  required_size = integer_part_size + fraction_places
68
70
  bounds = (
@@ -9,6 +9,8 @@ from classiq.interface.generator.arith.arithmetic_operations import (
9
9
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
10
10
  from classiq.interface.generator.function_params import get_zero_input_name
11
11
 
12
+ from classiq.exceptions import ClassiqValueError
13
+
12
14
 
13
15
  def get_arg_name(idx: int) -> str:
14
16
  return f"arg_{idx}"
@@ -25,7 +27,7 @@ class LogicalOps(ArithmeticOperationParams):
25
27
  @pydantic.validator("output_size")
26
28
  def _validate_output_size(cls, output_size: Optional[int]) -> int:
27
29
  if output_size is not None and output_size != 1:
28
- raise ValueError("logical operation output size must be 1")
30
+ raise ClassiqValueError("logical operation output size must be 1")
29
31
  return 1
30
32
 
31
33
  @pydantic.validator("args")
@@ -34,7 +36,7 @@ class LogicalOps(ArithmeticOperationParams):
34
36
  ) -> List[RegisterOrConst]:
35
37
  for arg_idx, arg in enumerate(arguments):
36
38
  if isinstance(arg, RegisterArithmeticInfo) and not arg.is_boolean_register:
37
- raise ValueError(
39
+ raise ClassiqValueError(
38
40
  f"All inputs to logical and must be of size 1 (at argument #{arg_idx})"
39
41
  )
40
42
  return arguments
@@ -0,0 +1,3 @@
1
+ from typing import Final
2
+
3
+ DEFAULT_MACHINE_PRECISION: Final[int] = 8
@@ -1,7 +1,6 @@
1
- from typing import Tuple, Union
1
+ from typing import Final, Tuple, Union
2
2
 
3
- MAXIMAL_MACHINE_PRECISION: int = 20
4
- MAX_FRACTION_PLACES: int = 8
3
+ MAXIMAL_MACHINE_PRECISION: Final[int] = 20
5
4
 
6
5
 
7
6
  def signed_int_to_unsigned(number: int) -> int:
@@ -32,8 +31,8 @@ def binary_to_float_or_int(
32
31
  return binary_to_float(bin_rep, fraction_part_size, is_signed)
33
32
 
34
33
 
35
- def _get_fraction_places(*, binary_value: str, machine_precision: int) -> int:
36
- fraction_places = machine_precision
34
+ def _get_fraction_places(*, binary_value: str) -> int:
35
+ fraction_places = MAXIMAL_MACHINE_PRECISION
37
36
  for bit in reversed(binary_value):
38
37
  if bit == "1" or fraction_places == 0:
39
38
  return fraction_places
@@ -41,79 +40,70 @@ def _get_fraction_places(*, binary_value: str, machine_precision: int) -> int:
41
40
  return fraction_places
42
41
 
43
42
 
44
- def get_int_representation_and_fraction_places(
45
- float_value: float, *, machine_precision: int
46
- ) -> Tuple[int, int]:
47
- int_val = signed_int_to_unsigned(int(float_value * 2**machine_precision))
43
+ def get_int_representation_and_fraction_places(float_value: float) -> Tuple[int, int]:
44
+ int_val = signed_int_to_unsigned(int(float_value * 2**MAXIMAL_MACHINE_PRECISION))
48
45
  if int_val == 0:
49
46
  return 0, 0
50
- fraction_places = _get_fraction_places(
51
- binary_value=bin(int_val)[2:], machine_precision=machine_precision
52
- )
53
- int_val = int_val >> (machine_precision - fraction_places)
47
+ fraction_places = _get_fraction_places(binary_value=bin(int_val)[2:])
48
+ int_val = int_val >> (MAXIMAL_MACHINE_PRECISION - fraction_places)
54
49
  return int_val, fraction_places
55
50
 
56
51
 
57
- def fraction_places(float_value: float, *, machine_precision: int) -> int:
58
- int_val = signed_int_to_unsigned(int(float_value * 2**machine_precision))
52
+ def fraction_places(float_value: float) -> int:
53
+ int_val = signed_int_to_unsigned(int(float_value * 2**MAXIMAL_MACHINE_PRECISION))
59
54
  if int_val == 0:
60
55
  return 0
61
- return _get_fraction_places(
62
- binary_value=bin(int_val)[2:], machine_precision=machine_precision
63
- )
56
+ return _get_fraction_places(binary_value=bin(int_val)[2:])
64
57
 
65
58
 
66
59
  def _bit_length(integer_representation: int) -> int:
67
60
  return 1 if integer_representation == 0 else integer_representation.bit_length()
68
61
 
69
62
 
70
- def binary_string(
71
- float_value: float, *, machine_precision: int = MAXIMAL_MACHINE_PRECISION
72
- ) -> str:
73
- int_val, _ = get_int_representation_and_fraction_places(
74
- float_value=float_value, machine_precision=machine_precision
75
- )
63
+ def binary_string(float_value: float) -> str:
64
+ int_val, _ = get_int_representation_and_fraction_places(float_value)
76
65
  bin_rep = bin(int_val)[2:]
77
- size_diff = size(
78
- float_value=float_value, machine_precision=machine_precision
79
- ) - len(bin_rep)
66
+ size_diff = size(float_value=float_value) - len(bin_rep)
80
67
  extension_bit = "0" if float_value >= 0 else "1"
81
68
  return bin_rep[::-1] + extension_bit * size_diff
82
69
 
83
70
 
84
71
  def integer_part_size(float_value: float) -> int:
85
- int_val, fraction_places = get_int_representation_and_fraction_places(
86
- float_value=float_value, machine_precision=MAXIMAL_MACHINE_PRECISION
87
- )
72
+ int_val, fraction_places = get_int_representation_and_fraction_places(float_value)
88
73
  return max(_bit_length(int_val) - fraction_places, 0)
89
74
 
90
75
 
91
- def size(float_value: float, *, machine_precision: int) -> int:
92
- int_val, fraction_places = get_int_representation_and_fraction_places(
93
- float_value=float_value, machine_precision=machine_precision
94
- )
76
+ def size(float_value: float) -> int:
77
+ int_val, fraction_places = get_int_representation_and_fraction_places(float_value)
95
78
  return max(_bit_length(int_val), fraction_places)
96
79
 
97
80
 
81
+ def _is_extra_sign_bit_needed(*, lb: float, ub: float) -> bool:
82
+ integer_lb = lb * 2 ** fraction_places(lb)
83
+ max_represented_number = (
84
+ 2 ** (len(binary_string(integer_lb)) - 1) - 1
85
+ ) / 2 ** fraction_places(lb)
86
+ return ub > max_represented_number
87
+
88
+
98
89
  def bounds_to_integer_part_size(lb: float, ub: float) -> int:
99
90
  lb, ub = min(lb, ub), max(lb, ub)
100
91
  ub_integer_part_size: int = integer_part_size(float_value=ub)
101
92
  lb_integer_part_size: int = integer_part_size(float_value=lb)
102
- if lb == 0:
93
+ if lb >= 0:
103
94
  return ub_integer_part_size
104
- if ub == 0:
95
+ if ub <= 0:
105
96
  return lb_integer_part_size
106
- is_extra_bit_needed = lb < 0 < ub and ub_integer_part_size >= lb_integer_part_size
107
- return max(ub_integer_part_size + 1 * is_extra_bit_needed, lb_integer_part_size)
97
+ return max(
98
+ ub_integer_part_size + 1 * _is_extra_sign_bit_needed(lb=lb, ub=ub),
99
+ lb_integer_part_size,
100
+ )
108
101
 
109
102
 
110
103
  def limit_fraction_places(number: float, *, machine_precision: int) -> float:
111
- orig_bin_rep = binary_string(number, machine_precision=MAXIMAL_MACHINE_PRECISION)[
112
- ::-1
113
- ]
114
- orig_fractions = fraction_places(
115
- number, machine_precision=MAXIMAL_MACHINE_PRECISION
116
- )
104
+ orig_bin_rep = binary_string(number)[::-1]
105
+ orig_fractions = fraction_places(number)
106
+
117
107
  removed_fractions = max(orig_fractions - machine_precision, 0)
118
108
  return binary_to_float(
119
109
  bin_rep=orig_bin_rep[: len(orig_bin_rep) - removed_fractions],
@@ -2,6 +2,7 @@ from typing import Any, Dict, Optional
2
2
 
3
3
  import pydantic
4
4
 
5
+ from classiq.interface.generator.arith import number_utils
5
6
  from classiq.interface.helpers.custom_pydantic_types import PydanticFloatTuple
6
7
  from classiq.interface.helpers.hashable_pydantic_base_model import (
7
8
  HashablePydanticBaseModel,
@@ -29,7 +30,17 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
29
30
  if bounds is not None:
30
31
  if min(bounds) < 0:
31
32
  assert values.get("is_signed")
32
- return tuple(bounds) # type: ignore[return-value]
33
+ fraction_places = values.get("fraction_places")
34
+ if not isinstance(fraction_places, int):
35
+ raise ClassiqValueError(
36
+ "RegisterUserInput must have an integer fraction_places"
37
+ )
38
+ return number_utils.limit_fraction_places(
39
+ min(bounds), machine_precision=fraction_places
40
+ ), number_utils.limit_fraction_places(
41
+ max(bounds), machine_precision=fraction_places
42
+ )
43
+
33
44
  size = values.get("size")
34
45
  if not isinstance(size, int):
35
46
  raise ClassiqValueError("RegisterUserInput must have an integer size")
@@ -39,6 +50,15 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
39
50
  fraction_factor = float(2 ** -values.get("fraction_places", 0))
40
51
  return (lb * fraction_factor, ub * fraction_factor)
41
52
 
53
+ def limit_fraction_places(self, machine_precision: int) -> "RegisterArithmeticInfo":
54
+ truncated_bits: int = max(self.fraction_places - machine_precision, 0)
55
+ return RegisterArithmeticInfo(
56
+ size=self.size - truncated_bits,
57
+ is_signed=self.is_signed,
58
+ fraction_places=self.fraction_places - truncated_bits,
59
+ bounds=self.bounds,
60
+ )
61
+
42
62
  @property
43
63
  def is_boolean_register(self) -> bool:
44
64
  return (not self.is_signed) and (self.size == 1) and (self.fraction_places == 0)