Skip to content

Dynamiqs

oqd_trical.backend.dynamiqs

DynamiqsBackend

Bases: BackendBase

Backend for running simulation of AtomicCircuit with Dynamiqs

Attributes:

Name Type Description
save_intermediate bool

Whether compiler saves the intermediate representation of the atomic circuit

approx_pass PassBase

Pass of approximations to apply to the system.

solver Literal[SESolver, MESolver]

Dynamiqs solver to use.

solver_options Dict[str, Any]

Dynamiqs solver options

intermediate AtomicEmulatorCircuit

Intermediate representation of the atomic circuit during compilation

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/base.py
class DynamiqsBackend(BackendBase):
    """Backend for running simulation of AtomicCircuit with Dynamiqs

    Attributes:
        save_intermediate (bool): Whether compiler saves the intermediate representation of the atomic circuit
        approx_pass (PassBase): Pass of approximations to apply to the system.
        solver (Literal["SESolver","MESolver"]): Dynamiqs solver to use.
        solver_options (Dict[str,Any]): Dynamiqs solver options
        intermediate (AtomicEmulatorCircuit): Intermediate representation of the atomic circuit during compilation
    """

    def __init__(
        self,
        save_intermediate=True,
        approx_pass=None,
        solver="SESolver",
        solver_options={},
    ):
        super().__init__()

        self.save_intermediate = save_intermediate
        self.intermediate = None
        self.approx_pass = approx_pass
        self.solver = solver
        self.solver_options = solver_options

    def compile(self, circuit, fock_cutoff, *, relabel=True):
        """
        Compiles a AtomicCircuit or AtomicEmulatorCircuit to a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

        Args:
            circuit (Union[AtomicCircuit,AtomicEmulatorCircuit]): circuit to be compiled.
            fock_cutoff (int, Dict[str, int]): Truncation for fock spaces.

        Returns:
            experiment (DynamiqsExperiment): Compiled [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].
            hilbert_space (Dict[str, int]): Hilbert space of the system.
        """
        assert isinstance(circuit, (AtomicCircuit, AtomicEmulatorCircuit))

        if isinstance(circuit, AtomicCircuit):
            canonicalize = canonicalize_atomic_circuit_factory()
            intermediate = canonicalize(circuit)
            conversion = Post(ConstructHamiltonian())
            intermediate = conversion(intermediate)
        else:
            intermediate = circuit

        intermediate = canonicalize_emulator_circuit_factory()(intermediate)

        if self.approx_pass:
            intermediate = Chain(
                self.approx_pass, canonicalize_emulator_circuit_factory()
            )(intermediate)

        get_hilbert_space = GetHilbertSpace()
        analysis = Post(get_hilbert_space)
        analysis(intermediate)

        if relabel:
            analysis(intermediate)
        else:
            analysis(circuit.system)

        hilbert_space = get_hilbert_space.hilbert_space
        _hilbert_space = hilbert_space.hilbert_space
        for k in _hilbert_space.keys():
            if k[0] == "P":
                if isinstance(fock_cutoff, int):
                    _hilbert_space[k] = set(range(fock_cutoff))
                else:
                    _hilbert_space[k] = set(range(fock_cutoff[k]))
        hilbert_space = HilbertSpace(hilbert_space=_hilbert_space)

        if any(map(lambda x: x is None, hilbert_space.hilbert_space.values())):
            raise "Hilbert space not fully specified."

        relabeller = Post(RelabelStates(hilbert_space.get_relabel_rules()))
        intermediate = relabeller(intermediate)

        if self.save_intermediate:
            self.intermediate = intermediate

        compiler_p3 = Post(DynamiqsCodeGeneration(hilbert_space=hilbert_space))
        experiment = compiler_p3(intermediate)

        return experiment, hilbert_space

    def run(self, experiment, hilbert_space, timestep, *, initial_state=None):
        """
        Runs a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

        Args:
            experiment (DynamiqsExperiment): [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment] to be executed.
            hilbert_space (Dict[str, int]): Hilbert space of the system.
            timestep (float): Timestep between tracked states of the evolution.

        Returns:
            result (Dict[str,Any]): Result of execution of [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].
        """
        vm = Pre(
            DynamiqsVM(
                hilbert_space=hilbert_space,
                timestep=timestep,
                solver=self.solver,
                solver_options=self.solver_options,
                initial_state=initial_state,
            )
        )

        vm(experiment)

        return vm.children[0].result

