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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. classiq/applications/combinatorial_optimization/combinatorial_problem.py +24 -45
  2. classiq/evaluators/classical_expression.py +32 -15
  3. classiq/evaluators/qmod_annotated_expression.py +207 -0
  4. classiq/evaluators/qmod_expression_visitors/__init__.py +0 -0
  5. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +134 -0
  6. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +232 -0
  7. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +44 -0
  8. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +308 -0
  9. classiq/evaluators/qmod_node_evaluators/__init__.py +0 -0
  10. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +112 -0
  11. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +132 -0
  12. classiq/evaluators/qmod_node_evaluators/bool_op_evaluation.py +70 -0
  13. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +311 -0
  14. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +107 -0
  15. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +67 -0
  16. classiq/evaluators/qmod_node_evaluators/list_evaluation.py +107 -0
  17. classiq/evaluators/qmod_node_evaluators/measurement_evaluation.py +25 -0
  18. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +50 -0
  19. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +66 -0
  20. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +225 -0
  21. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +58 -0
  22. classiq/evaluators/qmod_node_evaluators/utils.py +80 -0
  23. classiq/execution/execution_session.py +53 -6
  24. classiq/interface/_version.py +1 -1
  25. classiq/interface/analyzer/analysis_params.py +1 -1
  26. classiq/interface/analyzer/result.py +1 -1
  27. classiq/interface/debug_info/debug_info.py +0 -4
  28. classiq/interface/executor/quantum_code.py +2 -2
  29. classiq/interface/generator/arith/arithmetic_expression_validator.py +5 -1
  30. classiq/interface/generator/arith/binary_ops.py +43 -51
  31. classiq/interface/generator/arith/number_utils.py +3 -2
  32. classiq/interface/generator/arith/register_user_input.py +15 -0
  33. classiq/interface/generator/arith/unary_ops.py +32 -28
  34. classiq/interface/generator/expressions/atomic_expression_functions.py +5 -0
  35. classiq/interface/generator/expressions/expression_types.py +2 -2
  36. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  37. classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
  38. classiq/interface/generator/functions/classical_function_declaration.py +0 -4
  39. classiq/interface/generator/functions/classical_type.py +0 -32
  40. classiq/interface/generator/functions/concrete_types.py +20 -0
  41. classiq/interface/generator/generated_circuit_data.py +7 -10
  42. classiq/interface/generator/quantum_program.py +6 -1
  43. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +29 -0
  44. classiq/interface/ide/operation_registry.py +45 -0
  45. classiq/interface/ide/visual_model.py +84 -2
  46. classiq/interface/model/bounds.py +12 -2
  47. classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
  48. classiq/interface/model/quantum_type.py +67 -33
  49. classiq/interface/model/variable_declaration_statement.py +33 -6
  50. classiq/model_expansions/arithmetic.py +115 -0
  51. classiq/model_expansions/arithmetic_compute_result_attrs.py +71 -0
  52. classiq/model_expansions/atomic_expression_functions_defs.py +10 -6
  53. classiq/model_expansions/function_builder.py +4 -1
  54. classiq/model_expansions/generative_functions.py +15 -2
  55. classiq/model_expansions/interpreters/base_interpreter.py +7 -0
  56. classiq/model_expansions/interpreters/generative_interpreter.py +18 -1
  57. classiq/model_expansions/quantum_operations/assignment_result_processor.py +63 -21
  58. classiq/model_expansions/quantum_operations/bounds.py +7 -1
  59. classiq/model_expansions/quantum_operations/call_emitter.py +5 -2
  60. classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
  61. classiq/model_expansions/quantum_operations/variable_decleration.py +30 -10
  62. classiq/model_expansions/scope.py +7 -0
  63. classiq/model_expansions/scope_initialization.py +2 -0
  64. classiq/model_expansions/sympy_conversion/sympy_to_python.py +1 -1
  65. classiq/model_expansions/transformers/type_modifier_inference.py +5 -0
  66. classiq/model_expansions/transformers/var_splitter.py +1 -1
  67. classiq/model_expansions/visitors/boolean_expression_transformers.py +1 -1
  68. classiq/open_library/functions/__init__.py +0 -2
  69. classiq/open_library/functions/qaoa_penalty.py +8 -1
  70. classiq/open_library/functions/state_preparation.py +1 -32
  71. classiq/qmod/__init__.py +2 -0
  72. classiq/qmod/builtins/operations.py +66 -2
  73. classiq/qmod/classical_variable.py +74 -0
  74. classiq/qmod/declaration_inferrer.py +5 -3
  75. classiq/qmod/native/pretty_printer.py +18 -14
  76. classiq/qmod/pretty_print/pretty_printer.py +34 -15
  77. classiq/qmod/qfunc.py +2 -19
  78. classiq/qmod/qmod_variable.py +5 -8
  79. classiq/qmod/quantum_expandable.py +1 -1
  80. classiq/qmod/quantum_function.py +42 -2
  81. classiq/qmod/symbolic_type.py +2 -1
  82. classiq/qmod/write_qmod.py +3 -1
  83. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/METADATA +1 -1
  84. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/RECORD +86 -62
  85. classiq/interface/model/quantum_variable_declaration.py +0 -7
  86. /classiq/{model_expansions/sympy_conversion/arithmetics.py → evaluators/qmod_expression_visitors/sympy_wrappers.py} +0 -0
  87. {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/WHEEL +0 -0
@@ -52,8 +52,6 @@ VISUALIZATION_HIDE_LIST = [
52
52
  "stmt_block",
53
53
  ]
54
54
 
55
- CONTROLLED_PREFIX = "c_"
56
-
57
55
 
58
56
  def last_name_in_call_hierarchy(name: str) -> str:
59
57
  return name.split(CLASSIQ_HIERARCHY_SEPARATOR)[-1]
@@ -147,6 +145,8 @@ class StatementType(StrEnum):
147
145
  INPLACE_XOR = "inplace xor"
148
146
  INPLACE_ADD = "inplace add"
149
147
  REPEAT = "repeat"
148
+ BLOCK = "block"
149
+ IF = "if"
150
150
 
151
151
 
152
152
  # Mapping between statement kind (or sub-kind) and statement type (visualization name)
@@ -167,6 +167,8 @@ STATEMENTS_NAME: dict[str, StatementType] = {
167
167
  ArithmeticOperationKind.InplaceXor.value: StatementType.INPLACE_XOR,
168
168
  ArithmeticOperationKind.InplaceAdd.value: StatementType.INPLACE_ADD,
169
169
  "Repeat": StatementType.REPEAT,
170
+ "Block": StatementType.BLOCK,
171
+ "ClassicalIf": StatementType.IF,
170
172
  }
171
173
 
172
174
 
@@ -207,8 +209,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
207
209
  ARITH_ENGINE_PREFIX
208
210
  )
209
211
  name_with_suffix = self.add_suffix_from_generated_name(generated_name, name)
210
- modified_name = self.modify_name_for_controlled_qfunc(name_with_suffix)
211
- return modified_name
212
+ return name_with_suffix
212
213
 
213
214
  statement_kind: str = back_ref.kind
214
215
  if isinstance(back_ref, ArithmeticOperation):
@@ -217,11 +218,6 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
217
218
  generated_name, STATEMENTS_NAME[statement_kind]
218
219
  )
219
220
 
220
- def modify_name_for_controlled_qfunc(self, generated_name: str) -> str:
221
- if self.control_variable is None:
222
- return generated_name
223
- return f"{CONTROLLED_PREFIX}{generated_name}"
224
-
225
221
  def add_suffix_from_generated_name(self, generated_name: str, name: str) -> str:
226
222
  if part_match := PART_SUFFIX_REGEX.match(generated_name):
227
223
  suffix = f" [{part_match.group(1)}]"
@@ -272,7 +268,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
272
268
  for register in self.registers
273
269
  for qubit in register.qubit_indexes_absolute
274
270
  if register.role is RegisterRole.INPUT
275
- and register.name == self.control_variable
271
+ and self.port_to_passed_variable_map.get(register.name, register.name)
272
+ == self.control_variable
276
273
  )
277
274
 
278
275
  def propagate_absolute_qubits(self) -> "FunctionDebugInfoInterface":
@@ -76,6 +76,8 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
76
76
  execution_primitives_input: Optional[PrimitivesInput] = pydantic.Field(default=None)
