classiq 0.36.0__py3-none-any.whl → 0.37.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 (91) hide show
  1. classiq/__init__.py +1 -0
  2. classiq/_internals/api_wrapper.py +24 -6
  3. classiq/_internals/authentication/device.py +6 -3
  4. classiq/_internals/authentication/token_manager.py +21 -5
  5. classiq/_internals/client.py +7 -2
  6. classiq/_internals/config.py +12 -0
  7. classiq/_internals/host_checker.py +1 -1
  8. classiq/_internals/jobs.py +3 -1
  9. classiq/_internals/type_validation.py +3 -6
  10. classiq/analyzer/analyzer.py +1 -0
  11. classiq/analyzer/rb.py +3 -5
  12. classiq/applications_model_constructors/chemistry_model_constructor.py +42 -67
  13. classiq/applications_model_constructors/grover_model_constructor.py +27 -18
  14. classiq/exceptions.py +5 -0
  15. classiq/execution/jobs.py +13 -4
  16. classiq/executor.py +3 -2
  17. classiq/interface/_version.py +1 -1
  18. classiq/interface/analyzer/analysis_params.py +0 -6
  19. classiq/interface/analyzer/result.py +0 -4
  20. classiq/interface/backend/backend_preferences.py +2 -2
  21. classiq/interface/backend/quantum_backend_providers.py +1 -1
  22. classiq/interface/execution/resource_estimator.py +7 -0
  23. classiq/interface/execution/result.py +5 -0
  24. classiq/interface/executor/register_initialization.py +3 -1
  25. classiq/interface/executor/vqe_result.py +1 -0
  26. classiq/interface/generator/ansatz_library.py +3 -3
  27. classiq/interface/generator/arith/argument_utils.py +4 -4
  28. classiq/interface/generator/arith/arithmetic.py +4 -2
  29. classiq/interface/generator/arith/arithmetic_arg_type_validator.py +11 -5
  30. classiq/interface/generator/arith/arithmetic_expression_parser.py +8 -7
  31. classiq/interface/generator/arith/arithmetic_operations.py +7 -0
  32. classiq/interface/generator/arith/arithmetic_param_getters.py +97 -16
  33. classiq/interface/generator/arith/arithmetic_result_builder.py +13 -3
  34. classiq/interface/generator/arith/binary_ops.py +8 -10
  35. classiq/interface/generator/arith/extremum_operations.py +2 -2
  36. classiq/interface/generator/arith/number_utils.py +20 -23
  37. classiq/interface/generator/arith/register_user_input.py +3 -1
  38. classiq/interface/generator/arith/unary_ops.py +9 -13
  39. classiq/interface/generator/expressions/atomic_expression_functions.py +2 -0
  40. classiq/interface/generator/expressions/expression.py +7 -2
  41. classiq/interface/generator/expressions/qmod_qnum_proxy.py +22 -0
  42. classiq/interface/generator/expressions/qmod_sized_proxy.py +2 -12
  43. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/atomic_quantum_functions.py +63 -3
  44. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/std_lib_functions.py +143 -17
  45. classiq/interface/generator/functions/core_lib_declarations/quantum_operators.py +41 -16
  46. classiq/interface/generator/functions/native_function_definition.py +3 -3
  47. classiq/interface/generator/model/constraints.py +3 -3
  48. classiq/interface/generator/model/preferences/preferences.py +13 -9
  49. classiq/interface/generator/noise_properties.py +5 -5
  50. classiq/interface/generator/qpe.py +5 -5
  51. classiq/interface/generator/quantum_function_call.py +5 -3
  52. classiq/interface/generator/randomized_benchmarking.py +5 -3
  53. classiq/interface/generator/visitor.py +1 -2
  54. classiq/interface/hardware.py +1 -1
  55. classiq/interface/helpers/custom_pydantic_types.py +6 -0
  56. classiq/interface/model/{modular_addition_operation.py → inplace_binary_operation.py} +16 -2
  57. classiq/interface/model/native_function_definition.py +2 -24
  58. classiq/interface/model/operator_synthesis_data.py +6 -0
  59. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +8 -4
  60. classiq/interface/model/quantum_expressions/arithmetic_operation.py +9 -5
  61. classiq/interface/model/quantum_expressions/control_state.py +38 -0
  62. classiq/interface/model/quantum_expressions/quantum_expression.py +21 -11
  63. classiq/interface/model/quantum_function_call.py +81 -6
  64. classiq/interface/model/quantum_function_declaration.py +3 -3
  65. classiq/interface/model/quantum_if_operation.py +95 -0
  66. classiq/interface/model/resolvers/function_call_resolver.py +1 -1
  67. classiq/interface/model/validations/handles_validator.py +42 -15
  68. classiq/interface/server/routes.py +10 -6
  69. classiq/model/function_handler.pyi +86 -86
  70. classiq/model/model.py +1 -0
  71. classiq/qmod/__init__.py +6 -1
  72. classiq/qmod/builtins/__init__.py +13 -1
  73. classiq/qmod/builtins/classical_execution_primitives.py +109 -0
  74. classiq/qmod/builtins/classical_functions.py +68 -0
  75. classiq/qmod/builtins/functions.py +88 -18
  76. classiq/qmod/builtins/operations.py +60 -35
  77. classiq/qmod/classical_function.py +40 -0
  78. classiq/qmod/declaration_inferrer.py +5 -2
  79. classiq/qmod/qmod_variable.py +17 -10
  80. classiq/qmod/quantum_callable.py +24 -3
  81. classiq/qmod/quantum_expandable.py +131 -21
  82. classiq/qmod/quantum_function.py +12 -2
  83. classiq/qmod/symbolic.py +182 -107
  84. classiq/qmod/symbolic_expr.py +11 -10
  85. classiq/qmod/symbolic_type.py +8 -0
  86. classiq/quantum_functions/decorators.py +2 -4
  87. classiq/quantum_functions/function_library.py +1 -0
  88. {classiq-0.36.0.dist-info → classiq-0.37.0.dist-info}/METADATA +1 -1
  89. {classiq-0.36.0.dist-info → classiq-0.37.0.dist-info}/RECORD +90 -82
  90. classiq/interface/model/local_variable_declaration.py +0 -7
  91. {classiq-0.36.0.dist-info → classiq-0.37.0.dist-info}/WHEEL +0 -0