compile(circuit, fock_cutoff, *, relabel=True)

Compiles a AtomicCircuit or AtomicEmulatorCircuit to a DynamiqsExperiment.

Parameters:

Name Type Description Default
circuit Union[AtomicCircuit, AtomicEmulatorCircuit]

circuit to be compiled.

required
fock_cutoff (int, Dict[str, int])

Truncation for fock spaces.

required

Returns:

Name Type Description
experiment DynamiqsExperiment

Compiled DynamiqsExperiment.

hilbert_space Dict[str, int]

Hilbert space of the system.

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/base.py
def compile(self, circuit, fock_cutoff, *, relabel=True):
    """
    Compiles a AtomicCircuit or AtomicEmulatorCircuit to a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

    Args:
        circuit (Union[AtomicCircuit,AtomicEmulatorCircuit]): circuit to be compiled.
        fock_cutoff (int, Dict[str, int]): Truncation for fock spaces.

    Returns:
        experiment (DynamiqsExperiment): Compiled [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].
        hilbert_space (Dict[str, int]): Hilbert space of the system.
    """
    assert isinstance(circuit, (AtomicCircuit, AtomicEmulatorCircuit))

    if isinstance(circuit, AtomicCircuit):
        canonicalize = canonicalize_atomic_circuit_factory()
        intermediate = canonicalize(circuit)
        conversion = Post(ConstructHamiltonian())
        intermediate = conversion(intermediate)
    else:
        intermediate = circuit

    intermediate = canonicalize_emulator_circuit_factory()(intermediate)

    if self.approx_pass:
        intermediate = Chain(
            self.approx_pass, canonicalize_emulator_circuit_factory()
        )(intermediate)

    get_hilbert_space = GetHilbertSpace()
    analysis = Post(get_hilbert_space)
    analysis(intermediate)

    if relabel:
        analysis(intermediate)
    else:
        analysis(circuit.system)

    hilbert_space = get_hilbert_space.hilbert_space
    _hilbert_space = hilbert_space.hilbert_space
    for k in _hilbert_space.keys():
        if k[0] == "P":
            if isinstance(fock_cutoff, int):
                _hilbert_space[k] = set(range(fock_cutoff))
            else:
                _hilbert_space[k] = set(range(fock_cutoff[k]))
    hilbert_space = HilbertSpace(hilbert_space=_hilbert_space)

    if any(map(lambda x: x is None, hilbert_space.hilbert_space.values())):
        raise "Hilbert space not fully specified."

    relabeller = Post(RelabelStates(hilbert_space.get_relabel_rules()))
    intermediate = relabeller(intermediate)

    if self.save_intermediate:
        self.intermediate = intermediate

    compiler_p3 = Post(DynamiqsCodeGeneration(hilbert_space=hilbert_space))
    experiment = compiler_p3(intermediate)

    return experiment, hilbert_space

run(experiment, hilbert_space, timestep, *, initial_state=None)

Runs a DynamiqsExperiment.

Parameters:

Name Type Description Default
experiment DynamiqsExperiment

DynamiqsExperiment to be executed.

required
hilbert_space Dict[str, int]

Hilbert space of the system.

required
timestep float

Timestep between tracked states of the evolution.

required

Returns:

Name Type Description
result Dict[str, Any]

Result of execution of DynamiqsExperiment.

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/base.py
def run(self, experiment, hilbert_space, timestep, *, initial_state=None):
    """
    Runs a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

    Args:
        experiment (DynamiqsExperiment): [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment] to be executed.
        hilbert_space (Dict[str, int]): Hilbert space of the system.
        timestep (float): Timestep between tracked states of the evolution.

    Returns:
        result (Dict[str,Any]): Result of execution of [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].
    """
    vm = Pre(
        DynamiqsVM(
            hilbert_space=hilbert_space,
            timestep=timestep,
            solver=self.solver,
            solver_options=self.solver_options,
            initial_state=initial_state,
        )
    )

    vm(experiment)

    return vm.children[0].result

