classiq 0.85.0__py3-none-any.whl → 0.86.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -3
  2. classiq/evaluators/qmod_annotated_expression.py +207 -0
  3. classiq/evaluators/qmod_expression_visitors/__init__.py +0 -0
  4. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +134 -0
  5. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +232 -0
  6. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +44 -0
  7. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +308 -0
  8. classiq/evaluators/qmod_node_evaluators/__init__.py +0 -0
  9. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +112 -0
  10. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +132 -0
  11. classiq/evaluators/qmod_node_evaluators/bool_op_evaluation.py +70 -0
  12. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +311 -0
  13. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +107 -0
  14. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +67 -0
  15. classiq/evaluators/qmod_node_evaluators/list_evaluation.py +107 -0
  16. classiq/evaluators/qmod_node_evaluators/measurement_evaluation.py +25 -0
  17. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +50 -0
  18. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +66 -0
  19. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +225 -0
  20. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +58 -0
  21. classiq/evaluators/qmod_node_evaluators/utils.py +80 -0
  22. classiq/execution/execution_session.py +4 -0
  23. classiq/interface/_version.py +1 -1
  24. classiq/interface/analyzer/analysis_params.py +1 -1
  25. classiq/interface/analyzer/result.py +1 -1
  26. classiq/interface/executor/quantum_code.py +2 -2
  27. classiq/interface/generator/arith/arithmetic_expression_validator.py +5 -1
  28. classiq/interface/generator/arith/binary_ops.py +43 -51
  29. classiq/interface/generator/arith/number_utils.py +3 -2
  30. classiq/interface/generator/arith/register_user_input.py +15 -0
  31. classiq/interface/generator/arith/unary_ops.py +32 -28
  32. classiq/interface/generator/expressions/expression_types.py +2 -2
  33. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  34. classiq/interface/generator/functions/classical_function_declaration.py +0 -4
  35. classiq/interface/generator/functions/classical_type.py +0 -32
  36. classiq/interface/generator/generated_circuit_data.py +2 -0
  37. classiq/interface/generator/quantum_program.py +6 -1
  38. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +29 -0
  39. classiq/interface/ide/operation_registry.py +2 -2
  40. classiq/interface/ide/visual_model.py +22 -1
  41. classiq/interface/model/quantum_type.py +67 -33
  42. classiq/model_expansions/arithmetic.py +115 -0
  43. classiq/model_expansions/arithmetic_compute_result_attrs.py +71 -0
  44. classiq/model_expansions/atomic_expression_functions_defs.py +5 -5
  45. classiq/model_expansions/generative_functions.py +15 -2
  46. classiq/model_expansions/interpreters/base_interpreter.py +7 -0
  47. classiq/model_expansions/interpreters/generative_interpreter.py +2 -0
  48. classiq/model_expansions/quantum_operations/call_emitter.py +5 -2
  49. classiq/model_expansions/scope_initialization.py +2 -0
  50. classiq/model_expansions/sympy_conversion/sympy_to_python.py +1 -1
  51. classiq/model_expansions/transformers/type_modifier_inference.py +5 -0
  52. classiq/model_expansions/visitors/boolean_expression_transformers.py +1 -1
  53. classiq/qmod/builtins/operations.py +1 -1
  54. classiq/qmod/declaration_inferrer.py +5 -3
  55. classiq/qmod/write_qmod.py +3 -1
  56. {classiq-0.85.0.dist-info → classiq-0.86.0.dist-info}/METADATA +1 -1
  57. {classiq-0.85.0.dist-info → classiq-0.86.0.dist-info}/RECORD +59 -37
  58. /classiq/{model_expansions/sympy_conversion/arithmetics.py → evaluators/qmod_expression_visitors/sympy_wrappers.py} +0 -0
  59. {classiq-0.85.0.dist-info → classiq-0.86.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,66 @@
1
+ import ast
2
+ from typing import cast
3
+
4
+ from classiq.interface.exceptions import (
5
+ ClassiqExpansionError,
6
+ )
7
+ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
8
+ QmodStructInstance,
9
+ )
10
+ from classiq.interface.generator.functions.type_name import Struct
11
+ from classiq.interface.generator.types.struct_declaration import StructDeclaration
12
+ from classiq.interface.helpers.text_utils import readable_list, s
13
+
14
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
15
+ from classiq.evaluators.qmod_node_evaluators.compare_evaluation import (
16
+ comparison_allowed,
17
+ )
18
+ from classiq.evaluators.qmod_node_evaluators.utils import get_qmod_type_name
19
+
20
+
21
+ def eval_struct_instantiation(
22
+ expr_val: QmodAnnotatedExpression, node: ast.Call, decl: StructDeclaration
23
+ ) -> None:
24
+ if len(node.args) > 0 or any(kwarg.arg is None for kwarg in node.keywords):
25
+ raise ClassiqExpansionError(
26
+ "Classical structs must be instantiated using keyword arguments"
27
+ )
28
+
29
+ field_assignments = {cast(str, kwarg.arg): kwarg.value for kwarg in node.keywords}
30
+ assigned_fields = list(field_assignments)
31
+ expected_fields = list(decl.variables)
32
+ if set(assigned_fields) != set(expected_fields):
33
+ raise ClassiqExpansionError(
34
+ f"Struct {decl.name} has field{s(expected_fields)} "
35
+ f"{readable_list(expected_fields, quote=True)} but was instantiated "
36
+ f"using the field{s(assigned_fields)} "
37
+ f"{readable_list(assigned_fields, quote=True)}"
38
+ )
39
+
40
+ for field_name, field_value in field_assignments.items():
41
+ assignment_type = expr_val.get_type(field_value)
42
+ expected_type = decl.variables[field_name]
43
+ if not comparison_allowed(assignment_type, expected_type):
44
+ raise ClassiqExpansionError(
45
+ f"Cannot assign value of type {get_qmod_type_name(assignment_type)} "
46
+ f"to field {field_name!r} of type {get_qmod_type_name(expected_type)}"
47
+ )
48
+
49
+ classical_type = Struct(name=decl.name)
50
+ classical_type.set_classical_struct_decl(decl)
51
+ expr_val.set_type(node, classical_type)
52
+
53
+ if any(
54
+ not expr_val.has_value(field_value)
55
+ for field_value in field_assignments.values()
56
+ ):
57
+ return
58
+
59
+ struct_val = QmodStructInstance(
60
+ decl,
61
+ {
62
+ field_name: expr_val.get_value(field_value)
63
+ for field_name, field_value in field_assignments.items()
64
+ },
65
+ )
66
+ expr_val.set_value(node, struct_val)
@@ -0,0 +1,225 @@
1
+ import ast
2
+ from typing import Optional, cast
3
+
4
+ from classiq.interface.exceptions import (
5
+ ClassiqExpansionError,
6
+ ClassiqInternalExpansionError,
7
+ )
8
+ from classiq.interface.generator.arith.arithmetic import (
9
+ aggregate_numeric_types,
10
+ compute_arithmetic_result_type,
11
+ )
12
+ from classiq.interface.generator.expressions.expression import Expression
13
+ from classiq.interface.generator.functions.classical_type import (
14
+ ClassicalArray,
15
+ ClassicalTuple,
16
+ Integer,
17
+ Real,
18
+ )
19
+ from classiq.interface.model.handle_binding import (
20
+ SlicedHandleBinding,
21
+ SubscriptHandleBinding,
22
+ )
23
+ from classiq.interface.model.quantum_type import (
24
+ QuantumBitvector,
25
+ QuantumNumeric,
26
+ QuantumType,
27
+ register_info_to_quantum_type,
28
+ )
29
+
30
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
31
+ from classiq.evaluators.qmod_node_evaluators.utils import (
32
+ QmodType,
33
+ element_types,
34
+ get_qmod_type_name,
35
+ qnum_is_qint,
36
+ )
37
+
38
+
39
+ def _eval_slice(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
40
+ subject = node.value
41
+ slice_ = cast(ast.Slice, node.slice)
42
+ start = cast(ast.AST, slice_.lower)
43
+ stop = cast(ast.AST, slice_.upper)
44
+ if slice_.step is not None:
45
+ raise ClassiqExpansionError("Slice step is not supported")
46
+
47
+ start_type = expr_val.get_type(start)
48
+ stop_type = expr_val.get_type(stop)
49
+ for index_type in (start_type, stop_type):
50
+ if not isinstance(index_type, Integer):
51
+ raise ClassiqExpansionError(
52
+ f"Slice indices must be integers, not {get_qmod_type_name(index_type)!r}"
53
+ )
54
+
55
+ start_val: Optional[int] = None
56
+ if expr_val.has_value(start):
57
+ start_val = cast(int, expr_val.get_value(start))
58
+ if start_val < 0:
59
+ raise ClassiqExpansionError("Slice indices must be positive integers")
60
+ stop_val: Optional[int] = None
61
+ if expr_val.has_value(stop):
62
+ stop_val = cast(int, expr_val.get_value(stop))
63
+ if start_val is not None and stop_val < start_val:
64
+ raise ClassiqExpansionError(
65
+ "Slice upper bound must be greater or equal to the lower bound"
66
+ )
67
+
68
+ subject_type = expr_val.get_type(subject)
69
+ slice_type: QmodType
70
+ if isinstance(subject_type, ClassicalArray):
71
+ if subject_type.has_length and (
72
+ (start_val is not None and start_val >= subject_type.length_value)
73
+ or (stop_val is not None and stop_val > subject_type.length_value)
74
+ ):
75
+ raise ClassiqExpansionError("Array index out of range")
76
+ length_expr: Optional[Expression] = None
77
+ if start_val is not None and stop_val is not None:
78
+ length_expr = Expression(expr=str(stop_val - start_val))
79
+ slice_type = ClassicalArray(
80
+ element_type=subject_type.element_type, length=length_expr
81
+ )
82
+ elif isinstance(subject_type, ClassicalTuple):
83
+ if start_val is not None and stop_val is not None:
84
+ if start_val >= len(subject_type.element_types) or stop_val > len(
85
+ subject_type.element_types
86
+ ):
87
+ raise ClassiqExpansionError("Array index out of range")
88
+ slice_type = ClassicalTuple(
89
+ element_types=subject_type.element_types[start_val:stop_val]
90
+ )
91
+ else:
92
+ slice_type = subject_type.get_raw_type()
93
+ elif isinstance(subject_type, QuantumBitvector):
94
+ if start_val is not None and stop_val is not None:
95
+ if subject_type.has_length and (
96
+ start_val >= subject_type.length_value
97
+ or stop_val > subject_type.length_value
98
+ ):
99
+ raise ClassiqExpansionError("Array index out of range")
100
+ slice_length = Expression(expr=str(stop_val - start_val))
101
+ else:
102
+ slice_length = None
103
+ slice_type = QuantumBitvector(
104
+ element_type=subject_type.element_type, length=slice_length
105
+ )
106
+ else:
107
+ raise ClassiqExpansionError(
108
+ f"{get_qmod_type_name(subject_type)} is not subscriptable"
109
+ )
110
+ expr_val.set_type(node, slice_type)
111
+
112
+ if start_val is None or stop_val is None:
113
+ return
114
+ if expr_val.has_value(subject):
115
+ subject_val = expr_val.get_value(subject)
116
+ expr_val.set_value(node, subject_val[start_val:stop_val])
117
+ elif expr_val.has_var(subject):
118
+ subject_var = expr_val.get_var(subject)
119
+ expr_val.set_var(
120
+ node,
121
+ SlicedHandleBinding(
122
+ base_handle=subject_var,
123
+ start=Expression(expr=str(start_val)),
124
+ end=Expression(expr=str(stop_val)),
125
+ ),
126
+ )
127
+ expr_val.remove_var(subject)
128
+
129
+
130
+ def _eval_subscript(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
131
+ subject = node.value
132
+ subscript = node.slice
133
+
134
+ index_type = expr_val.get_type(subscript)
135
+ if not isinstance(index_type, Integer):
136
+ raise ClassiqExpansionError(
137
+ f"Array indices must be integers or slices, not {get_qmod_type_name(index_type)}"
138
+ )
139
+
140
+ sub_val: Optional[int] = None
141
+ if expr_val.has_value(subscript):
142
+ sub_val = cast(int, expr_val.get_value(subscript))
143
+ if sub_val < 0:
144
+ raise ClassiqExpansionError("Array indices must be positive integers")
145
+
146
+ subject_type = expr_val.get_type(subject)
147
+ sub_type: QmodType
148
+ if isinstance(subject_type, (ClassicalArray, QuantumBitvector)):
149
+ if (
150
+ sub_val is not None
151
+ and subject_type.has_length
152
+ and sub_val >= subject_type.length_value
153
+ ):
154
+ raise ClassiqExpansionError("Array index out of range")
155
+ sub_type = subject_type.element_type
156
+ elif isinstance(subject_type, ClassicalTuple):
157
+ if sub_val is not None:
158
+ if sub_val >= len(subject_type.element_types):
159
+ raise ClassiqExpansionError("Array index out of range")
160
+ sub_type = subject_type.element_types[sub_val]
161
+ else:
162
+ raw_subject_type = subject_type.get_raw_type()
163
+ if not isinstance(raw_subject_type, ClassicalArray):
164
+ raise ClassiqInternalExpansionError
165
+ sub_type = raw_subject_type.element_type
166
+ else:
167
+ raise ClassiqExpansionError(
168
+ f"{get_qmod_type_name(subject_type)} is not subscriptable"
169
+ )
170
+ expr_val.set_type(node, sub_type)
171
+
172
+ if sub_val is None:
173
+ return
174
+ if expr_val.has_value(subject):
175
+ subject_val = expr_val.get_value(subject)
176
+ expr_val.set_value(node, subject_val[sub_val])
177
+ elif expr_val.has_var(subject):
178
+ subject_var = expr_val.get_var(subject)
179
+ expr_val.set_var(
180
+ node,
181
+ SubscriptHandleBinding(
182
+ base_handle=subject_var, index=Expression(expr=str(sub_val))
183
+ ),
184
+ )
185
+ expr_val.remove_var(subject)
186
+
187
+
188
+ def eval_subscript(expr_val: QmodAnnotatedExpression, node: ast.Subscript) -> None:
189
+ if isinstance(node.slice, ast.Slice):
190
+ _eval_slice(expr_val, node)
191
+ else:
192
+ _eval_subscript(expr_val, node)
193
+
194
+
195
+ def eval_quantum_subscript(
196
+ expr_val: QmodAnnotatedExpression, node: ast.Subscript, machine_precision: int
197
+ ) -> None:
198
+ subject = node.value
199
+ subscript = node.slice
200
+
201
+ index_type = cast(QuantumType, expr_val.get_type(subscript))
202
+ if not qnum_is_qint(index_type):
203
+ raise ClassiqExpansionError("Quantum indices must be unsigned quantum numerics")
204
+ subject_type = expr_val.get_type(subject)
205
+ if not isinstance(subject_type, (ClassicalArray, ClassicalTuple)) or not all(
206
+ isinstance(element_type, (Integer, Real))
207
+ for element_type in element_types(subject_type)
208
+ ):
209
+ raise ClassiqExpansionError(
210
+ "Only classical numeric arrays may have quantum subscripts"
211
+ )
212
+
213
+ expr_val.set_quantum_subscript(node, subject, subscript)
214
+ if not expr_val.has_value(subject):
215
+ expr_val.set_type(node, QuantumNumeric())
216
+ return
217
+
218
+ items = expr_val.get_value(subject)
219
+ numeric_types = [
220
+ compute_arithmetic_result_type(str(num), {}, machine_precision) for num in items
221
+ ]
222
+ unified_numeric_type = register_info_to_quantum_type(
223
+ aggregate_numeric_types(numeric_types)
224
+ )
225
+ expr_val.set_type(node, unified_numeric_type)
@@ -0,0 +1,58 @@
1
+ import ast
2
+ from typing import Any
3
+
4
+ from classiq.interface.exceptions import (
5
+ ClassiqExpansionError,
6
+ ClassiqInternalExpansionError,
7
+ )
8
+ from classiq.interface.generator.functions.classical_type import Integer, Real
9
+ from classiq.interface.generator.functions.type_name import TypeName
10
+
11
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
12
+ from classiq.evaluators.qmod_node_evaluators.bool_op_evaluation import is_bool_type
13
+ from classiq.evaluators.qmod_node_evaluators.utils import QmodType, is_numeric_type
14
+
15
+
16
+ def unary_op_allowed(op: ast.AST, operand_type: QmodType) -> bool:
17
+ if isinstance(op, ast.Not):
18
+ return is_bool_type(operand_type)
19
+ return is_numeric_type(operand_type)
20
+
21
+
22
+ def eval_unary_op(expr_val: QmodAnnotatedExpression, node: ast.UnaryOp) -> None:
23
+ operand = node.operand
24
+ op = node.op
25
+
26
+ operand_type = expr_val.get_type(operand)
27
+ if not unary_op_allowed(op, operand_type):
28
+ expected_val_type = "Boolean" if isinstance(op, ast.Not) else "scalar"
29
+ raise ClassiqExpansionError(
30
+ f"The operand of the unary operator {type(op).__name__!r} must be "
31
+ f"a {expected_val_type} value"
32
+ )
33
+ if isinstance(op, ast.Invert) and isinstance(operand_type, Real):
34
+ raise ClassiqExpansionError(
35
+ f"Operation {type(op).__name__!r} on a real value is not supported"
36
+ )
37
+ op_type: QmodType
38
+ if isinstance(operand_type, TypeName):
39
+ op_type = Integer()
40
+ else:
41
+ op_type = operand_type
42
+ expr_val.set_type(node, op_type)
43
+
44
+ if not expr_val.has_value(operand):
45
+ return
46
+ operand_value = expr_val.get_value(operand)
47
+ constant_value: Any
48
+ if isinstance(op, ast.Not):
49
+ constant_value = not operand_value
50
+ elif isinstance(op, ast.Invert):
51
+ constant_value = ~operand_value
52
+ elif isinstance(op, ast.UAdd):
53
+ constant_value = +operand_value
54
+ elif isinstance(op, ast.USub):
55
+ constant_value = -operand_value
56
+ else:
57
+ raise ClassiqInternalExpansionError
58
+ expr_val.set_value(node, constant_value)
@@ -0,0 +1,80 @@
1
+ from typing import Any, Optional, Union, cast
2
+
3
+ import sympy
4
+
5
+ from classiq.interface.generator.functions.classical_type import (
6
+ ClassicalArray,
7
+ ClassicalTuple,
8
+ ClassicalType,
9
+ Integer,
10
+ Real,
11
+ )
12
+ from classiq.interface.generator.functions.type_name import TypeName
13
+ from classiq.interface.model.quantum_type import (
14
+ QuantumBit,
15
+ QuantumNumeric,
16
+ QuantumScalar,
17
+ QuantumType,
18
+ )
19
+
20
+ QmodType = Union[ClassicalType, QuantumType]
21
+ IntegerValueType = int
22
+ RealValueType = Union[float, complex]
23
+ NumberValueType = Union[IntegerValueType, RealValueType]
24
+ SYMPY_SYMBOLS = {sym: getattr(sympy, sym) for sym in sympy.__all__}
25
+
26
+
27
+ def get_qmod_type_name(type_: Any) -> str:
28
+ return getattr(type_, "type_name", type(type_).__name__)
29
+
30
+
31
+ def is_classical_type(qmod_type: QmodType) -> bool:
32
+ if isinstance(qmod_type, TypeName):
33
+ return qmod_type.has_classical_struct_decl or qmod_type.is_enum
34
+ return isinstance(qmod_type, ClassicalType)
35
+
36
+
37
+ def qnum_is_qbit(qmod_type: QuantumNumeric) -> bool:
38
+ return (
39
+ (not qmod_type.has_size_in_bits or qmod_type.size_in_bits == 1)
40
+ and (not qmod_type.has_sign or not qmod_type.sign_value)
41
+ and (not qmod_type.has_fraction_digits or qmod_type.fraction_digits_value == 0)
42
+ )
43
+
44
+
45
+ def qnum_is_qint(qmod_type: QuantumType) -> bool:
46
+ return isinstance(qmod_type, QuantumBit) or (
47
+ isinstance(qmod_type, QuantumNumeric)
48
+ and (not qmod_type.has_sign or not qmod_type.sign_value)
49
+ and (not qmod_type.has_fraction_digits or qmod_type.fraction_digits_value == 0)
50
+ )
51
+
52
+
53
+ def element_types(
54
+ classical_type: Union[ClassicalArray, ClassicalTuple],
55
+ ) -> list[ClassicalType]:
56
+ if isinstance(classical_type, ClassicalArray):
57
+ return [classical_type.element_type]
58
+ return cast(list[ClassicalType], classical_type.element_types)
59
+
60
+
61
+ def array_len(
62
+ classical_type: Union[ClassicalArray, ClassicalTuple],
63
+ ) -> Optional[int]:
64
+ if isinstance(classical_type, ClassicalTuple):
65
+ return len(classical_type.element_types)
66
+ if classical_type.has_length:
67
+ return classical_type.length_value
68
+ return None
69
+
70
+
71
+ def is_numeric_type(qmod_type: QmodType) -> bool:
72
+ return isinstance(qmod_type, (Integer, Real, QuantumScalar)) or (
73
+ isinstance(qmod_type, TypeName) and qmod_type.is_enum
74
+ )
75
+
76
+
77
+ def is_classical_integer(qmod_type: QmodType) -> bool:
78
+ return isinstance(qmod_type, Integer) or (
79
+ isinstance(qmod_type, TypeName) and qmod_type.is_enum
80
+ )
@@ -504,6 +504,10 @@ class ExecutionSession:
504
504
  raise NotImplementedError(
505
505
  "Filtering is only supported on a single value per model output"
506
506
  )
507
+ if len(legal_bitstrings) == 0:
508
+ raise ClassiqValueError(
509
+ f"The condition was false for every possible value of {output_name}"
510
+ )
507
511
  else:
508
512
  raise NotImplementedError(
509
513
  "Filtering is only supported on QuantumBit and QuantumNumeric"
@@ -3,5 +3,5 @@ from packaging.version import Version
3
3
  # This file was generated automatically
4
4
  # Please don't track in version control (DONTTRACK)
5
5
 
6
- SEMVER_VERSION = '0.85.0'
6
+ SEMVER_VERSION = '0.86.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -14,7 +14,7 @@ from classiq.interface.generator.model.preferences.preferences import (
14
14
  from classiq.interface.hardware import Provider
15
15
  from classiq.interface.helpers.custom_pydantic_types import PydanticNonEmptyString
16
16
 
17
- MAX_KB_OF_FILE = 500
17
+ MAX_KB_OF_FILE = 50000
18
18
  MAX_FILE_LENGTH = MAX_KB_OF_FILE * 1024
19
19
 
20
20
  MAX_QUBITS = 100
@@ -37,7 +37,7 @@ class DataID(pydantic.BaseModel):
37
37
 
38
38
 
39
39
  class QasmCode(pydantic.BaseModel):
40
- code: str = Field(..., max_length=MAX_FILE_LENGTH * 100)
40
+ code: str = Field(..., max_length=MAX_FILE_LENGTH)
41
41
 
42
42
 
43
43
  class AnalysisStatus(StrEnum):
@@ -95,8 +95,8 @@ class QuantumCode(QuantumBaseCode):
95
95
  ) -> Optional[ExecutionData]:
96
96
  if (
97
97
  synthesis_execution_data is not None
98
- and values.data.get("syntax") is not QuantumInstructionSet.QASM
99
- ):
98
+ and synthesis_execution_data.function_execution
99
+ ) and values.data.get("syntax") is not QuantumInstructionSet.QASM:
100
100
  raise ClassiqValueError("Only QASM supports the requested configuration")
101
101
 
102
102
  return synthesis_execution_data
@@ -63,6 +63,10 @@ def is_constant(expr: Union[str, Expr]) -> bool:
63
63
  return False
64
64
 
65
65
 
66
+ def is_variable(expr: str) -> bool:
67
+ return IDENITIFIER_REGEX.fullmatch(expr) is not None
68
+
69
+
66
70
  class ExpressionValidator(ast.NodeVisitor):
67
71
  def __init__(
68
72
  self,
@@ -95,7 +99,7 @@ class ExpressionValidator(ast.NodeVisitor):
95
99
  @staticmethod
96
100
  def _get_adjusted_expression(expression: str) -> str:
97
101
  # This works around the simplification of the trivial expressions such as a + 0, 1 * a, etc.
98
- if IDENITIFIER_REGEX.fullmatch(expression) or is_constant(expression):
102
+ if is_variable(expression) or is_constant(expression):
99
103
  return f"0 + {expression}"
100
104
  return expression
101
105
 
@@ -20,7 +20,6 @@ from classiq.interface.exceptions import ClassiqValueError
20
20
  from classiq.interface.generator.arith import argument_utils, number_utils
21
21
  from classiq.interface.generator.arith.argument_utils import (
22
22
  RegisterOrConst,
23
- as_arithmetic_info,
24
23
  )
25
24
  from classiq.interface.generator.arith.arithmetic_operations import (
26
25
  MODULO_WITH_FRACTION_PLACES_ERROR_MSG,
@@ -33,6 +32,13 @@ from classiq.interface.generator.arith.register_user_input import RegisterArithm
33
32
  from classiq.interface.generator.arith.unary_ops import Negation
34
33
  from classiq.interface.generator.function_params import get_zero_input_name
35
34
 
35
+ from classiq.model_expansions.arithmetic import NumericAttributes
36
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
37
+ compute_result_attrs_add,
38
+ compute_result_attrs_assign,
39
+ compute_result_attrs_subtract,
40
+ )
41
+
36
42
  LeftDataT = TypeVar("LeftDataT")
37
43
  RightDataT = TypeVar("RightDataT")
38
44
  _NumericArgumentInplaceErrorMessage: str = "Cannot inplace the numeric argument {}"
@@ -268,40 +274,35 @@ class Adder(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
268
274
  output_name = "sum"
269
275
 
270
276
  def _get_result_register(self) -> RegisterArithmeticInfo:
271
- left_arg = argument_utils.limit_fraction_places(
277
+ left_attrs = NumericAttributes.from_type_or_constant(
272
278
  self.left_arg, self.machine_precision
273
279
  )
274
- right_arg = argument_utils.limit_fraction_places(
280
+ right_attrs = NumericAttributes.from_type_or_constant(
275
281
  self.right_arg, self.machine_precision
276
282
  )
277
- lb = argument_utils.lower_bound(left_arg) + argument_utils.lower_bound(
278
- right_arg
279
- )
280
- ub = argument_utils.upper_bound(left_arg) + argument_utils.upper_bound(
281
- right_arg
282
- )
283
- fraction_places = max(
284
- argument_utils.fraction_places(left_arg),
285
- argument_utils.fraction_places(right_arg),
286
- )
283
+
284
+ if self._is_zero(self.left_arg) or self._is_zero(self.right_arg):
285
+ other_arg_attrs = (
286
+ right_attrs if self._is_zero(self.left_arg) else left_attrs
287
+ )
288
+ result_attrs = compute_result_attrs_assign(
289
+ other_arg_attrs, self.machine_precision
290
+ )
291
+ else:
292
+ result_attrs = compute_result_attrs_add(
293
+ left_attrs, right_attrs, self.machine_precision
294
+ )
295
+
287
296
  return RegisterArithmeticInfo(
288
- size=self.output_size or self._get_output_size(ub, lb, fraction_places),
289
- fraction_places=fraction_places,
290
- is_signed=self._include_sign and lb < 0,
291
- bounds=(lb, ub) if self._include_sign else None,
297
+ size=self.output_size or result_attrs.size,
298
+ fraction_places=result_attrs.fraction_digits,
299
+ is_signed=self._include_sign and result_attrs.is_signed,
300
+ bounds=result_attrs.bounds if self._include_sign else None,
292
301
  )
293
302
 
294
- def _get_output_size(self, ub: float, lb: float, fraction_places: int) -> int:
295
- if isinstance(self.left_arg, float) and self.left_arg == 0.0:
296
- if isinstance(self.right_arg, RegisterArithmeticInfo):
297
- return self.right_arg.size
298
- return as_arithmetic_info(self.right_arg).size
299
- elif isinstance(self.right_arg, float) and self.right_arg == 0.0:
300
- assert isinstance(self.left_arg, RegisterArithmeticInfo)
301
- return self.left_arg.size
302
-
303
- integer_part_size = number_utils.bounds_to_integer_part_size(lb, ub)
304
- return integer_part_size + fraction_places
303
+ @staticmethod
304
+ def _is_zero(arg: RegisterOrConst) -> bool:
305
+ return isinstance(arg, float) and arg == 0
305
306
 
306
307
 
307
308
  class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
@@ -337,41 +338,32 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
337
338
  return self._is_arg_trimmed_register(self.right_arg, self.machine_precision)
338
339
 
339
340
  def _get_result_register(self) -> RegisterArithmeticInfo:
340
- bounds = (
341
- argument_utils.lower_bound(self.effective_left_arg)
342
- - argument_utils.upper_bound(self.effective_right_arg),
343
- argument_utils.upper_bound(self.effective_left_arg)
344
- - argument_utils.lower_bound(self.effective_right_arg),
341
+ left_attrs = NumericAttributes.from_type_or_constant(
342
+ self.left_arg, self.machine_precision
345
343
  )
346
- fraction_places = max(
347
- argument_utils.fraction_places(self.effective_left_arg),
348
- argument_utils.fraction_places(self.effective_right_arg),
344
+ right_attrs = NumericAttributes.from_type_or_constant(
345
+ self.right_arg, self.machine_precision
346
+ )
347
+ result_attrs = compute_result_attrs_subtract(
348
+ left_attrs, right_attrs, self.machine_precision
349
349
  )
350
350
 
351
- size = self.output_size or self._get_output_size(bounds, fraction_places)
352
- is_signed = self._include_sign and min(bounds) < 0
351
+ size = self.output_size or result_attrs.size
352
+ is_signed = self._include_sign and result_attrs.is_signed
353
353
  return RegisterArithmeticInfo(
354
354
  size=size,
355
- fraction_places=fraction_places,
355
+ fraction_places=result_attrs.fraction_digits,
356
356
  is_signed=is_signed,
357
357
  bounds=self._legal_bounds(
358
- bounds,
358
+ result_attrs.bounds,
359
359
  RegisterArithmeticInfo.get_maximal_bounds(
360
- size=size, is_signed=is_signed, fraction_places=fraction_places
360
+ size=size,
361
+ is_signed=is_signed,
362
+ fraction_places=result_attrs.fraction_digits,
361
363
  ),
362
364
  ),
363
365
  )
364
366
 
365
- def _get_output_size(
366
- self, bounds: tuple[float, float], fraction_places: int
367
- ) -> int:
368
- if isinstance(self.right_arg, float) and self.effective_right_arg == 0:
369
- assert isinstance(self.effective_left_arg, RegisterArithmeticInfo)
370
- return self.effective_left_arg.size
371
- integer_part_size = number_utils.bounds_to_integer_part_size(*bounds)
372
- size_needed = integer_part_size + fraction_places
373
- return size_needed
374
-
375
367
  def garbage_output_size(self) -> pydantic.NonNegativeInt:
376
368
  if (
377
369
  not self.left_arg_is_trimmed_register
@@ -79,10 +79,11 @@ def size(float_value: float) -> int:
79
79
 
80
80
 
81
81
  def _is_extra_sign_bit_needed(*, lb: float, ub: float) -> bool:
82
- integer_lb = lb * 2 ** fraction_places(lb)
82
+ fractions = max(fraction_places(lb), fraction_places(ub))
83
+ integer_lb = lb * 2**fractions
83
84
  max_represented_number = (
84
85
  2 ** (len(binary_string(integer_lb)) - 1) - 1
85
- ) / 2 ** fraction_places(lb)
86
+ ) / 2**fractions
86
87
  return ub > max_represented_number
87
88
 
88
89