77
77
  synthesis_warnings: Optional[list[str]] = pydantic.Field(default=None)
78
78
  should_warn: bool = pydantic.Field(default=False)
79
+ # Unique identifier for the circuit (since the program_id might change when running show). Used for the circuit store.
80
+ circuit_id: str = pydantic.Field(default_factory=get_uuid_as_str)
79
81
 
80
82
  def __str__(self) -> str:
81
83
  return self.model_dump_json(indent=2)
@@ -168,7 +170,10 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
168
170
 
169
171
  @property
170
172
  def _can_use_transpiled_code(self) -> bool:
171
- return self.data.execution_data is None
173
+ return (
174
+ self.data.execution_data is None
175
+ or not self.data.execution_data.function_execution
176
+ )
172
177
 
173
178
  @property
174
179
  def program_circuit(self) -> CircuitCodeInterface:
@@ -3,6 +3,7 @@ from typing import Optional
3
3
 
4
4
  import pydantic
5
5
  import sympy
6
+ from typing_extensions import Self
6
7
 
7
8
  from classiq.interface.backend.pydantic_backend import PydanticExecutionParameter
8
9
  from classiq.interface.generator.parameters import ParameterType
@@ -34,3 +35,31 @@ class ExecutionData(pydantic.BaseModel):
34
35
  if function_execution_data.power_vars is not None
35
36
  )
36
37
  )
38
+
39
+ def to_inverse(self) -> Self:
40
+ return type(self)(
41
+ function_execution={
42
+ self._inverse_name(name): value
43
+ for name, value in self.function_execution.items()
44
+ }
45
+ )
46
+
47
+ def to_control(self) -> Self:
48
+ return type(self)(
49
+ function_execution={
50
+ self._control_name(name): value
51
+ for name, value in self.function_execution.items()
52
+ }
53
+ )
54
+
55
+ @staticmethod
56
+ def _inverse_name(name: str) -> str:
57
+ # see inverse of qiskit.circuit.Instruction
58
+ if name.endswith("_dg"):
59
+ return name[:-3]
60
+ return f"{name}_dg"
61
+
62
+ @staticmethod
63
+ def _control_name(name: str) -> str:
64
+ # see inverse of qiskit.circuit.QuantumCircuit
65
+ return f"c_{name}"
@@ -0,0 +1,45 @@
1
+ from typing import Any
2
+
3
+ from classiq.interface.ide.visual_model import Operation
4
+
5
+
6
+ class OperationRegistry:
7
+ def __init__(self) -> None:
8
+ self._operation_hash_to_op_id: dict[int, int] = {}
9
+ self._id_to_operations: dict[int, Operation] = {}
10
+ self._unique_op_counter = 0
11
+ self._deduped_op_counter = 0
12
+
13
+ def build_operation(self, **kwargs: Any) -> Operation:
14
+ operation = Operation(**kwargs)
15
+ return self._add_operation(operation)
16
+
17
+ def _add_operation(self, op: Operation) -> Operation:
18
+ """
19
+ Adds an operation to the global dictionaries for operations.
20
+ if operation already exist in the registry, it returns the existing operation.
21
+ """
22
+ op_hash = hash(op)
23
+ if op_hash not in self._operation_hash_to_op_id:
24
+ self._operation_hash_to_op_id[op_hash] = op.id
25
+ self._id_to_operations[op.id] = op
26
+ self._unique_op_counter += 1
27
+ else:
28
+ self._deduped_op_counter += 1
29
+ op = self._id_to_operations[self._operation_hash_to_op_id[op_hash]]
30
+ return op
31
+
32
+ def get_operation_mapping(self) -> dict[int, Operation]:
33
+ return self._id_to_operations
34
+
35
+ def get_operations(self, op_ids: list[int]) -> list[Operation]:
36
+ """
37
+ Returns a list of operations based on their IDs.
38
+ """
39
+ return [self._id_to_operations[op_id] for op_id in op_ids]
40
+
41
+ def get_unique_op_number(self) -> int:
42
+ return self._unique_op_counter
43
+
44
+ def get_deduped_op_number(self) -> int:
45
+ return self._deduped_op_counter
@@ -1,9 +1,12 @@
1
+ import json
1
2
  from collections import Counter
