classiq 0.104.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 (52) 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/combinatorial_optimization/combinatorial_problem.py +1 -1
  9. classiq/execution/all_hardware_devices.py +59 -1
  10. classiq/execution/functions/__init__.py +11 -1
  11. classiq/execution/functions/expectation_value.py +106 -0
  12. classiq/execution/functions/minimize.py +90 -0
  13. classiq/execution/functions/sample.py +8 -189
  14. classiq/execution/functions/state_vector.py +113 -0
  15. classiq/execution/functions/util/__init__.py +0 -0
  16. classiq/execution/functions/util/backend_preferences.py +188 -0
  17. classiq/interface/_version.py +1 -1
  18. classiq/interface/backend/backend_preferences.py +66 -0
  19. classiq/interface/backend/quantum_backend_providers.py +11 -0
  20. classiq/interface/exceptions.py +0 -4
  21. classiq/interface/generator/arith/binary_ops.py +24 -0
  22. classiq/interface/generator/arith/number_utils.py +15 -6
  23. classiq/interface/generator/compiler_keywords.py +1 -0
  24. classiq/interface/generator/function_param_list.py +4 -0
  25. classiq/interface/generator/function_params.py +1 -1
  26. classiq/interface/generator/functions/classical_type.py +15 -0
  27. classiq/interface/generator/functions/type_name.py +17 -4
  28. classiq/interface/generator/transpiler_basis_gates.py +1 -0
  29. classiq/interface/generator/types/compilation_metadata.py +15 -6
  30. classiq/interface/hardware.py +1 -0
  31. classiq/interface/interface_version.py +1 -1
  32. classiq/interface/model/model.py +19 -0
  33. classiq/interface/model/quantum_type.py +15 -0
  34. classiq/interface/qubits_mapping/__init__.py +4 -0
  35. classiq/interface/qubits_mapping/path_expr_range.py +69 -0
  36. classiq/interface/qubits_mapping/qubits_mapping.py +231 -0
  37. classiq/interface/qubits_mapping/slices.py +112 -0
  38. classiq/model_expansions/arithmetic.py +6 -0
  39. classiq/qmod/builtins/functions/__init__.py +12 -9
  40. classiq/qmod/builtins/functions/allocation.py +0 -36
  41. classiq/qmod/builtins/functions/arithmetic.py +52 -0
  42. classiq/qmod/builtins/functions/gray_code.py +23 -0
  43. classiq/qmod/builtins/functions/mcx_func.py +10 -0
  44. classiq/qmod/builtins/structs.py +22 -3
  45. classiq/qprog_to_cudaq.py +347 -0
  46. {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/METADATA +4 -1
  47. {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/RECORD +52 -39
  48. /classiq/execution/functions/{_logging.py → util/_logging.py} +0 -0
  49. /classiq/execution/functions/{constants.py → util/constants.py} +0 -0
  50. /classiq/execution/functions/{parse_provider_backend.py → util/parse_provider_backend.py} +0 -0
  51. {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/WHEEL +0 -0
  52. {classiq-0.104.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()
@@ -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
@@ -1,7 +1,15 @@
1
- from classiq.interface.hardware import HardwareInformation
1
+ from typing import TYPE_CHECKING
2
+
3
+ from classiq.interface.hardware import HardwareInformation, Provider
2
4
 
3
5
  from classiq._internals import async_utils
4
6
  from classiq._internals.api_wrapper import ApiWrapper
7
+ from classiq.execution.functions.util.parse_provider_backend import (
8
+ _PROVIDER_TO_CANONICAL_NAME,
9
+ )
10
+
11
+ if TYPE_CHECKING:
12
+ from pandas import DataFrame
5
13
 
6
14
 
7
15
  def get_all_hardware_devices() -> list[HardwareInformation]:
@@ -9,3 +17,53 @@ def get_all_hardware_devices() -> list[HardwareInformation]:
9
17
  Returns a list of all hardware devices known to Classiq.
10
18
  """
11
19
  return async_utils.run(ApiWrapper.call_get_all_hardware_devices())
20
+
21
+
22
+ def _extract_relevant_hardware_fields_for_user(info: HardwareInformation) -> tuple:
23
+ return (
24
+ _PROVIDER_TO_CANONICAL_NAME[info.provider],
25
+ info.name,
26
+ info.number_of_qubits,
27
+ info.status.availability.is_available,
28
+ info.status.pending_jobs,
29
+ info.status.queue_time,
30
+ )
31
+
32
+
33
+ _NON_DISPLAYED_DEVICES = [
34
+ (Provider.CLASSIQ, "simulator_statevector"),
35
+ (Provider.CLASSIQ, "nvidia_simulator_statevector"),
36
+ (Provider.GOOGLE, "cuquantum_statevector"),
37
+ ]
38
+
39
+
40
+ def get_backend_details() -> "DataFrame":
41
+ """
42
+ Returns a pandas DataFrame containing hardware devices known to Classiq.
43
+ """
44
+ from pandas import DataFrame
45
+
46
+ devices = get_all_hardware_devices()
47
+ displayed_devices = [
48
+ device
49
+ for device in devices
50
+ if (device.provider, device.name) not in _NON_DISPLAYED_DEVICES
51
+ ]
52
+ # Remove "ibm_" prefix.
53
+ for device in displayed_devices:
54
+ if device.provider == Provider.IBM_QUANTUM and device.name.startswith("ibm_"):
55
+ device.name = device.name[len("ibm_") :]
56
+ tuples = [
57
+ _extract_relevant_hardware_fields_for_user(info) for info in displayed_devices
58
+ ]
59
+ return DataFrame(
60
+ tuples,
61
+ columns=[
62
+ "provider",
63
+ "name",
64
+ "number of qubits",
65
+ "available",
66
+ "pending jobs",
67
+ "queue time",
68
+ ],
69
+ )
@@ -1,3 +1,13 @@
1
+ from classiq.execution.functions.expectation_value import _get_expectation_value
2
+ from classiq.execution.functions.minimize import _minimize
1
3
  from classiq.execution.functions.sample import _new_sample
4
+ from classiq.execution.functions.state_vector import _calculate_state_vector
5
+ from classiq.execution.functions.util.constants import Verbosity
2
6
 
3
- __all__ = ["_new_sample"]
7
+ __all__ = [
8
+ "Verbosity",
9
+ "_calculate_state_vector",
10
+ "_get_expectation_value",
11
+ "_minimize",
12
+ "_new_sample",
13
+ ]
@@ -0,0 +1,106 @@
1
+ from typing import Any
2
+
3
+ from classiq.interface.backend.backend_preferences import (
4
+ BackendPreferencesTypes,
5
+ ClassiqBackendPreferences,
6
+ )
7
+ from classiq.interface.backend.provider_config.provider_config import ProviderConfig
8
+ from classiq.interface.backend.quantum_backend_providers import (
9
+ ClassiqSimulatorBackendNames,
10
+ )
11
+ from classiq.interface.executor.execution_preferences import ExecutionPreferences
12
+ from classiq.interface.generator.model.preferences import create_random_seed
13
+ from classiq.interface.generator.model.preferences.preferences import (
14
+ TranspilationOption,
15
+ )
16
+ from classiq.interface.hardware import Provider
17
+
18
+ from classiq import (
19
+ ExecutionParams,
20
+ QuantumProgram,
21
+ )
22
+ from classiq.execution.execution_session import ExecutionSession
23
+ from classiq.execution.functions.util._logging import _logger
24
+ from classiq.execution.functions.util.backend_preferences import (
25
+ _get_backend_preferences_from_specifier,
26
+ )
27
+ from classiq.execution.functions.util.constants import Verbosity
28
+ from classiq.execution.functions.util.parse_provider_backend import (
29
+ _parse_provider_backend,
30
+ )
31
+ from classiq.qmod.builtins.structs import SparsePauliOp
32
+
33
+ _DEFAULT_BACKEND_NAME = "simulator"
34
+
35
+
36
+ def _get_backend_preferences(
37
+ backend: str, estimate: bool, config: dict[str, Any] | ProviderConfig | None
38
+ ) -> BackendPreferencesTypes:
39
+ provider, backend_name = _parse_provider_backend(backend)
40
+ backend_preferences: BackendPreferencesTypes
41
+ if not estimate:
42
+ if not (
43
+ provider == Provider.CLASSIQ and backend_name.lower().strip() == "simulator"
44
+ ):
45
+ raise ValueError(
46
+ "Calculating exact expectation value is supported only for the 'classiq/simulator' backend"
47
+ )
48
+ backend_preferences = ClassiqBackendPreferences(
49
+ # This backend name is for exact simulation
50
+ backend_name=ClassiqSimulatorBackendNames.SIMULATOR_STATEVECTOR
51
+ )
52
+ else:
53
+ backend_preferences = _get_backend_preferences_from_specifier(
54
+ backend, config or {}
55
+ )
56
+ return backend_preferences
57
+
58
+
59
+ def _get_expectation_value(
60
+ qprog: QuantumProgram,
61
+ observable: SparsePauliOp,
62
+ backend: str | None = None,
63
+ *,
64
+ estimate: bool = True,
65
+ parameters: ExecutionParams | None = None,
66
+ config: dict[str, Any] | ProviderConfig | None = None,
67
+ num_shots: int | None = None,
68
+ random_seed: int | None = None,
69
+ transpilation_option: TranspilationOption = TranspilationOption.DECOMPOSE,
70
+ verbosity: Verbosity = Verbosity.INFO,
71
+ ) -> complex:
72
+ """
73
+ Get the expectation value of the observable O with respect to the state |psi>
74
+
75
+ Args:
76
+ qprog: The quantum program which generates the state |psi> to be observed
77
+ observable: The observable O
78
+ backend: The device (hardware or simulator) on which to run the quantum program. Specified as "provider/device_id". Use the `get_backend_details` function to see supported devices.
79
+ estimate: Whether to estimate the expectation value by repeatedly measuring the circuit, or else calculate the expectation value using a simulator.
80
+ parameters: The classical parameters for the quantum program
81
+ config: Provider-specific configuration, such as api keys
82
+ num_shots:
83
+ random_seed: The random seed used for transpilation and simulation
84
+ transpilation_option: Advanced configuration for hardware-specific transpilation
85
+ verbosity: What level of information should be logged
86
+
87
+ Returns: The expectation value
88
+ """
89
+ if backend is None:
90
+ backend = _DEFAULT_BACKEND_NAME
91
+ backend_preferences = _get_backend_preferences(backend, estimate, config)
92
+ ep = ExecutionPreferences(
93
+ backend_preferences=backend_preferences,
94
+ num_shots=num_shots,
95
+ random_seed=create_random_seed() if random_seed is None else random_seed,
96
+ transpile_to_hardware=transpilation_option,
97
+ )
98
+
99
+ if verbosity != Verbosity.QUIET:
100
+ _logger.info(f"Submitting job to {backend}")
101
+ with ExecutionSession(qprog, execution_preferences=ep) as session:
102
+ job = session.submit_estimate(hamiltonian=observable, parameters=parameters)
103
+ if verbosity != Verbosity.QUIET:
104
+ _logger.info(f"Job id: {job.id}")
105
+ result = job.get_estimate_result()
106
+ return result.value
@@ -0,0 +1,90 @@
1
+ from typing import Any
2
+
3
+ from classiq.interface.backend.provider_config.provider_config import ProviderConfig
4
+ from classiq.interface.executor.execution_preferences import ExecutionPreferences
5
+ from classiq.interface.generator.model.preferences import create_random_seed
6
+ from classiq.interface.generator.model.preferences.preferences import (
7
+ TranspilationOption,
8
+ )
9
+
10
+ from classiq import (
11
+ ExecutionParams,
12
+ QuantumProgram,
13
+ )
14
+ from classiq.execution.execution_session import ExecutionSession
15
+ from classiq.execution.functions.util._logging import _logger
16
+ from classiq.execution.functions.util.backend_preferences import (
17
+ _get_backend_preferences_from_specifier,
18
+ )
19
+ from classiq.execution.functions.util.constants import Verbosity
20
+ from classiq.qmod.builtins.structs import SparsePauliOp
21
+ from classiq.qmod.qmod_variable import QmodExpressionCreator
22
+
23
+ _DEFAULT_BACKEND_NAME = "simulator"
24
+
25
+
26
+ def _minimize(
27
+ qprog: QuantumProgram,
28
+ cost_function: SparsePauliOp | QmodExpressionCreator,
29
+ initial_params: ExecutionParams,
30
+ max_iteration: int,
31
+ backend: str | None = None,
32
+ *,
33
+ quantile: float = 1.0,
34
+ tolerance: float | None = None,
35
+ config: dict[str, Any] | ProviderConfig | None = None,
36
+ random_seed: int | None = None,
37
+ transpilation_option: TranspilationOption = TranspilationOption.DECOMPOSE,
38
+ verbosity: Verbosity = Verbosity.INFO,
39
+ ) -> list[tuple[float, ExecutionParams]]:
40
+ """
41
+ Minimize the given cost function using the quantum program.
42
+
43
+ Args:
44
+ qprog: The parametric quantum program that generates the state (ansatz).
45
+ Only quantum programs with exactly one execution parameter are supported.
46
+ cost_function: The cost function to minimize. It can be one of the following:
47
+ - A quantum cost function defined by a Hamiltonian.
48
+ - A classical cost function represented as a callable that returns a Qmod expression.
49
+ The callable should accept `QVar`s as arguments and use names matching the Model outputs.
50
+ initial_params: The initial parameters for the minimization.
51
+ This parameter must be of type `CReal` or `CArray`. The dictionary must contain a single key-value pair, where:
52
+ - The key is the name of the parameter.
53
+ - The value is either a float or a list of floats.
54
+ max_iteration: The maximum number of iterations for the minimization.
55
+ backend: The device (hardware or simulator) on which to run the quantum programs. Specified as "provider/device_id". Use the `get_backend_details` function to see supported devices.
56
+ quantile: The quantile to use for cost estimation.
57
+ tolerance: The tolerance for the minimization.
58
+ config: Provider-specific configuration, such as api keys
59
+ random_seed: Set this to increase determinism
60
+ transpilation_option: Advanced configuration for hardware-specific transpilation
61
+ verbosity: What level of information should be logged
62
+ Returns:
63
+ 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.
64
+
65
+ See Also:
66
+ 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.
67
+ More information about [Hamiltonians](https://docs.classiq.io/latest/qmod-reference/language-reference/classical-types/#hamiltonians).
68
+ """
69
+ if backend is None:
70
+ backend = _DEFAULT_BACKEND_NAME
71
+ backend_preferences = _get_backend_preferences_from_specifier(backend, config or {})
72
+
73
+ ep = ExecutionPreferences(
74
+ backend_preferences=backend_preferences,
75
+ random_seed=create_random_seed() if random_seed is None else random_seed,
76
+ transpile_to_hardware=transpilation_option,
77
+ )
78
+
79
+ if verbosity != Verbosity.QUIET:
80
+ _logger.info(f"Submitting job to {backend}")
81
+ with ExecutionSession(qprog, execution_preferences=ep) as session:
82
+ job = session.submit_minimize(
83
+ cost_function, initial_params, max_iteration, quantile, tolerance
84
+ )
85
+ if verbosity != Verbosity.QUIET:
86
+ _logger.info(f"Job id: {job.id}")
87
+ result = job.get_minimization_result()
88
+ return session._minimize_result_to_result(
89
+ result=result, initial_params=initial_params
90
+ )