@@ -6,12 +6,26 @@ from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
6
6
  from classiq.interface.model.handle_binding import HandleBinding
7
7
  from classiq.interface.model.quantum_statement import QuantumOperation
8
8
 
9
+ from classiq._internals.enum_utils import StrEnum
9
10
  from classiq.exceptions import ClassiqValueError
10
11
 
11
12
 
12
- class ModularAdditionOperation(QuantumOperation):
13
+ class BinaryOperation(StrEnum):
14
+ Addition = "addition"
15
+ Xor = "xor"
16
+
17
+ @property
18
+ def internal_function(self) -> str:
19
+ return {
20
+ BinaryOperation.Addition: "modular_add",
21
+ BinaryOperation.Xor: "integer_xor",
22
+ }[self]
23
+
24
+
25
+ class InplaceBinaryOperation(QuantumOperation):
13
26
  target: HandleBinding
14
27
  value: HandleBinding
28
+ operation: BinaryOperation
15
29
 
16
30
  @property
17
31
  def wiring_inouts(self) -> Mapping[str, HandleBinding]:
@@ -20,5 +34,5 @@ class ModularAdditionOperation(QuantumOperation):
20
34
  @pydantic.validator("target", "value")
21
35
  def validate_handle(cls, handle: HandleBinding) -> HandleBinding:
22
36
  if not handle.is_bindable():
23
- raise ClassiqValueError(f"Cannot bind '{handle}'") # noqa: B907
37
+ raise ClassiqValueError(f"Cannot bind '{handle!r}'")
24
38
  return handle
@@ -1,9 +1,7 @@
1
- from typing import Any, Dict, List, Optional
1
+ from typing import List
2
2
 
3
3
  import pydantic
4
4
 
5
- from classiq.interface.model.local_variable_declaration import LocalVariableDeclaration
6
- from classiq.interface.model.port_declaration import PortDeclaration
7
5
  from classiq.interface.model.quantum_function_call import ConcreteQuantumStatement
8
6
  from classiq.interface.model.quantum_function_declaration import (
9
7
  QuantumFunctionDeclaration,
@@ -28,12 +26,8 @@ class NativeFunctionDefinition(QuantumFunctionDeclaration):
28
26
  default_factory=list, description="List of function calls to perform."
29
27
  )
30
28
 
31
- local_handles: List[LocalVariableDeclaration] = pydantic.Field(
32
- default_factory=list, description="List of local handles."
33
- )
34
-
35
29
  def validate_body(self) -> None:
36
- handle_validator = HandleValidator(self.port_declarations, self.local_handles)
30
+ handle_validator = HandleValidator(self.port_declarations)
37
31
 
38
32
  for statement in self.body:
39
33
  if isinstance(statement, VariableDeclarationStatement):
@@ -42,19 +36,3 @@ class NativeFunctionDefinition(QuantumFunctionDeclaration):
42
36
  handle_validator.handle_call(statement)
43
37
 
44
38
  handle_validator.report_errored_handles(ClassiqValueError)
45
-
46
- @pydantic.validator("local_handles")
47
- def validate_local_handles(
48
- cls, local_handles: List[LocalVariableDeclaration], values: Dict[str, Any]
49
- ) -> List[LocalVariableDeclaration]:
50
- ports: Optional[Dict[str, PortDeclaration]] = values.get("port_declarations")
51
- if ports is None:
52
- return local_handles
53
-
54
- intersection = {handle.name for handle in local_handles} & ports.keys()
55
- if intersection:
56
- raise ClassiqValueError(
57
- f"The names {intersection} are both local handles and ports"
58
- )
59
-
60
- return local_handles
@@ -40,3 +40,9 @@ class ControlOperatorSynthesisData(OperatorSynthesisData):
40
40
  return {
41
41
  "control_states": [self._ctrl_state],
42
42
  }