3
+ from collections.abc import Iterator
2
4
  from functools import cached_property
5
+ from itertools import count
3
6
  from typing import Any, Optional
4
7
 
5
8
  import pydantic
6
- from pydantic import ConfigDict
9
+ from pydantic import ConfigDict, field_validator
7
10
 
8
11
  from classiq.interface.enum_utils import StrEnum
9
12
  from classiq.interface.generator.generated_circuit_data import (
@@ -13,6 +16,26 @@ from classiq.interface.generator.hardware.hardware_data import SynthesisHardware
13
16
  from classiq.interface.helpers.versioned_model import VersionedModel
14
17
 
15
18
 
19
+ class OperationIdCounter:
20
+ _op_id_counter: Iterator[int] = count()
21
+
22
+ def next_id(self) -> int:
23
+ return next(self._op_id_counter)
24
+
25
+ def reset_operation_counter(self) -> None:
26
+ self._op_id_counter = count()
27
+
28
+
29
+ _operation_id_counter = OperationIdCounter()
30
+
31
+
32
+ def reset_operation_counter() -> None:
33
+ """
34
+ Call this at the start of every new task to restart ids at 0.
35
+ """
36
+ _operation_id_counter.reset_operation_counter()
37
+
38
+
16
39
  class OperationType(StrEnum):
17
40
  REGULAR = "REGULAR"
18
41
  INVISIBLE = "INVISIBLE"
@@ -27,6 +50,8 @@ class OperationData(pydantic.BaseModel):
27
50
  width: int
28
51
  gate_count: Counter[str] = pydantic.Field(default_factory=dict)
29
52
 
53
+ model_config = ConfigDict(frozen=True)
54
+
30
55
 
31
56
  class CircuitMetrics(pydantic.BaseModel):
32
57
  depth: int
@@ -60,6 +85,21 @@ class OperationLinks(pydantic.BaseModel):
60
85
  inputs: list[OperationLink]
61
86
  outputs: list[OperationLink]
62
87
 
88
+ model_config = ConfigDict(frozen=True)
89
+
90
+ @field_validator("inputs", "outputs", mode="after")
91
+ @classmethod
92
+ def sort_links(cls, v: list[OperationLink]) -> Any:
93
+ """
94
+ sorting the input/output links on creation
95
+ the sort is done by 'label-qubits-type'
96
+ since hash is non-deterministic between runs
97
+ """
98
+ return sorted(v, key=hash)
99
+
100
+ def __hash__(self) -> int:
101
+ return hash(json.dumps(self.model_dump(exclude_none=True), sort_keys=True))
102
+
63
103
  @cached_property
64
104
  def input_width(self) -> int:
65
105
  return sum(len(link.qubits) for link in self.inputs)
@@ -108,6 +148,8 @@ class AtomicGate(StrEnum):
108
148
 
109
149
  class Operation(pydantic.BaseModel):
110
150
  name: str
151
+ inner_label: Optional[str] = None
152
+ _id: int = pydantic.PrivateAttr(default_factory=_operation_id_counter.next_id)
111
153
  qasm_name: str = pydantic.Field(default="")
112
154
  details: str = pydantic.Field(default="")
113
155
  children: list["Operation"] = pydantic.Field(default_factory=list)
@@ -120,7 +162,7 @@ class Operation(pydantic.BaseModel):
120
162
  target_qubits: tuple[int, ...]
121
163
  operation_level: OperationLevel
122
164
  operation_type: OperationType = pydantic.Field(
123
- description="Identifies unique operations that are visualized differently"
165
+ description="Identifies unique operations that are visualized differently",
124
166
  )
125
167
  gate: AtomicGate = pydantic.Field(
126
168
  default=AtomicGate.UNKNOWN, description="Gate type"
@@ -128,6 +170,40 @@ class Operation(pydantic.BaseModel):
128
170
  is_daggered: bool = pydantic.Field(default=False)
129
171
  expanded: bool = pydantic.Field(default=False)
130
172
  show_expanded_label: bool = pydantic.Field(default=False)
173
+ is_low_level_fallback: bool = pydantic.Field(default=False)
174
+
175
+ model_config = ConfigDict(frozen=True)
176
+
177
+ @property
178
+ def id(self) -> int:
179
+ return self._id
180
+
181
+ def __hash__(self) -> int:
182
+ """
183
+ using a custom hashable_dict in order to compare the operation
184
+ with the qubits in order
185
+ """
186
+ js = json.dumps(
187
+ self._hashable_dict(),
188
+ sort_keys=True,
189
+ default=lambda o: o.value if hasattr(o, "value") else str(o),
190
+ )
191
+ return hash(js)
192
+
193
+ def __eq__(self, other: object) -> bool:
194
+ return (
195
+ isinstance(other, Operation)
196
+ and self._hashable_dict() == other._hashable_dict()
197
+ )
198
+
199
+ def _hashable_dict(self) -> dict:
200
+ data = self.model_dump(
201
+ exclude_none=True,
202
+ )
203
+ # force qubit order for equality
204
+ for key in ("target_qubits", "auxiliary_qubits", "control_qubits"):
205
+ data[key] = sorted(data[key])
206
+ return data
131
207
 
132
208
 
133
209
  class ProgramVisualModel(VersionedModel):
@@ -135,3 +211,9 @@ class ProgramVisualModel(VersionedModel):
135
211
  id_to_operations: dict[int, Operation] = pydantic.Field(default_factory=dict)
136
212
  main_operation_id: int = pydantic.Field(default=None)
137
213
  program_data: ProgramData
214
+
215
+ @property
216
+ def main_op_from_mapping(self) -> Operation:
217
+ if self.main_operation_id is None:
218
+ raise ValueError("Main operation ID is not set.")
219
+ return self.id_to_operations[self.main_operation_id]
@@ -1,6 +1,6 @@
1
1
  from typing import Literal, Optional
2
2
 
3
- from classiq.interface.helpers.custom_pydantic_types import PydanticFloatTuple
3
+ from classiq.interface.generator.expressions.expression import Expression
4
4
  from classiq.interface.model.handle_binding import ConcreteHandleBinding
5
5
  from classiq.interface.model.quantum_statement import QuantumOperation
6
6
 
@@ -9,4 +9,14 @@ class SetBoundsStatement(QuantumOperation):
9
9
  kind: Literal["SetBoundsStatement"]
10
10
 
11
11
  target: ConcreteHandleBinding
12
- bounds: Optional[PydanticFloatTuple]
12
+ lower_bound: Optional[Expression]
13
+ upper_bound: Optional[Expression]
14
+
15
+ @property
16
+ def expressions(self) -> list[Expression]:
17
+ exprs = []
18
+ if self.lower_bound is not None:
19
+ exprs.append(self.lower_bound)
20
+ if self.upper_bound is not None:
21
+ exprs.append(self.upper_bound)
22
+ return exprs
@@ -1,6 +1,8 @@
1
1
  from collections.abc import Mapping, Sequence
2
2
  from typing import Literal
3
3
 
4
+ from pydantic import PrivateAttr
5
+
4
6
  from classiq.interface.enum_utils import StrEnum
5
7
  from classiq.interface.generator.arith.arithmetic import (
6
8
  ARITHMETIC_EXPRESSION_RESULT_NAME,
@@ -27,6 +29,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
27
29
  kind: Literal["ArithmeticOperation"]
28
30
 
29
31
  operation_kind: ArithmeticOperationKind
32
+ _classical_assignment: bool = PrivateAttr(default=False)
30
33
 
31
34
  @property
32
35
  def is_inplace(self) -> bool:
@@ -50,7 +53,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
50
53
  self,
51
54
  ) -> Mapping[str, ConcreteHandleBinding]:
52
55
  inouts = dict(super().wiring_inouts)
53
- if self.is_inplace:
56
+ if self.is_inplace and not self._classical_assignment:
54
57
  inouts[self.result_name()] = self.result_var
55
58
  return inouts
56
59
 
@@ -60,7 +63,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
60
63
  HandleMetadata(handle=handle, readable_location="in an expression")
61
64
  for handle in self.var_handles
62
65
  ]
63
- if self.is_inplace:
66
+ if self.is_inplace and not self._classical_assignment:
64
67
  inouts.append(
65
68
  HandleMetadata(
66
69
  handle=self.result_var,
@@ -71,13 +74,13 @@ class ArithmeticOperation(QuantumAssignmentOperation):
71
74
 
72
75
  @property
73
76
  def wiring_outputs(self) -> Mapping[str, HandleBinding]:
74
- if self.is_inplace:
77
+ if self.is_inplace or self._classical_assignment:
75
78
  return {}
76
79
  return super().wiring_outputs
77
80
 
78
81
  @property
79
82
  def readable_outputs(self) -> Sequence[HandleMetadata]:
80
- if self.is_inplace:
83
+ if self.is_inplace or self._classical_assignment:
81
84
  return []
82
85
  return [
83
86
  HandleMetadata(
@@ -85,6 +85,35 @@ class QuantumScalar(QuantumType):
85
85
  def get_proxy(self, handle: "HandleBinding") -> QmodQScalarProxy:
86
86
  return QmodQScalarProxy(handle, size=self.size_in_bits)
87
87
 
88
+ @property
89
+ def has_sign(self) -> bool:
90
+ raise NotImplementedError
91
+
92
+ @property
93
+ def sign_value(self) -> bool:
94
+ raise NotImplementedError
95
+
96
+ @property
97
+ def has_fraction_digits(self) -> bool:
98
+ raise NotImplementedError
99
+
100
+ @property
101
+ def fraction_digits_value(self) -> int:
102
+ raise NotImplementedError
103
+
104
+ def get_effective_bounds(
105
+ self, machine_precision: Optional[int] = None
106
+ ) -> tuple[float, float]:
107
+ raise NotImplementedError
108
+
109
+ @property
110
+ def is_qbit(self) -> bool:
111
+ return (
112
+ self.size_in_bits == 1
113
+ and self.fraction_digits_value == 0
114
+ and not self.sign_value
115
+ )
116
+
88
117
 
89
118
  class QuantumBit(QuantumScalar):
90
119
  kind: Literal["qbit"]
@@ -117,6 +146,27 @@ class QuantumBit(QuantumScalar):
117
146
  def is_evaluated(self) -> bool:
118
147
  return True
119
148
 
149
+ @property
150
+ def has_sign(self) -> bool:
151
+ return True
152
+
153
+ @property
154
+ def sign_value(self) -> bool:
155
+ return False
156
+
157
+ @property
158
+ def has_fraction_digits(self) -> bool:
159
+ return True
160
+
161
+ @property
162
+ def fraction_digits_value(self) -> int:
163
+ return 0
164
+
165
+ def get_effective_bounds(
166
+ self, machine_precision: Optional[int] = None
167
+ ) -> tuple[float, float]:
168
+ return (0, 1)
169
+
120
170
 
121
171
  class QuantumBitvector(QuantumType):
122
172
  element_type: "ConcreteQuantumType" = Field(
@@ -243,14 +293,6 @@ class QuantumNumeric(QuantumScalar):
243
293
  0 if self.fraction_digits is None else self.fraction_digits.to_int_value()
244
294
  )
245
295
 
246
- @property
247
- def is_qbit(self) -> bool:
248
- return (
249
- self.size_in_bits == 1
250
- and self.fraction_digits_value == 0
251
- and not self.sign_value
252
- )
253
-
254
296
  def _update_size_in_bits_from_declaration(self) -> None:
255
297
  if self.size is not None and self.size.is_evaluated():
256
298
  self._size_in_bits = self.size.to_int_value()
@@ -302,20 +344,8 @@ class QuantumNumeric(QuantumScalar):
302
344
  exprs.append(self.fraction_digits)
303
345
  return exprs
304
346
 
305
- def get_bounds(
306
- self, machine_precision: Optional[int] = None
307
- ) -> Optional[tuple[float, float]]:
308
- if (
309
- self._bounds is None
310
- or machine_precision is None
311
- or self.fraction_digits_value <= machine_precision
312
- ):
313
- return self._bounds
314
-
315
- return (
316
- number_utils.limit_fraction_places(self._bounds[0], machine_precision),
317
- number_utils.limit_fraction_places(self._bounds[1], machine_precision),
318
- )
347
+ def get_bounds(self) -> Optional[tuple[float, float]]:
348
+ return self._bounds
319
349
 
320
350
  def set_bounds(self, bounds: Optional[tuple[float, float]]) -> None:
321
351
  self._bounds = bounds
@@ -323,19 +353,23 @@ class QuantumNumeric(QuantumScalar):
323
353
  def reset_bounds(self) -> None:
324
354
  self.set_bounds(None)
325
355
 
326
- def get_maximal_bounds(
356
+ def get_maximal_bounds(self) -> tuple[float, float]:
357
+ return RegisterArithmeticInfo.get_maximal_bounds(
358
+ size=self.size_in_bits,
359
+ is_signed=self.sign_value,
360
+ fraction_places=self.fraction_digits_value,
361
+ )
362
+
363
+ def get_effective_bounds(
327
364
  self, machine_precision: Optional[int] = None
328
365
  ) -> tuple[float, float]:
329
- size = self.size_in_bits
330
- fraction_digits = self.fraction_digits_value
331
- if machine_precision is not None and fraction_digits > machine_precision:
332
- size -= fraction_digits - machine_precision
333
- fraction_digits = machine_precision
366
+ bounds = self.get_bounds() or self.get_maximal_bounds()
334
367
 
335
- return RegisterArithmeticInfo.get_maximal_bounds(
336
- size=size,
337
- is_signed=self.sign_value,
338
- fraction_places=fraction_digits,
368
+ if machine_precision is None or machine_precision >= self.fraction_digits_value:
369
+ return bounds
370
+ return (
371
+ number_utils.limit_fraction_places(bounds[0], machine_precision),
372
+ number_utils.limit_fraction_places(bounds[1], machine_precision),
339
373
  )
340
374
 
341
375
 
@@ -359,7 +393,7 @@ def register_info_to_quantum_type(reg_info: RegisterArithmeticInfo) -> QuantumNu
359
393
  result.set_size_in_bits(reg_info.size)
360
394
  result.is_signed = Expression(expr=str(reg_info.is_signed))
361
395
  result.fraction_digits = Expression(expr=str(reg_info.fraction_places))
362
- result.set_bounds(reg_info.bounds)
396
+ result.set_bounds(tuple(reg_info.bounds)) # type: ignore[arg-type]
363
397
  return result
364
398
 
365
399
 
@@ -1,15 +1,42 @@
1
- from typing import Literal
1
+ from typing import Any, Literal, Optional
2
+
3
+ import pydantic
2
4
 
3
5
  from classiq.interface.generator.expressions.expression import Expression
4
- from classiq.interface.model.quantum_statement import QuantumStatement
5
- from classiq.interface.model.quantum_variable_declaration import (
6
- QuantumVariableDeclaration,
6
+ from classiq.interface.generator.functions.concrete_types import (
7
+ ConcreteQuantumType,
8
+ ConcreteType,
7
9
  )
10
+ from classiq.interface.model.quantum_statement import QuantumStatement
11
+ from classiq.interface.model.quantum_type import QuantumType
8
12
 
9
13
 
10
- class VariableDeclarationStatement(QuantumStatement, QuantumVariableDeclaration):
14
+ class VariableDeclarationStatement(QuantumStatement):
11
15
  kind: Literal["VariableDeclarationStatement"]
12
16
 
17
+ name: str
18
+ quantum_type: Optional[ConcreteQuantumType] = None
19
+ qmod_type: ConcreteType
20
+
21
+ @pydantic.model_validator(mode="before")
22
+ @classmethod
23
+ def _set_qmod_type(cls, values: Any) -> dict[str, Any]:
24
+ if isinstance(values, dict):
25
+ if "quantum_type" in values and (
26
+ "qmod_type" not in values or values["qmod_type"] is None
27
+ ):
28
+ values["qmod_type"] = values["quantum_type"]
29
+ values["quantum_type"] = None
30
+ return values
31
+ if values.quantum_type is not None and values.qmod_type is None:
32
+ values.qmod_type = values.quantum_type
33
+ values.quantum_type = None
34
+ return values
35
+
13
36
  @property
14
37
  def expressions(self) -> list[Expression]:
15
- return self.quantum_type.expressions
38
+ return self.qmod_type.expressions
39
+
40
+ @property
41
+ def is_quantum(self) -> bool:
42
+ return isinstance(self.qmod_type, QuantumType)
@@ -0,0 +1,115 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, Union
3
+
4
+ from classiq.interface.generator.arith import number_utils
5
+ from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
6
+ from classiq.interface.model.quantum_type import (
7
+ QuantumScalar,
8
+ register_info_to_quantum_type,
9
+ )
10
+
11
+
12
+ @dataclass
13
+ class NumericAttributes:
14
+ size: int
15
+ is_signed: bool
16
+ fraction_digits: int
17
+ bounds: tuple[float, float]
18
+
19
+ def __init__(
20
+ self,
21
+ size: int,
22
+ is_signed: bool,
23
+ fraction_digits: int,
24
+ bounds: Optional[tuple[float, float]] = None,
25
+ trim_bounds: bool = False,
26
+ ) -> None:
27
+ self.size = size
28
+ self.is_signed = is_signed
29
+ self.fraction_digits = fraction_digits
30
+ if bounds is None:
31
+ bounds = RegisterArithmeticInfo.get_maximal_bounds(
32
+ size=size,
33
+ is_signed=is_signed,
34
+ fraction_places=fraction_digits,
35
+ )
36
+ if trim_bounds:
37
+ bounds = (
38
+ number_utils.limit_fraction_places(bounds[0], fraction_digits),
39
+ number_utils.limit_fraction_places(bounds[1], fraction_digits),
40
+ )
41
+ self.bounds = bounds
42
+
43
+ @property
44
+ def lb(self) -> float:
45
+ return self.bounds[0]
46
+
47
+ @property
48
+ def ub(self) -> float:
49
+ return self.bounds[1]
50
+
51
+ @classmethod
52
+ def from_bounds(
53
+ cls, lb: float, ub: float, fraction_places: int, machine_precision: int
54
+ ) -> "NumericAttributes":
55
+ size, is_signed, fraction_digits = number_utils.bounds_to_attributes(
56
+ lb, ub, fraction_places, machine_precision
57
+ )
58
+ return cls(
59
+ size=size,
60
+ is_signed=is_signed,
61
+ fraction_digits=fraction_digits,
62
+ bounds=(lb, ub),
63
+ )
64
+
65
+ @classmethod
66
+ def from_constant(
67
+ cls,
68
+ value: float,
69
+ machine_precision: Optional[int] = None,
70
+ ) -> "NumericAttributes":
71
+ if machine_precision is not None:
72
+ value = number_utils.limit_fraction_places(value, machine_precision)
73
+
74
+ return cls(
75
+ size=number_utils.size(value),
76
+ is_signed=value < 0,
77
+ fraction_digits=number_utils.fraction_places(value),
78
+ bounds=(value, value),
79
+ )
80
+
81
+ @classmethod
82
+ def from_quantum_scalar(
83
+ cls,
84
+ quantum_type: QuantumScalar,
85
+ machine_precision: Optional[int] = None,
86
+ ) -> "NumericAttributes":
87
+ return cls(
88
+ size=quantum_type.size_in_bits,
89
+ is_signed=quantum_type.sign_value,
90
+ fraction_digits=quantum_type.fraction_digits_value,
91
+ bounds=quantum_type.get_effective_bounds(machine_precision),
92
+ )
93
+
94
+ @classmethod
95
+ def from_register_arithmetic_info(
96
+ cls,
97
+ register: RegisterArithmeticInfo,
98
+ machine_precision: Optional[int] = None,
99
+ ) -> "NumericAttributes":
100
+ return cls.from_quantum_scalar(
101
+ quantum_type=register_info_to_quantum_type(register),
102
+ machine_precision=machine_precision,
103
+ )
104
+
105
+ @classmethod
106
+ def from_type_or_constant(
107
+ cls,
108
+ from_: Union[float, QuantumScalar, RegisterArithmeticInfo],
109
+ machine_precision: Optional[int] = None,
110
+ ) -> "NumericAttributes":
111
+ if isinstance(from_, QuantumScalar):
112
+ return cls.from_quantum_scalar(from_, machine_precision)
113
+ if isinstance(from_, RegisterArithmeticInfo):
114
+ return cls.from_register_arithmetic_info(from_, machine_precision)
115
+ return cls.from_constant(from_, machine_precision)