Source code for mimiqcircuits.backends.stochastic_kind

#
# Copyright © 2023-2026 QPerfect. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Resolution-time taxonomy for operations as classified by a backend.

Mirrors the Julia surface in `AbstractQCSs.jl/src/stochastic_kind.jl`.
The classification is **backend-dependent**: a ``MixedUnitary`` is
``TrajectorySampleable`` for an MPS-style simulator that resolves it
offline but could be ``RuntimeOnly`` for a simulator that resolves it
state-dependently. The dispatch hook is therefore a method on
:class:`Backend` (``backend.stochastic_kind(op)``), not a property of
the op alone. Free-function helpers
(``is_deterministic(backend, x)``, ``first_stochastic(backend, c)``,
…) wrap the method and accept either an :class:`Operation` or an
:class:`Instruction`.

This file deliberately avoids importing from
:mod:`mimiqcircuits.backends.backend` to keep the dependency cycle one
way: ``backend.py`` imports from this module, not the other way
around.
"""
from __future__ import annotations

import enum
from typing import TYPE_CHECKING

from mimiqcircuits.instruction import Instruction
from mimiqcircuits.operations.ifstatement import IfStatement
from mimiqcircuits.operations.krauschannel import krauschannel
from mimiqcircuits.operations.losschannel import LossErr
from mimiqcircuits.operations.measure import AbstractMeasurement

if TYPE_CHECKING:  # pragma: no cover
    from mimiqcircuits.backends.backend import Backend
    from mimiqcircuits.circuit import Circuit


[docs] class StochasticKind(enum.IntEnum): """Resolution-time taxonomy for operations. Ordered so worst-case combines (``max``) compose across composite ops: ``Deterministic < TrajectorySampleable < RuntimeOnly``. - ``Deterministic`` — no randomness; ``compile()`` can lower the op into the backend's compiled form using only the source-circuit information. - ``TrajectorySampleable`` — non-deterministic but resolvable in ``prepare_trajectory()`` using RNG draws alone. Classical sample space and probabilities are state-independent *as far as this backend is concerned*. - ``RuntimeOnly`` — must be resolved during ``evolve()`` because branch probabilities or control-flow truth values depend on the live quantum or classical state. **Backend-dependent.** See :meth:`Backend.stochastic_kind`. """ Deterministic = 0 TrajectorySampleable = 1 RuntimeOnly = 2
def _resolve_op(x): """Accept either an :class:`Operation` or an :class:`Instruction`.""" if isinstance(x, Instruction): return x.operation return x
[docs] def default_stochastic_kind(op) -> StochasticKind: """Backend-agnostic default classification used by :meth:`Backend.stochastic_kind`. Concrete backends override the method on :class:`Backend` (not this function) when they handle a specific op type differently. Defaults: - ``IfStatement`` / similar wrappers: recurse on the inner op. - ``krauschannel``: ``TrajectorySampleable`` if ``ismixedunitary``, else ``RuntimeOnly``. - ``LossErr``: ``RuntimeOnly`` (until ``sample_losses`` moves into ``prepare_trajectory()``; F-S2 follow-up). - ``AbstractMeasurement``: ``RuntimeOnly``. - everything else: ``Deterministic``. """ op = _resolve_op(op) if isinstance(op, IfStatement): return default_stochastic_kind(op.get_operation()) if isinstance(op, krauschannel): return ( StochasticKind.TrajectorySampleable if op.ismixedunitary() else StochasticKind.RuntimeOnly ) if isinstance(op, LossErr): return StochasticKind.RuntimeOnly if isinstance(op, AbstractMeasurement): return StochasticKind.RuntimeOnly return StochasticKind.Deterministic
# ── Free-function helpers (Julia-parity call syntax) ──────────────────────
[docs] def stochastic_kind(backend: "Backend", x) -> StochasticKind: """Free-function form: ``stochastic_kind(backend, x)``. Delegates to ``backend.stochastic_kind(x)`` so subclasses' overrides bite.""" return backend.stochastic_kind(x)
[docs] def is_deterministic(backend: "Backend", x) -> bool: return backend.stochastic_kind(x) == StochasticKind.Deterministic
[docs] def is_trajectory_sampleable(backend: "Backend", x) -> bool: return backend.stochastic_kind(x) == StochasticKind.TrajectorySampleable
[docs] def is_runtime_only(backend: "Backend", x) -> bool: return backend.stochastic_kind(x) == StochasticKind.RuntimeOnly
[docs] def is_stochastic(backend: "Backend", x) -> bool: return backend.stochastic_kind(x) != StochasticKind.Deterministic
[docs] def first_stochastic(backend: "Backend", circuit: "Circuit"): """Index of the first non-deterministic instruction in ``circuit`` as classified by ``backend``, or ``None`` if purely deterministic.""" for i, inst in enumerate(circuit): if is_stochastic(backend, inst): return i return None
[docs] def last_stochastic(backend: "Backend", circuit: "Circuit"): """Index of the last non-deterministic instruction in ``circuit``, or ``None`` if purely deterministic.""" last = None for i, inst in enumerate(circuit): if is_stochastic(backend, inst): last = i return last
[docs] def first_runtime_only(backend: "Backend", circuit: "Circuit"): """Index of the first ``RuntimeOnly`` instruction in ``circuit``, or ``None`` if none exists. Distinct from :func:`first_stochastic`: ``TrajectorySampleable`` ops do not require barrier protection in MPS-style backends.""" for i, inst in enumerate(circuit): if is_runtime_only(backend, inst): return i return None
[docs] def last_runtime_only(backend: "Backend", circuit: "Circuit"): last = None for i, inst in enumerate(circuit): if is_runtime_only(backend, inst): last = i return last
__all__ = [ "StochasticKind", "default_stochastic_kind", "stochastic_kind", "is_deterministic", "is_trajectory_sampleable", "is_runtime_only", "is_stochastic", "first_stochastic", "last_stochastic", "first_runtime_only", "last_runtime_only", ]