43
+
44
+
45
+ class ComputeOperatorSynthesisData(OperatorSynthesisData):
46
+ @property
47
+ def call_kwargs(self) -> Dict[str, Any]:
48
+ return {"should_control": False}
@@ -12,7 +12,7 @@ from classiq.interface.model.handle_binding import (
12
12
  SubscriptHandleBinding,
13
13
  )
14
14
  from classiq.interface.model.quantum_expressions.quantum_expression import (
15
- QuantumExpressionOperation,
15
+ QuantumAssignmentOperation,
16
16
  )
17
17
  from classiq.interface.model.quantum_type import QuantumBit, QuantumNumeric, QuantumType
18
18
 
@@ -27,7 +27,7 @@ VAR_DOMAIN_ILLEGAL = (
27
27
  )
28
28
 
29
29
 
30
- class AmplitudeLoadingOperation(QuantumExpressionOperation):
30
+ class AmplitudeLoadingOperation(QuantumAssignmentOperation):
31
31
  _result_type: QuantumType = pydantic.PrivateAttr(default_factory=QuantumBit)
32
32
 
33
33
  @property
@@ -40,7 +40,11 @@ class AmplitudeLoadingOperation(QuantumExpressionOperation):
40
40
  return dict()
41
41
  return {AMPLITUDE_IO_NAME: self.var_handles[0]}
42
42
 
43
- def initialize_var_types(self, var_types: Dict[str, QuantumType]) -> None:
43
+ def initialize_var_types(
44
+ self,
45
+ var_types: Dict[str, QuantumType],
46
+ machine_precision: int,
47
+ ) -> None:
44
48
  if len(var_types) != 1:
45
49
  raise ClassiqValueError(MULTI_VARS_UNSUPPORTED_ERROR)
46
50
  var_type = var_types[self.var_handles[0].name]
@@ -49,7 +53,7 @@ class AmplitudeLoadingOperation(QuantumExpressionOperation):
49
53
  and var_type.fraction_digits == var_type.size_in_bits
50
54
  ):
51
55
  raise ClassiqValueError(VAR_DOMAIN_ILLEGAL)
52
- super().initialize_var_types(var_types)
56
+ super().initialize_var_types(var_types, machine_precision)
53
57
 
54
58
  @classmethod
55
59
  def result_name(cls) -> str:
@@ -12,20 +12,24 @@ from classiq.interface.model.handle_binding import (
12
12
  SubscriptHandleBinding,
13
13
  )
14
14
  from classiq.interface.model.quantum_expressions.quantum_expression import (
15
- QuantumExpressionOperation,
15
+ QuantumAssignmentOperation,
16
16
  )
17
17
  from classiq.interface.model.quantum_type import QuantumType
18
18
 
19
19
 
20
- class ArithmeticOperation(QuantumExpressionOperation):
20
+ class ArithmeticOperation(QuantumAssignmentOperation):
21
21
  inplace_result: bool = pydantic.Field(
22
22
  description="Determines whether the result variable is initialized",
23
23
  )
24
24
 
25
- def initialize_var_types(self, var_types: Dict[str, QuantumType]) -> None:
26
- super().initialize_var_types(var_types)
25
+ def initialize_var_types(
26
+ self,
27
+ var_types: Dict[str, QuantumType],
28
+ machine_precision: int,
29
+ ) -> None:
30
+ super().initialize_var_types(var_types, machine_precision)
27
31
  self._result_type = compute_arithmetic_result_type(
28
- self.expression.expr, var_types
32
+ self.expression.expr, var_types, machine_precision
29
33
  )
30
34
 
31
35
  @property
@@ -0,0 +1,38 @@
1
+ from classiq.exceptions import ClassiqValueError
2
+
3
+
4
+ def min_unsigned_bit_length(number: int) -> int:
5
+ if number < 0:
6
+ raise ClassiqValueError(
7
+ f"Quantum register is not signed but control value " # noqa: B907
8
+ f"'{number}' is negative"
9
+ )
10
+ return 1 if number == 0 else number.bit_length()
11
+
12
+
13
+ def min_signed_bit_length(number: int) -> int:
14
+ pos_val = abs(number)
15
+ is_whole = pos_val & (pos_val - 1) == 0
16
+ if number <= 0 and is_whole:
17
+ return min_unsigned_bit_length(pos_val)
18
+ return min_unsigned_bit_length(pos_val) + 1
19
+
20
+
21
+ def min_bit_length(number: int, is_signed: bool) -> int:
22
+ return (
23
+ min_signed_bit_length(number) if is_signed else min_unsigned_bit_length(number)
24
+ )
25
+
26
+
27
+ def to_twos_complement(value: int, bits: int, is_signed: bool) -> str:
28
+ required_bits = min_bit_length(value, is_signed)
29
+ if bits < required_bits:
30
+ raise ClassiqValueError(
31
+ f"Cannot express '{value}' using {bits} bits: " # noqa: B907
32
+ f"at least {required_bits} bits are required"
33
+ )
34
+ if value >= 0:
35
+ return bin(value)[2:].zfill(bits)[::-1]
36
+ mask = (1 << bits) - 1
37
+ value = (abs(value) ^ mask) + 1
38
+ return bin(value)[:1:-1].rjust(bits, "1")
@@ -4,6 +4,9 @@ from typing import Dict, List, Mapping, Optional, Set, Union
4
4
 
