Skip to main content

Create function (Code-level)

This guide explains how developers create a function by defining its runtime environment, implementing execution logic, managing source code, and deploying function versions.

The focus of this guide is function implementation and lifecycle at the code level, rather than UI operations or job execution.

1. Function Concept

A function represents a reusable execution unit that encapsulates quantum or hybrid computation logic.

Each function consists of:

  • A runtime template (framework and execution environment)
  • Source code implementing the function logic
  • One or more versions
  • Deployment artifacts that make the function available for invocation

2. Defining Function Metadata

When creating a function, the following metadata is required.

Function Name

The function name uniquely identifies the function within a project.

Rules:

  • Allowed characters: lowercase letters (a–z), numbers (0–9), and hyphens (-)
  • Spaces are not allowed
  • Minimum length: 2 characters
  • Names must be unique within the project

If the name does not meet these rules or already exists, function creation will fail.

Description (Optional)

An optional description can be provided to explain the function’s purpose and intended use.

3. Selecting a Runtime Template

A runtime template defines:

  • The execution environment
  • The supported quantum or classical framework
  • The initial project structure

Examples of supported templates include:

  • Qiskit
  • Braket
  • CUDA Quantum
  • PennyLane
  • Other supported environments

Each template provides:

  • A default handler.py implementation
  • A compatible runtime environment
  • Framework-specific helper libraries

Important: The runtime template is immutable. Once the function is created, the template cannot be changed.

4. Function Versions

Each function is initialized with a default version (for example, v1).

A version represents:

  • A specific snapshot of the function’s source code
  • A deployable unit that can be independently invoked

Multiple versions allow developers to:

  • Iterate safely on implementation
  • Test new logic without affecting stable versions
  • Roll back to previous implementations if needed

5. Implementing Function Logic

Core Entry Point: handler.py

The handler.py file contains the primary execution logic of the function.

The function must expose a processing(invocation_input) method.

def processing(invocation_input):
# implement computation logic here
return result

invocation_input

  • Represents the input payload provided at invocation time
  • Typically contains parameters required for computation or circuit construction

Typical responsibilities of handler.py include:

  • Building quantum circuits
  • Preparing execution logic
  • Validating inputs
  • Returning execution results

Dependency Management: requirements.txt

The requirements.txt file specifies all Python dependencies required by the function.

  • Libraries listed here are installed automatically during deployment
  • Missing dependencies may cause deployment or runtime failures

Example:

qiskit
numpy

Processing Function Return Contract

The processing() function is responsible for initializing and constructing the computation logic only.

It MUST NOT:

  • Execute the circuit on a backend
  • Submit jobs directly
  • Bind to a specific execution device
  • Perform SDK-level execution calls

The execution engine handles backend selection, device configuration, and job submission. The function must return a standardized object depending on the selected runtime template.

SDK-Specific Return Requirements

  1. Amazon Braket (braket)
  • Required Return Object: braket.circuits.circuit.Circuit
  • Description: The function must return a Braket Circuit object with applied quantum gates. You can explicitly define measurement instructions, or rely on the engine's default execution flow to measure all qubits.
  • Example:
from braket.circuits.circuit import Circuit

def processing(invocation_input):
# Initialize Braket circuit
circuit = Circuit()

# Apply quantum gates
circuit.h(0).cnot(0, 1)

# MANDATORY: Return the Circuit object
return circuit
  1. IBM Qiskit (qiskit)
  • Required Return Object: qiskit.QuantumCircuit
  • Description: The function must return a Qiskit QuantumCircuit object. This circuit should typically include classical registers and measure instructions if the problem requires retrieving probability distributions or state counts.
  • Example:
from qiskit import QuantumCircuit

def processing(invocation_input):
# Initialize circuit with 2 qubits and 2 classical bits
qc = QuantumCircuit(2, 2)

# Add gates and measurements
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])

# MANDATORY: Return the QuantumCircuit object
return qc

  1. PennyLane (pennylane)
  • Required Return Object: A Python function containing PennyLane operations and returning PennyLane measurement objects (e.g., qml.probs(), qml.expval(), qml.sample()).
  • Description: Unlike other SDKs that return an instantiated object, PennyLane's standard design within this architecture requires returning the unexecuted function itself. It should not be wrapped with the @qml.qnode decorator yet, as the QNode requires Device configuration, which is handled by the downstream execution engine.
  • Example:
import pennylane as qml

def processing(invocation_input):
# Define the circuit as a nested function
def simple_circuit():

# Apply gate
qml.Hadamard(wires=0)

# Return standard PennyLane measurements
return qml.probs(wires=0), qml.probs()

# MANDATORY: Return the FUNCTION itself (do not call it)
return simple_circuit

  1. Rigetti PyQuil (pyquil)
  • Required Return Object: pyquil.quil.Program
  • Description: You must initialize a Program object, declare memory regions/registers (like ro) to store classical readouts, apply quantum gates, and add MEASURE instructions.
  • Example:
from pyquil import Program
from pyquil.gates import X, MEASURE

def processing(invocation_input):
p = Program()

# Declare classical memory for readout
ro = p.declare('ro', 'BIT', 1)

# Apply gates
p += X(0)
p += X(1)
p += X(2)

# Add measurement instruction
p += MEASURE(0, ro[0])

# MANDATORY: Return the Program object
return p

  1. D-Wave (pyqubo / dimod)
  • Required Return Object: dimod.BinaryQuadraticModel (BQM)
  • Description: For D-Wave (Quantum Annealing), instead of a gate-based circuit, a mathematical model is constructed. The processing function uses PyQUBO (or equivalent) to define the objective function (Hamiltonian) and constraints. It must be compiled into a BQM object before being returned. *Example:
from pyqubo import Array, Constraint

def processing(invocation_input):
# Initialize binary variables
x = Array.create('x', shape=3, vartype='BINARY')

# Objective function and constraint
H = (x[0] + x[1] - 1)**2 + (x[1] + x[2] - 1)**2
constraint = Constraint(x[0] * x[1], label='constraint')
model = H + constraint

# Compile to BQM
bqm = model.compile().to_bqm()

# MANDATORY: Return the BQM object
return bqm

  1. Microsoft Q# (qsharp)
  • Required Return Object: Compiled Q# Operation (output of qsharp.compile())
  • Description: The function must initialize the Q# environment, specifying the project root and target profile. It should then compile the target Q# operation using the provided invocation_input and return the compiled object. This allows the execution engine to seamlessly execute the Q# code on the desired backend.
  • Example:
import os
import qsharp

def processing(invocation_input):
# Optional: Debugging the execution path
print("Current working directory:", os.getcwd())

# Initialize the Q# workspace and target profile
qsharp.init(project_root='./function', target_profile=qsharp.TargetProfile.Base)

# MANDATORY: Return the compiled Q# operation
return qsharp.compile(f"Main.RandomNBits({invocation_input})")

Updated Return Object Summary Table

SDK NameStandard ImportRequired Return Type
Braketfrom braket.circuits.circuit import CircuitCircuit
Qiskitfrom qiskit import QuantumCircuitQuantumCircuit
PennyLaneimport pennylane as qmlfunction (callable containing qml operations)
PyQuilfrom pyquil import ProgramProgram
D-Wavefrom pyqubo import Arraydimod.BinaryQuadraticModel (BQM)
Q#import qsharpCompiled Q# Operation (via qsharp.compile())

Result Transformation: post_processing(job_result)

After the execution engine completes a job, the platform generates a job_result object containing the raw execution output.

Each function must define a post_processing(job_result) method inside handler.py. This method is responsible for transforming the raw execution result into the final response returned to the caller.

Function Signature

def post_processing(job_result):
return transformed_output

Input

  • job_result: The completed execution result returned by the execution engine.

Output

  • Any serializable Python object (e.g., dict, list, string, number).

If post_processing is not defined, function deployment will fail.

Example 1 – Calculate Measurement Probabilities

Use case: Convert raw measurement counts into a normalized probability distribution.

def post_processing(job_result):
counts = job_result.get_counts()
total_shots = sum(counts.values())

probabilities = {
state: count / total_shots
for state, count in counts.items()
}

return probabilities

Example 2 – Filter Specific Quantum State

Use case: Extract the success rate of a target quantum state.

def post_processing(job_result):
counts = job_result.get_counts()
target_state = "11"

success_rate = counts.get(target_state, 0) / sum(counts.values())

return f"{target_state}: {success_rate * 100:.2f}%"

Example 3 – Return Full Execution Result

Use case: Preserve the complete raw execution output for debugging or advanced analysis.

def post_processing(job_result):
return job_result

6. Modifying and Extending Source Code

Developers may customize the function implementation in several ways:

  • Edit the default template code
  • Add additional Python modules (*.py)
  • Upload custom source files to replace or extend existing logic

The platform supports both:

  • Direct code editing via the built-in editor
  • Uploading local Python files

All source code changes are tracked through function versioning.

7. Saving Changes and Version Creation

Whenever source code is modified:

  • Saving the changes creates a new function version
  • Each version preserves a complete snapshot of the source code
  • Versions can be selected independently for deployment

This mechanism enables controlled iteration and experimentation.

8. Deploying a Function Version

Deployment packages a specific function version into an executable artifact.

During deployment:

  • Dependencies from requirements.txt are installed
  • The source code is validated
  • The function version becomes available for invocation

After successful deployment:

  • The function can be invoked via SDK or API
  • The function version can be used to create execution jobs

9. Key Constraints and Best Practices

  • Runtime templates cannot be changed after function creation
  • Each deployment corresponds to a specific function version
  • Declare all required dependencies before deployment
  • Use versioning to isolate experimental changes from stable releases

10. Next Steps

After deploying a function, developers typically proceed to:

  • Invoke the function programmatically using SDK or API
  • Create and manage execution jobs based on the deployed function
  • Monitor execution results and logs

Refer to the following guides for more details: