Skip to content

Temporal Operators

API Reference

masa.common.pctl.Next

Next(prob: float, subformula: BoundedPCTLFormula)

Bases: BoundedPCTLFormula

Bounded PCTL next operator \(X\) with probability threshold.

This represents:

\[ \mathbb{P}_{\ge p}[X\,\Phi]. \]

Informally, the formula holds at state \(s\) iff the probability that \(\Phi\) holds in the next state is at least \(p\).

Parameters:

Name Type Description Default
prob float

Probability threshold \(p \in [0,1]\).

required
subformula BoundedPCTLFormula

The subformula \(\Phi\) evaluated at the next state.

required

Attributes:

Name Type Description
prob

Probability threshold \(p\).

subformula

Nested formula \(\Phi\).

bound_param

Local bound contributed by this operator (fixed to 1).

See Also

_prob_seq: Computes the shifted probability sequence for \(X\,\Phi\).

Source code in masa/common/pctl.py
def __init__(self, prob: float, subformula: BoundedPCTLFormula):
    super().__init__()
    self.prob = prob
    self.subformula = subformula
    self.bound_param = 1

prob instance-attribute

prob = prob

subformula instance-attribute

subformula = subformula

bound_param instance-attribute

bound_param = 1

_bound property

_bound

Total bound for Next.

Returns:

Type Description

1 + subformula.bound.

_next_prob_seq_core_dense staticmethod

_next_prob_seq_core_dense(m: ndarray, sub_seq: ndarray) -> jnp.ndarray

JAX core for Next on a dense kernel.

Parameters:

Name Type Description Default
m ndarray

Dense Markov-chain transition matrix of shape (S, S), where column s is the distribution over next states from state s.

required
sub_seq ndarray

Subformula sequence of shape (T, S).

required

Returns:

Type Description
ndarray

Sequence of shape (T + 1, S). Row 0 is all zeros (the shifted

ndarray

operator is defined to have probability 0 at time 0), and rows 1..T

ndarray

contain one-step expectations of sub_seq.

Source code in masa/common/pctl.py
@staticmethod
@jit
def _next_prob_seq_core_dense(m: jnp.ndarray, sub_seq: jnp.ndarray) -> jnp.ndarray:
    r"""JAX core for :class:`Next` on a dense kernel.

    Args:
        m: Dense Markov-chain transition matrix of shape ``(S, S)``, where
            column ``s`` is the distribution over next states from state ``s``.
        sub_seq: Subformula sequence of shape ``(T, S)``.

    Returns:
        Sequence of shape ``(T + 1, S)``. Row 0 is all zeros (the shifted
        operator is defined to have probability 0 at time 0), and rows 1..T
        contain one-step expectations of ``sub_seq``.
    """
    tail = (m.T @ sub_seq.T)
    tail = jnp.swapaxes(tail, 0, 1)
    zeros = jnp.zeros_like(tail[:1, :])
    return jnp.concatenate([zeros, tail], axis=0)

_next_prob_seq_core_compact staticmethod

_next_prob_seq_core_compact(succ: ndarray, p: ndarray, sub_seq: ndarray, K: int) -> jnp.ndarray

JAX core for Next on a compact kernel.

Parameters:

Name Type Description Default
succ ndarray

Successor ids of shape (K, S).

required
p ndarray

Successor probabilities of shape (K, S) aligned with succ.

required
sub_seq ndarray

Subformula sequence of shape (T, S).

required
K int

Max successors per state (static argument for JIT).

required

Returns:

Type Description
ndarray

Sequence of shape (T + 1, S) with row 0 all zeros.

Source code in masa/common/pctl.py
@staticmethod
@partial(jit, static_argnames=("K",))
def _next_prob_seq_core_compact(
    succ: jnp.ndarray, p: jnp.ndarray, sub_seq: jnp.ndarray, K: int
) -> jnp.ndarray:
    r"""JAX core for :class:`Next` on a compact kernel.

    Args:
        succ: Successor ids of shape ``(K, S)``.
        p: Successor probabilities of shape ``(K, S)`` aligned with ``succ``.
        sub_seq: Subformula sequence of shape ``(T, S)``.
        K: Max successors per state (static argument for JIT).

    Returns:
        Sequence of shape ``(T + 1, S)`` with row 0 all zeros.
    """
    def one_step(v):
        v_succ = v[succ]
        return jnp.sum(p * v_succ, axis=0)

    tail = jax.vmap(one_step)(sub_seq)
    zeros = jnp.zeros_like(tail[:1, :])
    return jnp.concatenate([zeros, tail], axis=0)

_prob_seq

_prob_seq(kernel: Kernel, vec_label_fn: ndarray, atom_dict: Dict[str, int], max_k: int | None = None) -> np.ndarray

Compute the probability sequence for \(X\,\Phi\).

Parameters:

Name Type Description Default
kernel Kernel

Markov-chain kernel (dense or compact).

required
vec_label_fn ndarray

Vectorized labeling matrix (n_atoms, S).

required
atom_dict Dict[str, int]

Mapping from atom string to row index.

required
max_k int | None

Local horizon (inclusive). If None, defaults to 1.

None

Returns:

Type Description
ndarray

Float64 array of shape (max_k + 1, S).

Notes

If max_k == 0, returns a single row of zeros.

Source code in masa/common/pctl.py
def _prob_seq(
    self,
    kernel: Kernel,
    vec_label_fn: np.ndarray,
    atom_dict: Dict[str, int],
    max_k: int | None = None,
) -> np.ndarray:
    r"""Compute the probability sequence for :math:`X\,\Phi`.

    Args:
        kernel: Markov-chain kernel (dense or compact).
        vec_label_fn: Vectorized labeling matrix ``(n_atoms, S)``.
        atom_dict: Mapping from atom string to row index.
        max_k: Local horizon (inclusive). If ``None``, defaults to 1.

    Returns:
        Float64 array of shape ``(max_k + 1, S)``.

    Notes:
        If ``max_k == 0``, returns a single row of zeros.
    """
    if max_k is None:
        max_k = self.bound_param

    n_states = kernel_n_states(kernel)
    if max_k == 0:
        return np.zeros((1, n_states), dtype=np.float64)

    sub_seq_np = self.subformula._prob_seq(kernel, vec_label_fn, atom_dict, max_k=max_k - 1)
    sub_seq_j = jnp.asarray(sub_seq_np, dtype=jnp.float64)

    if isinstance(kernel, tuple):
        succ, p = kernel
        succ_j = jnp.asarray(succ, dtype=jnp.int64)
        p_j = jnp.asarray(p, dtype=jnp.float64)
        seq_j = self._next_prob_seq_core_compact(succ_j, p_j, sub_seq_j, K=succ.shape[0])
    else:
        m_j = jnp.asarray(kernel, dtype=jnp.float64)
        seq_j = self._next_prob_seq_core_dense(m_j, sub_seq_j)

    return np.asarray(seq_j, dtype=np.float64)

sat

sat(kernel: Kernel, vec_label_fn: ndarray, atom_dict: Dict[str, int]) -> np.ndarray

Threshold the one-step probability for \(\mathbb{P}_{\ge p}[X\,\Phi]\).

Parameters:

Name Type Description Default
kernel Kernel

Markov-chain kernel (dense or compact).

required
vec_label_fn ndarray

Vectorized labeling matrix (n_atoms, S).

required
atom_dict Dict[str, int]

Mapping from atom string to row index.

required

Returns:

Type Description
ndarray

Float64 array of shape (S,) in {0.0, 1.0}.

