classiq 0.51.1__py3-none-any.whl → 0.53.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 (158) hide show
  1. classiq/_internals/api_wrapper.py +47 -15
  2. classiq/_internals/authentication/auth0.py +20 -4
  3. classiq/_internals/authentication/password_manager.py +16 -4
  4. classiq/_internals/client.py +3 -3
  5. classiq/_internals/host_checker.py +5 -3
  6. classiq/_internals/jobs.py +3 -3
  7. classiq/analyzer/analyzer_utilities.py +1 -1
  8. classiq/applications/chemistry/ground_state_problem.py +1 -1
  9. classiq/applications/combinatorial_helpers/pyomo_utils.py +3 -1
  10. classiq/applications/qnn/gradients/quantum_gradient.py +1 -1
  11. classiq/applications/qnn/qlayer.py +25 -6
  12. classiq/execution/__init__.py +5 -0
  13. classiq/execution/execution_session.py +43 -2
  14. classiq/execution/iqcc.py +63 -0
  15. classiq/execution/jobs.py +2 -2
  16. classiq/execution/qaoa.py +84 -0
  17. classiq/executor.py +3 -3
  18. classiq/interface/_version.py +1 -1
  19. classiq/interface/analyzer/analysis_params.py +19 -9
  20. classiq/interface/analyzer/cytoscape_graph.py +10 -3
  21. classiq/interface/analyzer/result.py +6 -5
  22. classiq/interface/applications/qsvm.py +13 -12
  23. classiq/interface/backend/backend_preferences.py +78 -105
  24. classiq/interface/backend/ionq/ionq_quantum_program.py +12 -19
  25. classiq/interface/backend/pydantic_backend.py +24 -12
  26. classiq/interface/backend/quantum_backend_providers.py +2 -0
  27. classiq/interface/chemistry/fermionic_operator.py +7 -7
  28. classiq/interface/chemistry/ground_state_problem.py +23 -18
  29. classiq/interface/chemistry/molecule.py +10 -5
  30. classiq/interface/chemistry/operator.py +71 -44
  31. classiq/interface/combinatorial_optimization/mht_qaoa_input.py +2 -1
  32. classiq/interface/debug_info/debug_info.py +3 -4
  33. classiq/interface/exceptions.py +3 -1
  34. classiq/interface/execution/iqcc.py +19 -0
  35. classiq/interface/execution/jobs.py +10 -10
  36. classiq/interface/executor/aws_execution_cost.py +37 -20
  37. classiq/interface/executor/execution_preferences.py +1 -2
  38. classiq/interface/executor/execution_request.py +2 -2
  39. classiq/interface/executor/execution_result.py +4 -2
  40. classiq/interface/executor/iqae_result.py +1 -1
  41. classiq/interface/executor/optimizer_preferences.py +14 -10
  42. classiq/interface/executor/quantum_code.py +21 -16
  43. classiq/interface/executor/register_initialization.py +10 -10
  44. classiq/interface/executor/result.py +31 -17
  45. classiq/interface/executor/vqe_result.py +1 -1
  46. classiq/interface/finance/function_input.py +27 -18
  47. classiq/interface/finance/log_normal_model_input.py +2 -2
  48. classiq/interface/finance/model_input.py +3 -2
  49. classiq/interface/generator/amplitude_loading.py +8 -6
  50. classiq/interface/generator/arith/argument_utils.py +24 -0
  51. classiq/interface/generator/arith/arithmetic.py +5 -3
  52. classiq/interface/generator/arith/arithmetic_expression_abc.py +36 -14
  53. classiq/interface/generator/arith/arithmetic_operations.py +6 -3
  54. classiq/interface/generator/arith/binary_ops.py +88 -63
  55. classiq/interface/generator/arith/extremum_operations.py +22 -13
  56. classiq/interface/generator/arith/logical_ops.py +6 -4
  57. classiq/interface/generator/arith/number_utils.py +3 -3
  58. classiq/interface/generator/arith/register_user_input.py +32 -17
  59. classiq/interface/generator/arith/unary_ops.py +5 -4
  60. classiq/interface/generator/chemistry_function_params.py +2 -1
  61. classiq/interface/generator/circuit_code/circuit_code.py +2 -1
  62. classiq/interface/generator/commuting_pauli_exponentiation.py +6 -5
  63. classiq/interface/generator/complex_type.py +14 -18
  64. classiq/interface/generator/control_state.py +32 -26
  65. classiq/interface/generator/expressions/expression.py +6 -5
  66. classiq/interface/generator/function_params.py +22 -39
  67. classiq/interface/generator/functions/classical_function_declaration.py +1 -1
  68. classiq/interface/generator/functions/classical_type.py +32 -23
  69. classiq/interface/generator/functions/concrete_types.py +8 -7
  70. classiq/interface/generator/functions/function_declaration.py +4 -5
  71. classiq/interface/generator/functions/type_name.py +5 -4
  72. classiq/interface/generator/generated_circuit_data.py +9 -6
  73. classiq/interface/generator/grover_diffuser.py +26 -18
  74. classiq/interface/generator/grover_operator.py +32 -22
  75. classiq/interface/generator/hamiltonian_evolution/exponentiation.py +3 -4
  76. classiq/interface/generator/hamiltonian_evolution/qdrift.py +4 -4
  77. classiq/interface/generator/hamiltonian_evolution/suzuki_trotter.py +8 -7
  78. classiq/interface/generator/hardware/hardware_data.py +27 -26
  79. classiq/interface/generator/hardware_efficient_ansatz.py +11 -6
  80. classiq/interface/generator/hartree_fock.py +2 -1
  81. classiq/interface/generator/identity.py +7 -2
  82. classiq/interface/generator/linear_pauli_rotations.py +27 -14
  83. classiq/interface/generator/mcu.py +15 -12
  84. classiq/interface/generator/mcx.py +18 -10
  85. classiq/interface/generator/model/constraints.py +4 -2
  86. classiq/interface/generator/model/model.py +2 -1
  87. classiq/interface/generator/model/preferences/preferences.py +30 -32
  88. classiq/interface/generator/oracles/custom_oracle.py +13 -10
  89. classiq/interface/generator/piecewise_linear_amplitude_loading.py +37 -21
  90. classiq/interface/generator/qpe.py +38 -26
  91. classiq/interface/generator/qsvm.py +4 -4
  92. classiq/interface/generator/quantum_function_call.py +57 -44
  93. classiq/interface/generator/quantum_program.py +8 -6
  94. classiq/interface/generator/range_types.py +10 -11
  95. classiq/interface/generator/standard_gates/controlled_standard_gates.py +9 -5
  96. classiq/interface/generator/standard_gates/standard_angle_metaclass.py +2 -6
  97. classiq/interface/generator/standard_gates/u_gate.py +7 -10
  98. classiq/interface/generator/state_preparation/computational_basis_state_preparation.py +2 -1
  99. classiq/interface/generator/state_preparation/distributions.py +12 -12
  100. classiq/interface/generator/state_preparation/state_preparation.py +22 -16
  101. classiq/interface/generator/types/enum_declaration.py +2 -1
  102. classiq/interface/generator/ucc.py +2 -1
  103. classiq/interface/generator/unitary_gate.py +2 -1
  104. classiq/interface/generator/user_defined_function_params.py +3 -0
  105. classiq/interface/generator/visitor.py +1 -1
  106. classiq/interface/hardware.py +18 -3
  107. classiq/interface/helpers/custom_pydantic_types.py +38 -47
  108. classiq/interface/helpers/dotdict.py +18 -0
  109. classiq/interface/helpers/pydantic_model_helpers.py +3 -2
  110. classiq/interface/helpers/versioned_model.py +1 -4
  111. classiq/interface/ide/ide_data.py +5 -5
  112. classiq/interface/ide/visual_model.py +18 -5
  113. classiq/interface/interface_version.py +1 -1
  114. classiq/interface/jobs.py +12 -22
  115. classiq/interface/model/bind_operation.py +2 -1
  116. classiq/interface/model/classical_parameter_declaration.py +10 -4
  117. classiq/interface/model/handle_binding.py +20 -24
  118. classiq/interface/model/inplace_binary_operation.py +16 -9
  119. classiq/interface/model/model.py +21 -11
  120. classiq/interface/model/native_function_definition.py +10 -0
  121. classiq/interface/model/port_declaration.py +10 -7
  122. classiq/interface/model/quantum_expressions/arithmetic_operation.py +6 -4
  123. classiq/interface/model/quantum_function_declaration.py +22 -11
  124. classiq/interface/model/quantum_statement.py +6 -7
  125. classiq/interface/model/quantum_type.py +22 -19
  126. classiq/interface/model/statement_block.py +9 -9
  127. classiq/interface/server/global_versions.py +4 -5
  128. classiq/interface/server/routes.py +8 -0
  129. classiq/model_expansions/evaluators/parameter_types.py +3 -3
  130. classiq/model_expansions/expression_renamer.py +1 -1
  131. classiq/model_expansions/quantum_operations/control.py +11 -12
  132. classiq/model_expansions/quantum_operations/emitter.py +22 -0
  133. classiq/model_expansions/quantum_operations/expression_operation.py +2 -20
  134. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +42 -12
  135. classiq/model_expansions/quantum_operations/invert.py +1 -1
  136. classiq/model_expansions/quantum_operations/phase.py +4 -5
  137. classiq/model_expansions/quantum_operations/power.py +1 -1
  138. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +50 -9
  139. classiq/model_expansions/quantum_operations/variable_decleration.py +2 -2
  140. classiq/model_expansions/quantum_operations/within_apply.py +1 -1
  141. classiq/qmod/__init__.py +2 -0
  142. classiq/qmod/builtins/__init__.py +1 -3
  143. classiq/qmod/builtins/functions/__init__.py +9 -0
  144. classiq/qmod/builtins/functions/arithmetic.py +10 -0
  145. classiq/qmod/builtins/functions/standard_gates.py +14 -14
  146. classiq/qmod/builtins/functions/variational.py +37 -0
  147. classiq/qmod/create_model_function.py +16 -6
  148. classiq/qmod/qmod_parameter.py +3 -1
  149. classiq/qmod/quantum_expandable.py +43 -10
  150. classiq/qmod/quantum_function.py +24 -2
  151. classiq/qmod/semantics/static_semantics_visitor.py +3 -1
  152. classiq/qmod/synthesize_separately.py +16 -0
  153. classiq/qmod/type_attribute_remover.py +1 -1
  154. classiq/qmod/write_qmod.py +2 -4
  155. classiq/synthesis.py +11 -13
  156. {classiq-0.51.1.dist-info → classiq-0.53.0.dist-info}/METADATA +3 -2
  157. {classiq-0.51.1.dist-info → classiq-0.53.0.dist-info}/RECORD +158 -152
  158. {classiq-0.51.1.dist-info → classiq-0.53.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,84 @@
1
+ import math
2
+ from typing import Callable, Optional, Tuple, Type
3
+
4
+ import numpy as np
5
+ import scipy
6
+
7
+ from classiq.interface.executor.execution_preferences import ExecutionPreferences
8
+ from classiq.interface.executor.result import ExecutionDetails
9
+ from classiq.interface.model.model import SerializedModel
10
+
11
+ from classiq.execution import ExecutionSession
12
+ from classiq.qmod.builtins.functions import (
13
+ RX,
14
+ allocate,
15
+ apply_to_all,
16
+ hadamard_transform,
17
+ )
18
+ from classiq.qmod.builtins.operations import phase, repeat
19
+ from classiq.qmod.cparam import CReal
20
+ from classiq.qmod.create_model_function import create_model
21
+ from classiq.qmod.qfunc import qfunc
22
+ from classiq.qmod.qmod_parameter import CArray
23
+ from classiq.qmod.qmod_variable import Output, QVar
24
+ from classiq.synthesis import SerializedQuantumProgram, synthesize
25
+
26
+
27
+ def execute_qaoa(
28
+ problem_vars: Type[QVar],
29
+ cost_func: Callable,
30
+ num_layers: int,
31
+ maxiter: int,
32
+ execution_preferences: Optional[ExecutionPreferences] = None,
33
+ ) -> Tuple[SerializedModel, SerializedQuantumProgram, ExecutionDetails]:
34
+ """
35
+ Implements a simple QAOA algorithm, including the creation and synthesis of the QAOA
36
+ ansatz and the classical optimization loop.
37
+
38
+ Args:
39
+ problem_vars: the quantum type (scalar, array, or struct) of the problem variable(s)
40
+ cost_func: the arithmetic expression that evaluates the cost given an instance of the problem_vars type
41
+ num_layers: the number of layers of the QAOA ansatz
42
+ maxiter: the maximum number of iterations for the classical optimization loop
43
+ execution_preferences: the execution settings for running the QAOA ansatz
44
+
45
+ Returns:
46
+ a tuple containing the model of the QAOA ansatz, the corresponding synthesized quantum program,
47
+ and the result of the execution with the optimized parameters
48
+ """
49
+
50
+ @qfunc
51
+ def main(
52
+ params: CArray[CReal, num_layers * 2], # type:ignore[valid-type]
53
+ v: Output[problem_vars], # type:ignore[valid-type]
54
+ ) -> None:
55
+ allocate(v.size, v) # type:ignore[attr-defined]
56
+ hadamard_transform(v)
57
+ repeat(
58
+ num_layers,
59
+ lambda i: [ # type:ignore[arg-type]
60
+ phase(-cost_func(v), params[i]), # type:ignore[func-returns-value]
61
+ apply_to_all(lambda q: RX(params[num_layers + i], q), v),
62
+ ],
63
+ )
64
+
65
+ model = create_model(main)
66
+ qprog = synthesize(model)
67
+
68
+ es = ExecutionSession(qprog, execution_preferences)
69
+ initial_params = (
70
+ np.concatenate((np.linspace(0, 1, num_layers), np.linspace(1, 0, num_layers)))
71
+ * math.pi
72
+ )
73
+ final_params = scipy.optimize.minimize(
74
+ lambda params: es.estimate_cost(
75
+ lambda state: cost_func(state["v"]),
76
+ {"params": params.tolist()},
77
+ ),
78
+ x0=initial_params,
79
+ method="COBYLA",
80
+ options={"maxiter": maxiter},
81
+ ).x.tolist()
82
+ result = es.sample({"params": final_params})
83
+
84
+ return model, qprog, result
classiq/executor.py CHANGED
@@ -27,7 +27,7 @@ BackendPreferencesAndResult: TypeAlias = Tuple[
27
27
  def _parse_serialized_qprog(
28
28
  quantum_program: SerializedQuantumProgram,
29
29
  ) -> QuantumProgram:
30
- return QuantumProgram.parse_raw(quantum_program)
30
+ return QuantumProgram.model_validate_json(quantum_program)
31
31
 
32
32
 
33
33
  async def execute_async(quantum_program: SerializedQuantumProgram) -> ExecutionJob:
@@ -46,7 +46,7 @@ def execute(quantum_program: SerializedQuantumProgram) -> ExecutionJob:
46
46
  Returns:
47
47
  ExecutionJob: The result of the execution.
48
48
 
49
- For examples please see [Execution Documentation](https://docs.classiq.io/latest/reference-manual/executor/)
49
+ For examples please see [Execution Documentation](https://docs.classiq.io/latest/user-guide/executor/)
50
50
  """
51
51
  return async_utils.run(execute_async(quantum_program))
52
52
 
@@ -57,7 +57,7 @@ def set_quantum_program_execution_preferences(
57
57
  ) -> SerializedQuantumProgram:
58
58
  circuit = _parse_serialized_qprog(quantum_program)
59
59
  circuit.model.execution_preferences = preferences
60
- return SerializedQuantumProgram(circuit.json())
60
+ return SerializedQuantumProgram(circuit.model_dump_json())
61
61
 
62
62
 
63
63
  __all__ = [
@@ -3,5 +3,5 @@ from packaging.version import Version
3
3
  # This file was generated automatically
4
4
  # Please don't track in version control (DONTTRACK)
5
5
 
6
- SEMVER_VERSION = '0.51.1'
6
+ SEMVER_VERSION = '0.53.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -2,7 +2,7 @@ from typing import Dict, List, Optional
2
2
 
3
3
  import numpy
4
4
  import pydantic
5
- from pydantic import Field, conint, constr
5
+ from pydantic import ConfigDict, Field, StringConstraints
6
6
  from typing_extensions import Annotated
7
7
 
8
8
  from classiq.interface.backend.quantum_backend_providers import AnalyzerProviderVendor
@@ -36,7 +36,8 @@ class HardwareListParams(pydantic.BaseModel):
36
36
  providers: List[Provider]
37
37
  from_ide: bool = Field(default=False)
38
38
 
39
- @pydantic.validator("providers", always=True)
39
+ @pydantic.field_validator("providers")
40
+ @classmethod
40
41
  def set_default_providers(
41
42
  cls, providers: Optional[List[AnalyzerProviderVendor]]
42
43
  ) -> List[AnalyzerProviderVendor]:
@@ -55,8 +56,12 @@ class AnalysisOptionalDevicesParams(HardwareListParams):
55
56
 
56
57
 
57
58
  class GateNamsMapping(pydantic.BaseModel):
58
- qasm_name: Annotated[str, constr(max_length=MAX_NAME_LENGTH)]
59
- display_name: Annotated[str, constr(max_length=MAX_NAME_LENGTH)]
59
+ qasm_name: Annotated[
60
+ str, Annotated[str, StringConstraints(max_length=MAX_NAME_LENGTH)]
61
+ ]
62
+ display_name: Annotated[
63
+ str, Annotated[str, StringConstraints(max_length=MAX_NAME_LENGTH)]
64
+ ]
60
65
 
61
66
 
62
67
  class LatexParams(AnalysisParams):
@@ -66,7 +71,7 @@ class LatexParams(AnalysisParams):
66
71
 
67
72
 
68
73
  class AnalysisHardwareTranspilationParams(pydantic.BaseModel):
69
- hardware_data: Optional[SynthesisHardwareData]
74
+ hardware_data: Optional[SynthesisHardwareData] = None
70
75
  random_seed: int
71
76
  transpilation_option: TranspilationOption
72
77
 
@@ -91,13 +96,18 @@ class CircuitAnalysisHardwareParams(AnalysisParams):
91
96
 
92
97
  class AnalysisRBParams(pydantic.BaseModel):
93
98
  hardware: str
94
- counts: List[Dict[str, Annotated[int, conint(strict=True, gt=0, le=MAX_COUNTS)]]]
95
- num_clifford: List[Annotated[int, conint(strict=True, gt=0, le=MAX_NUM_CLIFFORD)]]
99
+ counts: List[
100
+ Dict[
101
+ str, Annotated[int, Annotated[int, Field(strict=True, gt=0, le=MAX_COUNTS)]]
102
+ ]
103
+ ]
104
+ num_clifford: List[
105
+ Annotated[int, Annotated[int, Field(strict=True, gt=0, le=MAX_NUM_CLIFFORD)]]
106
+ ]
96
107
 
97
108
 
98
109
  class ChemistryGenerationParams(pydantic.BaseModel):
99
- class Config:
100
- title = "Chemistry"
110
+ model_config = ConfigDict(title="Chemistry")
101
111
 
102
112
  molecule: MoleculeProblem = pydantic.Field(
103
113
  title="Molecule",
@@ -17,16 +17,23 @@ class CytoScapePosition(pydantic.BaseModel):
17
17
 
18
18
  class CytoScapeEdgeData(pydantic.BaseModel):
19
19
  source: str = pydantic.Field(
20
- default=..., description="the Id of the Node that is the Source of the edge"
20
+ default=" ", description="the Id of the Node that is the Source of the edge"
21
21
  )
22
22
  target: str = pydantic.Field(
23
- default=..., description="the Id of the Node that is the Target the edge"
23
+ default=" ", description="the Id of the Node that is the Target the edge"
24
24
  )
25
25
 
26
+ @pydantic.model_validator(mode="before")
27
+ @classmethod
28
+ def _validate_values(cls, values: Dict[str, Any]) -> Dict[str, Any]:
29
+ values["source"] = str(values["source"]) or " "
30
+ values["target"] = str(values["target"]) or " "
31
+ return values
32
+
26
33
 
27
34
  class CytoScapeEdge(pydantic.BaseModel):
28
35
  data: CytoScapeEdgeData = pydantic.Field(
29
- default=..., description="Edge's Data, mainly the source and target of the Edge"
36
+ description="Edge's Data, mainly the source and target of the Edge"
30
37
  )
31
38
 
32
39
 
@@ -3,7 +3,7 @@ from uuid import UUID
3
3
 
4
4
  import pydantic
5
5
  from pydantic import Field
6
- from typing_extensions import Annotated
6
+ from typing_extensions import Annotated, Self
7
7
 
8
8
  from classiq.interface.analyzer.analysis_params import MAX_FILE_LENGTH
9
9
  from classiq.interface.enum_utils import StrEnum
@@ -74,12 +74,13 @@ class HardwareComparisonInformation(pydantic.BaseModel):
74
74
  default=..., description="Number of total gates."
75
75
  )
76
76
 
77
- @pydantic.root_validator
78
- def validate_equal_length(cls, values: Dict[str, list]) -> Dict[str, list]:
77
+ @pydantic.model_validator(mode="after")
78
+ def validate_equal_length(self) -> Self:
79
+ values = self.model_dump()
79
80
  lengths = list(map(len, values.values()))
80
81
  if len(set(lengths)) != 1:
81
82
  raise ClassiqValueError("All lists should have the same length")
82
- return values
83
+ return self
83
84
 
84
85
 
85
86
  # TODO: copy the types for `devices` & `providers` from `HardwareComparisonInformation`
@@ -112,7 +113,7 @@ HardwareComparisonGraphType = Annotated[
112
113
  ]
113
114
 
114
115
  _HARDWARE_COMPARISON_TABLE_COLUMNS_NAMES: Dict[str, str] = {
115
- s.upper(): s.capitalize() for s in SingleHardwareInformation.__fields__
116
+ s.upper(): s.capitalize() for s in SingleHardwareInformation.model_fields
116
117
  }
117
118
 
118
119
 
@@ -1,20 +1,18 @@
1
1
  from collections.abc import Sequence
2
2
  from typing import (
3
- TYPE_CHECKING,
4
3
  Any,
5
4
  Iterable as IterableType,
6
5
  List,
7
6
  Optional,
8
7
  Tuple,
8
+ Type,
9
9
  Union,
10
10
  )
11
11
 
12
12
  import numpy as np
13
13
  import pydantic
14
14
  from numpy.typing import ArrayLike
15
-
16
- if TYPE_CHECKING:
17
- from pydantic.typing import AnyClassMethod
15
+ from pydantic import ConfigDict, field_validator
18
16
 
19
17
  from classiq.interface.helpers.versioned_model import VersionedModel
20
18
 
@@ -33,8 +31,12 @@ def listify(obj: Union[IterableType, ArrayLike]) -> list:
33
31
  return list(obj) # type: ignore[arg-type]
34
32
 
35
33
 
36
- def validate_array_to_list(name: str) -> "AnyClassMethod":
37
- return pydantic.validator(name, pre=True, allow_reuse=True)(listify)
34
+ def validate_array_to_list(name: str) -> Any:
35
+ @field_validator(name, mode="before")
36
+ def _listify(cls: Type[pydantic.BaseModel], value: Any) -> Any:
37
+ return listify(value)
38
+
39
+ return _listify
38
40
 
39
41
 
40
42
  Shape = Tuple[int, ...]
@@ -89,16 +91,15 @@ class QSVMData(VersionedModel):
89
91
  data: DataList
90
92
  labels: Optional[LabelsInt] = None
91
93
  internal_state: Optional[QSVMInternalState] = None
94
+ model_config = ConfigDict(extra="forbid")
92
95
 
93
- class Config:
94
- smart_union = True
95
- extra = "forbid"
96
-
97
- @pydantic.validator("data", pre=True)
96
+ @pydantic.field_validator("data", mode="before")
97
+ @classmethod
98
98
  def set_data(cls, data: Union[IterableType, ArrayLike]) -> list:
99
99
  return listify(data)
100
100
 
101
- @pydantic.validator("labels", pre=True)
101
+ @pydantic.field_validator("labels", mode="before")
102
+ @classmethod
102
103
  def set_labels(
103
104
  cls, labels: Optional[Union[IterableType, ArrayLike]]
104
105
  ) -> Optional[list]:
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  from typing import Any, Dict, Iterable, List, Optional, Union
4
4
 
5
5
  import pydantic
6
- from pydantic import BaseModel, validator
6
+ from pydantic import BaseModel
7
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
8
 
8
9
  from classiq.interface.backend import pydantic_backend
9
10
  from classiq.interface.backend.quantum_backend_providers import (
@@ -21,7 +22,6 @@ from classiq.interface.backend.quantum_backend_providers import (
21
22
  )
22
23
  from classiq.interface.exceptions import ClassiqValueError
23
24
  from classiq.interface.hardware import Provider
24
- from classiq.interface.helpers.pydantic_model_helpers import values_with_discriminator
25
25
 
26
26
 
27
27
  class BackendPreferences(BaseModel):
@@ -36,10 +36,7 @@ class BackendPreferences(BaseModel):
36
36
  backend_name (str): Name of the requested backend or target.
37
37
  """
38
38
 
39
- # Due to the way the field is currently implemented, i.e. it redefined with different types
40
- # in the subclass, it shouldn't be dumped with exclude_unset. This causes this field not to appear.
41
- # For example: don't use obj.dict(exclude_unset=True).
42
- backend_service_provider: str = pydantic.Field(
39
+ backend_service_provider: ProviderVendor = pydantic.Field(
43
40
  ..., description="Provider company or cloud for the requested backend."
44
41
  )
45
42
  backend_name: str = pydantic.Field(
@@ -50,12 +47,6 @@ class BackendPreferences(BaseModel):
50
47
  def hw_provider(self) -> Provider:
51
48
  return Provider(self.backend_service_provider)
52
49
 
53
- @pydantic.validator("backend_service_provider", pre=True)
54
- def validate_backend_service_provider(
55
- cls, backend_service_provider: Any
56
- ) -> Provider:
57
- return validate_backend_service_provider(backend_service_provider)
58
-
59
50
  @classmethod
60
51
  def batch_preferences(
61
52
  cls, *, backend_names: Iterable[str], **kwargs: Any
@@ -110,7 +101,9 @@ class AliceBobBackendPreferences(BackendPreferences):
110
101
  For more details, refer to the [Alice&Bob Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/alice-and-bob-backends/).
111
102
  """
112
103
 
113
- backend_service_provider: ProviderTypeVendor.ALICE_BOB
104
+ backend_service_provider: ProviderTypeVendor.ALICE_BOB = pydantic.Field(
105
+ default=ProviderVendor.ALICE_AND_BOB
106
+ )
114
107
  distance: Optional[int] = pydantic.Field(
115
108
  default=None, description="Repetition code distance"
116
109
  )
@@ -127,12 +120,6 @@ class AliceBobBackendPreferences(BackendPreferences):
127
120
  ..., description="AliceBob API key"
128
121
  )
129
122
 
130
- @pydantic.root_validator(pre=True)
131
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
132
- return values_with_discriminator(
133
- values, "backend_service_provider", ProviderVendor.ALICE_AND_BOB
134
- )
135
-
136
123
  @property
137
124
  def parameters(self) -> Dict[str, Any]:
138
125
  parameters = {
@@ -154,13 +141,9 @@ class ClassiqBackendPreferences(BackendPreferences):
154
141
  For more details, refer to the [Classiq Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/classiq-backends/).
155
142
  """
156
143
 
157
- backend_service_provider: ProviderTypeVendor.CLASSIQ
158
-
159
- @pydantic.root_validator(pre=True)
160
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
161
- return values_with_discriminator(
162
- values, "backend_service_provider", ProviderVendor.CLASSIQ
163
- )
144
+ backend_service_provider: ProviderTypeVendor.CLASSIQ = pydantic.Field(
145
+ default=ProviderVendor.CLASSIQ
146
+ )
164
147
 
165
148
  def is_nvidia_backend(self) -> bool:
166
149
  return self.backend_name in list(ClassiqNvidiaBackendNames)
@@ -197,7 +180,9 @@ class AwsBackendPreferences(BackendPreferences):
197
180
  [AwsBackendPreferences examples](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/amazon-backends/?h=awsbackend#usage)
198
181
  """
199
182
 
200
- backend_service_provider: ProviderTypeVendor.AMAZON_BRAKET
183
+ backend_service_provider: ProviderTypeVendor.AMAZON_BRAKET = pydantic.Field(
184
+ default=ProviderVendor.AMAZON_BRAKET
185
+ )
201
186
  aws_role_arn: pydantic_backend.PydanticAwsRoleArn = pydantic.Field(
202
187
  description="ARN of the role to be assumed for execution on your Braket account."
203
188
  )
@@ -206,21 +191,14 @@ class AwsBackendPreferences(BackendPreferences):
206
191
  description="S3 Folder Path Within The S3 Bucket"
207
192
  )
208
193
 
209
- @validator("s3_bucket_name")
210
- def _validate_s3_bucket_name(
211
- cls, s3_bucket_name: str, values: Dict[str, Any]
212
- ) -> str:
194
+ @pydantic.field_validator("s3_bucket_name", mode="before")
195
+ @classmethod
196
+ def _validate_s3_bucket_name(cls, s3_bucket_name: str) -> str:
213
197
  s3_bucket_name = s3_bucket_name.strip()
214
198
  if not s3_bucket_name.startswith("amazon-braket-"):
215
199
  raise ClassiqValueError('S3 bucket name should start with "amazon-braket-"')
216
200
  return s3_bucket_name
217
201
 
218
- @pydantic.root_validator(pre=True)
219
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
220
- return values_with_discriminator(
221
- values, "backend_service_provider", ProviderVendor.AMAZON_BRAKET
222
- )
223
-
224
202
 
225
203
  class IBMBackendProvider(BaseModel):
226
204
  """
@@ -255,7 +233,9 @@ class IBMBackendPreferences(BackendPreferences):
255
233
  See examples in the [IBM Quantum Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/ibm-backends/?h=).
256
234
  """
257
235
 
258
- backend_service_provider: ProviderTypeVendor.IBM_QUANTUM
236
+ backend_service_provider: ProviderTypeVendor.IBM_QUANTUM = pydantic.Field(
237
+ default=ProviderVendor.IBM_QUANTUM
238
+ )
259
239
  access_token: Optional[str] = pydantic.Field(
260
240
  default=None,
261
241
  description="IBM Quantum access token to be used"
@@ -270,14 +250,8 @@ class IBMBackendPreferences(BackendPreferences):
270
250
  description="QCTRL API key to access QCTRL optimization abilities",
271
251
  )
272
252
 
273
- @pydantic.root_validator(pre=True)
274
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
275
- return values_with_discriminator(
276
- values, "backend_service_provider", ProviderVendor.IBM_QUANTUM
277
- )
278
253
 
279
-
280
- class AzureCredential(pydantic.BaseSettings):
254
+ class AzureCredential(BaseSettings):
281
255
  """
282
256
  Represents the credentials and configuration required to authenticate with Azure services.
283
257
 
@@ -296,11 +270,20 @@ class AzureCredential(pydantic.BaseSettings):
296
270
  description="Azure Resource ID (including Azure subscription ID, resource "
297
271
  "group and workspace), for personal resource",
298
272
  )
273
+ model_config = SettingsConfigDict(
274
+ title="Azure Service Principal Credential",
275
+ env_prefix="AZURE_",
276
+ case_sensitive=False,
277
+ extra="allow",
278
+ )
299
279
 
300
- class Config:
301
- title = "Azure Service Principal Credential"
302
- env_prefix = "AZURE_"
303
- case_sensitive = False
280
+ def __init__(self, **data: Any) -> None:
281
+ initial_data = {
282
+ field: data[field] for field in data if field in self.model_fields
283
+ }
284
+ super().__init__(**data)
285
+ for field, value in initial_data.items():
286
+ setattr(self, field, value)
304
287
 
305
288
 
306
289
  class AzureBackendPreferences(BackendPreferences):
@@ -317,7 +300,9 @@ class AzureBackendPreferences(BackendPreferences):
317
300
 
318
301
  """
319
302
 
320
- backend_service_provider: ProviderTypeVendor.AZURE_QUANTUM
303
+ backend_service_provider: ProviderTypeVendor.AZURE_QUANTUM = pydantic.Field(
304
+ default=ProviderVendor.AZURE_QUANTUM
305
+ )
321
306
 
322
307
  location: str = pydantic.Field(
323
308
  default="East US", description="Azure personal resource region"
@@ -343,12 +328,6 @@ class AzureBackendPreferences(BackendPreferences):
343
328
  """
344
329
  return self.credentials is None
345
330
 
346
- @pydantic.root_validator(pre=True)
347
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
348
- return values_with_discriminator(
349
- values, "backend_service_provider", ProviderVendor.AZURE_QUANTUM
350
- )
351
-
352
331
 
353
332
  class IonqBackendPreferences(BackendPreferences):
354
333
  """
@@ -365,7 +344,9 @@ class IonqBackendPreferences(BackendPreferences):
365
344
  See examples in the [IonQ Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/ionq-backends/?h=).
366
345
  """
367
346
 
368
- backend_service_provider: ProviderTypeVendor.IONQ
347
+ backend_service_provider: ProviderTypeVendor.IONQ = pydantic.Field(
348
+ default=ProviderVendor.IONQ
349
+ )
369
350
  api_key: pydantic_backend.PydanticIonQApiKeyType = pydantic.Field(
370
351
  ..., description="IonQ API key"
371
352
  )
@@ -374,12 +355,6 @@ class IonqBackendPreferences(BackendPreferences):
374
355
  description="Error mitigation configuration.",
375
356
  )
376
357
 
377
- @pydantic.root_validator(pre=True)
378
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
379
- return values_with_discriminator(
380
- values, "backend_service_provider", ProviderVendor.IONQ
381
- )
382
-
383
358
 
384
359
  class GCPBackendPreferences(BackendPreferences):
385
360
  """
@@ -394,13 +369,9 @@ class GCPBackendPreferences(BackendPreferences):
394
369
  See examples in the [Google Cloud Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/google-backends/?h=).
395
370
  """
396
371
 
397
- backend_service_provider: ProviderTypeVendor.GOOGLE
398
-
399
- @pydantic.root_validator(pre=True)
400
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
401
- return values_with_discriminator(
402
- values, "backend_service_provider", ProviderVendor.GOOGLE
403
- )
372
+ backend_service_provider: ProviderTypeVendor.GOOGLE = pydantic.Field(
373
+ default=ProviderVendor.GOOGLE
374
+ )
404
375
 
405
376
  def is_nvidia_backend(self) -> bool:
406
377
  return True
@@ -417,16 +388,12 @@ class OQCBackendPreferences(BackendPreferences):
417
388
  password (str): OQC password
418
389
  """
419
390
 
420
- backend_service_provider: ProviderTypeVendor.OQC
391
+ backend_service_provider: ProviderTypeVendor.OQC = pydantic.Field(
392
+ default=ProviderVendor.OQC
393
+ )
421
394
  username: str = pydantic.Field(description="OQC username")
422
395
  password: str = pydantic.Field(description="OQC password")
423
396
 
424
- @pydantic.root_validator(pre=True)
425
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
426
- return values_with_discriminator(
427
- values, "backend_service_provider", ProviderVendor.OQC
428
- )
429
-
430
397
 
431
398
  class IntelBackendPreferences(BackendPreferences):
432
399
  """
@@ -438,13 +405,9 @@ class IntelBackendPreferences(BackendPreferences):
438
405
  For more details, refer to the [Classiq Backend Documentation](https://docs.classiq.io/latest/reference-manual/executor/cloud-providers/classiq-backends/).
439
406
  """
440
407
 
441
- backend_service_provider: ProviderTypeVendor.INTEL
442
-
443
- @pydantic.root_validator(pre=True)
444
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
445
- return values_with_discriminator(
446
- values, "backend_service_provider", ProviderVendor.INTEL
447
- )
408
+ backend_service_provider: ProviderTypeVendor.INTEL = pydantic.Field(
409
+ default=ProviderVendor.INTEL
410
+ )
448
411
 
449
412
 
450
413
  class AQTBackendPreferences(BackendPreferences):
@@ -458,17 +421,38 @@ class AQTBackendPreferences(BackendPreferences):
458
421
  workspace: The AQT workspace where the simulator/hardware is located.
459
422
  """
460
423
 
461
- # TODO[pydantic]: Add a default value
462
- backend_service_provider: ProviderTypeVendor.AQT
424
+ backend_service_provider: ProviderTypeVendor.AQT = pydantic.Field(
425
+ default=ProviderVendor.AQT
426
+ )
463
427
  api_key: str = pydantic.Field(description="AQT API key")
464
428
  workspace: str = pydantic.Field(description="AQT workspace")
465
429
 
466
- # TODO[pydantic]: Remove this validator
467
- @pydantic.root_validator(pre=True)
468
- def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
469
- return values_with_discriminator(
470
- values, "backend_service_provider", ProviderVendor.AQT
471
- )
430
+
431
+ class IQCCBackendPreferences(BackendPreferences):
432
+ """
433
+ NOTE: This is a work in progress and is subject to change.
434
+
435
+ Represents the backend preferences specific to IQCC (Israeli Quantum Computing
436
+ Center).
437
+
438
+ Attributes:
439
+ auth_token: The authorization token generated by calling `generate_iqcc_token`.
440
+ target_id: The target ID of the login node.
441
+ target_scope_id: The scope ID of the specified target.
442
+ ssh_user_name: The user name to use when connecting to the SSH server on the login node.
443
+ ssh_key: The private key to use when connecting to the SSH server on the login node.
444
+ slurm_account: The account to use when initiating SLURM jobs.
445
+ """
446
+
447
+ backend_service_provider: ProviderTypeVendor.IQCC = pydantic.Field(
448
+ default=ProviderVendor.IQCC
449
+ )
450
+ auth_token: str
451
+ target_id: str
452
+ target_scope_id: str
453
+ ssh_user_name: str
454
+ ssh_key: str
455
+ slurm_account: str
472
456
 
473
457
 
474
458
  def is_exact_simulator(backend_preferences: BackendPreferences) -> bool:
@@ -502,6 +486,7 @@ BackendPreferencesTypes = Union[
502
486
  OQCBackendPreferences,
503
487
  IntelBackendPreferences,
504
488
  AQTBackendPreferences,
489
+ IQCCBackendPreferences,
505
490
  ]
506
491
 
507
492
  __all__ = [
@@ -525,17 +510,5 @@ __all__ = [
525
510
  "OQCBackendNames",
526
511
  "IntelBackendPreferences",
527
512
  "AQTBackendPreferences",
513
+ "IQCCBackendPreferences",
528
514
  ]
529
-
530
-
531
- def validate_backend_service_provider(backend_service_provider: Any) -> Provider:
532
- if isinstance(backend_service_provider, Provider):
533
- return backend_service_provider
534
- if isinstance(backend_service_provider, str):
535
- for member in Provider:
536
- if member.lower() == backend_service_provider.lower():
537
- return Provider(member)
538
- raise ClassiqValueError(
539
- f"""Vendor {backend_service_provider} is not supported.
540
- The supported providers are {', '.join(Provider)}."""
541
- )