DynamiqsCodeGeneration

Bases: ConversionRule

Rule that converts an AtomicEmulatorCircuit to a DynamiqsExperiment

Attributes:

Name Type Description
hilbert_space Dict[str, int]

Hilbert space of the system.

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/codegen.py
class DynamiqsCodeGeneration(ConversionRule):
    """
    Rule that converts an [`AtomicEmulatorCircuit`][oqd_trical.light_matter.interface.emulator.AtomicEmulatorCircuit]
    to a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment]

    Attributes:
        hilbert_space (Dict[str, int]): Hilbert space of the system.
    """

    def __init__(self, hilbert_space: HilbertSpace):
        super().__init__()

        self.hilbert_space = hilbert_space

    def map_AtomicEmulatorCircuit(self, model, operands):
        return DynamiqsExperiment(
            frame=None
            if (
                isinstance(operands["frame"], PrunedOperator)
                or operands["frame"] is None
            )
            else dq.timecallable(operands["frame"]),
            sequence=operands["sequence"],
        )

    def map_AtomicEmulatorGate(self, model, operands):
        if isinstance(operands["hamiltonian"], PrunedOperator):
            return DynamiqsGate(hamiltonian=None, duration=operands["duration"])

        return DynamiqsGate(
            hamiltonian=dq.timecallable(operands["hamiltonian"]),
            duration=operands["duration"],
        )

    def map_Identity(self, model, operands):
        op = dq.eye(self.hilbert_space.size[model.subsystem])
        return lambda t: op

    def map_KetBra(self, model, operands):
        ket = dq.basis(self.hilbert_space.size[model.subsystem], model.ket)
        bra = dq.basis(self.hilbert_space.size[model.subsystem], model.bra).dag()
        op = ket @ bra

        if not isinstance(op, dq.QArray):
            op = dq.asqarray(op)
        return lambda t: op

    def map_Annihilation(self, model, operands):
        op = dq.destroy(self.hilbert_space.size[model.subsystem])
        return lambda t: op

    def map_Creation(self, model, operands):
        op = dq.create(self.hilbert_space.size[model.subsystem])
        return lambda t: op

    def map_Displacement(self, model, operands):
        return lambda t: dq.displace(
            self.hilbert_space.size[model.subsystem], operands["alpha"](t)
        )

    def map_OperatorMul(self, model, operands):
        return lambda t: operands["op1"](t) @ operands["op2"](t)

    def map_OperatorKron(self, model, operands):
        return lambda t: dq.tensor(operands["op1"](t), operands["op2"](t))

    def map_OperatorAdd(self, model, operands):
        return lambda t: operands["op1"](t) + operands["op2"](t)

    def map_OperatorScalarMul(self, model, operands):
        return lambda t: operands["coeff"](t) * operands["op"](t)

    def map_WaveCoefficient(self, model, operands):
        return lambda t: operands["amplitude"](t) * jnp.exp(
            1j * (operands["frequency"](t) * t + operands["phase"](t))
        )

    def map_CoefficientAdd(self, model, operands):
        return lambda t: operands["coeff1"](t) + operands["coeff2"](t)

    def map_CoefficientMul(self, model, operands):
        return lambda t: operands["coeff1"](t) * operands["coeff2"](t)

    def map_MathNum(self, model, operands):
        return lambda t: model.value

    def map_MathImag(self, model, operands):
        return lambda t: 1j

    def map_MathVar(self, model, operands):
        if model.name == "t":
            return lambda t: t

        raise ValueError(
            f"Unsupported variable {model.name}, only variable t is supported"
        )

    def map_MathFunc(self, model, operands):
        if getattr(math, model.func, None):
            return lambda t: getattr(jnp, model.func)(operands["expr"](t))

        if model.func == "heaviside":
            return lambda t: jnp.heaviside(operands["expr"](t), 1)

        if model.func == "conj":
            return lambda t: jnp.conj(operands["expr"](t))

        raise ValueError(f"Unsupported function {model.func}")

    def map_MathAdd(self, model, operands):
        return lambda t: operands["expr1"](t) + operands["expr2"](t)

    def map_MathSub(self, model, operands):
        return lambda t: operands["expr1"](t) - operands["expr2"](t)

    def map_MathMul(self, model, operands):
        return lambda t: operands["expr1"](t) * operands["expr2"](t)

    def map_MathDiv(self, model, operands):
        return lambda t: operands["expr1"](t) / operands["expr2"](t)

    def map_MathPow(self, model, operands):
        return lambda t: operands["expr1"](t) ** operands["expr2"](t)

