classiq 0.39.0__py3-none-any.whl → 0.41.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 (100) hide show
  1. classiq/__init__.py +5 -2
  2. classiq/_internals/api_wrapper.py +3 -21
  3. classiq/applications/chemistry/chemistry_model_constructor.py +87 -101
  4. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +7 -26
  5. classiq/applications/combinatorial_helpers/optimization_model.py +7 -6
  6. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +33 -55
  7. classiq/applications/combinatorial_optimization/__init__.py +4 -0
  8. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +29 -26
  9. classiq/applications/finance/finance_model_constructor.py +23 -26
  10. classiq/applications/grover/grover_model_constructor.py +37 -38
  11. classiq/applications/qsvm/qsvm.py +1 -2
  12. classiq/applications/qsvm/qsvm_model_constructor.py +15 -16
  13. classiq/execution/__init__.py +4 -0
  14. classiq/execution/execution_session.py +151 -0
  15. classiq/execution/qnn.py +80 -0
  16. classiq/executor.py +2 -109
  17. classiq/interface/_version.py +1 -1
  18. classiq/interface/analyzer/analysis_params.py +11 -0
  19. classiq/interface/applications/qsvm.py +0 -8
  20. classiq/interface/ast_node.py +12 -2
  21. classiq/interface/backend/backend_preferences.py +30 -6
  22. classiq/interface/backend/quantum_backend_providers.py +11 -11
  23. classiq/interface/executor/execution_preferences.py +7 -67
  24. classiq/interface/executor/execution_result.py +22 -1
  25. classiq/interface/generator/application_apis/chemistry_declarations.py +2 -4
  26. classiq/interface/generator/application_apis/finance_declarations.py +1 -1
  27. classiq/interface/generator/arith/binary_ops.py +88 -25
  28. classiq/interface/generator/arith/unary_ops.py +28 -19
  29. classiq/interface/generator/expressions/atomic_expression_functions.py +6 -2
  30. classiq/interface/generator/expressions/enums/__init__.py +10 -0
  31. classiq/interface/generator/expressions/enums/classical_enum.py +5 -1
  32. classiq/interface/generator/expressions/expression.py +9 -2
  33. classiq/interface/generator/expressions/qmod_qarray_proxy.py +89 -0
  34. classiq/interface/generator/expressions/qmod_qscalar_proxy.py +20 -0
  35. classiq/interface/generator/expressions/qmod_sized_proxy.py +22 -0
  36. classiq/interface/generator/expressions/sympy_supported_expressions.py +10 -1
  37. classiq/interface/generator/functions/builtins/core_library/atomic_quantum_functions.py +8 -6
  38. classiq/interface/generator/functions/builtins/core_library/exponentiation_functions.py +10 -4
  39. classiq/interface/generator/functions/builtins/internal_operators.py +7 -62
  40. classiq/interface/generator/functions/builtins/open_lib_functions.py +1627 -271
  41. classiq/interface/generator/functions/classical_type.py +27 -17
  42. classiq/interface/generator/model/preferences/preferences.py +4 -2
  43. classiq/interface/generator/synthesis_metadata/synthesis_duration.py +0 -4
  44. classiq/interface/model/bind_operation.py +3 -1
  45. classiq/interface/model/call_synthesis_data.py +2 -13
  46. classiq/interface/model/classical_if.py +3 -1
  47. classiq/interface/model/classical_parameter_declaration.py +13 -0
  48. classiq/interface/model/control.py +6 -8
  49. classiq/interface/model/inplace_binary_operation.py +3 -1
  50. classiq/interface/model/invert.py +3 -1
  51. classiq/interface/model/port_declaration.py +8 -1
  52. classiq/interface/model/power.py +3 -1
  53. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +4 -2
  54. classiq/interface/model/quantum_expressions/arithmetic_operation.py +3 -1
  55. classiq/interface/model/quantum_expressions/quantum_expression.py +11 -1
  56. classiq/interface/model/quantum_function_call.py +4 -10
  57. classiq/interface/model/quantum_function_declaration.py +26 -4
  58. classiq/interface/model/quantum_lambda_function.py +1 -20
  59. classiq/interface/model/quantum_statement.py +9 -2
  60. classiq/interface/model/quantum_type.py +6 -5
  61. classiq/interface/model/repeat.py +3 -1
  62. classiq/interface/model/resolvers/function_call_resolver.py +0 -5
  63. classiq/interface/model/statement_block.py +19 -16
  64. classiq/interface/model/validations/handles_validator.py +8 -2
  65. classiq/interface/model/variable_declaration_statement.py +3 -1
  66. classiq/interface/model/within_apply_operation.py +3 -1
  67. classiq/interface/server/routes.py +0 -5
  68. classiq/qmod/__init__.py +5 -2
  69. classiq/qmod/builtins/classical_execution_primitives.py +22 -2
  70. classiq/qmod/builtins/classical_functions.py +30 -35
  71. classiq/qmod/builtins/functions.py +263 -153
  72. classiq/qmod/builtins/operations.py +50 -26
  73. classiq/qmod/builtins/structs.py +50 -48
  74. classiq/qmod/declaration_inferrer.py +32 -27
  75. classiq/qmod/native/__init__.py +9 -0
  76. classiq/qmod/native/expression_to_qmod.py +8 -4
  77. classiq/qmod/native/pretty_printer.py +11 -18
  78. classiq/qmod/pretty_print/__init__.py +9 -0
  79. classiq/qmod/pretty_print/expression_to_python.py +221 -0
  80. classiq/qmod/pretty_print/pretty_printer.py +421 -0
  81. classiq/qmod/qmod_constant.py +7 -7
  82. classiq/qmod/qmod_parameter.py +57 -33
  83. classiq/qmod/qmod_struct.py +2 -2
  84. classiq/qmod/qmod_variable.py +40 -29
  85. classiq/qmod/quantum_callable.py +8 -4
  86. classiq/qmod/quantum_expandable.py +22 -15
  87. classiq/qmod/quantum_function.py +15 -4
  88. classiq/qmod/symbolic.py +73 -68
  89. classiq/qmod/symbolic_expr.py +1 -1
  90. classiq/qmod/symbolic_type.py +1 -4
  91. classiq/qmod/utilities.py +29 -0
  92. classiq/synthesis.py +15 -16
  93. {classiq-0.39.0.dist-info → classiq-0.41.0.dist-info}/METADATA +5 -4
  94. {classiq-0.39.0.dist-info → classiq-0.41.0.dist-info}/RECORD +95 -94
  95. classiq/interface/executor/error_mitigation.py +0 -6
  96. classiq/interface/generator/functions/builtins/core_library/chemistry_functions.py +0 -0
  97. classiq/interface/model/common_model_types.py +0 -23
  98. classiq/interface/model/quantum_expressions/control_state.py +0 -38
  99. classiq/interface/model/quantum_if_operation.py +0 -94
  100. {classiq-0.39.0.dist-info → classiq-0.41.0.dist-info}/WHEEL +0 -0
@@ -154,7 +154,11 @@ class InplacableBinaryOpParams(
154
154
  if self.inplace_arg is None:
155
155
  return 0
156
156
  arg = self.left_arg if self.inplace_arg == ArgToInplace.LEFT else self.right_arg
157
- return max(0, arg.integer_part_size - self.result_register.integer_part_size) # type: ignore[attr-defined]
157
+ return max(
158
+ 0, arg.integer_part_size - self.result_register.integer_part_size # type: ignore[attr-defined]
159
+ ) + max(
160
+ 0, arg.fraction_places - self.result_register.fraction_places # type: ignore[attr-defined]
161
+ )
158
162
 
159
163
  def _carried_arguments(self) -> Tuple[Optional[LeftDataT], Optional[RightDataT]]:
160
164
  if self.inplace_arg == ArgToInplace.RIGHT and isinstance(
@@ -259,15 +263,21 @@ class Adder(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
259
263
  output_name = "sum"
260
264
 
261
265
  def _get_result_register(self) -> RegisterArithmeticInfo:
262
- lb = argument_utils.lower_bound(self.left_arg) + argument_utils.lower_bound(
263
- self.right_arg
266
+ left_arg = argument_utils.limit_fraction_places(
267
+ self.left_arg, machine_precision=self.machine_precision
264
268
  )
265
- ub = argument_utils.upper_bound(self.left_arg) + argument_utils.upper_bound(
266
- self.right_arg
269
+ right_arg = argument_utils.limit_fraction_places(
270
+ self.right_arg, machine_precision=self.machine_precision
271
+ )
272
+ lb = argument_utils.lower_bound(left_arg) + argument_utils.lower_bound(
273
+ right_arg
274
+ )
275
+ ub = argument_utils.upper_bound(left_arg) + argument_utils.upper_bound(
276
+ right_arg
267
277
  )
268
278
  fraction_places = max(
269
- argument_utils.fraction_places(self.left_arg),
270
- argument_utils.fraction_places(self.right_arg),
279
+ argument_utils.fraction_places(left_arg),
280
+ argument_utils.fraction_places(right_arg),
271
281
  )
272
282
  return RegisterArithmeticInfo(
273
283
  size=self.output_size or self._get_output_size(ub, lb, fraction_places),
@@ -291,16 +301,47 @@ class Adder(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
291
301
  class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
292
302
  output_name = "difference"
293
303
 
304
+ @staticmethod
305
+ def _get_effective_arg(
306
+ arg: RegisterOrConst, machine_precision: int
307
+ ) -> RegisterOrConst:
308
+ return argument_utils.limit_fraction_places(
309
+ arg, machine_precision=machine_precision
310
+ )
311
+
312
+ @property
313
+ def effective_left_arg(self) -> RegisterOrConst:
314
+ return self._get_effective_arg(self.left_arg, self.machine_precision)
315
+
316
+ @property
317
+ def effective_right_arg(self) -> RegisterOrConst:
318
+ return self._get_effective_arg(self.right_arg, self.machine_precision)
319
+
320
+ @staticmethod
321
+ def _is_arg_trimmed_register(arg: RegisterOrConst, machine_precision: int) -> bool:
322
+ return (
323
+ isinstance(arg, RegisterArithmeticInfo)
324
+ and arg.fraction_places > machine_precision
325
+ )
326
+
327
+ @property
328
+ def left_arg_is_trimmed_register(self) -> bool:
329
+ return self._is_arg_trimmed_register(self.left_arg, self.machine_precision)
330
+
331
+ @property
332
+ def right_arg_is_trimmed_register(self) -> bool:
333
+ return self._is_arg_trimmed_register(self.right_arg, self.machine_precision)
334
+
294
335
  def _get_result_register(self) -> RegisterArithmeticInfo:
295
336
  bounds = (
296
- argument_utils.lower_bound(self.left_arg)
297
- - argument_utils.upper_bound(self.right_arg),
298
- argument_utils.upper_bound(self.left_arg)
299
- - argument_utils.lower_bound(self.right_arg),
337
+ argument_utils.lower_bound(self.effective_left_arg)
338
+ - argument_utils.upper_bound(self.effective_right_arg),
339
+ argument_utils.upper_bound(self.effective_left_arg)
340
+ - argument_utils.lower_bound(self.effective_right_arg),
300
341
  )
301
342
  fraction_places = max(
302
- argument_utils.fraction_places(self.left_arg),
303
- argument_utils.fraction_places(self.right_arg),
343
+ argument_utils.fraction_places(self.effective_left_arg),
344
+ argument_utils.fraction_places(self.effective_right_arg),
304
345
  )
305
346
 
306
347
  return RegisterArithmeticInfo(
@@ -313,38 +354,60 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
313
354
  def _get_output_size(
314
355
  self, bounds: Tuple[float, float], fraction_places: int
315
356
  ) -> int:
316
- if isinstance(self.right_arg, float) and self.right_arg == 0:
317
- assert isinstance(self.left_arg, RegisterArithmeticInfo)
318
- return self.left_arg.size
357
+ if isinstance(self.right_arg, float) and self.effective_right_arg == 0:
358
+ assert isinstance(self.effective_left_arg, RegisterArithmeticInfo)
359
+ return self.effective_left_arg.size
319
360
  integer_part_size = number_utils.bounds_to_integer_part_size(*bounds)
320
361
  size_needed = integer_part_size + fraction_places
321
362
  return size_needed
322
363
 
323
364
  def garbage_output_size(self) -> pydantic.NonNegativeInt:
324
- if not isinstance(self.right_arg, RegisterArithmeticInfo):
365
+ if (
366
+ not self.left_arg_is_trimmed_register
367
+ and not self.right_arg_is_trimmed_register
368
+ ):
369
+ return self._untrimmed_garbage_output_size()
370
+ if not self.is_inplaced():
371
+ return 0
372
+ inplace_arg_name = (
373
+ self.left_arg_name
374
+ if self.inplace_arg == ArgToInplace.LEFT
375
+ else self.right_arg_name
376
+ )
377
+ return max(
378
+ 0,
379
+ self._inputs[inplace_arg_name].fraction_places
380
+ - self.result_register.fraction_places,
381
+ )
382
+
383
+ def _untrimmed_garbage_output_size(self) -> pydantic.NonNegativeInt:
384
+ if not isinstance(self.effective_right_arg, RegisterArithmeticInfo):
325
385
  adder_params = Adder(
326
- left_arg=self.left_arg,
327
- right_arg=-self.right_arg,
386
+ left_arg=self.effective_left_arg,
387
+ right_arg=-self.effective_right_arg,
328
388
  output_size=self.output_size,
329
389
  inplace_arg=self.inplace_arg,
330
390
  )
331
391
  return adder_params.garbage_output_size()
332
392
 
333
393
  negation_params = Negation(
334
- arg=self.right_arg,
394
+ arg=self.effective_right_arg,
335
395
  output_size=self.negation_output_size,
336
396
  inplace=self.should_inplace_negation,
337
397
  )
338
398
  negation_result = negation_params.result_register
339
- if self.output_size is None and max(self.right_arg.bounds) > 0:
399
+ if self.output_size is None and max(self.effective_right_arg.bounds) > 0:
340
400
  negation_result = negation_result.copy(
341
401
  update=dict(
342
402
  is_signed=True,
343
- bounds=(-max(self.right_arg.bounds), -min(self.right_arg.bounds)),
403
+ bounds=(
404
+ -max(self.effective_right_arg.bounds),
405
+ -min(self.effective_right_arg.bounds),
406
+ ),
344
407
  )
345
408
  )
346
409
  adder_params = Adder(
347
- left_arg=self.left_arg,
410
+ left_arg=self.effective_left_arg,
348
411
  right_arg=negation_result,
349
412
  output_size=self.output_size,
350
413
  inplace_arg=self.arg_to_inplace_adder,
@@ -359,10 +422,10 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
359
422
  return self.inplace_arg == ArgToInplace.LEFT
360
423
 
361
424
  def _expected_negation_output_size(self) -> int:
362
- return argument_utils.fraction_places(self.right_arg) + min(
425
+ return argument_utils.fraction_places(self.effective_right_arg) + min(
363
426
  self.result_register.integer_part_size,
364
427
  number_utils.bounds_to_integer_part_size(
365
- *(-bound for bound in argument_utils.bounds(self.right_arg))
428
+ *(-bound for bound in argument_utils.bounds(self.effective_right_arg))
366
429
  ),
367
430
  )
368
431
 
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Iterable, Optional
1
+ from typing import TYPE_CHECKING, Final, Iterable, Optional
2
2
 
3
3
  import pydantic
4
4
 
@@ -11,7 +11,7 @@ from classiq.interface.generator.function_params import get_zero_input_name
11
11
 
12
12
  from classiq.exceptions import ClassiqValueError
13
13
 
14
- UNARY_ARG_NAME: str = "arg"
14
+ UNARY_ARG_NAME: Final[str] = "arg"
15
15
 
16
16
 
17
17
  class UnaryOpParams(ArithmeticOperationParams):
@@ -19,17 +19,16 @@ class UnaryOpParams(ArithmeticOperationParams):
19
19
  inplace: bool = False
20
20
 
21
21
  def garbage_output_size(self) -> pydantic.NonNegativeInt:
22
- return int(self.is_inplaced()) * max(
23
- self.arg.size - self.result_register.size, 0
22
+ return int(self.is_inplaced()) * (
23
+ max(self.arg.integer_part_size - self.result_register.integer_part_size, 0)
24
+ + max(self.arg.fraction_places - self.result_register.fraction_places, 0)
24
25
  )
25
26
 
26
27
  def should_add_zero_inputs(self) -> bool:
27
- return not self.is_inplaced() or self.should_add_zero_input_for_extension()
28
+ return not self.is_inplaced() or self.zero_input_for_extension() > 0
28
29
 
29
- def should_add_zero_input_for_extension(
30
- self, output_size: Optional[int] = None
31
- ) -> bool:
32
- return (output_size or self.result_register.size) > self.arg.size
30
+ def zero_input_for_extension(self) -> pydantic.NonNegativeInt:
31
+ return max(0, self.result_register.size - self.arg.size)
33
32
 
34
33
  def _create_ios(self) -> None:
35
34
  self._inputs = {UNARY_ARG_NAME: self.arg}
@@ -41,8 +40,8 @@ class UnaryOpParams(ArithmeticOperationParams):
41
40
  zero_input_register = self.result_register
42
41
  self._zero_inputs = {zero_input_name: zero_input_register}
43
42
  return
44
- if self.should_add_zero_input_for_extension():
45
- output_extension_size = self.result_register.size - self.arg.size
43
+ if self.zero_input_for_extension() > 0:
44
+ output_extension_size = self.zero_input_for_extension()
46
45
  self._create_zero_input_registers({zero_input_name: output_extension_size})
47
46
  if self.garbage_output_size() > 0:
48
47
  self._outputs[self.garbage_output_name] = RegisterArithmeticInfo(
@@ -80,23 +79,33 @@ class BitwiseInvert(UnaryOpParams):
80
79
  class Negation(UnaryOpParams):
81
80
  output_name = "negated"
82
81
 
83
- def _expected_result_size(self) -> pydantic.PositiveInt:
84
- if self.arg.size == 1:
82
+ @staticmethod
83
+ def _expected_result_size(arg: RegisterArithmeticInfo) -> pydantic.PositiveInt:
84
+ if arg.size == 1:
85
85
  return 1
86
- return self.arg.fraction_places + number_utils.bounds_to_integer_part_size(
87
- *(-bound for bound in self.arg.bounds)
86
+ return arg.fraction_places + number_utils.bounds_to_integer_part_size(
87
+ *(-bound for bound in arg.bounds)
88
88
  )
89
89
 
90
90
  def _get_result_register(self) -> RegisterArithmeticInfo:
91
- is_signed = max(self.arg.bounds) > 0 and self._include_sign
92
- bounds = (-max(self.arg.bounds), -min(self.arg.bounds))
91
+ eff_arg = self.arg.limit_fraction_places(self.machine_precision)
92
+ is_signed = max(eff_arg.bounds) > 0 and self._include_sign
93
+ bounds = (-max(eff_arg.bounds), -min(eff_arg.bounds))
93
94
  return RegisterArithmeticInfo(
94
- size=self.output_size or self._expected_result_size(),
95
- fraction_places=self.arg.fraction_places,
95
+ size=self.output_size or self._expected_result_size(eff_arg),
96
+ fraction_places=eff_arg.fraction_places,
96
97
  is_signed=is_signed,
97
98
  bounds=bounds if (is_signed or min(bounds) >= 0) else None,
98
99
  )
99
100
 
101
+ def zero_input_for_extension(self) -> pydantic.NonNegativeInt:
102
+ eff_arg = self.arg.limit_fraction_places(self.machine_precision)
103
+ if (self.output_size or eff_arg.size) == 1:
104
+ return 0
105
+ return (
106
+ self.output_size or self._expected_result_size(self.arg)
107
+ ) - self.arg.size
108
+
100
109
 
101
110
  class Sign(UnaryOpParams):
102
111
  output_name = "sign"
@@ -1,3 +1,5 @@
1
+ from classiq.interface.generator.functions.classical_type import CLASSICAL_ATTRIBUTES
2
+
1
3
  SUPPORTED_BUILTIN_FUNCTIONS = {"len", "sum", "print"}
2
4
 
3
5
  SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS = {
@@ -9,8 +11,6 @@ SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS = {
9
11
  "get_type",
10
12
  "struct_literal",
11
13
  "get_field",
12
- "fraction_digits",
13
- "is_signed",
14
14
  "molecule_problem_to_hamiltonian",
15
15
  "fock_hamiltonian_problem_to_hamiltonian",
16
16
  "molecule_ground_state_solution_post_process",
@@ -20,3 +20,7 @@ SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS = {
20
20
  "BitwiseOr",
21
21
  *SUPPORTED_BUILTIN_FUNCTIONS,
22
22
  }
23
+
24
+ SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS_QMOD = (
25
+ SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS - CLASSICAL_ATTRIBUTES
26
+ )
@@ -8,3 +8,13 @@ from .pauli import Pauli
8
8
  from .qsvm_feature_map_entanglement import QSVMFeatureMapEntanglement
9
9
 
10
10
  BUILTIN_ENUMS = dict(filter(lambda pair: isinstance(pair[1], EnumMeta), vars().items()))
11
+
12
+ __all__ = [
13
+ "Element",
14
+ "FermionMapping",
15
+ "FinanceFunctionType",
16
+ "LadderOperator",
17
+ "Optimizer",
18
+ "Pauli",
19
+ "QSVMFeatureMapEntanglement",
20
+ ]
@@ -2,4 +2,8 @@ import enum
2
2
 
3
3
 
4
4
  class ClassicalEnum(int, enum.Enum):
5
- pass
5
+ def __str__(self) -> str:
6
+ return f"{type(self).__name__}.{self.name}"
7
+
8
+ def __repr__(self) -> str:
9
+ return str(self)
@@ -1,4 +1,5 @@
1
1
  import ast
2
+ import sys
2
3
  from typing import Any, Mapping, Optional, Type
3
4
 
4
5
  import pydantic
@@ -9,7 +10,7 @@ from classiq.interface.generator.arith.arithmetic_expression_validator import (
9
10
  DEFAULT_SUPPORTED_FUNC_NAMES,
10
11
  )
11
12
  from classiq.interface.generator.expressions.atomic_expression_functions import (
12
- SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS,
13
+ SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS_QMOD,
13
14
  )
14
15
  from classiq.interface.generator.expressions.evaluated_expression import (
15
16
  EvaluatedExpression,
@@ -34,13 +35,19 @@ class Expression(HashableASTNode):
34
35
  @pydantic.validator("expr")
35
36
  def validate_expression(cls, expr: str) -> str:
36
37
  supported_functions = (
37
- SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS
38
+ SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS_QMOD
38
39
  | set(SYMPY_SUPPORTED_EXPRESSIONS)
39
40
  | set(DEFAULT_SUPPORTED_FUNC_NAMES)
40
41
  )
41
42
  validate_expression_str(expr, supported_functions=supported_functions)
42
43
  return expr
43
44
 
45
+ @pydantic.validator("expr")
46
+ def format_expression(cls, expr: str) -> str:
47
+ if sys.version_info >= (3, 9):
48
+ expr = ast.unparse(ast.parse(expr))
49
+ return expr
50
+
44
51
  def is_evaluated(self) -> bool:
45
52
  return self._evaluated_expr is not None
46
53
 
@@ -0,0 +1,89 @@
1
+ from typing import Optional, Tuple, Union
2
+
3
+ from classiq.interface.generator.expressions.expression import Expression
4
+ from classiq.interface.generator.expressions.qmod_sized_proxy import QmodSizedProxy
5
+ from classiq.interface.model.handle_binding import (
6
+ HandleBinding,
7
+ SlicedHandleBinding,
8
+ SubscriptHandleBinding,
9
+ )
10
+
11
+ from classiq.exceptions import ClassiqValueError
12
+
13
+ ILLEGAL_SLICING_STEP_MSG = "Slicing with a step of a quantum variable is not supported"
14
+ SLICE_OUT_OF_BOUNDS_MSG = "Slice end index out of bounds"
15
+ QARRAY_ELEMENT_NOT_SUBSCRIPTABLE = "Subscripting an element in QArray is illegal"
16
+
17
+
18
+ class QmodQArrayProxy(QmodSizedProxy):
19
+ def __init__(
20
+ self,
21
+ name: str,
22
+ size: int,
23
+ slice_: Optional[Tuple[int, int]] = None,
24
+ index_: Optional[int] = None,
25
+ ) -> None:
26
+ super().__init__(size)
27
+ self._name = name
28
+ self._slice = slice_
29
+ self._index = index_
30
+
31
+ def __getitem__(self, key: Union[slice, int]) -> "QmodQArrayProxy":
32
+ if self._index is not None:
33
+ raise ClassiqValueError(QARRAY_ELEMENT_NOT_SUBSCRIPTABLE)
34
+
35
+ new_index: Optional[int] = None
36
+
37
+ if isinstance(key, slice):
38
+ if key.step is not None:
39
+ raise ClassiqValueError(ILLEGAL_SLICING_STEP_MSG)
40
+ new_slice = self._get_new_slice(key.start, key.stop)
41
+ else:
42
+ new_slice = self._get_new_slice(key, key + 1)
43
+ new_index = new_slice[0]
44
+
45
+ if (self._slice is not None and new_slice[1] > self._slice[1]) or new_slice[
46
+ 1
47
+ ] > self._size:
48
+ raise ClassiqValueError(SLICE_OUT_OF_BOUNDS_MSG)
49
+
50
+ return QmodQArrayProxy(
51
+ self._name, self._size, slice_=new_slice, index_=new_index
52
+ )
53
+
54
+ def _get_new_slice(self, start: int, end: int) -> Tuple[int, int]:
55
+ if self._slice is not None:
56
+ return self._slice[0] + start, self._slice[0] + end
57
+ return start, end
58
+
59
+ @property
60
+ def index(self) -> Optional[int]:
61
+ return self._index
62
+
63
+ @property
64
+ def slice(self) -> Optional[Tuple[int, int]]:
65
+ return self._slice
66
+
67
+ @property
68
+ def handle(self) -> HandleBinding:
69
+ if self._index is not None:
70
+ return SubscriptHandleBinding(
71
+ name=self._name,
72
+ index=Expression(expr=str(self._index)),
73
+ )
74
+
75
+ if self._slice is not None:
76
+ return SlicedHandleBinding(
77
+ name=self._name,
78
+ start=Expression(expr=str(self._slice[0])),
79
+ end=Expression(expr=str(self._slice[1])),
80
+ )
81
+
82
+ return HandleBinding(name=self._name)
83
+
84
+ def __len__(self) -> int:
85
+ if (slice_ := self.slice) is not None:
86
+ return slice_[1] - slice_[0]
87
+ elif self.index is not None:
88
+ return 1
89
+ return self._size
@@ -1,6 +1,9 @@
1
+ from typing import Any, Mapping
2
+
1
3
  from sympy import Symbol
2
4
 
3
5
  from classiq.interface.generator.expressions.qmod_sized_proxy import QmodSizedProxy
6
+ from classiq.interface.model.handle_binding import HandleBinding
4
7
 
5
8
 
6
9
  class QmodQScalarProxy(Symbol, QmodSizedProxy):
@@ -9,6 +12,11 @@ class QmodQScalarProxy(Symbol, QmodSizedProxy):
9
12
 
10
13
  def __init__(self, name: str, size: int) -> None:
11
14
  super().__init__(size)
15
+ self.name = name
16
+
17
+ @property
18
+ def handle(self) -> HandleBinding:
19
+ return HandleBinding(name=self.name)
12
20
 
13
21
 
14
22
  class QmodQBitProxy(QmodQScalarProxy):
@@ -24,6 +32,10 @@ class QmodQNumProxy(QmodQScalarProxy):
24
32
  self._fraction_digits = fraction_digits
25
33
  self._is_signed = is_signed
26
34
 
35
+ @property
36
+ def size(self) -> int:
37
+ return self._size
38
+
27
39
  @property
28
40
  def fraction_digits(self) -> int:
29
41
  return self._fraction_digits
@@ -31,3 +43,11 @@ class QmodQNumProxy(QmodQScalarProxy):
31
43
  @property
32
44
  def is_signed(self) -> bool:
33
45
  return self._is_signed
46
+
47
+ @property
48
+ def fields(self) -> Mapping[str, Any]:
49
+ return {
50
+ "size": self.size,
51
+ "is_signed": self.is_signed,
52
+ "fraction_digits": self.fraction_digits,
53
+ }
@@ -1,6 +1,28 @@
1
+ from typing import TYPE_CHECKING, Any, Mapping
2
+
3
+ from classiq.exceptions import ClassiqNotImplementedError
4
+
5
+ if TYPE_CHECKING:
6
+ from classiq.interface.model.handle_binding import HandleBinding
7
+
8
+
1
9
  class QmodSizedProxy:
2
10
  def __init__(self, size: int) -> None:
3
11
  self._size = size
4
12
 
5
13
  def __len__(self) -> int:
6
14
  return self._size
15
+
16
+ @property
17
+ def handle(self) -> "HandleBinding":
18
+ raise ClassiqNotImplementedError("cannot compute handle")
19
+
20
+ @property
21
+ def len(self) -> int:
22
+ return self._size
23
+
24
+ @property
25
+ def fields(self) -> Mapping[str, Any]:
26
+ return {
27
+ "len": self.len,
28
+ }
@@ -58,7 +58,16 @@ SPECIAL_FUNCTIONS: List[str] = [
58
58
  "catalan",
59
59
  ]
60
60
  PIECEWISE_FUNCTIONS: List[str] = ["Piecewise", "Heaviside"]
61
- CONSTANTS: List[str] = ["pi", "E", "I", "GoldenRatio", "EulerGamma", "Catalan"]
61
+ NUMERIC_CONSTANTS: List[str] = [
62
+ "pi",
63
+ "E",
64
+ "I",
65
+ "GoldenRatio",
66
+ "EulerGamma",
67
+ "Catalan",
68
+ ]
69
+ CONSTANTS: List[str] = NUMERIC_CONSTANTS + ["true", "false"]
70
+
62
71
  DATA_TYPES: List[str] = ["Matrix"]
63
72
  LOGIC_OPERATORS: List[str] = [
64
73
  "And",
@@ -423,7 +423,7 @@ UNITARY_FUNCTION = QuantumFunctionDeclaration(
423
423
  DEFAULT_TARGET_NAME: PortDeclaration(
424
424
  name=DEFAULT_TARGET_NAME,
425
425
  direction=PortDeclarationDirection.Inout,
426
- size=Expression(expr="log(len(elements[0]), 2)"),
426
+ size=Expression(expr="log(get_field(elements[0], 'len'), 2)"),
427
427
  )
428
428
  },
429
429
  )
@@ -436,7 +436,7 @@ PREPARE_STATE_FUNCTION = QuantumFunctionDeclaration(
436
436
  "out": PortDeclaration(
437
437
  name="out",
438
438
  direction=PortDeclarationDirection.Output,
439
- size=Expression(expr="log(len(probabilities), 2)"),
439
+ size=Expression(expr="log(get_field(probabilities, 'len'), 2)"),
440
440
  )
441
441
  },
442
442
  )
@@ -448,7 +448,7 @@ PREPARE_AMPLITUDES_FUNCTION = QuantumFunctionDeclaration(
448
448
  "out": PortDeclaration(
449
449
  name="out",
450
450
  direction=PortDeclarationDirection.Output,
451
- size=Expression(expr="log(len(amplitudes), 2)"),
451
+ size=Expression(expr="log(get_field(amplitudes, 'len'), 2)"),
452
452
  )
453
453
  },
454
454
  )
@@ -467,7 +467,9 @@ ADD_FUNCTION = QuantumFunctionDeclaration(
467
467
  "result": PortDeclaration(
468
468
  name="result",
469
469
  direction=PortDeclarationDirection.Output,
470
- size=Expression(expr="Max(len(left), len(right)) + 1"),
470
+ size=Expression(
471
+ expr="Max(get_field(left, 'len'), get_field(right, 'len')) + 1"
472
+ ),
471
473
  ),
472
474
  },
473
475
  )
@@ -578,7 +580,7 @@ INPLACE_PREPARE_STATE = QuantumFunctionDeclaration(
578
580
  "target": PortDeclaration(
579
581
  name="target",
580
582
  direction=PortDeclarationDirection.Inout,
581
- size=Expression(expr="log(len(probabilities), 2)"),
583
+ size=Expression(expr="log(get_field(probabilities, 'len'), 2)"),
582
584
  )
583
585
  },
584
586
  )
@@ -591,7 +593,7 @@ INPLACE_PREPARE_AMPLITUDES = QuantumFunctionDeclaration(
591
593
  "target": PortDeclaration(
592
594
  name="target",
593
595
  direction=PortDeclarationDirection.Inout,
594
- size=Expression(expr="log(len(amplitudes), 2)"),
596
+ size=Expression(expr="log(get_field(amplitudes, 'len'), 2)"),
595
597
  )
596
598
  },
597
599
  )
@@ -26,7 +26,7 @@ SINGLE_PAULI_EXPONENT_FUNCTION = QuantumFunctionDeclaration(
26
26
  "qbv": PortDeclaration(
27
27
  name="qbv",
28
28
  direction=PortDeclarationDirection.Inout,
29
- size=Expression(expr="len(pauli_string)"),
29
+ size=Expression(expr="get_field(pauli_string, 'len')"),
30
30
  )
31
31
  },
32
32
  )
@@ -44,7 +44,9 @@ SUZUKI_TROTTER_FUNCTION = QuantumFunctionDeclaration(
44
44
  "qbv": PortDeclaration(
45
45
  name="qbv",
46
46
  direction=PortDeclarationDirection.Inout,
47
- size=Expression(expr="len(get_field(pauli_operator[0], 'pauli'))"),
47
+ size=Expression(
48
+ expr="get_field(get_field(pauli_operator[0], 'pauli'), 'len')"
49
+ ),
48
50
  )
49
51
  },
50
52
  )
@@ -60,7 +62,9 @@ QDRIFT_FUNCTION = QuantumFunctionDeclaration(
60
62
  "qbv": PortDeclaration(
61
63
  name="qbv",
62
64
  direction=PortDeclarationDirection.Inout,
63
- size=Expression(expr="len(get_field(pauli_operator[0], 'pauli'))"),
65
+ size=Expression(
66
+ expr="get_field(get_field(pauli_operator[0], 'pauli'), 'len')"
67
+ ),
64
68
  )
65
69
  },
66
70
  )
@@ -76,7 +80,9 @@ EXPONENTIATION_WITH_DEPTH_CONSTRAINT = QuantumFunctionDeclaration(
76
80
  "qbv": PortDeclaration(
77
81
  name="qbv",
78
82
  direction=PortDeclarationDirection.Inout,
79
- size=Expression(expr="len(get_field(pauli_operator[0], 'pauli'))"),
83
+ size=Expression(
84
+ expr="get_field(get_field(pauli_operator[0], 'pauli'), 'len')"
85
+ ),
80
86
  )
81
87
  },
82
88
  )