classiq 0.46.1__py3-none-any.whl → 0.48.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 (71) hide show
  1. classiq/_internals/api_wrapper.py +45 -8
  2. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +2 -7
  3. classiq/applications/grover/grover_model_constructor.py +2 -1
  4. classiq/execution/execution_session.py +133 -45
  5. classiq/execution/jobs.py +120 -1
  6. classiq/interface/_version.py +1 -1
  7. classiq/interface/backend/quantum_backend_providers.py +0 -1
  8. classiq/interface/debug_info/debug_info.py +23 -1
  9. classiq/interface/execution/primitives.py +17 -0
  10. classiq/interface/executor/iqae_result.py +3 -3
  11. classiq/interface/executor/result.py +3 -1
  12. classiq/interface/generator/arith/arithmetic_operations.py +5 -2
  13. classiq/interface/generator/arith/binary_ops.py +21 -14
  14. classiq/interface/generator/arith/extremum_operations.py +9 -1
  15. classiq/interface/generator/arith/number_utils.py +6 -0
  16. classiq/interface/generator/arith/register_user_input.py +30 -21
  17. classiq/interface/generator/arith/unary_ops.py +13 -1
  18. classiq/interface/generator/expressions/expression.py +8 -0
  19. classiq/interface/generator/functions/type_name.py +1 -3
  20. classiq/interface/generator/generated_circuit_data.py +47 -2
  21. classiq/interface/generator/quantum_program.py +10 -2
  22. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +17 -3
  23. classiq/interface/ide/visual_model.py +10 -5
  24. classiq/interface/interface_version.py +1 -1
  25. classiq/interface/model/bind_operation.py +0 -3
  26. classiq/interface/model/phase_operation.py +11 -0
  27. classiq/interface/model/port_declaration.py +1 -12
  28. classiq/interface/model/quantum_expressions/arithmetic_operation.py +34 -6
  29. classiq/interface/model/quantum_lambda_function.py +4 -1
  30. classiq/interface/model/quantum_statement.py +16 -1
  31. classiq/interface/model/quantum_variable_declaration.py +0 -22
  32. classiq/interface/model/statement_block.py +3 -0
  33. classiq/interface/server/global_versions.py +4 -4
  34. classiq/interface/server/routes.py +0 -3
  35. classiq/model_expansions/capturing/propagated_var_stack.py +5 -2
  36. classiq/model_expansions/closure.py +7 -2
  37. classiq/model_expansions/evaluators/quantum_type_utils.py +0 -7
  38. classiq/model_expansions/generative_functions.py +146 -28
  39. classiq/model_expansions/interpreter.py +17 -5
  40. classiq/model_expansions/quantum_operations/classicalif.py +27 -10
  41. classiq/model_expansions/quantum_operations/control.py +22 -15
  42. classiq/model_expansions/quantum_operations/emitter.py +68 -7
  43. classiq/model_expansions/quantum_operations/expression_operation.py +25 -16
  44. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +167 -95
  45. classiq/model_expansions/quantum_operations/invert.py +12 -6
  46. classiq/model_expansions/quantum_operations/phase.py +189 -0
  47. classiq/model_expansions/quantum_operations/power.py +9 -8
  48. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +20 -5
  49. classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -1
  50. classiq/model_expansions/quantum_operations/repeat.py +32 -13
  51. classiq/model_expansions/quantum_operations/within_apply.py +19 -6
  52. classiq/model_expansions/scope.py +16 -5
  53. classiq/model_expansions/scope_initialization.py +11 -1
  54. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +23 -1
  55. classiq/model_expansions/visitors/variable_references.py +11 -7
  56. classiq/qmod/builtins/__init__.py +10 -0
  57. classiq/qmod/builtins/constants.py +10 -0
  58. classiq/qmod/builtins/functions/state_preparation.py +4 -1
  59. classiq/qmod/builtins/operations.py +55 -161
  60. classiq/qmod/create_model_function.py +1 -1
  61. classiq/qmod/generative.py +14 -5
  62. classiq/qmod/native/pretty_printer.py +14 -4
  63. classiq/qmod/pretty_print/pretty_printer.py +14 -4
  64. classiq/qmod/qmod_constant.py +28 -18
  65. classiq/qmod/qmod_variable.py +43 -23
  66. classiq/qmod/quantum_expandable.py +14 -1
  67. classiq/qmod/semantics/static_semantics_visitor.py +10 -0
  68. classiq/qmod/semantics/validation/constants_validation.py +16 -0
  69. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/METADATA +9 -4
  70. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/RECORD +71 -66
  71. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/WHEEL +0 -0
