Helios and Selene

Helios is Quantinuum’s third generation quantum processessing unit, announced in November of 2025. Up-to-date technical specifications can be found in the Helios product data sheet. You can read more about Helios in the introductory blog, benchmark paper, or documentation.

With Helios comes a new software stack, including Selene, Quantinuum’s emulation framework for hybrid (quantum/classical) programs. Selene is a very useful simulation tool that can be used both locally or remotely via Nexus.

This tutorial shows the user how to instantiate and submit jobs to Helios and Selene. As submission to these new backends is the focus, we extract a Hamiltonian for H2 in the STO-3G basis from the express module. Then, we build a trial state with a Trotter ansatz. The ground state energy of this system is then estimated with the Pauli averaging protocol.

from inquanto.express import load_h5
from inquanto.operators import QubitOperator,QubitOperatorList
from inquanto.states import QubitState
from inquanto.ansatzes import TrotterAnsatz
from inquanto.computables import ExpectationValue
from inquanto.protocols import PauliAveraging
from pytket.partition import PauliPartitionStrat


h2 = load_h5("h2_sto3g.h5", as_tuple=True)
hamiltonian = h2.hamiltonian_operator.qubit_encode()

exponents = QubitOperatorList(QubitOperator("Y0 X1 X2 X3", 1j), -0.111)
reference = QubitState([1, 1, 0, 0])
ansatz = TrotterAnsatz(exponents, reference)

energy = ExpectationValue(ansatz, hamiltonian)

Submitting to Helios

With the system defined, we need to choose a backend. First we demonstrate how to access and use a Helios backend with qnexus.

Once the user has logged in with their Quantinuum Nexus credentials, we create a reference to a Nexus project. Then we define a backend configuration, specifying a number of backend parameters. In this case, we use a HeliosConfig.

The HeliosConfig must be instantiated with a system_name and max_cost. Here we specify Helios-1E, the Helios emulator, and a max_cost of 500. The max_cost argument sets a maximum threshold for the number of HQCs that a job can consume. This is required as Helios - and all related backends - can execute dynamic, hybrid programs with classical control flow. A consequence of this is that the cost of running a particular program is not statically known at runtime. If a job exceeds this threshold, it is terminated and any results of completed measurements are returned to the user.

Finally, the number of qubits, n_qubits must be defined in a HeliosEmulatorConfig as an upper bound that ensures the emulator runs efficiently.

from qnexus.client import auth, projects
from qnexus.models import HeliosConfig, HeliosEmulatorConfig, StatevectorSimulator

# Login with Quantinuum Nexus credentials.
# auth.login()

project_ref = projects.get_or_create(name="InQuanto Documentation")

helios_config = HeliosConfig(
    system_name="Helios-1E",
    max_cost = 500,
    emulator_config=HeliosEmulatorConfig(
        n_qubits=10, 
    )
)

With the backend defined, we can then instantiate, build, and compile a PauliAveraging protocol, remembering to include the reference to a Nexus project through the project_ref.

It should be noted that the compilation step for these backends (Helios hardware and emulators, including Selene) is not quite the same as for other backends. There are no pre-defined compilation passes that can be applied by invoking the optimization_level argument when calling compile_circuits(). However, users can still define and apply their own sets of passes with the preoptimize_passes and compiler_passes arguments as outlined in the circuit compilation tutorial.


helios_protocol = PauliAveraging(
   backend=helios_config,
   shots_per_circuit=1000,
   pauli_partition_strategy=PauliPartitionStrat.CommutingSets,
   project_ref=project_ref
)
helios_protocol.build_from({}, energy)
helios_protocol.compile_circuits()

Here, we use an asynchronous workflow to first launch and then retrieve the job from the remote emulator once it has completed.

handles = helios_protocol.launch()
helios_protocol.retrieve(handles)
print("Number of circuits: ", helios_protocol.n_circuit)

energy.evaluate(helios_protocol.get_evaluator())
Number of circuits:  2
-1.1284691520823564

Selene

Now, we turn to Selene. More specifically, we take advantage of the QuEST state vector simulator. In this case, we are using a local backend and as such the definition of a Nexus project reference or qnexus config are not needed and we can simply instantiate Selene via InQuanto’s hugr module. This module is named after the hierarchical unified graph representation (HUGR), an intermediate representation of quantum circuits developed for the Quantinuum ecosystem. However, users do not need to interact with or manipulate so called “HUGRs” to use Selene or Helios with InQuanto.

Whilst here we demonstrate running Selene locally here, you can also submit Selene jobs to Nexus using with a qnexus.SeleneConfig.

from inquanto.hugr.hugr_utils import SELENE

backend = SELENE.QUEST
selene_protocol = PauliAveraging(
   backend=backend,
   shots_per_circuit=1000,
   pauli_partition_strategy=PauliPartitionStrat.CommutingSets,
)
selene_protocol.build_from({}, energy)
selene_protocol.compile_circuits()

Instead of launching and retrieving this job asynchronously, we use run().

selene_protocol.run()
print("Number of circuits: ", selene_protocol.n_circuit)

energy.evaluate(selene_protocol.get_evaluator())
Number of circuits:  2
-1.1357335665698658