classiq 0.69.0__py3-none-any.whl → 0.70.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 (60) hide show
  1. classiq/analyzer/analyzer.py +0 -18
  2. classiq/analyzer/url_utils.py +9 -4
  3. classiq/applications/combinatorial_helpers/pyomo_utils.py +2 -0
  4. classiq/interface/_version.py +1 -1
  5. classiq/interface/backend/quantum_backend_providers.py +6 -0
  6. classiq/interface/chemistry/operator.py +1 -21
  7. classiq/interface/executor/quantum_instruction_set.py +1 -0
  8. classiq/interface/generator/arith/arithmetic.py +21 -6
  9. classiq/interface/generator/circuit_code/circuit_code.py +4 -0
  10. classiq/interface/generator/circuit_code/types_and_constants.py +1 -0
  11. classiq/interface/generator/expressions/atomic_expression_functions.py +1 -2
  12. classiq/interface/generator/expressions/expression_types.py +8 -2
  13. classiq/interface/generator/expressions/proxies/__init__.py +0 -0
  14. classiq/interface/generator/expressions/proxies/classical/__init__.py +0 -0
  15. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +75 -0
  16. classiq/interface/generator/expressions/proxies/classical/classical_proxy.py +26 -0
  17. classiq/interface/generator/expressions/proxies/classical/classical_scalar_proxy.py +32 -0
  18. classiq/interface/generator/expressions/proxies/classical/classical_struct_proxy.py +31 -0
  19. classiq/interface/generator/expressions/proxies/quantum/__init__.py +0 -0
  20. classiq/interface/generator/expressions/{qmod_qarray_proxy.py → proxies/quantum/qmod_qarray_proxy.py} +3 -1
  21. classiq/interface/generator/expressions/{qmod_qscalar_proxy.py → proxies/quantum/qmod_qscalar_proxy.py} +3 -1
  22. classiq/interface/generator/expressions/{qmod_qstruct_proxy.py → proxies/quantum/qmod_qstruct_proxy.py} +3 -1
  23. classiq/interface/generator/functions/classical_type.py +23 -29
  24. classiq/interface/generator/functions/type_name.py +26 -2
  25. classiq/interface/generator/generated_circuit_data.py +21 -3
  26. classiq/interface/generator/model/preferences/preferences.py +1 -0
  27. classiq/interface/generator/quantum_program.py +0 -1
  28. classiq/interface/model/native_function_definition.py +0 -10
  29. classiq/interface/model/quantum_type.py +15 -3
  30. classiq/model_expansions/atomic_expression_functions_defs.py +9 -3
  31. classiq/model_expansions/evaluators/arg_type_match.py +4 -2
  32. classiq/model_expansions/evaluators/classical_expression.py +2 -2
  33. classiq/model_expansions/evaluators/control.py +1 -1
  34. classiq/model_expansions/evaluators/parameter_types.py +58 -16
  35. classiq/model_expansions/evaluators/quantum_type_utils.py +7 -57
  36. classiq/model_expansions/expression_evaluator.py +3 -1
  37. classiq/model_expansions/generative_functions.py +63 -4
  38. classiq/model_expansions/quantum_operations/arithmetic/__init__.py +0 -0
  39. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +60 -0
  40. classiq/model_expansions/quantum_operations/assignment_result_processor.py +9 -0
  41. classiq/model_expansions/quantum_operations/quantum_function_call.py +0 -22
  42. classiq/model_expansions/scope.py +7 -6
  43. classiq/model_expansions/scope_initialization.py +17 -16
  44. classiq/model_expansions/transformers/model_renamer.py +13 -4
  45. classiq/model_expansions/visitors/variable_references.py +8 -4
  46. classiq/open_library/functions/__init__.py +2 -0
  47. classiq/open_library/functions/lookup_table.py +58 -0
  48. classiq/qmod/declaration_inferrer.py +3 -1
  49. classiq/qmod/qmod_parameter.py +8 -0
  50. classiq/qmod/qmod_variable.py +11 -14
  51. classiq/qmod/semantics/annotation/call_annotation.py +0 -28
  52. classiq/qmod/semantics/annotation/qstruct_annotator.py +21 -1
  53. classiq/qmod/semantics/validation/main_validation.py +1 -1
  54. classiq/qmod/semantics/validation/type_hints.py +29 -0
  55. classiq/qmod/utilities.py +38 -1
  56. {classiq-0.69.0.dist-info → classiq-0.70.0.dist-info}/METADATA +10 -12
  57. {classiq-0.69.0.dist-info → classiq-0.70.0.dist-info}/RECORD +60 -49
  58. {classiq-0.69.0.dist-info → classiq-0.70.0.dist-info}/WHEEL +1 -1
  59. /classiq/interface/generator/expressions/{qmod_struct_instance.py → proxies/classical/qmod_struct_instance.py} +0 -0
  60. /classiq/interface/generator/expressions/{qmod_sized_proxy.py → proxies/quantum/qmod_sized_proxy.py} +0 -0