@@ -21,6 +21,7 @@ from classiq.interface.exceptions import ClassiqValueError
21
21
  from classiq.interface.generator.arith import argument_utils, number_utils
22
22
  from classiq.interface.generator.arith.argument_utils import RegisterOrConst
23
23
  from classiq.interface.generator.arith.arithmetic_operations import (
24
+ MODULO_WITH_FRACTION_PLACES_ERROR_MSG,
24
25
  ArithmeticOperationParams,
25
26
  )
26
27
  from classiq.interface.generator.arith.ast_node_rewrite import (
@@ -391,17 +392,20 @@ class Subtractor(InplacableBinaryOpParams[RegisterOrConst, RegisterOrConst]):
391
392
  arg=self.effective_right_arg,
392
393
  output_size=self.negation_output_size,
393
394
  inplace=self.should_inplace_negation,
395
+ bypass_bounds_validation=True,
394
396
  )
395
397
  negation_result = negation_params.result_register
396
398
  if self.output_size is None and max(self.effective_right_arg.bounds) > 0:
397
- negation_result = negation_result.copy(
398
- update=dict(
399
- is_signed=True,
400
- bounds=(
401
- -max(self.effective_right_arg.bounds),
402
- -min(self.effective_right_arg.bounds),
403
- ),
404
- )
399
+ bounds = (
400
+ -max(self.effective_right_arg.bounds),
401
+ -min(self.effective_right_arg.bounds),
402
+ )
403
+ negation_result = RegisterArithmeticInfo(
404
+ size=negation_result.size,
405
+ fraction_places=negation_result.fraction_places,
406
+ is_signed=True,
407
+ bounds=bounds,
408
+ bypass_bounds_validation=True,
405
409
  )
406
410
  adder_params = Adder(
407
411
  left_arg=self.effective_left_arg,
@@ -465,12 +469,8 @@ class Multiplier(BinaryOpWithFloatInputs):
465
469
  for right in argument_utils.bounds(args[1])
466
470
  ]
467
471
  return (
468
- number_utils.limit_fraction_places(
469
- min(extremal_values), machine_precision=machine_precision
470
- ),
471
- number_utils.limit_fraction_places(
472
- max(extremal_values), machine_precision=machine_precision
473
- ),
472
+ number_utils.limit_fraction_places(min(extremal_values), machine_precision),
473
+ number_utils.limit_fraction_places(max(extremal_values), machine_precision),
474
474
  )
475
475
 
476
476
  def _get_result_register(self) -> RegisterArithmeticInfo:
@@ -482,6 +482,13 @@ class Multiplier(BinaryOpWithFloatInputs):
482
482
  self.right_arg, self.machine_precision
483
483
  )
484
484
  bounds = self._get_bounds((left_arg, right_arg), self.machine_precision)
485
+ if self.output_size:
486
+ if fraction_places:
487
+ raise ValueError(MODULO_WITH_FRACTION_PLACES_ERROR_MSG)
488
+ max_bounds = RegisterArithmeticInfo.get_maximal_bounds(
489
+ size=self.output_size, is_signed=False, fraction_places=0
490
+ )
491
+ bounds = number_utils.bounds_cut(bounds, max_bounds)
485
492
 
486
493
  return RegisterArithmeticInfo(
487
494
  size=self.output_size
@@ -4,9 +4,10 @@ from typing import Any, Dict, Iterable
4
4
  import pydantic
5
5
 
6
6
  from classiq.interface.exceptions import ClassiqValueError
7
- from classiq.interface.generator.arith import argument_utils
7
+ from classiq.interface.generator.arith import argument_utils, number_utils
8
8
  from classiq.interface.generator.arith.argument_utils import RegisterOrConst
9
9
  from classiq.interface.generator.arith.arithmetic_operations import (
10
+ MODULO_WITH_FRACTION_PLACES_ERROR_MSG,
10
11
  ArithmeticOperationParams,
11
12
  )
12
13
  from classiq.interface.generator.arith.binary_ops import (
@@ -108,6 +109,13 @@ class Extremum(ArithmeticOperationParams):
108
109
  argument_utils.upper_bound(eff_right_arg),
109
110
  ),
110
111
  )
