Skip to content

QuTiP

oqd_trical.backend.qutip

QutipBackend

Bases: BackendBase

Backend for running simulation of AtomicCircuit with QuTiP

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]

QuTiP solver to use.

solver_options Dict[str, Any]

Qutip solver options

intermediate AtomicEmulatorCircuit

Intermediate representation of the atomic circuit during compilation

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

    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"]): QuTiP solver to use.
        solver_options (Dict[str,Any]): Qutip 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={"progress_bar": True},
    ):
        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 [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].

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

        Returns:
            experiment (QutipExperiment): Compiled [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
            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)

        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(QutipCodeGeneration(hilbert_space=hilbert_space))
        experiment = compiler_p3(intermediate)

        return experiment, hilbert_space

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

        Args:
            experiment (QutipExperiment): [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment] 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 [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
        """
        vm = Pre(
            QutipVM(
                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 QutipExperiment.

Parameters:

Name Type Description Default
circuit Union[AtomicCircuit, AtomicEmulatorCircuit]

circuit to be compiled.

required
fock_cutoff int

Truncation for fock spaces.

required

Returns:

Name Type Description
experiment QutipExperiment

Compiled QutipExperiment.

hilbert_space Dict[str, int]

Hilbert space of the system.

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

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

    Returns:
        experiment (QutipExperiment): Compiled [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
        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)

    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(QutipCodeGeneration(hilbert_space=hilbert_space))
    experiment = compiler_p3(intermediate)

    return experiment, hilbert_space

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

Runs a QutipExperiment.

Parameters:

Name Type Description Default
experiment QutipExperiment

QutipExperiment 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 QutipExperiment.

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

    Args:
        experiment (QutipExperiment): [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment] 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 [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
    """
    vm = Pre(
        QutipVM(
            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

QutipCodeGeneration

Bases: ConversionRule

Rule that converts an AtomicEmulatorCircuit to a QutipExperiment

Attributes:

Name Type Description
hilbert_space Dict[str, int]

Hilbert space of the system.

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

    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 QutipExperiment(
            frame=None
            if (
                isinstance(operands["frame"], PrunedOperator)
                or operands["frame"] is None
            )
            else operands["frame"],
            sequence=operands["sequence"],
        )

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

        return QutipGate(
            hamiltonian=operands["hamiltonian"], duration=operands["duration"]
        )

    def map_Identity(self, model, operands):
        op = qt.identity(self.hilbert_space.size[model.subsystem])
        return qt.QobjEvo(op)

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

        if not isinstance(op, qt.Qobj):
            op = qt.Qobj(op)
        return qt.QobjEvo(op)

    def map_Annihilation(self, model, operands):
        op = qt.destroy(self.hilbert_space.size[model.subsystem])
        return qt.QobjEvo(op)

    def map_Creation(self, model, operands):
        op = qt.create(self.hilbert_space.size[model.subsystem])
        return qt.QobjEvo(op)

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

    def map_OperatorMul(self, model, operands):
        return operands["op1"] * operands["op2"]

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

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

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

    def map_WaveCoefficient(self, model, operands):
        return lambda t: operands["amplitude"](t) * np.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(math, model.func)(operands["expr"](t))

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

        if model.func == "conj":
            return lambda t: np.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)

QutipVM

Bases: RewriteRule

Rule that executes a QutipExperiment.

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]

QuTiP solver to use.

solver_options Dict[str, Any]

Qutip solver options

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

    Attributes:
        hilbert_space (Dict[str, int]): Hilbert space of the system.
        timestep (float): Timestep between tracked states of the evolution.
        solver (Literal["SESolver","MESolver"]): QuTiP solver to use.
        solver_options (Dict[str,Any]): Qutip 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[0] != list(self.hilbert_space.size.values()):
                raise ValueError("Initial state incompatible with Hilbert space")
            self.current_state = initial_state
        else:
            self.current_state = tensor(
                [
                    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": SESolver,
            "MESolver": MESolver,
        }[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_QutipExperiment(self, model):
        self.frame = model.frame

    def map_QutipGate(self, model):
        tspan = np.arange(0, model.duration, self.timestep)

        if tspan[-1] != model.duration:
            tspan = np.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

        solver = self.solver(model.hamiltonian, options=self.solver_options)

        res = solver.run(
            self.current_state,
            tspan,
        )

        self.current_state = res.final_state

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

base

QutipBackend

Bases: BackendBase

Backend for running simulation of AtomicCircuit with QuTiP

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]

QuTiP solver to use.

solver_options Dict[str, Any]

Qutip solver options

intermediate AtomicEmulatorCircuit

Intermediate representation of the atomic circuit during compilation

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

    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"]): QuTiP solver to use.
        solver_options (Dict[str,Any]): Qutip 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={"progress_bar": True},
    ):
        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 [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].

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

        Returns:
            experiment (QutipExperiment): Compiled [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
            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)

        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(QutipCodeGeneration(hilbert_space=hilbert_space))
        experiment = compiler_p3(intermediate)

        return experiment, hilbert_space

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

        Args:
            experiment (QutipExperiment): [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment] 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 [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
        """
        vm = Pre(
            QutipVM(
                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 QutipExperiment.

Parameters:

Name Type Description Default
circuit Union[AtomicCircuit, AtomicEmulatorCircuit]

circuit to be compiled.

required
fock_cutoff int

Truncation for fock spaces.

required

Returns:

Name Type Description
experiment QutipExperiment

Compiled QutipExperiment.

hilbert_space Dict[str, int]

Hilbert space of the system.

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

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

    Returns:
        experiment (QutipExperiment): Compiled [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
        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)

    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(QutipCodeGeneration(hilbert_space=hilbert_space))
    experiment = compiler_p3(intermediate)

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

Runs a QutipExperiment.

Parameters:

Name Type Description Default
experiment QutipExperiment

QutipExperiment 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 QutipExperiment.

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

    Args:
        experiment (QutipExperiment): [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment] 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 [`QutipExperiment`][oqd_trical.backend.qutip.interface.QutipExperiment].
    """
    vm = Pre(
        QutipVM(
            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

QutipCodeGeneration

Bases: ConversionRule

Rule that converts an AtomicEmulatorCircuit to a QutipExperiment

Attributes:

Name Type Description
hilbert_space Dict[str, int]

Hilbert space of the system.

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

    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 QutipExperiment(
            frame=None
            if (
                isinstance(operands["frame"], PrunedOperator)
                or operands["frame"] is None
            )
            else operands["frame"],
            sequence=operands["sequence"],
        )

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

        return QutipGate(
            hamiltonian=operands["hamiltonian"], duration=operands["duration"]
        )

    def map_Identity(self, model, operands):
        op = qt.identity(self.hilbert_space.size[model.subsystem])
        return qt.QobjEvo(op)

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

        if not isinstance(op, qt.Qobj):
            op = qt.Qobj(op)
        return qt.QobjEvo(op)

    def map_Annihilation(self, model, operands):
        op = qt.destroy(self.hilbert_space.size[model.subsystem])
        return qt.QobjEvo(op)

    def map_Creation(self, model, operands):
        op = qt.create(self.hilbert_space.size[model.subsystem])
        return qt.QobjEvo(op)

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

    def map_OperatorMul(self, model, operands):
        return operands["op1"] * operands["op2"]

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

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

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

    def map_WaveCoefficient(self, model, operands):
        return lambda t: operands["amplitude"](t) * np.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(math, model.func)(operands["expr"](t))

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

        if model.func == "conj":
            return lambda t: np.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

QutipExperiment

Bases: TypeReflectBaseModel

Class representing a qutip 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/qutip/interface.py
class QutipExperiment(TypeReflectBaseModel):
    """
    Class representing a qutip 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[QobjEvo]
    sequence: List[QutipGate]

QutipGate

Bases: TypeReflectBaseModel

Class representing a qutip 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/qutip/interface.py
class QutipGate(TypeReflectBaseModel):
    """
    Class representing a qutip 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[QobjEvo]
    duration: float

vm

QutipVM

Bases: RewriteRule

Rule that executes a QutipExperiment.

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]

QuTiP solver to use.

solver_options Dict[str, Any]

Qutip solver options

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

    Attributes:
        hilbert_space (Dict[str, int]): Hilbert space of the system.
        timestep (float): Timestep between tracked states of the evolution.
        solver (Literal["SESolver","MESolver"]): QuTiP solver to use.
        solver_options (Dict[str,Any]): Qutip 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[0] != list(self.hilbert_space.size.values()):
                raise ValueError("Initial state incompatible with Hilbert space")
            self.current_state = initial_state
        else:
            self.current_state = tensor(
                [
                    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": SESolver,
            "MESolver": MESolver,
        }[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_QutipExperiment(self, model):
        self.frame = model.frame

    def map_QutipGate(self, model):
        tspan = np.arange(0, model.duration, self.timestep)

        if tspan[-1] != model.duration:
            tspan = np.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

        solver = self.solver(model.hamiltonian, options=self.solver_options)

        res = solver.run(
            self.current_state,
            tspan,
        )

        self.current_state = res.final_state

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