@@ -1,9 +1,5 @@
1
1
  from typing import TYPE_CHECKING
2
2
 
3
- from classiq.interface.exceptions import ClassiqValueError
4
- from classiq.interface.generator.expressions.expression import Expression
5
- from classiq.interface.model.allocate import Allocate
6
- from classiq.interface.model.handle_binding import HandleBinding
7
3
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
8
4
 
9
5
  from classiq.model_expansions.closure import FunctionClosure
@@ -17,21 +13,12 @@ if TYPE_CHECKING:
17
13
  from classiq.model_expansions.interpreters.base_interpreter import BaseInterpreter
18
14
 
19
15
 
20
- ALLOCATE_COMPATIBILITY_ERROR_MESSAGE = (
21
- "'allocate' expects two argument: The number of qubits to allocate (integer) and "
22
- "the variable to be allocated (quantum variable)"
23
- )
24
-
25
-
26
16
  class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
27
17
  def __init__(self, interpreter: "BaseInterpreter") -> None:
28
18
  super().__init__(interpreter)
29
19
  self._model = self._interpreter._model
30
20
 
31
21
  def emit(self, call: QuantumFunctionCall, /) -> bool:
32
- if call.function == "allocate": # FIXME: Remove compatibility (CAD-25935)
33
- self._allocate_compatibility(call)
34
- return True
35
22
  function = self._interpreter.evaluate(call.function).as_type(FunctionClosure)
36
23
  args = call.positional_args
37
24
  with ErrorManager().call(function.name):
@@ -40,15 +27,6 @@ class QuantumFunctionCallEmitter(CallEmitter[QuantumFunctionCall]):
40
27
  )
41
28
  return True
42
29
 
43
- def _allocate_compatibility(self, call: QuantumFunctionCall) -> None:
44
- if len(call.positional_args) != 2:
45
- raise ClassiqValueError(ALLOCATE_COMPATIBILITY_ERROR_MESSAGE)
46
- size, target = call.positional_args
47
- if not isinstance(size, Expression) or not isinstance(target, HandleBinding):
48
- raise ClassiqValueError(ALLOCATE_COMPATIBILITY_ERROR_MESSAGE)
49
- allocate = Allocate(size=size, target=target, source_ref=call.source_ref)
50
- self._interpreter.emit_statement(allocate)
51
-
52
30
 
53
31
  class DeclarativeQuantumFunctionCallEmitter(
54
32
  QuantumFunctionCallEmitter, DeclarativeCallEmitter
@@ -19,7 +19,7 @@ from classiq.interface.generator.expressions.expression import Expression
19
19
  from classiq.interface.generator.expressions.expression_constants import (
20
20
  CPARAM_EXECUTION_SUFFIX_PATTERN,
21
21
  )
22
- from classiq.interface.generator.expressions.qmod_struct_instance import (
22
+ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
23
23
  QmodStructInstance,
24
24
  )
25
25
  from classiq.interface.generator.functions.type_name import TypeName
@@ -72,9 +72,9 @@ class QuantumSymbol:
72
72
  array_length = self.quantum_type.length_value
73
73
  if start < 0 or end > array_length:
74
74
  raise ClassiqExpansionError(
75
- f"Slice [{start}:{end}] is out of bounds of "
76
- f"{self.quantum_type.type_name} {str(self.handle)!r} of length "
77
- f"{array_length}"
75
+ f"Slice [{start}:{end}] is out of bounds for "
76
+ f"{self.quantum_type.type_name.lower()} {str(self.handle)!r} (of "
77
+ f"length {array_length})"
78
78
  )
79
79
  return QuantumSymbol(
80
80
  handle=SlicedHandleBinding(
@@ -96,8 +96,9 @@ class QuantumSymbol:
96
96
  array_length = self.quantum_type.length_value
97
97
  if index < 0 or index >= array_length:
98
98
  raise ClassiqExpansionError(
99
- f"Subscript {index} is out of bounds of {self.quantum_type.type_name} "
100
- f"{str(self.handle)!r} of length {array_length}"
99
+ f"Index {index} is out of bounds for "
100
+ f"{self.quantum_type.type_name.lower()} {str(self.handle)!r} (of "
101
+ f"length {array_length})"
101
102
  )
102
103
  return QuantumSymbol(
103
104
  handle=SubscriptHandleBinding(
@@ -1,16 +1,17 @@
1
1
  from collections.abc import Sequence
2
- from typing import Any
3
2
 
4
- from classiq.interface.exceptions import ClassiqError
3
+ from classiq.interface.exceptions import ClassiqError, ClassiqInternalExpansionError
5
4
  from classiq.interface.generator.constant import Constant
5
+ from classiq.interface.generator.functions.classical_type import (
6
+ ClassicalArray,
7
+ ClassicalList,
8
+ )
6
9
  from classiq.interface.generator.functions.concrete_types import ConcreteClassicalType
7
10
  from classiq.interface.model.handle_binding import HandleBinding
8
11
  from classiq.interface.model.model import MAIN_FUNCTION_NAME, Model
9
12
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
10
13
  from classiq.interface.model.port_declaration import PortDeclaration
11
- from classiq.interface.model.quantum_function_declaration import (
12
- PositionalArg,
13
- )
14
+ from classiq.interface.model.quantum_function_declaration import PositionalArg
14
15
 
15
16
  from classiq.model_expansions.closure import FunctionClosure, GenerativeFunctionClosure
16
17
  from classiq.model_expansions.evaluators.classical_expression import (
@@ -125,14 +126,6 @@ def init_builtin_types() -> None:
125
126
  QMODULE.type_decls |= BUILTIN_STRUCT_DECLARATIONS
126
127
 
127
128
 
128
- def _add_exec_param_parts_to_scope(param_val: Any, scope: Scope) -> None:
129
- if not isinstance(param_val, list):
130
- scope[str(param_val)] = Evaluated(value=param_val)
131
- return
132
- for param_part in param_val:
133
- _add_exec_param_parts_to_scope(param_part, scope)
134
-
135
-
136
129
  def init_exec_params(model: Model, scope: Scope) -> dict[str, ConcreteClassicalType]:
137
130
  if model.execution_parameters is not None:
138
131
  exec_params = model.execution_parameters
@@ -144,8 +137,16 @@ def init_exec_params(model: Model, scope: Scope) -> dict[str, ConcreteClassicalT
144
137
  ).param_decls
145
138
  }
146
139
  for param_name, param_type in exec_params.items():
147
- param_val = param_type.as_symbolic(param_name)
140
+ param_val = param_type.get_classical_proxy(
141
+ handle=HandleBinding(name=param_name)
142
+ )
148
143
  scope[param_name] = Evaluated(value=param_val)
149
- if isinstance(param_val, list):
150
- _add_exec_param_parts_to_scope(param_val, scope)
151
144
  return exec_params
145
+
146
+
147
+ def _get_shape(classical_type: ConcreteClassicalType) -> tuple[int, ...]:
148
+ if isinstance(classical_type, ClassicalList):
149
+ raise ClassiqInternalExpansionError("Unexpected classical list")
150
+ if isinstance(classical_type, ClassicalArray):
151
+ return classical_type.size, *_get_shape(classical_type.element_type)
152
+ return ()
@@ -1,4 +1,5 @@
1
1
  import ast
2
+ import re
2
3
  from collections.abc import Mapping, Sequence
3
4
  from dataclasses import dataclass
4
5
  from typing import TypeVar, cast
@@ -16,6 +17,12 @@ from classiq.model_expansions.visitors.variable_references import VarRefCollecto
16
17
  AST_NODE = TypeVar("AST_NODE", bound=NodeType)
17
18
 
18
19
 
20
+ def _replace_full_word(pattern: str, substitution: str, target: str) -> str:
21
+ return re.sub(
22
+ rf"(^|\b|\W)({re.escape(pattern)})($|\b|\W)", rf"\1{substitution}\3", target
23
+ )
24
+
25
+
19
26
  @dataclass(frozen=True)
20
27
  class HandleRenaming:
21
28
  source_handle: HandleBinding
@@ -74,7 +81,7 @@ class ModelRenamer:
74
81
  symbol_mapping: SymbolRenaming,
75
82
  expression: Expression,
76
83
  ) -> Expression:
77
- vrc = VarRefCollector(ignore_duplicated_handles=True)
84
+ vrc = VarRefCollector(ignore_duplicated_handles=True, ignore_sympy_symbols=True)
78
85
  vrc.visit(ast.parse(expression.expr))
79
86
 
80
87
  handle_names = {
@@ -87,10 +94,12 @@ class ModelRenamer:
87
94
  new_handle = handle.collapse()
88
95
  for handle_to_replace, replacement in handle_names.items():
89
96
  new_handle = new_handle.replace_prefix(handle_to_replace, replacement)
90
- new_expr_str = new_expr_str.replace(str(handle), str(new_handle))
97
+ new_expr_str = _replace_full_word(
98
+ str(handle), str(new_handle), new_expr_str
99
+ )
91
100
  if handle.qmod_expr != str(handle):
92
- new_expr_str = new_expr_str.replace(
93
- handle.qmod_expr, new_handle.qmod_expr
101
+ new_expr_str = _replace_full_word(
102
+ str(handle.qmod_expr), str(new_handle.qmod_expr), new_expr_str
94
103
  )
95
104
 
96
105
  new_expr = Expression(expr=new_expr_str)
@@ -24,10 +24,14 @@ from classiq.interface.model.handle_binding import (
24
24
 
25
25
  class VarRefCollector(ast.NodeVisitor):
26
26
  def __init__(
27
- self, ignore_duplicated_handles: bool = False, unevaluated: bool = False
27
+ self,
28
+ ignore_duplicated_handles: bool = False,
29
+ unevaluated: bool = False,
30
+ ignore_sympy_symbols: bool = False,
28
31
  ) -> None:
29
32
  self._var_handles: dict[HandleBinding, bool] = {}
30
33
  self._ignore_duplicated_handles = ignore_duplicated_handles
34
+ self._ignore_sympy_symbols = ignore_sympy_symbols
31
35
  self._unevaluated = unevaluated
32
36
  self._is_nested = False
33
37
 
@@ -117,9 +121,9 @@ class VarRefCollector(ast.NodeVisitor):
117
121
  return handle
118
122
 
119
123
  def visit_Name(self, node: ast.Name) -> Optional[HandleBinding]:
120
- if node.id in set(SYMPY_SUPPORTED_EXPRESSIONS) | set(
121
- DEFAULT_SUPPORTED_FUNC_NAMES
122
- ):
124
+ if not self._ignore_sympy_symbols and node.id in set(
125
+ SYMPY_SUPPORTED_EXPRESSIONS
126
+ ) | set(DEFAULT_SUPPORTED_FUNC_NAMES):
123
127
  return None
124
128
  handle = HandleBinding(name=node.id)
125
129
  if not self._is_nested:
@@ -9,6 +9,7 @@ from .grover import *
9
9
  from .hea import *
10
10
  from .linear_pauli_rotation import *
11
11
  from .linear_pauli_rotation import _single_pauli
12
+ from .lookup_table import span_lookup_table
12
13
  from .modular_exponentiation import *
13
14
  from .modular_exponentiation import _check_msb
14
15
  from .qaoa_penalty import *
@@ -131,6 +132,7 @@ __all__ = [
131
132
  "qsvt_lcu_step",
132
133
  "qsvt_step",
133
134
  "reflect_about_zero",
135
+ "span_lookup_table",
134
136
  "suzuki_trotter",
135
137
  "swap_test",
136
138
  "switch",
@@ -0,0 +1,58 @@
1
+ from itertools import product
2
+
3
+ from classiq.interface.exceptions import ClassiqValueError
4
+
5
+ from classiq.qmod.builtins.operations import assign, bind, within_apply
6
+ from classiq.qmod.qmod_variable import QNum
7
+ from classiq.qmod.symbolic import subscript
8
+ from classiq.qmod.utilities import RealFunction, get_temp_var_name, qnum_values
9
+
10
+
11
+ def _get_qnum_values(num: QNum) -> list[float]:
12
+ size = num.size
13
+ is_signed = num.is_signed
14
+ fraction_digits = num.fraction_digits
15
+ if (
16
+ not isinstance(size, int)
17
+ or not isinstance(is_signed, bool)
18
+ or not isinstance(fraction_digits, int)
19
+ ):
20
+ raise ClassiqValueError(
21
+ "Must call 'span_lookup_table' inside a generative qfunc"
22
+ )
23
+
24
+ return qnum_values(size, is_signed, fraction_digits)
25
+
26
+
27
+ def span_lookup_table(func: RealFunction, *targets: QNum) -> QNum:
28
+ """
29
+ Applies a classical function to quantum numbers.
30
+
31
+ Works by reducing the function into a lookup table over all the possible values
32
+ of the quantum numbers.
33
+
34
+ Args:
35
+ func: A Python function
36
+ *targets: One or more initialized quantum numbers
37
+
38
+ Returns:
39
+ The quantum result of applying func to targets
40
+
41
+ Notes:
42
+ Must be called inside a generative function (`@qfunc(generative=True)`)
43
+ """
44
+ if len(targets) == 0:
45
+ raise ClassiqValueError("No targets specified")
46
+
47
+ target_vals = [_get_qnum_values(target) for target in targets]
48
+ lookup_table = [func(*vals[::-1]) for vals in product(*target_vals[::-1])]
49
+
50
+ index_size = sum(target.size for target in targets)
51
+ index: QNum = QNum(get_temp_var_name(), size=index_size)
52
+ result: QNum = QNum(get_temp_var_name("result"))
53
+
54
+ within_apply(
55
+ lambda: bind(list(targets), index),
56
+ lambda: assign(subscript(lookup_table, index), result),
57
+ )
58
+ return result
@@ -38,6 +38,7 @@ from classiq.qmod.model_state_container import ModelStateContainer
38
38
  from classiq.qmod.python_classical_type import PythonClassicalType
39
39
  from classiq.qmod.qmod_variable import QVar
40
40
  from classiq.qmod.quantum_callable import QCallableList
41
+ from classiq.qmod.semantics.validation.type_hints import validate_annotation
41
42
  from classiq.qmod.utilities import unmangle_keyword, version_portable_get_args
42
43
 
43
44
  if sys.version_info[0:2] >= (3, 9):
@@ -123,7 +124,7 @@ def _extract_operand_decl(
123
124
 
124
125
 
125
126
  def _extract_operand_param(py_type: Any) -> tuple[Optional[str], Any]:
126
- if sys.version_info[0:2] < (3, 9) or get_origin(py_type) is not Annotated:
127
+ if get_origin(py_type) is not Annotated:
127
128
  return None, py_type
128
129
  args = get_args(py_type)
129
130
  if len(args) == 2:
@@ -163,6 +164,7 @@ def _extract_positional_args(
163
164
  ) -> Sequence[AnonPositionalArg]:
164
165
  result: list[AnonPositionalArg] = []
165
166
  for name, py_type in args:
167
+ validate_annotation(py_type)
166
168
  if name == "return":
167
169
  continue
168
170
  name = unmangle_keyword(name)
@@ -22,6 +22,11 @@ from classiq.qmod.cparam import ( # noqa: F401
22
22
  CParamScalar,
23
23
  CReal,
24
24
  )
25
+ from classiq.qmod.generative import (
26
+ generative_mode_context,
27
+ interpret_expression,
28
+ is_generative_mode,
29
+ )
25
30
  from classiq.qmod.model_state_container import ModelStateContainer
26
31
  from classiq.qmod.symbolic_expr import Symbolic, SymbolicExpr
27
32
 
@@ -66,6 +71,9 @@ class CParamList(CParam):
66
71
 
67
72
  @property
68
73
  def len(self) -> CParamScalar:
74
+ if is_generative_mode():
75
+ with generative_mode_context(False):
76
+ return interpret_expression(str(self.len))
69
77
  return CParamScalar(f"get_field({self}, 'len')")
70
78
 
71
79
 
@@ -23,7 +23,7 @@ from typing_extensions import ParamSpec, Self, _AnnotatedAlias
23
23
  from classiq.interface.exceptions import ClassiqValueError
24
24
  from classiq.interface.generator.expressions.expression import Expression
25
25
  from classiq.interface.generator.expressions.non_symbolic_expr import NonSymbolicExpr
26
- from classiq.interface.generator.expressions.qmod_qarray_proxy import (
26
+ from classiq.interface.generator.expressions.proxies.quantum.qmod_qarray_proxy import (
27
27
  ILLEGAL_SLICE_MSG,
28
28
  ILLEGAL_SLICING_STEP_MSG,
29
29
  )
@@ -72,12 +72,6 @@ from classiq.qmod.utilities import (
72
72
  )
73
73
 
74
74
 
75
- def _is_input_output_typehint(type_hint: Any) -> bool:
76
- return isinstance(type_hint, _AnnotatedAlias) and isinstance(
77
- type_hint.__metadata__[0], PortDeclarationDirection
78
- )
79
-
80
-
81
75
  def get_type_hint_expr(type_hint: Any) -> str:
82
76
  if isinstance(type_hint, ForwardRef): # expression in string literal
83
77
  return str(type_hint.__forward_arg__)
@@ -143,9 +137,12 @@ class QVar(Symbolic):
143
137
 
144
138
  @staticmethod
145
139
  def from_type_hint(type_hint: Any) -> Optional[type["QVar"]]:
146
- if _is_input_output_typehint(type_hint):
147
- return QVar.from_type_hint(type_hint.__args__[0])
148
- type_ = get_origin(type_hint) or type_hint
140
+ non_annotated_type = (
141
+ type_hint.__origin__
142
+ if isinstance(type_hint, _AnnotatedAlias)
143
+ else type_hint
144
+ )
145
+ type_ = get_origin(non_annotated_type) or non_annotated_type
149
146
  if issubclass(type_, QVar):
150
147
  if issubclass(type_, QStruct):
151
148
  with _no_current_expandable():
@@ -170,10 +167,10 @@ class QVar(Symbolic):
170
167
 
171
168
  @classmethod
172
169
  def port_direction(cls, type_hint: Any) -> PortDeclarationDirection:
173
- if _is_input_output_typehint(type_hint):
174
- assert len(type_hint.__metadata__) >= 1
175
- return type_hint.__metadata__[0]
176
- assert type_hint == cls or get_origin(type_hint) == cls
170
+ if isinstance(type_hint, _AnnotatedAlias):
171
+ for metadata in type_hint.__metadata__:
172
+ if isinstance(metadata, PortDeclarationDirection):
173
+ return metadata
177
174
  return PortDeclarationDirection.Inout
178
175
 
179
176
  def __str__(self) -> str:
@@ -3,18 +3,9 @@ from contextlib import contextmanager
3
3
  from typing import Any, Optional
4
4
 
5
5
  from classiq.interface.exceptions import ClassiqError
6
- from classiq.interface.generator.expressions.expression import Expression
7
- from classiq.interface.generator.functions.classical_type import Integer
8
- from classiq.interface.generator.functions.port_declaration import (
9
- PortDeclarationDirection,
10
- )
11
- from classiq.interface.model.classical_parameter_declaration import (
12
- ClassicalParameterDeclaration,
13
- )
14
6
  from classiq.interface.model.model import Model
15
7
  from classiq.interface.model.model_visitor import ModelVisitor
16
8
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
17
- from classiq.interface.model.port_declaration import PortDeclaration
18
9
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
19
10
  from classiq.interface.model.quantum_function_declaration import (
20
11
  AnonQuantumOperandDeclaration,
@@ -24,36 +15,17 @@ from classiq.interface.model.quantum_function_declaration import (
24
15
  from classiq.interface.model.quantum_lambda_function import (
25
16
  QuantumLambdaFunction,
26
17
  )
27
- from classiq.interface.model.quantum_type import QuantumBitvector
28
18
 
29
19
  from classiq.qmod.builtins.functions import BUILTIN_FUNCTION_DECLARATIONS
30
20
  from classiq.qmod.semantics.annotation.qstruct_annotator import QStructAnnotator
31
21
  from classiq.qmod.semantics.error_manager import ErrorManager
32
22
  from classiq.qmod.semantics.lambdas import get_renamed_parameters
33
23
 
34
- ALLOCATE_DECL_FOR_COMPATIBILITY = QuantumFunctionDeclaration(
35
- name="allocate",
36
- positional_arg_declarations=[
37
- ClassicalParameterDeclaration(
38
- name="num_qubits",
39
- classical_type=Integer(),
40
- ),
41
- PortDeclaration(
42
- name="out",
43
- quantum_type=QuantumBitvector(length=Expression(expr="num_qubits")),
44
- direction=PortDeclarationDirection.Output,
45
- ),
46
- ],
47
- )
48
-
49
24
 
50
25
  def _annotate_function_call_decl(
51
26
  fc: QuantumFunctionCall,
52
27
  function_dict: Mapping[str, QuantumFunctionDeclaration],
53
28
  ) -> None:
54
- if fc.function == "allocate": # FIXME: Remove compatibility (CAD-25935)
55
- fc.set_func_decl(ALLOCATE_DECL_FOR_COMPATIBILITY)
56
- return
57
29
  if fc._func_decl is None:
58
30
  func_decl = function_dict.get(fc.func_name)
59
31
  if func_decl is None:
@@ -9,8 +9,18 @@ class QStructAnnotator(ModelVisitor):
9
9
  self._visited: set[TypeName] = set()
10
10
 
11
11
  def visit_TypeName(self, type_name: TypeName) -> None:
12
+ self._annotate_quantum_struct(type_name)
13
+ self._annotate_classical_struct(type_name)
14
+
15
+ def _annotate_quantum_struct(self, type_name: TypeName) -> None:
16
+ if (
17
+ type_name.has_classical_struct_decl
18
+ or type_name.has_fields
19
+ or type_name in self._visited
20
+ ):
21
+ return
12
22
  decl = QMODULE.qstruct_decls.get(type_name.name)
13
- if decl is None or type_name.has_fields or (type_name in self._visited):
23
+ if decl is None:
14
24
  return
15
25
  self._visited.add(type_name)
16
26
  new_fields = {
@@ -21,3 +31,13 @@ class QStructAnnotator(ModelVisitor):
21
31
  # qstructs
22
32
  self.visit(new_fields)
23
33
  type_name.set_fields(new_fields)
34
+
35
+ def _annotate_classical_struct(self, type_name: TypeName) -> None:
36
+ if type_name.has_classical_struct_decl or type_name.has_fields:
37
+ return
38
+ decl = QMODULE.type_decls.get(type_name.name)
39
+ if decl is None:
40
+ return
41
+ type_name.set_classical_struct_decl(decl)
42
+ for field_type in decl.variables.values():
43
+ self.visit(field_type)
@@ -25,7 +25,7 @@ def _validate_main_classical_param_type(
25
25
  ) -> None:
26
26
  if isinstance(param, ClassicalList):
27
27
  raise ClassiqExpansionError(
28
- f"Classical array parameter {param_name!r} of function 'main' must must "
28
+ f"Classical array parameter {param_name!r} of function 'main' must "
29
29
  f"specify array length",
30
30
  )
31
31
  if isinstance(param, ClassicalArray):
@@ -0,0 +1,29 @@
1
+ from typing import Any
2
+
3
+ from typing_extensions import _AnnotatedAlias
4
+
5
+ from classiq.interface.exceptions import ClassiqValueError
6
+ from classiq.interface.generator.functions.port_declaration import (
7
+ PortDeclarationDirection,
8
+ )
9
+
10
+ annotation_map: dict[PortDeclarationDirection, str] = {
11
+ PortDeclarationDirection.Input: PortDeclarationDirection.Input.name,
12
+ PortDeclarationDirection.Output: PortDeclarationDirection.Output.name,
13
+ }
14
+
15
+
16
+ def validate_annotation(type_hint: Any) -> None:
17
+ if not isinstance(type_hint, _AnnotatedAlias):
18
+ return
19
+ directions: list[PortDeclarationDirection] = [
20
+ direction
21
+ for direction in type_hint.__metadata__
22
+ if isinstance(direction, PortDeclarationDirection)
23
+ ]
24
+ if len(directions) <= 1:
25
+ return
26
+ raise ClassiqValueError(
27
+ f"Multiple directions are not allowed in a single type hint: "
28
+ f"[{', '.join(annotation_map[direction] for direction in reversed(directions))}]\n"
29
+ )
classiq/qmod/utilities.py CHANGED
@@ -3,6 +3,7 @@ import inspect
3
3
  import itertools
4
4
  import keyword
5
5
  import sys
6
+ from collections import Counter
6
7
  from collections.abc import Iterable
7
8
  from enum import Enum as PythonEnum
8
9
  from types import FrameType
@@ -21,7 +22,7 @@ from typing import (
21
22
 
22
23
  from typing_extensions import ParamSpec
23
24
 
24
- from classiq.interface.generator.expressions.qmod_struct_instance import (
25
+ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
25
26
  QmodStructInstance,
26
27
  )
27
28
  from classiq.interface.source_reference import SourceReference
@@ -160,3 +161,39 @@ def suppress_return_value(func: Callable[Params, None]) -> Callable[Params, None
160
161
 
161
162
 
162
163
  Statements = Union[None, list[Union[None, "QVar"]], tuple[Union[None, "QVar"], ...]]
164
+
165
+
166
+ def _eval_qnum(val: int, size: int, is_signed: bool, fraction_digits: int) -> float:
167
+ if val < 0 or val >= 2**size:
168
+ raise ValueError
169
+ if size == 1 and is_signed and fraction_digits == 1:
170
+ return -0.5 if val == 1 else 0
171
+ if is_signed and val & (1 << (size - 1)) > 0:
172
+ val ^= 1 << (size - 1)
173
+ val -= 1 << (size - 1)
174
+ return val * 2**-fraction_digits
175
+
176
+
177
+ def qnum_values(size: int, is_signed: bool, fraction_digits: int) -> list[float]:
178
+ return [_eval_qnum(i, size, is_signed, fraction_digits) for i in range(2**size)]
179
+
180
+
181
+ def qnum_attributes(max_size: int) -> list[tuple[int, bool, int]]:
182
+ return [(1, True, 1)] + [
183
+ (size, is_signed, fraction_digits)
184
+ for size in range(1, max_size + 1)
185
+ for is_signed in (False, True)
186
+ for fraction_digits in range(size - int(is_signed) + 1)
187
+ ]
188
+
189
+
190
+ _VAR_NAME_COUNTER: Counter[str] = Counter()
191
+
192
+
193
+ def get_temp_var_name(var_name: str = "temp") -> str:
194
+ n = _VAR_NAME_COUNTER[var_name]
195
+ _VAR_NAME_COUNTER[var_name] += 1
196
+ return f"{var_name}_{n}"
197
+
198
+
199
+ RealFunction = Callable[Params, float]
@@ -1,26 +1,25 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: classiq
3
- Version: 0.69.0
3
+ Version: 0.70.0
4
4
  Summary: Classiq's Python SDK for quantum computing
5
- Home-page: https://classiq.io
6
5
  License: Proprietary
7
6
  Keywords: quantum computing,quantum circuits,quantum algorithms,QAD,QDL
8
7
  Author: Classiq Technologies
9
8
  Author-email: support@classiq.io
10
- Requires-Python: >=3.9,<3.13
9
+ Requires-Python: >=3.9, <3.13
11
10
  Classifier: Development Status :: 4 - Beta
11
+ Classifier: License :: Other/Proprietary License
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Intended Audience :: Education
14
14
  Classifier: Intended Audience :: Science/Research
15
15
  Classifier: License :: Other/Proprietary License
16
16
  Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: OS Independent
18
- Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
19
  Classifier: Programming Language :: Python :: 3.9
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
22
  Classifier: Programming Language :: Python :: 3.12
23
- Classifier: Programming Language :: Python :: 3 :: Only
24
23
  Classifier: Topic :: Scientific/Engineering
25
24
  Classifier: Topic :: Software Development
26
25
  Classifier: Topic :: Software Development :: Code Generators
@@ -33,12 +32,12 @@ Requires-Dist: ConfigArgParse (>=1.5.3,<2.0.0)
33
32
  Requires-Dist: Pyomo (>=6.5,<6.6)
34
33
  Requires-Dist: black (>=24.0,<25.0)
35
34
  Requires-Dist: httpx (>=0.23.0,<1)
36
- Requires-Dist: ipywidgets (>=7.7.1,<8.0.0) ; extra == "analyzer-sdk"
37
- Requires-Dist: jupyterlab (>=4.2.5,<5.0.0) ; extra == "analyzer-sdk"
35
+ Requires-Dist: ipywidgets ; extra == "analyzer-sdk"
36
+ Requires-Dist: jupyterlab ; extra == "analyzer-sdk"
38
37
  Requires-Dist: keyring (>=23.5.0,<24.0.0)
39
38
  Requires-Dist: matplotlib (>=3.4.3,<4.0.0)
40
39
  Requires-Dist: networkx (>=2.5.1,<3.0.0)
41
- Requires-Dist: notebook (>=7.2.2,<8.0.0) ; extra == "analyzer-sdk"
40
+ Requires-Dist: notebook ; extra == "analyzer-sdk"
42
41
  Requires-Dist: numexpr (>=2.7.3,<3.0.0)
43
42
  Requires-Dist: numpy (>=1.20.1,<2.0.0) ; python_version < "3.12"
44
43
  Requires-Dist: numpy (>=1.26.0,<2.0.0) ; python_version >= "3.12"
@@ -51,10 +50,9 @@ Requires-Dist: scipy (>=1.10.0,<2.0.0) ; python_version < "3.12"
51
50
  Requires-Dist: scipy (>=1.11.0,<2.0.0) ; python_version >= "3.12"
52
51
  Requires-Dist: sympy (>=1.13.0,<2.0.0)
53
52
  Requires-Dist: tabulate (>=0.8.9,<1)
54
- Requires-Dist: torch (>=2.0,<3.0) ; extra == "qml"
55
- Requires-Dist: torchvision (>=0.15,<1.0) ; extra == "qml"
53
+ Requires-Dist: torch ; extra == "qml"
54
+ Requires-Dist: torchvision ; extra == "qml"
56
55
  Requires-Dist: tqdm (>=4.67.1,<5.0.0)
57
- Project-URL: Documentation, https://docs.classiq.io/
58
56
  Description-Content-Type: text/markdown
59
57
 
60
58
  <p align="center">