classiq 0.102.0__py3-none-any.whl → 0.104.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 (65) hide show
  1. classiq/applications/chemistry/op_utils.py +32 -0
  2. classiq/evaluators/qmod_annotated_expression.py +1 -1
  3. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +1 -8
  4. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +1 -1
  5. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +2 -2
  6. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +18 -29
  7. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +1 -6
  8. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +1 -7
  9. classiq/evaluators/qmod_type_inference/quantum_type_comparison.py +52 -0
  10. classiq/execution/execution_session.py +1 -1
  11. classiq/execution/functions/__init__.py +3 -0
  12. classiq/execution/functions/_logging.py +19 -0
  13. classiq/execution/functions/constants.py +9 -0
  14. classiq/execution/functions/parse_provider_backend.py +90 -0
  15. classiq/execution/functions/sample.py +257 -0
  16. classiq/interface/_version.py +1 -1
  17. classiq/interface/backend/backend_preferences.py +15 -0
  18. classiq/interface/backend/provider_config/providers/aqt.py +1 -1
  19. classiq/interface/backend/provider_config/providers/azure.py +1 -2
  20. classiq/interface/backend/provider_config/providers/ibm.py +1 -1
  21. classiq/interface/backend/quantum_backend_providers.py +3 -0
  22. classiq/interface/executor/result.py +9 -5
  23. classiq/interface/generator/arith/binary_ops.py +38 -2
  24. classiq/interface/generator/function_param_list.py +4 -2
  25. classiq/interface/generator/functions/builtins/internal_operators.py +5 -9
  26. classiq/interface/generator/functions/classical_type.py +45 -0
  27. classiq/interface/generator/functions/type_name.py +23 -0
  28. classiq/interface/generator/generated_circuit_data.py +0 -2
  29. classiq/interface/generator/types/compilation_metadata.py +9 -0
  30. classiq/interface/hardware.py +1 -0
  31. classiq/interface/helpers/model_normalizer.py +42 -6
  32. classiq/interface/interface_version.py +1 -1
  33. classiq/interface/model/invert.py +8 -0
  34. classiq/interface/model/model_visitor.py +4 -2
  35. classiq/interface/model/quantum_type.py +21 -0
  36. classiq/interface/model/statement_block.py +0 -4
  37. classiq/model_expansions/capturing/captured_vars.py +16 -12
  38. classiq/model_expansions/function_builder.py +9 -1
  39. classiq/model_expansions/interpreters/base_interpreter.py +9 -8
  40. classiq/model_expansions/interpreters/generative_interpreter.py +9 -24
  41. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -0
  42. classiq/model_expansions/quantum_operations/assignment_result_processor.py +132 -28
  43. classiq/model_expansions/quantum_operations/bind.py +4 -0
  44. classiq/model_expansions/quantum_operations/call_emitter.py +5 -35
  45. classiq/model_expansions/quantum_operations/emitter.py +1 -4
  46. classiq/model_expansions/quantum_operations/expression_evaluator.py +0 -3
  47. classiq/model_expansions/visitors/uncomputation_signature_inference.py +0 -9
  48. classiq/qmod/builtins/functions/__init__.py +9 -0
  49. classiq/qmod/builtins/functions/arithmetic.py +131 -0
  50. classiq/qmod/builtins/functions/exponentiation.py +32 -2
  51. classiq/qmod/builtins/operations.py +2 -38
  52. classiq/qmod/native/pretty_printer.py +1 -12
  53. classiq/qmod/pretty_print/pretty_printer.py +1 -17
  54. classiq/qmod/qmod_parameter.py +4 -0
  55. classiq/qmod/qmod_variable.py +38 -63
  56. classiq/qmod/quantum_function.py +43 -7
  57. classiq/qmod/semantics/validation/function_name_collisions_validation.py +7 -4
  58. classiq/qmod/semantics/validation/model_validation.py +7 -2
  59. classiq/qmod/symbolic_type.py +4 -2
  60. {classiq-0.102.0.dist-info → classiq-0.104.0.dist-info}/METADATA +1 -1
  61. {classiq-0.102.0.dist-info → classiq-0.104.0.dist-info}/RECORD +63 -59
  62. classiq/interface/generator/amplitude_loading.py +0 -103
  63. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +0 -77
  64. {classiq-0.102.0.dist-info → classiq-0.104.0.dist-info}/WHEEL +0 -0
  65. {classiq-0.102.0.dist-info → classiq-0.104.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -74,6 +74,38 @@ def qubit_op_to_qmod(
74
74
  )
75
75
 
76
76
 
77
+ def qmod_to_qubit_op(operator: SparsePauliOp) -> QubitOperator:
78
+ """
79
+ Transforms Qmod's SparsePauliOp data structure to OpenFermion's QubitOperator data structure.
80
+
81
+ Args:
82
+ operator (SparsePauliOp): The operator to be transformed
83
+
84
+ Returns:
85
+ QubitOperator: The operator in OpenFermion's data structure
86
+ """
87
+
88
+ # Initiating the QubitOperator as the 0 operator
89
+ qo = QubitOperator()
90
+ for sparse_pauli_term in operator.terms:
91
+ # loop over all the IndexedPaulis
92
+ coeff = sparse_pauli_term.coefficient
93
+ if sparse_pauli_term.paulis:
94
+ qo.terms[
95
+ tuple(
96
+ [
97
+ (p.index, p.pauli.name)
98
+ for p in sparse_pauli_term.paulis # type: ignore[attr-defined]
99
+ if p.pauli is not Pauli.I
100
+ ]
101
+ )
102
+ ] = coeff
103
+ # Operator is the identity
104
+ else:
105
+ qo.terms[()] = coeff
106
+ return qo
107
+
108
+
77
109
  _PAULIS_TO_XZ = {"I": (0, 0), "X": (1, 0), "Z": (0, 1), "Y": (1, 1)}
78
110
  _XZ_TO_PAULIS = {(0, 0): "I", (1, 0): "X", (0, 1): "Z", (1, 1): "Y"}
79
111
 
@@ -207,7 +207,7 @@ class QmodAnnotatedExpression:
207
207
  node = id(node)
208
208
  return node in self._quantum_subscripts
209
209
 
210
- def get_quantum_subcript(
210
+ def get_quantum_subscript(
211
211
  self, node: ast.AST | QmodExprNodeId
212
212
  ) -> QuantumSubscriptAnnotation:
213
213
  if isinstance(node, ast.AST):
@@ -77,7 +77,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
77
77
  self,
78
78
  expr_val: QmodAnnotatedExpression,
79
79
  *,
80
- treat_qnum_as_float: bool = False,
81
80
  machine_precision: int = DEFAULT_MACHINE_PRECISION,
82
81
  classical_struct_declarations: Sequence[StructDeclaration] | None = None,
83
82
  enum_declarations: Sequence[EnumDeclaration] | None = None,
@@ -88,7 +87,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
88
87
  scope: Mapping[str, Any] | None = None,
89
88
  ) -> None:
90
89
  self._expr_val = expr_val
91
- self._treat_qnum_as_float = treat_qnum_as_float
92
90
  self._machine_precision = machine_precision
93
91
  self._classical_struct_decls = nameables_to_dict(
94
92
  classical_struct_declarations or []
@@ -114,9 +112,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
114
112
 
115
113
  def visit_BinOp(self, node: ast.BinOp) -> None:
116
114
  super().generic_visit(node)
117
- eval_binary_op(
118
- self._expr_val, node, self._treat_qnum_as_float, self._machine_precision
119
- )
115
+ eval_binary_op(self._expr_val, node, self._machine_precision)
120
116
 
121
117
  def visit_UnaryOp(self, node: ast.UnaryOp) -> None:
122
118
  super().generic_visit(node)
@@ -164,7 +160,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
164
160
  self._expr_val,
165
161
  node,
166
162
  func_name,
167
- self._treat_qnum_as_float,
168
163
  self._machine_precision,
169
164
  )
170
165
  return
@@ -256,7 +251,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
256
251
  def evaluate_qmod_expression(
257
252
  expr: str,
258
253
  *,
259
- treat_qnum_as_float: bool = False,
260
254
  machine_precision: int = DEFAULT_MACHINE_PRECISION,
261
255
  classical_struct_declarations: Sequence[StructDeclaration] | None = None,
262
256
  enum_declarations: Sequence[EnumDeclaration] | None = None,
@@ -270,7 +264,6 @@ def evaluate_qmod_expression(
270
264
  expr_value = QmodAnnotatedExpression(expr_ast)
271
265
  QmodExpressionEvaluator(
272
266
  expr_value,
273
- treat_qnum_as_float=treat_qnum_as_float,
274
267
  machine_precision=machine_precision,
275
268
  classical_struct_declarations=classical_struct_declarations,
276
269
  enum_declarations=enum_declarations,
@@ -122,7 +122,7 @@ class _InverseVarMaskTransformer(OutOfPlaceNodeTransformer):
122
122
 
123
123
  class _SympyCompatibilityTransformer(OutOfPlaceNodeTransformer):
124
124
  def visit_BoolOp(self, node: ast.BoolOp) -> ast.Call:
125
- if len(node.values) != 2:
125
+ if len(node.values) < 2:
126
126
  raise ClassiqInternalExpansionError
127
127
  node = cast(ast.BoolOp, self.generic_visit(node))
128
128
  if isinstance(node.op, ast.Or):
@@ -107,7 +107,7 @@ def _eval_type_attribute(
107
107
  if isinstance(subject_type, QuantumNumeric):
108
108
  if attr == "is_signed":
109
109
  expr_val.set_type(node, Bool())
110
- if subject_type.has_sign:
110
+ if subject_type.has_constant_sign:
111
111
  expr_val.set_value(node, subject_type.sign_value)
112
112
  _remove_quantum_var(expr_val, subject)
113
113
  elif subject_type.has_size_in_bits:
@@ -120,7 +120,7 @@ def _eval_type_attribute(
120
120
  return
121
121
  if attr == "fraction_digits":
122
122
  expr_val.set_type(node, Integer())
123
- if subject_type.has_fraction_digits:
123
+ if subject_type.has_constant_fraction_digits:
124
124
  expr_val.set_value(node, subject_type.fraction_digits_value)
125
125
  _remove_quantum_var(expr_val, subject)
126
126
  elif subject_type.has_size_in_bits:
@@ -50,7 +50,6 @@ def _validate_binary_op(
50
50
  op: ast.AST,
51
51
  left_type: QmodType,
52
52
  right_type: QmodType,
53
- treat_qnum_as_float: bool,
54
53
  ) -> None:
55
54
  if not _binary_op_allowed(left_type, right_type, op):
56
55
  raise ClassiqExpansionError(
@@ -76,20 +75,19 @@ def _validate_binary_op(
76
75
  f"Binary operation {type(op).__name__!r} is not supported"
77
76
  )
78
77
 
79
- if not treat_qnum_as_float:
80
- if isinstance(op, ast.FloorDiv) and (
81
- not is_classical_type(left_type) or not is_classical_type(right_type)
82
- ):
83
- raise ClassiqExpansionError(
84
- f"{type(op).__name__!r} with quantum variables is not supported"
85
- )
78
+ if isinstance(op, ast.FloorDiv) and (
79
+ not is_classical_type(left_type) or not is_classical_type(right_type)
80
+ ):
81
+ raise ClassiqExpansionError(
82
+ f"{type(op).__name__!r} with quantum variables is not supported"
83
+ )
86
84
 
87
- if not is_classical_type(right_type) and isinstance(
88
- op, (ast.Div, ast.Mod, ast.Pow, ast.LShift, ast.RShift)
89
- ):
90
- raise ClassiqExpansionError(
91
- f"Right-hand side of binary operation {type(op).__name__!r} must be classical numeric value"
92
- )
85
+ if not is_classical_type(right_type) and isinstance(
86
+ op, (ast.Div, ast.Mod, ast.Pow, ast.LShift, ast.RShift)
87
+ ):
88
+ raise ClassiqExpansionError(
89
+ f"Right-hand side of binary operation {type(op).__name__!r} must be classical numeric value"
90
+ )
93
91
 
94
92
 
95
93
  def _infer_binary_op_type(
@@ -98,7 +96,6 @@ def _infer_binary_op_type(
98
96
  left_type: QmodType,
99
97
  right_type: QmodType,
100
98
  machine_precision: int,
101
- treat_qnum_as_float: bool,
102
99
  ) -> QmodType:
103
100
  op = node.op
104
101
 
@@ -113,19 +110,13 @@ def _infer_binary_op_type(
113
110
  return Integer()
114
111
  return Real()
115
112
 
116
- left_attrs = get_numeric_attrs(
117
- expr_val, node.left, left_type, machine_precision, treat_qnum_as_float
118
- )
119
- right_attrs = get_numeric_attrs(
120
- expr_val, node.right, right_type, machine_precision, treat_qnum_as_float
121
- )
113
+ left_attrs = get_numeric_attrs(expr_val, node.left, left_type, machine_precision)
114
+ right_attrs = get_numeric_attrs(expr_val, node.right, right_type, machine_precision)
122
115
 
123
116
  if left_attrs is None or right_attrs is None:
124
117
  return QuantumNumeric()
125
118
 
126
- right_value = get_classical_value_for_arithmetic(
127
- expr_val, node.right, right_type, treat_qnum_as_float
128
- )
119
+ right_value = get_classical_value_for_arithmetic(expr_val, node.right, right_type)
129
120
 
130
121
  if isinstance(op, ast.Add):
131
122
  result_attrs = compute_result_attrs_add(
@@ -157,14 +148,14 @@ def _infer_binary_op_type(
157
148
  return QuantumNumeric()
158
149
 
159
150
  elif isinstance(op, ast.Mod):
160
- if right_value is None or treat_qnum_as_float:
151
+ if right_value is None:
161
152
  return QuantumNumeric()
162
153
  result_attrs = compute_result_attrs_modulo(
163
154
  left_attrs, right_attrs, machine_precision
164
155
  )
165
156
 
166
157
  elif isinstance(op, ast.Pow):
167
- if right_value is None or treat_qnum_as_float:
158
+ if right_value is None:
168
159
  return QuantumNumeric()
169
160
  result_attrs = compute_result_attrs_power(
170
161
  left_attrs, right_attrs, machine_precision
@@ -252,7 +243,6 @@ def _eval_binary_op_constant(
252
243
  def eval_binary_op(
253
244
  expr_val: QmodAnnotatedExpression,
254
245
  node: ast.BinOp,
255
- treat_qnum_as_float: bool,
256
246
  machine_precision: int,
257
247
  ) -> None:
258
248
  left = node.left
@@ -261,7 +251,7 @@ def eval_binary_op(
261
251
 
262
252
  left_type = expr_val.get_type(left)
263
253
  right_type = expr_val.get_type(right)
264
- _validate_binary_op(op, left_type, right_type, treat_qnum_as_float)
254
+ _validate_binary_op(op, left_type, right_type)
265
255
 
266
256
  inferred_type = _infer_binary_op_type(
267
257
  expr_val,
@@ -269,7 +259,6 @@ def eval_binary_op(
269
259
  left_type,
270
260
  right_type,
271
261
  machine_precision,
272
- treat_qnum_as_float,
273
262
  )
274
263
  expr_val.set_type(node, inferred_type)
275
264
 
@@ -31,7 +31,6 @@ def _infer_min_max_op_type(
31
31
  node: ast.Call,
32
32
  func_name: str,
33
33
  args_types: list[QmodType],
34
- treat_qnum_as_float: bool,
35
34
  machine_precision: int,
36
35
  ) -> QmodType:
37
36
  if all(is_classical_type(arg_type) for arg_type in args_types):
@@ -41,9 +40,7 @@ def _infer_min_max_op_type(
41
40
 
42
41
  args_attrs: list[NumericAttributes] = []
43
42
  for arg, arg_type in zip(node.args, args_types):
44
- attrs = get_numeric_attrs(
45
- expr_val, arg, arg_type, machine_precision, treat_qnum_as_float
46
- )
43
+ attrs = get_numeric_attrs(expr_val, arg, arg_type, machine_precision)
47
44
  if attrs is None:
48
45
  return QuantumNumeric()
49
46
  args_attrs.append(attrs)
@@ -62,7 +59,6 @@ def eval_min_max_op(
62
59
  expr_val: QmodAnnotatedExpression,
63
60
  node: ast.Call,
64
61
  func_name: str,
65
- treat_qnum_as_float: bool,
66
62
  machine_precision: int,
67
63
  ) -> None:
68
64
  if len(node.args) < 1:
@@ -79,7 +75,6 @@ def eval_min_max_op(
79
75
  node,
80
76
  func_name,
81
77
  args_types,
82
- treat_qnum_as_float,
83
78
  machine_precision,
84
79
  )
85
80
  expr_val.set_type(node, inferred_type)
@@ -21,14 +21,11 @@ def get_numeric_attrs(
21
21
  node: ast.AST,
22
22
  qmod_type: QmodType,
23
23
  machine_precision: int,
24
- treat_qnum_as_float: bool,
25
24
  ) -> NumericAttributes | None:
26
25
  if isinstance(qmod_type, Bool):
27
26
  return NumericAttributes.from_bounds(0, 1, 0, machine_precision)
28
27
  if is_classical_type(qmod_type):
29
- value = get_classical_value_for_arithmetic(
30
- expr_val, node, qmod_type, treat_qnum_as_float
31
- )
28
+ value = get_classical_value_for_arithmetic(expr_val, node, qmod_type)
32
29
  if value is None:
33
30
  return None
34
31
  return NumericAttributes.from_constant(value, machine_precision)
@@ -44,7 +41,6 @@ def get_classical_value_for_arithmetic(
44
41
  expr_val: QmodAnnotatedExpression,
45
42
  node: ast.AST,
46
43
  qmod_type: QmodType,
47
- treat_qnum_as_float: bool,
48
44
  ) -> float | None:
49
45
  if not is_classical_type(qmod_type):
50
46
  return None
@@ -55,8 +51,6 @@ def get_classical_value_for_arithmetic(
55
51
  if isinstance(value, sympy.Basic):
56
52
  value = get_sympy_val(value)
57
53
  if not isinstance(value, (int, float)):
58
- if treat_qnum_as_float and isinstance(value, complex):
59
- return None
60
54
  raise ClassiqExpansionError(
61
55
  "Arithmetic of quantum variables and non-real values is not supported"
62
56
  )
@@ -0,0 +1,52 @@
1
+ from classiq.interface.exceptions import (
2
+ ClassiqInternalExpansionError,
3
+ )
4
+ from classiq.interface.generator.expressions.expression import Expression
5
+ from classiq.interface.generator.functions.type_name import TypeName
6
+ from classiq.interface.model.quantum_type import (
7
+ QuantumBit,
8
+ QuantumBitvector,
9
+ QuantumNumeric,
10
+ QuantumType,
11
+ )
12
+
13
+
14
+ def _compare_expressions(expr1: Expression | None, expr2: Expression | None) -> bool:
15
+ if expr1 is None:
16
+ return expr2 is None
17
+ if expr2 is None:
18
+ return False
19
+ return expr1.expr == expr2.expr
20
+
21
+
22
+ def compare_quantum_types(type_1: QuantumType, type_2: QuantumType) -> bool:
23
+ for qmod_type in (type_1, type_2):
24
+ if isinstance(qmod_type, TypeName) and not qmod_type.has_fields:
25
+ raise ClassiqInternalExpansionError("Quantum struct expected")
26
+ if isinstance(type_1, QuantumBit):
27
+ return isinstance(type_2, QuantumBit)
28
+ if isinstance(type_1, QuantumNumeric):
29
+ return (
30
+ isinstance(type_2, QuantumNumeric)
31
+ and _compare_expressions(type_1.size, type_2.size)
32
+ and _compare_expressions(type_1.is_signed, type_2.is_signed)
33
+ and _compare_expressions(type_1.fraction_digits, type_2.fraction_digits)
34
+ )
35
+ if isinstance(type_1, QuantumBitvector):
36
+ return (
37
+ isinstance(type_2, QuantumBitvector)
38
+ and _compare_expressions(type_1.length, type_2.length)
39
+ and compare_quantum_types(type_1.element_type, type_2.element_type)
40
+ )
41
+ if isinstance(type_1, TypeName):
42
+ return (
43
+ isinstance(type_2, TypeName)
44
+ and type_1.name == type_2.name
45
+ and all(
46
+ compare_quantum_types(field_type_1, field_type_2)
47
+ for field_type_1, field_type_2 in zip(
48
+ type_1.fields.values(), type_2.fields.values(), strict=True
49
+ )
50
+ )
51
+ )
52
+ raise ClassiqInternalExpansionError(f"Unexpected type {type(type_1).__name__}")
@@ -365,7 +365,7 @@ class ExecutionSession:
365
365
  A list of tuples, each containing the estimated cost and the corresponding parameters for that iteration. `cost` is a float, and `parameters` is a dictionary matching the execution parameter format.
366
366
 
367
367
  See Also:
368
- The [Classiq Tutorial](https://docs.classiq.io/latest/getting-started/classiq_tutorial/execution_tutorial_part2/) has examples on using this method in variational quantum algorithms.
368
+ The [Execution Tutorial](https://docs.classiq.io/latest/getting-started/classiq_tutorial/execution_tutorial_part2/) has examples on using this method in variational quantum algorithms.
369
369
  More information about [Hamiltonians](https://docs.classiq.io/latest/qmod-reference/language-reference/classical-types/#hamiltonians).
370
370
  """
371
371
  _hamiltonian_deprecation_warning(cost_function)
@@ -0,0 +1,3 @@
1
+ from classiq.execution.functions.sample import _new_sample
2
+
3
+ __all__ = ["_new_sample"]
@@ -0,0 +1,19 @@
1
+ import logging
2
+
3
+ _logger = logging.getLogger(__name__)
4
+
5
+
6
+ def _setup_logging() -> None:
7
+ if _logger.handlers:
8
+ return
9
+
10
+ handler = logging.StreamHandler()
11
+ formatter = logging.Formatter("%(message)s")
12
+ handler.setFormatter(formatter)
13
+ _logger.addHandler(handler)
14
+ _logger.setLevel(logging.INFO)
15
+
16
+ _logger.propagate = False
17
+
18
+
19
+ _setup_logging()
@@ -0,0 +1,9 @@
1
+ from enum import auto, unique
2
+
3
+ from classiq.interface.enum_utils import StrEnum
4
+
5
+
6
+ @unique
7
+ class Verbosity(StrEnum):
8
+ QUIET = auto()
9
+ INFO = auto()
@@ -0,0 +1,90 @@
1
+ from classiq.interface.hardware import Provider
2
+
3
+ _PROVIDER_BACKEND_SEPARATOR = "/"
4
+
5
+ _PROVIDER_TO_CANONICAL_NAME: dict[Provider, str] = {
6
+ Provider.IBM_QUANTUM: "ibm",
7
+ Provider.AZURE_QUANTUM: "azure",
8
+ Provider.AMAZON_BRAKET: "braket",
9
+ Provider.IONQ: "ionq",
10
+ Provider.CLASSIQ: "classiq",
11
+ Provider.GOOGLE: "google",
12
+ Provider.ALICE_AND_BOB: "alice&bob",
13
+ Provider.OQC: "oqc",
14
+ Provider.INTEL: "intel",
15
+ Provider.AQT: "aqt",
16
+ Provider.CINECA: "cineca",
17
+ Provider.SOFTBANK: "softbank",
18
+ }
19
+
20
+ _CANONICAL_NAMES_TO_PROVIDER: dict[str, Provider] = {
21
+ name: provider for provider, name in _PROVIDER_TO_CANONICAL_NAME.items()
22
+ }
23
+
24
+
25
+ def _error_suggested_provider(provider_name: str) -> Provider | None:
26
+ """
27
+ In the case that receive an incorrect provider name, return a possible suggestion.
28
+ """
29
+ provider_name = provider_name.strip().lower()
30
+ for canonical_name in _CANONICAL_NAMES_TO_PROVIDER:
31
+ if canonical_name in provider_name:
32
+ return _CANONICAL_NAMES_TO_PROVIDER[canonical_name]
33
+ # Special cases
34
+ if "gcp" in provider_name:
35
+ return Provider.GOOGLE
36
+ if "microsoft" in provider_name:
37
+ return Provider.AZURE_QUANTUM
38
+ if "amazon" in provider_name or "aws" in provider_name:
39
+ return Provider.AMAZON_BRAKET
40
+ if "oxford" in provider_name:
41
+ return Provider.OQC
42
+ if "alice" in provider_name or "bob" in provider_name:
43
+ return Provider.ALICE_AND_BOB
44
+ if "alpine" in provider_name:
45
+ return Provider.AQT
46
+
47
+ return None
48
+
49
+
50
+ def _parse_provider_backend(spec: str) -> tuple[Provider, str]:
51
+ """
52
+ Parse a backend specification into (provider, backend). Provider is case-insensitive.
53
+ Backend must NOT contain the separator. If provider is not specified, it defaults to
54
+ Classiq.
55
+ """
56
+ if not spec.strip():
57
+ raise ValueError("Backend specification must be a non-empty string")
58
+
59
+ spec = spec.strip()
60
+
61
+ if _PROVIDER_BACKEND_SEPARATOR not in spec:
62
+ return Provider.CLASSIQ, spec
63
+
64
+ provider_raw, backend = spec.split(_PROVIDER_BACKEND_SEPARATOR, 1)
65
+
66
+ if _PROVIDER_BACKEND_SEPARATOR in backend:
67
+ raise ValueError(
68
+ f"Backend name must not contain '{_PROVIDER_BACKEND_SEPARATOR}': '{backend}'"
69
+ )
70
+
71
+ provider_key = provider_raw.strip().lower()
72
+ backend = backend.strip()
73
+
74
+ if not provider_key:
75
+ raise ValueError("Provider name cannot be empty")
76
+ if not backend:
77
+ raise ValueError("Backend name cannot be empty")
78
+
79
+ try:
80
+ provider = _CANONICAL_NAMES_TO_PROVIDER[provider_key]
81
+ except KeyError:
82
+ error_message = f"Unrecognized provider '{provider_raw}'."
83
+ suggested_provider = _error_suggested_provider(provider_key)
84
+ if suggested_provider is not None:
85
+ error_message += (
86
+ f" Did you mean '{_PROVIDER_TO_CANONICAL_NAME[suggested_provider]}'?"
87
+ )
88
+ raise ValueError(error_message) from None
89
+
90
+ return provider, backend