112
+ if self.output_size:
113
+ if fraction_places:
114
+ raise ValueError(MODULO_WITH_FRACTION_PLACES_ERROR_MSG)
115
+ max_bounds = RegisterArithmeticInfo.get_maximal_bounds(
116
+ size=self.output_size, is_signed=False, fraction_places=0
117
+ )
118
+ bounds = number_utils.bounds_cut(bounds, max_bounds)
111
119
  return RegisterArithmeticInfo(
112
120
  size=self.output_size or required_size,
113
121
  fraction_places=fraction_places,
@@ -110,3 +110,9 @@ def limit_fraction_places(number: float, machine_precision: int) -> float:
110
110
  fraction_part_size=orig_fractions - removed_fractions,
111
111
  is_signed=number < 0,
112
112
  )
113
+
114
+
115
+ def bounds_cut(
116
+ bounds1: Tuple[float, float], bounds2: Tuple[float, float]
117
+ ) -> Tuple[float, float]:
118
+ return max(min(bounds1), min(bounds2)), min(max(bounds1), max(bounds2))
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, Optional
1
+ from typing import Any, Dict, Optional, Tuple
2
2
 
3
3
  import pydantic
4
4
 
@@ -14,6 +14,7 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
14
14
  size: pydantic.PositiveInt
15
15
  is_signed: bool = pydantic.Field(default=False)
16
16
  fraction_places: pydantic.NonNegativeInt = pydantic.Field(default=0)
17
+ bypass_bounds_validation: bool = pydantic.Field(default=False)
17
18
  bounds: PydanticFloatTuple = pydantic.Field(default=None)
18
19
 
19
20
  @pydantic.root_validator(pre=True)
@@ -22,32 +23,39 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
22
23
  values.pop("name")
23
24
  return values
24
25
 
26
+ @staticmethod
27
+ def get_maximal_bounds(
28
+ *, size: int, is_signed: bool, fraction_places: int
29
+ ) -> Tuple[float, float]:
30
+ lb = 0 if not is_signed else -(2 ** (size - 1))
31
+ ub = 2**size - 1 if not is_signed else 2 ** (size - 1) - 1
32
+ fraction_factor = float(2**-fraction_places)
33
+ return (lb * fraction_factor, ub * fraction_factor)
34
+
25
35
  @pydantic.validator("bounds", always=True)
26
36
  def _validate_bounds(
27
37
  cls, bounds: Optional[PydanticFloatTuple], values: Dict[str, Any]
28
38
  ) -> PydanticFloatTuple:
29
- if bounds is not None:
30
- if min(bounds) < 0:
31
- assert values.get("is_signed")
32
- fraction_places = values.get("fraction_places")
33
- if not isinstance(fraction_places, int):
34
- raise ClassiqValueError(
35
- "RegisterUserInput must have an integer fraction_places"
36
- )
37
- return number_utils.limit_fraction_places(
38
- min(bounds), machine_precision=fraction_places
39
- ), number_utils.limit_fraction_places(
40
- max(bounds), machine_precision=fraction_places
41
- )
42
-
43
39
  size = values.get("size")
40
+ is_signed = values.get("is_signed", False)
41
+ fraction_places = values.get("fraction_places", 0)
44
42
  if not isinstance(size, int):
45
- raise ClassiqValueError("RegisterUserInput must have an integer size")
46
- is_signed: bool = values.get("is_signed", False)
47
- lb = 0 if not is_signed else -(2 ** (size - 1))
48
- ub = 2**size - 1 if not is_signed else 2 ** (size - 1) - 1
49
- fraction_factor = float(2 ** -values.get("fraction_places", 0))
50
- return (lb * fraction_factor, ub * fraction_factor)
43
+ raise ClassiqValueError("RegisterArithmeticInfo must have an integer size")
44
+
45
+ maximal_bounds = cls.get_maximal_bounds(
46
+ size=size, is_signed=is_signed, fraction_places=fraction_places
47
+ )
48
+ if bounds is None:
49
+ return maximal_bounds
50
+
51
+ trimmed_bounds = (
52
+ number_utils.limit_fraction_places(min(bounds), fraction_places),
53
+ number_utils.limit_fraction_places(max(bounds), fraction_places),
54
+ )
55
+ if not values.get("bypass_bounds_validation", False):
56
+ assert min(trimmed_bounds) >= min(maximal_bounds), "Illegal bound min"
57
+ assert max(trimmed_bounds) <= max(maximal_bounds), "Illegal bound max"
58
+ return trimmed_bounds
51
59
 
