classiq 0.56.1__py3-none-any.whl → 0.58.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 (46) hide show
  1. classiq/analyzer/show_interactive_hack.py +16 -4
  2. classiq/applications/combinatorial_helpers/encoding_utils.py +1 -0
  3. classiq/applications/combinatorial_helpers/transformations/encoding.py +3 -1
  4. classiq/execution/jobs.py +8 -1
  5. classiq/executor.py +1 -1
  6. classiq/interface/_version.py +1 -1
  7. classiq/interface/backend/backend_preferences.py +27 -5
  8. classiq/interface/backend/pydantic_backend.py +0 -1
  9. classiq/interface/execution/jobs.py +4 -1
  10. classiq/interface/executor/execution_request.py +19 -5
  11. classiq/interface/generator/arith/arithmetic_expression_validator.py +28 -9
  12. classiq/interface/generator/functions/type_name.py +7 -9
  13. classiq/interface/generator/types/builtin_enum_declarations.py +1 -0
  14. classiq/model_expansions/closure.py +24 -6
  15. classiq/model_expansions/evaluators/parameter_types.py +1 -2
  16. classiq/model_expansions/evaluators/quantum_type_utils.py +0 -7
  17. classiq/model_expansions/function_builder.py +13 -0
  18. classiq/model_expansions/interpreter.py +9 -14
  19. classiq/model_expansions/quantum_operations/call_emitter.py +207 -0
  20. classiq/model_expansions/quantum_operations/classicalif.py +2 -2
  21. classiq/model_expansions/quantum_operations/control.py +7 -5
  22. classiq/model_expansions/quantum_operations/emitter.py +1 -186
  23. classiq/model_expansions/quantum_operations/expression_operation.py +26 -189
  24. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +2 -2
  25. classiq/model_expansions/quantum_operations/invert.py +2 -2
  26. classiq/model_expansions/quantum_operations/phase.py +3 -1
  27. classiq/model_expansions/quantum_operations/power.py +2 -2
  28. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +7 -9
  29. classiq/model_expansions/quantum_operations/quantum_function_call.py +2 -2
  30. classiq/model_expansions/quantum_operations/repeat.py +2 -2
  31. classiq/model_expansions/transformers/__init__.py +0 -0
  32. classiq/model_expansions/transformers/var_splitter.py +237 -0
  33. classiq/qmod/builtins/classical_functions.py +1 -0
  34. classiq/qmod/builtins/functions/state_preparation.py +1 -1
  35. classiq/qmod/create_model_function.py +25 -20
  36. classiq/qmod/native/pretty_printer.py +19 -4
  37. classiq/qmod/pretty_print/pretty_printer.py +53 -28
  38. classiq/qmod/qfunc.py +18 -16
  39. classiq/qmod/quantum_function.py +30 -24
  40. classiq/qmod/semantics/qstruct_annotator.py +23 -0
  41. classiq/qmod/semantics/static_semantics_visitor.py +4 -1
  42. classiq/qmod/write_qmod.py +3 -1
  43. classiq/synthesis.py +3 -1
  44. {classiq-0.56.1.dist-info → classiq-0.58.0.dist-info}/METADATA +1 -1
  45. {classiq-0.56.1.dist-info → classiq-0.58.0.dist-info}/RECORD +46 -42
  46. {classiq-0.56.1.dist-info → classiq-0.58.0.dist-info}/WHEEL +0 -0
@@ -10,7 +10,7 @@ from classiq._internals.async_utils import syncify_function
10
10
  from classiq.analyzer.url_utils import circuit_page_uri, client_ide_base_url
11
11
 
12
12
 
13
- async def handle_remote_app(circuit: QuantumProgram, dispaly_url: bool = False) -> None:
13
+ async def handle_remote_app(circuit: QuantumProgram, display_url: bool = True) -> None:
14
14
  if circuit.outputs.get(QuantumFormat.QASM) is None:
15
15
  raise ClassiqAnalyzerVisualizationError(
16
16
  "Missing QASM transpilation: visualization is only supported "
@@ -23,14 +23,26 @@ async def handle_remote_app(circuit: QuantumProgram, dispaly_url: bool = False)
23
23
  circuit_page_uri(circuit_id=circuit_dataid.id, circuit_version=circuit.version),
24
24
  )
25
25
 
26
- if dispaly_url:
26
+ if display_url:
27
27
  print(f"Opening: {app_url}") # noqa: T201
28
28
 
29
29
  webbrowser.open_new_tab(app_url)
30
30
 
31
31
 
32
- async def _show_interactive(self: QuantumProgram, dispaly_url: bool = False) -> None:
33
- await handle_remote_app(self, dispaly_url)
32
+ async def _show_interactive(self: QuantumProgram, display_url: bool = True) -> None:
33
+ """
34
+ Displays the interactive representation of the quantum program in the Classiq IDE.
35
+
36
+ Args:
37
+ self:
38
+ The serialized quantum program to be displayed.
39
+ display_url:
40
+ Whether to print the url
41
+
42
+ Links:
43
+ [Visualization tool](https://docs.classiq.io/latest/reference-manual/analyzer/quantum-program-visualization-tool/)
44
+ """
45
+ await handle_remote_app(self, display_url)
34
46
 
35
47
 
36
48
  QuantumProgram.show = syncify_function(_show_interactive) # type: ignore[attr-defined]
@@ -39,6 +39,7 @@ def is_var_span_power_of_2(var: _GeneralVarData) -> bool:
39
39
 
40
40
  ENCODED_SUFFIX = "_encoded"
41
41
  ONE_HOT_SUFFIX = "_one_hot"
42
+ CONSTRAINT_SUFFIX = "_constraint"
42
43
 
43
44
 
44
45
  def is_obj_encoded(var: _ComponentBase) -> bool:
@@ -85,7 +85,9 @@ class ModelEncoder:
85
85
  encoding_expr = self._get_encoding_expr(var_data, encoding_vars)
86
86
 
87
87
  self._add_expr_constraint(
88
- constraint_name=var_data.name + encoding_utils.ENCODED_SUFFIX,
88
+ constraint_name=var_data.name
89
+ + encoding_utils.ENCODED_SUFFIX
90
+ + encoding_utils.CONSTRAINT_SUFFIX,
89
91
  expr=EqualityExpression(args=[var_data, encoding_expr]),
90
92
  )
91
93
 
classiq/execution/jobs.py CHANGED
@@ -8,7 +8,7 @@ from classiq.interface.exceptions import (
8
8
  ClassiqError,
9
9
  )
10
10
  from classiq.interface.execution.jobs import ExecutionJobDetailsV1
11
- from classiq.interface.executor.execution_request import ExecutionJobDetails
11
+ from classiq.interface.executor.execution_request import ExecutionJobDetails, JobCost
12
12
  from classiq.interface.executor.execution_result import ResultsCollection
13
13
  from classiq.interface.executor.result import (
14
14
  EstimationResult,
@@ -92,6 +92,13 @@ class ExecutionJob:
92
92
  else:
93
93
  return f"{class_name}(name={self.name!r}, id={self.id!r})"
94
94
 
95
+ def cost(self, *, verbose: bool = False) -> Union[str, JobCost]:
96
+ if self._details.cost is None:
97
+ self._details.cost = JobCost()
98
+ if verbose:
99
+ return self._details.cost
100
+ return f"{self._details.cost.total_cost} {self._details.cost.currency_code}"
101
+
95
102
  @classmethod
96
103
  async def from_id_async(cls, id: str) -> "ExecutionJob":
97
104
  details = await ApiWrapper.call_get_execution_job_details(JobID(job_id=id))
classiq/executor.py CHANGED
@@ -46,7 +46,7 @@ def execute(quantum_program: SerializedQuantumProgram) -> ExecutionJob:
46
46
  Returns:
47
47
  ExecutionJob: The result of the execution.
48
48
 
49
- For examples please see [Execution Documentation](https://docs.classiq.io/latest/user-guide/executor/)
49
+ For examples please see [Execution Documentation](https://docs.classiq.io/latest/user-guide/execution/)
50
50
  """
51
51
  return async_utils.run(execute_async(quantum_program))
52
52
 
@@ -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.56.1'
6
+ SEMVER_VERSION = '0.58.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import warnings
3
4
  from collections.abc import Iterable
4
5
  from typing import Any, Optional, Union
5
6
 
@@ -21,7 +22,7 @@ from classiq.interface.backend.quantum_backend_providers import (
21
22
  ProviderTypeVendor,
22
23
  ProviderVendor,
23
24
  )
24
- from classiq.interface.exceptions import ClassiqValueError
25
+ from classiq.interface.exceptions import ClassiqDeprecationWarning, ClassiqValueError
25
26
  from classiq.interface.hardware import Provider
26
27
 
27
28
 
@@ -117,8 +118,8 @@ class AliceBobBackendPreferences(BackendPreferences):
117
118
  average_nb_photons: Optional[float] = pydantic.Field(
118
119
  default=None, description="Average number of photons"
119
120
  )
120
- api_key: pydantic_backend.PydanticAliceBobApiKeyType = pydantic.Field(
121
- ..., description="AliceBob API key"
121
+ api_key: Optional[pydantic_backend.PydanticAliceBobApiKeyType] = pydantic.Field(
122
+ default=None, description="AliceBob API key"
122
123
  )
123
124
 
124
125
  @property
@@ -131,6 +132,17 @@ class AliceBobBackendPreferences(BackendPreferences):
131
132
  }
132
133
  return {k: v for k, v in parameters.items() if v is not None}
133
134
 
135
+ @pydantic.field_validator("api_key", mode="after")
136
+ @classmethod
137
+ def _validate_api_key(cls, api_key: Optional[str]) -> Optional[str]:
138
+ if api_key is not None:
139
+ warnings.warn(
140
+ "API key is no longer required for Alice&Bob backends.",
141
+ ClassiqDeprecationWarning,
142
+ stacklevel=2,
143
+ )
144
+ return api_key
145
+
134
146
 
135
147
  class ClassiqBackendPreferences(BackendPreferences):
136
148
  """
@@ -230,6 +242,7 @@ class IBMBackendPreferences(BackendPreferences):
230
242
  access_token (Optional[str]): The IBM Quantum access token to be used with IBM Quantum hosted backends. Defaults to `None`.
231
243
  provider (IBMBackendProvider): Specifications for identifying a single IBM Quantum provider. Defaults to a new `IBMBackendProvider`.
232
244
  qctrl_api_key (Optional[str]): QCTRL API key to access QCTRL optimization abilities.
245
+ run_through_classiq (bool): Run through Classiq's credentials. Defaults to `False`.
233
246
 
234
247
  See examples in the [IBM Quantum Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/ibm-backends/?h=).
235
248
  """
@@ -250,6 +263,10 @@ class IBMBackendPreferences(BackendPreferences):
250
263
  default=None,
251
264
  description="QCTRL API key to access QCTRL optimization abilities",
252
265
  )
266
+ run_through_classiq: bool = pydantic.Field(
267
+ default=False,
268
+ description="Run through Classiq's credentials",
269
+ )
253
270
 
254
271
 
255
272
  class AzureCredential(BaseSettings):
@@ -341,6 +358,7 @@ class IonqBackendPreferences(BackendPreferences):
341
358
  backend_service_provider (ProviderTypeVendor.IONQ): Indicates the backend service provider as IonQ.
342
359
  api_key (PydanticIonQApiKeyType): The IonQ API key required for accessing IonQ's quantum computing services.
343
360
  error_mitigation (bool): A configuration option to enable or disable error mitigation during execution. Defaults to `False`.
361
+ run_through_classiq (bool): Running through Classiq's credentials while using user's allocated budget.
344
362
 
345
363
  See examples in the [IonQ Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/ionq-backends/?h=).
346
364
  """
@@ -348,13 +366,17 @@ class IonqBackendPreferences(BackendPreferences):
348
366
  backend_service_provider: ProviderTypeVendor.IONQ = pydantic.Field(
349
367
  default=ProviderVendor.IONQ
350
368
  )
351
- api_key: pydantic_backend.PydanticIonQApiKeyType = pydantic.Field(
352
- ..., description="IonQ API key"
369
+ api_key: Optional[pydantic_backend.PydanticIonQApiKeyType] = pydantic.Field(
370
+ default=None, description="IonQ API key"
353
371
  )
354
372
  error_mitigation: bool = pydantic.Field(
355
373
  default=False,
356
374
  description="Error mitigation configuration.",
357
375
  )
376
+ run_through_classiq: bool = pydantic.Field(
377
+ default=False,
378
+ description="Running through Classiq's credentials while using user's allocated budget.",
379
+ )
358
380
 
359
381
 
360
382
  class GCPBackendPreferences(BackendPreferences):
@@ -7,7 +7,6 @@ AZURE_QUANTUM_RESOURCE_ID_REGEX = r"^/subscriptions/([a-fA-F0-9-]*)/resourceGrou
7
7
  _IONQ_API_KEY_LENGTH: int = 32
8
8
  _ALICE_BOB_API_KEY_LENGTH: int = 72
9
9
  INVALID_API_KEY: str = _IONQ_API_KEY_LENGTH * "a"
10
- INVALID_API_KEY_ALICE_BOB: str = _ALICE_BOB_API_KEY_LENGTH * "a"
11
10
  INVALID_EMAIL_OQC: str = "aa@aa.aa"
12
11
  INVALID_PASSWORD_OQC: str = "Aa1!Aa1!"
13
12
 
@@ -1,8 +1,9 @@
1
1
  from datetime import datetime
2
2
  from typing import Optional
3
3
 
4
- from pydantic import BaseModel
4
+ from pydantic import BaseModel, Field
5
5
 
6
+ from classiq.interface.executor.execution_request import JobCost
6
7
  from classiq.interface.jobs import JobStatus
7
8
 
8
9
 
@@ -23,6 +24,8 @@ class ExecutionJobDetailsV1(BaseModel, extra="ignore"):
23
24
 
24
25
  error: Optional[str] = None
25
26
 
27
+ cost: Optional[JobCost] = Field(default=None)
28
+
26
29
 
27
30
  class ExecutionJobsQueryResultsV1(BaseModel, extra="ignore"):
28
31
  results: list[ExecutionJobDetailsV1]
@@ -43,23 +43,37 @@ class QuantumProgramExecutionRequest(ExecutionRequest):
43
43
  execution_payload: QuantumCodeExecution
44
44
 
45
45
 
46
+ class ProviderJobs(BaseModel):
47
+ provider_job_id: str = Field(default="DUMMY")
48
+ cost: float = Field(default=0)
49
+
50
+
51
+ class JobCost(BaseModel):
52
+ total_cost: float = Field(default=0)
53
+ currency_code: str = Field(default="USD")
54
+ organization: Optional[str] = Field(default=None)
55
+ jobs: list[ProviderJobs] = Field(default=[])
56
+
57
+
46
58
  class ExecutionJobDetails(VersionedModel):
47
59
  id: str
48
60
 
49
- name: Optional[str]
61
+ name: Optional[str] = Field(default=None)
50
62
  start_time: datetime
51
- end_time: Optional[datetime]
63
+ end_time: Optional[datetime] = Field(default=None)
52
64
 
53
- provider: Optional[str]
54
- backend_name: Optional[str]
65
+ provider: Optional[str] = Field(default=None)
66
+ backend_name: Optional[str] = Field(default=None)
55
67
 
56
68
  status: JobStatus
57
69
 
58
- num_shots: Optional[int]
70
+ num_shots: Optional[int] = Field(default=None)
59
71
  program_id: Optional[str] = Field(default=None)
60
72
 
61
73
  error: Optional[str] = Field(default=None)
62
74
 
75
+ cost: Optional[JobCost] = Field(default=None)
76
+
63
77
 
64
78
  class ExecutionJobsQueryResults(VersionedModel):
65
79
  results: list[ExecutionJobDetails]
@@ -16,9 +16,6 @@ DEFAULT_SUPPORTED_FUNC_NAMES: set[str] = {"CLShift", "CRShift", "min", "max"}
16
16
  DEFAULT_EXPRESSION_TYPE = "arithmetic"
17
17
  IDENITIFIER_REGEX = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*")
18
18
 
19
- _REPEATED_VARIABLES_ERROR_MESSAGE: str = (
20
- "Repeated variables in the beginning of an arithmetic expression are not allowed."
21
- )
22
19
  ValidKeyValuePairs: TypeAlias = dict[str, set[str]]
23
20
 
24
21
  SupportedNodesTypes = Union[
@@ -107,13 +104,25 @@ class ExpressionValidator(ast.NodeVisitor):
107
104
  raise ClassiqArithmeticError("Must call `validate` before getting ast_obj")
108
105
  return self._ast_obj
109
106
 
110
- @staticmethod
111
- def _check_repeated_variables(variables: tuple[Any, Any]) -> None:
107
+ def _check_repeated_variables(
108
+ self, variables: tuple[Any, Any], expr: ast.AST, error_suffix: str
109
+ ) -> None:
110
+ if (
111
+ isinstance(expr, ast.BinOp)
112
+ and isinstance(expr.op, ast.Pow)
113
+ and ast.Pow not in self.supported_nodes
114
+ ):
115
+ raise ClassiqValueError(
116
+ "Raising to a power (<var> ** <exp>) and multiplying a variable by "
117
+ "itself (<var> * <var>) are not supported"
118
+ )
112
119
  if (
113
120
  all(isinstance(var, ast.Name) for var in variables)
114
121
  and variables[0].id == variables[1].id
115
122
  ):
116
- raise ClassiqValueError(_REPEATED_VARIABLES_ERROR_MESSAGE)
123
+ raise ClassiqValueError(
124
+ f"Expression {ast.unparse(expr)!r} is not supported ({error_suffix})"
125
+ )
117
126
 
118
127
  @staticmethod
119
128
  def _check_multiple_comparators(node: ast.Compare) -> None:
@@ -135,7 +144,11 @@ class ExpressionValidator(ast.NodeVisitor):
135
144
  )
136
145
 
137
146
  def validate_Compare(self, node: ast.Compare) -> None: # noqa: N802
138
- self._check_repeated_variables((node.left, node.comparators[0]))
147
+ self._check_repeated_variables(
148
+ (node.left, node.comparators[0]),
149
+ node,
150
+ "both sides of the comparison are identical",
151
+ )
139
152
  self._check_multiple_comparators(node)
140
153
 
141
154
  def visit_Compare(self, node: ast.Compare) -> None:
@@ -143,7 +156,9 @@ class ExpressionValidator(ast.NodeVisitor):
143
156
  self.generic_visit(node)
144
157
 
145
158
  def validate_BinOp(self, node: ast.BinOp) -> None: # noqa: N802
146
- self._check_repeated_variables((node.left, node.right))
159
+ self._check_repeated_variables(
160
+ (node.left, node.right), node, "both sides of the operation are identical"
161
+ )
147
162
 
148
163
  def visit_BinOp(self, node: ast.BinOp) -> None:
149
164
  self.validate_BinOp(node)
@@ -151,7 +166,11 @@ class ExpressionValidator(ast.NodeVisitor):
151
166
 
152
167
  def validate_Call(self, node: ast.Call) -> None: # noqa: N802
153
168
  if len(node.args) >= 2:
154
- self._check_repeated_variables((node.args[0], node.args[1]))
169
+ self._check_repeated_variables(
170
+ (node.args[0], node.args[1]),
171
+ node,
172
+ "the first two call arguments are identical",
173
+ )
155
174
  node_id = AstNodeRewrite().extract_node_id(node)
156
175
  if node_id not in self._supported_functions:
157
176
  raise ClassiqValueError(f"{node_id} not in supported functions")
@@ -3,6 +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
7
  from classiq.interface.generator.expressions.qmod_qstruct_proxy import QmodQStructProxy
7
8
  from classiq.interface.generator.functions.classical_type import (
8
9
  ClassicalType,
@@ -57,18 +58,15 @@ class TypeName(ClassicalType, QuantumType):
57
58
 
58
59
  @property
59
60
  def fields(self) -> Mapping[str, "ConcreteQuantumType"]:
60
- from classiq.qmod.model_state_container import QMODULE
61
-
62
61
  if self._assigned_fields is None:
63
- qstruct_fields = QMODULE.qstruct_decls[self.name].fields
64
- self._assigned_fields = {
65
- field_name: field_type.model_copy()
66
- for field_name, field_type in qstruct_fields.items()
67
- }
68
-
62
+ raise ClassiqInternalError("Fields not set")
69
63
  return self._assigned_fields
70
64
 
71
- def _set_fields(self, fields: Mapping[str, "ConcreteQuantumType"]) -> None:
65
+ @property
66
+ def has_fields(self) -> bool:
67
+ return self._assigned_fields is not None
68
+
69
+ def set_fields(self, fields: Mapping[str, "ConcreteQuantumType"]) -> None:
72
70
  self._assigned_fields = fields
73
71
 
74
72
 
@@ -1,5 +1,6 @@
1
1
  # This file was generated automatically - do not edit manually
2
2
 
3
+
3
4
  from enum import IntEnum
4
5
 
5
6
 
@@ -21,6 +21,7 @@ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
21
21
  from classiq.interface.model.quantum_function_declaration import (
22
22
  NamedParamsQuantumFunctionDeclaration,
23
23
  PositionalArg,
24
+ QuantumOperandDeclaration,
24
25
  )
25
26
  from classiq.interface.model.quantum_statement import QuantumStatement
26
27
  from classiq.interface.model.variable_declaration_statement import (
@@ -213,26 +214,43 @@ def _generate_arg_id(
213
214
  arg_name = arg_declaration.name
214
215
  if isinstance(arg_declaration, ClassicalParameterDeclaration):
215
216
  return {arg_name: evaluated_classical_param_to_str(arg_value)}
216
- return {arg_name: _evaluated_arg_to_str(arg_value)}
217
+ if isinstance(arg_declaration, PortDeclaration):
218
+ return {arg_name: _evaluated_port_to_str(arg_value)}
219
+ if isinstance(arg_declaration, QuantumOperandDeclaration):
220
+ return {arg_name: _evaluated_operand_to_str(arg_value)}
217
221
 
218
222
 
219
- @singledispatch
220
223
  def _evaluated_arg_to_str(arg: Any) -> str:
221
224
  if isinstance(arg, str):
222
225
  return arg
223
226
  return str(uuid.uuid4())
224
227
 
225
228
 
226
- @_evaluated_arg_to_str.register
229
+ @singledispatch
230
+ def _evaluated_port_to_str(arg: Any) -> str:
231
+ return _evaluated_arg_to_str(arg)
232
+
233
+
234
+ @_evaluated_port_to_str.register
227
235
  def _evaluated_quantum_symbol_to_str(port: QuantumSymbol) -> str:
228
236
  return port.quantum_type.model_dump_json(exclude_none=True, exclude={"name"})
229
237
 
230
238
 
231
- @_evaluated_arg_to_str.register
239
+ @_evaluated_port_to_str.register
232
240
  def _evaluated_symbol_to_str(port: Symbol) -> str:
233
241
  return repr(port)
234
242
 
235
243
 
236
- @_evaluated_arg_to_str.register
237
- def _evaluated_operand_to_str(operand: FunctionClosure) -> str:
244
+ @singledispatch
245
+ def _evaluated_operand_to_str(arg: Any) -> str:
246
+ return _evaluated_arg_to_str(arg)
247
+
248
+
249
+ @_evaluated_operand_to_str.register
250
+ def _evaluated_one_operand_to_str(operand: FunctionClosure) -> str:
238
251
  return operand.closure_id
252
+
253
+
254
+ @_evaluated_operand_to_str.register
255
+ def _evaluated_list_operands_to_str(operands: list) -> str:
256
+ return json.dumps([_evaluated_operand_to_str(ope) for ope in operands])
@@ -35,7 +35,6 @@ from classiq.model_expansions.evaluators.quantum_type_utils import (
35
35
  set_is_signed,
36
36
  set_length,
37
37
  set_size,
38
- set_struct_fields,
39
38
  )
40
39
  from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
41
40
 
@@ -198,7 +197,7 @@ def _evaluate_qstruct_in_quantum_symbol(
198
197
  field_name: evaluate_type_in_quantum_symbol(field_type, scope, param_name)
199
198
  for field_name, field_type in type_to_update.fields.items()
200
199
  }
201
- set_struct_fields(type_to_update, new_fields)
200
+ type_to_update.set_fields(new_fields)
202
201
  return type_to_update
203
202
 
204
203
 
@@ -1,4 +1,3 @@
1
- from collections.abc import Mapping
2
1
  from typing import Optional
3
2
 
4
3
  from classiq.interface.exceptions import (
@@ -157,12 +156,6 @@ def set_element_type(
157
156
  quantum_array.element_type = element_type
158
157
 
159
158
 
160
- def set_struct_fields(
161
- quantum_struct: TypeName, fields: Mapping[str, ConcreteQuantumType]
162
- ) -> None:
163
- quantum_struct._set_fields(fields)
164
-
165
-
166
159
  def set_length(quantum_array: QuantumBitvector, length: int) -> None:
167
160
  quantum_array.length = Expression(expr=str(length))
168
161
 
@@ -26,6 +26,7 @@ from classiq.interface.model.quantum_function_declaration import (
26
26
  PositionalArg,
27
27
  )
28
28
  from classiq.interface.model.quantum_statement import QuantumStatement
29
+ from classiq.interface.source_reference import SourceReference
29
30
 
30
31
  from classiq.model_expansions.capturing.captured_var_manager import update_captured_vars
31
32
  from classiq.model_expansions.capturing.mangling_utils import demangle_name
@@ -82,6 +83,7 @@ class OperationBuilder:
82
83
  self._operations: list[OperationContext] = []
83
84
  self._blocks: list[str] = []
84
85
  self._functions_scope = functions_scope
86
+ self._current_source_ref: Optional[SourceReference] = None
85
87
 
86
88
  @property
87
89
  def current_operation(self) -> Closure:
@@ -99,6 +101,8 @@ class OperationBuilder:
99
101
  return self._operations[-1].blocks[self._blocks[-1]].statements
100
102
 
101
103
  def emit_statement(self, statement: QuantumStatement) -> None:
104
+ if self._current_source_ref is not None:
105
+ statement.source_ref = self._current_source_ref
102
106
  self._current_statements.append(statement)
103
107
 
104
108
  @property
@@ -131,6 +135,15 @@ class OperationBuilder:
131
135
  self._update_captured_vars()
132
136
  self._operations.pop()
133
137
 
138
+ @contextmanager
139
+ def source_ref_context(
140
+ self, source_ref: Optional[SourceReference]
141
+ ) -> Iterator[None]:
142
+ previous_source_ref = self._current_source_ref
143
+ self._current_source_ref = source_ref
144
+ yield
145
+ self._current_source_ref = previous_source_ref
146
+
134
147
  def _update_captured_vars(self) -> None:
135
148
  for block in self._operations[-1].blocks.values():
136
149
  block.captured_vars = update_captured_vars(block.captured_vars)
@@ -6,8 +6,10 @@ from typing import Any, Optional, cast
6
6
 
7
7
  import numpy as np
8
8
  from numpy.random import permutation
9
+ from pydantic import ValidationError
9
10
 
10
11
  from classiq.interface.exceptions import (
12
+ ClassiqError,
11
13
  ClassiqExpansionError,
12
14
  ClassiqInternalExpansionError,
13
15
  )
@@ -92,12 +94,6 @@ from classiq.qmod.builtins.functions import permute
92
94
  from classiq.qmod.quantum_function import GenerativeQFunc
93
95
  from classiq.qmod.semantics.error_manager import ErrorManager
94
96
 
95
- STATEMENT_TYPES_FOR_SOURCE_REFERENCE_PROPAGATION: tuple[type[QuantumStatement], ...] = (
96
- QuantumFunctionCall,
97
- VariableDeclarationStatement,
98
- QuantumAssignmentOperation,
99
- )
100
-
101
97
 
102
98
  class Interpreter:
103
99
  def __init__(
@@ -168,6 +164,8 @@ class Interpreter:
168
164
  except Exception as e:
169
165
  if isinstance(e, ClassiqInternalExpansionError) or debug_mode.get():
170
166
  raise e
167
+ if not isinstance(e, (ClassiqError, ValidationError)):
168
+ raise ClassiqInternalExpansionError(str(e)) from None
171
169
  prefix = ""
172
170
  if not isinstance(e, ClassiqExpansionError):
173
171
  prefix = f"{type(e).__name__}: "
@@ -333,17 +331,14 @@ class Interpreter:
333
331
  self.emit_statement(statement)
334
332
 
335
333
  def emit_statement(self, statement: QuantumStatement) -> None:
336
- with (
334
+ source_ref = statement.source_ref
335
+ error_context = (
337
336
  self._error_manager.node_context(statement)
338
- if statement.source_ref is not None
337
+ if source_ref is not None
339
338
  else nullcontext()
340
- ):
339
+ )
340
+ with error_context, self._builder.source_ref_context(source_ref):
341
341
  self.emit(statement)
342
- if isinstance(
343
- statement,
344
- STATEMENT_TYPES_FOR_SOURCE_REFERENCE_PROPAGATION,
345
- ):
346
- self._builder.current_statement.source_ref = statement.source_ref
347
342
 
348
343
  def _expand_operation(self, operation: Closure) -> OperationContext:
349
344
  with self._builder.operation_context(operation) as context: