classiq 0.42.2__py3-none-any.whl → 0.43.1__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 (113) hide show
  1. classiq/__init__.py +2 -6
  2. classiq/_internals/api_wrapper.py +6 -12
  3. classiq/_internals/authentication/token_manager.py +5 -2
  4. classiq/_internals/jobs.py +5 -10
  5. classiq/analyzer/rb.py +3 -3
  6. classiq/applications/chemistry/chemistry_model_constructor.py +12 -8
  7. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +2 -2
  8. classiq/applications/finance/finance_model_constructor.py +16 -13
  9. classiq/applications/qsvm/__init__.py +1 -3
  10. classiq/applications/qsvm/qsvm_model_constructor.py +7 -6
  11. classiq/exceptions.py +9 -4
  12. classiq/execution/execution_session.py +5 -2
  13. classiq/execution/qnn.py +1 -1
  14. classiq/executor.py +0 -2
  15. classiq/interface/_version.py +1 -1
  16. classiq/interface/chemistry/operator.py +19 -5
  17. classiq/interface/executor/constants.py +1 -0
  18. classiq/interface/finance/function_input.py +16 -10
  19. classiq/interface/generator/application_apis/chemistry_declarations.py +2 -2
  20. classiq/interface/generator/application_apis/qsvm_declarations.py +4 -2
  21. classiq/interface/generator/arith/argument_utils.py +20 -3
  22. classiq/interface/generator/arith/arithmetic_expression_validator.py +3 -26
  23. classiq/interface/generator/arith/binary_ops.py +8 -14
  24. classiq/interface/generator/arith/extremum_operations.py +30 -0
  25. classiq/interface/generator/arith/number_utils.py +1 -1
  26. classiq/interface/generator/arith/unary_ops.py +1 -3
  27. classiq/interface/generator/compiler_keywords.py +1 -1
  28. classiq/interface/generator/expressions/atomic_expression_functions.py +13 -3
  29. classiq/interface/generator/expressions/enums/__init__.py +0 -20
  30. classiq/interface/generator/expressions/enums/finance_functions.py +11 -18
  31. classiq/interface/generator/expressions/non_symbolic_expr.py +119 -0
  32. classiq/interface/generator/expressions/qmod_qarray_proxy.py +52 -37
  33. classiq/interface/generator/expressions/qmod_qscalar_proxy.py +16 -11
  34. classiq/interface/generator/expressions/qmod_sized_proxy.py +5 -5
  35. classiq/interface/generator/function_param_list_without_self_reference.py +0 -10
  36. classiq/interface/generator/function_params.py +0 -4
  37. classiq/interface/generator/functions/__init__.py +0 -20
  38. classiq/interface/generator/functions/builtins/core_library/exponentiation_functions.py +2 -2
  39. classiq/interface/generator/functions/builtins/open_lib_functions.py +530 -1
  40. classiq/interface/generator/functions/classical_type.py +22 -69
  41. classiq/interface/generator/functions/port_declaration.py +0 -11
  42. classiq/interface/generator/model/__init__.py +0 -1
  43. classiq/interface/generator/model/model.py +9 -185
  44. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +3 -1
  45. classiq/interface/generator/types/builtin_enum_declarations.py +69 -0
  46. classiq/interface/generator/types/builtin_struct_declarations/pauli_struct_declarations.py +2 -2
  47. classiq/interface/generator/types/enum_declaration.py +57 -0
  48. classiq/interface/jobs.py +36 -65
  49. classiq/interface/model/bind_operation.py +3 -0
  50. classiq/interface/model/classical_parameter_declaration.py +3 -0
  51. classiq/interface/model/handle_binding.py +7 -0
  52. classiq/interface/model/inplace_binary_operation.py +13 -15
  53. classiq/interface/model/model.py +8 -20
  54. classiq/interface/model/native_function_definition.py +0 -17
  55. classiq/interface/model/quantum_function_call.py +63 -182
  56. classiq/interface/model/quantum_type.py +71 -10
  57. classiq/interface/server/routes.py +0 -6
  58. classiq/qmod/__init__.py +3 -3
  59. classiq/qmod/builtins/__init__.py +10 -1
  60. classiq/qmod/builtins/classical_execution_primitives.py +4 -2
  61. classiq/qmod/builtins/enums.py +177 -0
  62. classiq/qmod/builtins/functions.py +1 -2
  63. classiq/qmod/builtins/operations.py +2 -4
  64. classiq/qmod/builtins/structs.py +16 -17
  65. classiq/qmod/declaration_inferrer.py +23 -20
  66. classiq/qmod/model_state_container.py +2 -0
  67. classiq/qmod/native/pretty_printer.py +31 -13
  68. classiq/qmod/pretty_print/pretty_printer.py +52 -27
  69. classiq/qmod/qmod_constant.py +7 -3
  70. classiq/qmod/qmod_parameter.py +2 -1
  71. classiq/qmod/qmod_struct.py +9 -33
  72. classiq/qmod/qmod_variable.py +55 -22
  73. classiq/qmod/quantum_callable.py +6 -1
  74. classiq/qmod/quantum_expandable.py +29 -11
  75. classiq/qmod/quantum_function.py +8 -4
  76. classiq/qmod/semantics/annotation.py +38 -0
  77. classiq/qmod/semantics/error_manager.py +49 -0
  78. classiq/qmod/semantics/static_semantics_visitor.py +308 -0
  79. classiq/qmod/semantics/validation/func_call_validation.py +149 -0
  80. classiq/qmod/semantics/validation/types_validation.py +21 -0
  81. classiq/qmod/symbolic.py +6 -6
  82. classiq/qmod/symbolic_expr.py +26 -11
  83. classiq/qmod/utilities.py +23 -1
  84. {classiq-0.42.2.dist-info → classiq-0.43.1.dist-info}/METADATA +2 -2
  85. {classiq-0.42.2.dist-info → classiq-0.43.1.dist-info}/RECORD +88 -103
  86. classiq/_internals/_qfunc_ext.py +0 -6
  87. classiq/applications/libraries/ampltitude_estimation_library.py +0 -11
  88. classiq/interface/generator/credit_risk_example/linear_gci.py +0 -122
  89. classiq/interface/generator/credit_risk_example/weighted_adder.py +0 -69
  90. classiq/interface/generator/expressions/enums/chemistry.py +0 -28
  91. classiq/interface/generator/expressions/enums/classical_enum.py +0 -20
  92. classiq/interface/generator/expressions/enums/ladder_operator.py +0 -6
  93. classiq/interface/generator/expressions/enums/optimizers.py +0 -9
  94. classiq/interface/generator/expressions/enums/pauli.py +0 -8
  95. classiq/interface/generator/expressions/enums/qsvm_feature_map_entanglement.py +0 -9
  96. classiq/interface/generator/functions/foreign_function_definition.py +0 -114
  97. classiq/interface/generator/functions/function_implementation.py +0 -107
  98. classiq/interface/generator/functions/native_function_definition.py +0 -155
  99. classiq/interface/generator/functions/quantum_function_declaration.py +0 -69
  100. classiq/interface/generator/functions/register.py +0 -44
  101. classiq/interface/generator/functions/register_mapping_data.py +0 -106
  102. classiq/interface/generator/inequality_mixer.py +0 -51
  103. classiq/interface/generator/model/classical_main_validator.py +0 -106
  104. classiq/interface/generator/range_mixer.py +0 -56
  105. classiq/interface/generator/state_propagator.py +0 -74
  106. classiq/interface/model/resolvers/function_call_resolver.py +0 -64
  107. classiq/interface/model/validations/__init__.py +0 -0
  108. classiq/interface/model/validations/handle_validation_base.py +0 -55
  109. classiq/interface/model/validations/handles_validator.py +0 -153
  110. classiq/interface/model/validations/port_to_wire_name_generator.py +0 -12
  111. /classiq/{interface/generator/credit_risk_example → qmod/semantics}/__init__.py +0 -0
  112. /classiq/{interface/model/resolvers → qmod/semantics/validation}/__init__.py +0 -0
  113. {classiq-0.42.2.dist-info → classiq-0.43.1.dist-info}/WHEEL +0 -0
