classiq 0.77.0__py3-none-any.whl → 0.78.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 (32) hide show
  1. classiq/applications/iqae/__init__.py +0 -0
  2. classiq/applications/iqae/iqae.py +207 -0
  3. classiq/execution/__init__.py +1 -1
  4. classiq/interface/_version.py +1 -1
  5. classiq/interface/applications/iqae/__init__.py +0 -0
  6. classiq/interface/applications/iqae/generic_iqae.py +222 -0
  7. classiq/interface/applications/iqae/iqae_result.py +45 -0
  8. classiq/interface/debug_info/debug_info.py +3 -0
  9. classiq/interface/executor/execution_result.py +1 -1
  10. classiq/interface/executor/user_budget.py +1 -1
  11. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +9 -3
  12. classiq/interface/generator/generated_circuit_data.py +18 -7
  13. classiq/interface/ide/visual_model.py +2 -0
  14. classiq/model_expansions/closure.py +1 -58
  15. classiq/model_expansions/evaluators/argument_types.py +1 -2
  16. classiq/model_expansions/function_builder.py +0 -45
  17. classiq/model_expansions/interpreters/base_interpreter.py +12 -14
  18. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +0 -17
  19. classiq/model_expansions/quantum_operations/call_emitter.py +71 -32
  20. classiq/model_expansions/quantum_operations/emitter.py +6 -1
  21. classiq/model_expansions/quantum_operations/function_calls_cache.py +84 -0
  22. classiq/model_expansions/scope.py +20 -10
  23. classiq/model_expansions/transformers/type_qualifier_inference.py +183 -0
  24. classiq/model_expansions/utils/text_utils.py +4 -2
  25. classiq/model_expansions/visitors/symbolic_param_inference.py +1 -12
  26. classiq/qmod/builtins/classical_execution_primitives.py +1 -1
  27. classiq/qmod/qmod_variable.py +7 -4
  28. classiq/qmod/utilities.py +4 -0
  29. {classiq-0.77.0.dist-info → classiq-0.78.0.dist-info}/METADATA +1 -1
  30. {classiq-0.77.0.dist-info → classiq-0.78.0.dist-info}/RECORD +31 -25
  31. classiq/interface/executor/iqae_result.py +0 -17
  32. {classiq-0.77.0.dist-info → classiq-0.78.0.dist-info}/WHEEL +0 -0