5
5
  import pydantic
6
6
 
7
+ from classiq.interface.generator.arith.arithmetic_expression_validator import (
8
+ DEFAULT_SUPPORTED_FUNC_NAMES,
9
+ )
7
10
  from classiq.interface.generator.expressions.expression import Expression
8
11
  from classiq.interface.generator.expressions.sympy_supported_expressions import (
9
12
  SYMPY_SUPPORTED_EXPRESSIONS,
@@ -25,25 +28,19 @@ class VarRefCollector(ast.NodeVisitor):
25
28
  def generic_visit(self, node: ast.AST) -> None:
26
29
  if isinstance(node, ast.Name) and node.id not in set(
27
30
  SYMPY_SUPPORTED_EXPRESSIONS
28
- ):
31
+ ) | set(DEFAULT_SUPPORTED_FUNC_NAMES):
29
32
  self.var_names.add(node.id)
30
33
  super().generic_visit(node)
31
34
 
32
35
 
33
36
  class QuantumExpressionOperation(QuantumOperation):
34
37
  expression: Expression = pydantic.Field()
35
- result_var: HandleBinding = pydantic.Field(
36
- description="The variable storing the expression result"
37
- )
38
38
  _var_handles: List[HandleBinding] = pydantic.PrivateAttr(
39
39
  default_factory=list,
40
40
  )
41
41
  _var_types: Dict[str, QuantumType] = pydantic.PrivateAttr(
42
42
  default_factory=dict,
43
43
  )
44
- _result_type: Optional[QuantumType] = pydantic.PrivateAttr(
45
- default=None,
46
- )
47
44
 
48
45
  @property
49
46
  def var_handles(self) -> List[HandleBinding]:
@@ -56,7 +53,11 @@ class QuantumExpressionOperation(QuantumOperation):
56
53
  def var_types(self) -> Dict[str, QuantumType]:
57
54
  return self._var_types
58
55
 
59
- def initialize_var_types(self, var_types: Dict[str, QuantumType]) -> None:
56
+ def initialize_var_types(
57
+ self,
58
+ var_types: Dict[str, QuantumType],
59
+ machine_precision: int,
60
+ ) -> None:
60
61
  assert len(var_types) == len(self.var_handles)
61
62
  self._var_types = var_types
62
63
 
@@ -68,15 +69,24 @@ class QuantumExpressionOperation(QuantumOperation):
68
69
  ]:
69
70
  return nameables_to_dict(self.var_handles)
70
71
 
71
- @property
72
- def wiring_outputs(self) -> Mapping[str, HandleBinding]:
73
- return {self.result_name(): self.result_var}
72
+
73
+ class QuantumAssignmentOperation(QuantumExpressionOperation):
74
+ result_var: HandleBinding = pydantic.Field(
75
+ description="The variable storing the expression result"
76
+ )
77
+ _result_type: Optional[QuantumType] = pydantic.PrivateAttr(
78
+ default=None,
79
+ )
74
80
 
75
81
  @property
76
82
  def result_type(self) -> QuantumType:
77
83
  assert self._result_type is not None
78
84
  return self._result_type
79
85
 
86
+ @property
87
+ def wiring_outputs(self) -> Mapping[str, HandleBinding]:
88
+ return {self.result_name(): self.result_var}
89
+
80
90
  @classmethod
81
91
  @abc.abstractmethod
82
92
  def result_name(cls) -> str:
@@ -23,7 +23,7 @@ from classiq.interface.model.handle_binding import (
23
23
  SlicedHandleBinding,
24
24
  SubscriptHandleBinding,
25
25
  )
26
- from classiq.interface.model.modular_addition_operation import ModularAdditionOperation
26
+ from classiq.interface.model.inplace_binary_operation import InplaceBinaryOperation
27
27
  from classiq.interface.model.numeric_reinterpretation import (
28
28
  NumericReinterpretationOperation,
29
29
  )
@@ -38,6 +38,7 @@ from classiq.interface.model.quantum_function_declaration import (
38
38
  QuantumFunctionDeclaration,
39
39
  QuantumOperandDeclaration,
40
40
  )
41
+ from classiq.interface.model.quantum_if_operation import QuantumIfOperation
41
42
  from classiq.interface.model.quantum_statement import QuantumOperation
42
43
  from classiq.interface.model.validation_handle import get_unique_handle_names
43
44
  from classiq.interface.model.variable_declaration_statement import (
@@ -288,7 +289,8 @@ class QuantumFunctionCall(QuantumOperation):
288
289
 
289
290
  def resolve_function_decl(
290
291
  self,
291
- function_dict: Mapping[str, FunctionDeclaration],
292
+ function_dict: Mapping[str, QuantumFunctionDeclaration],
293
+ check_operands: bool,
292
294
  ) -> None:
293
295
  if self._func_decl is None:
294
296
  func_decl = function_dict.get(self.func_name)
@@ -312,6 +314,8 @@ class QuantumFunctionCall(QuantumOperation):
312
314
  set(self.func_decl.operand_declarations.keys()),
313
315
  self.func_name,
314
316
  )
317
+ if check_operands:
318
+ _check_operands_against_declaration(self, self.func_decl, function_dict)
315
319
 
316
320
  for name, op in self.operands.items():
317
321
  op_decl = self.func_decl.operand_declarations[name]
@@ -352,7 +356,8 @@ ConcreteQuantumStatement = Union[
352
356
  VariableDeclarationStatement,
353
357
  BindOperation,
354
358
  NumericReinterpretationOperation,
355
- ModularAdditionOperation,
359
+ InplaceBinaryOperation,
360
+ QuantumIfOperation,
356
361
  ]
357
362
 
358
363
 
@@ -402,6 +407,7 @@ QuantumCallable = Union[str, QuantumLambdaFunction]
402
407
  QuantumOperand = Union[QuantumCallable, List[QuantumCallable], LambdaListComprehension]
403
408
 
404
409
  QuantumFunctionCall.update_forward_refs()
410
+ QuantumIfOperation.update_forward_refs(QuantumOperand=QuantumOperand)
405
411
 
406
412
 
407
413
  def get_lambda_defs(operand: QuantumOperand) -> List[QuantumCallable]:
@@ -441,16 +447,85 @@ def _check_ports_against_declaration(
441
447
  )
442
448
 
443
449
 
450
+ def _check_operand_against_declaration(
451
+ call: QuantumFunctionCall,
452
+ operand_decl: QuantumOperandDeclaration,
453
+ operand_argument: QuantumOperand,
454
+ function_dict: Mapping[str, QuantumFunctionDeclaration],
455
+ in_list: bool = False,
456
+ ) -> None:
457
+ if isinstance(operand_argument, list):
458
+ if in_list:
459
+ raise ClassiqValueError(
460
+ f"{str(operand_argument)!r} argument to {call.func_decl.name!r} is not "
461
+ f"a valid operand. Nested operand lists are not permitted"
462
+ )
463
+ for arg in operand_argument:
464
+ _check_operand_against_declaration(
465
+ call, operand_decl, arg, function_dict, in_list=True
466
+ )
467
+ return
468
+ operand_arg_decl: QuantumFunctionDeclaration
469
+ if isinstance(operand_argument, str):
470
+ if operand_argument not in function_dict:
471
+ raise ClassiqValueError(
472
+ f"{operand_argument!r} argument to {call.func_decl.name!r} is not a "
473
+ f"registered function"
474
+ )
475
+ operand_arg_decl = function_dict[operand_argument]
476
+ elif isinstance(operand_argument, QuantumLambdaFunction):
477
+ if operand_argument.func_decl is None:
478
+ return
479
+ operand_arg_decl = operand_argument.func_decl
480
+ elif isinstance(operand_argument, LambdaListComprehension):
481
+ if operand_argument.func.func_decl is None:
482
+ return
483
+ operand_arg_decl = operand_argument.func.func_decl
484
+ else:
485
+ raise ClassiqValueError(
486
+ f"{str(operand_argument)!r} argument to {call.func_decl.name!r} is not a "
487
+ f"valid operand"
488
+ )
489
+ num_arg_parameters = len(operand_arg_decl.get_positional_arg_decls())
490
+ num_decl_parameters = len(operand_decl.get_positional_arg_decls())
491
+ if num_arg_parameters != num_decl_parameters:
492
+ raise ClassiqValueError(
493
+ f"Signature of argument {operand_argument!r} to {call.func_decl.name!r} "
494
+ f"does not match the signature of parameter {operand_decl.name!r}. "
495
+ f"{operand_decl.name!r} accepts {num_decl_parameters} parameters but "
496
+ f"{operand_argument!r} accepts {num_arg_parameters} parameters"
497
+ )
498
+
499
+
500
+ def _check_operands_against_declaration(
501
+ call: QuantumFunctionCall,
502
+ decl: QuantumFunctionDeclaration,
503
+ function_dict: Mapping[str, QuantumFunctionDeclaration],
504
+ ) -> None:
505
+ for operand_parameter, operand_argument in call.operands.items():
506
+ _check_operand_against_declaration(
507
+ call,
508
+ decl.operand_declarations[operand_parameter],
509
+ operand_argument,
510
+ function_dict,
511
+ )
512
+
513
+
444
514
  def _check_params_against_declaration(
445
515
  call_params: Set[str],
446
516
  param_decls: Set[str],
447
517
  callee_name: str,
448
518
  ) -> None:
449
519
  unknown_params = call_params - param_decls
450
- if unknown_params:
451
- raise ClassiqValueError(
452
- f"Unknown parameters {unknown_params} in call to {callee_name!r}."
520
+ if any(re.match(r"arg\d+", param) for param in unknown_params):
521
+ error_msg = (
522
+ f"Unsupported passing of named function {callee_name!r} as an operand."
523
+ "\nSuggestion: replace the named function with lambda function."
453
524
  )
525
+ else:
526
+ error_msg = f"Unknown parameters {unknown_params} in call to {callee_name!r}."
527
+ if unknown_params:
528
+ raise ClassiqValueError(error_msg)
454
529
 
455
530
  missing_params = param_decls - call_params
456
531
  if missing_params:
@@ -83,9 +83,9 @@ class QuantumFunctionDeclaration(FunctionDeclaration):
83
83
  default_factory=list
84
84
  )
85
85
 
86
- BUILTIN_FUNCTION_DECLARATIONS: ClassVar[
87
- Dict[str, "QuantumFunctionDeclaration"]
88
- ] = {}
86
+ BUILTIN_FUNCTION_DECLARATIONS: ClassVar[Dict[str, "QuantumFunctionDeclaration"]] = (
87
+ {}
88
+ )
89
89
 
90
90
  @property
91
91
  def input_set(self) -> Set[str]:
@@ -0,0 +1,95 @@
1
+ from typing import TYPE_CHECKING, Optional
2
+
3
+ import pydantic
4
+ from sympy import Equality
5
+ from sympy.core.numbers import Integer
6
+
7
+ from classiq.interface.generator.expressions.expression import Expression
8
+ from classiq.interface.generator.expressions.qmod_qnum_proxy import QmodQNumProxy
9
+ from classiq.interface.model.quantum_expressions.control_state import (
10
+ min_bit_length,
11
+ to_twos_complement,
12
+ )
13
+ from classiq.interface.model.quantum_expressions.quantum_expression import (
14
+ QuantumExpressionOperation,
15
+ )
16
+
17
+ from classiq.exceptions import ClassiqValueError
18
+
19
+ if TYPE_CHECKING:
20
+ from classiq.interface.model.quantum_function_call import QuantumOperand
21
+
22
+
23
+ QUANTUM_IF_INOUT_NAME = "ctrl"
24
+ QUANTUM_IF_CONDITION_ARG_ERROR_MESSAGE_FORMAT = (
25
+ "quantum_if condition must be of the form '<quantum-variable> == "
26
+ "<classical-integer-expression>', but condition's {}-hand side was {!r}"
27
+ )
28
+
29
+
30
+ class QuantumIfOperation(QuantumExpressionOperation):
31
+ then: "QuantumOperand"
32
+ _ctrl: Optional[QmodQNumProxy] = pydantic.PrivateAttr(
33
+ default=None,
34
+ )
35
+ _ctrl_val: Optional[int] = pydantic.PrivateAttr(
36
+ default=None,
37
+ )
38
+
39
+ @property
40
+ def condition(self) -> Expression:
41
+ return self.expression
42
+
43
+ @property
44
+ def ctrl(self) -> QmodQNumProxy:
45
+ assert self._ctrl is not None
46
+ return self._ctrl
47
+
48
+ @property
49
+ def ctrl_val(self) -> int:
50
+ assert self._ctrl_val is not None
51
+ return self._ctrl_val
52
+
53
+ def resolve_condition(self) -> None:
54
+ condition = self.condition.value.value
55
+ if not isinstance(condition, Equality):
56
+ raise ClassiqValueError(
57
+ f"quantum_if condition must be an equality, was {str(condition)!r}"
58
+ )
59
+ ctrl, ctrl_val = condition.args
60
+ if isinstance(ctrl, Integer) and isinstance(ctrl_val, QmodQNumProxy):
61
+ ctrl, ctrl_val = ctrl_val, ctrl
62
+ if not isinstance(ctrl, QmodQNumProxy):
63
+ raise ClassiqValueError(
64
+ QUANTUM_IF_CONDITION_ARG_ERROR_MESSAGE_FORMAT.format("left", str(ctrl))
65
+ )
66
+ if not isinstance(ctrl_val, Integer):
67
+ raise ClassiqValueError(
68
+ QUANTUM_IF_CONDITION_ARG_ERROR_MESSAGE_FORMAT.format(
69
+ "right", str(ctrl_val)
70
+ )
71
+ )
72
+ self._ctrl, self._ctrl_val = ctrl, int(ctrl_val)
73
+
74
+ @property
75
+ def ctrl_state(self) -> str:
76
+ is_signed = self.ctrl.is_signed
77
+ fraction_places = self.ctrl.fraction_digits
78
+ ctrl_size = len(self.ctrl)
79
+ if not is_signed and self.ctrl_val < 0:
80
+ raise ClassiqValueError(
81
+ f"Variable {str(self.ctrl)!r} is not signed but control value "
82
+ f"{self.ctrl_val} is negative"
83
+ )
84
+ required_qubits = min_bit_length(self.ctrl_val, is_signed)
85
+ if ctrl_size < required_qubits:
86
+ raise ClassiqValueError(
87
+ f"Variable {str(self.ctrl)!r} has {ctrl_size} qubits but control value "
88
+ f"{str(self.ctrl_val)!r} requires at least {required_qubits} qubits"
89
+ )
90
+ if fraction_places != 0:
91
+ raise ClassiqValueError(
92
+ f"quantum-if on a non-integer quantum variable {str(self.ctrl)!r} is "
93
+ f"not supported at the moment"
94
+ )
95
+ return to_twos_complement(self.ctrl_val, ctrl_size, is_signed)
@@ -16,7 +16,7 @@ class FunctionCallResolver(Visitor):
16
16
  self._quantum_function_dict = quantum_function_dict
