classiq 0.66.1__py3-none-any.whl → 0.68.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 (89) hide show
  1. classiq/_internals/api_wrapper.py +5 -1
  2. classiq/_internals/async_utils.py +1 -1
  3. classiq/_internals/authentication/password_manager.py +1 -1
  4. classiq/_internals/client.py +1 -1
  5. classiq/applications/finance/finance_model_constructor.py +9 -0
  6. classiq/applications/grover/grover_model_constructor.py +10 -0
  7. classiq/applications/qnn/qlayer.py +8 -2
  8. classiq/applications/qsvm/qsvm_model_constructor.py +9 -0
  9. classiq/execution/execution_session.py +7 -3
  10. classiq/executor.py +7 -2
  11. classiq/interface/_version.py +1 -1
  12. classiq/interface/ast_node.py +1 -1
  13. classiq/interface/chemistry/operator.py +1 -1
  14. classiq/interface/debug_info/debug_info.py +27 -0
  15. classiq/interface/exceptions.py +2 -5
  16. classiq/interface/generator/arith/argument_utils.py +1 -1
  17. classiq/interface/generator/arith/arithmetic.py +99 -2
  18. classiq/interface/generator/arith/arithmetic_expression_parser.py +1 -1
  19. classiq/interface/generator/arith/binary_ops.py +3 -0
  20. classiq/interface/generator/function_param_list_without_self_reference.py +2 -0
  21. classiq/interface/generator/functions/type_name.py +2 -2
  22. classiq/interface/generator/generated_circuit_data.py +38 -3
  23. classiq/interface/generator/hardware_efficient_ansatz.py +1 -1
  24. classiq/interface/generator/hva.py +1 -1
  25. classiq/interface/generator/model/preferences/preferences.py +8 -1
  26. classiq/interface/generator/quantum_program.py +18 -1
  27. classiq/interface/generator/reset.py +14 -0
  28. classiq/interface/generator/types/enum_declaration.py +33 -2
  29. classiq/interface/generator/ucc.py +1 -1
  30. classiq/interface/interface_version.py +1 -1
  31. classiq/interface/model/classical_if.py +2 -2
  32. classiq/interface/model/control.py +2 -2
  33. classiq/interface/model/invert.py +2 -2
  34. classiq/interface/model/power.py +2 -2
  35. classiq/interface/model/quantum_function_call.py +4 -0
  36. classiq/interface/model/quantum_statement.py +13 -0
  37. classiq/interface/model/repeat.py +2 -2
  38. classiq/interface/model/statement_block.py +1 -1
  39. classiq/interface/model/within_apply_operation.py +2 -2
  40. classiq/model_expansions/atomic_expression_functions_defs.py +2 -1
  41. classiq/model_expansions/capturing/captured_vars.py +184 -54
  42. classiq/model_expansions/closure.py +6 -3
  43. classiq/model_expansions/evaluators/control.py +14 -38
  44. classiq/model_expansions/function_builder.py +19 -14
  45. classiq/model_expansions/generative_functions.py +7 -11
  46. classiq/model_expansions/interpreters/base_interpreter.py +14 -5
  47. classiq/model_expansions/interpreters/generative_interpreter.py +87 -26
  48. classiq/model_expansions/quantum_operations/allocate.py +9 -3
  49. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -0
  50. classiq/model_expansions/quantum_operations/bind.py +67 -14
  51. classiq/model_expansions/quantum_operations/block_evaluator.py +76 -0
  52. classiq/model_expansions/quantum_operations/call_emitter.py +79 -10
  53. classiq/model_expansions/quantum_operations/classicalif.py +10 -6
  54. classiq/model_expansions/quantum_operations/composite_emitter.py +27 -0
  55. classiq/model_expansions/quantum_operations/emitter.py +24 -3
  56. classiq/model_expansions/quantum_operations/expression_evaluator.py +33 -0
  57. classiq/model_expansions/quantum_operations/handle_evaluator.py +28 -0
  58. classiq/model_expansions/quantum_operations/quantum_function_call.py +3 -2
  59. classiq/model_expansions/quantum_operations/repeat.py +9 -3
  60. classiq/model_expansions/quantum_operations/variable_decleration.py +13 -2
  61. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +1 -1
  62. classiq/open_library/functions/__init__.py +1 -2
  63. classiq/open_library/functions/amplitude_amplification.py +12 -9
  64. classiq/open_library/functions/discrete_sine_cosine_transform.py +16 -13
  65. classiq/open_library/functions/grover.py +11 -15
  66. classiq/open_library/functions/modular_exponentiation.py +7 -13
  67. classiq/open_library/functions/state_preparation.py +16 -17
  68. classiq/open_library/functions/swap_test.py +1 -1
  69. classiq/open_library/functions/utility_functions.py +10 -2
  70. classiq/qmod/builtins/functions/__init__.py +3 -0
  71. classiq/qmod/builtins/functions/chemistry.py +6 -38
  72. classiq/qmod/builtins/functions/mid_circuit_measurement.py +15 -0
  73. classiq/qmod/quantum_expandable.py +30 -6
  74. classiq/qmod/quantum_function.py +4 -0
  75. classiq/qmod/semantics/annotation/call_annotation.py +8 -2
  76. classiq/qmod/semantics/annotation/model_annotation.py +9 -0
  77. classiq/qmod/semantics/error_manager.py +0 -6
  78. classiq/qmod/semantics/static_semantics_visitor.py +0 -347
  79. classiq/qmod/semantics/validation/types_validation.py +1 -1
  80. classiq/qmod/symbolic.py +2 -1
  81. classiq/qmod/utilities.py +2 -1
  82. classiq/qmod/write_qmod.py +10 -7
  83. classiq/synthesis.py +20 -7
  84. {classiq-0.66.1.dist-info → classiq-0.68.0.dist-info}/METADATA +1 -1
  85. {classiq-0.66.1.dist-info → classiq-0.68.0.dist-info}/RECORD +86 -81
  86. classiq/model_expansions/quantum_operations/shallow_emitter.py +0 -166
  87. classiq/qmod/semantics/validation/func_call_validation.py +0 -99
  88. classiq/qmod/semantics/validation/handle_validation.py +0 -85
  89. {classiq-0.66.1.dist-info → classiq-0.68.0.dist-info}/WHEEL +0 -0