DynamiqsVM

Bases: RewriteRule

Rule that executes a DynamiqsExperiment.

Attributes:

Name Type Description
hilbert_space Dict[str, int]

Hilbert space of the system.

timestep float

Timestep between tracked states of the evolution.

solver Literal[SESolver, MESolver]

Dynamiqs solver to use.

solver_options Dict[str, Any]

Dynamiqs solver options

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/vm.py
class DynamiqsVM(RewriteRule):
    """
    Rule that executes a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

    Attributes:
        hilbert_space (Dict[str, int]): Hilbert space of the system.
        timestep (float): Timestep between tracked states of the evolution.
        solver (Literal["SESolver","MESolver"]): Dynamiqs solver to use.
        solver_options (Dict[str,Any]): Dynamiqs solver options
    """

    def __init__(
        self,
        hilbert_space,
        timestep,
        *,
        initial_state=None,
        solver="SESolver",
        solver_options={},
    ):
        self.hilbert_space = hilbert_space
        self.timestep = timestep

        if initial_state:
            if initial_state.dims != tuple(self.hilbert_space.size.values()):
                raise ValueError("Initial state incompatible with Hilbert space")
            self.current_state = initial_state
        else:
            self.current_state = dq.tensor(
                *[
                    dq.basis(self.hilbert_space.size[k], 0)
                    for k in self.hilbert_space.size.keys()
                ]
            )

        self.states = [self.current_state]
        self.tspan = [0.0]

        self.solver = {
            "SESolver": sesolve,
            "MESolver": mesolve,
        }[solver]
        self.solver_options = solver_options

    @property
    def result(self):
        return dict(
            final_state=self.current_state,
            states=self.states,
            tspan=self.tspan,
            frame=self.frame,
            hilbert_space=self.hilbert_space,
        )

    def map_DynamiqsExperiment(self, model):
        self.frame = model.frame

    def map_DynamiqsGate(self, model):
        tspan = jnp.arange(0, model.duration, self.timestep)

        if tspan[-1] != model.duration:
            tspan = jnp.append(tspan, model.duration)

        tspan = tspan + self.tspan[-1]

        empty_hamiltonian = model.hamiltonian is None

        if empty_hamiltonian:
            self.tspan.extend(list(tspan[1:] + self.tspan[-1]))
            self.states.extend([self.current_state] * (len(tspan) - 1))
            return

        res = self.solver(
            model.hamiltonian,
            self.current_state,
            tspan,
            solver=self.solver_options["solver"]
            if "solver" in self.solver_options.keys()
            else dq.solver.Tsit5(),
        )

        self.current_state = res.final_state

        self.tspan.extend(list(tspan[1:]))
        self.states.extend(list(res.states[1:]))

base

DynamiqsBackend

Bases: BackendBase

Backend for running simulation of AtomicCircuit with Dynamiqs

Attributes:

Name Type Description
save_intermediate bool

Whether compiler saves the intermediate representation of the atomic circuit

approx_pass PassBase

Pass of approximations to apply to the system.

solver Literal[SESolver, MESolver]

Dynamiqs solver to use.

solver_options Dict[str, Any]

Dynamiqs solver options

intermediate AtomicEmulatorCircuit