@@ -1,122 +0,0 @@
1
- from statistics import NormalDist
2
- from typing import Any, Dict, List, Tuple
3
-
4
- import numpy as np
5
- import pydantic
6
-
7
- from classiq.interface.generator import function_params, linear_pauli_rotations
8
- from classiq.interface.generator.arith.register_user_input import RegisterUserInput
9
- from classiq.interface.helpers.custom_pydantic_types import (
10
- PydanticNonOneProbabilityFloat,
11
- PydanticProbabilityFloat,
12
- )
13
-
14
- from classiq.exceptions import ClassiqValueError
15
-
16
- RHOS_PZEROS_LENGTH_ERROR_MSG = "rhos and p_zeros must have the same length"
17
-
18
-
19
- class LinearGCI(function_params.FunctionParams):
20
- """
21
- A circuit composed of a series of Y rotations to perform a linear approximation to the Gaussian Conditional
22
- Independence model.
23
- The model consists of a Bernoulli probability, which itself is a function of a latent random variable x:
24
- p(x) = F( (F_inv(p_zero) + x*sqrt(rho))/sqrt(1-rho) )
25
- with F being the Gaussian CDF, p_zero is p(x=0) when rho=0, and rho reflects the sensitivity.
26
-
27
- The circuit takes a state register |x> and zero-input target qubits.
28
- Each target qubit undergoes the following transformation:
29
- |x>|0> -> cos((slope*x + offset)/2)|x>|0> + sin((slope*x + offset)/2)|x>|1>
30
- Where the slope and the offset are determined by the linear approximation of the inverse sine of p(x).
31
- """
32
-
33
- num_state_qubits: pydantic.PositiveInt = pydantic.Field(
34
- description="The number of input qubits"
35
- )
36
- truncation_value: float = pydantic.Field(
37
- description="The truncation value of the latent normal distribution"
38
- )
39
- p_zeros: List[PydanticProbabilityFloat] = pydantic.Field(
40
- description="The probability when the latent normal variable equals zero"
41
- )
42
- rhos: List[PydanticNonOneProbabilityFloat] = pydantic.Field(
43
- description="The sensitivity of the probability to changes in the latent normal variable values"
44
- )
45
-
46
- @pydantic.validator("rhos")
47
- def validate_rhos(
48
- cls, rhos: List[PydanticNonOneProbabilityFloat], values: Dict[str, Any]
49
- ) -> List[PydanticNonOneProbabilityFloat]:
50
- p_zeros = values.get("p_zeros")
51
- if p_zeros is None:
52
- raise ClassiqValueError("Cannot validate rhos, p_zeros is missing")
53
-
54
- if len(p_zeros) != len(rhos):
55
- raise ClassiqValueError(RHOS_PZEROS_LENGTH_ERROR_MSG)
56
- return rhos
57
-
58
- @property
59
- def linear_pauli_rotations(self) -> linear_pauli_rotations.LinearPauliRotations:
60
- offsets, slopes = get_gci_values(
61
- qubit_count_state=self.num_state_qubits,
62
- truncation_value=self.truncation_value,
63
- rhos=self.rhos,
64
- p_zeros=self.p_zeros,
65
- )
66
- return linear_pauli_rotations.LinearPauliRotations(
67
- num_state_qubits=self.num_state_qubits,
68
- bases=["Y"] * len(self.rhos),
69
- offsets=offsets,
70
- slopes=slopes,
71
- )
72
-
73
- def _create_ios(self) -> None:
74
- state_name = linear_pauli_rotations.STATE
75
- target_name = linear_pauli_rotations.TARGET
76
- self._inputs = {
77
- state_name: RegisterUserInput(name=state_name, size=self.num_state_qubits),
78
- target_name: RegisterUserInput(name=target_name, size=len(self.rhos)),
79
- }
80
- self._outputs = {**self.inputs}
81
-
82
-
83
- def get_gci_values(
84
- *,
85
- qubit_count_state: int,
86
- truncation_value: float,
87
- rhos: List[float],
88
- p_zeros: List[float],
89
- ) -> Tuple[List[float], List[float]]:
90
- qubit_count_target = len(rhos)
91
-
92
- # Qiskit's code below
93
-
94
- # get normal (inverse) CDF and pdf
95
- def cdf(x: float) -> float:
96
- return NormalDist().cdf(x)
97
-
98
- def inv_cdf(p: float) -> float:
99
- return NormalDist().inv_cdf(p)
100
-
101
- def pdf(x: float) -> float:
102
- return NormalDist().pdf(x)
103
-
104
- # create linear rotations for conditional defaults
105
- slopes = []
106
- offsets = []
107
- for k in range(qubit_count_target):
108
- psi = inv_cdf(p_zeros[k]) / np.sqrt(1 - rhos[k])
109
-
110
- # compute slope / offset
111
- slope = -np.sqrt(rhos[k]) / np.sqrt(1 - rhos[k])
112
- slope *= pdf(psi) / np.sqrt(1 - cdf(psi)) / np.sqrt(cdf(psi))
113
- offset = 2 * np.arcsin(np.sqrt(cdf(psi)))
114
-
115
- # adjust for integer to normal range mapping
116
- offset += slope * (-truncation_value)
117
- slope *= 2 * truncation_value / (2**qubit_count_state - 1)
118
-
119
- offsets.append(offset)
120
- slopes.append(slope)
121
-
122
- return offsets, slopes
@@ -1,69 +0,0 @@
1
- from typing import Any, Dict, List, Optional
2
-
3
- import numpy as np
4
- import pydantic
5
-
6
- from classiq.interface.generator import function_params
7
- from classiq.interface.generator.arith.register_user_input import RegisterUserInput
8
- from classiq.interface.generator.function_params import get_zero_input_name
9
-
10
- from classiq.exceptions import ClassiqValueError
11
-
12
- LENGTH_ERROR_MESSAGE = "Input length error: Length of weights vector has to be identical to the number of input qubits"
13
- STATE = "state"
14
- SUM = "sum"
15
-
16
-
17
- class PydanticStrictNonNegativeInteger(pydantic.ConstrainedInt):
18
- strict = True
19
- ge = 0
20
-
21
-
22
- class WeightedAdder(function_params.FunctionParams):
23
- """
24
- Creates a circuit implementing a scalar product between an n-qubit state |q1,q2,...,qn> and an n-length non-negative
25
- integer vector (w1,w2,...wn), such that the result of the output register is |q1*w1+q2*w2+...qn*wn>.
26
- If no weights are provided, they are default to 1 for every qubit.
27
- """
28
-
29
- num_state_qubits: pydantic.PositiveInt = pydantic.Field(
30
- description="The number of input qubits"
31
- )
32
- weights: List[PydanticStrictNonNegativeInteger] = pydantic.Field(
33
- default=None,
34
- description="List of non-negative integers corresponding to the weight of each qubit",
35
- )
36
-
37
- @pydantic.validator("weights", always=True, pre=True)
38
- def validate_weights(
39
- cls,
40
- weights: Optional[List[PydanticStrictNonNegativeInteger]],
41
- values: Dict[str, Any],
42
- ) -> List[PydanticStrictNonNegativeInteger]:
43
- num_state_qubits = values.get("num_state_qubits")
44
- if num_state_qubits is None:
45
- raise ClassiqValueError(
46
- "Missing num_state_qubits and weights, either must be provided"
47
- )
48
- if weights is None:
49
- return [PydanticStrictNonNegativeInteger(1)] * num_state_qubits
50
- if len(weights) != num_state_qubits:
51
- raise ClassiqValueError(LENGTH_ERROR_MESSAGE)
52
- return weights
53
-
54
- def num_sum_qubits(self) -> int:
55
- sum_weights = np.sum(self.weights)
56
- if sum_weights > 0:
57
- return 1 + int(np.floor(np.log2(sum_weights)))
58
- return 1
59
-
60
- def _create_ios(self) -> None:
61
- self._inputs = {
62
- STATE: RegisterUserInput(name=STATE, size=self.num_state_qubits)
63
- }
64
- zero_input_name = get_zero_input_name(SUM)
65
- self._create_zero_input_registers({zero_input_name: self.num_sum_qubits()})
66
- self._outputs = {
67
- **self._inputs,
68
- SUM: RegisterUserInput(name=SUM, size=self.num_sum_qubits()),
69
- }
@@ -1,28 +0,0 @@
1
- import enum
2
- from typing import Dict
3
-
4
- from sympy import Basic, Integer
5
-
6
- from classiq.interface.chemistry.elements import ELEMENTS
7
- from classiq.interface.chemistry.ground_state_problem import (
8
- FermionMapping as LibraryFermionMapping,
9
- )
10
- from classiq.interface.generator.expressions.enums.classical_enum import ClassicalEnum
11
-
12
- Element = ClassicalEnum("Element", ELEMENTS) # type: ignore[call-overload]
13
-
14
-
15
- class MolecularBasis(enum.Enum):
16
- sto3g = 0
17
-
18
- def to_sympy(self) -> Basic:
19
- return Integer(self.value)
20
-
21
- @staticmethod
22
- def sympy_locals() -> Dict[str, Basic]:
23
- return {f"Basis_{basis.name}": basis.to_sympy() for basis in MolecularBasis}
24
-
25
-
26
- FermionMapping = ClassicalEnum( # type: ignore[call-overload]
27
- "FermionMapping", [mapping.name for mapping in LibraryFermionMapping]
28
- )
@@ -1,20 +0,0 @@
1
- import enum
2
- from typing import Any, Mapping
3
-
4
- from classiq.interface.helpers.classproperty import classproperty
5
-
6
-
7
- class ClassicalEnum(int, enum.Enum):
8
- def __str__(self) -> str:
9
- return f"{type(self).__name__}.{self.name}"
10
-
11
- def __repr__(self) -> str:
12
- return str(self)
13
-
14
- @classproperty
15
- def type_name(cls) -> str: # noqa: N805
16
- return "Enum"
17
-
18
- @classproperty
19
- def fields(cls) -> Mapping[str, Any]: # noqa: N805
20
- return {i.name: i for i in cls} # type:ignore[attr-defined]
@@ -1,6 +0,0 @@
1
- from classiq.interface.generator.expressions.enums.classical_enum import ClassicalEnum
2
-
3
-
4
- class LadderOperator(ClassicalEnum):
5
- PLUS = 0
6
- MINUS = 1
@@ -1,9 +0,0 @@
1
- from classiq.interface.generator.expressions.enums.classical_enum import ClassicalEnum
2
-
3
-
4
- class Optimizer(ClassicalEnum):
5
- COBYLA = 1
6
- SPSA = 2
7
- L_BFGS_B = 3
8
- NELDER_MEAD = 4
9
- ADAM = 5
@@ -1,8 +0,0 @@
1
- from classiq.interface.generator.expressions.enums.classical_enum import ClassicalEnum
2
-
3
-
4
- class Pauli(ClassicalEnum):
5
- I = 0 # noqa: E741
6
- X = 1
7
- Y = 2
8
- Z = 3
@@ -1,9 +0,0 @@
1
- from classiq.interface.generator.expressions.enums.classical_enum import ClassicalEnum
2
-
3
-
4
- class QSVMFeatureMapEntanglement(ClassicalEnum):
5
- FULL = 0
6
- LINEAR = 1
7
- CIRCULAR = 2
8
- SCA = 3
9
- PAIRWISE = 4
@@ -1,114 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Dict, List, NoReturn, Optional, Tuple, Union
4
-
5
- import pydantic
6
-
7
- from classiq.interface.generator.arith.register_user_input import RegisterUserInput
8
- from classiq.interface.generator.function_params import ArithmeticIODict, IOName
9
- from classiq.interface.generator.functions.function_implementation import (
10
- FunctionImplementation,
11
- )
12
- from classiq.interface.generator.functions.port_declaration import (
13
- SynthesisPortDeclaration,
14
- )
15
- from classiq.interface.generator.functions.quantum_function_declaration import (
16
- SynthesisQuantumFunctionDeclaration,
17
- )
18
- from classiq.interface.generator.functions.register import Register
19
- from classiq.interface.generator.functions.register_mapping_data import (
20
- RegisterMappingData,
21
- )
22
-
23
- from classiq.exceptions import ClassiqValueError
24
-
25
- ImplementationsType = Tuple[FunctionImplementation, ...]
26
-
27
-
28
- class SynthesisForeignFunctionDefinition(SynthesisQuantumFunctionDeclaration):
29
- """
30
- Facilitates the creation of a user-defined elementary function
31
-
32
- This class sets extra to forbid so that it can be used in a Union and not "steal"
33
- objects from other classes.
34
- """
35
-
36
- register_mapping: RegisterMappingData = pydantic.Field(
37
- default_factory=RegisterMappingData,
38
- description="The PortDirection data that is common to all implementations of the function",
39
- )
40
- implementations: Optional[ImplementationsType] = pydantic.Field(
41
- description="The implementations of the custom function",
42
- )
43
-
44
- @pydantic.validator("register_mapping")
45
- def _validate_register_mapping(
46
- cls, register_mapping: RegisterMappingData
47
- ) -> RegisterMappingData:
48
- if not register_mapping.output_registers:
49
- raise ClassiqValueError(
50
- "The outputs of a custom function must be non-empty"
51
- )
52
- return register_mapping
53
-
54
- @pydantic.validator("implementations", pre=True)
55
- def _parse_implementations(
56
- cls,
57
- implementations: Optional[Union[ImplementationsType, FunctionImplementation]],
58
- ) -> Optional[ImplementationsType]:
59
- if isinstance(implementations, FunctionImplementation):
60
- return (implementations,)
61
-
62
- return implementations
63
-
64
- @pydantic.validator("implementations")
65
- def _validate_implementations(
66
- cls,
67
- implementations: Optional[ImplementationsType],
68
- values: Dict[str, Any],
69
- ) -> Optional[ImplementationsType]:
70
- if not implementations:
71
- raise ClassiqValueError(
72
- "The implementations of a custom function must be non-empty."
73
- )
74
-
75
- register_mapping = values.get("register_mapping")
76
- assert isinstance(register_mapping, RegisterMappingData)
77
- for impl in implementations:
78
- impl.validate_ranges_of_all_registers(register_mapping=register_mapping)
79
-
80
- return implementations
81
-
82
- @property
83
- def inputs(self) -> ArithmeticIODict:
84
- return _map_reg_user_input(self.register_mapping.input_registers)
85
-
86
- @property
87
- def outputs(self) -> ArithmeticIODict:
88
- return _map_reg_user_input(self.register_mapping.output_registers)
89
-
90
- def renamed(self, new_name: str) -> SynthesisForeignFunctionDefinition:
91
- return SynthesisForeignFunctionDefinition(
92
- name=new_name,
93
- implementations=self.implementations,
94
- register_mapping=self.register_mapping,
95
- )
96
-
97
- @property
98
- def port_declarations(self) -> Dict[IOName, SynthesisPortDeclaration]:
99
- raise ClassiqValueError(
100
- "Bad usage of foreign function definition: port_declarations"
101
- )
102
-
103
- @port_declarations.setter
104
- def port_declarations(self, value: Any) -> NoReturn:
105
- raise ClassiqValueError(
106
- "Bad usage of foreign function definition: port_declarations"
107
- )
108
-
109
-
110
- def _map_reg_user_input(registers: List[Register]) -> ArithmeticIODict:
111
- return {
112
- reg.name: RegisterUserInput(size=len(reg.qubits), name=reg.name)
113
- for reg in registers
114
- }
@@ -1,107 +0,0 @@
1
- from itertools import chain
2
- from typing import Optional, Tuple, Union
3
-
4
- import pydantic
5
- from pydantic import BaseModel
6
-
7
- from classiq.interface.generator.functions.register import Register
8
- from classiq.interface.generator.functions.register_mapping_data import (
9
- RegisterMappingData,
10
- )
11
- from classiq.interface.helpers.custom_pydantic_types import PydanticNonEmptyString
12
-
13
- from classiq.exceptions import ClassiqValueError
14
-
15
- RegistersStrictType = Tuple[Register, ...]
16
- RegistersType = Union[Register, RegistersStrictType]
17
-
18
-
19
- def to_tuple(reg_type: RegistersType) -> RegistersStrictType:
20
- if isinstance(reg_type, Register):
21
- return (reg_type,)
22
- else:
23
- return reg_type
24
-
25
-
26
- class FunctionImplementation(BaseModel):
27
- """
28
- Facilitates the creation of a user-defined custom function implementation
29
- """
30
-
31
- class Config:
32
- validate_all = True
33
- extra = "forbid"
34
-
35
- name: Optional[PydanticNonEmptyString] = pydantic.Field(
36
- default=None,
37
- description="The name of the custom implementation",
38
- )
39
-
40
- serialized_circuit: PydanticNonEmptyString = pydantic.Field(
41
- description="The QASM code of the custom function implementation",
42
- )
43
-
44
- auxiliary_registers: RegistersStrictType = pydantic.Field(
45
- default_factory=tuple,
46
- description="A tuple of auxiliary registers to the custom implementation",
47
- )
48
-
49
- def num_qubits_in_all_registers(self, register_mapping: RegisterMappingData) -> int:
50
- all_input_registers = (
51
- register_mapping.input_registers
52
- + register_mapping.zero_input_registers
53
- + list(self.auxiliary_registers)
54
- )
55
- input_qubits = set(
56
- chain.from_iterable(register.qubits for register in all_input_registers)
57
- )
58
- return len(input_qubits)
59
-
60
- @pydantic.validator(
61
- "auxiliary_registers",
62
- pre=True,
63
- always=True,
64
- )
65
- def validate_all_registers_are_tuples(
66
- cls,
67
- registers: RegistersType,
68
- ) -> RegistersStrictType:
69
- if isinstance(registers, Register):
70
- return (registers,)
71
- return registers
72
-
73
- def validate_ranges_of_all_registers(
74
- self, register_mapping: RegisterMappingData
75
- ) -> None:
76
- input_registers = register_mapping.input_registers
77
- output_registers = register_mapping.output_registers
78
- zero_input_registers = register_mapping.zero_input_registers
79
- auxiliary_registers = list(self.auxiliary_registers)
80
-
81
- all_input_registers = (
82
- input_registers + zero_input_registers + auxiliary_registers
83
- )
84
- input_qubits = set(
85
- chain.from_iterable(register.qubits for register in all_input_registers)
86
- )
87
- num_qubits = len(input_qubits)
88
- all_qubits = set(range(num_qubits))
89
- if num_qubits != sum(register.width for register in all_input_registers):
90
- raise ClassiqValueError("The input registers must not overlap.")
91
- if input_qubits != all_qubits:
92
- raise ClassiqValueError(
93
- "The set of qubits contained in all registers must be consecutive."
94
- )
95
-
96
- all_output_registers = output_registers + auxiliary_registers
97
- output_qubits = set(
98
- chain.from_iterable(register.qubits for register in all_output_registers)
99
- )
100
- if len(output_qubits) != sum(
101
- register.width for register in all_output_registers
102
- ):
103
- raise ClassiqValueError("The output registers must not overlap.")
104
- if not output_qubits == all_qubits:
105
- raise ClassiqValueError(
106
- "The input and output qubits must be mutually consistent."
107
- )
@@ -1,155 +0,0 @@
1
- from typing import Any, Dict, List, Optional
2
-
3
- import pydantic
4
-
5
- from classiq.interface.generator.arith.register_user_input import RegisterUserInput
6
- from classiq.interface.generator.function_params import IOName, PortDirection
7
- from classiq.interface.generator.functions.port_declaration import (
8
- SynthesisPortDeclaration,
9
- )
10
- from classiq.interface.generator.functions.quantum_function_declaration import (
11
- SynthesisQuantumFunctionDeclaration,
12
- )
13
- from classiq.interface.generator.quantum_function_call import (
14
- SynthesisQuantumFunctionCall,
15
- WireDict,
16
- WireName,
17
- )
18
- from classiq.interface.generator.validations import flow_graph
19
-
20
- from classiq.exceptions import ClassiqValueError
21
-
22
- LOGIC_FLOW_DUPLICATE_NAME_ERROR_MSG = (
23
- "Cannot have multiple function calls with the same name"
24
- )
25
-
26
-
27
- class IOData(pydantic.BaseModel):
28
- wire: WireName = pydantic.Field(
29
- description="The name of the wire of the PortDirection data."
30
- )
31
- reg: RegisterUserInput = pydantic.Field(
32
- description="The register information about the PortDirection data."
33
- )
34
-
35
- class Config:
36
- frozen = True
37
-
38
-
39
- class SynthesisNativeFunctionDefinition(SynthesisQuantumFunctionDeclaration):
40
- """
41
- Facilitates the creation of a user-defined composite function
42
-
43
- This class sets extra to forbid so that it can be used in a Union and not "steal"
44
- objects from other classes.
45
- """
46
-
47
- input_ports_wiring: Dict[IOName, WireName] = pydantic.Field(
48
- description="The mapping between the functions input ports, to inner wires",
49
- default_factory=dict,
50
- )
51
-
52
- output_ports_wiring: Dict[IOName, WireName] = pydantic.Field(
53
- description="The mapping between the functions output ports, to inner wires",
54
- default_factory=dict,
55
- )
56
-
57
- body: List[SynthesisQuantumFunctionCall] = pydantic.Field(
58
- default_factory=list, description="List of function calls to perform."
59
- )
60
-
61
- def validate_body(self) -> None:
62
- function_call_names = {call.name for call in self.body}
63
- if len(function_call_names) != len(self.body):
64
- raise ClassiqValueError(LOGIC_FLOW_DUPLICATE_NAME_ERROR_MSG)
65
- flow_graph.validate_legal_wiring(
66
- self.body,
67
- flow_input_names=list(self.input_ports_wiring.values()),
68
- flow_output_names=list(self.output_ports_wiring.values()),
69
- )
70
- flow_graph.validate_acyclic_logic_flow(
71
- self.body,
72
- flow_input_names=list(self.input_ports_wiring.values()),
73
- flow_output_names=list(self.output_ports_wiring.values()),
74
- )
75
-
76
- @classmethod
77
- def _validate_direction_ports(
78
- cls,
79
- port_declarations: Dict[IOName, SynthesisPortDeclaration],
80
- directions_external_port_wiring: WireDict,
81
- direction: PortDirection,
82
- ) -> None:
83
- for io_name in directions_external_port_wiring:
84
- if (
85
- io_name not in port_declarations
86
- or not port_declarations[io_name].direction == direction
87
- ):
88
- raise ClassiqValueError(
89
- f"The wired {direction} port {io_name!r} is not declared."
90
- )
91
-
92
- @pydantic.root_validator
93
- def validate_ports(cls, values: Dict[str, Any]) -> Dict[str, Any]:
94
- port_declarations: Optional[Dict[IOName, SynthesisPortDeclaration]] = (
95
- values.get("port_declarations")
96
- )
97
- if port_declarations is None:
98
- return values
99
- cls._validate_direction_ports(
100
- port_declarations,
101
- values.get("input_ports_wiring", dict()),
102
- PortDirection.Input,
103
- )
104
- cls._validate_direction_ports(
105
- port_declarations,
106
- values.get("output_ports_wiring", dict()),
107
- PortDirection.Output,
108
- )
109
- return values
110
-
111
- @pydantic.validator("input_ports_wiring", always=True)
112
- def _populate_input_ports_wiring(
113
- cls, input_ports_wiring: Dict[IOName, WireName], values: Dict[str, Any]
114
- ) -> Dict[IOName, WireName]:
115
- return _validate_ports_wiring_for_direction(
116
- input_ports_wiring, values, PortDirection.Input
117
- )
118
-
119
- @pydantic.validator("output_ports_wiring", always=True)
120
- def _populate_output_ports_wiring(
121
- cls, output_ports_wiring: Dict[IOName, WireName], values: Dict[str, Any]
122
- ) -> Dict[IOName, WireName]:
123
- return _validate_ports_wiring_for_direction(
124
- output_ports_wiring, values, PortDirection.Output
125
- )
126
-
127
-
128
- def _get_single_port_wiring(
129
- ports_wiring: Dict[IOName, WireName], name: str, direction: PortDirection
130
- ) -> WireName:
131
- direction_id = "out" if direction == PortDirection.Output else "in"
132
- return ports_wiring.get(name) or f"{name}_{direction_id}"
133
-
134
-
135
- def _port_declarations_to_wiring(
136
- ports_wiring: Dict[IOName, WireName],
137
- port_decls: Dict[IOName, SynthesisPortDeclaration],
138
- direction: PortDirection,
139
- ) -> Dict[IOName, WireName]:
140
- return {
141
- name: _get_single_port_wiring(ports_wiring, name, direction)
142
- for name, port in port_decls.items()
143
- if port.direction.includes_port_direction(direction)
144
- }
145
-
146
-
147
- def _validate_ports_wiring_for_direction(
148
- ports_wiring: Dict[IOName, WireName],
149
- values: Dict[str, Any],
150
- direction: PortDirection,
151
- ) -> Dict[IOName, WireName]:
152
- port_decls = values.get("port_declarations")
153
- if port_decls is None:
154
- return ports_wiring
155
- return _port_declarations_to_wiring(ports_wiring, port_decls, direction)