classiq 0.70.0__py3-none-any.whl → 0.71.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 (56) hide show
  1. classiq/interface/_version.py +1 -1
  2. classiq/interface/debug_info/debug_info.py +4 -0
  3. classiq/interface/generator/expressions/expression_constants.py +0 -3
  4. classiq/interface/generator/expressions/expression_types.py +4 -2
  5. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +4 -0
  6. classiq/interface/generator/expressions/proxies/classical/classical_struct_proxy.py +5 -1
  7. classiq/interface/generator/expressions/proxies/classical/utils.py +34 -0
  8. classiq/interface/generator/functions/classical_type.py +1 -1
  9. classiq/interface/generator/functions/type_name.py +16 -0
  10. classiq/interface/generator/functions/type_qualifier.py +7 -0
  11. classiq/interface/generator/generated_circuit_data.py +1 -1
  12. classiq/interface/generator/quantum_function_call.py +8 -1
  13. classiq/interface/generator/synthesis_execution_parameter.py +1 -0
  14. classiq/interface/generator/types/compilation_metadata.py +1 -0
  15. classiq/interface/ide/visual_model.py +1 -0
  16. classiq/interface/interface_version.py +1 -1
  17. classiq/interface/model/allocate.py +7 -0
  18. classiq/interface/model/block.py +12 -0
  19. classiq/interface/model/classical_if.py +4 -0
  20. classiq/interface/model/inplace_binary_operation.py +4 -0
  21. classiq/interface/model/model.py +3 -1
  22. classiq/interface/model/phase_operation.py +4 -0
  23. classiq/interface/model/port_declaration.py +3 -0
  24. classiq/interface/model/power.py +4 -0
  25. classiq/interface/model/quantum_expressions/quantum_expression.py +4 -0
  26. classiq/interface/model/quantum_function_call.py +4 -0
  27. classiq/interface/model/quantum_function_declaration.py +1 -1
  28. classiq/interface/model/quantum_statement.py +5 -0
  29. classiq/interface/model/quantum_type.py +22 -0
  30. classiq/interface/model/repeat.py +4 -0
  31. classiq/interface/model/statement_block.py +3 -0
  32. classiq/interface/model/variable_declaration_statement.py +5 -0
  33. classiq/model_expansions/capturing/captured_vars.py +156 -34
  34. classiq/model_expansions/evaluators/classical_type_inference.py +70 -0
  35. classiq/model_expansions/evaluators/parameter_types.py +14 -0
  36. classiq/model_expansions/expression_evaluator.py +0 -11
  37. classiq/model_expansions/function_builder.py +2 -8
  38. classiq/model_expansions/generative_functions.py +7 -30
  39. classiq/model_expansions/interpreters/base_interpreter.py +3 -4
  40. classiq/model_expansions/quantum_operations/call_emitter.py +46 -6
  41. classiq/model_expansions/quantum_operations/emitter.py +41 -0
  42. classiq/model_expansions/quantum_operations/expression_evaluator.py +4 -0
  43. classiq/model_expansions/scope.py +0 -8
  44. classiq/model_expansions/scope_initialization.py +20 -28
  45. classiq/qmod/__init__.py +3 -1
  46. classiq/qmod/declaration_inferrer.py +52 -24
  47. classiq/qmod/native/pretty_printer.py +25 -3
  48. classiq/qmod/pretty_print/pretty_printer.py +31 -14
  49. classiq/qmod/python_classical_type.py +12 -1
  50. classiq/qmod/qfunc.py +33 -8
  51. classiq/qmod/qmod_variable.py +188 -147
  52. classiq/qmod/quantum_function.py +3 -4
  53. classiq/qmod/semantics/validation/type_hints.py +19 -10
  54. {classiq-0.70.0.dist-info → classiq-0.71.0.dist-info}/METADATA +1 -1
  55. {classiq-0.70.0.dist-info → classiq-0.71.0.dist-info}/RECORD +56 -52
  56. {classiq-0.70.0.dist-info → classiq-0.71.0.dist-info}/WHEEL +0 -0
classiq/qmod/qfunc.py CHANGED
@@ -35,6 +35,7 @@ def qfunc(
35
35
  *,
36
36
  external: Literal[True],
37
37
  synthesize_separately: Literal[False] = False,
38
+ atomic_qualifiers: Optional[list[str]] = None,
38
39
  ) -> Callable[[Callable], ExternalQFunc]: ...
39
40
 
40
41
 
@@ -43,11 +44,22 @@ def qfunc(
43
44
  *,
44
45
  generative: Literal[True],
45
46
  synthesize_separately: bool = False,
47
+ atomic_qualifiers: Optional[list[str]] = None,
46
48
  ) -> Callable[[Callable], GenerativeQFunc]: ...
47
49
 
48
50
 
49
51
  @overload
50
- def qfunc(*, synthesize_separately: bool) -> Callable[[Callable], QFunc]: ...
52
+ def qfunc(
53
+ *, synthesize_separately: bool, atomic_qualifiers: Optional[list[str]] = None
54
+ ) -> Callable[[Callable], QFunc]: ...
55
+
56
+
57
+ @overload
58
+ def qfunc(
59
+ *,
60
+ synthesize_separately: bool = False,
61
+ atomic_qualifiers: Optional[list[str]] = None,
62
+ ) -> Callable[[Callable], QFunc]: ...
51
63
 
52
64
 
53
65
  def qfunc(
@@ -56,24 +68,37 @@ def qfunc(
56
68
  external: bool = False,
57
69
  generative: bool = False,
58
70
  synthesize_separately: bool = False,
71
+ atomic_qualifiers: Optional[list[str]] = None,
59
72
  ) -> Union[Callable[[Callable], QCallable], QCallable]:
60
73
  def wrapper(func: Callable) -> QCallable:
61
74
  qfunc: BaseQFunc
75
+
76
+ if external:
77
+ _validate_directives(synthesize_separately, atomic_qualifiers)
78
+ return ExternalQFunc(func)
79
+
62
80
  if generative or _GENERATIVE_SWITCH:
63
81
  qfunc = GenerativeQFunc(func)
64
- elif external:
65
- if synthesize_separately:
66
- raise ClassiqInternalError(
67
- "External functions can't be marked as synthesized separately"
68
- )
69
- return ExternalQFunc(func)
70
82
  else:
71
83
  qfunc = QFunc(func)
72
84
  if synthesize_separately:
73
85
  qfunc.update_compilation_metadata(should_synthesize_separately=True)
86
+ if atomic_qualifiers is not None and len(atomic_qualifiers) > 0:
87
+ qfunc.update_compilation_metadata(atomic_qualifiers=atomic_qualifiers)
74
88
  return qfunc
75
89
 
76
90
  if func is not None:
77
91
  return wrapper(func)
78
-
79
92
  return wrapper
93
+
94
+
95
+ def _validate_directives(
96
+ synthesize_separately: bool, atomic_qualifiers: Optional[list[str]] = None
97
+ ) -> None:
98
+ error_msg = ""
99
+ if synthesize_separately:
100
+ error_msg += "External functions can't be marked as synthesized separately. \n"
101
+ if atomic_qualifiers is not None and len(atomic_qualifiers) > 0:
102
+ error_msg += "External functions can't have atomic qualifiers."
103
+ if error_msg:
104
+ raise ClassiqInternalError(error_msg)
@@ -10,17 +10,19 @@ from typing import ( # type: ignore[attr-defined]
10
10
  Generic,
11
11
  Literal,
12
12
  Optional,
13
+ Protocol,
13
14
  TypeVar,
14
15
  Union,
15
16
  _GenericAlias,
16
17
  cast,
17
18
  get_args,
18
19
  get_origin,
20
+ runtime_checkable,
19
21
  )
20
22
 
21
23
  from typing_extensions import ParamSpec, Self, _AnnotatedAlias
22
24
 
23
- from classiq.interface.exceptions import ClassiqValueError
25
+ from classiq.interface.exceptions import ClassiqInternalError, ClassiqValueError
24
26
  from classiq.interface.generator.expressions.expression import Expression
25
27
  from classiq.interface.generator.expressions.non_symbolic_expr import NonSymbolicExpr
26
28
  from classiq.interface.generator.expressions.proxies.quantum.qmod_qarray_proxy import (
@@ -31,6 +33,7 @@ from classiq.interface.generator.functions.port_declaration import (
31
33
  PortDeclarationDirection,
32
34
  )
33
35
  from classiq.interface.generator.functions.type_name import TypeName
36
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
34
37
  from classiq.interface.generator.types.qstruct_declaration import QStructDeclaration
35
38
  from classiq.interface.model.handle_binding import (
36
39
  FieldHandleBinding,
@@ -72,15 +75,6 @@ from classiq.qmod.utilities import (
72
75
  )
73
76
 
74
77
 
75
- def get_type_hint_expr(type_hint: Any) -> str:
76
- if isinstance(type_hint, ForwardRef): # expression in string literal
77
- return str(type_hint.__forward_arg__)
78
- if get_origin(type_hint) == Literal: # explicit numeric literal
79
- return str(get_args(type_hint)[0])
80
- else:
81
- return str(type_hint) # implicit numeric literal
82
-
83
-
84
78
  @contextmanager
85
79
  def _no_current_expandable() -> Iterator[None]:
86
80
  current_expandable = QCallable.CURRENT_EXPANDABLE
@@ -135,26 +129,6 @@ class QVar(Symbolic):
135
129
  def get_qmod_type(self) -> QuantumType:
136
130
  raise NotImplementedError()
137
131
 
138
- @staticmethod
139
- def from_type_hint(type_hint: Any) -> Optional[type["QVar"]]:
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
146
- if issubclass(type_, QVar):
147
- if issubclass(type_, QStruct):
148
- with _no_current_expandable():
149
- type_("DUMMY")._add_qmod_qstruct(qmodule=QMODULE)
150
- return type_
151
- return None
152
-
153
- @classmethod
154
- @abc.abstractmethod
155
- def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
156
- raise NotImplementedError()
157
-
158
132
  @classmethod
159
133
  @abc.abstractmethod
160
134
  def to_qvar(
@@ -165,14 +139,6 @@ class QVar(Symbolic):
165
139
  ) -> Self:
166
140
  raise NotImplementedError()
167
141
 
168
- @classmethod
169
- def port_direction(cls, type_hint: Any) -> PortDeclarationDirection:
170
- if isinstance(type_hint, _AnnotatedAlias):
171
- for metadata in type_hint.__metadata__:
172
- if isinstance(metadata, PortDeclarationDirection):
173
- return metadata
174
- return PortDeclarationDirection.Inout
175
-
176
142
  def __str__(self) -> str:
177
143
  return self._expr_str
178
144
 
@@ -191,6 +157,12 @@ class QVar(Symbolic):
191
157
  _Q = TypeVar("_Q", bound=QVar)
192
158
  Output = Annotated[_Q, PortDeclarationDirection.Output]
193
159
  Input = Annotated[_Q, PortDeclarationDirection.Input]
160
+ Const = Annotated[
161
+ _Q, TypeQualifier.Const
162
+ ] # A constant variable, up to a phase dependent on the computational basis state
163
+ QFree = Annotated[
164
+ _Q, TypeQualifier.QFree
165
+ ] # A quantum free variable, up to a phase dependent on the computational basis state
194
166
 
195
167
 
196
168
  class QScalar(QVar, SymbolicExpr):
@@ -282,10 +254,6 @@ class QScalar(QVar, SymbolicExpr):
282
254
 
283
255
 
284
256
  class QBit(QScalar):
285
- @classmethod
286
- def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
287
- return QuantumBit()
288
-
289
257
  @classmethod
290
258
  def to_qvar(
291
259
  cls,
@@ -342,43 +310,6 @@ class QNum(Generic[_P], QScalar):
342
310
  )
343
311
  super().__init__(name, _expr_str=_expr_str, depth=3)
344
312
 
345
- @classmethod
346
- def _get_attributes(cls, type_hint: Any) -> tuple[Any, Any, Any]:
347
- type_args = version_portable_get_args(type_hint)
348
- if len(type_args) == 0:
349
- return None, None, None
350
- if len(type_args) not in (1, 3):
351
- raise ClassiqValueError(
352
- "QNum receives three type arguments: QNum[size: int | CInt, "
353
- "is_signed: bool | CBool, fraction_digits: int | CInt]"
354
- )
355
- if len(type_args) == 1:
356
- return unwrap_forward_ref(type_args[0]), None, None
357
- return (
358
- unwrap_forward_ref(type_args[0]),
359
- unwrap_forward_ref(type_args[1]),
360
- unwrap_forward_ref(type_args[2]),
361
- )
362
-
363
- @classmethod
364
- def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
365
- size, is_signed, fraction_digits = cls._get_attributes(type_hint)
366
- return QuantumNumeric(
367
- size=(
368
- Expression(expr=get_type_hint_expr(size)) if size is not None else None
369
- ),
370
- is_signed=(
371
- Expression(expr=get_type_hint_expr(is_signed))
372
- if is_signed is not None
373
- else None
374
- ),
375
- fraction_digits=(
376
- Expression(expr=get_type_hint_expr(fraction_digits))
377
- if fraction_digits is not None
378
- else None
379
- ),
380
- )
381
-
382
313
  @classmethod
383
314
  def to_qvar(
384
315
  cls,
@@ -386,7 +317,7 @@ class QNum(Generic[_P], QScalar):
386
317
  type_hint: Any,
387
318
  expr_str: Optional[str],
388
319
  ) -> "QNum":
389
- return QNum(origin, *cls._get_attributes(type_hint), _expr_str=expr_str)
320
+ return QNum(origin, *_get_qnum_attributes(type_hint), _expr_str=expr_str)
390
321
 
391
322
  def get_qmod_type(self) -> QuantumType:
392
323
  return QuantumNumeric(
@@ -494,36 +425,6 @@ class QArray(ArrayBase[_P], QVar, NonSymbolicExpr):
494
425
  return interpret_expression(str(self.len))
495
426
  return CParamScalar(f"get_field({self}, 'len')")
496
427
 
497
- @classmethod
498
- def _get_attributes(cls, type_hint: Any) -> tuple[type[QVar], Any]:
499
- type_args = version_portable_get_args(type_hint)
500
- if len(type_args) == 0:
501
- return QBit, None
502
- first_arg = unwrap_forward_ref(type_args[0])
503
- if len(type_args) == 1:
504
- if isinstance(first_arg, (str, int)):
505
- return QBit, first_arg
506
- return first_arg, None
507
- if len(type_args) != 2:
508
- raise ClassiqValueError(
509
- "QArray receives two type arguments: QArray[element_type: QVar, "
510
- "length: int | CInt]"
511
- )
512
- second_arg = unwrap_forward_ref(type_args[1])
513
- return cast(tuple[type[QVar], Any], (first_arg, second_arg))
514
-
515
- @classmethod
516
- def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
517
- api_element_type, length = cls._get_attributes(type_hint)
518
- api_element_class = get_origin(api_element_type) or api_element_type
519
- element_type = api_element_class.to_qmod_quantum_type(api_element_type)
520
-
521
- length_expr: Optional[Expression] = None
522
- if length is not None:
523
- length_expr = Expression(expr=get_type_hint_expr(length))
524
-
525
- return QuantumBitvector(element_type=element_type, length=length_expr)
526
-
527
428
  @classmethod
528
429
  def to_qvar(
529
430
  cls,
@@ -531,16 +432,15 @@ class QArray(ArrayBase[_P], QVar, NonSymbolicExpr):
531
432
  type_hint: Any,
532
433
  expr_str: Optional[str],
533
434
  ) -> "QArray":
534
- return QArray(origin, *cls._get_attributes(type_hint), _expr_str=expr_str)
435
+ return QArray(origin, *_get_qarray_attributes(type_hint), _expr_str=expr_str)
535
436
 
536
437
  def get_qmod_type(self) -> QuantumBitvector:
537
- if isinstance(self._element_type, QuantumType):
538
- element_type = self._element_type
539
- else:
540
- element_class = get_origin(self._element_type) or self._element_type
541
- element_type = element_class.to_qmod_quantum_type(self._element_type)
542
438
  return QuantumBitvector(
543
- element_type=element_type,
439
+ element_type=(
440
+ self._element_type
441
+ if isinstance(self._element_type, QuantumType)
442
+ else _to_quantum_type(self._element_type)
443
+ ),
544
444
  length=self._length,
545
445
  )
546
446
 
@@ -558,6 +458,7 @@ class QStruct(QVar):
558
458
  _fields: Optional[Mapping[str, QVar]] = None,
559
459
  _expr_str: Optional[str] = None,
560
460
  ) -> None:
461
+ _register_qstruct(type(self), qmodule=QMODULE)
561
462
  name = _infer_variable_name(origin, self.CONSTRUCTOR_DEPTH)
562
463
  if _struct_name is None or _fields is None:
563
464
  with _no_current_expandable():
@@ -569,17 +470,10 @@ class QStruct(QVar):
569
470
  for field_name, var in _fields.items():
570
471
  setattr(self, field_name, var)
571
472
  super().__init__(name, expr_str=_expr_str)
572
- self._add_qmod_qstruct(qmodule=QMODULE)
573
473
 
574
474
  def get_qmod_type(self) -> QuantumType:
575
475
  return TypeName(name=self._struct_name)
576
476
 
577
- @classmethod
578
- def to_qmod_quantum_type(cls, type_hint: Any) -> QuantumType:
579
- with _no_current_expandable():
580
- type_hint("DUMMY")
581
- return TypeName(name=type_hint.__name__)
582
-
583
477
  @classmethod
584
478
  def to_qvar(
585
479
  cls,
@@ -588,24 +482,13 @@ class QStruct(QVar):
588
482
  expr_str: Optional[str],
589
483
  ) -> "QStruct":
590
484
  field_types = {
591
- field_name: (QVar.from_type_hint(field_type), field_type)
485
+ field_name: (_get_root_type(field_type), field_type)
592
486
  for field_name, field_type in type_hint.__annotations__.items()
593
487
  }
594
- illegal_fields = [
595
- (field_name, field_type)
596
- for field_name, (field_class, field_type) in field_types.items()
597
- if field_class is None
598
- ]
599
- if len(illegal_fields) > 0:
600
- raise ClassiqValueError(
601
- f"Field {illegal_fields[0][0]!r} of quantum struct "
602
- f"{type_hint.__name__} has a non-quantum type "
603
- f"{illegal_fields[0][1].__name__}."
604
- )
605
488
  base_handle = HandleBinding(name=origin) if isinstance(origin, str) else origin
606
489
  with _no_current_expandable():
607
490
  field_vars = {
608
- field_name: cast(type[QVar], field_class).to_qvar(
491
+ field_name: field_class.to_qvar(
609
492
  FieldHandleBinding(base_handle=base_handle, field=field_name),
610
493
  field_type,
611
494
  f"get_field({expr_str if expr_str is not None else str(origin)}, '{field_name}')",
@@ -619,16 +502,6 @@ class QStruct(QVar):
619
502
  _expr_str=expr_str,
620
503
  )
621
504
 
622
- def _add_qmod_qstruct(self, *, qmodule: ModelStateContainer) -> None:
623
- if self._struct_name in qmodule.qstruct_decls:
624
- return
625
-
626
- struct_decl = QStructDeclaration(
627
- name=self._struct_name,
628
- fields={name: qvar.get_qmod_type() for name, qvar in self._fields.items()},
629
- )
630
- qmodule.qstruct_decls[self._struct_name] = struct_decl
631
-
632
505
 
633
506
  def create_qvar_for_port_decl(port: AnonPortDeclaration, name: str) -> QVar:
634
507
  return _create_qvar_for_qtype(port.quantum_type, HandleBinding(name=name))
@@ -688,3 +561,171 @@ def get_qvar(qtype: QuantumType, origin: HandleBinding) -> "QVar":
688
561
  },
689
562
  )
690
563
  raise NotImplementedError
564
+
565
+
566
+ def get_port_from_type_hint(
567
+ py_type: Any,
568
+ ) -> tuple[QuantumType, PortDeclarationDirection, TypeQualifier]:
569
+ direction = PortDeclarationDirection.Inout # default
570
+ qualifier = TypeQualifier.Quantum # default
571
+
572
+ if isinstance(py_type, _AnnotatedAlias):
573
+ quantum_type = _to_quantum_type(py_type.__origin__)
574
+ for metadata in py_type.__metadata__:
575
+ if isinstance(metadata, PortDeclarationDirection):
576
+ direction = metadata
577
+ elif isinstance(metadata, TypeQualifier):
578
+ qualifier = metadata
579
+ else:
580
+ quantum_type = _to_quantum_type(py_type)
581
+
582
+ return quantum_type, direction, qualifier
583
+
584
+
585
+ def _to_quantum_type(py_type: Any) -> QuantumType:
586
+ root_type = _get_root_type(py_type)
587
+ if not issubclass(root_type, QVar):
588
+ raise ClassiqInternalError(f"Invalid quantum type {py_type}")
589
+ if issubclass(root_type, QBit):
590
+ return QuantumBit()
591
+ elif issubclass(root_type, QNum):
592
+ return _get_quantum_numeric(py_type)
593
+ elif issubclass(root_type, QArray):
594
+ return _get_quantum_bit_vector(py_type)
595
+ elif issubclass(root_type, QStruct):
596
+ return _get_quantum_struct(py_type)
597
+ else:
598
+ raise ClassiqInternalError(f"Invalid quantum type {py_type}")
599
+
600
+
601
+ def _get_quantum_numeric(type_hint: type[QNum]) -> QuantumNumeric:
602
+ size, is_signed, fraction_digits = _get_qnum_attributes(type_hint)
603
+ return QuantumNumeric(
604
+ size=(Expression(expr=_get_type_hint_expr(size)) if size is not None else None),
605
+ is_signed=(
606
+ Expression(expr=_get_type_hint_expr(is_signed))
607
+ if is_signed is not None
608
+ else None
609
+ ),
610
+ fraction_digits=(
611
+ Expression(expr=_get_type_hint_expr(fraction_digits))
612
+ if fraction_digits is not None
613
+ else None
614
+ ),
615
+ )
616
+
617
+
618
+ def _get_qnum_attributes(type_hint: type[QNum]) -> tuple[Any, Any, Any]:
619
+ type_args = version_portable_get_args(type_hint)
620
+ if len(type_args) == 0:
621
+ return None, None, None
622
+ if len(type_args) not in (1, 3):
623
+ raise ClassiqValueError(
624
+ "QNum receives three type arguments: QNum[size: int | CInt, "
625
+ "is_signed: bool | CBool, fraction_digits: int | CInt]"
626
+ )
627
+ if len(type_args) == 1:
628
+ return unwrap_forward_ref(type_args[0]), None, None
629
+ return (
630
+ unwrap_forward_ref(type_args[0]),
631
+ unwrap_forward_ref(type_args[1]),
632
+ unwrap_forward_ref(type_args[2]),
633
+ )
634
+
635
+
636
+ def _get_qarray_attributes(type_hint: type[QArray]) -> tuple[Any, Any]:
637
+ type_args = version_portable_get_args(type_hint)
638
+ if len(type_args) == 0:
639
+ return QBit, None
640
+ first_arg = unwrap_forward_ref(type_args[0])
641
+ if len(type_args) == 1:
642
+ if isinstance(first_arg, (str, int)):
643
+ return QBit, first_arg
644
+ return first_arg, None
645
+ if len(type_args) != 2:
646
+ raise ClassiqValueError(
647
+ "QArray receives two type arguments: QArray[element_type: QVar, "
648
+ "length: int | CInt]"
649
+ )
650
+ second_arg = unwrap_forward_ref(type_args[1])
651
+ return cast(tuple[type[QVar], Any], (first_arg, second_arg))
652
+
653
+
654
+ def _get_quantum_bit_vector(type_hint: type[QArray]) -> QuantumBitvector:
655
+ api_element_type, length = _get_qarray_attributes(type_hint)
656
+ element_type = _to_quantum_type(api_element_type)
657
+
658
+ length_expr: Expression | None = None
659
+ if length is not None:
660
+ length_expr = Expression(expr=_get_type_hint_expr(length))
661
+
662
+ return QuantumBitvector(element_type=element_type, length=length_expr)
663
+
664
+
665
+ def _get_quantum_struct(type_hint: type[QStruct]) -> TypeName:
666
+ _register_qstruct(type_hint, qmodule=QMODULE)
667
+ return TypeName(name=type_hint.__name__)
668
+
669
+
670
+ def _register_qstruct(
671
+ type_hint: type[QStruct], *, qmodule: ModelStateContainer
672
+ ) -> None:
673
+ struct_name = type_hint.__name__
674
+ if type_hint is QStruct or struct_name in qmodule.qstruct_decls:
675
+ return
676
+
677
+ _validate_fields(type_hint)
678
+ struct_decl = QStructDeclaration(
679
+ name=struct_name,
680
+ fields={
681
+ field_name: _to_quantum_type(field_type)
682
+ for field_name, field_type in type_hint.__annotations__.items()
683
+ },
684
+ )
685
+ qmodule.qstruct_decls[struct_name] = struct_decl
686
+
687
+
688
+ def _validate_fields(type_hint: type[QStruct]) -> None:
689
+ field_types = {
690
+ field_name: (_get_root_type(field_type), field_type)
691
+ for field_name, field_type in type_hint.__annotations__.items()
692
+ }
693
+ illegal_fields = [
694
+ (field_name, field_type)
695
+ for field_name, (field_class, field_type) in field_types.items()
696
+ if field_class is None
697
+ ]
698
+ if len(illegal_fields) > 0:
699
+ raise ClassiqValueError(
700
+ f"Field {illegal_fields[0][0]!r} of quantum struct "
701
+ f"{type_hint.__name__} has a non-quantum type "
702
+ f"{illegal_fields[0][1].__name__}."
703
+ )
704
+
705
+
706
+ @runtime_checkable
707
+ class _ModelConstant(Protocol):
708
+ # Applies to QConstant
709
+ def add_to_model(self) -> None: ...
710
+
711
+
712
+ def _get_type_hint_expr(type_hint: Any) -> str:
713
+ if isinstance(type_hint, ForwardRef): # expression in string literal
714
+ return str(type_hint.__forward_arg__)
715
+ if get_origin(type_hint) == Literal: # explicit numeric literal
716
+ return str(get_args(type_hint)[0])
717
+ if isinstance(
718
+ type_hint, _ModelConstant
719
+ ): # the Protocol is to prevent circular imports
720
+ type_hint.add_to_model()
721
+ return str(type_hint) # implicit numeric literal
722
+
723
+
724
+ def _get_root_type(py_type: Any) -> type[QVar]:
725
+ non_annotated_type = (
726
+ py_type.__origin__ if isinstance(py_type, _AnnotatedAlias) else py_type
727
+ )
728
+ root_type = get_origin(non_annotated_type) or non_annotated_type
729
+ if not issubclass(root_type, QVar):
730
+ raise ClassiqInternalError(f"Invalid quantum type {root_type}")
731
+ return root_type
@@ -25,11 +25,10 @@ from classiq.interface.model.quantum_function_declaration import (
25
25
 
26
26
  from classiq.qmod.classical_function import CFunc
27
27
  from classiq.qmod.cparam import CParamAbstract
28
- from classiq.qmod.declaration_inferrer import infer_func_decl
28
+ from classiq.qmod.declaration_inferrer import infer_func_decl, is_qvar
29
29
  from classiq.qmod.generative import set_frontend_interpreter
30
30
  from classiq.qmod.qmod_constant import QConstant
31
31
  from classiq.qmod.qmod_parameter import CArray
32
- from classiq.qmod.qmod_variable import QVar
33
32
  from classiq.qmod.quantum_callable import QCallable, QCallableList
34
33
  from classiq.qmod.quantum_expandable import QExpandable, QTerminalCallable
35
34
  from classiq.qmod.semantics.annotation.qstruct_annotator import QStructAnnotator
@@ -57,7 +56,7 @@ class BaseQFunc(QExpandable):
57
56
  )
58
57
 
59
58
  def update_compilation_metadata(self, **kwargs: Any) -> None:
60
- if kwargs["should_synthesize_separately"] and self._has_inputs:
59
+ if kwargs.get("should_synthesize_separately") and self._has_inputs:
61
60
  raise ClassiqError("Can't synthesize separately a function with inputs")
62
61
  self.compilation_metadata = self._compilation_metadata.model_copy(update=kwargs)
63
62
 
@@ -315,7 +314,7 @@ def _validate_no_gen_params(annotations: dict[str, Any]) -> None:
315
314
  or get_origin(annotation) is CArray
316
315
  or (get_origin(annotation) or annotation) is QCallable
317
316
  or (get_origin(annotation) or annotation) is QCallableList
318
- or QVar.from_type_hint(annotation) is not None
317
+ or is_qvar(annotation)
319
318
  )
320
319
  }
321
320
  if _illegal_params:
@@ -6,11 +6,7 @@ from classiq.interface.exceptions import ClassiqValueError
6
6
  from classiq.interface.generator.functions.port_declaration import (
7
7
  PortDeclarationDirection,
8
8
  )
9
-
10
- annotation_map: dict[PortDeclarationDirection, str] = {
11
- PortDeclarationDirection.Input: PortDeclarationDirection.Input.name,
12
- PortDeclarationDirection.Output: PortDeclarationDirection.Output.name,
13
- }
9
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
14
10
 
15
11
 
16
12
  def validate_annotation(type_hint: Any) -> None:
@@ -21,9 +17,22 @@ def validate_annotation(type_hint: Any) -> None:
21
17
  for direction in type_hint.__metadata__
22
18
  if isinstance(direction, PortDeclarationDirection)
23
19
  ]
24
- if len(directions) <= 1:
20
+ qualifiers: list[TypeQualifier] = [
21
+ qualifier
22
+ for qualifier in type_hint.__metadata__
23
+ if isinstance(qualifier, TypeQualifier)
24
+ ]
25
+ if len(directions) <= 1 and len(qualifiers) <= 1:
25
26
  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
- )
27
+ error_message = ""
28
+ if len(directions) > 1:
29
+ error_message += (
30
+ f"Multiple directions are not allowed in a single type hint: "
31
+ f"[{', '.join(direction.name for direction in reversed(directions))}]\n"
32
+ )
33
+ if len(qualifiers) > 1:
34
+ error_message += (
35
+ f"Multiple qualifiers are not allowed in a single type hint: "
36
+ f"[{', '.join(qualifier.name for qualifier in reversed(qualifiers))}]\n"
37
+ )
38
+ raise ClassiqValueError(error_message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: classiq
3
- Version: 0.70.0
3
+ Version: 0.71.0
4
4
  Summary: Classiq's Python SDK for quantum computing
5
5
  License: Proprietary
6
6
  Keywords: quantum computing,quantum circuits,quantum algorithms,QAD,QDL