52
60
  def limit_fraction_places(self, machine_precision: int) -> "RegisterArithmeticInfo":
53
61
  truncated_bits: int = max(self.fraction_places - machine_precision, 0)
@@ -56,6 +64,7 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
56
64
  is_signed=self.is_signed,
57
65
  fraction_places=self.fraction_places - truncated_bits,
58
66
  bounds=self.bounds,
67
+ bypass_bounds_validation=self.bypass_bounds_validation,
59
68
  )
60
69
 
61
70
  @property
@@ -5,6 +5,7 @@ import pydantic
5
5
  from classiq.interface.exceptions import ClassiqValueError
6
6
  from classiq.interface.generator.arith import argument_utils, number_utils
7
7
  from classiq.interface.generator.arith.arithmetic_operations import (
8
+ MODULO_WITH_FRACTION_PLACES_ERROR_MSG,
8
9
  ArithmeticOperationParams,
9
10
  )
10
11
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
@@ -74,6 +75,9 @@ class BitwiseInvert(UnaryOpParams):
74
75
 
75
76
 
76
77
  class Negation(UnaryOpParams):
78
+ bypass_bounds_validation: bool = pydantic.Field(
79
+ default=False
80
+ ) # True for efficient subtraction
77
81
  output_name = "negated"
78
82
 
79
83
  @staticmethod
@@ -88,11 +92,19 @@ class Negation(UnaryOpParams):
88
92
  eff_arg = self.arg.limit_fraction_places(self.machine_precision)
89
93
  is_signed = max(eff_arg.bounds) > 0 and self._include_sign
90
94
  bounds = (-max(eff_arg.bounds), -min(eff_arg.bounds))
95
+ if self.output_size and not self.bypass_bounds_validation:
96
+ if eff_arg.fraction_places:
97
+ raise ValueError(MODULO_WITH_FRACTION_PLACES_ERROR_MSG)
98
+ max_bounds = RegisterArithmeticInfo.get_maximal_bounds(
99
+ size=self.output_size, is_signed=False, fraction_places=0
100
+ )
101
+ bounds = number_utils.bounds_cut(bounds, max_bounds)
91
102
  return RegisterArithmeticInfo(
92
103
  size=self.output_size or self._expected_result_size(eff_arg),
93
104
  fraction_places=eff_arg.fraction_places,
94
105
  is_signed=is_signed,
95
- bounds=bounds if (is_signed or min(bounds) >= 0) else None,
106
+ bypass_bounds_validation=self.bypass_bounds_validation,
107
+ bounds=bounds,
96
108
  )
97
109
 
98
110
  def zero_input_for_extension(self) -> pydantic.NonNegativeInt:
@@ -69,6 +69,14 @@ class Expression(HashableASTNode):
69
69
  return self.as_constant(list)
70
70
 
71
71
  def _try_to_immediate_evaluate(self) -> None:
72
+ # FIXME remove special treatment (CAD-22999)
73
+ if self.expr == "SIGNED":
74
+ self._evaluated_expr = EvaluatedExpression(value=True)
75
+ return
76
+ if self.expr == "UNSIGNED":
77
+ self._evaluated_expr = EvaluatedExpression(value=False)
78
+ return
79
+
72
80
  try:
73
81
  result = ast.literal_eval(self.expr)
74
82
  if isinstance(result, (int, float, bool)):
@@ -67,9 +67,7 @@ class TypeName(ClassicalType, QuantumType): # type:ignore[misc]
67
67
  return self._assigned_fields
68
68
 
69
69
  def _set_fields(self, fields: Mapping[str, "ConcreteQuantumType"]) -> None:
70
- from classiq.qmod.model_state_container import QMODULE
71
-
72
- QMODULE.qstruct_decls[self.name].fields = fields
70
+ self._assigned_fields = fields
73
71
 
74
72
 
75
73
  class Enum(TypeName):
@@ -1,4 +1,5 @@
1
- from typing import Dict, List, Literal, Optional, Tuple, Union
1
+ import logging
2
+ from typing import Any, Dict, List, Literal, Optional, Tuple, Union
2
3
 
3
4
  import pydantic
4
5
  from typing_extensions import TypeAlias
@@ -10,10 +11,10 @@ from classiq.interface.generator.synthesis_metadata.synthesis_execution_data imp
10
11
  )
11
12
  from classiq.interface.ide.visual_model import OperationParameter
12
13
 
14
+ _logger = logging.getLogger(__name__)
13
15
  ParameterName = str
14
16
  IOQubitMapping: TypeAlias = Dict[str, Tuple[int, ...]]
15
17
 
16
-
17
18
  CLASSIQ_HIERARCHY_SEPARATOR: Literal["."] = "."
18
19
 
19
20
  VISUALIZATION_HIDE_LIST = [
@@ -102,6 +103,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
102
103
  absolute_qubits: Optional[Tuple[int, ...]]
103
104
  is_basis_gate: Optional[bool]
104
105
  parameters: List[OperationParameter] = list()
106
+ port_to_passed_variable_map: Dict[str, str] = pydantic.Field(default_factory=dict)
105
107
 
106
108
  @property
107
109
  def registers(self) -> List[GeneratedRegister]:
@@ -120,3 +122,46 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
120
122
  if self.generated_function is None:
121
123
  return list()
122
124
  return self.generated_function.control_states
125
+
126
+ @staticmethod
127
+ def create_parameters_from_dict(
128
+ parameters: Dict[str, str]
129
+ ) -> List[OperationParameter]:
130
+ return [
131
+ OperationParameter(label=key, value=value)
132
+ for key, value in parameters.items()
133
+ ]
134
+
135
+ def update(self, **kwargs: Any) -> None:
136
+ for key, value in kwargs.items():
137
+ setattr(self, key, value)
138
+
139
+ def propagate_absolute_qubits(self) -> None:
140
+ if self.absolute_qubits is None:
141
+ return
142
+
143
+ for register in self.registers:
144
+ register.qubit_indexes_absolute = list(
145
+ _get_absolute_from_relative(
146
+ self.absolute_qubits, tuple(register.qubit_indexes_relative)
147
+ )
148
+ )
149
+
150
+ for child in self.children:
151
+ child.absolute_qubits = _get_absolute_from_relative(
152
+ self.absolute_qubits, child.relative_qubits
153
+ )
154
+ child.propagate_absolute_qubits()
155
+
156
+
157
+ def _get_absolute_from_relative(
158
+ absolute_qubits: Tuple[int, ...], relative_qubits: Tuple[int, ...]
159
+ ) -> Tuple[int, ...]:
160
+ if max(relative_qubits) >= len(absolute_qubits):
161
+ _logger.warning(
162
+ "Invalid qubit computation (relative qubits: %s, absolute qubits: %s)",
163
+ relative_qubits,
164
+ absolute_qubits,
165
+ )
166
+ return tuple()
167
+ return tuple(absolute_qubits[relative_qubit] for relative_qubit in relative_qubits)
@@ -1,5 +1,5 @@
1
1
  import uuid
2
- from datetime import datetime
2
+ from datetime import datetime, timezone
3
3
  from pathlib import Path
4
4
  from typing import Dict, List, Optional, Tuple, Union
5
5
 
@@ -10,6 +10,7 @@ from classiq.interface.exceptions import (
10
10
  ClassiqMissingOutputFormatError,
11
11
  ClassiqStateInitializationError,
12
12
  )
13
+ from classiq.interface.execution.primitives import PrimitivesInput
13
14
  from classiq.interface.executor import quantum_code
14
15
  from classiq.interface.executor.quantum_instruction_set import QuantumInstructionSet
15
16
  from classiq.interface.executor.register_initialization import RegisterInitialization
@@ -50,16 +51,23 @@ def get_uuid_as_str() -> str:
50
51
  return str(uuid.uuid4())
51
52
 
52
53
 
54
+ def _get_formatted_utc_current_time() -> str:
55
+ # The purpose of this method is to replicate the behavior of
56
+ # datetime.utcnow().isoformat(), since `utcnow` is now deprecated
57
+ return datetime.now(timezone.utc).isoformat().split("+")[0]
58
+
59
+
53
60
  class QuantumProgram(VersionedModel, CircuitCodeInterface):
54
61
  hardware_data: SynthesisHardwareData
55
62
  initial_values: Optional[InitialConditions]
56
63
  data: GeneratedCircuitData
57
64
  model: ExecutionModel
58
65
  transpiled_circuit: Optional[TranspiledCircuitData]
59
- creation_time: str = pydantic.Field(default_factory=datetime.utcnow().isoformat)
66
+ creation_time: str = pydantic.Field(default_factory=_get_formatted_utc_current_time)
60
67
  synthesis_duration: Optional[SynthesisStepDurations]
61
68
  debug_info: Optional[List[FunctionDebugInfoInterface]]
62
69
  program_id: str = pydantic.Field(default_factory=get_uuid_as_str)
70
+ execution_primitives_input: Optional[PrimitivesInput] = pydantic.Field(default=None)
63
71
 
64
72
  def _hardware_agnostic_program_code(self) -> CodeAndSyntax:
65
73
  circuit_code = self.program_circuit.get_code_by_priority()
@@ -1,12 +1,26 @@
1
1
  from typing import Dict, Optional, Set
2
2
 
3
3
  import pydantic
4
+ import sympy
4
5
 
5
6
  from classiq.interface.backend.pydantic_backend import PydanticExecutionParameter
7
+ from classiq.interface.exceptions import ClassiqValueError
8
+ from classiq.interface.generator.parameters import ParameterType
6
9
 
7
10
 
8
11
  class FunctionExecutionData(pydantic.BaseModel):
9
- power_parameter: Optional[PydanticExecutionParameter] = pydantic.Field(default=None)
12
+ power_parameter: Optional[ParameterType] = pydantic.Field(default=None)
13
+
14
+ @property
15
+ def power_var(self) -> Optional[str]:
16
+ if self.power_parameter is None:
17
+ return None
18
+ power_vars = sympy.sympify(self.power_parameter).free_symbols
19
+ if len(power_vars) != 1:
20
+ raise ClassiqValueError(
21
+ f"Power parameter expression: {self.power_parameter} must contain exactly one variable"
22
+ )
23
+ return str(list(power_vars)[0])
10
24
 
11
25
 
12
26
  class ExecutionData(pydantic.BaseModel):
@@ -19,7 +33,7 @@ class ExecutionData(pydantic.BaseModel):
19
33
  self,
20
34
  ) -> Set[PydanticExecutionParameter]:
21
35
  return {
22
- function_execution_data.power_parameter
36
+ function_execution_data.power_var
23
37
  for function_execution_data in self.function_execution.values()
24
- if function_execution_data.power_parameter is not None
38
+ if function_execution_data.power_var is not None
25
39
  }
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Optional, Tuple
1
+ from typing import Any, Dict, List, Optional, Tuple
2
2
 
3
3
  import pydantic
4
4
 
@@ -38,6 +38,7 @@ class OperationParameter(pydantic.BaseModel):
38
38
 
39
39
  class OperationLink(pydantic.BaseModel):
40
40
  label: str
41
+ inner_label: Optional[str] = None
41
42
  qubits: Tuple[int, ...]
42
43
  type: str
43
44
 
@@ -47,6 +48,11 @@ class OperationLink(pydantic.BaseModel):
47
48
  def __hash__(self) -> int:
48
49
  return hash((type(self), self.label, self.qubits, self.type))
49
50
 
51
+ def __eq__(self, other: Any) -> bool:
52
+ if not isinstance(other, OperationLink):
53
+ return False
54
+ return hash(self) == hash(other)
55
+
50
56
 
51
57
  class OperationLinks(pydantic.BaseModel):
52
58
  inputs: List[OperationLink]
@@ -63,10 +69,9 @@ class Operation(pydantic.BaseModel):
63
69
  target_qubits: Tuple[int, ...]
64
70
  parameters: List[OperationParameter] = pydantic.Field(default_factory=list)
65
71
  operation_level: OperationLevel
66
- # This field is meant to identify unique operations, such as variable initialization
67
- # These will be visualized differently. We don't identify them yet, though, so
68
- # we always set this field to be REGULAR
69
- operation_type: OperationType = pydantic.Field(default=OperationType.REGULAR)
72
+ operation_type: OperationType = pydantic.Field(
73
+ description="Identifies unique operations that are visualized differently"
74
+ )
70
75
 
71
76
 
72
77
  class ProgramVisualModel(VersionedModel):
@@ -1 +1 @@
1
- INTERFACE_VERSION = "2"
1
+ INTERFACE_VERSION = "3"
@@ -56,6 +56,3 @@ class BindOperation(QuantumOperation):
56
56
  raise ClassiqValueError(f"Cannot bind '{handle}'")
57
57
 
58
58
  return handles
59
-
60
- def reversed(self) -> "BindOperation":
61
- return BindOperation(in_handles=self.out_handles, out_handles=self.in_handles)
@@ -0,0 +1,11 @@
1
+ from typing import Literal
2
+
3
+ from classiq.interface.generator.expressions.expression import Expression
4
+ from classiq.interface.model.quantum_expressions.quantum_expression import (
5
+ QuantumExpressionOperation,
6
+ )
7
+
8
+
9
+ class PhaseOperation(QuantumExpressionOperation):
10
+ kind: Literal["PhaseOperation"]
11
+ theta: Expression
@@ -1,25 +1,20 @@
1
- from typing import Any, Dict, Literal, Mapping, Optional
1
+ from typing import Any, Dict, Literal, Mapping
2
2
 
3
3
  import pydantic
4
4
 
5
5
  from classiq.interface.exceptions import ClassiqInternalError, ClassiqValueError
6
- from classiq.interface.generator.expressions.expression import Expression
7
6
  from classiq.interface.generator.functions.concrete_types import ConcreteQuantumType
8
7
  from classiq.interface.generator.functions.port_declaration import (
9
8
  PortDeclarationDirection,
10
9
  )
11
10
  from classiq.interface.helpers.pydantic_model_helpers import values_with_discriminator
12
11
  from classiq.interface.model.parameter import Parameter
13
- from classiq.interface.model.quantum_variable_declaration import (
14
- QuantumVariableDeclaration,
15
- )
16
12
 
17
13
 
18
14
  class AnonPortDeclaration(Parameter):
19
15
  kind: Literal["PortDeclaration"]
20
16
 
21
17
  quantum_type: ConcreteQuantumType
22
- size: Optional[Expression] = pydantic.Field(default=None, exclude=True)
23
18
  direction: PortDeclarationDirection
24
19
 
25
20
  @pydantic.root_validator(pre=True)
@@ -37,12 +32,6 @@ class AnonPortDeclaration(Parameter):
37
32
 
38
33
  return direction
39
34
 
40
- @pydantic.validator("size")
41
- def _propagate_size_to_type(
42
- cls, size: Optional[Expression], values: Mapping[str, Any]
43
- ) -> Optional[Expression]:
44
- return QuantumVariableDeclaration._propagate_size_to_type(size, values)
45
-
46
35
  def rename(self, new_name: str) -> "PortDeclaration":
47
36
  if type(self) not in (AnonPortDeclaration, PortDeclaration):
48
37
  raise ClassiqInternalError
@@ -1,7 +1,8 @@
1
- from typing import Dict, Literal, Mapping, Sequence
1
+ from typing import Dict, Literal, Mapping, Optional, Sequence
2
2
 
3
3
  import pydantic
4
4
 
