classiq 0.102.0__py3-none-any.whl → 1.0.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 (95) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/_internals/authentication/auth0.py +29 -0
  3. classiq/_internals/authentication/auth_flow_factory.py +43 -0
  4. classiq/_internals/authentication/machine_credentials_flow.py +26 -0
  5. classiq/_internals/authentication/password_manager.py +84 -0
  6. classiq/_internals/authentication/token_manager.py +24 -8
  7. classiq/analyzer/show_interactive_hack.py +0 -8
  8. classiq/applications/chemistry/op_utils.py +32 -0
  9. classiq/applications/combinatorial_optimization/combinatorial_problem.py +1 -1
  10. classiq/evaluators/qmod_annotated_expression.py +1 -1
  11. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +1 -8
  12. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +1 -1
  13. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +2 -2
  14. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +18 -29
  15. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +1 -6
  16. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +1 -7
  17. classiq/evaluators/qmod_type_inference/quantum_type_comparison.py +52 -0
  18. classiq/execution/all_hardware_devices.py +59 -1
  19. classiq/execution/execution_session.py +1 -1
  20. classiq/execution/functions/__init__.py +13 -0
  21. classiq/execution/functions/expectation_value.py +106 -0
  22. classiq/execution/functions/minimize.py +90 -0
  23. classiq/execution/functions/sample.py +76 -0
  24. classiq/execution/functions/state_vector.py +113 -0
  25. classiq/execution/functions/util/__init__.py +0 -0
  26. classiq/execution/functions/util/_logging.py +19 -0
  27. classiq/execution/functions/util/backend_preferences.py +188 -0
  28. classiq/execution/functions/util/constants.py +9 -0
  29. classiq/execution/functions/util/parse_provider_backend.py +90 -0
  30. classiq/interface/_version.py +1 -1
  31. classiq/interface/backend/backend_preferences.py +81 -0
  32. classiq/interface/backend/provider_config/providers/aqt.py +1 -1
  33. classiq/interface/backend/provider_config/providers/azure.py +1 -2
  34. classiq/interface/backend/provider_config/providers/ibm.py +1 -1
  35. classiq/interface/backend/quantum_backend_providers.py +14 -0
  36. classiq/interface/exceptions.py +0 -4
  37. classiq/interface/executor/result.py +9 -5
  38. classiq/interface/generator/arith/binary_ops.py +62 -2
  39. classiq/interface/generator/arith/number_utils.py +15 -6
  40. classiq/interface/generator/compiler_keywords.py +1 -0
  41. classiq/interface/generator/function_param_list.py +8 -2
  42. classiq/interface/generator/function_params.py +1 -1
  43. classiq/interface/generator/functions/builtins/internal_operators.py +5 -9
  44. classiq/interface/generator/functions/classical_type.py +60 -0
  45. classiq/interface/generator/functions/type_name.py +36 -0
  46. classiq/interface/generator/generated_circuit_data.py +0 -2
  47. classiq/interface/generator/transpiler_basis_gates.py +1 -0
  48. classiq/interface/generator/types/compilation_metadata.py +18 -0
  49. classiq/interface/hardware.py +2 -0
  50. classiq/interface/helpers/model_normalizer.py +42 -6
  51. classiq/interface/interface_version.py +1 -1
  52. classiq/interface/model/invert.py +8 -0
  53. classiq/interface/model/model.py +19 -0
  54. classiq/interface/model/model_visitor.py +4 -2
  55. classiq/interface/model/quantum_type.py +36 -0
  56. classiq/interface/model/statement_block.py +0 -4
  57. classiq/interface/qubits_mapping/__init__.py +4 -0
  58. classiq/interface/qubits_mapping/path_expr_range.py +69 -0
  59. classiq/interface/qubits_mapping/qubits_mapping.py +231 -0
  60. classiq/interface/qubits_mapping/slices.py +112 -0
  61. classiq/model_expansions/arithmetic.py +6 -0
  62. classiq/model_expansions/capturing/captured_vars.py +16 -12
  63. classiq/model_expansions/function_builder.py +9 -1
  64. classiq/model_expansions/interpreters/base_interpreter.py +9 -8
  65. classiq/model_expansions/interpreters/generative_interpreter.py +9 -24
  66. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -0
  67. classiq/model_expansions/quantum_operations/assignment_result_processor.py +132 -28
  68. classiq/model_expansions/quantum_operations/bind.py +4 -0
  69. classiq/model_expansions/quantum_operations/call_emitter.py +5 -35
  70. classiq/model_expansions/quantum_operations/emitter.py +1 -4
  71. classiq/model_expansions/quantum_operations/expression_evaluator.py +0 -3
  72. classiq/model_expansions/visitors/uncomputation_signature_inference.py +0 -9
  73. classiq/qmod/builtins/functions/__init__.py +21 -9
  74. classiq/qmod/builtins/functions/allocation.py +0 -36
  75. classiq/qmod/builtins/functions/arithmetic.py +183 -0
  76. classiq/qmod/builtins/functions/exponentiation.py +32 -2
  77. classiq/qmod/builtins/functions/gray_code.py +23 -0
  78. classiq/qmod/builtins/functions/mcx_func.py +10 -0
  79. classiq/qmod/builtins/operations.py +2 -38
  80. classiq/qmod/builtins/structs.py +22 -3
  81. classiq/qmod/native/pretty_printer.py +1 -12
  82. classiq/qmod/pretty_print/pretty_printer.py +1 -17
  83. classiq/qmod/qmod_parameter.py +4 -0
  84. classiq/qmod/qmod_variable.py +38 -63
  85. classiq/qmod/quantum_function.py +43 -7
  86. classiq/qmod/semantics/validation/function_name_collisions_validation.py +7 -4
  87. classiq/qmod/semantics/validation/model_validation.py +7 -2
  88. classiq/qmod/symbolic_type.py +4 -2
  89. classiq/qprog_to_cudaq.py +347 -0
  90. {classiq-0.102.0.dist-info → classiq-1.0.0.dist-info}/METADATA +4 -1
  91. {classiq-0.102.0.dist-info → classiq-1.0.0.dist-info}/RECORD +93 -76
  92. classiq/interface/generator/amplitude_loading.py +0 -103
  93. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +0 -77
  94. {classiq-0.102.0.dist-info → classiq-1.0.0.dist-info}/WHEEL +0 -0
  95. {classiq-0.102.0.dist-info → classiq-1.0.0.dist-info}/licenses/LICENSE.txt +0 -0
classiq/__init__.py CHANGED
@@ -52,6 +52,7 @@ from classiq.open_library import * # noqa: F403
52
52
  from classiq.open_library import __all__ as _open_library_all
53
53
  from classiq.qmod import * # noqa: F403
54
54
  from classiq.qmod import __all__ as _qmod_all
55
+ from classiq.qprog_to_cudaq import qprog_to_cudaq_kernel
55
56
  from classiq.quantum_program import ExecutionParams, assign_parameters, transpile
56
57
  from classiq.synthesis import (
57
58
  QmodFormat,
@@ -117,6 +118,7 @@ __all__ = (
117
118
  "matrix_to_hamiltonian",
118
119
  "matrix_to_pauli_operator",
119
120
  "pauli_operator_to_matrix",
121
+ "qprog_to_cudaq_kernel",
120
122
  "quantum_program_from_qasm",
121
123
  "quantum_program_from_qasm_async",
122
124
  "QmodFormat",
@@ -11,6 +11,8 @@ from classiq.interface.exceptions import ClassiqAuthenticationError
11
11
 
12
12
  from classiq._internals.config import CONFIG_ENV_FILES
13
13
 
14
+ GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"
15
+
14
16
 
15
17
  class AuthSettings(BaseSettings):
16
18
  domain: str = Field(
@@ -144,3 +146,30 @@ class Auth0:
144
146
  access_token=data["access_token"],
145
147
  refresh_token=data.get("refresh_token", None),
146
148
  )
149
+
150
+ async def client_credentials_login(
151
+ self, client_id: str, client_secret: str, organization: str | None = None
152
+ ) -> dict[str, Any]:
153
+ """Authenticate using client credentials (M2M flow).
154
+
155
+ Args:
156
+ client_id: The M2M application client ID
157
+ client_secret: The M2M application client secret
158
+ organization: Optional organization ID for organization-specific access
159
+
160
+ Returns:
161
+ Dictionary containing token data from Auth0
162
+ """
163
+ payload = {
164
+ "client_id": client_id,
165
+ "client_secret": client_secret,
166
+ "grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
167
+ "audience": self._auth_settings.audience,
168
+ }
169
+ if organization:
170
+ payload["organization"] = organization
171
+
172
+ return await self._make_request(
173
+ url="/oauth/token",
174
+ payload=payload,
175
+ )
@@ -0,0 +1,43 @@
1
+ import logging
2
+
3
+ from classiq._internals.authentication import password_manager as pm
4
+ from classiq._internals.authentication.authorization_flow import AuthorizationFlow
5
+ from classiq._internals.authentication.hybrid_flow import HybridFlow
6
+ from classiq._internals.authentication.machine_credentials_flow import (
7
+ M2MClientCredentialsFlow,
8
+ )
9
+ from classiq._internals.config import Configuration
10
+
11
+ _logger = logging.getLogger(__name__)
12
+
13
+
14
+ class AuthFlowFactory:
15
+ """Factory for creating appropriate authorization and authentication flows based on context."""
16
+
17
+ @staticmethod
18
+ def create_flow(
19
+ password_manager: pm.PasswordManager, config: Configuration
20
+ ) -> AuthorizationFlow:
21
+ """
22
+ Create an authentication with or without authentication flow based on the password manager type.
23
+
24
+ Args:
25
+ password_manager: The password manager instance.
26
+ config: Configuration for authentication.
27
+
28
+ Returns:
29
+ Appropriate AuthorizationFlow instance (M2MClientCredentialsFlow or HybridFlow).
30
+
31
+ Raises:
32
+ ClassiqAuthenticationError: If M2M token generation failed.
33
+ """
34
+ # M2M authentication flow using client credentials to authenticate with Auth0 and get access token
35
+ if isinstance(password_manager, pm.M2MPasswordManager):
36
+ return M2MClientCredentialsFlow(
37
+ client_id=password_manager.client_id,
38
+ client_secret=password_manager.client_secret,
39
+ organization=password_manager.org_id,
40
+ )
41
+
42
+ # Interactive user authentication flow
43
+ return HybridFlow(require_refresh_token=True, text_only=config.text_only)
@@ -0,0 +1,26 @@
1
+ from classiq._internals.authentication.auth0 import Tokens
2
+ from classiq._internals.authentication.authorization_flow import AuthorizationFlow
3
+
4
+
5
+ class M2MClientCredentialsFlow(AuthorizationFlow):
6
+ """Machine-to-Machine (M2M) authentication using OAuth2 Client Credentials flow."""
7
+
8
+ def __init__(
9
+ self,
10
+ client_id: str,
11
+ client_secret: str,
12
+ organization: str | None = None,
13
+ ) -> None:
14
+ # M2M authentication doesn't use refresh tokens or interactive flows
15
+ super().__init__(require_refresh_token=False, text_only=False)
16
+ self.client_id = client_id
17
+ self.client_secret = client_secret
18
+ self.organization = organization
19
+
20
+ async def get_tokens(self) -> Tokens:
21
+ data = await self.auth0_client.client_credentials_login(
22
+ client_id=self.client_id,
23
+ client_secret=self.client_secret,
24
+ organization=self.organization,
25
+ )
26
+ return self.handle_ready_data(data)
@@ -37,6 +37,8 @@ class PasswordManagerSettings(BaseSettings):
37
37
 
38
38
  class PasswordManager(abc.ABC):
39
39
  _SERVICE_NAME: str = "classiqTokenService"
40
+ text_only_supported: bool = True
41
+ has_refresh_token: bool = True
40
42
 
41
43
  def __init__(self) -> None:
42
44
  self._settings = PasswordManagerSettings()
@@ -76,6 +78,8 @@ class PasswordManager(abc.ABC):
76
78
 
77
79
 
78
80
  class KeyringPasswordManager(PasswordManager):
81
+ text_only_supported: bool = False
82
+
79
83
  def _get(self, key: str) -> str | None:
80
84
  return keyring.get_password(service_name=self._SERVICE_NAME, username=key)
81
85
 
@@ -154,3 +158,83 @@ class FilePasswordManager(PasswordManager):
154
158
  @staticmethod
155
159
  def is_supported() -> bool:
156
160
  return "windows" not in platform.platform().lower()
161
+
162
+
163
+ class M2MPasswordManager(PasswordManager):
164
+ """Password manager for M2M authentication using environment variables.
165
+
166
+ Supports machine-to-machine authentication by reading credentials from:
167
+ - CLASSIQ_CLIENT_ID (required)
168
+ - CLASSIQ_CLIENT_SECRET (required)
169
+ - CLASSIQ_ORG_ID (optional)
170
+
171
+ This manager stores tokens in memory after M2M authentication.
172
+ """
173
+
174
+ has_refresh_token: bool = False
175
+
176
+ M2M_CREDS_ENV_KEYS = {
177
+ "client_id": "CLASSIQ_CLIENT_ID",
178
+ "client_secret": "CLASSIQ_CLIENT_SECRET",
179
+ "org_id": "CLASSIQ_ORG_ID",
180
+ }
181
+
182
+ M2M_REQUIRED_CREDS = {"client_id", "client_secret"}
183
+
184
+ def __init__(self) -> None:
185
+ super().__init__()
186
+ self._token_storage: dict[str, str | None] = {}
187
+ # Initialize all environment variable values
188
+ self._m2m_creds: dict[str, str | None] = {
189
+ key: os.getenv(env_key) for key, env_key in self.M2M_CREDS_ENV_KEYS.items()
190
+ }
191
+
192
+ def _get(self, key: str) -> str | None:
193
+ """Get token from in-memory storage."""
194
+ return self._token_storage.get(key)
195
+
196
+ def _set(self, key: str, value: str | None) -> None:
197
+ """Store token in memory."""
198
+ if value is None:
199
+ self._clear(key)
200
+ return
201
+ self._token_storage[key] = value
202
+
203
+ def _clear(self, key: str) -> None:
204
+ """Clear token from memory storage."""
205
+ self._token_storage.pop(key, None)
206
+
207
+ @staticmethod
208
+ def is_supported() -> bool:
209
+ """Check if required M2M environment variables are present."""
210
+ manager = M2MPasswordManager()
211
+ return all(
212
+ manager._m2m_creds.get(key) for key in M2MPasswordManager.M2M_REQUIRED_CREDS
213
+ )
214
+
215
+ def _get_required_cred(self, key: str) -> str:
216
+ value = self._m2m_creds.get(key)
217
+ if value is None:
218
+ raise ValueError(
219
+ f"M2M authentication requires {self.M2M_CREDS_ENV_KEYS[key]} environment variable"
220
+ )
221
+ return value
222
+
223
+ # to prevent mypy from complaining about the return type
224
+ def _get_optional_cred(self, key: str) -> str | None:
225
+ return self._m2m_creds.get(key)
226
+
227
+ @property
228
+ def client_id(self) -> str:
229
+ """Get client ID from initialized credentials."""
230
+ return self._get_required_cred("client_id")
231
+
232
+ @property
233
+ def client_secret(self) -> str:
234
+ """Get client secret from initialized credentials."""
235
+ return self._get_required_cred("client_secret")
236
+
237
+ @property
238
+ def org_id(self) -> str | None:
239
+ """Get organization ID from initialized credentials (optional)."""
240
+ return self._get_optional_cred("org_id")
@@ -11,13 +11,15 @@ from classiq.interface.exceptions import (
11
11
 
12
12
  from classiq._internals.authentication import password_manager as pm
13
13
  from classiq._internals.authentication.auth0 import Auth0, Tokens
14
- from classiq._internals.authentication.hybrid_flow import HybridFlow
14
+ from classiq._internals.authentication.auth_flow_factory import AuthFlowFactory
15
15
  from classiq._internals.config import Configuration
16
16
 
17
- PASSWORD_MANAGERS: Sequence[type[pm.PasswordManager]] = [
17
+ SUPPORTED_PASSWORD_MANAGERS: Sequence[type[pm.PasswordManager]] = [
18
+ pm.M2MPasswordManager,
18
19
  pm.KeyringPasswordManager,
19
20
  pm.FilePasswordManager,
20
21
  ]
22
+
21
23
  _logger = logging.getLogger(__name__)
22
24
 
23
25
 
@@ -57,9 +59,11 @@ class TokenManager:
57
59
 
58
60
  if should_skip_authentication:
59
61
  return pm.DummyPasswordManager()
60
- if config.text_only:
61
- return pm.FilePasswordManager()
62
- for password_manager in PASSWORD_MANAGERS:
62
+
63
+ # Filter out M2MPasswordManager and FilePasswordManager in text-only mode
64
+ for password_manager in SUPPORTED_PASSWORD_MANAGERS:
65
+ if config.text_only and not password_manager.text_only_supported:
66
+ continue
63
67
  if password_manager.is_supported():
64
68
  return password_manager()
65
69
  raise ClassiqPasswordManagerSelectionError(
@@ -75,6 +79,10 @@ class TokenManager:
75
79
  self._save_tokens(tokens)
76
80
 
77
81
  async def update_expired_access_token(self) -> None:
82
+ if not self._password_manager.has_refresh_token:
83
+ await self._authentication_helper()
84
+ return
85
+
78
86
  if self._refresh_token is not None:
79
87
  await self._refresh()
80
88
  return
@@ -84,6 +92,11 @@ class TokenManager:
84
92
  )
85
93
 
86
94
  async def manual_authentication(self, overwrite: bool) -> None:
95
+ # For M2M authentication, always re-authenticate (no refresh token concept)
96
+ if not self._password_manager.has_refresh_token:
97
+ await self._authentication_helper()
98
+ return
99
+
87
100
  if self._refresh_token is None:
88
101
  await self._authentication_helper()
89
102
  return
@@ -126,8 +139,11 @@ class TokenManager:
126
139
  async def _authentication_helper(self) -> None:
127
140
  # TODO: consider using refresh token rotation
128
141
  # (https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation)
129
- authorization_flow = HybridFlow(
130
- require_refresh_token=True, text_only=self._config.text_only
142
+ authorization_flow = AuthFlowFactory.create_flow(
143
+ password_manager=self._password_manager, config=self._config
131
144
  )
132
145
  tokens = await authorization_flow.get_tokens()
133
- self._save_tokens(tokens, force_override_refresh_token=True)
146
+
147
+ # Password managers without refresh tokens don't use rotation
148
+ force_override = self._password_manager.has_refresh_token
149
+ self._save_tokens(tokens, force_override_refresh_token=force_override)
@@ -7,8 +7,6 @@ from tempfile import NamedTemporaryFile
7
7
  from urllib.parse import urljoin
8
8
 
9
9
  from classiq.interface.analyzer.result import DataID
10
- from classiq.interface.exceptions import ClassiqAnalyzerVisualizationError
11
- from classiq.interface.generator.model.preferences.preferences import QuantumFormat
12
10
  from classiq.interface.generator.quantum_program import QuantumProgram
13
11
 
14
12
  from classiq._internals.api_wrapper import ApiWrapper
@@ -116,12 +114,6 @@ def get_visualization_renderer() -> VisualizationRenderer:
116
114
 
117
115
 
118
116
  async def handle_remote_app(circuit: QuantumProgram, display_url: bool = True) -> None:
119
- if circuit.outputs.get(QuantumFormat.QASM) is None:
120
- raise ClassiqAnalyzerVisualizationError(
121
- "Missing QASM transpilation: visualization is only supported "
122
- "for QASM programs. Try adding QASM to the output formats "
123
- "synthesis preferences"
124
- )
125
117
  circuit_dataid = DataID(id=circuit.program_id)
126
118
 
127
119
  renderer = get_visualization_renderer()
@@ -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
 
@@ -37,7 +37,7 @@ class CombinatorialProblem:
37
37
  self,
38
38
  pyo_model: pyo.ConcreteModel,
39
39
  num_layers: int,
40
- penalty_factor: int = 1,
40
+ penalty_factor: float = 1,
41
41
  ):
42
42
  self.problem_vars_, self.cost_func = pyo_model_to_qmod_problem(
43
43
  pyo_model, penalty_factor
@@ -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
  )