@@ -3,6 +3,7 @@ from typing import Any, Optional, Protocol, TypeVar
3
3
 
4
4
  import httpx
5
5
  import pydantic
6
+ from pydantic.main import IncEx
6
7
 
7
8
  import classiq.interface.executor.execution_result
8
9
  import classiq.interface.pyomo_extension
@@ -72,11 +73,12 @@ class ApiWrapper:
72
73
  model: pydantic.BaseModel,
73
74
  use_versioned_url: bool = True,
74
75
  http_client: Optional[httpx.AsyncClient] = None,
76
+ exclude: Optional[IncEx] = None,
75
77
  ) -> dict:
76
78
  # TODO: we can't use model.dict() - it doesn't serialize complex class.
77
79
  # This was added because JSON serializer doesn't serialize complex type, and pydantic does.
78
80
  # We should add support for smarter json serialization.
79
- body = json.loads(model.model_dump_json())
81
+ body = json.loads(model.model_dump_json(exclude=exclude))
80
82
  return await cls._call_task(
81
83
  http_method,
82
84
  url,
@@ -135,6 +137,7 @@ class ApiWrapper:
135
137
  url=routes.EXECUTION_SESSIONS_PREFIX,
136
138
  model=circuit,
137
139
  http_client=http_client,
140
+ exclude={"debug_info"},
138
141
  )
139
142
  return raw_result["id"]
140
143
 
@@ -164,6 +167,7 @@ class ApiWrapper:
164
167
  url=routes.CONVERSION_GENERATED_CIRCUIT_TO_EXECUTION_INPUT_FULL,
165
168
  model=circuit,
166
169
  http_client=http_client,
170
+ exclude={"debug_info"},
167
171
  )
168
172
 
169
173
  @classmethod
@@ -58,7 +58,7 @@ def enable_jupyter_notebook() -> None:
58
58
 
59
59
 
60
60
  def _make_iterable_interval(
61
- interval_sec: Union[SupportsFloat, Iterable[SupportsFloat]]
61
+ interval_sec: Union[SupportsFloat, Iterable[SupportsFloat]],
62
62
  ) -> Iterable[float]:
63
63
  if isinstance(interval_sec, Iterable):
64
64
  return map(float, interval_sec)
@@ -117,7 +117,7 @@ class DummyPasswordManager(PasswordManager):
117
117
 
118
118
  class FilePasswordManager(PasswordManager):
119
119
  _CLASSIQ_CREDENTIALS_FILE_PATH: str = "{}/.classiq-credentials".format(
120
- os.getenv("HOME")
120
+ os.getenv("CLASSIQ_DIR", os.getenv("HOME"))
121
121
  )
122
122
 
123
123
  def __init__(self) -> None:
@@ -89,7 +89,7 @@ P = ParamSpec("P")
89
89
 
90
90
 
91
91
  def try_again_on_failure(
92
- func: Callable[P, Awaitable[Ret]]
92
+ func: Callable[P, Awaitable[Ret]],
93
93
  ) -> Callable[P, Awaitable[Ret]]:
94
94
  def check_approved_api_error(error_message: str) -> bool:
95
95
  for approved_api_error in APPROVED_API_ERROR_MESSAGES_FOR_RESTART:
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from math import floor, log
2
3
  from typing import Union
3
4
 
@@ -32,6 +33,14 @@ def construct_finance_model(
32
33
  finance_function_input: FinanceFunctionInput,
33
34
  phase_port_size: int,
34
35
  ) -> SerializedModel:
36
+ warnings.warn(
37
+ "Function 'construct_finance_model' has been deprecated and will no longer"
38
+ "be supported starting on 03/02/2025 the earliest\nHint: It is now possible to "
39
+ "implement Option Pricing in pure Qmod. For example, see the Option Pricing notebook on the "
40
+ "Classiq library at https://github.com/Classiq/classiq-library/blob/main/applications/finance/option_pricing/option_pricing.ipynb",
41
+ category=DeprecationWarning,
42
+ stacklevel=2,
43
+ )
35
44
  if isinstance(finance_model_input, LogNormalModelInput):
36
45
  finance_model = f"struct_literal(LogNormalModel, num_qubits={finance_model_input.num_qubits}, mu={finance_model_input.mu}, sigma={finance_model_input.sigma})"
37
46
  finance_function = "log_normal_finance"
@@ -1,3 +1,5 @@
1
+ import warnings
2
+
1
3
  from classiq.interface.generator.expressions.expression import Expression
2
4
  from classiq.interface.generator.functions.port_declaration import (
3
5
  PortDeclarationDirection,
@@ -90,6 +92,14 @@ def construct_grover_model(
90
92
  expression: str,
91
93
  num_reps: int = 1,
92
94
  ) -> SerializedModel:
95
+ warnings.warn(
96
+ "Function 'construct_grover_model' has been deprecated and will no longer"
97
+ "be supported starting on 03/02/2025 the earliest\nHint: It is now possible to "
98
+ "implement the Grover algorithm in pure Qmod. For example, see the Grover notebook on the "
99
+ "Classiq library at https://github.com/Classiq/classiq-library/blob/main/algorithms/grover/3_sat_grover/3_sat_grover.ipynb",
100
+ category=DeprecationWarning,
101
+ stacklevel=2,
102
+ )
93
103
  predicate_port_decls = grover_main_port_declarations(
94
104
  definitions, PortDeclarationDirection.Inout
95
105
  )
@@ -244,9 +244,11 @@ class QLayer(nn.Module):
244
244
 
245
245
  self.weight = Parameter(value)
246
246
 
247
- @staticmethod
248
- def _make_execute(quantum_program: SerializedQuantumProgram) -> ExecuteFunction:
247
+ def _make_execute(
248
+ self, quantum_program: SerializedQuantumProgram
249
+ ) -> ExecuteFunction:
249
250
  session = ExecutionSession(quantum_program)
251
+ self._session = session
250
252
 
251
253
  def execute(_ignored: Any, arguments: MultipleArguments) -> ResultsCollection:
252
254
  result: ResultsCollection = []
@@ -266,3 +268,7 @@ class QLayer(nn.Module):
266
268
  self._post_process,
267
269
  self._epsilon,
268
270
  )
271
+
272
+ def _cleanup(self) -> None:
273
+ if hasattr(self, "_session"):
274
+ self._session.close()
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from typing import Any
2
3
 
3
4
  from classiq.interface.applications.qsvm import DataList, LabelsInt
@@ -92,6 +93,14 @@ def construct_qsvm_model(
92
93
  feature_map_function_name: str,
93
94
  **kwargs: Any,
94
95
  ) -> SerializedModel:
96
+ warnings.warn(
97
+ "Function 'construct_qsvm_model' has been deprecated and will no longer"
98
+ "be supported starting on 03/02/2025 the earliest\nHint: It is now possible to "
99
+ "implement QSVM in pure Qmod. For example, see the QSVM notebook on the "
100
+ "Classiq library at https://github.com/Classiq/classiq-library/blob/main/algorithms/qml/qsvm/qsvm.ipynb",
101
+ category=DeprecationWarning,
102
+ stacklevel=2,
103
+ )
95
104
  qsvm_qmod = Model(
96
105
  functions=[
97
106
  NativeFunctionDefinition(
@@ -1,4 +1,3 @@
1
- import json
2
1
  import random
3
2
  from types import TracebackType
4
3
  from typing import Callable, Optional, Union, cast
@@ -16,7 +15,10 @@ from classiq.interface.executor.result import (
16
15
  )
17
16
  from classiq.interface.generator.arith import number_utils
18
17
  from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
19
- from classiq.interface.generator.quantum_program import QuantumProgram
18
+ from classiq.interface.generator.quantum_program import (
19
+ OMIT_DEBUG_INFO_FLAG,
20
+ QuantumProgram,
21
+ )
20
22
  from classiq.interface.model.quantum_type import QuantumBit, QuantumNumeric
21
23
 
22
24
  from classiq._internals import async_utils
@@ -46,7 +48,9 @@ def _deserialize_program(program: Program) -> QuantumProgram:
46
48
  return (
47
49
  program
48
50
  if isinstance(program, QuantumProgram)
49
- else QuantumProgram.model_validate(json.loads(program))
51
+ else QuantumProgram.model_validate_json(
52
+ program, context={OMIT_DEBUG_INFO_FLAG: True}
53
+ )
50
54
  )
51
55
 
52
56
 
classiq/executor.py CHANGED
@@ -10,7 +10,10 @@ from classiq.interface.executor.execution_preferences import ExecutionPreference
10
10
  from classiq.interface.executor.quantum_code import QuantumCode
11
11
  from classiq.interface.executor.quantum_instruction_set import QuantumInstructionSet
12
12
  from classiq.interface.executor.result import ExecutionDetails
13
- from classiq.interface.generator.quantum_program import QuantumProgram
13
+ from classiq.interface.generator.quantum_program import (
14
+ OMIT_DEBUG_INFO_FLAG,
15
+ QuantumProgram,
16
+ )
14
17
 
15
18
  from classiq._internals import async_utils
16
19
  from classiq._internals.api_wrapper import ApiWrapper
@@ -27,7 +30,9 @@ BackendPreferencesAndResult: TypeAlias = tuple[
27
30
  def _parse_serialized_qprog(
28
31
  quantum_program: SerializedQuantumProgram,
29
32
  ) -> QuantumProgram:
30
- return QuantumProgram.model_validate_json(quantum_program)
33
+ return QuantumProgram.model_validate_json(
34
+ quantum_program, context={OMIT_DEBUG_INFO_FLAG: True}
35
+ )
31
36
 
32
37
 
33
38
  async def execute_async(quantum_program: SerializedQuantumProgram) -> ExecutionJob:
@@ -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.66.1'
6
+ SEMVER_VERSION = '0.68.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -20,7 +20,7 @@ class ASTNode(HashablePydanticBaseModel):
20
20
  return self
21
21
 
22
22
 
23
- def without_statement_blocks(
23
+ def reset_lists(
24
24
  ast_node: ASTNodeType, statement_block_fields: list[str]
25
25
  ) -> ASTNodeType:
26
26
  return ast_node.model_copy(update={field: [] for field in statement_block_fields})
@@ -69,7 +69,7 @@ class PauliOperator(HashablePydanticBaseModel, VersionedModel):
69
69
 
70
70
  @staticmethod
71
71
  def _validate_monomial_coefficient(
72
- coeff: Union[sympy.Expr, ParameterComplexType]
72
+ coeff: Union[sympy.Expr, ParameterComplexType],
73
73
  ) -> ParameterComplexType:
74
74
  if isinstance(coeff, str):
75
75
  validate_expression_str(coeff)
@@ -32,6 +32,7 @@ class FunctionDebugInfo(BaseModel):
32
32
  statement_type: Union[StatementType, None] = None
33
33
  is_allocate_or_free: bool = Field(default=False)
34
34
  is_inverse: bool = Field(default=False)
35
+ release_by_inverse: bool = Field(default=False)
35
36
  port_to_passed_variable_map: dict[str, str] = Field(default_factory=dict)
36
37
  node: Optional[ConcreteQuantumStatement] = None
37
38
 
@@ -86,3 +87,29 @@ class DebugInfoCollection(BaseModel):
86
87
  if (debug_info := self.get(key)) is None:
87
88
  return None
88
89
  return self.blackbox_data.get(debug_info.name)
90
+
91
+
92
+ def get_back_refs(
93
+ debug_info: FunctionDebugInfo, collected_debug_info: DebugInfoCollection
94
+ ) -> list[ConcreteQuantumStatement]:
95
+ back_refs: list[ConcreteQuantumStatement] = []
96
+ while (node := debug_info.node) is not None:
97
+ back_refs.insert(0, node)
98
+ if node.back_ref is None:
99
+ break
100
+ next_debug_info = collected_debug_info.get(node.back_ref)
101
+ if next_debug_info is None:
102
+ break
103
+ debug_info = next_debug_info
104
+ return back_refs
105
+
106
+
107
+ def new_function_debug_info_by_node(
108
+ node: ConcreteQuantumStatement,
109
+ ) -> FunctionDebugInfo:
110
+ return FunctionDebugInfo(
111
+ name="",
112
+ parameters=dict(),
113
+ level=OperationLevel.QMOD_STATEMENT,
114
+ node=node._as_back_ref(),
115
+ )
@@ -6,7 +6,8 @@ _logger = logging.getLogger(__name__)
6
6
 
7
7
  CLASSIQ_SLACK_COMMUNITY_LINK = (
8
8
  "\nIf you need further assistance, please reach out on our Community Slack channel "
9
- "at: https://short.classiq.io/join-slack"
9
+ "at: https://short.classiq.io/join-slack or open a support ticket at: "
10
+ "https://classiq-community.freshdesk.com/support/tickets/new"
10
11
  )
11
12
 
12
13
 
@@ -177,10 +178,6 @@ class ClassiqExecutorInvalidHamiltonianError(ClassiqCombOptError):
177
178
  super().__init__("Invalid hamiltonian")
178
179
 
179
180
 
180
- class ClassiqSemanticError(ClassiqError):
181
- pass
182
-
183
-
184
181
  class ClassiqDeprecationWarning(FutureWarning):
185
182
  pass
186
183
 
@@ -91,7 +91,7 @@ def unsigned_integer_interpretation(
91
91
  int_val <<= fraction_digits_diff
92
92
 
93
93
  # extend sign bit
94
- if int(value) < 0:
94
+ if value < 0:
95
95
  bin_val = number_utils.binary_string(int_val)
96
96
  bin_val += "1" * (register.size - len(bin_val))
97
97
  int_val = number_utils.binary_to_int(bin_val[::-1])
@@ -1,4 +1,5 @@
1
- from typing import Any, Final, Optional
1
+ import ast
2
+ from typing import Any, Final, Optional, cast
2
3
 
3
4
  import networkx as nx
4
5
  import pydantic
@@ -32,6 +33,8 @@ ARITHMETIC_EXPRESSION_TARGET_NAME: Final[str] = "arithmetic_target"
32
33
  ARITHMETIC_EXPRESSION_RESULT_NAME: Final[str] = "expression_result"
33
34
  ARITHMETIC_EXPRESSION_GARBAGE_NAME: Final[str] = "expression_garbage"
34
35
 
36
+ TARGET_ASSIGNMENT_ERROR = "Expression does not support target assignment"
37
+
35
38
 
36
39
  def is_zero(expr: str) -> bool:
37
40
  return is_constant(expr) and float(expr) == 0
@@ -60,7 +63,7 @@ class Arithmetic(ArithmeticExpressionABC):
60
63
  degree or operation_allows_target(id2op(node))
61
64
  for node, degree in graph.out_degree
62
65
  ):
63
- raise ClassiqValueError("Expression does not support target assignment")
66
+ raise ClassiqValueError(TARGET_ASSIGNMENT_ERROR)
64
67
 
65
68
  def _create_ios(self) -> None:
66
69
  self._inputs = {
@@ -93,6 +96,9 @@ def get_arithmetic_params(
93
96
  machine_precision: int,
94
97
  enable_target: bool = False,
95
98
  ) -> Arithmetic:
99
+ expr_str, var_types = _substitute_quantum_subscripts(
100
+ expr_str, var_types, machine_precision
101
+ )
96
102
  return Arithmetic(
97
103
  expression=expr_str,
98
104
  definitions={
@@ -119,3 +125,94 @@ def compute_arithmetic_result_type(
119
125
  return register_info_to_quantum_type(
120
126
  arith_param.outputs[ARITHMETIC_EXPRESSION_RESULT_NAME]
121
127
  )
128
+
129
+
130
+ def aggregate_numeric_types(
131
+ numeric_types: list[QuantumNumeric],
132
+ ) -> RegisterArithmeticInfo:
133
+ if all(
134
+ numeric_type.size_in_bits == 1
135
+ and numeric_type.sign_value
136
+ and numeric_type.fraction_digits_value == 1
137
+ for numeric_type in numeric_types
138
+ ):
139
+ return RegisterArithmeticInfo(size=1, is_signed=True, fraction_places=1)
140
+ int_size = max(
141
+ numeric_type.size_in_bits
142
+ - int(numeric_type.sign_value)
143
+ - numeric_type.fraction_digits_value
144
+ for numeric_type in numeric_types
145
+ )
146
+ is_signed = any(numeric_type.sign_value for numeric_type in numeric_types)
147
+ frac_size = max(
148
+ numeric_type.fraction_digits_value for numeric_type in numeric_types
149
+ )
150
+ total_size = int_size + int(is_signed) + frac_size
151
+ return RegisterArithmeticInfo(
152
+ size=total_size, is_signed=is_signed, fraction_places=frac_size
153
+ )
154
+
155
+
156
+ class _QuantumSubscriptRemover(ast.NodeTransformer):
157
+ def __init__(self, machine_precision: int) -> None:
158
+ self._machine_precision = machine_precision
159
+ self.substitutions_types: dict[str, QuantumNumeric] = {}
160
+
161
+ def visit_Call(self, node: ast.Call) -> ast.expr:
162
+ if not isinstance(node.func, ast.Name) or node.func.id != "Piecewise":
163
+ return node
164
+ items = [
165
+ cast(float, cast(ast.Num, cast(ast.Tuple, arg).elts[0]).value)
166
+ for arg in node.args
167
+ ]
168
+ numeric_types = [
169
+ compute_arithmetic_result_type(str(num), {}, self._machine_precision)
170
+ for num in items
171
+ ]
172
+ unified_numeric_type = register_info_to_quantum_type(
173
+ aggregate_numeric_types(numeric_types)
174
+ )
175
+ substitution_var_name = f"__lut__{len(self.substitutions_types)}__"
176
+ self.substitutions_types[substitution_var_name] = unified_numeric_type
177
+ return ast.Name(id=substitution_var_name)
178
+
179
+
180
+ class _NameCollector(ast.NodeVisitor):
181
+ def __init__(self) -> None:
182
+ self.names: set[str] = set()
183
+
184
+ def visit_Name(self, node: ast.Name) -> None:
185
+ self.names.add(node.id)
186
+
187
+
188
+ def _substitute_quantum_subscripts(
189
+ expr_str: str, var_types: dict[str, QuantumType], machine_precision: int
190
+ ) -> tuple[str, dict[str, QuantumType]]:
191
+ """
192
+ Remove quantum lookup expressions ([1, 2, 3, 4][n]) from an arithmetic expression
193
+ for the purpose of calculating its numeric attributes.
194
+ Each quantum lookup expression is replaced by a numeric value with equivalent
195
+ numeric properties.
196
+
197
+ Args:
198
+ expr_str: arithmetic expression
199
+ var_types: quantum variable type mapping
200
+ machine_precision: global machine precision
201
+
202
+ Returns:
203
+ 1. the reduced expression
204
+ 2. updated type mapping
205
+ """
206
+ expr_ast = ast.parse(expr_str)
207
+ subscript_remover = _QuantumSubscriptRemover(machine_precision)
208
+ expr_ast = subscript_remover.visit(expr_ast)
209
+ var_types_substituted = var_types | subscript_remover.substitutions_types
210
+ expr_str_substituted = ast.unparse(expr_ast)
211
+ names_collector = _NameCollector()
212
+ names_collector.visit(ast.parse(expr_str_substituted))
213
+ var_types_substituted = {
214
+ var_name: var_type
215
+ for var_name, var_type in var_types_substituted.items()
216
+ if var_name in names_collector.names
217
+ }
218
+ return expr_str_substituted, var_types_substituted
@@ -91,7 +91,7 @@ class ExpressionVisitor(ExpressionValidator):
91
91
  class InDegreeLimiter:
92
92
  @staticmethod
93
93
  def _sort_in_edges(
94
- in_edges: Collection[tuple[Node, str]]
94
+ in_edges: Collection[tuple[Node, str]],
95
95
  ) -> list[tuple[Node, str]]:
96
96
  return sorted(
97
97
  in_edges,
@@ -398,6 +398,7 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
398
398
  right_arg=-self.effective_right_arg,
399
399
  output_size=self.output_size,
400
400
  inplace_arg=self.inplace_arg,
401
+ machine_precision=self.machine_precision,
401
402
  )
402
403
  return adder_params.garbage_output_size()
403
404
 
@@ -406,6 +407,7 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
406
407
  output_size=self.negation_output_size,
407
408
  inplace=self.should_inplace_negation,
408
409
  bypass_bounds_validation=True,
410
+ machine_precision=self.machine_precision,
409
411
  )
410
412
  negation_result = negation_params.result_register
411
413
  if self.output_size is None and max(self.effective_right_arg.bounds) > 0:
@@ -425,6 +427,7 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
425
427
  right_arg=negation_result,
426
428
  output_size=self.output_size,
427
429
  inplace_arg=self.arg_to_inplace_adder,
430
+ machine_precision=self.machine_precision,
428
431
  )
429
432
  negation_garbage_size = negation_params.garbage_output_size() * int(
430
433
  not self.should_uncompute_negation
@@ -65,6 +65,7 @@ from classiq.interface.generator.piecewise_linear_amplitude_loading import (
65
65
  from classiq.interface.generator.qft import QFT
66
66
  from classiq.interface.generator.qsvm import QSVMFeatureMap
67
67
  from classiq.interface.generator.randomized_benchmarking import RandomizedBenchmarking
68
+ from classiq.interface.generator.reset import Reset
68
69
  from classiq.interface.generator.standard_gates.standard_gates_param_list import (
69
70
  standard_gate_function_param_library,
70
71
  )
@@ -150,6 +151,7 @@ function_param_library_without_self_reference: FunctionParamLibrary = (
150
151
  PiecewiseLinearRotationAmplitudeLoading,
151
152
  HadamardTransform,
152
153
  Copy,
154
+ Reset,
153
155
  },
154
156
  standard_gate_function_param_library.param_list,
155
157
  oracle_function_param_library.param_list,
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, Literal, Optional
3
3
 
4
4
  import pydantic
5
5
 
6
- from classiq.interface.exceptions import ClassiqInternalError
6
+ from classiq.interface.exceptions import ClassiqExpansionError
7
7
  from classiq.interface.generator.expressions.qmod_qstruct_proxy import QmodQStructProxy
8
8
  from classiq.interface.generator.functions.classical_type import (
9
9
  ClassicalType,
@@ -59,7 +59,7 @@ class TypeName(ClassicalType, QuantumType):
59
59
  @property
60
60
  def fields(self) -> Mapping[str, "ConcreteQuantumType"]:
61
61
  if self._assigned_fields is None:
62
- raise ClassiqInternalError("Fields not set")
62
+ raise ClassiqExpansionError(f"Type {self.name!r} is undefined")
63
63
  return self._assigned_fields
64
64
 
65
65
  @property
@@ -11,6 +11,7 @@ from classiq.interface.generator.register_role import RegisterRole
11
11
  from classiq.interface.generator.synthesis_metadata.synthesis_execution_data import (
12
12
  ExecutionData,
13
13
  )
14
+ from classiq.interface.model.statement_block import StatementBlock
14
15
 
15
16
  from classiq.model_expansions.capturing.mangling_utils import (
16
17
  demangle_capture_name,
@@ -22,7 +23,7 @@ _logger = logging.getLogger(__name__)
22
23
  ParameterName = str
23
24
  IOQubitMapping: TypeAlias = dict[str, tuple[int, ...]]
24
25
 
25
- CLASSIQ_HIERARCHY_SEPARATOR: Literal["."] = "."
26
+ CLASSIQ_HIERARCHY_SEPARATOR: Literal["__"] = "__"
26
27
 
27
28
  VISUALIZATION_HIDE_LIST = [
28
29
  "apply_to_all",
@@ -135,6 +136,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
135
136
  level: OperationLevel = pydantic.Field(default=OperationLevel.UNKNOWN)
136
137
  parameters: list[OperationParameter] = list()
137
138
  port_to_passed_variable_map: dict[str, str] = pydantic.Field(default={})
139
+ release_by_inverse: bool = pydantic.Field(default=False)
140
+ back_refs: StatementBlock = pydantic.Field(default_factory=list)
138
141
 
139
142
  model_config = ConfigDict(extra="allow")
140
143
  # Temporary field to store the override debug info for parallel old/new visualization
@@ -166,7 +169,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
166
169
 
167
170
  @staticmethod
168
171
  def create_parameters_from_dict(
169
- parameters: dict[str, str]
172
+ parameters: dict[str, str],
170
173
  ) -> list[OperationParameter]:
171
174
  return [
172
175
  OperationParameter(label=key, value=value)
@@ -227,14 +230,46 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
227
230
  )
228
231
 
229
232
  def inverse(self) -> "FunctionDebugInfoInterface":
230
- inverted_children = [child.inverse() for child in self.children[::-1]]
233
+ if self.override_debug_info is not None:
234
+ self.override_debug_info = self.override_debug_info.inverse()
235
+ return self
236
+ inverse_generated_function = (
237
+ self.generated_function.model_copy(
238
+ update=dict(registers=self._inverse_registers)
239
+ )
240
+ if self.generated_function
241
+ else None
242
+ )
243
+ inverted_children = [child.inverse() for child in reversed(self.children)]
231
244
  return self.model_copy(
232
245
  update=dict(
233
246
  is_inverse=not self.is_inverse,
234
247
  children=inverted_children,
248
+ generated_function=inverse_generated_function,
235
249
  )
236
250
  )
237
251
 
252
+ @property
253
+ def _inverse_registers(self) -> list[GeneratedRegister]:
254
+ return [
255
+ reg.model_copy(update=dict(role=self._inverse_register_role(reg.role)))
256
+ for reg in self.registers
257
+ ]
258
+
259
+ def _inverse_register_role(self, role: RegisterRole) -> RegisterRole:
260
+ if role is RegisterRole.INPUT:
261
+ return RegisterRole.OUTPUT
262
+ if role is RegisterRole.EXPLICIT_ZERO_INPUT or role is RegisterRole.ZERO_INPUT:
263
+ if self.release_by_inverse:
264
+ return RegisterRole.ZERO_OUTPUT
265
+ return RegisterRole.OUTPUT
266
+ if role is RegisterRole.AUXILIARY:
267
+ return RegisterRole.AUXILIARY
268
+ if role is RegisterRole.OUTPUT or role is RegisterRole.GARBAGE_OUTPUT:
269
+ return RegisterRole.INPUT
270
+ if role is RegisterRole.ZERO_OUTPUT:
271
+ return RegisterRole.ZERO_INPUT
272
+
238
273
 
239
274
  def _get_absolute_from_relative(
240
275
  absolute_qubits: tuple[int, ...], relative_qubits: tuple[int, ...]
@@ -60,7 +60,7 @@ class HardwareEfficientAnsatz(function_params.FunctionParams):
60
60
  description='List of gates for the two qubit gates entangling layer, e.g. ["cx", "cry"]',
61
61
  )
62
62
  parameter_prefix: str = pydantic.Field(
63
- default="param_",
63
+ default="hea_param_",
64
64
  description="Prefix for the generated parameters",
65
65
  )
66
66
 
@@ -17,6 +17,6 @@ class HVA(ChemistryFunctionParams):
17
17
  default=False, description="Whether to evolve the operator naively"
18
18
  )
19
19
  parameter_prefix: str = pydantic.Field(
20
- default="param_",
20
+ default="hva_param_",
21
21
  description="Prefix for the generated parameters",
22
22
  )
@@ -36,7 +36,7 @@ if TYPE_CHECKING:
36
36
  PydanticBackendName = str
37
37
  else:
38
38
  PydanticBackendName = Annotated[
39
- str, Field(strict=True, min_length=1, pattern="^([.A-Za-z0-9_-]*)$")
39
+ str, Field(strict=True, pattern="^([.A-Za-z0-9_-][ .A-Za-z0-9_-]*)$")
40
40
  ]
41
41
 
42
42
 
@@ -262,6 +262,13 @@ class Preferences(pydantic.BaseModel, extra="forbid"):
262
262
 
263
263
  return output_format
264
264
 
265
+ @pydantic.field_validator("backend_name")
266
+ @classmethod
267
+ def validate_backend_name(cls, backend_name: Optional[str]) -> Optional[str]:
268
+ if backend_name is None:
269
+ return backend_name
270
+ return backend_name.rstrip()
271
+
265
272
  @pydantic.model_validator(mode="after")
266
273
  def validate_backend(self) -> Self:
267
274
  backend_name = self.backend_name
@@ -1,9 +1,11 @@
1
1
  import uuid
2
2
  from datetime import datetime, timezone
3
3
  from pathlib import Path
4
- from typing import Optional, Union
4
+ from typing import Any, Optional, Union
5
5
 
6
6
  import pydantic
7
+ from pydantic import model_validator
8
+ from pydantic_core.core_schema import ValidationInfo
7
9
  from typing_extensions import TypeAlias
8
10
 
9
11
  from classiq.interface.exceptions import (
@@ -36,6 +38,8 @@ from classiq.interface.ide.visual_model import CircuitMetrics
36
38
  RegisterName: TypeAlias = str
37
39
  InitialConditions: TypeAlias = dict[RegisterName, int]
38
40
 
41
+ OMIT_DEBUG_INFO_FLAG = "omit_debug_info"
42
+
39
43
 
40
44
  class TranspiledCircuitData(CircuitCodeInterface):
41
45
  depth: int
@@ -71,6 +75,19 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
71
75
  program_id: str = pydantic.Field(default_factory=get_uuid_as_str)
72
76
  execution_primitives_input: Optional[PrimitivesInput] = pydantic.Field(default=None)
73
77
 
78
+ @model_validator(mode="before")
79
+ @classmethod
80
+ def remove_debug_info(
81
+ cls, data: dict[str, Any], info: ValidationInfo
82
+ ) -> dict[str, Any]:
83
+ if (
84
+ isinstance(data, dict)
85
+ and info.context is not None
86
+ and info.context.get(OMIT_DEBUG_INFO_FLAG, False)
87
+ ):
88
+ data.pop("debug_info", None)
89
+ return data
90
+
74
91
  def _hardware_agnostic_program_code(self) -> CodeAndSyntax:
75
92
  circuit_code = self.program_circuit.get_code_by_priority()
76
93
  if circuit_code is not None: