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
@@ -1,22 +1,11 @@
1
1
  import dataclasses
2
- import json
3
- from collections.abc import Collection, Sequence
2
+ from collections.abc import Sequence
4
3
  from dataclasses import dataclass, field
5
- from functools import singledispatch
6
4
  from typing import Any, Optional
7
5
 
8
6
  from typing_extensions import Self
9
7
 
10
8
  from classiq.interface.exceptions import ClassiqInternalExpansionError
11
- from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
12
- ClassicalProxy,
13
- )
14
- from classiq.interface.generator.expressions.proxies.classical.classical_struct_proxy import (
15
- ClassicalStructProxy,
16
- )
17
- from classiq.interface.generator.expressions.proxies.classical.utils import (
18
- get_proxy_type,
19
- )
20
9
  from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
21
10
  from classiq.interface.model.quantum_function_declaration import (
22
11
  NamedParamsQuantumFunctionDeclaration,
@@ -27,10 +16,7 @@ from classiq.interface.model.quantum_statement import QuantumStatement
27
16
 
28
17
  from classiq.model_expansions.capturing.captured_vars import CapturedVars
29
18
  from classiq.model_expansions.scope import (
30
- Evaluated,
31
- QuantumSymbol,
32
19
  Scope,
33
- evaluated_to_str as evaluated_classical_param_to_str,
34
20
  )
35
21
  from classiq.qmod.builtins.functions import permute
36
22
  from classiq.qmod.quantum_function import GenerativeQFunc
@@ -67,13 +53,6 @@ class FunctionClosure(Closure):
67
53
  raise ClassiqInternalExpansionError
68
54
  return self._depth
69
55
 
70
- # creates a unique id for the function closure based on the arguments values.
71
- # The closure is changing across the interpreter flow so it's closure_id may change
72
- @property
73
- def closure_id(self) -> str:
74
- signature = _generate_closure_id(self.scope.data.values())
75
- return f"{self.name}__{signature}"
76
-
77
56
  @property
78
57
  def body(self) -> Sequence[QuantumStatement]:
79
58
  if self.name == permute.func_decl.name:
@@ -134,39 +113,3 @@ class FunctionClosure(Closure):
134
113
  @dataclass(frozen=True)
135
114
  class GenerativeFunctionClosure(GenerativeClosure, FunctionClosure):
136
115
  pass
137
-
138
-
139
- def _generate_closure_id(evaluated_args: Collection[Evaluated]) -> str:
140
- args_signature = [
141
- _evaluated_arg_to_str(eval_arg.value) for eval_arg in evaluated_args
142
- ]
143
- return json.dumps(args_signature)
144
-
145
-
146
- @singledispatch
147
- def _evaluated_arg_to_str(arg: Any) -> str:
148
- if isinstance(arg, str):
149
- return arg
150
- if isinstance(arg, QuantumSymbol):
151
- return _evaluated_quantum_symbol_to_str(arg)
152
- if isinstance(arg, FunctionClosure):
153
- return _evaluated_one_operand_to_str(arg)
154
- if isinstance(arg, list) and arg and isinstance(arg[0], FunctionClosure):
155
- return _evaluated_operands_list_to_str(arg)
156
- if isinstance(arg, ClassicalProxy):
157
- if isinstance(arg, ClassicalStructProxy):
158
- return repr(arg.struct_declaration)
159
- return repr(get_proxy_type(arg))
160
- return evaluated_classical_param_to_str(arg)
161
-
162
-
163
- def _evaluated_quantum_symbol_to_str(port: QuantumSymbol) -> str:
164
- return port.quantum_type.model_dump_json(exclude_none=True, exclude={"name"})
165
-
166
-
167
- def _evaluated_one_operand_to_str(operand: FunctionClosure) -> str:
168
- return operand.closure_id
169
-
170
-
171
- def _evaluated_operands_list_to_str(arg: list[FunctionClosure]) -> str:
172
- return json.dumps([_evaluated_one_operand_to_str(ope) for ope in arg])
@@ -13,7 +13,7 @@ from classiq.model_expansions.scope import Evaluated, QuantumVariable
13
13
  def add_information_from_output_arguments(
14
14
  parameters: Sequence[AnonPositionalArg],
15
15
  args: list[Evaluated],
16
- ) -> list[Evaluated]:
16
+ ) -> None:
17
17
  """
18
18
  This function propagates the quantum type information from the output arguments
19
19
  to the arguments that were passed to it.
@@ -40,4 +40,3 @@ def add_information_from_output_arguments(
40
40
  argument_as_quantum_symbol.quantum_type,
41
41
  str(argument_as_quantum_symbol),
42
42
  )
43
- return args
@@ -11,15 +11,12 @@ from classiq.interface.generator.compiler_keywords import (
11
11
  from classiq.interface.generator.functions.builtins.internal_operators import (
12
12
  WITHIN_APPLY_NAME,
13
13
  )
14
- from classiq.interface.generator.functions.type_qualifier import TypeQualifier
15
14
  from classiq.interface.model.model import MAIN_FUNCTION_NAME
16
15
  from classiq.interface.model.native_function_definition import (
17
16
  NativeFunctionDefinition,
18
17
  )
19
- from classiq.interface.model.port_declaration import PortDeclaration
20
18
  from classiq.interface.model.quantum_function_declaration import (
21
19
  PositionalArg,
22
- QuantumOperandDeclaration,
23
20
  )
24
21
  from classiq.interface.model.quantum_statement import QuantumStatement
25
22
  from classiq.interface.model.variable_declaration_statement import (
@@ -215,7 +212,6 @@ class OperationBuilder:
215
212
  self, function_context: FunctionContext, params: Sequence[PositionalArg]
216
213
  ) -> NativeFunctionDefinition:
217
214
  name = self._get_expanded_function_name(function_context)
218
- self._override_type_qualifier(function_context, params)
219
215
 
220
216
  return NativeFunctionDefinition(
221
217
  name=name,
@@ -248,44 +244,3 @@ class OperationBuilder:
248
244
  raise ClassiqInternalExpansionError("Could not allocate function name")
249
245
 
250
246
  return name
251
-
252
- def _override_type_qualifier(
253
- self, function_context: FunctionContext, params: Sequence[PositionalArg]
254
- ) -> None:
255
- """
256
- The type qualifier can be changed according to the operand passed to the
257
- function. For example,
258
- apply_to_all(X, q) --> q will be QFree after expansion
259
- apply_to_all(H, q) --> q will be Quantum after expansion
260
- This also holds for the intermediate lambda created during the expansion.
261
-
262
- We don't override the type qualifier if it's explicitly specified (QFree or
263
- Const), neither in the function declaration nor in the operand declaration.
264
- """
265
-
266
- if function_context.is_lambda:
267
- self._update_type_qualifiers(params)
268
- return
269
-
270
- orig_name = function_context.name
271
- if orig_name == MAIN_FUNCTION_NAME or orig_name not in self.current_scope:
272
- return
273
-
274
- orig_func = self.current_scope[orig_name].value
275
- if not any(
276
- isinstance(param_decl, QuantumOperandDeclaration)
277
- for param_decl in orig_func.positional_arg_declarations
278
- ):
279
- return
280
-
281
- self._update_type_qualifiers(params)
282
-
283
- @staticmethod
284
- def _update_type_qualifiers(params: Sequence[PositionalArg]) -> None:
285
- # only override the qualifier if it's unspecified (not QFree or Const)
286
- for param in params:
287
- if (
288
- isinstance(param, PortDeclaration)
289
- and param.type_qualifier is TypeQualifier.Quantum
290
- ):
291
- param.type_qualifier = TypeQualifier.Inferred
@@ -104,10 +104,8 @@ class BaseInterpreter:
104
104
  main_closure.positional_arg_declarations, main_closure
105
105
  )
106
106
  context = self._expand_operation(main_closure)
107
- self._expanded_functions[main_closure.closure_id] = (
108
- self._builder.create_definition(
109
- cast(FunctionContext, context), main_closure.positional_arg_declarations
110
- )
107
+ self._expanded_functions[main_closure.name] = self._builder.create_definition(
108
+ cast(FunctionContext, context), main_closure.positional_arg_declarations
111
109
  )
112
110
 
113
111
  def _get_main_closure(self, main_func: FunctionClosure) -> FunctionClosure:
@@ -275,19 +273,19 @@ class BaseInterpreter:
275
273
 
276
274
  def _expand_operation(self, operation: Closure) -> OperationContext:
277
275
  with self._builder.operation_context(operation) as context:
278
- if isinstance(operation, FunctionClosure) and (
279
- (func_def := self._expanded_functions.get(operation.closure_id))
280
- is not None
281
- ):
282
- cached_closure = self._top_level_scope[func_def.name].value
283
- operation.captured_vars.set(
284
- cached_closure.captured_vars, cached_closure, operation
285
- )
286
- else:
287
- self._expand_body(operation)
276
+ self._expand_body(operation)
288
277
 
289
278
  return context
290
279
 
280
+ def _expand_cached_function(
281
+ self, operation: FunctionClosure, func_def: NativeFunctionDefinition
282
+ ) -> None:
283
+ with self._builder.operation_context(operation):
284
+ cached_closure = self._top_level_scope[func_def.name].value
285
+ operation.captured_vars.set(
286
+ cached_closure.captured_vars, cached_closure, operation
287
+ )
288
+
291
289
  def _expand_body(self, operation: Closure) -> None:
292
290
  for block, block_body in operation.blocks.items():
293
291
  self._expand_block(block_body, block)
@@ -1,17 +1,12 @@
1
1
  import inspect
2
2
  import os
3
- from typing import Optional
4
3
 
5
4
  from pydantic import ValidationError
6
5
 
7
6
  from classiq.interface.exceptions import ClassiqError
8
7
  from classiq.interface.model.allocate import Allocate
9
8
  from classiq.interface.model.bind_operation import BindOperation
10
- from classiq.interface.model.native_function_definition import NativeFunctionDefinition
11
9
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
12
- from classiq.interface.model.quantum_function_declaration import (
13
- NamedParamsQuantumFunctionDeclaration,
14
- )
15
10
  from classiq.interface.source_reference import SourceReference
16
11
 
17
12
  from classiq.model_expansions.closure import FunctionClosure, GenerativeFunctionClosure
@@ -24,22 +19,10 @@ from classiq.model_expansions.quantum_operations.quantum_function_call import (
24
19
  DeclarativeQuantumFunctionCallEmitter,
25
20
  )
26
21
  from classiq.model_expansions.scope import Scope
27
- from classiq.model_expansions.visitors.symbolic_param_inference import (
28
- SymbolicParamInference,
29
- )
30
22
  from classiq.qmod.model_state_container import QMODULE
31
23
 
32
24
 
33
25
  class FrontendGenerativeInterpreter(GenerativeInterpreter):
34
- def infer_symbolic_parameters(
35
- self,
36
- functions: list[NativeFunctionDefinition],
37
- additional_signatures: Optional[
38
- list[NamedParamsQuantumFunctionDeclaration]
39
- ] = None,
40
- ) -> None:
41
- SymbolicParamInference(functions, additional_signatures).infer()
42
-
43
26
  def emit_allocate(self, allocate: Allocate) -> None:
44
27
  AllocateEmitter(self, allow_symbolic_size=True).emit(allocate)
45
28
 
@@ -28,16 +28,19 @@ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_insta
28
28
  from classiq.interface.generator.functions.port_declaration import (
29
29
  PortDeclarationDirection,
30
30
  )
31
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
31
32
  from classiq.interface.model.classical_parameter_declaration import (
32
33
  ClassicalParameterDeclaration,
33
34
  )
34
35
  from classiq.interface.model.handle_binding import HandleBinding
36
+ from classiq.interface.model.model import MAIN_FUNCTION_NAME
35
37
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
36
38
  from classiq.interface.model.port_declaration import PortDeclaration
37
39
  from classiq.interface.model.quantum_function_call import ArgValue, QuantumFunctionCall
38
40
  from classiq.interface.model.quantum_function_declaration import (
39
41
  NamedParamsQuantumFunctionDeclaration,
40
42
  PositionalArg,
43
+ QuantumOperandDeclaration,
41
44
  )
42
45
  from classiq.interface.model.quantum_statement import QuantumStatement
43
46
  from classiq.interface.model.variable_declaration_statement import (
@@ -49,7 +52,7 @@ from classiq.model_expansions.capturing.captured_vars import (
49
52
  UNINITIALIZED_VAR_MESSAGE,
50
53
  validate_args_are_not_propagated,
51
54
  )
52
- from classiq.model_expansions.closure import FunctionClosure
55
+ from classiq.model_expansions.closure import Closure, FunctionClosure
53
56
  from classiq.model_expansions.evaluators.argument_types import (
54
57
  add_information_from_output_arguments,
55
58
  )
@@ -63,6 +66,9 @@ from classiq.model_expansions.quantum_operations.emitter import (
63
66
  Emitter,
64
67
  QuantumStatementT,
65
68
  )
69
+ from classiq.model_expansions.quantum_operations.function_calls_cache import (
70
+ get_func_call_cache_key,
71
+ )
66
72
  from classiq.model_expansions.scope import (
67
73
  Evaluated,
68
74
  QuantumSymbol,
@@ -70,6 +76,9 @@ from classiq.model_expansions.scope import (
70
76
  QuantumVariable,
71
77
  Scope,
72
78
  )
79
+ from classiq.model_expansions.transformers.type_qualifier_inference import (
80
+ TypeQualifierInference,
81
+ )
73
82
  from classiq.model_expansions.transformers.var_splitter import VarSplitter
74
83
  from classiq.model_expansions.utils.text_utils import are, readable_list, s
75
84
  from classiq.qmod.semantics.validation.signature_validation import (
@@ -174,19 +183,22 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
174
183
  )
175
184
  new_positional_arg_decls = new_declaration.positional_arg_declarations
176
185
  if not self.should_expand_function(function, evaluated_args):
177
- is_atomic = True
178
186
  new_declaration = self._expanded_functions_by_name.get(
179
187
  function.name, new_declaration
180
188
  )
181
189
  else:
182
- is_atomic = False
183
190
  new_declaration = self._expand_function(
184
191
  evaluated_args, new_declaration, function
185
192
  )
193
+ new_positional_arg_decls = new_declaration.positional_arg_declarations
194
+ evaluated_args = [
195
+ arg
196
+ for arg in evaluated_args
197
+ if isinstance(arg.value, QuantumVariable) or _is_symbolic(arg.value)
198
+ ]
186
199
 
187
- new_positional_args = self._get_new_positional_args(
188
- evaluated_args, is_atomic, new_positional_arg_decls
189
- )
200
+ add_information_from_output_arguments(new_positional_arg_decls, evaluated_args)
201
+ new_positional_args = [arg.emit() for arg in evaluated_args]
190
202
  captured_args = function.captured_vars.filter_vars(function).get_captured_args(
191
203
  self._builder.current_function
192
204
  )
@@ -229,18 +241,20 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
229
241
  function: FunctionClosure,
230
242
  ) -> NamedParamsQuantumFunctionDeclaration:
231
243
  self._add_params_to_scope(decl.positional_arg_declarations, args, function)
232
- context = self._expand_operation(function.with_new_declaration(decl))
233
- function_context = cast(FunctionContext, context)
234
- closure_id = function_context.closure.closure_id
235
- if closure_id in self._expanded_functions:
236
- function_def = self._expanded_functions[closure_id]
244
+ function = function.with_new_declaration(decl)
245
+ cache_key = get_func_call_cache_key(decl, args)
246
+ if cache_key in self._expanded_functions:
247
+ function_def = self._expanded_functions[cache_key]
248
+ self._expand_cached_function(function, function_def)
237
249
  self._expanded_functions_compilation_metadata[
238
250
  function_def.name
239
251
  ].occurrences_number += 1
240
252
  return function_def
241
253
 
254
+ context = self._expand_operation(function)
255
+ function_context = cast(FunctionContext, context)
242
256
  function_def = self._create_function_definition(function_context, args)
243
- self._expanded_functions[closure_id] = function_def
257
+ self._expanded_functions[cache_key] = function_def
244
258
  self._top_level_scope[function_def.name] = Evaluated(
245
259
  value=function_context.closure.with_new_declaration(function_def)
246
260
  )
@@ -270,6 +284,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
270
284
  )
271
285
  captured_ports = captured_vars.get_captured_parameters()
272
286
  if len(captured_ports) == 0:
287
+ self._override_type_qualifier(function_context, func_def)
273
288
  return func_def
274
289
  func_def.positional_arg_declarations = list(
275
290
  chain.from_iterable((func_def.positional_arg_declarations, captured_ports))
@@ -281,6 +296,8 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
281
296
  rewrite_mapping |= captured_vars.get_classical_captured_mapping()
282
297
  func_def.body = self.rewrite(func_def.body, rewrite_mapping)
283
298
 
299
+ self._override_type_qualifier(function_context, func_def)
300
+
284
301
  return func_def
285
302
 
286
303
  @staticmethod
@@ -309,26 +326,6 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
309
326
  else:
310
327
  closure.scope[parameter.name] = argument
311
328
 
312
- def _get_new_positional_args(
313
- self,
314
- evaluated_args: list[Evaluated],
315
- is_atomic: bool,
316
- new_positional_arg_decls: Sequence[PositionalArg],
317
- ) -> list[ArgValue]:
318
- evaluated_args = add_information_from_output_arguments(
319
- new_positional_arg_decls, evaluated_args
320
- )
321
- if is_atomic:
322
- return [arg.emit() for arg in evaluated_args]
323
-
324
- positional_args = [
325
- arg.emit()
326
- for arg in evaluated_args
327
- if isinstance(arg.value, QuantumVariable) or _is_symbolic(arg.value)
328
- ]
329
-
330
- return positional_args
331
-
332
329
  def _prepare_fully_typed_declaration(
333
330
  self, function: FunctionClosure, evaluated_args: list[Evaluated]
334
331
  ) -> NamedParamsQuantumFunctionDeclaration:
@@ -381,3 +378,45 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
381
378
  raise ClassiqExpansionError(UNINITIALIZED_VAR_MESSAGE.format(var_name))
382
379
  if var_state and param.direction == PortDeclarationDirection.Output:
383
380
  raise ClassiqExpansionError(INITIALIZED_VAR_MESSAGE.format(var_name))
381
+
382
+ def _override_type_qualifier(
383
+ self, func_context: FunctionContext, func_def: NativeFunctionDefinition
384
+ ) -> None:
385
+ """
386
+ The type qualifier can be changed according to the operand passed to the
387
+ function. For example,
388
+ apply_to_all(X, q) --> q will be QFree after expansion
389
+ apply_to_all(H, q) --> q will be Quantum after expansion
390
+ This also holds for the intermediate lambda created during the expansion.
391
+
392
+ We don't override the type qualifier if it's explicitly specified (QFree or
393
+ Const), neither in the function declaration nor in the operand declaration.
394
+ """
395
+
396
+ if func_context.is_lambda:
397
+ self._update_type_qualifiers(func_def)
398
+ return
399
+
400
+ orig_name = func_context.name
401
+ if (
402
+ orig_name == MAIN_FUNCTION_NAME
403
+ or orig_name not in func_context.closure.scope
404
+ ):
405
+ return
406
+
407
+ orig_func = func_context.closure.scope[orig_name].value
408
+ if isinstance(orig_func, Closure) and not any(
409
+ isinstance(param_decl, QuantumOperandDeclaration)
410
+ for param_decl in orig_func.positional_arg_declarations
411
+ ):
412
+ return
413
+
414
+ self._update_type_qualifiers(func_def)
415
+
416
+ @staticmethod
417
+ def _update_type_qualifiers(func_def: NativeFunctionDefinition) -> None:
418
+ # only override the qualifier if it's unspecified (not QFree or Const)
419
+ for port in func_def.port_declarations:
420
+ if port.type_qualifier is TypeQualifier.Quantum:
421
+ port.type_qualifier = TypeQualifier.Inferred
422
+ TypeQualifierInference().run(func_def.port_declarations, func_def.body)
@@ -47,7 +47,7 @@ from classiq.interface.model.quantum_function_declaration import (
47
47
  )
48
48
  from classiq.interface.model.quantum_statement import QuantumOperation, QuantumStatement
49
49
 
50
- from classiq.model_expansions.closure import Closure, GenerativeClosure
50
+ from classiq.model_expansions.closure import Closure, FunctionClosure, GenerativeClosure
51
51
  from classiq.model_expansions.function_builder import (
52
52
  OperationBuilder,
53
53
  OperationContext,
@@ -87,6 +87,11 @@ class Emitter(Generic[QuantumStatementT], ABC):
87
87
  def _expand_operation(self, closure: Closure) -> OperationContext:
88
88
  return self._interpreter._expand_operation(closure)
89
89
 
90
+ def _expand_cached_function(
91
+ self, closure: FunctionClosure, func_def: NativeFunctionDefinition
92
+ ) -> None:
93
+ return self._interpreter._expand_cached_function(closure, func_def)
94
+
90
95
  @property
91
96
  def _builder(self) -> OperationBuilder:
92
97
  return self._interpreter._builder
@@ -0,0 +1,84 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
5
+ from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
6
+ ClassicalProxy,
7
+ )
8
+ from classiq.interface.generator.expressions.proxies.classical.classical_struct_proxy import (
9
+ ClassicalStructProxy,
10
+ )
11
+ from classiq.interface.generator.expressions.proxies.classical.utils import (
12
+ get_proxy_type,
13
+ )
14
+ from classiq.interface.generator.functions.port_declaration import (
15
+ PortDeclarationDirection,
16
+ )
17
+ from classiq.interface.model.port_declaration import PortDeclaration
18
+ from classiq.interface.model.quantum_function_declaration import (
19
+ NamedParamsQuantumFunctionDeclaration,
20
+ )
21
+
22
+ from classiq.model_expansions.closure import FunctionClosure
23
+ from classiq.model_expansions.scope import (
24
+ Evaluated,
25
+ QuantumSymbol,
26
+ evaluated_to_str as evaluated_classical_param_to_str,
27
+ )
28
+
29
+
30
+ def get_func_call_cache_key(
31
+ decl: NamedParamsQuantumFunctionDeclaration,
32
+ args: list[Evaluated],
33
+ ) -> str:
34
+ if len(decl.positional_arg_declarations) != len(args):
35
+ raise ClassiqInternalExpansionError(
36
+ "Mismatch between number of args to number of arg declarations"
37
+ )
38
+
39
+ # output arguments cannot affect the morphization of the function, as their
40
+ # attributes are defined locally by the function, according to other arguments
41
+ non_outputs_args = [
42
+ arg
43
+ for arg_decl, arg in zip(decl.positional_arg_declarations, args)
44
+ if not (
45
+ isinstance(arg_decl, PortDeclaration)
46
+ and arg_decl.direction is PortDeclarationDirection.Output
47
+ )
48
+ ]
49
+ return f"{decl.name}__{_evaluated_args_to_str(non_outputs_args)}"
50
+
51
+
52
+ def _evaluated_args_to_str(evaluated_args: list[Evaluated]) -> str:
53
+ args_signature = [
54
+ _evaluated_arg_to_str(eval_arg.value) for eval_arg in evaluated_args
55
+ ]
56
+ return json.dumps(args_signature)
57
+
58
+
59
+ def _evaluated_arg_to_str(arg: Any) -> str:
60
+ if isinstance(arg, str):
61
+ return arg
62
+ if isinstance(arg, QuantumSymbol):
63
+ return _evaluated_quantum_symbol_to_str(arg)
64
+ if isinstance(arg, FunctionClosure):
65
+ return _evaluated_one_operand_to_str(arg)
66
+ if isinstance(arg, list) and arg and isinstance(arg[0], FunctionClosure):
67
+ return _evaluated_operands_list_to_str(arg)
68
+ if isinstance(arg, ClassicalProxy):
69
+ if isinstance(arg, ClassicalStructProxy):
70
+ return repr(arg.struct_declaration)
71
+ return repr(get_proxy_type(arg))
72
+ return evaluated_classical_param_to_str(arg)
73
+
74
+
75
+ def _evaluated_quantum_symbol_to_str(port: QuantumSymbol) -> str:
76
+ return port.quantum_type.model_dump_json(exclude_none=True, exclude={"name"})
77
+
78
+
79
+ def _evaluated_one_operand_to_str(operand: FunctionClosure) -> str:
80
+ return operand.name
81
+
82
+
83
+ def _evaluated_operands_list_to_str(arg: list[FunctionClosure]) -> str:
84
+ return json.dumps([_evaluated_one_operand_to_str(ope) for ope in arg])
@@ -3,7 +3,9 @@ from collections import UserDict
3
3
  from collections.abc import Iterator
4
4
  from dataclasses import dataclass
5
5
  from functools import singledispatch
6
- from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union
6
+ from typing import TYPE_CHECKING, Any, NoReturn, Optional, TypeVar, Union
7
+
8
+ import sympy
7
9
 
8
10
  from classiq.interface.exceptions import (
9
11
  ClassiqExpansionError,
@@ -34,6 +36,8 @@ from classiq.interface.model.quantum_type import (
34
36
  QuantumType,
35
37
  )
36
38
 
39
+ from classiq.model_expansions.utils.text_utils import readable_list, s
40
+
37
41
  if TYPE_CHECKING:
38
42
  from classiq.model_expansions.closure import FunctionClosure
39
43
 
@@ -206,27 +210,33 @@ def _evaluated_to_str_struct_literal(value: QmodStructInstance) -> str:
206
210
  return f"struct_literal({value.struct_declaration.name}, {', '.join(f'{k}={evaluated_to_str(v)}' for k, v in value.fields.items())})"
207
211
 
208
212
 
213
+ def _raise_type_error(val: Any, t: type, location_hint: Optional[str]) -> NoReturn:
214
+ if isinstance(val, sympy.Basic) and len(val.free_symbols) > 0:
215
+ symbolic_vars = list(val.free_symbols)
216
+ suffix = f" {location_hint}" if location_hint is not None else ""
217
+ raise ClassiqExpansionError(
218
+ f"Cannot use execution parameter{s(symbolic_vars)} {readable_list(symbolic_vars, quote=True)} in a compile-time context{suffix}"
219
+ )
220
+ raise ClassiqExpansionError(f"Invalid access to expression {val!r} as {t}")
221
+
222
+
209
223
  @dataclass(frozen=True)
210
224
  class Evaluated: # FIXME: Merge with EvaluatedExpression if possible
211
225
  value: Any
212
226
  defining_function: Optional["FunctionClosure"] = None
213
227
 
214
- def as_type(self, t: type[T]) -> T:
228
+ def as_type(self, t: type[T], location_hint: Optional[str] = None) -> T:
215
229
  if t is int:
216
- return self._as_int() # type: ignore[return-value]
230
+ return self._as_int(location_hint) # type: ignore[return-value]
217
231
 
218
232
  if not isinstance(self.value, t):
219
- raise ClassiqExpansionError(
220
- f"Invalid access to expression {self.value!r} as {t}"
221
- )
233
+ _raise_type_error(self.value, t, location_hint)
222
234
 
223
235
  return self.value
224
236
 
225
- def _as_int(self) -> int:
237
+ def _as_int(self, location_hint: Optional[str]) -> int:
226
238
  if not isinstance(self.value, (int, float)):
227
- raise ClassiqExpansionError(
228
- f"Invalid access to expression {self.value!r} as {int}"
229
- )
239
+ _raise_type_error(self.value, int, location_hint)
230
240
 
231
241
  return int(self.value)
232
242