classiq 0.37.0__py3-none-any.whl → 0.38.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 (228) hide show
  1. classiq/__init__.py +2 -2
  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 +24 -16
  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 +13 -5
  10. classiq/_internals/jobs.py +10 -7
  11. classiq/analyzer/analyzer.py +26 -28
  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/benchmarking/mirror_benchmarking.py +9 -6
  16. classiq/applications/combinatorial_optimization/__init__.py +5 -0
  17. classiq/applications/qnn/circuit_utils.py +2 -2
  18. classiq/applications/qnn/gradients/quantum_gradient.py +2 -2
  19. classiq/applications/qnn/types.py +2 -2
  20. classiq/applications/qsvm/qsvm.py +4 -7
  21. classiq/applications/qsvm/qsvm_data_generation.py +2 -5
  22. classiq/applications_model_constructors/__init__.py +9 -1
  23. classiq/applications_model_constructors/chemistry_model_constructor.py +9 -16
  24. classiq/applications_model_constructors/combinatorial_helpers/__init__.py +0 -0
  25. classiq/applications_model_constructors/combinatorial_helpers/allowed_constraints.py +20 -0
  26. classiq/applications_model_constructors/combinatorial_helpers/arithmetic/__init__.py +0 -0
  27. classiq/applications_model_constructors/combinatorial_helpers/arithmetic/arithmetic_expression.py +35 -0
  28. classiq/applications_model_constructors/combinatorial_helpers/arithmetic/isolation.py +42 -0
  29. classiq/applications_model_constructors/combinatorial_helpers/combinatorial_problem_utils.py +130 -0
  30. classiq/applications_model_constructors/combinatorial_helpers/encoding_mapping.py +107 -0
  31. classiq/applications_model_constructors/combinatorial_helpers/encoding_utils.py +122 -0
  32. classiq/applications_model_constructors/combinatorial_helpers/memory.py +79 -0
  33. classiq/applications_model_constructors/combinatorial_helpers/multiple_comp_basis_sp.py +34 -0
  34. classiq/applications_model_constructors/combinatorial_helpers/optimization_model.py +166 -0
  35. classiq/applications_model_constructors/combinatorial_helpers/pauli_helpers/__init__.py +0 -0
  36. classiq/applications_model_constructors/combinatorial_helpers/pauli_helpers/pauli_sparsing.py +31 -0
  37. classiq/applications_model_constructors/combinatorial_helpers/pauli_helpers/pauli_utils.py +65 -0
  38. classiq/applications_model_constructors/combinatorial_helpers/py.typed +0 -0
  39. classiq/applications_model_constructors/combinatorial_helpers/pyomo_utils.py +243 -0
  40. classiq/applications_model_constructors/combinatorial_helpers/sympy_utils.py +22 -0
  41. classiq/applications_model_constructors/combinatorial_helpers/transformations/__init__.py +0 -0
  42. classiq/applications_model_constructors/combinatorial_helpers/transformations/encoding.py +194 -0
  43. classiq/applications_model_constructors/combinatorial_helpers/transformations/fixed_variables.py +144 -0
  44. classiq/applications_model_constructors/combinatorial_helpers/transformations/ising_converter.py +124 -0
  45. classiq/applications_model_constructors/combinatorial_helpers/transformations/penalty.py +32 -0
  46. classiq/applications_model_constructors/combinatorial_helpers/transformations/penalty_support.py +41 -0
  47. classiq/applications_model_constructors/combinatorial_helpers/transformations/sign_seperation.py +75 -0
  48. classiq/applications_model_constructors/combinatorial_helpers/transformations/slack_variables.py +90 -0
  49. classiq/applications_model_constructors/combinatorial_optimization_model_constructor.py +48 -91
  50. classiq/applications_model_constructors/finance_model_constructor.py +4 -17
  51. classiq/applications_model_constructors/grover_model_constructor.py +20 -91
  52. classiq/applications_model_constructors/libraries/qmci_library.py +17 -19
  53. classiq/builtin_functions/standard_gates.py +1 -1
  54. classiq/exceptions.py +43 -1
  55. classiq/executor.py +10 -9
  56. classiq/interface/_version.py +1 -1
  57. classiq/interface/analyzer/analysis_params.py +6 -3
  58. classiq/interface/analyzer/result.py +12 -4
  59. classiq/interface/applications/qsvm.py +13 -1
  60. classiq/interface/backend/backend_preferences.py +4 -2
  61. classiq/interface/backend/pydantic_backend.py +3 -1
  62. classiq/interface/backend/quantum_backend_providers.py +1 -0
  63. classiq/interface/chemistry/fermionic_operator.py +15 -13
  64. classiq/interface/chemistry/ground_state_problem.py +18 -3
  65. classiq/interface/chemistry/molecule.py +8 -6
  66. classiq/interface/chemistry/operator.py +20 -14
  67. classiq/interface/combinatorial_optimization/examples/ascending_sequence.py +1 -1
  68. classiq/interface/combinatorial_optimization/examples/greater_than_ilp.py +1 -1
  69. classiq/interface/combinatorial_optimization/examples/ilp.py +2 -1
  70. classiq/interface/combinatorial_optimization/examples/integer_portfolio_optimization.py +2 -2
  71. classiq/interface/combinatorial_optimization/examples/mds.py +2 -1
  72. classiq/interface/combinatorial_optimization/examples/mht.py +3 -3
  73. classiq/interface/combinatorial_optimization/examples/mis.py +4 -1
  74. classiq/interface/combinatorial_optimization/examples/mvc.py +2 -1
  75. classiq/interface/combinatorial_optimization/examples/set_cover.py +2 -1
  76. classiq/interface/combinatorial_optimization/examples/tsp.py +4 -3
  77. classiq/interface/combinatorial_optimization/examples/tsp_digraph.py +6 -2
  78. classiq/interface/combinatorial_optimization/mht_qaoa_input.py +9 -3
  79. classiq/interface/executor/aws_execution_cost.py +4 -3
  80. classiq/interface/executor/estimation.py +2 -2
  81. classiq/interface/executor/execution_preferences.py +5 -34
  82. classiq/interface/executor/execution_request.py +19 -17
  83. classiq/interface/executor/optimizer_preferences.py +22 -13
  84. classiq/interface/executor/{quantum_program.py → quantum_code.py} +21 -15
  85. classiq/interface/executor/quantum_instruction_set.py +2 -1
  86. classiq/interface/executor/register_initialization.py +1 -3
  87. classiq/interface/executor/result.py +41 -10
  88. classiq/interface/executor/vqe_result.py +1 -1
  89. classiq/interface/finance/function_input.py +17 -4
  90. classiq/interface/finance/gaussian_model_input.py +3 -1
  91. classiq/interface/finance/log_normal_model_input.py +3 -1
  92. classiq/interface/finance/model_input.py +2 -0
  93. classiq/interface/generator/amplitude_loading.py +6 -3
  94. classiq/interface/generator/application_apis/__init__.py +1 -0
  95. classiq/interface/generator/application_apis/arithmetic_declarations.py +14 -0
  96. classiq/interface/generator/arith/argument_utils.py +14 -4
  97. classiq/interface/generator/arith/arithmetic.py +3 -1
  98. classiq/interface/generator/arith/arithmetic_arg_type_validator.py +12 -13
  99. classiq/interface/generator/arith/arithmetic_expression_abc.py +4 -1
  100. classiq/interface/generator/arith/arithmetic_expression_parser.py +8 -2
  101. classiq/interface/generator/arith/arithmetic_expression_validator.py +16 -2
  102. classiq/interface/generator/arith/arithmetic_operations.py +5 -10
  103. classiq/interface/generator/arith/ast_node_rewrite.py +1 -1
  104. classiq/interface/generator/arith/binary_ops.py +202 -54
  105. classiq/interface/generator/arith/extremum_operations.py +5 -3
  106. classiq/interface/generator/arith/logical_ops.py +4 -2
  107. classiq/interface/generator/arith/machine_precision.py +3 -0
  108. classiq/interface/generator/arith/number_utils.py +34 -44
  109. classiq/interface/generator/arith/register_user_input.py +21 -1
  110. classiq/interface/generator/arith/unary_ops.py +16 -25
  111. classiq/interface/generator/chemistry_function_params.py +4 -4
  112. classiq/interface/generator/commuting_pauli_exponentiation.py +3 -1
  113. classiq/interface/generator/compiler_keywords.py +4 -0
  114. classiq/interface/generator/complex_type.py +3 -10
  115. classiq/interface/generator/control_state.py +5 -3
  116. classiq/interface/generator/credit_risk_example/linear_gci.py +10 -3
  117. classiq/interface/generator/credit_risk_example/weighted_adder.py +14 -4
  118. classiq/interface/generator/expressions/atomic_expression_functions.py +5 -3
  119. classiq/interface/generator/expressions/evaluated_expression.py +18 -4
  120. classiq/interface/generator/expressions/expression.py +1 -1
  121. classiq/interface/generator/expressions/qmod_qscalar_proxy.py +33 -0
  122. classiq/interface/generator/expressions/sympy_supported_expressions.py +2 -1
  123. classiq/interface/generator/finance.py +1 -1
  124. classiq/interface/generator/function_params.py +7 -6
  125. classiq/interface/generator/functions/__init__.py +1 -1
  126. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/std_lib_functions.py +505 -138
  127. classiq/interface/generator/functions/core_lib_declarations/quantum_operators.py +25 -99
  128. classiq/interface/generator/functions/foreign_function_definition.py +12 -4
  129. classiq/interface/generator/functions/function_implementation.py +8 -4
  130. classiq/interface/generator/functions/native_function_definition.py +4 -2
  131. classiq/interface/generator/functions/register.py +4 -2
  132. classiq/interface/generator/functions/register_mapping_data.py +14 -10
  133. classiq/interface/generator/generated_circuit_data.py +2 -2
  134. classiq/interface/generator/grover_operator.py +5 -3
  135. classiq/interface/generator/hamiltonian_evolution/suzuki_trotter.py +5 -1
  136. classiq/interface/generator/hardware/hardware_data.py +6 -4
  137. classiq/interface/generator/hardware_efficient_ansatz.py +25 -8
  138. classiq/interface/generator/hartree_fock.py +3 -1
  139. classiq/interface/generator/linear_pauli_rotations.py +3 -1
  140. classiq/interface/generator/mcu.py +5 -3
  141. classiq/interface/generator/mcx.py +7 -5
  142. classiq/interface/generator/model/constraints.py +2 -1
  143. classiq/interface/generator/model/model.py +11 -19
  144. classiq/interface/generator/model/preferences/preferences.py +4 -3
  145. classiq/interface/generator/oracles/custom_oracle.py +4 -2
  146. classiq/interface/generator/oracles/oracle_abc.py +2 -2
  147. classiq/interface/generator/qpe.py +6 -4
  148. classiq/interface/generator/qsvm.py +5 -8
  149. classiq/interface/generator/quantum_function_call.py +21 -16
  150. classiq/interface/generator/{generated_circuit.py → quantum_program.py} +10 -14
  151. classiq/interface/generator/range_types.py +3 -1
  152. classiq/interface/generator/slice_parsing_utils.py +8 -3
  153. classiq/interface/generator/standard_gates/controlled_standard_gates.py +4 -2
  154. classiq/interface/generator/state_preparation/metrics.py +2 -1
  155. classiq/interface/generator/state_preparation/state_preparation.py +7 -5
  156. classiq/interface/generator/state_propagator.py +16 -5
  157. classiq/interface/generator/types/builtin_struct_declarations/__init__.py +0 -1
  158. classiq/interface/generator/types/struct_declaration.py +8 -3
  159. classiq/interface/generator/ucc.py +6 -4
  160. classiq/interface/generator/unitary_gate.py +7 -3
  161. classiq/interface/generator/validations/flow_graph.py +6 -4
  162. classiq/interface/generator/validations/validator_functions.py +6 -4
  163. classiq/interface/hardware.py +2 -2
  164. classiq/interface/helpers/custom_encoders.py +3 -0
  165. classiq/interface/helpers/pydantic_model_helpers.py +0 -6
  166. classiq/interface/helpers/validation_helpers.py +1 -1
  167. classiq/interface/helpers/versioned_model.py +4 -1
  168. classiq/interface/ide/show.py +2 -2
  169. classiq/interface/jobs.py +72 -3
  170. classiq/interface/model/bind_operation.py +18 -11
  171. classiq/interface/model/call_synthesis_data.py +68 -0
  172. classiq/interface/model/inplace_binary_operation.py +2 -2
  173. classiq/interface/model/model.py +27 -21
  174. classiq/interface/model/native_function_definition.py +3 -5
  175. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +9 -4
  176. classiq/interface/model/quantum_expressions/control_state.py +2 -2
  177. classiq/interface/model/quantum_function_call.py +25 -139
  178. classiq/interface/model/quantum_function_declaration.py +8 -0
  179. classiq/interface/model/quantum_if_operation.py +2 -3
  180. classiq/interface/model/quantum_lambda_function.py +64 -0
  181. classiq/interface/model/quantum_type.py +57 -56
  182. classiq/interface/model/quantum_variable_declaration.py +1 -1
  183. classiq/interface/model/statement_block.py +32 -0
  184. classiq/interface/model/validations/handles_validator.py +14 -12
  185. classiq/interface/model/within_apply_operation.py +11 -0
  186. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +4 -1
  187. classiq/interface/server/routes.py +5 -0
  188. classiq/model/function_handler.py +5 -9
  189. classiq/model/model.py +2 -19
  190. classiq/qmod/__init__.py +13 -6
  191. classiq/qmod/builtins/classical_execution_primitives.py +27 -36
  192. classiq/qmod/builtins/classical_functions.py +24 -14
  193. classiq/qmod/builtins/functions.py +162 -145
  194. classiq/qmod/builtins/operations.py +24 -35
  195. classiq/qmod/builtins/structs.py +15 -15
  196. classiq/qmod/cfunc.py +42 -0
  197. classiq/qmod/classical_function.py +6 -14
  198. classiq/qmod/declaration_inferrer.py +12 -21
  199. classiq/qmod/expression_query.py +23 -0
  200. classiq/qmod/model_state_container.py +2 -0
  201. classiq/qmod/native/__init__.py +0 -0
  202. classiq/qmod/native/expression_to_qmod.py +189 -0
  203. classiq/qmod/native/pretty_printer.py +311 -0
  204. classiq/qmod/qfunc.py +27 -0
  205. classiq/qmod/qmod_constant.py +76 -0
  206. classiq/qmod/qmod_parameter.py +34 -12
  207. classiq/qmod/qmod_struct.py +3 -3
  208. classiq/qmod/qmod_variable.py +102 -18
  209. classiq/qmod/quantum_expandable.py +16 -16
  210. classiq/qmod/quantum_function.py +37 -8
  211. classiq/qmod/symbolic.py +47 -4
  212. classiq/qmod/symbolic_expr.py +9 -0
  213. classiq/qmod/utilities.py +13 -0
  214. classiq/qmod/write_qmod.py +39 -0
  215. classiq/quantum_functions/__init__.py +2 -2
  216. classiq/quantum_functions/annotation_parser.py +9 -11
  217. classiq/quantum_functions/function_parser.py +1 -1
  218. classiq/quantum_functions/quantum_function.py +3 -3
  219. classiq/quantum_register.py +17 -9
  220. {classiq-0.37.0.dist-info → classiq-0.38.0.dist-info}/METADATA +2 -1
  221. {classiq-0.37.0.dist-info → classiq-0.38.0.dist-info}/RECORD +222 -186
  222. {classiq-0.37.0.dist-info → classiq-0.38.0.dist-info}/WHEEL +1 -1
  223. classiq/interface/generator/expressions/qmod_qnum_proxy.py +0 -22
  224. classiq/interface/generator/types/builtin_struct_declarations/qaoa_declarations.py +0 -23
  225. classiq/interface/generator/types/combinatorial_problem.py +0 -26
  226. classiq/interface/model/numeric_reinterpretation.py +0 -25
  227. classiq/interface/model/operator_synthesis_data.py +0 -48
  228. classiq/model/function_handler.pyi +0 -152
@@ -1,5 +1,6 @@
1
1
  import ast
2
2
  import builtins
3
+ import re
3
4
  from _ast import AST
4
5
  from typing import Any, Optional, Set, Tuple, Type, Union
5
6
 
@@ -15,6 +16,7 @@ from classiq.exceptions import ClassiqArithmeticError, ClassiqValueError
15
16
  DEFAULT_SUPPORTED_FUNC_NAMES: Set[str] = {"CLShift", "CRShift", "min", "max"}
16
17
 
17
18
  DEFAULT_EXPRESSION_TYPE = "arithmetic"
19
+ IDENITIFIER_REGEX = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*")
18
20
 
19
21
  _REPEATED_VARIABLES_ERROR_MESSAGE: str = (
20
22
  "Repeated variables in the beginning of an arithmetic expression are not allowed."
@@ -75,17 +77,25 @@ class ExpressionValidator(ast.NodeVisitor):
75
77
 
76
78
  def validate(self, expression: str) -> None:
77
79
  try:
78
- ast_expr = ast.parse(expression, filename="", mode=self._mode)
80
+ adjusted_expression = self._get_adjusted_expression(expression)
81
+ ast_expr = ast.parse(adjusted_expression, filename="", mode=self._mode)
79
82
  except SyntaxError as e:
80
83
  raise ClassiqValueError(f"Failed to parse expression {expression!r}") from e
81
84
  try:
82
- self._ast_obj = AstNodeRewrite().visit(ast_expr)
85
+ self._ast_obj = self.rewrite_ast(ast_expr)
83
86
  self.visit(self._ast_obj)
84
87
  except RecursionError as e:
85
88
  raise ClassiqValueError(
86
89
  f"Failed to parse expression since it is too long: {expression}"
87
90
  ) from e
88
91
 
92
+ @staticmethod
93
+ def _get_adjusted_expression(expression: str) -> str:
94
+ # This works around the simplification of the trivial expressions such as a + 0, 1 * a, etc.
95
+ if IDENITIFIER_REGEX.fullmatch(expression):
96
+ return f"0 + {expression}"
97
+ return expression
98
+
89
99
  @property
90
100
  def ast_obj(self) -> ast.AST:
91
101
  if not self._ast_obj:
@@ -182,6 +192,10 @@ class ExpressionValidator(ast.NodeVisitor):
182
192
  self._supported_functions.add(node.name)
183
193
  self.generic_visit(node)
184
194
 
195
+ @classmethod
196
+ def rewrite_ast(cls, expression_ast: AST) -> AST:
197
+ return expression_ast
198
+
185
199
 
186
200
  def validate_expression(
187
201
  expression: str,
@@ -1,11 +1,11 @@
1
- from __future__ import annotations
2
-
3
1
  import abc
4
2
  from typing import ClassVar, Iterable, Optional, Tuple
5
3
 
6
4
  import pydantic
7
5
 
8
- from classiq.interface.generator.arith import argument_utils, number_utils
6
+ from classiq.interface.generator.arith.machine_precision import (
7
+ DEFAULT_MACHINE_PRECISION,
8
+ )
9
9
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
10
10
  from classiq.interface.generator.function_params import FunctionParams
11
11
 
@@ -14,7 +14,7 @@ DEFAULT_GARBAGE_OUT_NAME: str = "extra_qubits"
14
14
 
15
15
  class ArithmeticOperationParams(FunctionParams):
16
16
  output_size: Optional[pydantic.PositiveInt]
17
- machine_precision: pydantic.PositiveInt = number_utils.MAX_FRACTION_PLACES
17
+ machine_precision: pydantic.PositiveInt = DEFAULT_MACHINE_PRECISION
18
18
  output_name: ClassVar[str]
19
19
  garbage_output_name: ClassVar[str] = DEFAULT_GARBAGE_OUT_NAME
20
20
  _result_register: Optional[RegisterArithmeticInfo] = pydantic.PrivateAttr(
@@ -46,11 +46,6 @@ class ArithmeticOperationParams(FunctionParams):
46
46
  return suggested_bounds
47
47
  return None
48
48
 
49
- def _compute_fraction_places(self, argument: argument_utils.RegisterOrConst) -> int:
50
- return argument_utils.fraction_places(
51
- argument, machine_precision=self.machine_precision
52
- )
53
-
54
49
  @abc.abstractmethod
55
- def get_params_inplace_options(self) -> Iterable[ArithmeticOperationParams]:
50
+ def get_params_inplace_options(self) -> Iterable["ArithmeticOperationParams"]:
56
51
  pass
@@ -30,7 +30,7 @@ class AstNodeRewrite(ast.NodeTransformer):
30
30
  elif hasattr(node, "op"):
31
31
  return type(node.op).__name__ + next(self.count_str_gen)
32
32
  elif hasattr(node, "func"):
33
- return node.func.id
33
+ return self.extract_node_id(node.func)
34
34
  elif hasattr(node, "value"):
35
35
  return node.value
36
36
  elif hasattr(node, "ops"):
@@ -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