17
17
 
18
18
  def visit_QuantumFunctionCall(self, fc: QuantumFunctionCall) -> None:
19
- fc.resolve_function_decl(self._quantum_function_dict)
19
+ fc.resolve_function_decl(self._quantum_function_dict, check_operands=True)
20
20
  self.visit_BaseModel(fc)
21
21
 
22
22
  def visit_NativeFunctionDefinition(
@@ -1,13 +1,20 @@
1
- from typing import Dict, Iterable, Mapping, Union
1
+ from typing import Dict, Mapping, Set, Union
2
2
 
3
3
  from classiq.interface.generator.function_params import PortDirection
4
+ from classiq.interface.generator.functions.core_lib_declarations.quantum_operators import (
5
+ APPLY,
6
+ OPERAND_FIELD_NAME,
7
+ )
4
8
  from classiq.interface.model.handle_binding import (
5
9
  HandleBinding,
6
10
  SlicedHandleBinding,
7
11
  SubscriptHandleBinding,
8
12
  )
9
- from classiq.interface.model.local_variable_declaration import LocalVariableDeclaration
10
13
  from classiq.interface.model.port_declaration import PortDeclaration
14
+ from classiq.interface.model.quantum_function_call import (
15
+ QuantumFunctionCall,
16
+ QuantumLambdaFunction,
17
+ )
11
18
  from classiq.interface.model.quantum_statement import QuantumOperation
12
19
  from classiq.interface.model.validation_handle import HandleState, ValidationHandle
13
20
  from classiq.interface.model.validations.handle_validation_base import (
@@ -17,23 +24,21 @@ from classiq.interface.model.variable_declaration_statement import (
17
24
  VariableDeclarationStatement,
18
25
  )
19
26
 
27
+ from classiq.exceptions import ClassiqValueError
28
+
20
29
 
21
30
  def _initialize_handles_to_state(
22
31
  port_declarations: Mapping[str, PortDeclaration],
23
- local_handles: Iterable[LocalVariableDeclaration],
24
32
  ) -> Dict[str, ValidationHandle]:
25
33
  handles_to_state: Dict[str, ValidationHandle] = dict()
26
34
 
27
35
  for port_decl in port_declarations.values():
28
36
  handles_to_state[port_decl.name] = ValidationHandle(
29
- initial_state=HandleState.INITIALIZED
30
- if port_decl.direction.includes_port_direction(PortDirection.Input)
31
- else HandleState.UNINITIALIZED
32
- )
33
-
34
- for local_handle in local_handles:
35
- handles_to_state[local_handle.name] = ValidationHandle(
36
- initial_state=HandleState.UNINITIALIZED
37
+ initial_state=(
38
+ HandleState.INITIALIZED
39
+ if port_decl.direction.includes_port_direction(PortDirection.Input)
40
+ else HandleState.UNINITIALIZED
41
+ )
37
42
  )
38
43
 
39
44
  return handles_to_state
@@ -43,19 +48,18 @@ class HandleValidator(HandleValidationBase):
43
48
  def __init__(
44
49
  self,
45
50
  port_declarations: Mapping[str, PortDeclaration],
46
- local_handles: Iterable[LocalVariableDeclaration],
47
51
  ) -> None:
48
52
  super().__init__(port_declarations)
49
53
  self._port_declarations = port_declarations.values()
50
- self._handles_to_state = _initialize_handles_to_state(
51
- port_declarations, local_handles
52
- )
54
+ self._handles_to_state = _initialize_handles_to_state(port_declarations)
53
55
 
54
56
  @property
55
57
  def _validation_handles_state(self) -> Mapping[str, ValidationHandle]:
56
58
  return self._handles_to_state
57
59
 
58
60
  def handle_call(self, call: QuantumOperation) -> None:
61
+ if isinstance(call, QuantumFunctionCall) and call.function == APPLY.name:
62
+ self._handle_apply(call)
59
63
  self._handle_inputs(call.wiring_inputs)
60
64
  self._handle_outputs(call.wiring_outputs)
61
65
  self._handle_inouts(call.wiring_inouts)
@@ -125,3 +129,26 @@ class HandleValidator(HandleValidationBase):
125
129
  self._handles_to_state[handle].append_error(
126
130
  f"Invalid use of inout handle {handle!r}, used both in slice or subscript and whole"
127
131
  )
132
+
133
+ def _handle_apply(self, call: QuantumFunctionCall) -> None:
134
+ operand = call.operands[OPERAND_FIELD_NAME]
135
+ if not isinstance(operand, QuantumLambdaFunction):
136
+ return
137
+ local_variables: Set[str] = set()
138
+ output_capturing_variables: Set[str] = set()
139
+ for statement in operand.body:
140
+ if isinstance(statement, VariableDeclarationStatement):
141
+ local_variables.add(statement.name)
142
+ elif isinstance(statement, QuantumOperation):
143
+ for handle in statement.wiring_outputs.values():
144
+ if (
145
+ handle.name in local_variables
146
+ or handle.name in output_capturing_variables
147
+ ):
148
+ continue
149
+ output_capturing_variables.add(handle.name)
150
+ self._handles_to_state[handle.name].initialize()
151
+ else:
152
+ raise ClassiqValueError(
153
+ f"Unknown statement type {type(statement).__name__}"
154
+ )
@@ -4,9 +4,8 @@ LEGACY_EXECUTE_PREFIX = "/execute"
4
4
  EXECUTION_PREFIX = "/execution"
5
5
  CONVERSION_PREFIX = "/conversion"
6
6
 
7
- SYNTHESIS_SERVICE_PREFIX = "/synthesis/v1"
8
-
9
- EXECUTION_SERVICE_PREFIX = "/execution/v1"
7
+ EXECUTION_NON_VERSIONED_PREFIX = "/execution/v1"
8
+ SYNTHESIS_NON_VERSIONED_PREFIX = "/synthesis/v1"
10
9
 
11
10
  ANALYZER_CIRCUIT_PAGE = "circuit"
12
11
  DEFAULT_IDE_FE_APP = "https://platform.classiq.io/"
@@ -51,10 +50,11 @@ TASK_PREDICT_SUFFIX = TASKS_SUFFIX + "/predict"
51
50
  TASK_RB_SUFFIX = TASKS_SUFFIX + RB
52
51
  TASKS_GENERATE_FULL_PATH = TASKS_GENERATE_SUFFIX
53
52
 
54
-
55
53
  EXECUTION_JOBS_SUFFIX = "/jobs"
56
54
  EXECUTION_JOBS_FULL_PATH = EXECUTION_PREFIX + EXECUTION_JOBS_SUFFIX
57
- EXECUTION_SERVICE_JOBS_FULL_PATH = EXECUTION_SERVICE_PREFIX + EXECUTION_JOBS_SUFFIX
55
+ EXECUTION_JOBS_NON_VERSIONED_FULL_PATH = (
56
+ EXECUTION_NON_VERSIONED_PREFIX + EXECUTION_JOBS_SUFFIX
57
+ )
58
58
  EXECUTE_QUANTUM_PROGRAM_FULL_PATH = LEGACY_EXECUTE_PREFIX + QUANTUM_PROGRAM_SUFFIX
59
59
  EXECUTE_ESTIMATE_FULL_PATH = LEGACY_EXECUTE_PREFIX + ESTIMATE_SUFFIX
60
60
 
@@ -62,8 +62,12 @@ ANALYZER_FULL_PATH = ANALYZER_PREFIX + TASKS_SUFFIX
62
62
  ANALYZER_RB_FULL_PATH = ANALYZER_PREFIX + TASK_RB_SUFFIX
63
63
  GENERATE_RESOURCE_ESTIMATOR_REPORT = "/resource_estimator_report"
64
64
 
65
+ TASKS_SOLVE_EXACT_SUFFIX = "/tasks/solve_exact"
66
+
65
67
  GENERATE_HAMILTONIAN_SUFFIX = "/generate_hamiltonian"
66
- GENERATE_HAMILTONIAN_FULL_PATH = SYNTHESIS_SERVICE_PREFIX + GENERATE_HAMILTONIAN_SUFFIX
68
+ GENERATE_HAMILTONIAN_FULL_PATH = (
69
+ SYNTHESIS_NON_VERSIONED_PREFIX + GENERATE_HAMILTONIAN_SUFFIX
70
+ )
67
71
 
68
72
  FINANCE_GENERATE_MODEL_PATH = MODEL_GENERATE_PREFIX + "/finance"
69
73