Source code for qecc.CliffordClass

#!/usr/bin/python
# -*- coding: utf-8 -*-
##
# CliffordClass.py: Implementation of qecc.Clifford and related utility
#     functions.
##
# © 2012 Christopher E. Granade (cgranade@gmail.com) and
#     Ben Criger (bcriger@gmail.com).
# This file is a part of the QuaEC project.
# Licensed under the AGPL version 3.
##
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
##

## RELOAD FIX ##
# This is a pretty poor way of fixing it, but should work for now.
from sys import version_info
if version_info[0] == 3:
    PY3 = True
    from importlib import reload
elif version_info[0] == 2:
    PY3 = False
else:
    raise EnvironmentError("sys.version_info refers to a version of "
        "Python neither 2 nor 3. This is not permitted. "
        "sys.version_info = {}".format(version_info))

if PY3:
    from . import PauliClass as _pc
    from . import bsf as _bsf
    from . import utils as u
else:
    import PauliClass as _pc
    import bsf as _bsf
    import utils as u

from functools import reduce

try:
    reload(_pc)
    reload(_bsf)
except:
    # If it fails, we're no worse off, so just ignore the problem.
    pass

## IMPORTS ##

import operator as op

from copy import copy, deepcopy
from itertools import product, chain, combinations
from .PauliClass import *
from .bsf import *
from numpy import hstack, newaxis
from .exceptions import *

from .paulicollections import PauliList

from .constraint_solvers import solve_commutation_constraints
from .unitary_reps import clifford_as_unitary

from .singletons import EmptyClifford, Unspecified

import warnings

## ALL ##

__all__ = [
    'Clifford',
    'eye_c', 'cnot', 'replace_one_character', 'cz', 'hadamard',
    'phase', 'permutation', 'swap', 'pauli_gate', 'paulify',
    'generic_clifford',
    'gen_cliff', 'transcoding_cliffords', 'min_len_transcoding_clifford',
    'clifford_group'
]

## CONSTANTS ##

VALID_OPS = ['I', 'X', 'Y', 'Z']
VALID_PHS = list(range(4))
KINDS = ['X', 'Z']
PHASES = [" +", "+i", " -", "-i"]

## CLASSES ##