Source code in masa/common/pctl.py
def sat(
    self,
    kernel: Kernel,
    vec_label_fn: np.ndarray,
    atom_dict: Dict[str, int],
) -> np.ndarray:
    r"""Threshold the one-step probability for :math:`\mathbb{P}_{\ge p}[X\,\Phi]`.

    Args:
        kernel: Markov-chain kernel (dense or compact).
        vec_label_fn: Vectorized labeling matrix ``(n_atoms, S)``.
        atom_dict: Mapping from atom string to row index.

    Returns:
        Float64 array of shape ``(S,)`` in ``{0.0, 1.0}``.
    """
    probs = self._prob_seq(kernel, vec_label_fn, atom_dict, max_k=self.bound_param)[
        self.bound_param
    ]
    return (probs >= self.prob).astype(np.float64)

masa.common.pctl.Until

Until(prob: float, bound, subformula_1: BoundedPCTLFormula, subformula_2: BoundedPCTLFormula)

Bases: BoundedPCTLFormula

Bounded PCTL until operator \(U^{\le B}\) with probability threshold.

This represents:

\[ \mathbb{P}_{\ge p}[\Phi_1\ U^{\le B}\ \Phi_2]. \]

Informally, the formula holds at state \(s\) iff the probability that \(\Phi_2\) becomes true within \(B\) steps while \(\Phi_1\) holds at all preceding steps is at least \(p\).

The bounded-until recurrence computed by _prob_seq is:

\[ P_0 &= \mathrm{sat}_2, \\\\ P_{k+1} &= \mathrm{sat}_2 + \bigl((1-\mathrm{sat}_2)\,\mathrm{sat}_1\bigr)\, \mathbb{E}[P_k(s')]. \]

Parameters:

Name Type Description Default
prob float

Probability threshold \(p \in [0,1]\).

required
bound

Local bound \(B\).

required
subformula_1 BoundedPCTLFormula

Continuation condition \(\Phi_1\).

required
subformula_2 BoundedPCTLFormula

Target condition \(\Phi_2\).

required

Attributes:

Name Type Description
prob

Probability threshold.

bound_param

Local bound \(B\).

subformula_1

Continuation formula.

subformula_2

Target formula.

Source code in masa/common/pctl.py
def __init__(self, prob: float, bound, subformula_1: BoundedPCTLFormula, subformula_2: BoundedPCTLFormula):
    super().__init__()
    self.prob = prob
    self.bound_param = int(bound)
    self.subformula_1 = subformula_1
    self.subformula_2 = subformula_2

prob instance-attribute

prob = prob

bound_param instance-attribute

bound_param = int(bound)

subformula_1 instance-attribute

subformula_1 = subformula_1

subformula_2 instance-attribute

subformula_2 = subformula_2

_bound property

_bound

Total bound for Until.

Returns:

Type Description

bound_param + subformula_1.bound + subformula_2.bound.

_until_prob_seq_core_dense staticmethod

_until_prob_seq_core_dense(m: ndarray, sat1: ndarray, sat2: ndarray, max_k: int) -> jnp.ndarray

JAX core for bounded-until probabilities using a dense kernel.

Parameters:

Name Type Description Default
m ndarray

Dense Markov-chain transition matrix of shape (S, S).

required
sat1 ndarray

Satisfaction mask for \(\Phi_1\), shape (S,) in {0,1}.

required
sat2 ndarray

Satisfaction mask for \(\Phi_2\), shape (S,) in {0,1}.

required
max_k int

Local bound \(B\).

required

Returns:

Type Description
ndarray

Float64 JAX array of shape (max_k + 1, S).

Source code in masa/common/pctl.py
@staticmethod
@partial(jit, static_argnames=("max_k",))
def _until_prob_seq_core_dense(
    m: jnp.ndarray, sat1: jnp.ndarray, sat2: jnp.ndarray, max_k: int
) -> jnp.ndarray:
    r"""JAX core for bounded-until probabilities using a dense kernel.

    Args:
        m: Dense Markov-chain transition matrix of shape ``(S, S)``.
        sat1: Satisfaction mask for :math:`\Phi_1`, shape ``(S,)`` in ``{0,1}``.
        sat2: Satisfaction mask for :math:`\Phi_2`, shape ``(S,)`` in ``{0,1}``.
        max_k: Local bound :math:`B`.

    Returns:
        Float64 JAX array of shape ``(max_k + 1, S)``.
    """
    cont_mask = (1.0 - sat2) * sat1
    prob0 = sat2

    def body(prob, _):
        exp = m.T @ prob
        next_prob = sat2 + cont_mask * exp
        return next_prob, next_prob

    _, tail = jax.lax.scan(body, prob0, None, length=max_k)
    return jnp.concatenate([prob0[None, :], tail], axis=0)

_until_prob_seq_core_compact staticmethod

_until_prob_seq_core_compact(succ: ndarray, p: ndarray, sat1: ndarray, sat2: ndarray, max_k: int, K: int) -> jnp.ndarray

JAX core for bounded-until probabilities using a compact kernel.

Parameters:

Name Type Description Default
succ ndarray

Successor ids (K, S).

required
p ndarray

Successor probabilities (K, S).

required
sat1 ndarray

Satisfaction mask for \(\Phi_1\), shape (S,) in {0,1}.

required
sat2 ndarray

Satisfaction mask for \(\Phi_2\), shape (S,) in {0,1}.

required
max_k int

Local bound \(B\).

required
K int

Max successors per state (static argument for JIT).

required

Returns:

Type Description
ndarray

Float64 JAX array of shape (max_k + 1, S).

Source code in masa/common/pctl.py
@staticmethod
@partial(jit, static_argnames=("max_k", "K"))
def _until_prob_seq_core_compact(
    succ: jnp.ndarray, p: jnp.ndarray, sat1: jnp.ndarray, sat2: jnp.ndarray, max_k: int, K: int
) -> jnp.ndarray:
    r"""JAX core for bounded-until probabilities using a compact kernel.

    Args:
        succ: Successor ids ``(K, S)``.
        p: Successor probabilities ``(K, S)``.
        sat1: Satisfaction mask for :math:`\Phi_1`, shape ``(S,)`` in ``{0,1}``.
        sat2: Satisfaction mask for :math:`\Phi_2`, shape ``(S,)`` in ``{0,1}``.
        max_k: Local bound :math:`B`.
        K: Max successors per state (static argument for JIT).

    Returns:
        Float64 JAX array of shape ``(max_k + 1, S)``.
    """
    cont_mask = (1.0 - sat2) * sat1
    prob0 = sat2

    def exp_step(v):
        v_succ = v[succ]
        return jnp.sum(p * v_succ, axis=0)

    def body(prob, _):
        exp = exp_step(prob)
        next_prob = sat2 + cont_mask * exp
        return next_prob, next_prob

    _, tail = jax.lax.scan(body, prob0, None, length=max_k)
    return jnp.concatenate([prob0[None, :], tail], axis=0)

_prob_seq

_prob_seq(kernel: Kernel, vec_label_fn: ndarray, atom_dict: Dict[str, int], max_k: int | None = None) -> np.ndarray

Compute the bounded-until probability sequence.

Parameters:

Name Type Description Default
kernel Kernel

Markov-chain kernel (dense or compact).

required
vec_label_fn ndarray

Vectorized labeling matrix (n_atoms, S).

required
atom_dict Dict[str, int]

Mapping from atom string to row index.

required
max_k int | None

Local horizon (inclusive). If None, defaults to bound_param.

None

Returns:

Type Description
ndarray

Float64 array of shape (max_k + 1, S).

Source code in masa/common/pctl.py
def _prob_seq(
    self,
    kernel: Kernel,
    vec_label_fn: np.ndarray,
    atom_dict: Dict[str, int],
    max_k: int | None = None,
) -> np.ndarray:
    r"""Compute the bounded-until probability sequence.

    Args:
        kernel: Markov-chain kernel (dense or compact).
        vec_label_fn: Vectorized labeling matrix ``(n_atoms, S)``.
        atom_dict: Mapping from atom string to row index.
        max_k: Local horizon (inclusive). If ``None``, defaults to
            :attr:`bound_param`.

    Returns:
        Float64 array of shape ``(max_k + 1, S)``.
    """
    if max_k is None:
        max_k = self.bound_param

    sat1_np = self.subformula_1.sat(kernel, vec_label_fn, atom_dict).astype(np.float64)
    sat2_np = self.subformula_2.sat(kernel, vec_label_fn, atom_dict).astype(np.float64)

    sat1_j = jnp.asarray(sat1_np, dtype=jnp.float64)
    sat2_j = jnp.asarray(sat2_np, dtype=jnp.float64)

    if isinstance(kernel, tuple):
        succ, p = kernel
        succ_j = jnp.asarray(succ, dtype=jnp.int64)
        p_j = jnp.asarray(p, dtype=jnp.float64)
        seq_j = self._until_prob_seq_core_compact(succ_j, p_j, sat1_j, sat2_j, max_k=max_k, K=succ.shape[0])
    else:
        m_j = jnp.asarray(kernel, dtype=jnp.float64)
        seq_j = self._until_prob_seq_core_dense(m_j, sat1_j, sat2_j, max_k=max_k)

    return np.asarray(seq_j, dtype=np.float64)

sat

sat(kernel: Kernel, vec_label_fn: ndarray, atom_dict: Dict[str, int]) -> np.ndarray

Threshold \(\mathbb{P}_{\ge p}[\Phi_1\ U^{\le B}\ \Phi_2]\).

Parameters:

Name Type Description Default
kernel Kernel

Markov-chain kernel (dense or compact).

required
vec_label_fn ndarray

Vectorized labeling matrix (n_atoms, S).

required
atom_dict Dict[str, int]

Mapping from atom string to row index.

required

Returns:

Type Description
ndarray

Float64 array of shape (S,) in {0.0, 1.0}.

Source code in masa/common/pctl.py
def sat(
    self,
    kernel: Kernel,
    vec_label_fn: np.ndarray,
    atom_dict: Dict[str, int],
) -> np.ndarray:
    r"""Threshold :math:`\mathbb{P}_{\ge p}[\Phi_1\ U^{\le B}\ \Phi_2]`.

    Args:
        kernel: Markov-chain kernel (dense or compact).
        vec_label_fn: Vectorized labeling matrix ``(n_atoms, S)``.
        atom_dict: Mapping from atom string to row index.

    Returns:
        Float64 array of shape ``(S,)`` in ``{0.0, 1.0}``.
    """
    probs = self._prob_seq(kernel, vec_label_fn, atom_dict, max_k=self.bound_param)[
        self.bound_param
    ]
    return (probs >= self.prob).astype(np.float64)

masa.common.pctl.Always

Always(prob: float, bound: int, subformula: BoundedPCTLFormula)

Bases: BoundedPCTLFormula

Bounded PCTL always operator \(G^{\le B}\) with probability threshold.

This represents:

\[ \mathbb{P}_{\ge p}[G^{\le B}\,\Phi]. \]

MASA implements bounded always via duality:

\[ G^{\le B}\Phi \equiv \neg(\top\ U^{\le B}\ \neg\Phi), \]

with the threshold transformation \(p \mapsto 1-p\) applied to the inner until.

Parameters:

Name Type Description Default
prob float

Probability threshold \(p \in [0,1]\).

required
bound int

Local bound \(B\).

required
subformula BoundedPCTLFormula

Subformula \(\Phi\).

required

Attributes:

Name Type Description
prob

Probability threshold.

bound_param

Local bound.

subformula

Nested formula.

_inner

Desugared formula (internal) built from Neg, Until, and Truth.

Source code in masa/common/pctl.py
def __init__(self, prob: float, bound: int, subformula: BoundedPCTLFormula):
    super().__init__()
    self.prob = float(prob)
    self.bound_param = int(bound)
    self.subformula = subformula

    self._inner = Neg(
        Until(
            prob=1.0-self.prob,
            bound=self.bound_param,
            subformula_1=Truth(),
            subformula_2=Neg(self.subformula),
        )
    )

prob instance-attribute

prob = float(prob)

bound_param instance-attribute

bound_param = int(bound)

subformula instance-attribute

subformula = subformula

_inner instance-attribute

_inner = Neg(Until(prob=1.0 - self.prob, bound=self.bound_param, subformula_1=Truth(), subformula_2=Neg(self.subformula)))

_bound property

_bound: int

Total bound delegated to the desugared inner formula.

_prob_seq

_prob_seq(kernel, vec_label_fn, atom_dict, max_k=None)

Delegate probability-sequence computation to _inner.

Source code in masa/common/pctl.py
def _prob_seq(self, kernel, vec_label_fn, atom_dict, max_k=None):
    """Delegate probability-sequence computation to :attr:`_inner`."""
    return self._inner._prob_seq(kernel, vec_label_fn, atom_dict, max_k)

sat

sat(kernel, vec_label_fn, atom_dict)

Delegate probability-sequence computation to _inner.

Source code in masa/common/pctl.py
def sat(self, kernel, vec_label_fn, atom_dict):
    """Delegate probability-sequence computation to :attr:`_inner`."""
    return self._inner.sat(kernel, vec_label_fn, atom_dict)

masa.common.pctl.Eventually

Eventually(prob: float, bound: int, subformula: BoundedPCTLFormula)

Bases: BoundedPCTLFormula

Bounded PCTL eventually operator \(F^{\le B}\) with probability threshold.

This represents:

\[ \mathbb{P}_{\ge p}[F^{\le B}\,\Phi]. \]

MASA implements bounded eventually as a bounded until:

\[ F^{\le B}\Phi \equiv \top\ U^{\le B}\ \Phi. \]

Parameters:

Name Type Description Default
prob float

Probability threshold \(p \in [0,1]\).

required
bound int

Local bound \(B\).

required
subformula BoundedPCTLFormula

Subformula \(\Phi\).

required

Attributes:

Name Type Description
prob

Probability threshold.

bound_param

Local bound.

subformula

Nested formula.

_inner

Desugared formula (internal) built from Until and Truth.

Source code in masa/common/pctl.py
def __init__(self, prob: float, bound: int, subformula: BoundedPCTLFormula):
    super().__init__()
    self.prob = float(prob)
    self.bound_param = int(bound)
    self.subformula = subformula

    self._inner = Until(
        prob=self.prob,
        bound=self.bound_param,
        subformula_1=Truth(),
        subformula_2=self.subformula,
    )

prob instance-attribute

prob = float(prob)

bound_param instance-attribute

bound_param = int(bound)

subformula instance-attribute

subformula = subformula

_inner instance-attribute

_inner = Until(prob=self.prob, bound=self.bound_param, subformula_1=Truth(), subformula_2=self.subformula)

_bound property

_bound: int

Total bound delegated to the desugared inner formula.

_prob_seq

_prob_seq(kernel, vec_label_fn, atom_dict, max_k=None)

Delegate probability-sequence computation to _inner.

Source code in masa/common/pctl.py
def _prob_seq(self, kernel, vec_label_fn, atom_dict, max_k=None):
    """Delegate probability-sequence computation to :attr:`_inner`."""
    return self._inner._prob_seq(kernel, vec_label_fn, atom_dict, max_k)

sat

sat(kernel, vec_label_fn, atom_dict)

Delegate satisfaction evaluation to _inner.

Source code in masa/common/pctl.py
def sat(self, kernel, vec_label_fn, atom_dict):
    """Delegate satisfaction evaluation to :attr:`_inner`."""
    return self._inner.sat(kernel, vec_label_fn, atom_dict)