Intermediate representation of the atomic circuit during compilation

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/base.py
class DynamiqsBackend(BackendBase):
    """Backend for running simulation of AtomicCircuit with Dynamiqs

    Attributes:
        save_intermediate (bool): Whether compiler saves the intermediate representation of the atomic circuit
        approx_pass (PassBase): Pass of approximations to apply to the system.
        solver (Literal["SESolver","MESolver"]): Dynamiqs solver to use.
        solver_options (Dict[str,Any]): Dynamiqs solver options
        intermediate (AtomicEmulatorCircuit): Intermediate representation of the atomic circuit during compilation
    """

    def __init__(
        self,
        save_intermediate=True,
        approx_pass=None,
        solver="SESolver",
        solver_options={},
    ):
        super().__init__()

        self.save_intermediate = save_intermediate
        self.intermediate = None
        self.approx_pass = approx_pass
        self.solver = solver
        self.solver_options = solver_options

    def compile(self, circuit, fock_cutoff, *, relabel=True):
        """
        Compiles a AtomicCircuit or AtomicEmulatorCircuit to a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

        Args:
            circuit (Union[AtomicCircuit,AtomicEmulatorCircuit]): circuit to be compiled.
            fock_cutoff (int, Dict[str, int]): Truncation for fock spaces.

        Returns:
            experiment (DynamiqsExperiment): Compiled [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].
            hilbert_space (Dict[str, int]): Hilbert space of the system.
        """
        assert isinstance(circuit, (AtomicCircuit, AtomicEmulatorCircuit))

        if isinstance(circuit, AtomicCircuit):
            canonicalize = canonicalize_atomic_circuit_factory()
            intermediate = canonicalize(circuit)
            conversion = Post(ConstructHamiltonian())
            intermediate = conversion(intermediate)
        else:
            intermediate = circuit

        intermediate = canonicalize_emulator_circuit_factory()(intermediate)

        if self.approx_pass:
            intermediate = Chain(
                self.approx_pass, canonicalize_emulator_circuit_factory()
            )(intermediate)

        get_hilbert_space = GetHilbertSpace()
        analysis = Post(get_hilbert_space)
        analysis(intermediate)

        if relabel:
            analysis(intermediate)
        else:
            analysis(circuit.system)

        hilbert_space = get_hilbert_space.hilbert_space
        _hilbert_space = hilbert_space.hilbert_space
        for k in _hilbert_space.keys():
            if k[0] == "P":
                if isinstance(fock_cutoff, int):
                    _hilbert_space[k] = set(range(fock_cutoff))
                else:
                    _hilbert_space[k] = set(range(fock_cutoff[k]))
        hilbert_space = HilbertSpace(hilbert_space=_hilbert_space)

        if any(map(lambda x: x is None, hilbert_space.hilbert_space.values())):
            raise "Hilbert space not fully specified."

        relabeller = Post(RelabelStates(hilbert_space.get_relabel_rules()))
        intermediate = relabeller(intermediate)

        if self.save_intermediate:
            self.intermediate = intermediate

        compiler_p3 = Post(DynamiqsCodeGeneration(hilbert_space=hilbert_space))
        experiment = compiler_p3(intermediate)

        return experiment, hilbert_space

    def run(self, experiment, hilbert_space, timestep, *, initial_state=None):
        """
        Runs a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

        Args:
            experiment (DynamiqsExperiment): [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment] to be executed.
            hilbert_space (Dict[str, int]): Hilbert space of the system.
            timestep (float): Timestep between tracked states of the evolution.

        Returns:
            result (Dict[str,Any]): Result of execution of [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].
        """
        vm = Pre(
            DynamiqsVM(
                hilbert_space=hilbert_space,
                timestep=timestep,
                solver=self.solver,
                solver_options=self.solver_options,
                initial_state=initial_state,
            )
        )

        vm(experiment)

        return vm.children[0].result
compile(circuit, fock_cutoff, *, relabel=True)

Compiles a AtomicCircuit or AtomicEmulatorCircuit to a DynamiqsExperiment.

Parameters:

Name Type Description Default
circuit Union[AtomicCircuit, AtomicEmulatorCircuit]

circuit to be compiled.

required
fock_cutoff (int, Dict[str, int])

Truncation for fock spaces.

required

Returns:

Name Type Description
experiment DynamiqsExperiment

Compiled DynamiqsExperiment.

hilbert_space Dict[str, int]

Hilbert space of the system.

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/base.py
def compile(self, circuit, fock_cutoff, *, relabel=True):
    """
    Compiles a AtomicCircuit or AtomicEmulatorCircuit to a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

    Args:
        circuit (Union[AtomicCircuit,AtomicEmulatorCircuit]): circuit to be compiled.
        fock_cutoff (int, Dict[str, int]): Truncation for fock spaces.

    Returns:
        experiment (DynamiqsExperiment): Compiled [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].
        hilbert_space (Dict[str, int]): Hilbert space of the system.
    """
    assert isinstance(circuit, (AtomicCircuit, AtomicEmulatorCircuit))

    if isinstance(circuit, AtomicCircuit):
        canonicalize = canonicalize_atomic_circuit_factory()
        intermediate = canonicalize(circuit)
        conversion = Post(ConstructHamiltonian())
        intermediate = conversion(intermediate)
    else:
        intermediate = circuit

    intermediate = canonicalize_emulator_circuit_factory()(intermediate)

    if self.approx_pass:
        intermediate = Chain(
            self.approx_pass, canonicalize_emulator_circuit_factory()
        )(intermediate)

    get_hilbert_space = GetHilbertSpace()
    analysis = Post(get_hilbert_space)
    analysis(intermediate)

    if relabel:
        analysis(intermediate)
    else:
        analysis(circuit.system)

    hilbert_space = get_hilbert_space.hilbert_space
    _hilbert_space = hilbert_space.hilbert_space
    for k in _hilbert_space.keys():
        if k[0] == "P":
            if isinstance(fock_cutoff, int):
                _hilbert_space[k] = set(range(fock_cutoff))
            else:
                _hilbert_space[k] = set(range(fock_cutoff[k]))
    hilbert_space = HilbertSpace(hilbert_space=_hilbert_space)

    if any(map(lambda x: x is None, hilbert_space.hilbert_space.values())):
        raise "Hilbert space not fully specified."

    relabeller = Post(RelabelStates(hilbert_space.get_relabel_rules()))
    intermediate = relabeller(intermediate)

    if self.save_intermediate:
        self.intermediate = intermediate

    compiler_p3 = Post(DynamiqsCodeGeneration(hilbert_space=hilbert_space))
    experiment = compiler_p3(intermediate)

    return experiment, hilbert_space
run(experiment, hilbert_space, timestep, *, initial_state=None)

Runs a DynamiqsExperiment.

Parameters:

Name Type Description Default
experiment DynamiqsExperiment

DynamiqsExperiment to be executed.

required
hilbert_space Dict[str, int]

Hilbert space of the system.

required
timestep float

Timestep between tracked states of the evolution.

required

Returns:

Name Type Description
result Dict[str, Any]

Result of execution of DynamiqsExperiment.

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/base.py
def run(self, experiment, hilbert_space, timestep, *, initial_state=None):
    """
    Runs a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

    Args:
        experiment (DynamiqsExperiment): [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment] to be executed.
        hilbert_space (Dict[str, int]): Hilbert space of the system.
        timestep (float): Timestep between tracked states of the evolution.

    Returns:
        result (Dict[str,Any]): Result of execution of [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].
    """
    vm = Pre(
        DynamiqsVM(
            hilbert_space=hilbert_space,
            timestep=timestep,
            solver=self.solver,
            solver_options=self.solver_options,
            initial_state=initial_state,
        )
    )

    vm(experiment)

    return vm.children[0].result

codegen

DynamiqsCodeGeneration

Bases: ConversionRule

Rule that converts an AtomicEmulatorCircuit to a DynamiqsExperiment

Attributes:

Name Type Description
hilbert_space Dict[str, int]

Hilbert space of the system.

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/codegen.py
class DynamiqsCodeGeneration(ConversionRule):
    """
    Rule that converts an [`AtomicEmulatorCircuit`][oqd_trical.light_matter.interface.emulator.AtomicEmulatorCircuit]
    to a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment]

    Attributes:
        hilbert_space (Dict[str, int]): Hilbert space of the system.
    """

    def __init__(self, hilbert_space: HilbertSpace):
        super().__init__()

        self.hilbert_space = hilbert_space

    def map_AtomicEmulatorCircuit(self, model, operands):
        return DynamiqsExperiment(
            frame=None
            if (
                isinstance(operands["frame"], PrunedOperator)
                or operands["frame"] is None
            )
            else dq.timecallable(operands["frame"]),
            sequence=operands["sequence"],
        )

    def map_AtomicEmulatorGate(self, model, operands):
        if isinstance(operands["hamiltonian"], PrunedOperator):
            return DynamiqsGate(hamiltonian=None, duration=operands["duration"])

        return DynamiqsGate(
            hamiltonian=dq.timecallable(operands["hamiltonian"]),
            duration=operands["duration"],
        )

    def map_Identity(self, model, operands):
        op = dq.eye(self.hilbert_space.size[model.subsystem])
        return lambda t: op

    def map_KetBra(self, model, operands):
        ket = dq.basis(self.hilbert_space.size[model.subsystem], model.ket)
        bra = dq.basis(self.hilbert_space.size[model.subsystem], model.bra).dag()
        op = ket @ bra

        if not isinstance(op, dq.QArray):
            op = dq.asqarray(op)
        return lambda t: op

    def map_Annihilation(self, model, operands):
        op = dq.destroy(self.hilbert_space.size[model.subsystem])
        return lambda t: op

    def map_Creation(self, model, operands):
        op = dq.create(self.hilbert_space.size[model.subsystem])
        return lambda t: op

    def map_Displacement(self, model, operands):
        return lambda t: dq.displace(
            self.hilbert_space.size[model.subsystem], operands["alpha"](t)
        )

    def map_OperatorMul(self, model, operands):
        return lambda t: operands["op1"](t) @ operands["op2"](t)

    def map_OperatorKron(self, model, operands):
        return lambda t: dq.tensor(operands["op1"](t), operands["op2"](t))

    def map_OperatorAdd(self, model, operands):
        return lambda t: operands["op1"](t) + operands["op2"](t)

    def map_OperatorScalarMul(self, model, operands):
        return lambda t: operands["coeff"](t) * operands["op"](t)

    def map_WaveCoefficient(self, model, operands):
        return lambda t: operands["amplitude"](t) * jnp.exp(
            1j * (operands["frequency"](t) * t + operands["phase"](t))
        )

    def map_CoefficientAdd(self, model, operands):
        return lambda t: operands["coeff1"](t) + operands["coeff2"](t)

    def map_CoefficientMul(self, model, operands):
        return lambda t: operands["coeff1"](t) * operands["coeff2"](t)

    def map_MathNum(self, model, operands):
        return lambda t: model.value

    def map_MathImag(self, model, operands):
        return lambda t: 1j

    def map_MathVar(self, model, operands):
        if model.name == "t":
            return lambda t: t

        raise ValueError(
            f"Unsupported variable {model.name}, only variable t is supported"
        )

    def map_MathFunc(self, model, operands):
        if getattr(math, model.func, None):
            return lambda t: getattr(jnp, model.func)(operands["expr"](t))

        if model.func == "heaviside":
            return lambda t: jnp.heaviside(operands["expr"](t), 1)

        if model.func == "conj":
            return lambda t: jnp.conj(operands["expr"](t))

        raise ValueError(f"Unsupported function {model.func}")

    def map_MathAdd(self, model, operands):
        return lambda t: operands["expr1"](t) + operands["expr2"](t)

    def map_MathSub(self, model, operands):
        return lambda t: operands["expr1"](t) - operands["expr2"](t)

    def map_MathMul(self, model, operands):
        return lambda t: operands["expr1"](t) * operands["expr2"](t)

    def map_MathDiv(self, model, operands):
        return lambda t: operands["expr1"](t) / operands["expr2"](t)

    def map_MathPow(self, model, operands):
        return lambda t: operands["expr1"](t) ** operands["expr2"](t)

interface

DynamiqsExperiment

Bases: TypeReflectBaseModel

Class representing a Dynamiqs experiment represented in terms of atomic operations expressed in terms of their Hamiltonians.

Attributes:

Name Type Description
base Operator

Free Hamiltonian.

sequence List[AtomicEmulatorGate]

List of gates to apply.

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/interface.py
class DynamiqsExperiment(TypeReflectBaseModel):
    """
    Class representing a Dynamiqs experiment represented in terms of atomic operations expressed in terms of their Hamiltonians.

    Attributes:
        base (Operator): Free Hamiltonian.
        sequence (List[AtomicEmulatorGate]): List of gates to apply.

    """

    model_config = ConfigDict(validate_assignments=True, arbitrary_types_allowed=True)

    frame: Optional[dq.TimeQArray]
    sequence: List[DynamiqsGate]

DynamiqsGate

Bases: TypeReflectBaseModel

Class representing a Dynamiqs gate represented in terms of atomic operations expressed in terms of their Hamiltonians.

Attributes:

Name Type Description
hamiltonian Operator

Hamiltonian to evolve by.

duration float

Time to evolve for.

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/interface.py
class DynamiqsGate(TypeReflectBaseModel):
    """
    Class representing a Dynamiqs gate represented in terms of atomic operations expressed in terms of their Hamiltonians.

    Attributes:
        hamiltonian (Operator): Hamiltonian to evolve by.
        duration (float): Time to evolve for.
    """

    model_config = ConfigDict(validate_assignments=True, arbitrary_types_allowed=True)

    hamiltonian: Optional[dq.TimeQArray]
    duration: float

vm

DynamiqsVM

Bases: RewriteRule

Rule that executes a DynamiqsExperiment.

Attributes:

Name Type Description
hilbert_space Dict[str, int]

Hilbert space of the system.

timestep float

Timestep between tracked states of the evolution.

solver Literal[SESolver, MESolver]

Dynamiqs solver to use.

solver_options Dict[str, Any]

Dynamiqs solver options

Source code in oqd-trical/src/oqd_trical/backend/dynamiqs/vm.py
class DynamiqsVM(RewriteRule):
    """
    Rule that executes a [`DynamiqsExperiment`][oqd_trical.backend.dynamiqs.interface.DynamiqsExperiment].

    Attributes:
        hilbert_space (Dict[str, int]): Hilbert space of the system.
        timestep (float): Timestep between tracked states of the evolution.
        solver (Literal["SESolver","MESolver"]): Dynamiqs solver to use.
        solver_options (Dict[str,Any]): Dynamiqs solver options
    """

    def __init__(
        self,
        hilbert_space,
        timestep,
        *,
        initial_state=None,
        solver="SESolver",
        solver_options={},
    ):
        self.hilbert_space = hilbert_space
        self.timestep = timestep

        if initial_state:
            if initial_state.dims != tuple(self.hilbert_space.size.values()):
                raise ValueError("Initial state incompatible with Hilbert space")
            self.current_state = initial_state
        else:
            self.current_state = dq.tensor(
                *[
                    dq.basis(self.hilbert_space.size[k], 0)
                    for k in self.hilbert_space.size.keys()
                ]
            )

        self.states = [self.current_state]
        self.tspan = [0.0]

        self.solver = {
            "SESolver": sesolve,
            "MESolver": mesolve,
        }[solver]
        self.solver_options = solver_options

    @property
    def result(self):
        return dict(
            final_state=self.current_state,
            states=self.states,
            tspan=self.tspan,
            frame=self.frame,
            hilbert_space=self.hilbert_space,
        )

    def map_DynamiqsExperiment(self, model):
        self.frame = model.frame

    def map_DynamiqsGate(self, model):
        tspan = jnp.arange(0, model.duration, self.timestep)

        if tspan[-1] != model.duration:
            tspan = jnp.append(tspan, model.duration)

        tspan = tspan + self.tspan[-1]

        empty_hamiltonian = model.hamiltonian is None

        if empty_hamiltonian:
            self.tspan.extend(list(tspan[1:] + self.tspan[-1]))
            self.states.extend([self.current_state] * (len(tspan) - 1))
            return

        res = self.solver(
            model.hamiltonian,
            self.current_state,
            tspan,
            solver=self.solver_options["solver"]
            if "solver" in self.solver_options.keys()
            else dq.solver.Tsit5(),
        )

        self.current_state = res.final_state

        self.tspan.extend(list(tspan[1:]))
        self.states.extend(list(res.states[1:]))