[docs]class Clifford(object): r""" Class representing an element of the Cifford group on :math:`n` qubits. :param xbars: A list of operators :math:`\bar{X}_i` such that the represented Clifford operation :math:`C` acts as :math:`C(X_i) = \bar{X}_i`. Note that in order for the represented operator to be an automorphism, each :math:`\bar{X}_i` must have phase either 0 or 2. A warning will result if this condition is not met. :param zbars: See ``xbars``. :type xbars: list of :class:`qecc.Pauli` instances :type zbars: list of :class:`qecc.Pauli` instances """ ## CONSTRUCTOR ## def __init__(self, xbars, zbars): # Require that all specified operators be Paulis. # Moreover, we should warn the caller if the output phase is not either # 0 or 2, since such operators are not automorphisms of the Pauli group. self.xout = PauliList(*xbars) self.zout = PauliList(*zbars) for output_xz in chain(self.xout, self.zout): if output_xz is not Unspecified and output_xz.ph not in [0, 2]: warnings.warn( 'The output phase of a Clifford operator has been specified as {}, such that the operator is not a valid automorphism.\n'.format( output_xz.ph ) + 'To avoid this warning, please choose all output phases to be from the set {0, 2}.' ) # Prevent fully unspecified operators. if all([P is Unspecified for P in chain(xbars, zbars)]): raise ValueError("At least one output must be specified.") ## SEQUENCE PROTOCOL ## def __len__(self): """ Yields the number of qubits on which the Clifford ``self`` acts. """ for P in self.xout + self.zout: if P is not Unspecified: return len(P) ## PROPERTIES ## @property def nq(self): """ Returns the number of qubits on which this :class:`qecc.Clifford` object acts. """ return len(self) @property def n_unspecified(self): """ Returns the number of unspecifed outputs of this :class:`qecc.Clifford` object. """ return len([P for P in chain(self.xout, self.zout) if P is Unspecified]) ## PRINTING ## def __repr__(self): return "<Clifford operator on {nq} qubit{s} at 0x{id:x}>".format( nq=len(self), id=id(self), s='s' if len(self) > 1 else '' ) def __str__(self): """ Returns a string representing a Clifford in Pauli notation (yielding a list of input Paulis, and a list of output Paulis.) """ SPARSE_NQ = 3 if len(self) > SPARSE_NQ: return self.str_sparse() left_side_x,left_side_z=elem_gens(len(self)) right_side=self.xout+self.zout return '\n'.join( '{gen.op} |-> {outsign}{out}'.format( gen=gen, out=out.op if out is not Unspecified else "Unspecified", outsign=PHASES[out.ph] if out is not Unspecified else "" ) for gen, out in zip(left_side_x + left_side_z, self.xout + self.zout) )
[docs] def str_sparse(self): """ Provides a compact representation for :class:`qecc.Clifford` objects, intended for use in the case where many of the outputs have small support. """ out = list(zip(KINDS, list(map(enumerate, [self.xout, self.zout])))) nq = len(self) outstr = "\n".join( [ "{}[{}] |-> {}{}".format(kind, idx, PHASES[P.ph], P.str_sparse(incl_ph=False)) for kind, Ps in out for idx, P in Ps if elem_gen(nq, idx, kind) != P ]) return outstr if len(outstr) > 0 else "eye_p({})".format(self.nq)
[docs] def is_valid(self, quiet=True): """ Returns ``True`` if this instance represents a valid automorphism. In particular, this method returns ``True`` if all output phase assignments are either 0 or 2, and if all of the commutation relations on its outputs are obeyed. Unspecified outputs are ignored. :param bool quiet: If set to ``True``, this method will not print out any information, but will return ``True`` or ``False`` as described above. Otherwise, if the operator is not a valid Clifford operator, diagnostic information will be printed. """ if any(P.ph not in [0, 2] for P in chain(self.xout, self.zout) if P is not Unspecified): if not quiet: print("At least one output operator has a phase other than 0 or 2.") return False for P in sum(elem_gens(len(self)), []): for Q in sum(elem_gens(len(self)), []): UP = self.conjugate_pauli(P) UQ = self.conjugate_pauli(Q) if UP is not Unspecified and UQ is not Unspecified and com(UP, UQ) != com(P, Q): if not quiet: print("c({P}, {Q}) == {cPQ}, but c(U({P}), U({Q})) == c({UP}, {UQ}) == {cUPUQ}.".format( P=P, Q=Q, cPQ=com(P,Q), UP=UP, UQ=UQ, cUPUQ=com(UP, UQ) )) return False return True
[docs] def inv(self): r""" Calculates the inverse :math:`C^{-1}` of this Clifford operator :math:`C`, such that :math:`C^{-1} \cdot C` is the identity Clifford. """ # Since BSM doesn't consider phases, what we get is the inverse up to # a Pauli correction. Since Pauli operators are self inverse, we can # find the correction as (almost_inv * self). almost_inv = self.as_bsm().inv().as_clifford() return almost_inv * self * almost_inv
[docs] def conjugate_pauli(self,pauli): r""" Given an instance of :class:`qecc.Pauli` representing the operator :math:`P`, calculates the mapping :math:`CPC^{\dagger}`, where :math:`C` is the operator represented by this instance. :arg pauli: Representation of the Pauli operator :math:`P`. :type pauli: qecc.Pauli :returns: Representation of the Pauli operator :math:`CPC^{\dagger}`, where :math:`C` is the Clifford operator represented by this instance. :rtype: :class:`qecc.Pauli` """ if not isinstance(pauli,Pauli): # If we don't have a Pauli, maybe we have an iterable. try: dummy = iter(pauli) # Yep. It was an iterable. return list(map(self.conjugate_pauli, pauli)) except TypeError: # Nope. Wasn't iterable. Raise an error. raise TypeError("Cliffords conjugate Paulis.") #Initialize the output Pauli to the identity: rolling_pauli=Pauli('I'*len(pauli), phase=pauli.ph) for idx,op in enumerate(pauli.op): #For every X/Z the input Pauli contains, multiply by the # corresponding output Pauli from self. if op == 'X': rolling_pauli=rolling_pauli*self.xout[idx] elif op == 'Z': rolling_pauli=rolling_pauli*self.zout[idx] elif op == 'Y': #Y = iXZ: rolling_pauli=rolling_pauli*self.xout[idx]*self.zout[idx] rolling_pauli.mul_phase(1) return rolling_pauli
def __call__(self, other): if isinstance(other, Pauli): return self.conjugate_pauli(other) elif isinstance(other, str): return self.conjugate_pauli(Pauli(other)) elif isinstance(other, PauliList): return PauliList(*list(map(self, other))) elif isinstance(other, stab.StabilizerCode): return stab.StabilizerCode( self(other.group_generators), self(other.logical_xs), self(other.logical_zs)) else: return NotImplemented ## EQUALITY OPERATORS ## def __eq__(self,other): if isinstance(other, Clifford): return all([P == Q for P, Q in zip(self.xout + self.zout, other.xout + other.zout)]) else: return False def __ne__(self, other): return not self.__eq__(other) ## ALGEBRAIC OPERATORS ## def __mul__(self,other): """multiplies two Cliffords, self and other, by conjugating the output Paulis from other, according to the relations given by self, yielding self*other. """ if not isinstance(other,Clifford): return NotImplemented Xs=[] Zs=[] for ex, zed in zip(other.xout,other.zout): Xs.append(self.conjugate_pauli(ex)) Zs.append(self.conjugate_pauli(zed)) return Clifford(Xs,Zs) def __rand__(self, other): if other is EmptyClifford: return self return NotImplemented def __and__(self,other): """Takes the tensor product of two Cliffords ``self`` and ``other``.""" if other is EmptyClifford: return self if not isinstance(other,Clifford): return NotImplemented nq_self=len(self) nq_other=len(other) id_self_size=eye_p(nq_self) id_other_size=eye_p(nq_other) """We embed each Clifford into a larger space, and concatenate the output lists.""" exones=[] extwos=[] zedones=[] zedtwos=[] for idx in range(nq_self): exones.append(self.xout[idx] & id_other_size) zedones.append(self.zout[idx] & id_other_size) for idx in range(nq_other): extwos.append(id_self_size & other.xout[idx]) zedtwos.append(id_self_size & other.zout[idx]) return Clifford(exones+extwos,zedones+zedtwos) ## CONSTRAINT SOLVING ##
[docs] def constraint_completions(self): """ Yields an iterator onto possible Clifford operators whose outputs agree with this operator for all outputs that are specified. Note that all yielded operators assign the phase 0 to all outputs, by convention. If this operator is fully specified, the iterator will yield exactly one element, which will be equal to this operator. For example: >>> import qecc as q >>> C = q.Clifford([q.Pauli('XI'), q.Pauli('IX')], [q.Unspecified, q.Unspecified]) >>> it = C.constraint_completions() >>> print it.next() XI |-> +XI IX |-> +IX ZI |-> +ZI IZ |-> +IZ >>> print it.next() XI |-> +XI IX |-> +IX ZI |-> +ZI IZ |-> +IY >>> print len(list(C.constraint_completions())) 8 If this operator is not a valid Clifford operator, then this method will raise an :class:`qecc.InvalidCliffordError` upon iteraton. """ # Check for validity first. if not self.is_valid(): raise InvalidCliffordError("The specified constraints are invalid or are contradictory.") # Useful constants. XKIND, ZKIND = list(range(2)) # Start by finding the first unspecified output. nq = len(self) X_bars, Z_bars = self.xout, self.zout P_bars = list(map(copy, [X_bars, Z_bars])) # <- Useful for indexing by kinds. XZ_pairs = list(zip(X_bars, Z_bars)) try: unspecified_idx, unspecified_kind = next(iter( (idx, kind) for idx, kind in product(range(nq), list(range(2))) if XZ_pairs[idx][kind] is Unspecified )) except StopIteration: # If there are no unspecified constraints, then self is the only # satisfying completion. yield self return # We must always commute with disjoint qubits. commutation_constraints = reduce(op.add, (XZ_pairs[idx] for idx in range(nq) if idx != unspecified_idx), tuple() ) # On the same qubit, we must anticommute with the opposite operator. anticommutation_constraints = [XZ_pairs[unspecified_idx][XKIND if unspecified_kind == ZKIND else ZKIND]] # Filter out Unspecified constraints. specified_pred = lambda P: P is not Unspecified commutation_constraints = list(filter(specified_pred, commutation_constraints)) anticommutation_constraints = list(filter(specified_pred, anticommutation_constraints)) # Now we iterate over satisfactions of the constraints, yielding # all satisfactions of the remaining constraints recursively. Xgs, Zgs = elem_gens(nq) for P in solve_commutation_constraints(commutation_constraints, anticommutation_constraints, search_in_gens=Xgs+Zgs): P_bars[unspecified_kind][unspecified_idx] = Pauli(P.op, phase=0) # I wish I had "yield from" here. Ah, well. We have to recurse # manually instead. C = Clifford(*P_bars) for completion in C.constraint_completions(): yield completion return
## TYPE CONVERSIONS ##
[docs] def as_bsm(self): """ Returns a representation of the Clifford operator as a binary symplectic matrix. :rtype: :class:`qecc.BinarySymplecticMatrix` """ def to_col(P): v = P.as_bsv() out = hstack([v.x, v.z])[..., newaxis] return out return BinarySymplecticMatrix(hstack(list(map(to_col, self.xout + self.zout))))
[docs] def as_unitary(self): """ Returns a :class:`numpy.ndarray` containing a unitary matrix representation of this Clifford operator. Raises a :class:`RuntimeError` if NumPy cannot be imported. """ return clifford_as_unitary(self)
[docs] def circuit_decomposition(self, include_pauli=True): """ Returns a :class:`qecc.Circuit` object consisting of the circuit decomposition of `self.as_bsm()` and a :class:`qecc.Pauli` object which ensures the output phases of the :class:`qecc.Clifford` object are preserved. :param bool include_pauli: If `True`, Pauli locations are added at the end of the returned circuit. If `False`, the returned `Circuit` is correct only up to a Pauli operator at the end. :rtype: `Circuit` """ pauli_correct = paulify( (self.as_bsm().circuit_decomposition().as_clifford())*self.inv() ).as_circuit() if include_pauli else () return (self.as_bsm().circuit_decomposition())+pauli_correct
## FUNCTIONS ##
[docs]def eye_c(nq): """ Yields the identity Clifford, defined to map every generator of the Pauli group to itself. :rtype: Clifford """ return Clifford(*elem_gens(nq)) if nq > 0 else EmptyClifford
def replace_one_character(string,location,new_character): """ Replaces the character in ``string`` at ``location`` with ``new_character``. :rtype: str """ return string[:location]+new_character+string[location+1:]
[docs]def cnot(nq,ctrl,targ): """ Yields the ``nq``-qubit CNOT Clifford controlled on ``ctrl``, acting a Pauli :math:`X` on ``targ``. :rtype: :class:`qecc.Clifford` """ #Initialize to the identity Clifford: cnotto=eye_c(nq) #Wherever ctrl has an X, put an X on targ: cnotto.xout[ctrl].op=replace_one_character(cnotto.xout[ctrl].op,targ,'X') #Wherever targ has a Z, put a Z on ctrl: cnotto.zout[targ].op=replace_one_character(cnotto.zout[targ].op,ctrl,'Z') return cnotto
[docs]def cz(nq, q1, q2): """ Yields the ``nq``-qubit C-Z Clifford, acting on qubits ``q1`` and ``q2``. :rtype: :class:`qecc.Clifford` """ #Initialize to the identity Clifford: gate = eye_c(nq) #Wherever ctrl or targ get an X, map to XZ: gate.xout[q1].op = replace_one_character(gate.xout[q1].op, q2, 'Z') gate.xout[q2].op = replace_one_character(gate.xout[q2].op, q1, 'Z') return gate
[docs]def hadamard(nq,q): """ Yields the ``nq``-qubit Clifford, switching :math:`X` and :math:`Z` on qubit ``q``, yielding a minus sign on :math:`Y`. :rtype: :class:`qecc.Clifford` """ #Switch a Z and an X in the identity Clifford: return eye_c(q) & Clifford([Pauli('Z')],[Pauli('X')]) & eye_c(nq-q-1)
[docs]def phase(nq,q): r""" Yields the :math:`\frac{\pi}{4}_z`-rotation Clifford, acting on qubit ``q``. :rtype: :class:`qecc.Clifford` """ return eye_c(q) & Clifford([Pauli('Y')],[Pauli('Z')]) & eye_c(nq-q-1)
def permutation(lst, p): """ Permutes a list ``lst`` according to a set of indices ``p``. :rtype: list """ return [lst[idx] for idx in p]
[docs]def swap(nq, q1, q2): """ Yields the swap Clifford, on ``nq`` qubits, which swaps the Pauli generators on ``q1`` and ``q2``. :rtype: :class:`qecc.Clifford` """ p = list(range(nq)) p[q1], p[q2] = p[q2], p[q1] gate = eye_c(nq) gate.xout = permutation(gate.xout, p) gate.zout = permutation(gate.zout, p) return gate
[docs]def pauli_gate(pauli): """ Imports an instance of the :class:`qecc.Pauli` class into the :class:`qecc.Clifford` class, representing a Pauli as a series of sign changes. :rtype: :class:`qecc.Clifford` """ nq = len(pauli.op) return Clifford(*tuple( [gen.mul_phase(2*com(pauli,gen)) for gen in gen_set] for gen_set in elem_gens(nq) ))
@u.deprecated("Deprecated; see method from_clifford in PauliClass.") def paulify(cliff_in): """ Tests an input Clifford ``cliff_in`` to determine if it is, in fact, a Pauli. If so, it outputs the Pauli. If not, it returns the Clifford. :arg cliff_in: Representation of Clifford operator to be converted, if possible. :rtype: :class:`qecc.Pauli` Example: >>> import qecc as q >>> cliff=q.Clifford([q.Pauli('XI',2),q.Pauli('IX')],map(q.Pauli,['ZI','IZ'])) >>> q.paulify(cliff) i^0 ZI Converting a Pauli into a Clifford and back again will erase the phase: >>> import qecc as q >>> paul=q.Pauli('YZ',3) >>> cliff=q.pauli_gate(paul) >>> q.paulify(cliff) i^0 YZ """ nq=len(cliff_in.xout) #Determine number of qubits. test_ex,test_zed=elem_gens(nq) #Get paulis to compare. """If the Paulis input to the Clifford are only altered in phase, then the Clifford is also a Pauli.""" for ex_clif,zed_clif,ex_test,zed_test in zip(cliff_in.xout, cliff_in.zout,test_ex,test_zed): if ex_clif.op != ex_test.op or zed_clif.op != zed_test.op: print("Clifford is not Pauli.") return cliff_in #If the Clifford is Pauli, determine which by examining operators with altered phases. exact=eye_p(nq) zedact=eye_p(nq) #Initialize accumulators """If a negative sign appears on a given generator, assign a Pauli to that qubit that conjugates the generator to a minus sign, e.g. ZXZ = -X """ for idx_x in range(nq): if cliff_in.xout[idx_x].ph==2: exact.op = replace_one_character(exact.op, idx_x, 'Z') for idx_z in range(nq): if cliff_in.zout[idx_z].ph==2: zedact.op = replace_one_character(zedact.op, idx_z, 'X') return Pauli((exact*zedact).op)
[docs]def generic_clifford(paulis_in, paulis_out): """ Given two lists of :class:`qecc.Pauli` instances, ``paulis_in`` and ``paulis_out``, produces an instance ``C`` of :class:`qecc.Clifford` such that ``C(paulis_in[i]) == paulis_out[i]`` for all ``i`` in ``range(2 * nq)``, where ``nq`` is the length of each element of the two lists. Each of ``paulis_in`` and ``paulis_out`` is assumed to be ordered such that the slice ``[0:nq]`` produces a list of logical :math:`X` operators, and such that the slice ``[nq:2*nq]`` produces the logical :math:`Z` operators. :param paulis_in: A list of length ``2 * nq`` logical Pauli operators specifying the input constraints for the desired Clifford operation. :param paulis_out: A list of length ``2 * nq`` logical Pauli operators specifying the output constraints for the desired Clifford operation. :return: A Clifford operator mapping the input constraints to the output constraints. :rtype: qecc.Clifford """ nq=len(paulis_in)/2 xins=paulis_in[0:nq] zins=paulis_in[nq:2*nq] xouts=paulis_out[0:nq] zouts=paulis_out[nq:2*nq] G = Clifford(xouts,zouts) H = Clifford(xins,zins) return G*H.inv()
# For backwards compatibility, we define gen_cliff as an alias. gen_cliff = generic_clifford @u.deprecated("Deprecated, see method transcoding_cliffords in StabilizerCode") def transcoding_cliffords(stab_in,xs_in,zs_in,stab_out,xs_out,zs_out): r""" Returns an iterator onto all :class:`qecc.Clifford` objects which take states specified by ``stab_in``, ``xs_in``, and ``zs_in``, and return states specified by ``stab_out``, ``xs_out``, and ``zs_out``. :arg stab_in: Stabilizer generating set for input code :arg xs_in: Logical :math:`X` operators for input code :arg zs_in: Logical :math:`Z` operators for input code :arg stab_out: Stabilizer generating set for output code :arg xs_out: Logical :math:`X` operators for output code :arg zs_out: Logical :math:`Z` operators for output code """ #Preliminaries: nq_in=len(stab_in[0]) nq_out=len(stab_out[0]) nq_anc=abs(nq_in-nq_out) #Decide left side: if nq_in<nq_out: stab_left=stab_out xs_left=xs_out zs_left=zs_out stab_right=stab_in xs_right=xs_in zs_right=zs_in else: stab_right=stab_out xs_right=xs_out zs_right=zs_out stab_left=stab_in xs_left=xs_in zs_left=zs_in cliff_xouts_left=stab_left+xs_left cliff_zouts_left=[Unspecified]*len(stab_left)+zs_left cliff_left=next(Clifford(cliff_xouts_left,cliff_zouts_left).constraint_completions()) list_left=cliff_left.xout+cliff_left.zout for mcset in mutually_commuting_sets(n_elems=len(stab_left)-len(stab_right),n_bits=nq_anc): temp_xouts_right=pad(stab_right,lower_right=mcset)+[p&eye_p(nq_anc) for p in xs_right] temp_zouts_right=[Unspecified]*len(stab_left)+[p&eye_p(nq_anc) for p in zs_right] for completion in Clifford(temp_xouts_right,temp_zouts_right).constraint_completions(): if nq_in<nq_out: yield gen_cliff(completion.xout+completion.zout,list_left) else: yield gen_cliff(list_left,completion.xout+completion.zout) @u.deprecated("Deprecated, see min_len_transcoding_clifford in StabilizerCode") def min_len_transcoding_clifford(paulis_in,paulis_out): circuit_iter=[p.as_bsm().circuit_decomposition() for p in transcoding_cliffords(paulis_in,paulis_out)] return min(*circuit_iter)
[docs]def clifford_group(nq, consider_phases=False): r""" Given a number of qubits :math:`n`, returns an iterator that produces all elements of :math:`\mathcal{C}_n`, the Clifford group on :math:`n` qubits. :param int nq: The number of qubits upon which each yielded element will act. :param bool consider_phases: If ``True``, then Clifford operators whose assignments of phases to the generators of the Pauli group differ will be treated as distinct. Otherwise, the yielded elements will be drawn from the group :math:`\hat{\mathcal{C}}_n = \mathrm{Aut}(\hat{\mathcal{P}}_n / \{ i^k I : k \in \mathbb{Z}_4 \})`, such that the phases of the outputs are not considered. """ idx = 0 for P in pauli_group(nq): if P.wt > 0: C = Clifford([P] + [Unspecified]*(nq -1), [Unspecified]*nq) for completion in C.constraint_completions(): if consider_phases: P_bars = [completion.xout, completion.zout] # phase_array is chosen to disregard global phases by # absorbing them into xout[0]. for phase_array in product([0, 2], repeat=2*nq): for idx_kind, idx_qubit in product(list(range(2)), list(range(nq))): P_bars[idx_kind][idx_qubit].ph = phase_array[nq*idx_kind + idx_qubit] yield Clifford(*P_bars) else: yield completion
## MORE IMPORTS ## # Due to circular dependencies, we need for some things in this module to be # defined /before/ we import stab, even though it's used before. # The easiest way to ensure this is to import at the end, even though that's # bad style, in general. from . import stab