5
+ from classiq.interface.enum_utils import StrEnum
5
6
  from classiq.interface.generator.arith.arithmetic import (
6
7
  ARITHMETIC_EXPRESSION_RESULT_NAME,
7
8
  compute_arithmetic_result_type,
@@ -17,13 +18,40 @@ from classiq.interface.model.quantum_statement import HandleMetadata
17
18
  from classiq.interface.model.quantum_type import QuantumType
18
19
 
19
20
 
21
+ class ArithmeticOperationKind(StrEnum):
22
+ InplaceAdd = "inplace_add"
23
+ Assignment = "assignment"
24
+ InplaceXor = "inplace_xor"
25
+
26
+
20
27
  class ArithmeticOperation(QuantumAssignmentOperation):
21
28
  kind: Literal["ArithmeticOperation"]
22
29
 
23
- inplace_result: bool = pydantic.Field(
30
+ inplace_result: Optional[bool] = pydantic.Field(
24
31
  description="Determines whether the result variable is initialized",
32
+ default=None,
33
+ exclude=True,
34
+ )
35
+
36
+ operation_kind: ArithmeticOperationKind = pydantic.Field(
37
+ default=ArithmeticOperationKind.InplaceXor,
25
38
  )
26
39
 
40
+ @property
41
+ def is_inplace(self) -> bool:
42
+ return self.get_operation_kind() in (
43
+ ArithmeticOperationKind.InplaceXor,
44
+ ArithmeticOperationKind.InplaceAdd,
45
+ )
46
+
47
+ def get_operation_kind(self) -> ArithmeticOperationKind:
48
+ if hasattr(self, "inplace_result"):
49
+ if self.inplace_result is False:
50
+ return ArithmeticOperationKind.Assignment
51
+ if self.inplace_result is True:
52
+ return ArithmeticOperationKind.InplaceXor
53
+ return self.operation_kind
54
+
27
55
  def initialize_var_types(
28
56
  self,
29
57
  var_types: Dict[str, QuantumType],
@@ -39,7 +67,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
39
67
  self,
40
68
  ) -> Mapping[str, ConcreteHandleBinding]:
41
69
  inouts = dict(super().wiring_inouts)
42
- if self.inplace_result:
70
+ if self.is_inplace:
43
71
  inouts[self.result_name()] = self.result_var
44
72
  return inouts
45
73
 
@@ -49,7 +77,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
49
77
  HandleMetadata(handle=handle, readable_location="in an expression")
50
78
  for handle in self.var_handles
51
79
  ]
52
- if self.inplace_result:
80
+ if self.is_inplace:
53
81
  inouts.append(
54
82
  HandleMetadata(
55
83
  handle=self.result_var,
@@ -60,13 +88,13 @@ class ArithmeticOperation(QuantumAssignmentOperation):
60
88
 
61
89
  @property
62
90
  def wiring_outputs(self) -> Mapping[str, HandleBinding]:
63
- if self.inplace_result:
91
+ if self.is_inplace:
64
92
  return {}
65
93
  return super().wiring_outputs
66
94
 
67
95
  @property
68
96
  def readable_outputs(self) -> Sequence[HandleMetadata]:
69
- if self.inplace_result:
97
+ if self.is_inplace:
70
98
  return []
71
99
  return [
72
100
  HandleMetadata(
@@ -19,7 +19,7 @@ class QuantumLambdaFunction(ASTNode):
19
19
 
20
20
  rename_params: Dict[str, str] = pydantic.Field(
21
21
  default_factory=dict,
22
- exclude=False,
22
+ exclude=True,
23
23
  )
24
24
 
25
25
  pos_rename_params: List[str] = pydantic.Field(
@@ -41,6 +41,9 @@ class QuantumLambdaFunction(ASTNode):
41
41
  def py_callable(self) -> Callable:
42
42
  return self._py_callable
43
43
 
44
+ def is_generative(self) -> bool:
45
+ return self.py_callable is not None
46
+
44
47
  def set_py_callable(self, py_callable: Callable) -> None:
45
48
  self._py_callable = py_callable
46
49
 
@@ -1,6 +1,7 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any, Dict, Mapping, Optional, Sequence
2
+ from typing import Any, Callable, Dict, Mapping, Optional, Sequence
3
3
 
4
+ import pydantic
4
5
  from pydantic import Extra, root_validator
5
6
 
6
7
  from classiq.interface.ast_node import ASTNode
@@ -29,6 +30,8 @@ class HandleMetadata:
29
30
 
30
31
 
31
32
  class QuantumOperation(QuantumStatement):
33
+ _generative_blocks: Dict[str, Callable] = pydantic.PrivateAttr(default_factory=dict)
34
+
32
35
  @property
33
36
  def wiring_inputs(self) -> Mapping[str, HandleBinding]:
34
37
  return dict()
@@ -64,3 +67,15 @@ class QuantumOperation(QuantumStatement):
64
67
  @property
65
68
  def readable_outputs(self) -> Sequence[HandleMetadata]:
66
69
  return [HandleMetadata(handle=handle) for handle in self.outputs]
70
+
71
+ def set_generative_block(self, block_name: str, py_callable: Callable) -> None:
72
+ self._generative_blocks[block_name] = py_callable
73
+
74
+ def get_generative_block(self, block_name: str) -> Callable:
75
+ return self._generative_blocks[block_name]
76
+
77
+ def has_generative_block(self, block_name: str) -> bool:
78
+ return block_name in self._generative_blocks
79
+
80
+ def is_generative(self) -> bool:
81
+ return len(self._generative_blocks) > 0