File without changes
@@ -0,0 +1,207 @@
1
+ from typing import Literal, Optional, cast
2
+
3
+ from classiq.interface.applications.iqae.generic_iqae import GenericIQAE
4
+ from classiq.interface.applications.iqae.iqae_result import (
5
+ IQAEIterationData,
6
+ IQAEResult,
7
+ )
8
+ from classiq.interface.executor.execution_preferences import ExecutionPreferences
9
+ from classiq.interface.generator.model import Constraints, Preferences
10
+ from classiq.interface.generator.quantum_program import QuantumProgram
11
+ from classiq.interface.model.model import SerializedModel
12
+
13
+ from classiq.execution import ExecutionSession
14
+ from classiq.open_library import amplitude_amplification
15
+ from classiq.qmod import (
16
+ CInt,
17
+ Output,
18
+ QArray,
19
+ QBit,
20
+ QCallable,
21
+ )
22
+ from classiq.qmod.builtins import Z, allocate, bind, within_apply
23
+ from classiq.qmod.create_model_function import create_model
24
+ from classiq.qmod.qfunc import qfunc
25
+ from classiq.synthesis import synthesize
26
+
27
+
28
+ class IQAE:
29
+ """
30
+ Implementation of Iterative Quantum Amplitude Estimation [1].
31
+ Given $A$ s.t. $A|0>_n|0> = \\sqrt{1-a}|\\psi_0>_n|0> + \\sqrt{a}|\\psi_1>_n|1>$, the algorithm estimates
32
+ $a$ by iteratively sampling $Q^kA$, where $Q=AS_0A^{\\dagger}S_{\\psi_0}$, and $k$ is an integer variable.
33
+
34
+ For estimating $a$, The algorithm estimates $\\theta_a$ which is defined by $a = sin^2(\\theta_a)$, so it starts with a
35
+ confidence interval $(0, \\pi/2)$ and narrows down this interval on each iteration according to the sample results.
36
+
37
+ References:
38
+ [1]: Grinko, D., Gacon, J., Zoufal, C., & Woerner, S. (2019).
39
+ Iterative Quantum Amplitude Estimation.
40
+ `arXiv:1912.05559 <https://arxiv.org/abs/1912.05559>`.
41
+ """
42
+
43
+ _NUM_SHOUTS = 2048
44
+
45
+ def __init__(
46
+ self,
47
+ state_prep_op: QCallable[QArray[QBit, Literal["problem_vars_size"]], QBit],
48
+ problem_vars_size: int,
49
+ constraints: Optional[Constraints] = None,
50
+ preferences: Optional[Preferences] = None,
51
+ ) -> None:
52
+ self._state_prep_op = state_prep_op
53
+ self._problem_vars_size: int = problem_vars_size
54
+ self._constraints: Optional[Constraints] = constraints
55
+ self._preferences: Optional[Preferences] = preferences
56
+ self._model: Optional[SerializedModel] = None
57
+ self._qprog: Optional[QuantumProgram] = None
58
+
59
+ """
60
+ Args:
61
+ state_prep_op (Qfunc): implementation of the operator $A$ in Qmod.
62
+ problem_vars_size (int): The size of the problem in terms of the number of qubits, e.g., $n$ of the first register A works on.
63
+ constraints (Constraints): Constraints for the synthesis of the model. See Constraints (Optional).
64
+ preferences (Preferences): Preferences for the synthesis of the model. See Preferences (Optional).
65
+ """
66
+
67
+ def get_model(self) -> SerializedModel:
68
+ """
69
+ Implement the quantum part of IQAE in terms of the Qmod Model
70
+
71
+ Args:
72
+
73
+
74
+ Returns:
75
+ SerializedModel (str): A serialized model.
76
+
77
+ """
78
+
79
+ state_prep_op = self._state_prep_op
80
+ problem_vars_size = self._problem_vars_size
81
+
82
+ @qfunc
83
+ def space_transform(est_reg: QArray) -> None:
84
+ state_prep_op(est_reg[0 : est_reg.len - 1], est_reg[est_reg.len - 1])
85
+
86
+ @qfunc
87
+ def oracle(est_reg: QArray) -> None:
88
+ Z(est_reg[est_reg.len - 1])
89
+
90
+ @qfunc
91
+ def main(
92
+ k: CInt,
93
+ indicator: Output[QBit],
94
+ ) -> None:
95
+ est_reg: QArray = QArray("est_reg")
96
+ problem_vars: QArray = QArray("problem_vars", length=problem_vars_size)
97
+ allocate(problem_vars)
98
+ allocate(indicator)
99
+ within_apply(
100
+ lambda: bind([problem_vars, indicator], est_reg),
101
+ lambda: amplitude_amplification(
102
+ k,
103
+ oracle,
104
+ space_transform,
105
+ est_reg,
106
+ ),
107
+ )
108
+
109
+ if self._model is None:
110
+ self._model = create_model(
111
+ main,
112
+ constraints=self._constraints,
113
+ preferences=self._preferences,
114
+ )
115
+ return self._model
116
+
117
+ def get_qprog(self) -> QuantumProgram:
118
+ """
119
+ Create an executable quantum Program for IQAE.
120
+
121
+ Args:
122
+
123
+ Returns:
124
+ QuantumProgram (QuantumProgram): Quantum program. See QuantumProgram.
125
+ """
126
+
127
+ if self._qprog is None:
128
+ model = self.get_model()
129
+ self._qprog = synthesize(model)
130
+ return self._qprog
131
+
132
+ def run(
133
+ self,
134
+ epsilon: float,
135
+ alpha: float,
136
+ execution_preferences: Optional[ExecutionPreferences] = None,
137
+ ) -> IQAEResult:
138
+ """
139
+ Executes IQAE's quantum program with the provided epsilon, alpha, and execution
140
+ preferences.
141
+ If execution_preferences has been proved, or if it does not contain num_shot, then num_shot is set to 2048.
142
+
143
+ Args:
144
+ epsilon (float): Target accuracy in therm of $\\theta_a$ e.g $a = sin^2(\\theta_a \\pm \\epsilon)$ .
145
+ alpha (float): Specifies the confidence level (1 - alpha)
146
+ execution_preferences (Preferences): Preferences for the execution of the model. See ExecutionPreferences (Optional).
147
+ Returns:
148
+ IQAEResult (IQAEResult): A result of the IQAE algorithm. See IQAEResult.
149
+ """
150
+
151
+ if self._qprog is None:
152
+ self._qprog = self.get_qprog()
153
+
154
+ if execution_preferences is None:
155
+ execution_preferences = ExecutionPreferences(
156
+ num_shots=self._NUM_SHOUTS,
157
+ )
158
+ elif execution_preferences.num_shots is None:
159
+ execution_preferences.num_shots = self._NUM_SHOUTS
160
+
161
+ return self._run(
162
+ epsilon=epsilon,
163
+ alpha=alpha,
164
+ execution_preferences=execution_preferences,
165
+ quantum_program=self._qprog,
166
+ )
167
+
168
+ def _run(
169
+ self,
170
+ epsilon: float,
171
+ alpha: float,
172
+ execution_preferences: ExecutionPreferences,
173
+ quantum_program: QuantumProgram,
174
+ ) -> IQAEResult:
175
+
176
+ iterations_data: list[IQAEIterationData] = []
177
+ warnings: list[str] = []
178
+ with ExecutionSession(quantum_program, execution_preferences) as executor:
179
+
180
+ def _iqae_sample(k: int, _: int) -> int:
181
+ sample_results = executor.sample({"k": k})
182
+ iterations_data.append(
183
+ IQAEIterationData(
184
+ grover_iterations=k,
185
+ sample_results=sample_results,
186
+ )
187
+ )
188
+ return sample_results.counts_of_output("indicator").get("1", 0)
189
+
190
+ iqae = GenericIQAE(
191
+ epsilon=epsilon,
192
+ alpha=alpha,
193
+ num_shots=cast(int, execution_preferences.num_shots),
194
+ sample_callable=_iqae_sample,
195
+ )
196
+
197
+ try:
198
+ iqae.run()
199
+ except RuntimeError as ex:
200
+ warnings.append(f"Algorithm error: {ex.args[0]}")
201
+
202
+ return IQAEResult(
203
+ estimation=iqae.current_estimation(),
204
+ confidence_interval=iqae.current_estimation_confidence_interval().tolist(),
205
+ iterations_data=iterations_data,
206
+ warnings=warnings,
207
+ )
@@ -1,10 +1,10 @@
1
1
  from ..executor import * # noqa: F403
2
2
  from ..executor import __all__ as _exec_all
3
+ from ..interface.applications.iqae.iqae_result import IQAEResult
3
4
  from ..interface.backend.backend_preferences import * # noqa: F403
4
5
  from ..interface.backend.backend_preferences import __all__ as _be_all
5
6
  from ..interface.executor.execution_preferences import * # noqa: F403
6
7
  from ..interface.executor.execution_preferences import __all__ as _ep_all
7
- from ..interface.executor.iqae_result import IQAEResult
8
8
  from ..interface.executor.result import ExecutionDetails
9
9
  from ..interface.executor.vqe_result import VQESolverResult
10
10
  from .execution_session import ExecutionSession
@@ -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.77.0'
6
+ SEMVER_VERSION = '0.78.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
File without changes
@@ -0,0 +1,222 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+
6
+ import numpy as np
7
+
8
+ MAX_ITERATIONS_NUMBER = 1000
9
+
10
+
11
+ class GenericIQAE:
12
+ """
13
+ The implementation is based on Algorithm 1 & Algorithm 2 in [1], with the intent of demistifying variables names
14
+ and simplifying the code flow.
15
+ Moreover, we separated the algorithm flow from quantum execution to allow migrating this code to any execution
16
+ interface and to improve its testability.
17
+
18
+ References:
19
+ [1]: Grinko, D., Gacon, J., Zoufal, C., & Woerner, S. (2019).
20
+ Iterative Quantum Amplitude Estimation.
21
+ `arXiv:1912.05559 <https://arxiv.org/abs/1912.05559>`.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ epsilon: float,
27
+ alpha: float,
28
+ num_shots: int,
29
+ sample_callable: Callable[[int, int], int],
30
+ ) -> None:
31
+ """
32
+ Parameters:
33
+ epsilon: Target accuracy.
34
+ alpha: Specifies the confidence level (1 - alpha).
35
+ num_shots: The maximum number of shots in each iteration.
36
+ sample_callable: A callable which gets k and num_shots, and returns the count of good states for $Q^kA$
37
+ (states with 1 in the last qubit).
38
+ This callable is responsible for the quantum execution and the results parsing.
39
+ """
40
+ self._epsilon = epsilon
41
+ self._alpha = alpha
42
+ self._num_shots = num_shots
43
+ self._sample_callable = sample_callable
44
+
45
+ self.iterations: list[IterationInfo] = []
46
+
47
+ # Prepare initial values (lines 2-6, Algorithm 1, [1])
48
+ self._new_iteration()
49
+ self._current().k = 0
50
+ self._current().is_upper_plane = True
51
+ self._current().confidence_interval = np.array([0, np.pi / 2])
52
+
53
+ self._max_rounds = np.ceil(np.log2(np.pi / (8 * self._epsilon)))
54
+
55
+ def _new_iteration(self) -> None:
56
+ if len(self.iterations) > MAX_ITERATIONS_NUMBER:
57
+ raise RuntimeError(
58
+ f"Maximum number of iterations ({MAX_ITERATIONS_NUMBER}) achieved."
59
+ )
60
+ self.iterations.append(IterationInfo())
61
+
62
+ def _current(self) -> IterationInfo:
63
+ return self.iterations[-1]
64
+
65
+ def _prev(self) -> IterationInfo:
66
+ return self.iterations[-2]
67
+
68
+ def run(self) -> float:
69
+ """
70
+ Execute the estimation algorithm.
71
+ See Algorithm 1, [1].
72
+ """
73
+ while interval_len(self._current().confidence_interval) > 2 * self._epsilon:
74
+ self._new_iteration()
75
+ self._find_next_K()
76
+ self._sample()
77
+ self._calculate_confidence_interval()
78
+
79
+ return self.current_estimation()
80
+
81
+ def current_estimation_confidence_interval(self) -> np.ndarray:
82
+ return np.sin(self._current().confidence_interval) ** 2
83
+
84
+ def current_estimation(self) -> float:
85
+ return self.current_estimation_confidence_interval().mean()
86
+
87
+ def _find_next_K(self, r: int = 2) -> None: # noqa: N802
88
+ self._current().K, self._current().is_upper_plane = self.find_next_K(
89
+ K=self._prev().K,
90
+ is_upper_plane=self._prev().is_upper_plane,
91
+ confidence_interval=self._prev().confidence_interval,
92
+ r=r,
93
+ )
94
+
95
+ @staticmethod
96
+ def find_next_K( # noqa: N802
97
+ K: int, # noqa: N803
98
+ is_upper_plane: bool,
99
+ confidence_interval: np.ndarray,
100
+ r: int = 2,
101
+ ) -> tuple[int, bool]:
102
+ """
103
+ We want to find the largest K (with some lower and upper bounds) such that the K-scaled confidence interval
104
+ lies completely in the upper or lower half planes.
105
+ See Algorithm 2, [1].
106
+ """
107
+ K_max = int(np.pi // interval_len(confidence_interval)) # noqa: N806
108
+ K_max = K_max - (K_max - 2) % 4 # noqa: N806
109
+ K_min = r * K # noqa: N806
110
+
111
+ for K_cand in range(K_max, K_min - 1, -4): # noqa: N806
112
+ scaled_confidence_interval = (K_cand * confidence_interval) % (2 * np.pi)
113
+
114
+ if all(scaled_confidence_interval <= np.pi):
115
+ return K_cand, True
116
+ if all(scaled_confidence_interval >= np.pi):
117
+ return K_cand, False
118
+
119
+ return K, is_upper_plane
120
+
121
+ def _sample(self) -> None:
122
+ """
123
+ Use the external sample callable to get the count of good states for $Q^kA$ (states with 1 in the last qubit).
124
+ Effectively implements line 16, Algorithm 1, [1].
125
+ """
126
+ # To optimize results, the paper's algorithm applies the "no-overshooting condition" which limits
127
+ # the number of shots in each iteration (lines 12-15, Algorithm 1, [1]). As the calculation is not very
128
+ # simple, we currently don't support this and use constant number of shots.
129
+ self._current().num_shots = self._num_shots
130
+
131
+ self._current().good_counts = self._sample_callable(
132
+ self._current().k, self._current().num_shots
133
+ )
134
+
135
+ def _calculate_confidence_interval(self) -> None:
136
+ """
137
+ Calculate the next confidence interval based on the last sample's results.
138
+ Effectively implements lines 17-28, Algorithm 1, [1].
139
+ """
140
+ prob = self._current().good_counts / self._current().num_shots
141
+
142
+ # The paper specifies two possibles confidence interval methods: Clopper-Perason or Chernoff-Hoeffding.
143
+ # We currently support only the latter.
144
+ prob_min, prob_max = self._chernoff_hoeffding(prob)
145
+
146
+ if self._current().is_upper_plane:
147
+ theta = np.arccos(1 - 2 * np.array([prob_min, prob_max]))
148
+ else:
149
+ theta = 2 * np.pi - np.arccos(1 - 2 * np.array([prob_max, prob_min]))
150
+
151
+ scaled_confidence_interval = (
152
+ self._current().K * self._prev().confidence_interval
153
+ )
154
+ number_of_wraps = scaled_confidence_interval // (2 * np.pi)
155
+
156
+ # Sometimes we have edge cases where the lower or upper bound of the scaled interval fall exactly
157
+ # on 2pi*T for some integer T, and the number of wraps might be rounded up or down wrongfuly.
158
+ # To fix it, use the number of wraps of the middle point in the scaled interval.
159
+ if number_of_wraps[0] + 1 == number_of_wraps[1]:
160
+ number_of_wraps_of_middle = np.mean(scaled_confidence_interval) // (
161
+ 2 * np.pi
162
+ )
163
+ number_of_wraps = np.array(
164
+ [number_of_wraps_of_middle, number_of_wraps_of_middle]
165
+ )
166
+
167
+ if number_of_wraps[0] != number_of_wraps[1]:
168
+ raise RuntimeError(
169
+ f"Number of wraps of the lower and upper bounds should be equal, got {number_of_wraps}"
170
+ )
171
+
172
+ self._current().confidence_interval = (
173
+ 2 * np.pi * number_of_wraps + theta
174
+ ) / self._current().K
175
+
176
+ def _chernoff_hoeffding(self, prob: float) -> tuple[float, float]:
177
+ """
178
+ The Chernoff-Hoeffding confidence interval method.
179
+ Effectively implements lines 20-22, Algorithm 1, [1].
180
+ """
181
+ epsilon = np.sqrt(
182
+ np.log(2 * self._max_rounds / self._alpha)
183
+ / (2 * self._accumulated_num_shots())
184
+ )
185
+ return max(0, prob - epsilon), min(1, prob + epsilon)
186
+
187
+ def _accumulated_num_shots(self) -> int:
188
+ num_shots = 0
189
+ for iteration in reversed(self.iterations):
190
+ if iteration.K == self._current().K:
191
+ num_shots += iteration.num_shots
192
+ else:
193
+ break
194
+ return num_shots
195
+
196
+
197
+ @dataclass(init=False, repr=False, eq=False)
198
+ class IterationInfo:
199
+ """
200
+ The information stored on each iteration of IQAE.
201
+ """
202
+
203
+ K: int # K = 4k + 2 where k is the power of Q on each iteration
204
+ is_upper_plane: bool # Wheter the scaled confidence interval is in the upper or lower half plane (in the paper: "up")
205
+ confidence_interval: (
206
+ np.ndarray
207
+ ) # The current confidence interval (in the paper: "(theta_l, theta_u)")
208
+
209
+ good_counts: int
210
+ num_shots: int = 0
211
+
212
+ @property
213
+ def k(self) -> int:
214
+ return (self.K - 2) // 4
215
+
216
+ @k.setter
217
+ def k(self, k: int) -> None:
218
+ self.K = 4 * k + 2
219
+
220
+
221
+ def interval_len(interval: np.ndarray) -> float:
222
+ return interval[1] - interval[0]
@@ -0,0 +1,45 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+ from classiq.interface.executor.result import ExecutionDetails
4
+ from classiq.interface.generator.functions.classical_type import QmodPyObject
5
+ from classiq.interface.helpers.versioned_model import VersionedModel
6
+
7
+
8
+ class IQAEIterationData(BaseModel):
9
+ """
10
+ Handles the data storage for a single iteration of the Iterative Quantum Amplitude
11
+ Estimation algorithm.
12
+
13
+ This class is intended to represent the results and state of a single Grover iteration
14
+ of the IQAE process.
15
+
16
+ Attributes:
17
+ grover_iterations (int): The iteration number of Grover's algorithm.
18
+ sample_results (ExecutionDetails): The `ExecutionDetails` of Grover iteration. See ExecutionDetails.
19
+ """
20
+
21
+ grover_iterations: int
22
+ sample_results: ExecutionDetails
23
+
24
+
25
+ class IQAEResult(VersionedModel, QmodPyObject):
26
+ """
27
+ Represents the result of an Iterative Quantum Amplitude Estimation (IQAE)
28
+ process.
29
+
30
+ This class encapsulates the output of the IQAE algorithm, including the
31
+ estimated value, confidence interval, intermediate iteration data, and
32
+ any warnings generated during the computation.
33
+
34
+ Attributes:
35
+ estimation (float): Estimation of the amplitude.
36
+ confidence_interval (list[float]): The interval in which the amplitude is within, with a probability equal to epsilon.
37
+ iterations_data (list[IQAEIterationData]): List of `IQAEIterationData` of each Grover iteration.
38
+ See IQAEIterationData.
39
+ warnings (list[str]): List of warnings generated during the IQAE process of each Grover iteration.
40
+ """
41
+
42
+ estimation: float
43
+ confidence_interval: list[float] = Field(min_length=2, max_length=2)
44
+ iterations_data: list[IQAEIterationData]
45
+ warnings: list[str]
@@ -24,6 +24,7 @@ class FunctionDebugInfo(BaseModel):
24
24
  statement_type: Union[StatementType, None] = None
25
25
  is_inverse: bool = Field(default=False)
26
26
  release_by_inverse: bool = Field(default=False)
27
+ control_variable: Optional[str] = Field(default=None)
27
28
  port_to_passed_variable_map: dict[str, str] = Field(default_factory=dict)
28
29
  node: Optional[ConcreteQuantumStatement] = None
29
30
 
@@ -85,6 +86,8 @@ def get_back_refs(
85
86
  # For backwards compatibility, we make sure that the back_ref is not a block
86
87
  # Remove this check when we start saving blocks in the debug info collection.
87
88
  assert not isinstance(node, Block)
89
+ if len(back_refs) > 0 and node.back_ref == back_refs[0].back_ref:
90
+ break
88
91
  back_refs.insert(0, node)
89
92
  if node.back_ref is None:
90
93
  break
@@ -3,8 +3,8 @@ from typing import Annotated, Any, Literal, Union
3
3
  from pydantic import BaseModel, ConfigDict, Field
4
4
  from typing_extensions import TypeAlias
5
5
 
6
+ from classiq.interface.applications.iqae.iqae_result import IQAEResult
6
7
  from classiq.interface.enum_utils import StrEnum
7
- from classiq.interface.executor.iqae_result import IQAEResult
8
8
  from classiq.interface.executor.result import (
9
9
  EstimationResult,
10
10
  EstimationResults,
@@ -29,7 +29,7 @@ class UserBudgets(VersionedModel):
29
29
  def format_row(
30
30
  provider: str, available: float, used: float, currency: str
31
31
  ) -> str:
32
- return f"| {provider:<20} | {available:<18.0f} | {used:<18.0f} | {currency:<8} |"
32
+ return f"| {provider:<20} | {available:<18.3f} | {used:<18.3f} | {currency:<8} |"
33
33
 
34
34
  table_data: dict = defaultdict(
35
35
  lambda: {"used": 0.0, "available": 0.0, "currency": "USD"}
@@ -1,5 +1,5 @@
1
1
  import inspect
2
- from typing import Any
2
+ from typing import Any, NoReturn
3
3
 
4
4
  import sympy
5
5
 
@@ -37,5 +37,11 @@ class AnyClassicalValue(sympy.Symbol):
37
37
  return super().__getattribute__(attr)
38
38
  return AnyClassicalValue(f"get_field({self}, '{attr}')")
39
39
 
40
- def __len__(self) -> "AnyClassicalValue":
41
- return self.len
40
+ def __len__(self) -> NoReturn:
41
+ raise TypeError("object of type 'AnyClassicalValue' has no len()")
42
+
43
+ def __iter__(self) -> NoReturn:
44
+ raise TypeError("'AnyClassicalValue' object is not iterable")
45
+
46
+ def __bool__(self) -> bool:
47
+ return True
@@ -145,6 +145,8 @@ class StatementType(StrEnum):
145
145
  POWER = "power"
146
146
  INVERT = "invert"
147
147
  WITHIN_APPLY = "within apply"
148
+ WITHIN = "within"
149
+ APPLY = "apply"
148
150
  ASSIGN = "assign"
149
151
  ASSIGN_AMPLITUDE = "assign amplitude"
150
152
  PHASE = "phase"
@@ -161,6 +163,9 @@ STATEMENTS_NAME: dict[str, StatementType] = {
161
163
  "Power": StatementType.POWER,
162
164
  "Invert": StatementType.INVERT,
163
165
  "WithinApply": StatementType.WITHIN_APPLY,
166
+ "Compute": StatementType.WITHIN,
167
+ "Action": StatementType.APPLY,
168
+ "Uncompute": StatementType.WITHIN,
164
169
  ArithmeticOperationKind.Assignment.value: StatementType.ASSIGN,
165
170
  "InplaceBinaryOperation": StatementType.ASSIGN,
166
171
  "AmplitudeLoadingOperation": StatementType.ASSIGN_AMPLITUDE,
@@ -176,6 +181,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
176
181
  children: list["FunctionDebugInfoInterface"]
177
182
  relative_qubits: tuple[int, ...]
178
183
  absolute_qubits: Optional[tuple[int, ...]] = Field(default=None)
184
+ control_variable: Optional[str] = Field(default=None)
179
185
  is_basis_gate: Optional[bool] = Field(default=None)
180
186
  is_inverse: bool = Field(default=False)
181
187
  is_unitary: bool = Field(default=True, exclude=True)
@@ -205,14 +211,21 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
205
211
 
206
212
  if isinstance(back_ref, QuantumFunctionCall):
207
213
  name = generate_original_function_name(back_ref.func_name)
208
- if part_match := PART_SUFFIX_REGEX.match(generated_name):
209
- name += f" [{part_match.group(1)}]"
210
- return name.removeprefix(ARITH_ENGINE_PREFIX)
214
+ return self.add_suffix_from_generated_name(generated_name, name)
211
215
 
212
216
  statement_kind: str = back_ref.kind
213
217
  if isinstance(back_ref, ArithmeticOperation):
214
218
  statement_kind = back_ref.operation_kind.value
215
- return STATEMENTS_NAME[statement_kind]
219
+ return self.add_suffix_from_generated_name(
220
+ generated_name, STATEMENTS_NAME[statement_kind]
221
+ )
222
+
223
+ def add_suffix_from_generated_name(self, generated_name: str, name: str) -> str:
224
+ if part_match := PART_SUFFIX_REGEX.match(generated_name):
225
+ suffix = f" [{part_match.group(1)}]"
226
+ else:
227
+ suffix = ""
228
+ return f"{name}{suffix}"
216
229
 
217
230
  @property
218
231
  def first_back_ref(self) -> Optional[ConcreteQuantumStatement]:
@@ -347,9 +360,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
347
360
  if role is RegisterRole.INPUT:
348
361
  return RegisterRole.OUTPUT
349
362
  if role is RegisterRole.EXPLICIT_ZERO_INPUT or role is RegisterRole.ZERO_INPUT:
350
- if self.release_by_inverse:
351
- return RegisterRole.ZERO_OUTPUT
352
- return RegisterRole.OUTPUT
363
+ return RegisterRole.ZERO_OUTPUT
353
364
  if role is RegisterRole.AUXILIARY:
354
365
  return RegisterRole.AUXILIARY
355
366
  if role is RegisterRole.OUTPUT or role is RegisterRole.GARBAGE_OUTPUT:
@@ -15,6 +15,7 @@ from classiq.interface.helpers.versioned_model import VersionedModel
15
15
 
16
16
  class OperationType(StrEnum):
17
17
  REGULAR = "REGULAR"
18
+ INVISIBLE = "INVISIBLE"
18
19
  ALLOCATE = "ALLOCATE"
19
20
  FREE = "FREE"
20
21
  BIND = "BIND"
@@ -124,6 +125,7 @@ class Operation(pydantic.BaseModel):
124
125
  )
125
126
  is_daggered: bool = pydantic.Field(default=False)
126
127
  expanded: bool = pydantic.Field(default=False)
128
+ show_expanded_label: bool = pydantic.Field(default=False)
127
129
 
128
130
 
129
131
  class ProgramVisualModel(VersionedModel):