classiq 0.83.0__py3-none-any.whl → 0.84.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.
- classiq/_internals/api_wrapper.py +27 -0
- classiq/applications/chemistry/chemistry_model_constructor.py +0 -2
- classiq/applications/chemistry/hartree_fock.py +68 -0
- classiq/applications/chemistry/mapping.py +85 -0
- classiq/applications/chemistry/op_utils.py +79 -0
- classiq/applications/chemistry/problems.py +195 -0
- classiq/applications/chemistry/ucc.py +109 -0
- classiq/applications/chemistry/z2_symmetries.py +368 -0
- classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +30 -1
- classiq/{model_expansions/evaluators → evaluators}/arg_type_match.py +12 -4
- classiq/{model_expansions/evaluators → evaluators}/argument_types.py +1 -1
- classiq/{model_expansions/evaluators → evaluators}/classical_expression.py +1 -1
- classiq/{model_expansions/evaluators → evaluators}/classical_type_inference.py +3 -4
- classiq/{model_expansions/evaluators → evaluators}/parameter_types.py +17 -15
- classiq/execution/__init__.py +12 -1
- classiq/execution/execution_session.py +189 -43
- classiq/execution/jobs.py +26 -1
- classiq/execution/qnn.py +2 -2
- classiq/execution/user_budgets.py +39 -0
- classiq/interface/_version.py +1 -1
- classiq/interface/constants.py +1 -0
- classiq/interface/execution/primitives.py +29 -1
- classiq/interface/executor/estimate_cost.py +35 -0
- classiq/interface/executor/execution_result.py +13 -0
- classiq/interface/executor/result.py +116 -1
- classiq/interface/executor/user_budget.py +26 -33
- classiq/interface/generator/expressions/atomic_expression_functions.py +5 -1
- classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +0 -6
- classiq/interface/generator/functions/classical_type.py +2 -35
- classiq/interface/generator/functions/concrete_types.py +0 -3
- classiq/interface/generator/functions/type_modifier.py +0 -19
- classiq/interface/generator/generated_circuit_data.py +0 -8
- classiq/interface/generator/types/compilation_metadata.py +0 -3
- classiq/interface/ide/visual_model.py +6 -2
- classiq/interface/model/model.py +12 -7
- classiq/interface/model/port_declaration.py +2 -24
- classiq/interface/pretty_print/__init__.py +0 -0
- classiq/{qmod/native → interface/pretty_print}/expression_to_qmod.py +18 -11
- classiq/interface/server/routes.py +4 -0
- classiq/model_expansions/atomic_expression_functions_defs.py +42 -5
- classiq/model_expansions/interpreters/base_interpreter.py +3 -3
- classiq/model_expansions/quantum_operations/allocate.py +1 -1
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +1 -1
- classiq/model_expansions/quantum_operations/bind.py +2 -2
- classiq/model_expansions/quantum_operations/call_emitter.py +26 -20
- classiq/model_expansions/quantum_operations/variable_decleration.py +1 -1
- classiq/model_expansions/scope_initialization.py +3 -3
- classiq/model_expansions/transformers/model_renamer.py +6 -4
- classiq/model_expansions/transformers/type_modifier_inference.py +81 -43
- classiq/model_expansions/visitors/symbolic_param_inference.py +2 -3
- classiq/open_library/functions/__init__.py +3 -0
- classiq/open_library/functions/amplitude_amplification.py +10 -18
- classiq/open_library/functions/discrete_sine_cosine_transform.py +5 -5
- classiq/open_library/functions/grover.py +14 -6
- classiq/open_library/functions/modular_exponentiation.py +22 -20
- classiq/open_library/functions/state_preparation.py +17 -0
- classiq/qmod/builtins/enums.py +23 -0
- classiq/qmod/builtins/functions/__init__.py +2 -0
- classiq/qmod/builtins/functions/exponentiation.py +32 -4
- classiq/qmod/builtins/structs.py +55 -3
- classiq/qmod/declaration_inferrer.py +3 -2
- classiq/qmod/native/pretty_printer.py +2 -6
- classiq/qmod/pretty_print/expression_to_python.py +2 -1
- classiq/qmod/pretty_print/pretty_printer.py +1 -6
- classiq/qmod/python_classical_type.py +12 -5
- classiq/qmod/qmod_constant.py +2 -5
- classiq/qmod/qmod_parameter.py +2 -5
- classiq/qmod/qmod_variable.py +56 -15
- classiq/qmod/quantum_expandable.py +4 -2
- classiq/qmod/quantum_function.py +7 -2
- classiq/qmod/semantics/annotation/qstruct_annotator.py +1 -1
- classiq/qmod/semantics/validation/main_validation.py +1 -9
- classiq/qmod/utilities.py +0 -2
- classiq/qmod/write_qmod.py +1 -1
- {classiq-0.83.0.dist-info → classiq-0.84.0.dist-info}/METADATA +4 -1
- {classiq-0.83.0.dist-info → classiq-0.84.0.dist-info}/RECORD +82 -73
- /classiq/{model_expansions/evaluators → evaluators}/__init__.py +0 -0
- /classiq/{model_expansions/evaluators → evaluators}/control.py +0 -0
- /classiq/{model_expansions → evaluators}/expression_evaluator.py +0 -0
- /classiq/{model_expansions/evaluators → evaluators}/quantum_type_utils.py +0 -0
- /classiq/{model_expansions/evaluators → evaluators}/type_type_match.py +0 -0
- {classiq-0.83.0.dist-info → classiq-0.84.0.dist-info}/WHEEL +0 -0
@@ -1,24 +1,36 @@
|
|
1
|
+
import inspect
|
1
2
|
import random
|
2
3
|
from types import TracebackType
|
3
4
|
from typing import Callable, Optional, Union, cast
|
4
5
|
|
5
|
-
import numpy as np
|
6
|
-
|
7
6
|
from classiq.interface.chemistry.operator import PauliOperator, pauli_integers_to_str
|
8
7
|
from classiq.interface.exceptions import ClassiqError, ClassiqValueError
|
9
|
-
from classiq.interface.execution.primitives import
|
8
|
+
from classiq.interface.execution.primitives import (
|
9
|
+
EstimateInput,
|
10
|
+
MinimizeClassicalCostInput,
|
11
|
+
MinimizeQuantumCostInput,
|
12
|
+
PrimitivesInput,
|
13
|
+
)
|
14
|
+
from classiq.interface.executor.estimate_cost import estimate_cost
|
10
15
|
from classiq.interface.executor.execution_preferences import ExecutionPreferences
|
16
|
+
from classiq.interface.executor.execution_result import TaggedMinimizeResult
|
11
17
|
from classiq.interface.executor.result import (
|
12
18
|
EstimationResult,
|
13
19
|
ExecutionDetails,
|
14
20
|
ParsedState,
|
15
21
|
)
|
16
22
|
from classiq.interface.generator.arith import number_utils
|
23
|
+
from classiq.interface.generator.expressions.expression import Expression
|
17
24
|
from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
|
18
25
|
from classiq.interface.generator.quantum_program import (
|
19
26
|
QuantumProgram,
|
20
27
|
)
|
21
|
-
from classiq.interface.
|
28
|
+
from classiq.interface.helpers.custom_pydantic_types import PydanticPauliList
|
29
|
+
from classiq.interface.model.quantum_type import (
|
30
|
+
QuantumBit,
|
31
|
+
QuantumNumeric,
|
32
|
+
RegisterQuantumTypeDict,
|
33
|
+
)
|
22
34
|
|
23
35
|
from classiq._internals import async_utils
|
24
36
|
from classiq._internals.api_wrapper import ApiWrapper
|
@@ -27,38 +39,24 @@ from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import
|
|
27
39
|
_pauli_dict_to_pauli_terms,
|
28
40
|
)
|
29
41
|
from classiq.execution.jobs import ExecutionJob
|
30
|
-
from classiq.qmod.builtins import PauliTerm
|
31
42
|
from classiq.qmod.builtins.classical_execution_primitives import (
|
32
43
|
CARRAY_SEPARATOR,
|
33
44
|
ExecutionParams,
|
34
45
|
)
|
46
|
+
from classiq.qmod.builtins.structs import PauliTerm, SparsePauliOp
|
47
|
+
from classiq.qmod.qmod_variable import (
|
48
|
+
QmodExpressionCreator,
|
49
|
+
create_qvar_from_quantum_type,
|
50
|
+
)
|
35
51
|
|
36
|
-
Hamiltonian =
|
37
|
-
ParsedExecutionParams = dict[str, Union[float, int]]
|
52
|
+
Hamiltonian = SparsePauliOp
|
38
53
|
ExecutionParameters = Optional[Union[ExecutionParams, list[ExecutionParams]]]
|
54
|
+
ParsedExecutionParams = dict[str, Union[float, int]]
|
39
55
|
ParsedExecutionParameters = Optional[
|
40
56
|
Union[ParsedExecutionParams, list[ParsedExecutionParams]]
|
41
57
|
]
|
42
58
|
|
43
59
|
|
44
|
-
def hamiltonian_to_pauli_terms(hamiltonian: Hamiltonian) -> list[PauliTerm]:
|
45
|
-
if isinstance(hamiltonian[0], PauliTerm):
|
46
|
-
return cast(list[PauliTerm], hamiltonian)
|
47
|
-
else:
|
48
|
-
return _pauli_dict_to_pauli_terms(cast(list[QmodPyStruct], hamiltonian))
|
49
|
-
|
50
|
-
|
51
|
-
def _hamiltonian_to_pauli_operator(hamiltonian: Hamiltonian) -> PauliOperator:
|
52
|
-
pauli_list = [
|
53
|
-
(
|
54
|
-
pauli_integers_to_str(elem.pauli), # type: ignore[arg-type]
|
55
|
-
elem.coefficient,
|
56
|
-
)
|
57
|
-
for elem in hamiltonian_to_pauli_terms(hamiltonian)
|
58
|
-
]
|
59
|
-
return PauliOperator(pauli_list=pauli_list)
|
60
|
-
|
61
|
-
|
62
60
|
def parse_params(params: ExecutionParams) -> ParsedExecutionParams:
|
63
61
|
result = {}
|
64
62
|
for key, values in params.items():
|
@@ -251,7 +249,7 @@ class ExecutionSession:
|
|
251
249
|
"""
|
252
250
|
execution_primitives_input = PrimitivesInput(
|
253
251
|
estimate=EstimateInput(
|
254
|
-
hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
|
252
|
+
hamiltonian=self._hamiltonian_to_pauli_operator(hamiltonian),
|
255
253
|
parameters=(
|
256
254
|
[parse_params(parameters)] if parameters is not None else [{}]
|
257
255
|
),
|
@@ -293,12 +291,109 @@ class ExecutionSession:
|
|
293
291
|
"""
|
294
292
|
execution_primitives_input = PrimitivesInput(
|
295
293
|
estimate=EstimateInput(
|
296
|
-
hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
|
294
|
+
hamiltonian=self._hamiltonian_to_pauli_operator(hamiltonian),
|
297
295
|
parameters=[parse_params(params) for params in parameters],
|
298
296
|
)
|
299
297
|
)
|
300
298
|
return self._execute(execution_primitives_input)
|
301
299
|
|
300
|
+
def minimize(
|
301
|
+
self,
|
302
|
+
cost_function: Union[Hamiltonian, QmodExpressionCreator],
|
303
|
+
initial_params: ExecutionParams,
|
304
|
+
max_iteration: int,
|
305
|
+
quantile: float = 1.0,
|
306
|
+
) -> list[tuple[float, ExecutionParams]]:
|
307
|
+
"""
|
308
|
+
Minimizes the given cost function using the quantum program.
|
309
|
+
Args:
|
310
|
+
cost_function: The cost function to minimize. It can be one of the following:
|
311
|
+
- A quantum cost function defined by a Hamiltonian.
|
312
|
+
- A classical cost function represented as a callable that returns a Qmod expression.
|
313
|
+
The callable should accept `QVar`s as arguments and use names matching the Model outputs.
|
314
|
+
initial_params: The initial parameters for the minimization.
|
315
|
+
Only Models with exactly one execution parameter are supported. This parameter must be of type
|
316
|
+
`CReal` or `CArray`. The dictionary must contain a single key-value pair, where:
|
317
|
+
- The key is the name of the parameter.
|
318
|
+
- The value is either a float or a list of floats.
|
319
|
+
max_iteration: The maximum number of iterations for the minimization.
|
320
|
+
quantile: The quantile to use for cost estimation.
|
321
|
+
Returns:
|
322
|
+
A list of tuples, each containing the estimated cost and the corresponding parameters for that iteration.
|
323
|
+
`cost` is a float, and `parameters` is a dictionary matching the execution parameter format.
|
324
|
+
"""
|
325
|
+
job = self.submit_minimize(
|
326
|
+
cost_function=cost_function,
|
327
|
+
initial_params=initial_params,
|
328
|
+
max_iteration=max_iteration,
|
329
|
+
quantile=quantile,
|
330
|
+
)
|
331
|
+
result = job.get_minimization_result(_http_client=self._async_client)
|
332
|
+
|
333
|
+
return self._minimize_result_to_result(
|
334
|
+
result=result, initial_params=initial_params
|
335
|
+
)
|
336
|
+
|
337
|
+
def submit_minimize(
|
338
|
+
self,
|
339
|
+
cost_function: Union[Hamiltonian, QmodExpressionCreator],
|
340
|
+
initial_params: ExecutionParams,
|
341
|
+
max_iteration: int,
|
342
|
+
quantile: float = 1.0,
|
343
|
+
) -> ExecutionJob:
|
344
|
+
"""
|
345
|
+
Initiates an execution job with the `minimize` primitive.
|
346
|
+
|
347
|
+
This is a non-blocking version of `minimize`: it gets the same parameters and initiates the same execution job, but instead
|
348
|
+
of waiting for the result, it returns the job object immediately.
|
349
|
+
|
350
|
+
Args:
|
351
|
+
cost_function: The cost function to minimize. It can be one of the following:
|
352
|
+
- A quantum cost function defined by a Hamiltonian.
|
353
|
+
- A classical cost function represented as a callable that returns a Qmod expression.
|
354
|
+
The callable should accept `QVar`s as arguments and use names matching the Model outputs.
|
355
|
+
initial_params: The initial parameters for the minimization.
|
356
|
+
Only Models with exactly one execution parameter are supported. This parameter must be of type
|
357
|
+
`CReal` or `CArray`. The dictionary must contain a single key-value pair, where:
|
358
|
+
- The key is the name of the parameter.
|
359
|
+
- The value is either a float or a list of floats.
|
360
|
+
max_iteration: The maximum number of iterations for the minimization.
|
361
|
+
quantile: The quantile to use for cost estimation.
|
362
|
+
|
363
|
+
Returns:
|
364
|
+
The execution job.
|
365
|
+
"""
|
366
|
+
if len(initial_params) != 1:
|
367
|
+
raise ClassiqValueError(
|
368
|
+
"The initial parameters must be a dictionary with a single key-value pair."
|
369
|
+
)
|
370
|
+
|
371
|
+
_cost_function: Union[PauliOperator, Expression]
|
372
|
+
_initial_params = parse_params(initial_params)
|
373
|
+
minimize: Union[MinimizeQuantumCostInput, MinimizeClassicalCostInput]
|
374
|
+
if callable(cost_function):
|
375
|
+
circuit_output_types = self.program.model.circuit_output_types
|
376
|
+
_cost_function = self._create_qmod_expression(
|
377
|
+
circuit_output_types, cost_function
|
378
|
+
)
|
379
|
+
minimize = MinimizeClassicalCostInput(
|
380
|
+
cost_function=_cost_function,
|
381
|
+
initial_params=_initial_params,
|
382
|
+
max_iteration=max_iteration,
|
383
|
+
quantile=quantile,
|
384
|
+
)
|
385
|
+
else:
|
386
|
+
_cost_function = self._hamiltonian_to_pauli_operator(cost_function)
|
387
|
+
minimize = MinimizeQuantumCostInput(
|
388
|
+
cost_function=_cost_function,
|
389
|
+
initial_params=_initial_params,
|
390
|
+
max_iteration=max_iteration,
|
391
|
+
quantile=quantile,
|
392
|
+
)
|
393
|
+
|
394
|
+
execution_primitives_input = PrimitivesInput(minimize=minimize)
|
395
|
+
return self._execute(execution_primitives_input)
|
396
|
+
|
302
397
|
def estimate_cost(
|
303
398
|
self,
|
304
399
|
cost_func: Callable[[ParsedState], float],
|
@@ -319,23 +414,8 @@ class ExecutionSession:
|
|
319
414
|
See Also:
|
320
415
|
sample
|
321
416
|
"""
|
322
|
-
if quantile < 0 or quantile > 1:
|
323
|
-
raise ClassiqValueError("'quantile' must be between 0 and 1")
|
324
417
|
res = self.sample(parameters)
|
325
|
-
|
326
|
-
counts = np.array(res.parsed_counts)
|
327
|
-
costs = np.vectorize(lambda sample: cost_func(sample.state))(counts)
|
328
|
-
shots = np.vectorize(lambda sample: sample.shots)(counts)
|
329
|
-
|
330
|
-
if quantile == 1:
|
331
|
-
return float(np.average(costs, weights=shots))
|
332
|
-
costs = np.repeat(costs, shots)
|
333
|
-
sort_idx = costs.argsort()
|
334
|
-
sort_idx = sort_idx[: int(quantile * len(costs))]
|
335
|
-
costs = costs[sort_idx]
|
336
|
-
if costs.size == 0:
|
337
|
-
return np.nan
|
338
|
-
return float(np.average(costs))
|
418
|
+
return estimate_cost(cost_func, res.parsed_counts, quantile=quantile)
|
339
419
|
|
340
420
|
def set_measured_state_filter(
|
341
421
|
self,
|
@@ -387,3 +467,69 @@ class ExecutionSession:
|
|
387
467
|
)
|
388
468
|
|
389
469
|
self.program.model.register_filter_bitstrings[output_name] = legal_bitstrings
|
470
|
+
|
471
|
+
@staticmethod
|
472
|
+
def _hamiltonian_to_pauli_operator(hamiltonian: Hamiltonian) -> PauliOperator:
|
473
|
+
pauli_list: PydanticPauliList
|
474
|
+
# FIXME: Remove compatibility (CLS-2912)
|
475
|
+
if isinstance(hamiltonian, list): # type:ignore[unreachable]
|
476
|
+
pauli_list = [ # type:ignore[unreachable]
|
477
|
+
(
|
478
|
+
pauli_integers_to_str(elem.pauli),
|
479
|
+
cast(complex, elem.coefficient),
|
480
|
+
)
|
481
|
+
for elem in ExecutionSession._hamiltonian_to_pauli_terms(hamiltonian)
|
482
|
+
]
|
483
|
+
return PauliOperator(pauli_list=pauli_list)
|
484
|
+
pauli_list = []
|
485
|
+
for term in cast(list, hamiltonian.terms):
|
486
|
+
paulis = ["I"] * cast(int, hamiltonian.num_qubits)
|
487
|
+
for indexed_pauli in term.paulis:
|
488
|
+
paulis[len(paulis) - indexed_pauli.index - 1] = indexed_pauli.pauli.name
|
489
|
+
pauli_list.append(("".join(paulis), term.coefficient))
|
490
|
+
return PauliOperator(pauli_list=pauli_list)
|
491
|
+
|
492
|
+
@staticmethod
|
493
|
+
def _create_qmod_expression(
|
494
|
+
circuit_output_types: RegisterQuantumTypeDict,
|
495
|
+
qmod_expression_creator: QmodExpressionCreator,
|
496
|
+
) -> Expression:
|
497
|
+
symbolic_output = {
|
498
|
+
name: create_qvar_from_quantum_type(reg.quantum_types, name)
|
499
|
+
for name, reg in circuit_output_types.items()
|
500
|
+
}
|
501
|
+
for name in inspect.signature(qmod_expression_creator).parameters.keys():
|
502
|
+
if name not in symbolic_output:
|
503
|
+
raise ClassiqValueError(
|
504
|
+
f"The provided QVar: {name} does not match the model outputs: {tuple(circuit_output_types.keys())}. "
|
505
|
+
)
|
506
|
+
|
507
|
+
qmod_expression = qmod_expression_creator(**symbolic_output)
|
508
|
+
return Expression(expr=str(qmod_expression))
|
509
|
+
|
510
|
+
@staticmethod
|
511
|
+
def _hamiltonian_to_pauli_terms(hamiltonian: list) -> list[PauliTerm]:
|
512
|
+
if isinstance(hamiltonian[0], PauliTerm):
|
513
|
+
return cast(list[PauliTerm], hamiltonian)
|
514
|
+
else:
|
515
|
+
return _pauli_dict_to_pauli_terms(cast(list[QmodPyStruct], hamiltonian))
|
516
|
+
|
517
|
+
@staticmethod
|
518
|
+
def _minimize_result_to_result(
|
519
|
+
result: TaggedMinimizeResult, initial_params: ExecutionParams
|
520
|
+
) -> list[tuple[float, ExecutionParams]]:
|
521
|
+
param_name = next(iter(initial_params.keys()))
|
522
|
+
param_value = initial_params[param_name]
|
523
|
+
return [
|
524
|
+
(
|
525
|
+
res.expectation_value,
|
526
|
+
{
|
527
|
+
param_name: (
|
528
|
+
res.parameters[0]
|
529
|
+
if isinstance(param_value, (float, int))
|
530
|
+
else res.parameters
|
531
|
+
)
|
532
|
+
},
|
533
|
+
)
|
534
|
+
for res in result.value
|
535
|
+
]
|
classiq/execution/jobs.py
CHANGED
@@ -10,7 +10,10 @@ from classiq.interface.exceptions import (
|
|
10
10
|
ClassiqError,
|
11
11
|
)
|
12
12
|
from classiq.interface.executor.execution_request import ExecutionJobDetails, JobCost
|
13
|
-
from classiq.interface.executor.execution_result import
|
13
|
+
from classiq.interface.executor.execution_result import (
|
14
|
+
ResultsCollection,
|
15
|
+
TaggedMinimizeResult,
|
16
|
+
)
|
14
17
|
from classiq.interface.executor.result import (
|
15
18
|
EstimationResult,
|
16
19
|
EstimationResults,
|
@@ -244,6 +247,28 @@ class ExecutionJob:
|
|
244
247
|
|
245
248
|
raise ClassiqExecutionResultError("batch_estimate")
|
246
249
|
|
250
|
+
def get_minimization_result(
|
251
|
+
self, _http_client: Optional[httpx.AsyncClient] = None
|
252
|
+
) -> TaggedMinimizeResult:
|
253
|
+
"""
|
254
|
+
Returns the job's result as a single minimization result after validation. If the result is not yet available, waits for it.
|
255
|
+
|
256
|
+
Returns:
|
257
|
+
The minimization result of the execution job.
|
258
|
+
|
259
|
+
Raises:
|
260
|
+
ClassiqExecutionResultError: In case the result does not contain a single minimization result.
|
261
|
+
ClassiqAPIError: In case the job has failed.
|
262
|
+
"""
|
263
|
+
results = self.result(_http_client=_http_client)
|
264
|
+
if len(results) != 1:
|
265
|
+
raise ClassiqExecutionResultError("minimization")
|
266
|
+
|
267
|
+
result = results[0]
|
268
|
+
if isinstance(result, TaggedMinimizeResult):
|
269
|
+
return result
|
270
|
+
raise ClassiqExecutionResultError("minimization")
|
271
|
+
|
247
272
|
async def poll_async(
|
248
273
|
self,
|
249
274
|
timeout_sec: Optional[float] = None,
|
classiq/execution/qnn.py
CHANGED
@@ -15,7 +15,7 @@ from classiq.interface.executor.quantum_code import Arguments, MultipleArguments
|
|
15
15
|
|
16
16
|
from classiq import QuantumProgram
|
17
17
|
from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import (
|
18
|
-
|
18
|
+
pauli_operator_to_sparse_hamiltonian,
|
19
19
|
)
|
20
20
|
from classiq.execution.execution_session import ExecutionSession
|
21
21
|
|
@@ -27,7 +27,7 @@ def _execute_qnn_estimate(
|
|
27
27
|
arguments: list[Arguments],
|
28
28
|
observable: PauliOperator,
|
29
29
|
) -> ResultsCollection:
|
30
|
-
hamiltonian =
|
30
|
+
hamiltonian = pauli_operator_to_sparse_hamiltonian(observable.pauli_list)
|
31
31
|
return [
|
32
32
|
TaggedEstimationResult(
|
33
33
|
name=DEFAULT_RESULT_NAME,
|
@@ -36,3 +36,42 @@ async def get_budget_async(
|
|
36
36
|
|
37
37
|
|
38
38
|
get_budget = syncify_function(get_budget_async)
|
39
|
+
|
40
|
+
|
41
|
+
async def set_budget_limit_async(
|
42
|
+
provider_vendor: ProviderVendor,
|
43
|
+
limit: float,
|
44
|
+
) -> UserBudgets:
|
45
|
+
provider = PROVIDER_MAPPER.get(provider_vendor, None)
|
46
|
+
if not provider:
|
47
|
+
raise ValueError(f"Unsupported provider: {provider_vendor}")
|
48
|
+
|
49
|
+
budget = get_budget(provider_vendor)
|
50
|
+
if budget is None:
|
51
|
+
raise ValueError(f"No budget found for provider: {provider_vendor}")
|
52
|
+
|
53
|
+
if limit <= 0:
|
54
|
+
raise ValueError("Budget limit must be greater than zero.")
|
55
|
+
|
56
|
+
if limit > budget.budgets[0].available_budget:
|
57
|
+
print( # noqa: T201
|
58
|
+
f"Budget limit {limit} exceeds available budget {budget.budgets[0].available_budget} for provider {provider_vendor}.\n"
|
59
|
+
"Setting budget limit to the maximum available budget."
|
60
|
+
)
|
61
|
+
budgets_list = await ApiWrapper().call_set_budget_limit(provider, limit)
|
62
|
+
return UserBudgets(budgets=[budgets_list])
|
63
|
+
|
64
|
+
|
65
|
+
set_budget_limit = syncify_function(set_budget_limit_async)
|
66
|
+
|
67
|
+
|
68
|
+
async def clear_budget_limit_async(provider_vendor: ProviderVendor) -> UserBudgets:
|
69
|
+
provider = PROVIDER_MAPPER.get(provider_vendor, None)
|
70
|
+
if not provider:
|
71
|
+
raise ValueError(f"Unsupported provider: {provider_vendor}")
|
72
|
+
|
73
|
+
budgets_list = await ApiWrapper().call_clear_budget_limit(provider)
|
74
|
+
return UserBudgets(budgets=[budgets_list])
|
75
|
+
|
76
|
+
|
77
|
+
clear_budget_limit = syncify_function(clear_budget_limit_async)
|
classiq/interface/_version.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
DEFAULT_DECIMAL_PRECISION = 4
|
@@ -1,9 +1,10 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Annotated, Literal, Optional, Union
|
2
2
|
|
3
3
|
from pydantic import BaseModel, Field
|
4
4
|
|
5
5
|
from classiq.interface.chemistry.operator import PauliOperator
|
6
6
|
from classiq.interface.executor.quantum_code import Arguments
|
7
|
+
from classiq.interface.generator.expressions.expression import Expression
|
7
8
|
from classiq.interface.helpers.custom_encoders import CUSTOM_ENCODERS
|
8
9
|
|
9
10
|
|
@@ -12,7 +13,34 @@ class EstimateInput(BaseModel, json_encoders=CUSTOM_ENCODERS):
|
|
12
13
|
parameters: list[Arguments]
|
13
14
|
|
14
15
|
|
16
|
+
class MinimizeCostInput(BaseModel, json_encoders=CUSTOM_ENCODERS):
|
17
|
+
initial_params: Arguments
|
18
|
+
max_iteration: int
|
19
|
+
quantile: float
|
20
|
+
|
21
|
+
|
22
|
+
class MinimizeClassicalCostInput(MinimizeCostInput):
|
23
|
+
cost_function: Expression
|
24
|
+
kind: Literal["MinimizeClassicalCostInput"] = Field(
|
25
|
+
default="MinimizeClassicalCostInput"
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
class MinimizeQuantumCostInput(MinimizeCostInput):
|
30
|
+
cost_function: PauliOperator
|
31
|
+
kind: Literal["MinimizeQuantumCostInput"] = Field(
|
32
|
+
default="MinimizeQuantumCostInput"
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
ConcreteMinimizeCostInput = Annotated[
|
37
|
+
Union[MinimizeQuantumCostInput, MinimizeClassicalCostInput],
|
38
|
+
Field(discriminator="kind"),
|
39
|
+
]
|
40
|
+
|
41
|
+
|
15
42
|
class PrimitivesInput(BaseModel, json_encoders=CUSTOM_ENCODERS):
|
16
43
|
sample: Optional[list[Arguments]] = Field(default=None)
|
17
44
|
estimate: Optional[EstimateInput] = Field(default=None)
|
45
|
+
minimize: Optional[ConcreteMinimizeCostInput] = Field(default=None)
|
18
46
|
random_seed: Optional[int] = Field(default=None)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from typing import Callable
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
|
5
|
+
from classiq.interface.exceptions import ClassiqValueError
|
6
|
+
from classiq.interface.executor.result import ParsedCounts, ParsedState
|
7
|
+
|
8
|
+
|
9
|
+
def estimate_cost(
|
10
|
+
cost_func: Callable[[ParsedState], float],
|
11
|
+
parsed_counts: ParsedCounts,
|
12
|
+
quantile: float = 1.0,
|
13
|
+
) -> float:
|
14
|
+
if quantile < 0 or quantile > 1:
|
15
|
+
raise ClassiqValueError("'quantile' must be between 0 and 1")
|
16
|
+
costs = np.fromiter((cost_func(sample.state) for sample in parsed_counts), float)
|
17
|
+
shots = np.fromiter((sample.shots for sample in parsed_counts), int)
|
18
|
+
|
19
|
+
if quantile == 1:
|
20
|
+
return float(np.average(costs, weights=shots))
|
21
|
+
return float(estimate_quantile_cost(costs, shots, quantile=quantile))
|
22
|
+
|
23
|
+
|
24
|
+
def estimate_quantile_cost(
|
25
|
+
costs: np.ndarray,
|
26
|
+
shots: np.ndarray,
|
27
|
+
quantile: float,
|
28
|
+
) -> np.floating:
|
29
|
+
repeated_costs = np.repeat(costs, shots)
|
30
|
+
sort_idx = repeated_costs.argsort()
|
31
|
+
cutoff_idx = sort_idx[: int(quantile * len(repeated_costs))]
|
32
|
+
sorted_costs = repeated_costs[cutoff_idx]
|
33
|
+
if sorted_costs.size == 0:
|
34
|
+
sorted_costs = repeated_costs[sort_idx[0]]
|
35
|
+
return np.average(sorted_costs)
|
@@ -25,6 +25,7 @@ class SavedResultValueType(StrEnum):
|
|
25
25
|
EstimationResult = "EstimationResult"
|
26
26
|
EstimationResults = "EstimationResults"
|
27
27
|
IQAEResult = "IQAEResult"
|
28
|
+
MinimizeResult = "MinimizeResult"
|
28
29
|
Unstructured = "Unstructured"
|
29
30
|
|
30
31
|
|
@@ -88,6 +89,17 @@ class TaggedUnstructured(BaseModel):
|
|
88
89
|
value: Any = None
|
89
90
|
|
90
91
|
|
92
|
+
class SingleMinimizeResult(BaseModel):
|
93
|
+
expectation_value: float
|
94
|
+
parameters: list[float]
|
95
|
+
|
96
|
+
|
97
|
+
class TaggedMinimizeResult(BaseModel):
|
98
|
+
value_type: Literal[SavedResultValueType.MinimizeResult]
|
99
|
+
name: str
|
100
|
+
value: list[SingleMinimizeResult]
|
101
|
+
|
102
|
+
|
91
103
|
SavedResult = Annotated[
|
92
104
|
Union[
|
93
105
|
TaggedInteger,
|
@@ -100,6 +112,7 @@ SavedResult = Annotated[
|
|
100
112
|
TaggedEstimationResults,
|
101
113
|
TaggedIQAEResult,
|
102
114
|
TaggedUnstructured,
|
115
|
+
TaggedMinimizeResult,
|
103
116
|
],
|
104
117
|
Field(..., discriminator="value_type"),
|
105
118
|
]
|
@@ -10,11 +10,16 @@ from typing import (
|
|
10
10
|
Union,
|
11
11
|
)
|
12
12
|
|
13
|
+
import pandas as pd
|
13
14
|
import pydantic
|
14
15
|
from pydantic import BaseModel
|
15
16
|
from typing_extensions import Self, TypeAlias
|
16
17
|
|
17
|
-
from classiq.interface.exceptions import
|
18
|
+
from classiq.interface.exceptions import (
|
19
|
+
ClassiqError,
|
20
|
+
ClassiqInternalError,
|
21
|
+
ClassiqValueError,
|
22
|
+
)
|
18
23
|
from classiq.interface.executor.quantum_code import OutputQubitsMap, Qubits
|
19
24
|
from classiq.interface.generator.arith import number_utils
|
20
25
|
from classiq.interface.generator.complex_type import Complex
|
@@ -22,6 +27,7 @@ from classiq.interface.generator.functions.classical_type import QmodPyObject
|
|
22
27
|
from classiq.interface.helpers.custom_pydantic_types import PydanticNonNegIntTuple
|
23
28
|
from classiq.interface.helpers.datastructures import get_sdk_compatible_python_object
|
24
29
|
from classiq.interface.helpers.versioned_model import VersionedModel
|
30
|
+
from classiq.interface.model.quantum_type import RegisterQuantumTypeDict
|
25
31
|
|
26
32
|
_ILLEGAL_QUBIT_ERROR_MSG: str = "Illegal qubit index requested"
|
27
33
|
_REPEATED_QUBIT_ERROR_MSG: str = "Requested a qubit more than once"
|
@@ -36,6 +42,11 @@ ParsedStates: TypeAlias = Mapping[State, ParsedState]
|
|
36
42
|
Counts: TypeAlias = dict[State, MeasuredShots]
|
37
43
|
StateVector: TypeAlias = Optional[dict[str, Complex]]
|
38
44
|
|
45
|
+
BITSTRING = "bitstring"
|
46
|
+
PROBABILITY = "probability"
|
47
|
+
AMPLITUDE = "amplitude"
|
48
|
+
COUNT = "count"
|
49
|
+
|
39
50
|
if TYPE_CHECKING:
|
40
51
|
DotAccessParsedState = Mapping[Name, Any]
|
41
52
|
else:
|
@@ -150,6 +161,36 @@ def prepare_parsed_state_vector(
|
|
150
161
|
return sorted(parsed_state_vector, key=lambda k: abs(k.amplitude), reverse=True)
|
151
162
|
|
152
163
|
|
164
|
+
def _flatten_columns(df: pd.DataFrame, columns_to_flatten: list[str]) -> pd.DataFrame:
|
165
|
+
if len(df.columns) == 0:
|
166
|
+
return df
|
167
|
+
|
168
|
+
flattened_data = {}
|
169
|
+
|
170
|
+
for col in columns_to_flatten:
|
171
|
+
if col not in df.columns:
|
172
|
+
continue
|
173
|
+
|
174
|
+
flat_df = pd.json_normalize(df[col]).add_prefix(f"{col}.")
|
175
|
+
flat_df.index = df.index
|
176
|
+
flattened_data[col] = flat_df
|
177
|
+
|
178
|
+
new_df_columns = []
|
179
|
+
dfs_to_concat = []
|
180
|
+
|
181
|
+
for col_name in df.columns:
|
182
|
+
if col_name in flattened_data:
|
183
|
+
new_df_columns.extend(flattened_data[col_name].columns)
|
184
|
+
dfs_to_concat.append(flattened_data[col_name])
|
185
|
+
elif col_name not in columns_to_flatten:
|
186
|
+
new_df_columns.append(col_name)
|
187
|
+
dfs_to_concat.append(df[[col_name]])
|
188
|
+
|
189
|
+
final_df = pd.concat(dfs_to_concat, axis=1)
|
190
|
+
valid_final_columns = [c for c in new_df_columns if c in final_df.columns]
|
191
|
+
return final_df[valid_final_columns]
|
192
|
+
|
193
|
+
|
153
194
|
class ExecutionDetails(BaseModel, QmodPyObject):
|
154
195
|
vendor_format_result: dict[str, Any] = pydantic.Field(
|
155
196
|
..., description="Result in proprietary vendor format"
|
@@ -192,6 +233,8 @@ class ExecutionDetails(BaseModel, QmodPyObject):
|
|
192
233
|
default=None, description="The total number of shots the circuit was executed"
|
193
234
|
)
|
194
235
|
|
236
|
+
output_type_map: RegisterQuantumTypeDict = pydantic.Field(default_factory=dict)
|
237
|
+
|
195
238
|
@pydantic.field_validator("counts", mode="after")
|
196
239
|
@classmethod
|
197
240
|
def _clean_spaces_from_counts_keys(cls, v: Counts) -> Counts:
|
@@ -302,6 +345,78 @@ class ExecutionDetails(BaseModel, QmodPyObject):
|
|
302
345
|
)[::-1]
|
303
346
|
return number_utils.binary_to_float_or_int(bin_rep=register_binary_string)
|
304
347
|
|
348
|
+
def _counts_df(self) -> pd.DataFrame:
|
349
|
+
data: dict[str, Any] = defaultdict(list)
|
350
|
+
|
351
|
+
for bitstring, count in self.counts.items():
|
352
|
+
data[BITSTRING].append(bitstring)
|
353
|
+
data[COUNT].append(count)
|
354
|
+
if self.probabilities:
|
355
|
+
data[PROBABILITY].append(self.probabilities[bitstring])
|
356
|
+
elif self.num_shots:
|
357
|
+
data[PROBABILITY].append(count / self.num_shots)
|
358
|
+
|
359
|
+
for name, value in self.parsed_states[bitstring].items():
|
360
|
+
data[name].append(value)
|
361
|
+
|
362
|
+
final_columns = [COUNT, PROBABILITY, BITSTRING]
|
363
|
+
columns = [
|
364
|
+
col for col in data.keys() if col not in final_columns
|
365
|
+
] + final_columns
|
366
|
+
|
367
|
+
return pd.DataFrame(data, columns=columns)
|
368
|
+
|
369
|
+
def _state_vector_df(self) -> pd.DataFrame:
|
370
|
+
data: dict[str, Any] = defaultdict(list)
|
371
|
+
|
372
|
+
if not self.state_vector:
|
373
|
+
raise ClassiqInternalError("No state vector")
|
374
|
+
if not self.parsed_state_vector_states:
|
375
|
+
raise ClassiqInternalError("No parsed state vector states")
|
376
|
+
|
377
|
+
for bitstring, amplitude in self.state_vector.items():
|
378
|
+
data[BITSTRING].append(bitstring)
|
379
|
+
data[AMPLITUDE].append(amplitude)
|
380
|
+
data[PROBABILITY].append(abs(amplitude) ** 2)
|
381
|
+
for name, value in self.parsed_state_vector_states[bitstring].items():
|
382
|
+
data[name].append(value)
|
383
|
+
|
384
|
+
final_columns = [AMPLITUDE, PROBABILITY, BITSTRING]
|
385
|
+
columns = [
|
386
|
+
col for col in data.keys() if col not in final_columns
|
387
|
+
] + final_columns
|
388
|
+
|
389
|
+
return pd.DataFrame(data, columns=columns)
|
390
|
+
|
391
|
+
@functools.cached_property
|
392
|
+
def dataframe(self) -> pd.DataFrame:
|
393
|
+
reserved_words = frozenset([BITSTRING, PROBABILITY, COUNT, AMPLITUDE])
|
394
|
+
_invalid_output_names = reserved_words.intersection(
|
395
|
+
self.output_qubits_map.keys()
|
396
|
+
)
|
397
|
+
if _invalid_output_names:
|
398
|
+
raise ClassiqValueError(f"Invalid output names: {_invalid_output_names}")
|
399
|
+
|
400
|
+
if self.state_vector:
|
401
|
+
df = self._state_vector_df()
|
402
|
+
else:
|
403
|
+
df = self._counts_df()
|
404
|
+
|
405
|
+
df.sort_values(
|
406
|
+
by=[AMPLITUDE if self.state_vector else COUNT, BITSTRING],
|
407
|
+
inplace=True,
|
408
|
+
ascending=[False, True],
|
409
|
+
ignore_index=True,
|
410
|
+
)
|
411
|
+
|
412
|
+
outputs_to_flatten = [
|
413
|
+
output
|
414
|
+
for output, type in self.output_type_map.items()
|
415
|
+
if type.quantum_types.kind == "struct_instance"
|
416
|
+
]
|
417
|
+
df = _flatten_columns(df, outputs_to_flatten)
|
418
|
+
return df
|
419
|
+
|
305
420
|
|
306
421
|
class MultipleExecutionDetails(VersionedModel):
|
307
422
|
details: list[ExecutionDetails]
|