Skip to content

utils

qemcmc.utils

MCMCState dataclass

MCMCState(bitstring, accepted, energy=None, position=None)

Represents a single step in an MCMC trajectory.

Stores the proposed configuration, whether it was accepted by the Metropolis rule, its energy, and the position of the step in the chain.

MCMCChain dataclass

MCMCChain(states=None, name='MCMC')

Container for the sequence of states produced during an MCMC run.

This class records all proposed states, tracks accepted configurations, and provides helper methods for extracting trajectories, energies, and empirical distributions from the Markov chain.

Source code in src/qemcmc/utils/helpers.py
def __init__(self, states: Optional[List[MCMCState]] = None, name: Optional[str] = "MCMC"):
    self.name = name

    if states is None:
        self._states: List[MCMCState] = []
        self._current_state: MCMCState = None
        self._states_accepted: List[MCMCState] = []
        self.markov_chain: List[str] = []

    else:
        self._states = states
        self._current_state: MCMCState = next((s for s in self._states[::-1] if s.accepted), None)
        self._states_accepted: List[MCMCState] = [state for state in states if state.accepted]
        self.markov_chain: List[str] = self.get_list_markov_chain()

get_random_state

get_random_state(num_spins)

Generate a random state for a given number of spins.

Parameters: num_spins (int): The number of spins in the system. Returns: str: A bitstring representing the random state.

Source code in src/qemcmc/utils/helpers.py
def get_random_state(num_spins: int) -> str:
    """
    Generate a random state for a given number of spins.

    Parameters:
        num_spins (int): The number of spins in the system.
    Returns:
        str: A bitstring representing the random state.
    """

    next_state = np.random.randint(0, 2, size=num_spins, dtype=np.int8)
    # s_prime = f"{next_state:0{num_spins}b}"
    s_prime = "".join(str(bit) for bit in next_state)
    return s_prime

get_all_possible_states

get_all_possible_states(num_spins)

Returns all possible binary strings of length n=num_spins

Paremeters: num_spins: n length of the bitstring Returns: list: A list of all possible binary strings of length num_spins.

Source code in src/qemcmc/utils/helpers.py
def get_all_possible_states(num_spins: int) -> list:
    """
    Returns all possible binary strings of length n=num_spins

    Paremeters:
        num_spins: n length of the bitstring
    Returns:
        list: A list of all possible binary strings of length num_spins.
    """

    num_possible_states = 2 ** (num_spins)
    possible_states = [f"{k:0{num_spins}b}" for k in range(0, num_possible_states)]
    return possible_states

validate_subgroups

validate_subgroups(subgroups, subgroup_probs, n_spins)

Validate coarse-graining subgroups.

Requirements: - subgroups is a non-empty list of non-empty sequences - each element is an int in [0, n-1] - each subgroup has no duplicate indices - every spin from 0 to n-1 appears in at least one subgroup

Raises:

Type Description
ValueError / TypeError
Source code in src/qemcmc/utils/helpers.py
def validate_subgroups(subgroups, subgroup_probs, n_spins):
    """
    Validate coarse-graining subgroups.

    Requirements:
      - subgroups is a non-empty list of non-empty sequences
      - each element is an int in [0, n-1]
      - each subgroup has no duplicate indices
      - every spin from 0 to n-1 appears in at least one subgroup

    Raises
    ------
    ValueError/TypeError
    """

    if not isinstance(subgroups, list) or len(subgroups) == 0:
        raise ValueError("subgroups must be a non-empty list")

    for g in subgroups:
        if not isinstance(g, Sequence) or len(g) == 0:
            raise ValueError("each subgroup must be a non-empty list")

        if not all(isinstance(i, int) for i in g):
            raise TypeError("subgroup indices must be integers")

        if not all(0 <= i < n_spins for i in g):
            raise ValueError("subgroup index out of bounds")

        if len(set(g)) != len(g):
            raise ValueError("duplicate indices inside a subgroup")

    covered = set(i for g in subgroups for i in g)
    if covered != set(range(n_spins)):
        raise ValueError("subgroups must cover all spins exactly once or at least once")

    if subgroup_probs is not None:
        if len(subgroup_probs) != len(subgroups):
            raise ValueError("subgroup_probs must match subgroups length")

        if any(p < 0 for p in subgroup_probs):
            raise ValueError("subgroup_probs must be non-negative")

        if not np.isclose(sum(subgroup_probs), 1.0):
            raise ValueError("subgroup_probs must sum to 1")