Repository: BQSKit/bqskit (language: python)

Issue:
Add-convert-to-var
Adding the convert to variable untry pass
Adding test to that pass and also to the toU3 pass
Reproducible circuits
Hi

I am trying to get reproducible circuits from BQSKit for the same qiskit circuit but I keep getting circuits with different depths and gate counts. I have tried setting a seed using both `seed_random_sources` and `SetRandomSeedPass` but I am unable to reproduce the same results each time. 

Is there any way I can achieve this using BQSKit?

This is what I am trying to run:
```python
seed_random_sources(0)
...
task = CompilationTask(
    bqskit_circuit, [
        SetRandomSeedPass(seed=0),
        QuickPartitioner(),
        ForEachBlockPass([QSearchSynthesisPass(), ScanningGateRemovalPass()]),
        UnfoldPass()
    ],
)

compiler = Compiler()
out_circuit = compiler.compile(task)
```
Compiler ignores custom gate set when compiling a random 2-qubit circuit
Example to reproduce:
```
import bqskit
from bqskit.ext import qiskit_to_bqskit
import qiskit as qs
import qutip as qt
import numpy as np

circ = qs.QuantumCircuit(2)
u = qt.rand_unitary_haar(4)
random_unitary = qs.quantum_info.operators.Operator(np.array(u))
circ.unitary(random_unitary, [0,1], label='random')

model = bqskit.MachineModel(2, gate_set={bqskit.ir.gates.CZGate(2), bqskit.ir.gates.U3Gate(1)})
circ_bqskit = qiskit_to_bqskit(circ)
output_circuit = bqskit.compile(circ_bqskit, model=model)
print(output_circuit.gate_set)
```
This will print `{U3Gate, CNOTGate}` instead of the desired `{U3Gate, CZGate}` (and the circuit `output_circuit` will consist of CNOTs and U3 gates instead of using the desired CZ operations). This appears to be the case for any non-CNOT 2-qubit gate.

Behavior is correct when using `bqskit.compile(np.array(u), model=model)`, so it seems that the circuit object is causing the problem.

Behavior is correct when specifying a 3-qubit gate instead of a 2-qubit gate, e.g. `gate_set={bqskit.ir.gates.Toffoli(3), bqskit.ir.gates.U3Gate(1)}` (if circuit size is extended to 3 qubits instead of 2).
logger info for leap when checking new best dist
In leap synthesis, when [check_new_best](https://github.com/BQSKit/bqskit/blob/74c1f6fd7975b4b7a0098333c022fc88567ee2af/bqskit/passes/synthesis/leap.py#L216), the debug information displays the cost values to a certain degree of precision, which might give the impression that several trials have the same 'best cost.' However, the actual cost values are distinct. It's better to change the precision of the cost. 


Discussion / hints:

Thanks for the bug report. The `SetRandomSeedPass` should handle this as you would expect; however, it has been a while since we implemented it.

To try and understand what is going wrong, can you try setting the seed at the instantiation level by changing the associated line to the following:

```
ForEachBlockPass([QSearchSynthesisPass(instantiate_options={'seed':0}), ScanningGateRemovalPass(instantiate_options={'seed':0})]),
```

If this works, there is a disconnect in the passes. Otherwise, something more serious is happening.
Thanks. I think this worked, the output is a more consistent that before. 
I didn't get time to verify programatically if the circuits are identical. But it seems like they are. 
I am glad to hear that this workaround works. Thanks for checking.

The original workflow you provided should have this expected behavior without setting the instantiate option; I will continue investigating and update here.
The problem is that the SetRandomSeedPass only runs on one dask worker. So with small jobs, everything is on one worker, and everything works as expected. However, with bigger jobs, only one worker has its seed set. To solve this, we need to tweak how we pass instantiation options between passes, which we had planned to do in the 1.1 update. For now, we have a workaround that works, and I will add a failing test in the 1.0.4 update to keep track of this in the future.



Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d3444a265..89b6f9898 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -14,7 +14,6 @@ repos:
         args: [--django]
     -   id: check-case-conflict
     -   id: check-merge-conflict
-    -   id: check-symlinks
     -   id: mixed-line-ending
     -   id: check-added-large-files
         args: ['--maxkb=8192']
@@ -24,7 +23,7 @@ repos:
     -   id: no-commit-to-branch
         args: [--branch, master]
 -   repo: https://github.com/PyCQA/docformatter
-    rev: v1.5.1
+    rev: v1.7.5
     hooks:
     -   id: docformatter
         args:
@@ -33,7 +32,7 @@ repos:
             - --wrap-summaries=80
             - --wrap-descriptions=80
 -   repo: https://github.com/pre-commit/mirrors-autopep8
-    rev: v2.0.1
+    rev: v2.0.2
     hooks:
     -   id: autopep8
         args:
@@ -42,13 +41,13 @@ repos:
             - --ignore=E731
         exclude: 'tests/ext.*'
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v3.3.1
+    rev: v3.9.0
     hooks:
     -   id: pyupgrade
         args:
             - --py38-plus
 -   repo: https://github.com/asottile/reorder_python_imports
-    rev: v3.9.0
+    rev: v3.10.0
     hooks:
     -   id: reorder-python-imports
         args:
@@ -57,23 +56,19 @@ repos:
             - --py37-plus
         exclude: 'tests/ext.*'
 -   repo: https://github.com/asottile/add-trailing-comma
-    rev: v2.4.0
+    rev: v3.0.0
     hooks:
     -   id: add-trailing-comma
         args:
             - --py36-plus
--   repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v2.2.0
-    hooks:
-    -   id: setup-cfg-fmt
 - repo: https://github.com/PyCQA/autoflake
-  rev: v2.0.0
+  rev: v2.2.0
   hooks:
     - id: autoflake
       args:
         - --in-place
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.991
+    rev: v1.4.1
     hooks:
     - id: mypy
       exclude: tests/qis/test_pauli.py
@@ -83,6 +78,7 @@ repos:
     hooks:
     -   id: flake8
         args:
-            - --ignore=E741,E241,E226,F405,F403,W503,F901,E731,TYP006
+            - --ignore=E741,W503,F901,E731,TYP006,TYP001,TYP002
             - --max-line-length=80
+            - --per-file-ignores=bqskit/runtime/worker.py:E402
         additional_dependencies: [flake8-typing-imports==1.13.0]
diff --git a/bqskit/__init__.py b/bqskit/__init__.py
index 09a2fccd9..dd6a87128 100644
--- a/bqskit/__init__.py
+++ b/bqskit/__init__.py
@@ -4,6 +4,8 @@
 import logging
 from sys import stdout as _stdout
 
+from .version import __version__  # noqa: F401
+from .version import __version_info__  # noqa: F401
 from bqskit.compiler.compile import compile
 from bqskit.compiler.machine import MachineModel
 from bqskit.ir.circuit import Circuit
@@ -11,15 +13,7 @@
 from bqskit.ir.lang.qasm2 import OPENQASM2Language as _qasm
 
 # Initialize Logging
-_logger = logging.getLogger('bqskit')
-_handler = logging.StreamHandler(_stdout)
-_handler.setLevel(0)
-_fmt_header = '(%(thread)d): %(asctime)s.%(msecs)03d - %(levelname)-8s |'
-_fmt_message = ' %(name)s: %(message)s'
-_fmt = _fmt_header + _fmt_message
-_formatter = logging.Formatter(_fmt, '%H:%M:%S')
-_handler.setFormatter(_formatter)
-_logger.addHandler(_handler)
+_logging_initialized = False
 
 
 def enable_logging(verbose: bool = False) -> None:
@@ -30,42 +24,68 @@ def enable_logging(verbose: bool = False) -> None:
         verbose (bool): If set to True, will print more verbose messages.
             Defaults to False.
     """
+    global _logging_initialized
+    if not _logging_initialized:
+        _logger = logging.getLogger('bqskit')
+        _handler = logging.StreamHandler(_stdout)
+        _handler.setLevel(0)
+        _fmt_header = '%(asctime)s.%(msecs)03d - %(levelname)-8s |'
+        _fmt_message = ' %(name)s: %(message)s'
+        _fmt = _fmt_header + _fmt_message
+        _formatter = logging.Formatter(_fmt, '%H:%M:%S')
+        _handler.setFormatter(_formatter)
+        _logger.addHandler(_handler)
+        _logging_initialized = True
+
     level = logging.DEBUG if verbose else logging.INFO
     logging.getLogger('bqskit').setLevel(level)
 
 
-def disable_parallelism() -> None:
-    """
-    Disables the default parallelism in BQSKit.
+def disable_logging() -> None:
+    """Disable logging for BQSKit."""
+    logging.getLogger('bqskit').setLevel(logging.CRITICAL)
 
-    If parallelism is disabled, everything will run single threaded. Performance
-    will degrade drastically but will free up computer resources. This setting
-    is ignored if connecting to an external cluster.
-    """
-    from bqskit.compiler import Compiler
-    Compiler._parallelism_enabled = False
 
+def enable_dashboard() -> None:
+    import warnings
+    warnings.warn(
+        'Dask has been removed from BQSKit. As a result, the'
+        ' enable_dashboard method has been removed.'
+        'This warning will turn into an error in a future update.',
+        DeprecationWarning,
+    )
 
-def enable_parallelism() -> None:
-    """
-    Enables parallelism; it is enabled by default.
 
-    See `disable_parallelism` for more info.
-    """
-    from bqskit.compiler import Compiler
-    Compiler._parallelism_enabled = True
+def disable_dashboard() -> None:
+    import warnings
+    warnings.warn(
+        'Dask has been removed from BQSKit. As a result, the'
+        ' disable_dashboard method has been removed.'
+        'This warning will turn into an error in a future update.',
+        DeprecationWarning,
+    )
 
 
-def enable_dashboard() -> None:
-    """Enables the dask dashboard."""
-    from bqskit.compiler import Compiler
-    Compiler._dashboard = True
+def disable_parallelism() -> None:
+    import warnings
+    warnings.warn(
+        'The disable_parallelism method has been removed.'
+        ' Instead, set the "num_workers" parameter to 1 during '
+        'Compiler construction. This warning will turn into'
+        'an error in a future update.',
+        DeprecationWarning,
+    )
 
 
-def disable_dashboard() -> None:
-    """Enables the dask dashboard."""
-    from bqskit.compiler import Compiler
-    Compiler._dashboard = False
+def enable_parallelism() -> None:
+    import warnings
+    warnings.warn(
+        'The enable_parallelism method has been removed.'
+        ' Instead, set the "num_workers" parameter to 1 during '
+        'Compiler construction. This warning will turn into'
+        'an error in a future update.',
+        DeprecationWarning,
+    )
 
 
 __all__ = [
@@ -73,6 +93,7 @@ def disable_dashboard() -> None:
     'MachineModel',
     'Circuit',
     'enable_logging',
+    'disable_logging',
 ]
 
 # Register supported languages
diff --git a/bqskit/compiler/__init__.py b/bqskit/compiler/__init__.py
index ccac308d1..f7048aa56 100644
--- a/bqskit/compiler/__init__.py
+++ b/bqskit/compiler/__init__.py
@@ -26,23 +26,39 @@
 
     BasePass
     Compiler
-    CompilationTask
     MachineModel
+    GateSet
+    GateSetLike
+    PassData
+    CompilationStatus
+    CompilationTask
+    Workflow
+    WorkflowLike
 """
 from __future__ import annotations
 
 from bqskit.compiler.basepass import BasePass
 from bqskit.compiler.compile import compile
 from bqskit.compiler.compiler import Compiler
-from bqskit.compiler.executor import Executor
+from bqskit.compiler.gateset import GateSet
+from bqskit.compiler.gateset import GateSetLike
 from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
+from bqskit.compiler.status import CompilationStatus
 from bqskit.compiler.task import CompilationTask
+from bqskit.compiler.workflow import Workflow
+from bqskit.compiler.workflow import WorkflowLike
 
 __all__ = [
     'BasePass',
     'compile',
     'Compiler',
-    'Executor',
+    'GateSet',
+    'GateSetLike',
     'MachineModel',
+    'PassData',
+    'CompilationStatus',
     'CompilationTask',
+    'Workflow',
+    'WorkflowLike',
 ]
diff --git a/bqskit/compiler/basepass.py b/bqskit/compiler/basepass.py
index 44da2a0bb..472f2cffb 100644
--- a/bqskit/compiler/basepass.py
+++ b/bqskit/compiler/basepass.py
@@ -2,25 +2,19 @@
 from __future__ import annotations
 
 import abc
-import logging
-from typing import Any
-from typing import Callable
-from typing import TypeVar
+import warnings
+from typing import TYPE_CHECKING
 
-from distributed import get_client
-from distributed import rejoin
-from distributed import secede
-
-from bqskit.compiler.machine import MachineModel
-from bqskit.ir.circuit import Circuit
-from bqskit.qis.graph import CouplingGraph
-from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
-from bqskit.utils.typing import is_iterable
-from bqskit.utils.typing import is_sequence
-
-_logger = logging.getLogger(__name__)
-
-T = TypeVar('T')
+if TYPE_CHECKING:
+    from typing import Any
+    from bqskit.compiler.machine import MachineModel
+    from bqskit.compiler.passdata import PassData
+    from bqskit.compiler.workflow import Workflow
+    from bqskit.ir.circuit import Circuit
+    from bqskit.qis.graph import CouplingGraph
+    from bqskit.qis.state.system import StateSystem
+    from bqskit.qis.state.state import StateVector
+    from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
 class BasePass(abc.ABC):
@@ -33,8 +27,8 @@ class BasePass(abc.ABC):
 
     Examples:
         >>> class PrintCNOTCountPass(BasePass):
-        ...     def run(self, circ: Circuit, data: dict[str, Any] = {}) -> None:
-        ...         print(f"Number of CNOTs: {circ.count(CNOTGate())}")
+        ...     async def run(self, circuit: Circuit, data: PassData) -> None:
+        ...         print(f"Number of CNOTs: {circuit.count(CNOTGate())}")
     """
 
     @property
@@ -43,18 +37,16 @@ def name(self) -> str:
         return self.__class__.__name__
 
     @abc.abstractmethod
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """
         Perform the pass's operation on `circuit`.
 
         Args:
             circuit (Circuit): The circuit to operate on.
 
-            data (Dict[str, Any]): Associated data for the pass.
+            data (PassData): Associated data for the pass.
                 Can be used to get auxillary information from previous
                 passes and to store information for future passes.
-                This function should never error based on what is in
-                this dictionary.
 
         Note:
             - This function should be self-contained and have no side effects.
@@ -62,174 +54,121 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
         """
 
     @staticmethod
-    def get_model(
-        circuit: Circuit | UnitaryMatrix,
-        data: dict[str, Any],
-    ) -> MachineModel:
+    def get_model(_: Any, data: PassData) -> MachineModel:
         """
         Retrieve the machine model from the data dictionary.
 
-        Args:
-            circuit (Circuit): The pass circuit.
-
-            data (dict[str, Any]): The data dictionary.
-
-        Returns:
-            MachineModel: The machine model in the data dictionary, or
-                a default one.
+        (Deprecated)
         """
-
-        if len(data) == 0:
-            return MachineModel(circuit.num_qudits)
-
-        if 'machine_model' not in data:
-            data['machine_model'] = MachineModel(circuit.num_qudits)
-
-        if (
-            not isinstance(data['machine_model'], MachineModel)
-            or data['machine_model'].num_qudits < circuit.num_qudits
-        ):
-            _logger.warning('Expected machine_model to be a valid model.')
-            return MachineModel(circuit.num_qudits)
-
-        return data['machine_model']
+        warnings.warn(
+            'BasePass calls to retrieve elements from the data dictionary'
+            ' are now deprecated. We have upgraded the features of the data'
+            ' dictionary. It can now be used directly to retrieve elements'
+            " and provide defaults if they don't exist. This warning will"
+            ' become an error in the future.',
+            DeprecationWarning,
+        )
+        return data.model
 
     @staticmethod
-    def get_placement(
-        circuit: Circuit | UnitaryMatrix,
-        data: dict[str, Any],
-    ) -> list[int]:
+    def get_placement(_: Any, data: PassData) -> list[int]:
         """
         Retrieve the logical to physical qubit map from the data dictionary.
 
-        Args:
-            circuit (Circuit | UnitaryMatrix): The pass circuit.
-
-            data (dict[str, Any]): The data dictionary.
-
-        Returns:
-            (list[int]): A list with length equal to circuit.num_qudits.
-            Logical qubit i is mapped to the physical qubit described
-            by the i-th element in the list.
+        (Deprecated)
         """
-        # Default placement is trivial map: i -> i
-        default_placement = list(range(circuit.num_qudits))
-        model = BasePass.get_model(circuit, data)
-        sg = model.coupling_graph.get_subgraph(default_placement)
-
-        # Check in data for existing placement
-        if 'placement' in data:
-            p = data['placement']
-
-            # Check it's valid
-            if is_sequence(p) and len(p) == circuit.num_qudits:
-                return data['placement']
-
-        # If none found try to return default placement
-        if sg.is_fully_connected():
-            if len(data) != 0:
-                data['placement'] = default_placement
-            return default_placement
-
-        raise RuntimeError(
-            "No valid placement found and trivial one doesn't work."
-            '\nConsider running a placement pass before the current pass.',
+        warnings.warn(
+            'BasePass calls to retrieve elements from the data dictionary'
+            ' are now deprecated. We have upgraded the features of the data'
+            ' dictionary. It can now be used directly to retrieve elements'
+            " and provide defaults if they don't exist. This warning will"
+            ' become an error in the future.',
+            DeprecationWarning,
         )
+        return data.placement
 
     @staticmethod
-    def get_connectivity(
-        circuit: Circuit | UnitaryMatrix,
-        data: dict[str, Any],
-    ) -> CouplingGraph:
+    def get_connectivity(_: Any, data: PassData) -> CouplingGraph:
         """
         Retrieve the current connectivity of the circuit.
 
-        Args:
-            circuit (Circuit | UnitaryMatrix): The pass circuit.
-
-            data (dict[str, Any]): The data dictionary.
-
-        Returns:
-            (list[int]): A coupling graph of the connectivity.
+        (Deprecated)
         """
-        model = BasePass.get_model(circuit, data)
-        try:
-            placement = BasePass.get_placement(circuit, data)
-        except RuntimeError:
-            placement = list(range(circuit.num_qudits))
-        return model.coupling_graph.get_subgraph(placement)
+        warnings.warn(
+            'BasePass calls to retrieve elements from the data dictionary'
+            ' are now deprecated. We have upgraded the features of the data'
+            ' dictionary. It can now be used directly to retrieve elements'
+            " and provide defaults if they don't exist. This warning will"
+            ' become an error in the future.',
+            DeprecationWarning,
+        )
+        return data.connectivity
 
     @staticmethod
-    def get_target(circuit: Circuit, data: dict[str, Any]) -> UnitaryMatrix:
+    def get_target(
+        _: Any,
+        data: PassData,
+    ) -> UnitaryMatrix | StateVector | StateSystem:
         """
-        Retrieve the target unitary from the data dictionary.
+        Retrieve the target from the data dictionary.
 
-        Args:
-            circuit (Circuit): The pass circuit.
+        (Deprecated)
+        """
+        warnings.warn(
+            'BasePass calls to retrieve elements from the data dictionary'
+            ' are now deprecated. We have upgraded the features of the data'
+            ' dictionary. It can now be used directly to retrieve elements'
+            " and provide defaults if they don't exist. This warning will"
+            ' become an error in the future.',
+            DeprecationWarning,
+        )
+        return data.target
 
-            data (dict[str, Any]): The data dictionary.
+    @staticmethod
+    def in_parallel(data: dict[str, Any]) -> bool:
+        """
+        Return true if pass is being executed in a parallel.
 
-        Returns:
-            UnitaryMatrix: The target unitary.
+        (Deprecated)
         """
-        if len(data) == 0:
-            return circuit.get_unitary()
+        warnings.warn(
+            'BasePass calls to `in_parallel` are deprecated and will always'
+            ' return True. This warning will become an error in the future.',
+            DeprecationWarning,
+        )
+        return True
 
-        if 'target_unitary' not in data:
-            data['target_unitary'] = circuit.get_unitary()
+    @staticmethod
+    def execute(*args: Any, **kwargs: Any) -> Any:
+        """
+        Map a function over iterable arguments in parallel.
 
-        if not isinstance(data['target_unitary'], UnitaryMatrix):
-            _logger.warning('Expected target_unitary to be a unitary.')
-            return circuit.get_unitary()
+        (Defunct)
+        """
+        raise RuntimeError(
+            'Since Dask was removed, the execute function became defunct.'
+            ' We have switched to using `get_runtime().map(...)`. You can'
+            ' import `get_runtime` from `bqskit.runtime`, and alongside a'
+            ' an `await` keyword, should be a  drop-in replacement for the'
+            ' execute function. See the following link for more info: '
+            'https://bqskit.readthedocs.io/en/latest/source/runtime.html'
+            'In a future version, this error will become an AttributeError.',
+        )
 
-        return data['target_unitary']
 
-    @staticmethod
-    def in_parallel(data: dict[str, Any]) -> bool:
-        """Return true if pass is being executed in a parallel environment."""
-        if 'parallel' not in data:
-            return False
+async def _sub_do_work(
+    workflow: Workflow,
+    circuit: Circuit,
+    data: PassData,
+) -> tuple[Circuit, PassData]:
+    """Execute a sequence of passes on circuit."""
+    if 'calculate_error_bound' in data and data['calculate_error_bound']:
+        old_utry = circuit.get_unitary()
 
-        if not isinstance(data['parallel'], bool):
-            _logger.warning('Expected parallel to be a bool.')
-            return False
+    await workflow.run(circuit, data)
 
-        return data['parallel']
+    if 'calculate_error_bound' in data and data['calculate_error_bound']:
+        new_utry = circuit.get_unitary()
+        data.error = new_utry.get_distance_from(old_utry)
 
-    @staticmethod
-    def execute(
-        data: dict[str, Any],
-        fn: Callable[..., T],
-        *args: Any,
-        **kwargs: Any,
-    ) -> list[T]:
-        """Map a function over iterable arguments in parallel."""
-
-        if not all(is_iterable(arg) for arg in args):
-            raise ValueError('Each argument must be a sized iterable')
-
-        if not all(len(arg) == len(args[0]) for arg in args):
-            raise ValueError('Each iterable argument must have same length.')
-
-        # Execute using dask if dask is started and there are multiple tasks
-        if BasePass.in_parallel(data) and len(args[0]) > 1:
-            if fn == Circuit.instantiate:
-                kwargs['parallel'] = True
-
-            client = get_client()
-            futures = client.map(fn, *args, **kwargs)
-            secede()
-            results = client.gather(futures)
-            rejoin()
-            return results
-
-        # Otherwise execute in current process
-        else:
-            results = []
-            if len(args) == 1:
-                for arg in args[0]:
-                    results.append(fn(arg, **kwargs))
-            else:
-                for subargs in zip(*args):
-                    results.append(fn(*subargs, **kwargs))
-            return results
+    return circuit, data
diff --git a/bqskit/compiler/compile.py b/bqskit/compiler/compile.py
index 41679806e..07e4e8296 100644
--- a/bqskit/compiler/compile.py
+++ b/bqskit/compiler/compile.py
@@ -1,6 +1,7 @@
 """This module defines a standard `compile` function using BQSKit."""
 from __future__ import annotations
 
+import functools
 import logging
 import warnings
 from typing import Any
@@ -9,41 +10,92 @@
 
 import numpy as np
 
-from bqskit.compiler.basepass import BasePass
 from bqskit.compiler.compiler import Compiler
 from bqskit.compiler.machine import MachineModel
-from bqskit.compiler.task import CompilationTask
+from bqskit.compiler.workflow import Workflow
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import RZGate
-from bqskit.ir.gates import SqrtXGate
-from bqskit.ir.gates import SwapGate
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates.barrier import BarrierPlaceholder
 from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.swap import SwapGate
+from bqskit.ir.gates.constant.sx import SqrtXGate
 from bqskit.ir.gates.measure import MeasurementPlaceholder
+from bqskit.ir.gates.parameterized.rz import RZGate
 from bqskit.ir.gates.parameterized.u3 import U3Gate
+from bqskit.ir.gates.parameterized.u8 import U8Gate
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
 from bqskit.ir.operation import Operation
 from bqskit.ir.opt import HilbertSchmidtCostGenerator
 from bqskit.ir.opt import ScipyMinimizer
-from bqskit.passes import *
+from bqskit.ir.opt.minimizers.lbfgs import LBFGSMinimizer
+from bqskit.passes.control.foreach import ForEachBlockPass
+from bqskit.passes.control.ifthenelse import IfThenElsePass
+from bqskit.passes.control.predicates.change import ChangePredicate
+from bqskit.passes.control.predicates.count import GateCountPredicate
+from bqskit.passes.control.predicates.multi import MultiPhysicalPredicate
+from bqskit.passes.control.predicates.notpredicate import NotPredicate
+from bqskit.passes.control.predicates.single import SinglePhysicalPredicate
+from bqskit.passes.control.predicates.width import WidthPredicate
+from bqskit.passes.control.whileloop import WhileLoopPass
+from bqskit.passes.group import PassGroup
+from bqskit.passes.mapping.apply import ApplyPlacement
+from bqskit.passes.mapping.embed import EmbedAllPermutationsPass
+from bqskit.passes.mapping.layout.pam import PAMLayoutPass
+from bqskit.passes.mapping.layout.sabre import GeneralizedSabreLayoutPass
+from bqskit.passes.mapping.placement.greedy import GreedyPlacementPass
+from bqskit.passes.mapping.routing.pam import PAMRoutingPass
+from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass
+from bqskit.passes.mapping.setmodel import SetModelPass
+from bqskit.passes.mapping.topology import SubtopologySelectionPass
+from bqskit.passes.measure import ExtractMeasurements
+from bqskit.passes.measure import RestoreMeasurements
+from bqskit.passes.noop import NOOPPass
+from bqskit.passes.partitioning.quick import QuickPartitioner
+from bqskit.passes.partitioning.single import GroupSingleQuditGatePass
+from bqskit.passes.processing.rebase import Rebase2QuditGatePass
+from bqskit.passes.processing.scan import ScanningGateRemovalPass
+from bqskit.passes.rules.u3 import U3Decomposition
+from bqskit.passes.rules.zxzxz import ZXZXZDecomposition
+from bqskit.passes.search.generators.single import SingleQuditLayerGenerator
+from bqskit.passes.search.heuristics.dijkstra import DijkstraHeuristic
+from bqskit.passes.synthesis.leap import LEAPSynthesisPass
+from bqskit.passes.synthesis.pas import PermutationAwareSynthesisPass
+from bqskit.passes.synthesis.qsearch import QSearchSynthesisPass
+from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.passes.synthesis.target import SetTargetPass
+from bqskit.passes.util.extend import ExtendBlockSizePass
+from bqskit.passes.util.fill import FillSingleQuditGatesPass
+from bqskit.passes.util.log import LogErrorPass
+from bqskit.passes.util.log import LogPass
+from bqskit.passes.util.random import SetRandomSeedPass
+from bqskit.passes.util.unfold import UnfoldPass
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
+from bqskit.qis.state.system import StateSystemLike
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 from bqskit.utils.typing import is_integer
 from bqskit.utils.typing import is_real_number
-_logger = logging.getLogger(__name__)
+
+
 if TYPE_CHECKING:
     from bqskit.qis.unitary import UnitaryLike
     from bqskit.qis.state import StateLike
+    from bqskit.compiler.basepass import BasePass
+
+
+_logger = logging.getLogger(__name__)
 
 
 def compile(
-    input: Circuit | UnitaryLike | StateLike,
+    input: Circuit | UnitaryLike | StateLike | StateSystemLike,
     model: MachineModel | None = None,
     optimization_level: int = 1,
     max_synthesis_size: int = 3,
-    synthesis_epsilon: float = 1e-10,
+    synthesis_epsilon: float = 1e-8,
     error_threshold: float | None = None,
     error_sim_size: int = 8,
     compiler: Compiler | None = None,
+    seed: int | None = None,
     *compiler_args: Any,
     **compiler_kwargs: Any,
 ) -> Circuit:
@@ -90,12 +142,15 @@ def compile(
             creating one. Save on startup time by passing a compiler in
             when calling `compile` multiple times. (Default: None)
 
+        seed (int | None): Set a seed for the compile function for
+            better reproducibility. If left as None, will not set seed.
+
         compiler_args (Any): Passed directly to BQSKit compiler construction.
-            Arguments for connecting to a dask cluster can go here.
+            Arguments for connecting to a cluster can go here.
             See :class:`Compiler` for more info.
 
         compiler_kwargs (Any): Passed directly to BQSKit compiler construction.
-            Arguments for connecting to a dask cluster can go here.
+            Arguments for connecting to a cluster can go here.
             See :class:`Compiler` for more info.
 
     Returns:
@@ -118,10 +173,13 @@ def compile(
         elif StateVector.is_pure_state(input):
             input = StateVector(input)
 
+        elif StateSystem.is_state_system(input):
+            input = StateSystem(input)
+
         else:
             raise TypeError(
-                'Input is neither a circuit, a unitary, nor a state.'
-                f' Got {type(input)}.',
+                'Input is neither a circuit, a unitary, a state system'
+                f', nor a state. Got {type(input)}.',
             )
     except Exception as e:
         raise TypeError(
@@ -130,17 +188,19 @@ def compile(
             ' circuit, unitary, or state.',
         ) from e
 
-    assert isinstance(input, (Circuit, UnitaryMatrix, StateVector))
+    assert isinstance(input, (Circuit, UnitaryMatrix, StateVector, StateSystem))
 
-    if not all(r == 2 for r in input.radixes):
+    if not all(r == input.radixes[0] for r in input.radixes):
         raise ValueError(
             'Currently, can only automatically build a workflow '
-            'for qubit-only systems.',
-        )  # TODO
+            'for same-level systems, such as qubit-only or qutrit-only'
+            'systems. Heterogenous-radix systems are not yet supported'
+            'with the standard workflows.',
+        )
 
     # Check `model`
     if model is None:
-        model = MachineModel(input.num_qudits)
+        model = MachineModel(input.num_qudits, radixes=input.radixes)
 
     if not isinstance(model, MachineModel):
         raise TypeError(f'Expected MachineModel for model, got {type(model)}.')
@@ -148,11 +208,13 @@ def compile(
     if model.num_qudits < input.num_qudits:
         raise ValueError('Machine is too small for circuit.')
 
-    if not all(r == 2 for r in model.radixes):
+    if not all(r == input.radixes[0] for r in model.radixes):
         raise ValueError(
             'Currently, can only automatically build a workflow '
-            'for qubit-only systems.',
-        )  # TODO
+            'for same-level systems, such as qubit-only or qutrit-only'
+            'systems. Heterogenous-radix systems are not yet supported'
+            'with the standard workflows.',
+        )
 
     model_mq_gates = [g for g in model.gate_set if g.num_qudits >= 2]
 
@@ -243,12 +305,83 @@ def compile(
             f'maximum synthesis size: {error_sim_size} < {max_synthesis_size}.',
         )
 
+    # Check `seed`
+    if seed is not None and not is_integer(seed):
+        raise TypeError(f'Expected integer for seed, got {type(seed)}.')
+
     # Build workflow
+    workflow = build_workflow(
+        input,
+        model,
+        optimization_level,
+        synthesis_epsilon,
+        max_synthesis_size,
+        error_threshold,
+        error_sim_size,
+        seed,
+    )
+    if isinstance(input, Circuit):
+        in_circuit = input
+
+    elif isinstance(input, UnitaryMatrix):
+        in_circuit = Circuit.from_unitary(input)
+
+    else:
+        in_circuit = Circuit(1)
+
+    # Connect to or construct a Compiler
+    managed_compiler = compiler is None
+
+    if managed_compiler:
+        compiler = Compiler(*compiler_args, **compiler_kwargs)
+
+    elif not isinstance(compiler, Compiler):
+        raise TypeError(
+            'Expected Compiler object for compiler parameter'
+            f', got {type(compiler)}.',
+        )
+
+    # Perform the compilation
+    out, data = compiler.compile(in_circuit, workflow, True)
+
+    # Log error if necessary
+    if error_threshold is not None:
+        error = data.error
+        nonsq_error = 1 - np.sqrt(max(1 - (error * error), 0))
+        if nonsq_error > error_threshold:
+            warnings.warn(
+                'Upper bound on error is greater than set threshold:'
+                f' {nonsq_error} > {error_threshold}.',
+            )
+
+    # Close managed compiler
+    if managed_compiler:
+        compiler.close()
+
+    return out
+
+
+def build_workflow(
+    input: Circuit | UnitaryMatrix | StateVector | StateSystem,
+    model: MachineModel,
+    optimization_level: int = 1,
+    synthesis_epsilon: float = 1e-10,
+    max_synthesis_size: int = 3,
+    error_threshold: float | None = None,
+    error_sim_size: int = 8,
+    seed: int | None = None,
+) -> Workflow:
+    """Build a BQSKit Off-the-Shelf workflow, see :func:`compile` for info."""
     if isinstance(input, Circuit):
         if input.num_qudits > max_synthesis_size:
             if any(
                 g.num_qudits > max_synthesis_size
-                and not isinstance(g, MeasurementPlaceholder)
+                and not isinstance(
+                    g, (
+                        MeasurementPlaceholder,
+                        BarrierPlaceholder,
+                    ),
+                )
                 for g in input.gate_set
             ):
                 raise ValueError(
@@ -256,7 +389,7 @@ def compile(
                     ' max_synthesis_size.\nConsider adjusting it.',
                 )
 
-        task = _circuit_workflow(
+        return _circuit_workflow(
             input,
             model,
             optimization_level,
@@ -264,6 +397,7 @@ def compile(
             max_synthesis_size,
             error_threshold,
             error_sim_size,
+            seed,
         )
 
     elif isinstance(input, UnitaryMatrix):
@@ -275,7 +409,7 @@ def compile(
                 'Consider adjusting max_synthesis_size or checking the input.',
             )
 
-        task = _circuit_workflow(
+        return _circuit_workflow(
             Circuit.from_unitary(utry),
             model,
             optimization_level,
@@ -283,6 +417,7 @@ def compile(
             max_synthesis_size,
             error_threshold,
             error_sim_size,
+            seed,
         )
 
     elif isinstance(input, StateVector):
@@ -294,7 +429,7 @@ def compile(
                 'Consider adjusting max_synthesis_size or checking the input.',
             )
 
-        task = _stateprep_workflow(
+        return _stateprep_workflow(
             state,
             model,
             optimization_level,
@@ -302,38 +437,30 @@ def compile(
             max_synthesis_size,
             error_threshold,
             error_sim_size,
+            seed,
         )
 
-    # Connect to or construct a Compiler
-    managed_compiler = compiler is None
-
-    if managed_compiler:
-        compiler = Compiler(*compiler_args, **compiler_kwargs)
-
-    elif not isinstance(compiler, Compiler):
-        raise TypeError(
-            'Expected Compiler object for compiler parameter'
-            f', got {type(compiler)}.',
-        )
-
-    # Perform the compilation
-    out = compiler.compile(task)
+    elif isinstance(input, StateSystem):
+        statemap = input
 
-    # Log error if necessary
-    if error_threshold is not None:
-        error = compiler.analyze(task, 'error')
-        nonsq_error = 1 - np.sqrt(max(1 - (error * error), 0))
-        if nonsq_error > error_threshold:
-            warnings.warn(
-                'Upper bound on error is greater than set threshold:'
-                f' {nonsq_error} > {error_threshold}.',
+        if statemap.num_qudits > max_synthesis_size:
+            raise ValueError(
+                'Cannot synthesize state larger than max_synthesis_size.\n'
+                'Consider adjusting max_synthesis_size or checking the input.',
             )
 
-    # Close managed compiler
-    if managed_compiler:
-        compiler.close()
+        return _statemap_workflow(
+            statemap,
+            model,
+            optimization_level,
+            synthesis_epsilon,
+            max_synthesis_size,
+            error_threshold,
+            error_sim_size,
+            seed,
+        )
 
-    return out
+    raise TypeError(f'Unexpected input type: {type(input)}.')
 
 
 def _circuit_workflow(
@@ -341,10 +468,11 @@ def _circuit_workflow(
     model: MachineModel,
     optimization_level: int = 1,
     synthesis_epsilon: float = 1e-10,
-    max_synthesis_size: int = 4,
+    max_synthesis_size: int = 3,
     error_threshold: float | None = None,
     error_sim_size: int = 8,
-) -> CompilationTask:
+    seed: int | None = None,
+) -> Workflow:
     """Build standard workflow for circuit compilation."""
     workflow_builders = [
         _opt1_workflow,
@@ -353,7 +481,8 @@ def _circuit_workflow(
         _opt4_workflow,
     ]
     workflow_builder = workflow_builders[optimization_level - 1]
-    workflow = [UnfoldPass(), ExtractMeasurements()]
+    workflow: list[BasePass] = [] if seed is None else [SetRandomSeedPass(seed)]
+    workflow += [UnfoldPass(), ExtractMeasurements()]
     workflow += workflow_builder(
         circuit,
         model,
@@ -363,19 +492,19 @@ def _circuit_workflow(
         error_sim_size,
     )
     workflow += [RestoreMeasurements()]
-    return CompilationTask(circuit, workflow)
+    return Workflow(workflow)
 
 
 def _opt1_workflow(
     circuit: Circuit,
     model: MachineModel,
     synthesis_epsilon: float = 1e-10,
-    max_synthesis_size: int = 4,
+    max_synthesis_size: int = 3,
     error_threshold: float | None = None,
     error_sim_size: int = 8,
 ) -> list[BasePass]:
     """Build Optimization Level 1 workflow for circuit compilation."""
-    layer_gen = _get_layer_gen(model)
+    layer_gen = model.gate_set.build_mq_layer_generator()
     scan_mq = ScanningGateRemovalPass(
         success_threshold=synthesis_epsilon,
         collection_filter=_mq_gate_collection_filter,
@@ -397,26 +526,33 @@ def _opt1_workflow(
             if g.num_qudits != 1
         )
         non_native_gates = [
-            g for g in circuit.gate_set
+            g for g in circuit.gate_set_no_blocks
             if g not in model.gate_set
         ]
         non_native_tq_gates = [
             g for g in non_native_gates
             if g.num_qudits == 2
         ]
-        if SwapGate() not in model.gate_set:
-            non_native_tq_gates.append(SwapGate())
+        if SwapGate(model.radixes[0]) not in model.gate_set:
+            non_native_tq_gates.append(SwapGate(model.radixes[0]))
         native_tq_gates = [g for g in model.gate_set if g.num_qudits == 2]
 
-        all_gates = model.gate_set.union(circuit.gate_set)
+        all_gates = model.gate_set.union(circuit.gate_set_no_blocks)
         if any(g.num_qudits > 2 for g in all_gates):
             multi_qudit_gate_rebase: BasePass = direct_synthesis
         else:
+            if model.radixes[0] == 2:
+                sq_gate: Gate = U3Gate()
+            elif model.radixes[0] == 3:
+                sq_gate = U8Gate()
+            else:
+                sq_gate = VariableUnitaryGate(1, [model.radixes[0]])
             multi_qudit_gate_rebase = Rebase2QuditGatePass(
                 non_native_tq_gates,
                 native_tq_gates,
                 max_depth=3,
                 max_retries=5,
+                single_qudit_gate=sq_gate,
             )
     else:
         smallest_entangler_size = 1
@@ -436,7 +572,7 @@ def _opt1_workflow(
                 ForEachBlockPass(
                     ForEachBlockPass(
                         [
-                            FillSingleQuditGatesPass(synthesis_epsilon),
+                            FillSingleQuditGatesPass(),
                             IfThenElsePass(
                                 NotPredicate(MultiPhysicalPredicate()),
                                 multi_qudit_gate_rebase,
@@ -468,7 +604,7 @@ def _opt1_workflow(
                 ForEachBlockPass(
                     ForEachBlockPass(
                         [
-                            FillSingleQuditGatesPass(synthesis_epsilon),
+                            FillSingleQuditGatesPass(),
                             IfThenElsePass(
                                 NotPredicate(MultiPhysicalPredicate()),
                                 multi_qudit_gate_rebase,
@@ -502,7 +638,7 @@ def _opt2_workflow(
 ) -> list[BasePass]:
     """Build Optimization Level 2 workflow for circuit compilation."""
     inst_ops = {'multistarts': 4, 'ftol': 5e-12, 'gtol': 1e-14}
-    layer_gen = _get_layer_gen(model)
+    layer_gen = model.gate_set.build_mq_layer_generator()
     scan_mq = ScanningGateRemovalPass(
         success_threshold=synthesis_epsilon,
         collection_filter=_mq_gate_collection_filter,
@@ -527,26 +663,33 @@ def _opt2_workflow(
             if g.num_qudits != 1
         )
         non_native_gates = [
-            g for g in circuit.gate_set
+            g for g in circuit.gate_set_no_blocks
             if g not in model.gate_set
         ]
         non_native_tq_gates = [
             g for g in non_native_gates
             if g.num_qudits == 2
         ]
-        if SwapGate() not in model.gate_set:
-            non_native_tq_gates.append(SwapGate())
+        if SwapGate(model.radixes[0]) not in model.gate_set:
+            non_native_tq_gates.append(SwapGate(model.radixes[0]))
         native_tq_gates = [g for g in model.gate_set if g.num_qudits == 2]
 
-        all_gates = model.gate_set.union(circuit.gate_set)
+        all_gates = model.gate_set.union(circuit.gate_set_no_blocks)
         if any(g.num_qudits > 2 for g in all_gates):
             multi_qudit_gate_rebase: BasePass = direct_synthesis
         else:
+            if model.radixes[0] == 2:
+                sq_gate: Gate = U3Gate()
+            elif model.radixes[0] == 3:
+                sq_gate = U8Gate()
+            else:
+                sq_gate = VariableUnitaryGate(1, [model.radixes[0]])
             multi_qudit_gate_rebase = Rebase2QuditGatePass(
                 non_native_tq_gates,
                 native_tq_gates,
                 max_depth=3,
                 max_retries=5,
+                single_qudit_gate=sq_gate,
             )
     else:
         smallest_entangler_size = 1
@@ -566,7 +709,7 @@ def _opt2_workflow(
                 ForEachBlockPass(
                     ForEachBlockPass(
                         [
-                            FillSingleQuditGatesPass(synthesis_epsilon),
+                            FillSingleQuditGatesPass(),
                             IfThenElsePass(
                                 NotPredicate(MultiPhysicalPredicate()),
                                 multi_qudit_gate_rebase,
@@ -598,7 +741,7 @@ def _opt2_workflow(
                 ForEachBlockPass(
                     ForEachBlockPass(
                         [
-                            FillSingleQuditGatesPass(synthesis_epsilon),
+                            FillSingleQuditGatesPass(),
                             IfThenElsePass(
                                 NotPredicate(MultiPhysicalPredicate()),
                                 multi_qudit_gate_rebase,
@@ -637,7 +780,7 @@ def _opt3_workflow(
     circuit: Circuit,
     model: MachineModel,
     synthesis_epsilon: float = 1e-10,
-    max_synthesis_size: int = 4,
+    max_synthesis_size: int = 3,
     error_threshold: float | None = None,
     error_sim_size: int = 8,
 ) -> list[BasePass]:
@@ -648,7 +791,7 @@ def _opt3_workflow(
         'ftol': 5e-16,
         'gtol': 1e-15,
     }
-    layer_gen = _get_layer_gen(model)
+    layer_gen = model.gate_set.build_mq_layer_generator()
     scan = ScanningGateRemovalPass(success_threshold=synthesis_epsilon)
     scan_mq = ScanningGateRemovalPass(
         success_threshold=synthesis_epsilon,
@@ -673,27 +816,34 @@ def _opt3_workflow(
             if g.num_qudits != 1
         )
         non_native_gates = [
-            g for g in circuit.gate_set
+            g for g in circuit.gate_set_no_blocks
             if g not in model.gate_set
         ]
         non_native_tq_gates = [
             g for g in non_native_gates
             if g.num_qudits == 2
         ]
-        if SwapGate() not in model.gate_set:
-            non_native_tq_gates.append(SwapGate())
+        if SwapGate(model.radixes[0]) not in model.gate_set:
+            non_native_tq_gates.append(SwapGate(model.radixes[0]))
         native_tq_gates = [g for g in model.gate_set if g.num_qudits == 2]
         native_mq_gates = [g for g in model.gate_set if g.num_qudits >= 2]
 
-        all_gates = model.gate_set.union(circuit.gate_set)
+        all_gates = model.gate_set.union(circuit.gate_set_no_blocks)
         if any(g.num_qudits > 2 for g in all_gates):
             multi_qudit_gate_rebase: BasePass = direct_synthesis
         else:
+            if model.radixes[0] == 2:
+                sq_gate: Gate = U3Gate()
+            elif model.radixes[0] == 3:
+                sq_gate = U8Gate()
+            else:
+                sq_gate = VariableUnitaryGate(1, [model.radixes[0]])
             multi_qudit_gate_rebase = Rebase2QuditGatePass(
                 non_native_tq_gates,
                 native_tq_gates,
                 max_depth=3,
                 max_retries=5,
+                single_qudit_gate=sq_gate,
             )
     else:
         smallest_entangler_size = 1
@@ -714,7 +864,7 @@ def _opt3_workflow(
                 ForEachBlockPass(
                     ForEachBlockPass(
                         [
-                            FillSingleQuditGatesPass(synthesis_epsilon),
+                            FillSingleQuditGatesPass(),
                             IfThenElsePass(
                                 NotPredicate(MultiPhysicalPredicate()),
                                 multi_qudit_gate_rebase,
@@ -746,7 +896,7 @@ def _opt3_workflow(
                 ForEachBlockPass(
                     ForEachBlockPass(
                         [
-                            FillSingleQuditGatesPass(synthesis_epsilon),
+                            FillSingleQuditGatesPass(),
                             IfThenElsePass(
                                 NotPredicate(MultiPhysicalPredicate()),
                                 multi_qudit_gate_rebase,
@@ -813,13 +963,207 @@ def _opt4_workflow(
     circuit: Circuit,
     model: MachineModel,
     synthesis_epsilon: float = 1e-10,
-    max_synthesis_size: int = 4,
+    max_synthesis_size: int = 3,
     error_threshold: float | None = None,
     error_sim_size: int = 8,
 ) -> list[BasePass]:
     """Build optimization Level 4 workflow for circuit compilation."""
-    # TODO
-    raise NotImplementedError('Optimization level 4 is not yet ready.')
+    if error_threshold is not None:
+        _logger.warning(
+            'Automated error upper bound calculated is not yet'
+            ' ready for opt level 4.',
+        )
+
+    if max_synthesis_size > 3:
+        _logger.warning(
+            'It is currently recommended to set max_synthesis_size to 3'
+            ' for optimization level 4. This may change in the future.',
+        )
+
+    inst_ops = {
+        'multistarts': 8,
+        'method': 'minimization',
+        'ftol': 5e-16,
+        'gtol': 1e-15,
+    }
+    layer_gen = model.gate_set.build_mq_layer_generator()
+    scan = ScanningGateRemovalPass(success_threshold=synthesis_epsilon)
+    scan_mq = ScanningGateRemovalPass(
+        success_threshold=synthesis_epsilon,
+        collection_filter=_mq_gate_collection_filter,
+    )
+    qsearch = QSearchSynthesisPass(
+        success_threshold=synthesis_epsilon,
+        layer_generator=layer_gen,
+        instantiate_options=inst_ops,
+    )
+    leap = LEAPSynthesisPass(
+        success_threshold=synthesis_epsilon,
+        layer_generator=layer_gen,
+        instantiate_options=inst_ops,
+        min_prefix_size=7,
+    )
+    direct_synthesis = IfThenElsePass(WidthPredicate(4), qsearch, leap)
+    single_qudit_gate_rebase = _get_single_qudit_gate_rebase_pass(model)
+    if circuit.num_qudits > 1:
+        smallest_entangler_size = min(
+            g.num_qudits for g in model.gate_set
+            if g.num_qudits != 1
+        )
+        non_native_gates = [
+            g for g in circuit.gate_set
+            if g not in model.gate_set
+        ]
+        non_native_tq_gates = [
+            g for g in non_native_gates
+            if g.num_qudits == 2
+        ]
+        if SwapGate(model.radixes[0]) not in model.gate_set:
+            non_native_tq_gates.append(SwapGate(model.radixes[0]))
+        native_tq_gates = [g for g in model.gate_set if g.num_qudits == 2]
+        native_mq_gates = [g for g in model.gate_set if g.num_qudits >= 2]
+
+        all_gates = model.gate_set.union(circuit.gate_set)
+        if any(g.num_qudits > 2 for g in all_gates):
+            multi_qudit_gate_rebase: BasePass = direct_synthesis
+        else:
+            if model.radixes[0] == 2:
+                sq_gate: Gate = U3Gate()
+            elif model.radixes[0] == 3:
+                sq_gate = U8Gate()
+            else:
+                sq_gate = VariableUnitaryGate(1, [model.radixes[0]])
+            multi_qudit_gate_rebase = Rebase2QuditGatePass(
+                non_native_tq_gates,
+                native_tq_gates,
+                max_depth=3,
+                max_retries=5,
+                single_qudit_gate=sq_gate,
+            )
+    else:
+        smallest_entangler_size = 1
+        multi_qudit_gate_rebase = NOOPPass()
+        native_mq_gates = []
+
+    return [
+        SetModelPass(model),
+
+        # Multi-qudit gate retargeting
+        IfThenElsePass(
+            NotPredicate(WidthPredicate(2)),
+            [
+                LogPass('Retargeting multi-qudit gates.'),
+                QuickPartitioner(max_synthesis_size),
+                ExtendBlockSizePass(smallest_entangler_size),
+                QuickPartitioner(error_sim_size),
+                ForEachBlockPass(
+                    ForEachBlockPass(
+                        [
+                            FillSingleQuditGatesPass(),
+                            IfThenElsePass(
+                                NotPredicate(MultiPhysicalPredicate()),
+                                multi_qudit_gate_rebase,
+                                scan_mq,
+                            ),
+                        ],
+                        replace_filter=_gen_replace_filter(model),
+                    ),
+                    calculate_error_bound=(error_threshold is not None),
+                ),
+                UnfoldPass(),
+            ],
+        ),
+
+        # Mapping via PostPAM
+        LogPass('Mapping circuit.'),
+        IfThenElsePass(
+            NotPredicate(WidthPredicate(2)),
+            [
+                SubtopologySelectionPass(max_synthesis_size),
+                QuickPartitioner(max_synthesis_size),
+                ForEachBlockPass(
+                    EmbedAllPermutationsPass(inner_synthesis=qsearch),
+                ),
+                GreedyPlacementPass(),
+                PAMLayoutPass(5),
+                PAMRoutingPass(),
+                UnfoldPass(),
+                ApplyPlacement(),
+            ],
+        ),
+
+        # Swap gate retargeting
+        IfThenElsePass(
+            NotPredicate(WidthPredicate(2)),
+            [
+                LogPass('Retargeting swap gates.'),
+                QuickPartitioner(max_synthesis_size),
+                ExtendBlockSizePass(smallest_entangler_size),
+                QuickPartitioner(error_sim_size),
+                ForEachBlockPass(
+                    ForEachBlockPass(
+                        [
+                            FillSingleQuditGatesPass(),
+                            IfThenElsePass(
+                                NotPredicate(MultiPhysicalPredicate()),
+                                multi_qudit_gate_rebase,
+                            ),
+                        ],
+                        replace_filter=_gen_replace_filter(model),
+                    ),
+                    calculate_error_bound=(error_threshold is not None),
+                ),
+                UnfoldPass(),
+            ],
+        ),
+
+        # Optimization: Iterative Resynthesis
+        IfThenElsePass(
+            NotPredicate(WidthPredicate(2)),
+            [
+                WhileLoopPass(
+                    GateCountPredicate(native_mq_gates),
+                    [
+                        LogPass('Resynthesizing blocks.'),
+                        QuickPartitioner(max_synthesis_size),
+                        ExtendBlockSizePass(smallest_entangler_size),
+                        QuickPartitioner(error_sim_size),
+                        ForEachBlockPass(
+                            ForEachBlockPass(
+                                direct_synthesis,
+                                replace_filter=_gen_replace_filter(model),
+                            ),
+                            calculate_error_bound=(error_threshold is not None),
+                        ),
+                        UnfoldPass(),
+                    ],
+                ),
+            ],
+        ),
+
+        # Single-qudit gate retargeting
+        LogPass('Retargeting single-qudit gates.'),
+        single_qudit_gate_rebase,
+
+        # Optimization: Iterative gate deletion on blocks
+        WhileLoopPass(
+            ChangePredicate(),
+            [
+                LogPass('Attempting to delete gates.'),
+                QuickPartitioner(max_synthesis_size),
+                QuickPartitioner(error_sim_size),
+                ForEachBlockPass(
+                    ForEachBlockPass(scan),
+                    calculate_error_bound=(error_threshold is not None),
+                ),
+                UnfoldPass(),
+            ],
+        ),
+
+        # Finalizing
+        LogErrorPass(),
+        ApplyPlacement(),
+    ]
 
 
 def _stateprep_workflow(
@@ -827,27 +1171,198 @@ def _stateprep_workflow(
     model: MachineModel,
     optimization_level: int = 1,
     synthesis_epsilon: float = 1e-10,
-    max_synthesis_size: int = 4,
+    max_synthesis_size: int = 3,
     error_threshold: float | None = None,
     error_sim_size: int = 8,
-) -> CompilationTask:
+    seed: int | None = None,
+) -> Workflow:
     """Build a workflow for state preparation."""
-    # TODO
-    raise NotImplementedError('State preparation is not yet implemented.')
+    layer_gen = model.gate_set.build_mq_layer_generator()
+
+    if optimization_level == 1:
+        inst_ops = {
+            'multistarts': 1,
+            'method': 'minimization',
+            'minimizer': LBFGSMinimizer(),
+        }
+        synthesis: SynthesisPass = LEAPSynthesisPass(
+            success_threshold=synthesis_epsilon,
+            layer_generator=layer_gen,
+            instantiate_options=inst_ops,
+            min_prefix_size=3,
+            cost=HilbertSchmidtCostGenerator(),
+        )
 
+    elif optimization_level == 2:
+        inst_ops = {
+            'multistarts': 4,
+            'method': 'minimization',
+            'ftol': 5e-12,
+            'gtol': 1e-14,
+        }
+        synthesis = LEAPSynthesisPass(
+            success_threshold=synthesis_epsilon,
+            layer_generator=layer_gen,
+            instantiate_options=inst_ops,
+            min_prefix_size=5,
+        )
 
-def _get_layer_gen(model: MachineModel) -> LayerGenerator:
-    """Build a `model`-compliant layer generator."""
-    tq_gates = [gate for gate in model.gate_set if gate.num_qudits == 2]
-    mq_gates = [gate for gate in model.gate_set if gate.num_qudits > 2]
+    elif optimization_level == 3:
+        inst_ops = {
+            'multistarts': 8,
+            'method': 'minimization',
+            'ftol': 5e-16,
+            'gtol': 1e-15,
+        }
+        if state.num_qudits > 3:
+            synthesis = LEAPSynthesisPass(
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+                min_prefix_size=7,
+            )
+        else:
+            synthesis = QSearchSynthesisPass(
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+            )
 
-    if len(tq_gates) == 1 and len(mq_gates) == 0:
-        if CNOTGate() in tq_gates:
-            return FourParamGenerator()
+    elif optimization_level == 4:
+        inst_ops = {
+            'multistarts': 8,
+            'method': 'minimization',
+            'ftol': 5e-16,
+            'gtol': 1e-15,
+        }
+        if state.num_qudits > 3:
+            in_synthesis = LEAPSynthesisPass(
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+                min_prefix_size=7,
+            )
         else:
-            return SimpleLayerGenerator(tq_gates[0])
+            in_synthesis = QSearchSynthesisPass(  # type: ignore
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+            )
+        synthesis = PermutationAwareSynthesisPass(inner_synthesis=in_synthesis)
 
-    return WideLayerGenerator(tq_gates + mq_gates)
+    scan = ScanningGateRemovalPass(
+        success_threshold=synthesis_epsilon,
+        instantiate_options=inst_ops,
+        cost=HilbertSchmidtCostGenerator(),
+    )
+
+    workflow: list[BasePass] = [] if seed is None else [SetRandomSeedPass(seed)]
+    workflow += [
+        SetModelPass(model),
+        SetTargetPass(state),
+        synthesis,
+        scan if optimization_level >= 2 else NOOPPass(),
+    ]
+
+    return Workflow(workflow)
+
+
+def _statemap_workflow(
+    state: StateSystem,
+    model: MachineModel,
+    optimization_level: int = 1,
+    synthesis_epsilon: float = 1e-8,
+    max_synthesis_size: int = 3,
+    error_threshold: float | None = None,
+    error_sim_size: int = 8,
+    seed: int | None = None,
+) -> Workflow:
+    """Build a workflow for state preparation."""
+    layer_gen = model.gate_set.build_mq_layer_generator()
+
+    if optimization_level == 1:
+        inst_ops = {
+            'multistarts': 1,
+            'method': 'minimization',
+        }
+        synthesis: SynthesisPass = LEAPSynthesisPass(
+            success_threshold=synthesis_epsilon,
+            layer_generator=layer_gen,
+            instantiate_options=inst_ops,
+            min_prefix_size=3,
+        )
+
+    elif optimization_level == 2:
+        inst_ops = {
+            'multistarts': 4,
+            'method': 'minimization',
+            'ftol': 5e-12,
+            'gtol': 1e-14,
+        }
+        synthesis = LEAPSynthesisPass(
+            success_threshold=synthesis_epsilon,
+            layer_generator=layer_gen,
+            instantiate_options=inst_ops,
+            min_prefix_size=5,
+        )
+
+    elif optimization_level == 3:
+        inst_ops = {
+            'multistarts': 8,
+            'method': 'minimization',
+            'ftol': 5e-16,
+            'gtol': 1e-15,
+        }
+        if state.num_qudits > 3:
+            synthesis = LEAPSynthesisPass(
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+                min_prefix_size=7,
+            )
+        else:
+            synthesis = QSearchSynthesisPass(
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+            )
+
+    elif optimization_level == 4:
+        inst_ops = {
+            'multistarts': 8,
+            'method': 'minimization',
+            'ftol': 5e-16,
+            'gtol': 1e-15,
+        }
+        if state.num_qudits > 3:
+            in_synthesis = LEAPSynthesisPass(
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+                min_prefix_size=7,
+            )
+        else:
+            in_synthesis = QSearchSynthesisPass(  # type: ignore
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+            )
+        synthesis = PermutationAwareSynthesisPass(inner_synthesis=in_synthesis)
+
+    scan = ScanningGateRemovalPass(
+        success_threshold=synthesis_epsilon,
+        instantiate_options=inst_ops,
+    )
+
+    workflow: list[BasePass] = [] if seed is None else [SetRandomSeedPass(seed)]
+    workflow += [
+        SetModelPass(model),
+        SetTargetPass(state),
+        synthesis,
+        scan if optimization_level >= 2 else NOOPPass(),
+    ]
+
+    return Workflow(workflow)
 
 
 def _get_single_qudit_gate_rebase_pass(model: MachineModel) -> BasePass:
@@ -904,34 +1419,36 @@ def _get_single_qudit_gate_rebase_pass(model: MachineModel) -> BasePass:
     ])
 
 
+def _replace_filter(new: Circuit, old: Operation, model: MachineModel) -> bool:
+    # return true if old doesn't satisfy model
+    if not isinstance(old.gate, CircuitGate):
+        return True
+
+    org = old.gate._circuit
+    org_mq_gates = [g for g in org.gate_set if g.num_qudits > 1]
+
+    if any(g not in model.gate_set for g in org_mq_gates):
+        return True
+
+    if any(
+        (old.location[e[0]], old.location[e[1]]) not in model.coupling_graph
+        for e in org.coupling_graph
+    ):
+        return True
+
+    # else pick shortest circuit
+    org_sq_n = sum(org.count(g) for g in org.gate_set if g.num_qudits == 1)
+    org_mq_n = sum(org.count(g) for g in org.gate_set if g.num_qudits >= 2)
+    new_sq_n = sum(new.count(g) for g in new.gate_set if g.num_qudits == 1)
+    new_mq_n = sum(new.count(g) for g in new.gate_set if g.num_qudits >= 2)
+    return (new_mq_n, new_sq_n) < (org_mq_n, org_sq_n)
+
+
 def _gen_replace_filter(
     model: MachineModel,
 ) -> Callable[[Circuit, Operation], bool]:
     """Generate a replace filter for use during the standard workflow."""
-    def _replace_filter(new: Circuit, old: Operation) -> bool:
-        # return true if old doesn't satisfy model
-        if not isinstance(old.gate, CircuitGate):
-            return True
-
-        org = old.gate._circuit
-        org_mq_gates = [g for g in org.gate_set if g.num_qudits > 1]
-
-        if any(g not in model.gate_set for g in org_mq_gates):
-            return True
-
-        if any(
-            (old.location[e[0]], old.location[e[1]]) not in model.coupling_graph
-            for e in org.coupling_graph
-        ):
-            return True
-
-        # else pick shortest circuit
-        org_sq_n = sum(org.count(g) for g in org.gate_set if g.num_qudits == 1)
-        org_mq_n = sum(org.count(g) for g in org.gate_set if g.num_qudits >= 2)
-        new_sq_n = sum(new.count(g) for g in new.gate_set if g.num_qudits == 1)
-        new_mq_n = sum(new.count(g) for g in new.gate_set if g.num_qudits >= 2)
-        return (new_mq_n, new_sq_n) < (org_mq_n, org_sq_n)
-    return _replace_filter
+    return functools.partial(_replace_filter, model=model)
 
 
 def _mq_gate_collection_filter(op: Operation) -> bool:
diff --git a/bqskit/compiler/compiler.py b/bqskit/compiler/compiler.py
index 560f90871..87d75f5c2 100644
--- a/bqskit/compiler/compiler.py
+++ b/bqskit/compiler/compiler.py
@@ -1,24 +1,34 @@
 """This module implements the Compiler class."""
 from __future__ import annotations
 
+import atexit
+import functools
 import logging
+import os
+import signal
 import sys
 import time
 import uuid
-from subprocess import PIPE
+import warnings
+from multiprocessing.connection import Client
+from multiprocessing.connection import Connection
 from subprocess import Popen
+from types import FrameType
+from typing import overload
 from typing import TYPE_CHECKING
 
+from bqskit.compiler.status import CompilationStatus
+from bqskit.compiler.task import CompilationTask
+from bqskit.compiler.workflow import Workflow
+from bqskit.compiler.workflow import WorkflowLike
+from bqskit.runtime import default_server_port
+from bqskit.runtime import default_worker_port
+from bqskit.runtime.message import RuntimeMessage
+
 if TYPE_CHECKING:
     from typing import Any
-    from dask.distributed import Future
-    from bqskit.compiler.task import CompilationTask
     from bqskit.ir.circuit import Circuit
-
-from dask.distributed import Client
-from threadpoolctl import threadpool_limits
-
-from bqskit.compiler.executor import Executor
+    from bqskit.compiler.passdata import PassData
 
 _logger = logging.getLogger(__name__)
 
@@ -27,68 +37,117 @@ class Compiler:
     """
     A compiler is responsible for accepting and managing compilation tasks.
 
-    The compiler class spins up or connects to a Dask execution environment,
-    which compilation tasks can then access to parallelize their operations.
-    The compiler is implemented as a context manager and it is recommended
-    to use it as one. If the compiler is not used in a context manager, it
-    is the responsibility of the user to call `close()`.
+    The compiler class either spins up a parallel runtime or connects to
+    a distributed one, which compilation tasks can then access to
+    parallelize their operations. The compiler is implemented as a
+    context manager and it is recommended to use it as one. If the
+    compiler is not used in a context manager, it is the responsibility
+    of the user to call `close()`.
 
     Examples:
         1. Use in a context manager:
         >>> with Compiler() as compiler:
-        ...     circuit = compiler.compile(task)
+        ...     circuit = compiler.compile(circuit, workflow)
 
         2. Use compiler without context manager:
         >>> compiler = Compiler()
-        >>> circuit = compiler.compile(task)
+        >>> circuit = compiler.compile(circuit, workflow)
         >>> compiler.close()
 
-        3. Connect to an already running dask cluster:
-        >>> with Compiler('localhost:8786') as compiler:
-        ...     circuit = compiler.compile(task)
+        3. Connect to an already running distributed runtime:
+        >>> with Compiler('localhost') as compiler:
+        ...     circuit = compiler.compile(circuit, workflow)
 
-        4. Connect to a dask cluster with a scheduler file:
-        >>> with Compiler(scheduler_file='/path/to/file/') as compiler:
-        ...     circuit = compiler.compile(task)
+        4. Start and attach to a runtime with 4 worker processes:
+        >>> with Compiler(num_workers=4) as compiler:
+        ...     circuit = compiler.compile(circuit, workflow)
     """
 
-    _parallelism_enabled = True
-    """Used to to limit default parallelism; see bqskit.disable_parallelism."""
-
-    _dashboard = False
-    """Used to enable the dask dashboard; see bqskit.enable_dashboard."""
-
-    def __init__(self, *args: Any, **kwargs: Any) -> None:
+    def __init__(
+        self,
+        ip: str | None = None,
+        port: int = default_server_port,
+        num_workers: int = -1,
+        runtime_log_level: int = logging.WARNING,
+        worker_port: int = default_worker_port,
+    ) -> None:
         """
         Construct a Compiler object.
 
-        Notes:
-            All arguments are passed directly to Dask. You can use
-            these to connect to and configure a Dask cluster.
+        This will either spawn a parallel runtime if `ip` is None, or
+        attempt to connect to a distributed one if `ip` is provided.
+
+        Args:
+            ip (str | None): If left as None, spawn a parallel runtime,
+                otherwise attempt a connection with an already running
+                runtime at this address. Defaults to None.
+
+            port (int): The port where a runtime is expected to be
+                listening.
+
+            num_workers (int): The number of workers to spawn. Ignored
+                if `ip` is None. If negative spawn as many workers as
+                cpus on the system. (Defaults to -1)
+
+            runtime_log_level (int): The runtime's logging level. This
+                is separate from the compilation logging. If you would
+                like logs from your compilation workflow, that setting
+                is set during task creation in :func:`compiler.compile`
+                or :func:`compiler.submit`.
+
+            worker_port (int): The optional port to pass to an attached
+                runtime. See :obj:`~bqskit.runtime.attached.AttachedServer`
+                for more info.
         """
-        # Connect to or start a Dask cluster
-        self.managed = len(args) == 0 and len(kwargs) == 0
-        if self.managed:
-            self.processes = start_dask_cluster()
-            self.client = Client('localhost:8786')
-        else:
-            self.client = Client(*args, **kwargs)
+        self.p: Popen | None = None  # type: ignore
+        self.conn: Connection | None = None
 
-        # Initialize workers for logging support
-        def set_logging_level(level: int) -> None:
-            import bqskit  # noqa
-            logging.getLogger('bqskit').setLevel(level)
+        atexit.register(self.close)
 
-        self.client.run(set_logging_level, logging.getLogger('bqskit').level)
+        if ip is None:
+            ip = 'localhost'
+            self._start_server(num_workers, runtime_log_level, worker_port)
 
-        # Limit threads for low level math calls on workers
-        def limit_threads() -> None:
-            threadpool_limits(limits=1)
-        self.client.run(limit_threads)
+        self._connect_to_server(ip, port)
 
-        # Keep track of submitted compilation tasks
-        self.tasks: dict[uuid.UUID, Future] = {}
-        _logger.info('Started compiler process.')
+    def _start_server(
+        self,
+        num_workers: int,
+        runtime_log_level: int,
+        worker_port: int,
+    ) -> None:
+        """
+        Start an attached serer with `num_workers` workers.
+
+        See :obj:`~bqskit.runtime.attached.AttachedServer` for more info.
+        """
+        params = f'{num_workers}, {runtime_log_level}, {worker_port=}'
+        import_str = 'from bqskit.runtime.attached import start_attached_server'
+        launch_str = f'{import_str}; start_attached_server({params})'
+        self.p = Popen([sys.executable, '-c', launch_str])
+        _logger.debug('Starting runtime server process.')
+
+    def _connect_to_server(self, ip: str, port: int) -> None:
+        """Connect to a runtime server at `ip` and `port`."""
+        max_retries = 7
+        wait_time = .25
+        for _ in range(max_retries):
+            try:
+                family = 'AF_INET' if sys.platform == 'win32' else None
+                conn = Client((ip, port), family)
+            except ConnectionRefusedError:
+                time.sleep(wait_time)
+                wait_time *= 2
+            else:
+                self.conn = conn
+                handle = functools.partial(sigint_handler, compiler=self)
+                self.old_signal = signal.signal(signal.SIGINT, handle)
+                if self.conn is None:
+                    raise RuntimeError('Connection unexpectedly none.')
+                self.conn.send((RuntimeMessage.CONNECT, None))
+                _logger.debug('Successfully connected to runtime server.')
+                return
+        raise RuntimeError('Client connection refused')
 
     def __enter__(self) -> Compiler:
         """Enter a context for this compiler."""
@@ -99,108 +158,353 @@ def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
         self.close()
 
     def close(self) -> None:
-        """Shutdown compiler."""
-        self.client.close()
-        self.tasks = {}
-        if self.managed:
-            for proc in self.processes:
-                proc.terminate()  # type: ignore
-                proc.wait()  # type: ignore
-        _logger.info('Stopped compiler process.')
+        """Shutdown the compiler."""
+        # Disconnect from server
+        if self.conn is not None:
+            try:
+                self.conn.send((RuntimeMessage.DISCONNECT, None))
+                try:
+                    self.conn.recv()
+                except EOFError:
+                    # When the server disconnects the conn, we then proceed
+                    pass
+                self.conn.close()
+            except Exception as e:
+                _logger.debug(
+                    'Unsuccessfully disconnected from runtime server.',
+                )
+                _logger.debug(e)
+            else:
+                _logger.debug('Disconnected from runtime server.')
+            finally:
+                self.conn = None
+
+        # Shutdown server if attached
+        if self.p is not None and self.p.pid is not None:
+            try:
+                os.kill(self.p.pid, signal.SIGINT)
+                _logger.debug('Interrupted attached runtime server.')
+
+                self.p.communicate(timeout=1)
+                if self.p.returncode is None:
+                    if sys.platform == 'win32':
+                        self.p.terminate()
+                    else:
+                        os.kill(self.p.pid, signal.SIGKILL)
+                    _logger.debug('Killed attached runtime server.')
+
+            except Exception as e:
+                _logger.debug(
+                    f'Error while shuting down attached runtime server: {e}.',
+                )
+            else:
+                _logger.debug('Successfully shutdown attached runtime server.')
+            finally:
+                self.p.communicate()
+                _logger.debug('Attached runtime server is down.')
+                self.p = None
+
+        # Reset interrupt signal handler and remove exit handler
+        if hasattr(self, 'old_signal'):
+            signal.signal(signal.SIGINT, self.old_signal)
 
     def __del__(self) -> None:
-        if self.managed and hasattr(self, 'processes'):
-            for proc in self.processes:
-                proc.terminate()  # type: ignore
-                proc.wait()  # type: ignore
-
-    def submit(self, task: CompilationTask) -> None:
-        """Submit a CompilationTask to the Compiler."""
-        executor = self.client.scatter(Executor(task))
-        future = self.client.submit(Executor.run, executor, pure=False)
-        self.tasks[task.task_id] = future
-        _logger.info('Submitted task: %s' % task.task_id)
-
-    def status(self, task: CompilationTask) -> str:
-        """Retrieve the status of the specified CompilationTask."""
-        return self.tasks[task.task_id].status
-
-    def result(self, task: CompilationTask) -> Circuit:
-        """Block until the CompilationTask is finished, return its result."""
-        circ = self.tasks[task.task_id].result()[0]
-        return circ
-
-    def cancel(self, task: CompilationTask) -> None:
-        """Remove a task from the compiler's workqueue."""
-        self.client.cancel(self.tasks[task.task_id])
-        _logger.info('Cancelled task: %s' % task.task_id)
-
-    def compile(self, task: CompilationTask) -> Circuit:
-        """Submit and execute the CompilationTask, block until its done."""
-        _logger.info('Compiling task: %s' % task.task_id)
-        self.submit(task)
-        result = self.result(task)
-        return result
+        self.close()
+        atexit.unregister(self.close)
+        _logger.debug('Compiler successfully shutdown.')
+
+    def submit(
+        self,
+        task_or_circuit: CompilationTask | Circuit,
+        workflow: WorkflowLike | None = None,
+        request_data: bool = False,
+        logging_level: int | None = None,
+        max_logging_depth: int = -1,
+    ) -> uuid.UUID:
+        """
+        Submit a compilation job to the Compiler.
+
+        Args:
+            task_or_circuit (CompilationTask | Circuit): The task to compile,
+                or the input circuit. If a task is specified, no other
+                argument should be specified. If a task is not specified,
+                the circuit must be paired with a workflow argument.
+
+            workflow (WorkflowLike): The compilation job submitted
+                is defined by executing this workflow on the input circuit.
+
+            request_data (bool): If true, the task result will contain the
+                associated pass data accumulated during compilation.
+                Defaults to False.
+
+            logging_level (int | None): Specify the python logging level
+                to be used during compilation. Defaults to None, which
+                will use current logging configuration.
+
+            max_logging_depth (int): Compilation jobs will create subtasks
+                which may also create subtasks. Tasks that have
+                `max_logging_depth` ancestors or more will stop logging.
+                Defaults to -1, which disables the feature, allowing all
+                tasks equal opportunity to log.
+
+        Returns:
+            (uuid.UUID): The ID of the generated task in the system. This
+                ID can be used to check the status of, cancel, and request
+                the result of the task.
+        """
+        # Build CompilationTask
+        if isinstance(task_or_circuit, CompilationTask):
+            if workflow is not None:
+                raise ValueError(
+                    'Cannot specify workflow and task.'
+                    ' Either specify a workflow and circuit or a task alone.',
+                )
+
+            task = task_or_circuit
 
-    def analyze(self, task: CompilationTask, key: str) -> Any:
-        """Gather the value associated with `key` in the task's data."""
-        if task.task_id not in self.tasks:
-            self.submit(task)
-        return self.tasks[task.task_id].result()[1][key]
-
-
-def start_dask_cluster() -> tuple[Popen[bytes], Popen[str]]:
-    """Start a dask cluster and return the scheduler and worker processes."""
-    scheduler_proc: Any = None
-    worker_proc: Any = None
-
-    # Start a dask scheduler in another process
-    try:
-        dashboard = '--dashboard' if Compiler._dashboard else '--no-dashboard'
-        scheduler_proc = Popen(
-            [
-                'dask-scheduler',
-                '--host', 'localhost',
-                '--port', '8786',
-                dashboard,
-            ],
-            bufsize=1,
-            universal_newlines=True,
-            stdout=sys.stdout,
-            stderr=PIPE,
-        )
-        for line in iter(scheduler_proc.stderr.readline, ''):
-            if 'Scheduler at:' in line:
-                break
-        scheduler_proc.stderr.close()
-    except Exception as e:
-        raise RuntimeError('Failed to start dask scheduler') from e
-
-    # Start dask workers in another process
-    try:
-        workers = []
-        if Compiler._parallelism_enabled:
-            workers.append('auto')
         else:
-            workers.append('1')
-            workers.append('--nthreads')
-            workers.append('1')
-
-        worker_proc = Popen(
-            ['dask-worker', '--nworkers', *workers, 'localhost:8786'],
-            bufsize=1,
-            universal_newlines=True,
-            stdout=sys.stdout,
-            stderr=PIPE,
+            if workflow is None:
+                m = 'Must specify workflow when providing a circuit to submit.'
+                raise TypeError(m)
+
+            task = CompilationTask(task_or_circuit, Workflow(workflow))
+
+        # Set task configuration
+        task.request_data = request_data
+        task.logging_level = logging_level or self._discover_lowest_log_level()
+        task.max_logging_depth = max_logging_depth
+
+        # Submit task to runtime
+        self._send(RuntimeMessage.SUBMIT, task)
+        return task.task_id
+
+    def status(self, task_id: CompilationTask | uuid.UUID) -> CompilationStatus:
+        """Retrieve the status of the specified task."""
+        if isinstance(task_id, CompilationTask):
+            warnings.warn(
+                'Request a status from a CompilationTask is deprecated.\n'
+                ' Instead, pass a task ID to request a status.\n'
+                ' `compiler.submit` returns a task id, and you can get an\n'
+                ' ID from a task via `task.task_id`.\n'
+                ' This warning will turn into an error in a future update.',
+                DeprecationWarning,
+            )
+            task_id = task_id.task_id
+        assert isinstance(task_id, uuid.UUID)
+
+        msg, payload = self._send_recv(RuntimeMessage.STATUS, task_id)
+        if msg != RuntimeMessage.STATUS:
+            raise RuntimeError(f'Unexpected message type: {msg}.')
+        return payload
+
+    def result(
+        self,
+        task_id: CompilationTask | uuid.UUID,
+    ) -> Circuit | tuple[Circuit, PassData]:
+        """Block until the task is finished, return its result."""
+        if isinstance(task_id, CompilationTask):
+            warnings.warn(
+                'Request a result from a CompilationTask is deprecated.'
+                ' Instead, pass a task ID to request a result.\n'
+                ' `compiler.submit` returns a task id, and you can get an\n'
+                ' ID from a task via `task.task_id`.\n'
+                ' This warning will turn into an error in a future update.',
+                DeprecationWarning,
+            )
+            task_id = task_id.task_id
+        assert isinstance(task_id, uuid.UUID)
+
+        msg, payload = self._send_recv(RuntimeMessage.REQUEST, task_id)
+        if msg != RuntimeMessage.RESULT:
+            raise RuntimeError(f'Unexpected message type: {msg}.')
+        return payload
+
+    def cancel(self, task_id: CompilationTask | uuid.UUID) -> bool:
+        """Cancel the execution of a task in the system."""
+        if isinstance(task_id, CompilationTask):
+            warnings.warn(
+                'Cancelling a CompilationTask is deprecated. Instead,'
+                ' Instead, pass a task ID to cancel a task.\n'
+                ' `compiler.submit` returns a task id, and you can get an\n'
+                ' ID from a task via `task.task_id`.\n'
+                ' This warning will turn into an error in a future update.',
+                DeprecationWarning,
+            )
+            task_id = task_id.task_id
+        assert isinstance(task_id, uuid.UUID)
+
+        msg, _ = self._send_recv(RuntimeMessage.CANCEL, task_id)
+        if msg != RuntimeMessage.CANCEL:
+            raise RuntimeError(f'Unexpected message type: {msg}.')
+        return True
+
+    @overload
+    def compile(
+        self,
+        task_or_circuit: CompilationTask,
+    ) -> Circuit | tuple[Circuit, PassData]:
+        """Submit a task, wait for its results; see :func:`submit` for more."""
+        ...
+
+    @overload
+    def compile(
+        self,
+        task_or_circuit: Circuit,
+        workflow: WorkflowLike,
+        request_data: None = None,
+        logging_level: int | None = None,
+        max_logging_depth: int = -1,
+    ) -> Circuit:
+        """Submit a task, wait for its results; see :func:`submit` for more."""
+        ...
+
+    @overload
+    def compile(
+        self,
+        task_or_circuit: Circuit,
+        workflow: WorkflowLike,
+        request_data: bool,
+        logging_level: int | None = None,
+        max_logging_depth: int = -1,
+    ) -> tuple[Circuit, PassData]:
+        """Submit a task, wait for its results; see :func:`submit` for more."""
+        ...
+
+    def compile(
+        self,
+        task_or_circuit: CompilationTask | Circuit,
+        workflow: WorkflowLike | None = None,
+        request_data: bool | None = None,
+        logging_level: int | None = None,
+        max_logging_depth: int = -1,
+    ) -> Circuit | tuple[Circuit, PassData]:
+        """Submit a task, wait for its results; see :func:`submit` for more."""
+        if isinstance(task_or_circuit, CompilationTask):
+            warnings.warn(
+                'Manually constructing and compiling CompilationTasks'
+                ' is deprecated. Instead, call compile directly with'
+                ' your input circuit and workflow. This warning will'
+                ' turn into an error in a future update.',
+                DeprecationWarning,
+            )
+
+        task_id = self.submit(
+            task_or_circuit,
+            workflow,
+            request_data if request_data else False,
+            logging_level,
+            max_logging_depth,
         )
-        for line in iter(worker_proc.stderr.readline, ''):
-            if 'Starting established connection' in line:
-                break
-        worker_proc.stderr.close()
-    except Exception as e:
-        scheduler_proc.terminate()
-        scheduler_proc.wait()
-        raise RuntimeError('Failed to start dask worker') from e
-
-    time.sleep(1)
-    return worker_proc, scheduler_proc
+        result = self.result(task_id)
+
+        # Ensure arrival of all log messages
+        time.sleep(0.05 if self.p is not None else 0.5)
+        self._recv_log_error_until_empty()
+
+        return result
+
+    def _send(self, msg: RuntimeMessage, payload: Any) -> None:
+        """Send a message to the runtime."""
+        if self.conn is None:
+            raise RuntimeError('Connection unexpectedly none.')
+
+        try:
+            self._recv_log_error_until_empty()
+
+            self.conn.send((msg, payload))
+
+        except Exception as e:
+            self.conn = None
+            self.close()
+            raise RuntimeError('Server connection unexpectedly closed.') from e
+
+    def _send_recv(
+        self,
+        msg: RuntimeMessage,
+        payload: Any,
+    ) -> tuple[RuntimeMessage, Any]:
+        """Send a message to the runtime, and wait for a response."""
+        if self.conn is None:
+            raise RuntimeError('Connection unexpectedly none.')
+
+        try:
+            self._recv_log_error_until_empty()
+
+            self.conn.send((msg, payload))
+
+            return self._recv_handle_log_error()
+
+        except Exception as e:
+            self.conn = None
+            self.close()
+            raise RuntimeError('Server connection unexpectedly closed.') from e
+
+    def _recv_handle_log_error(self) -> tuple[RuntimeMessage, Any]:
+        """Return next msg, transparently emit log records and raise errors."""
+        if self.conn is None:
+            raise RuntimeError('Connection unexpectedly none.')
+
+        to_return = None
+        while to_return is None or self.conn.poll():
+            msg, payload = self.conn.recv()
+
+            if msg == RuntimeMessage.LOG:
+                logger = logging.getLogger(payload.name)
+                if logger.isEnabledFor(payload.levelno):
+                    logger.handle(payload)
+
+            elif msg == RuntimeMessage.ERROR:
+                raise RuntimeError(payload)
+
+            else:
+                # Communication between runtime server and compiler
+                # is always round-trip. Once we have received our
+                # desired message (not log or error) we can therefore be
+                # certain any remaining messages in the pipeline are
+                # only either logs or error messages. We do want to
+                # handle these sooner rather than later, so we ensure to
+                # process every arrived message before returning.
+                # Hence, the `or self.conn.poll()` in the while condition.
+                to_return = (msg, payload)
+
+        return to_return
+
+    def _recv_log_error_until_empty(self) -> None:
+        """Handle all remaining log and error messages in the pipeline."""
+        if self.conn is None:
+            raise RuntimeError('Connection unexpectedly none.')
+
+        while self.conn.poll():
+            msg, payload = self.conn.recv()
+
+            if msg == RuntimeMessage.LOG:
+                logger = logging.getLogger(payload.name)
+                if logger.isEnabledFor(payload.levelno):
+                    logger.handle(payload)
+
+            elif msg == RuntimeMessage.ERROR:
+                raise RuntimeError(payload)
+
+            else:
+                raise RuntimeError(f'Unexpected message type: {msg}.')
+
+    def _discover_lowest_log_level(self) -> int:
+        """Searches through all python loggers for the lowest set level."""
+        lowest_level_found_so_far = logging.getLogger().getEffectiveLevel()
+
+        for _, logger in logging.getLogger().manager.loggerDict.items():
+            if isinstance(logger, logging.PlaceHolder):
+                continue
+
+            if logger.getEffectiveLevel() < lowest_level_found_so_far:
+                lowest_level_found_so_far = logger.getEffectiveLevel()
+
+        return lowest_level_found_so_far
+
+
+def sigint_handler(signum: int, frame: FrameType, compiler: Compiler) -> None:
+    """Interrupt signal handler for the compiler."""
+    signal.signal(signal.SIGINT, signal.SIG_IGN)
+    _logger.critical('Compiler interrupted.')
+    compiler.close()
+    raise KeyboardInterrupt
diff --git a/bqskit/compiler/executor.py b/bqskit/compiler/executor.py
deleted file mode 100644
index 7e726bffb..000000000
--- a/bqskit/compiler/executor.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""This module implements the Executor class."""
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from bqskit.compiler.task import CompilationTask
-if TYPE_CHECKING:
-    from typing import Any
-    from bqskit.ir.circuit import Circuit
-
-
-class Executor:
-    """An Executor is responsible for executing a CompilationTask."""
-
-    def __init__(self, task: CompilationTask) -> None:
-        """
-        Construct an Executor.
-
-        Creates a executor ready to execute the specified task.
-
-        Args:
-            task (CompilationTask): The task to execute.
-        """
-
-        if not isinstance(task, CompilationTask):
-            raise TypeError(f'Expected a CompilationTask, got {type(task)}.')
-
-        self.task_id = task.task_id
-        self.circuit = task.input_circuit
-        self.passes = task.passes
-        self.data: dict[str, Any] = {'parallel': True}
-        self.done = False
-
-    def run(self) -> tuple[Circuit, dict[str, Any]]:
-        """Execute the task."""
-        for pass_obj in self.passes:
-            pass_obj.run(self.circuit, self.data)
-        self.done = True
-        return self.circuit, self.data
diff --git a/bqskit/compiler/gateset.py b/bqskit/compiler/gateset.py
new file mode 100644
index 000000000..d5a9ba0ae
--- /dev/null
+++ b/bqskit/compiler/gateset.py
@@ -0,0 +1,223 @@
+"""This module implements the GateSet class."""
+from __future__ import annotations
+
+import sys
+from collections.abc import Set
+from typing import Any
+from typing import Iterable
+from typing import Iterator
+from typing import Sequence
+from typing import TYPE_CHECKING
+from typing import Union
+
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.generalgate import GeneralGate
+from bqskit.ir.gates.parameterized.cphase import ArbitraryCPhaseGate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
+from bqskit.ir.gates.parameterized.u8 import U8Gate
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
+from bqskit.utils.typing import is_integer
+from bqskit.utils.typing import is_sequence_of_int
+
+if TYPE_CHECKING:
+    from bqskit.passes.search.generator import LayerGenerator
+
+if sys.version_info < (3, 9):
+    SuperType = Set
+else:
+    SuperType = Set[Gate]
+
+
+class GateSet(SuperType):
+    """A set of a quantum processing unit's supported quantum gates."""
+
+    def __init__(self, gates: GateSetLike) -> None:
+        """Initialize a GateSet object."""
+        if isinstance(gates, GateSet):
+            self._gates: frozenset[Gate] = gates._gates.copy()
+            self.radix_set: frozenset[int] = gates.radix_set.copy()
+            return
+
+        if isinstance(gates, Gate):
+            gates = [gates]
+
+        radix_set: set[int] = set()
+        gate_set: set[Gate] = set()
+        for g in gates:
+            radix_set.update(g.radixes)
+            gate_set.add(g)
+        self.radix_set = frozenset(radix_set)
+        self._gates = frozenset(gate_set)
+
+    def build_layer_generator(self) -> LayerGenerator:
+        """Build a standard layer generator compliant with this gate set."""
+        if not any(isinstance(g, GeneralGate) for g in self.single_qudit_gates):
+            raise RuntimeError(
+                'Cannot automatically build a layer generator for a gate set'
+                " without any general single-qubit gates. Instead, it's best"
+                ' to use `build_mq_layer_generator` and then do single-qudit'
+                ' gate retargeting afterwards.',
+            )
+
+        from bqskit.passes.search.generators import SimpleLayerGenerator
+        from bqskit.passes.search.generators import WideLayerGenerator
+
+        sqs = [g for g in self.single_qudit_gates if isinstance(g, GeneralGate)]
+        sq = sqs[0]
+
+        if len(self.two_qudit_gates) == 1 and len(self.many_qudit_gates) == 0:
+            gate = list(self.two_qudit_gates)[0]
+            return SimpleLayerGenerator(gate, sq)
+
+        return WideLayerGenerator(self.multi_qudit_gates, sq)
+
+    def build_mq_layer_generator(self) -> LayerGenerator:
+        """
+        Produce a layer generator using this gate set's multi-qudit gates.
+
+        The resulting layer generator will use arbitrary single-qudit gates.
+        """
+        from bqskit.passes.search.generators import FourParamGenerator
+        from bqskit.passes.search.generators import SimpleLayerGenerator
+        from bqskit.passes.search.generators import WideLayerGenerator
+
+        if len(self.two_qudit_gates) == 1 and len(self.many_qudit_gates) == 0:
+            if CNOTGate() in self:
+                return FourParamGenerator()
+
+            gate = list(self.two_qudit_gates)[0]
+            return SimpleLayerGenerator(gate, self.get_general_sq_gate())
+
+        return WideLayerGenerator(
+            self.multi_qudit_gates,
+            self.get_general_sq_gate(),
+        )
+
+    def get_general_sq_gate(self) -> GeneralGate:
+        """
+        Return the gate set's arbitrary single-qudit gate.
+
+        During off-the-shelf BQSKit compilations, single-qudit gate rebasing is
+        done last. The earlier stages of compilation focus on multi-qudit gates
+        and use arbitrary single-qudit rotations. This function will return the
+        best arbitrary single-qudit rotation to use during these steps based on
+        the gates in the set.
+        """
+        if len(self.radix_set) == 0:
+            raise RuntimeError('Cannot suggest single-qudit gate for empty set')
+
+        if len(self.radix_set) != 1:
+            raise RuntimeError(
+                'Cannot automatically suggest general single-qudit gate for'
+                ' hybrid level systems.',
+            )
+
+        if U3Gate() in self:
+            return U3Gate()
+
+        if U8Gate() in self:
+            return U8Gate()
+
+        for g in self:
+            if isinstance(g, VariableUnitaryGate):
+                if g.num_qudits == 1:
+                    return g
+
+        radix = list(self.radix_set)[0]
+
+        if radix == 2:
+            return U3Gate()
+
+        return VariableUnitaryGate(1, [radix])
+
+    @property
+    def single_qudit_gates(self) -> set[Gate]:
+        """All gates that act on only one qudit."""
+        return {g for g in self if g.num_qudits == 1}
+
+    @property
+    def two_qudit_gates(self) -> set[Gate]:
+        """All gates that act on two qudits."""
+        return {g for g in self if g.num_qudits == 2}
+
+    @property
+    def many_qudit_gates(self) -> set[Gate]:
+        """All gates that act on more than two qudits."""
+        return {g for g in self if g.num_qudits > 2}
+
+    @property
+    def multi_qudit_gates(self) -> set[Gate]:
+        """All gates that act on more than one qudits."""
+        return {g for g in self if g.num_qudits >= 2}
+
+    @staticmethod
+    def default_gate_set(radixes: Sequence[int] | int = 2) -> GateSet:
+        """Build a default gate set for a given `radixes`."""
+        if is_integer(radixes):
+            radixes = [radixes]
+
+        assert is_sequence_of_int(radixes)
+
+        if len(radixes) == 0 or all(r == 2 for r in radixes):
+            return GateSet({CNOTGate(), U3Gate()})
+
+        unique_radixes = set(radixes)
+
+        gates: set[Gate] = set()
+        for r in unique_radixes:
+            gates.add(VariableUnitaryGate(1, [r]))
+
+        for r1 in unique_radixes:
+            for r2 in unique_radixes:
+                if r1 <= r2:
+                    gates.add(ArbitraryCPhaseGate([r1, r2]))
+
+        return GateSet(gates)
+
+    def __iter__(self) -> Iterator[Gate]:
+        """Iterator for this gate set's gates."""
+        return self._gates.__iter__()
+
+    def __len__(self) -> int:
+        """Number of gates in the set."""
+        return len(self._gates)
+
+    def __contains__(self, obj: object) -> bool:
+        """Return true if a gate is in the set."""
+        return self._gates.__contains__(obj)
+
+    def union(self, *others: Any) -> GateSet:
+        """Return a new GateSet with elements from this one and all others."""
+        return GateSet(self._gates.union(*others))
+
+    def intersection(self, *others: Any) -> GateSet:
+        """Return a new GateSet with elements common to all sets."""
+        return GateSet(self._gates.intersection(*others))
+
+    def difference(self, *others: Any) -> GateSet:
+        """Return a new GateSet with this set's elements but not the others."""
+        return GateSet(self._gates.difference(*others))
+
+    def symmetric_difference(self, other: Iterable[Gate]) -> GateSet:
+        """Return a new GateSet with elements in either set but not both."""
+        return GateSet(self._gates.symmetric_difference(other))
+
+    def issubset(self, other: Iterable[Gate]) -> bool:
+        """Test whether every element in the gate set is in other."""
+        return self._gates.issubset(other)
+
+    def issuperset(self, other: Iterable[Gate]) -> bool:
+        """Report whether this gate set contains `other`."""
+        return self._gates.issuperset(other)
+
+    def __str__(self) -> str:
+        """String representation of the GateSet."""
+        return self._gates.__str__().replace('frozenset', 'GateSet')
+
+    def __repr__(self) -> str:
+        """Detailed representation of the GateSet."""
+        return self._gates.__repr__().replace('frozenset', 'GateSet')
+
+
+GateSetLike = Union[GateSet, Iterable[Gate], Gate]
diff --git a/bqskit/compiler/machine.py b/bqskit/compiler/machine.py
index 7bf84ef75..b8d564023 100644
--- a/bqskit/compiler/machine.py
+++ b/bqskit/compiler/machine.py
@@ -2,22 +2,18 @@
 from __future__ import annotations
 
 from typing import Sequence
+from typing import TYPE_CHECKING
 
-from bqskit.ir.circuit import Circuit
-from bqskit.ir.gate import Gate
-from bqskit.ir.gates.constant.cx import CNOTGate
-from bqskit.ir.gates.parameterized.u3 import U3Gate
+from bqskit.compiler.gateset import GateSet
+from bqskit.compiler.gateset import GateSetLike
 from bqskit.ir.location import CircuitLocation
 from bqskit.qis.graph import CouplingGraph
 from bqskit.qis.graph import CouplingGraphLike
 from bqskit.utils.typing import is_integer
 from bqskit.utils.typing import is_valid_radixes
 
-
-default_gate_set: set[Gate] = {
-    CNOTGate(),
-    U3Gate(),
-}
+if TYPE_CHECKING:
+    from bqskit.ir.circuit import Circuit
 
 
 class MachineModel:
@@ -27,7 +23,7 @@ def __init__(
         self,
         num_qudits: int,
         coupling_graph: CouplingGraphLike | None = None,
-        gate_set: set[Gate] = default_gate_set,
+        gate_set: GateSetLike | None = None,
         radixes: Sequence[int] = [],
     ) -> None:
         """
@@ -42,8 +38,9 @@ def __init__(
                 an all-to-all coupling graph is used as a default.
                 (Default: None)
 
-            gate_set (set[Gate]): The native gate set available on the
-                machine.
+            gate_set (GateSetLike | None): The native gate set available
+                on the machine. If left as None, the default gate set
+                will be used. See :func:`~GateSet.default_gate_set`.
 
             radixes (Sequence[int]): A sequence with its length equal
                 to `num_qudits`. Each element specifies the base of a
@@ -84,11 +81,13 @@ def __init__(
         ):
             raise TypeError('Invalid coupling graph, expected list of tuples')
 
-        if not isinstance(gate_set, set):
-            raise TypeError(f'Expected set of gates, got {type(gate_set)}.')
+        if gate_set is None:
+            gate_set = GateSet.default_gate_set(radixes)
+        else:
+            gate_set = GateSet(gate_set)
 
-        if not all(isinstance(g, Gate) for g in gate_set):
-            raise TypeError(f'Expected set of gates, got {type(gate_set)}.')
+        if not isinstance(gate_set, GateSet):
+            raise TypeError(f'Expected GateSet, got {type(gate_set)}.')
 
         self.gate_set = gate_set
         self.coupling_graph = CouplingGraph(coupling_graph)
@@ -98,7 +97,12 @@ def get_locations(self, block_size: int) -> list[CircuitLocation]:
         """Return all `block_size` connected blocks of qudit indicies."""
         return self.coupling_graph.get_subgraphs_of_size(block_size)
 
-    def is_compatible(self, circuit: Circuit) -> bool:
+    # TODO: add placement support
+    def is_compatible(
+        self,
+        circuit: Circuit,
+        placement: list[int] | None = None,
+    ) -> bool:
         """Check if a circuit is compatible with this model."""
         if circuit.num_qudits > self.num_qudits:
             return False
diff --git a/bqskit/compiler/passdata.py b/bqskit/compiler/passdata.py
new file mode 100644
index 000000000..329ce7cae
--- /dev/null
+++ b/bqskit/compiler/passdata.py
@@ -0,0 +1,217 @@
+"""This module implements the PassData class."""
+from __future__ import annotations
+
+import copy
+import itertools as it
+from typing import Any
+from typing import Iterator
+from typing import MutableMapping
+from typing import Sequence
+
+from bqskit.compiler.gateset import GateSet
+from bqskit.compiler.machine import MachineModel
+from bqskit.ir.circuit import Circuit
+from bqskit.qis.graph import CouplingGraph
+from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+from bqskit.utils.typing import is_integer
+from bqskit.utils.typing import is_real_number
+from bqskit.utils.typing import is_sequence
+
+
+class PassData(MutableMapping[str, Any]):
+    """
+    A dictionary wrapper shared between all passes in a compilation run.
+
+    This class reserves certain keywords and supplies default initial values for
+    them. Other than that, it behaves very similar to a normal dictionary object
+    mapping `str` keys to any type of value.
+    """
+
+    _reserved_keys = [
+        'target',
+        'model',
+        'placement',
+        'error',
+        'seed',
+        'machine_model',
+    ]
+
+    def __init__(self, circuit: Circuit) -> None:
+        """Initialize a PassData object from `circuit`."""
+        self._target: Circuit | StateVector | UnitaryMatrix | StateSystem
+        if circuit.num_qudits <= 8:
+            self._target = circuit.get_unitary()
+        else:
+            self._target = circuit
+
+        self._error = 0.0
+        self._model = MachineModel(circuit.num_qudits)
+        self._placement = list(range(circuit.num_qudits))
+        self._data: dict[str, Any] = {}
+        self._seed: int | None = None
+
+    @property
+    def target(self) -> StateVector | UnitaryMatrix | StateSystem:
+        """Return the current target unitary or state."""
+        if isinstance(self._target, Circuit):
+            self._target = self._target.get_unitary()
+
+        return self._target
+
+    @target.setter
+    def target(self, _val: StateVector | UnitaryMatrix | StateSystem) -> None:
+        if not isinstance(_val, (StateVector, UnitaryMatrix, StateSystem)):
+            raise TypeError(
+                f'Cannot assign type {type(_val)} to target.'
+                ' Expected either a StateVector, StateSystem,'
+                ' or UnitaryMatrix.',
+            )
+        if len(self.placement) != _val.num_qudits:
+            self.placement = list(range(_val.num_qudits))
+        self._target = _val
+
+    @property
+    def error(self) -> float:
+        """Return the current target unitary or state."""
+        return self._error
+
+    @error.setter
+    def error(self, _val: float) -> None:
+        if not is_real_number(_val):
+            raise TypeError(
+                f'Cannot assign type {type(_val)} to error.'
+                ' Expected a real number.',
+            )
+
+        self._error = _val
+
+    @property
+    def model(self) -> MachineModel:
+        """Return the current target MachineModel."""
+        return self._model
+
+    @model.setter
+    def model(self, _val: MachineModel) -> None:
+        if not isinstance(_val, MachineModel):
+            raise TypeError(
+                f'Cannot set model to {type(_val)}.'
+                ' Expected a MachineModel.',
+            )
+
+        self._model = _val
+
+    @property
+    def gate_set(self) -> GateSet:
+        """Return the current target MachineModel's GateSet."""
+        return self._model.gate_set
+
+    @gate_set.setter
+    def gate_set(self, _val: GateSet) -> None:
+        if not isinstance(_val, GateSet):
+            raise TypeError(
+                f'Cannot set gate_set to {type(_val)}.'
+                ' Expected a GateSet.',
+            )
+
+        self._model.gate_set = _val
+
+    @property
+    def placement(self) -> list[int]:
+        """Return the current placement of circuit qudits on model qudits."""
+        return self._placement
+
+    @placement.setter
+    def placement(self, _val: Sequence[int]) -> None:
+        if not is_sequence(_val):
+            raise TypeError(
+                f'Cannot set placement to {type(_val)}.'
+                ' Expected a sequence of integers.',
+            )
+
+        if not all(is_integer(x) for x in _val):
+            raise TypeError(
+                'Cannot set placement. Expected a sequence of integers.',
+            )
+
+        self._placement = list(int(x) for x in _val)
+
+    @property
+    def seed(self) -> int | None:
+        """Return the pass's seed."""
+        return self._seed
+
+    @seed.setter
+    def seed(self, _val: int | None) -> None:
+        if _val is not None and not is_integer(_val):
+            raise TypeError(
+                f'Cannot set seed to {type(_val)}.'
+                ' Expected an integer or none.',
+            )
+        self._seed = _val
+
+    @property
+    def connectivity(self) -> CouplingGraph:
+        """Retrieve the physical connectivity of the circuit qudits."""
+        return self.model.coupling_graph.get_subgraph(self.placement)
+
+    def __getitem__(self, _key: str) -> Any:
+        """Retrieve the value associated with `_key` from the pass data."""
+        if _key in self._reserved_keys:
+            if _key == 'machine_model':
+                _key = 'model'
+            return self.__getattribute__(_key)
+
+        return self._data.__getitem__(_key)
+
+    def __setitem__(self, _key: str, _val: Any) -> None:
+        """Update the value associated with `_key` in the pass data."""
+        if _key in self._reserved_keys:
+            if _key == 'machine_model':
+                _key = 'model'
+            return self.__setattr__(_key, _val)
+
+        return self._data.__setitem__(_key, _val)
+
+    def __delitem__(self, _key: str) -> None:
+        """Delete the key-value pair associated with `_key`."""
+        if _key in self._reserved_keys:
+            raise RuntimeError(f'Cannot delete {_key} from data.')
+
+        return self._data.__delitem__(_key)
+
+    def __iter__(self) -> Iterator[str]:
+        """Return an iterator over all keys in the pass data."""
+        return it.chain(self._reserved_keys.__iter__(), self._data.__iter__())
+
+    def __len__(self) -> int:
+        """Return the number of key-value pairs in the pass data."""
+        return self._data.__len__() + len(self._reserved_keys)
+
+    def __contains__(self, _o: object) -> bool:
+        """Return true if `_o` is a key in the pass data."""
+        in_resv = self._reserved_keys.__contains__(_o)
+        in_data = self._data.__contains__(_o)
+        return in_resv or in_data
+
+    def copy(self) -> PassData:
+        """Returns a deep copy of the data."""
+        return copy.deepcopy(self)
+
+    def become(self, other: PassData, deepcopy: bool = False) -> None:
+        """Become a copy of `other`."""
+        if deepcopy:
+            self._target = copy.deepcopy(other._target)
+            self._error = copy.deepcopy(other._error)
+            self._model = copy.deepcopy(other._model)
+            self._placement = copy.deepcopy(other._placement)
+            self._data = copy.deepcopy(other._data)
+            self._seed = copy.deepcopy(other._seed)
+        else:
+            self._target = copy.copy(other._target)
+            self._error = copy.copy(other._error)
+            self._model = copy.copy(other._model)
+            self._placement = copy.copy(other._placement)
+            self._data = copy.copy(other._data)
+            self._seed = copy.copy(other._seed)
diff --git a/bqskit/compiler/status.py b/bqskit/compiler/status.py
new file mode 100644
index 000000000..74c94a355
--- /dev/null
+++ b/bqskit/compiler/status.py
@@ -0,0 +1,11 @@
+"""This module implements the CompilationStatus enum."""
+from __future__ import annotations
+
+from enum import IntEnum
+
+
+class CompilationStatus(IntEnum):
+    """The status of a CompilationTask."""
+    UNKNOWN = 0
+    RUNNING = 1
+    DONE = 2
diff --git a/bqskit/compiler/task.py b/bqskit/compiler/task.py
index 06da28720..0ba2a43c5 100644
--- a/bqskit/compiler/task.py
+++ b/bqskit/compiler/task.py
@@ -1,28 +1,15 @@
 """This module implements the CompilationTask class."""
 from __future__ import annotations
 
-import logging
 import uuid
-import warnings
-from typing import Sequence
-
-from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates.circuitgate import CircuitGate
-from bqskit.ir.operation import Operation
-from bqskit.passes.control import ForEachBlockPass
-from bqskit.passes.control.predicates.count import GateCountPredicate
-from bqskit.passes.control.whileloop import WhileLoopPass
-from bqskit.passes.partitioning.cluster import ClusteringPartitioner
-from bqskit.passes.partitioning.quick import QuickPartitioner
-from bqskit.passes.processing import ScanningGateRemovalPass
-from bqskit.passes.synthesis import QFASTDecompositionPass
-from bqskit.passes.synthesis.leap import LEAPSynthesisPass
-from bqskit.passes.util import UnfoldPass
-from bqskit.qis.unitary.unitarymatrix import UnitaryLike
-
-_logger = logging.getLogger(__name__)
+from typing import TYPE_CHECKING
+
+from bqskit.compiler.passdata import PassData
+from bqskit.compiler.workflow import Workflow
+
+if TYPE_CHECKING:
+    from bqskit.ir.circuit import Circuit
+    from bqskit.compiler.workflow import WorkflowLike
 
 
 class CompilationTask():
@@ -33,92 +20,40 @@ class CompilationTask():
     can be submitted to a BQSKit compiler to be efficiently executed.
     """
 
-    def __init__(self, input: Circuit, passes: Sequence[BasePass]) -> None:
+    def __init__(self, input: Circuit, workflow: WorkflowLike) -> None:
         """
         Construct a CompilationTask.
 
         Args:
             input (Circuit): The input circuit to be compiled.
 
-            passes (Sequence[BasePass]): The configured operations to be
+            workflow (WorkflowLike): The configured workflow to be
                 performed on the circuit.
         """
         self.task_id = uuid.uuid4()
-        self.input_circuit = input
-        self.passes = passes
-
-    @staticmethod
-    def synthesize(utry: UnitaryLike) -> CompilationTask:
-        """Produces a standard synthesis task for the given unitary."""
-        warnings.warn(
-            'Default task creation is deprecated and will soon be removed.\n'
-            'Instead, use the new compile function.',
-        )
-        circuit = Circuit.from_unitary(utry)
-        num_qudits = circuit.num_qudits
-
-        if num_qudits > 6:
-            _logger.warning('Synthesis input size is very large.')
-
-        inner_seq = [
-            LEAPSynthesisPass(),
-            ScanningGateRemovalPass(),
-        ]
-
-        passes: list[BasePass] = []
-        if num_qudits >= 5:
-            passes.append(QFASTDecompositionPass())
-            passes.append(
-                ForEachBlockPass(
-                    inner_seq,
-                    replace_filter=less_2q_gates,
-                ),
-            )
-            passes.append(UnfoldPass())
-        else:
-            passes.extend(inner_seq)
-
-        return CompilationTask(circuit, passes)
-
-    @staticmethod
-    def optimize(circuit: Circuit) -> CompilationTask:
-        """Produces a standard optimization task for the given circuit."""
-        warnings.warn(
-            'Default task creation is deprecated and will soon be removed.\n'
-            'Instead, use the new compile function.',
-        )
-        num_qudits = circuit.num_qudits
-
-        if num_qudits <= 3:
-            return CompilationTask.synthesize(circuit.get_unitary())
-
-        inner_seq = [
-            LEAPSynthesisPass(),
-            ScanningGateRemovalPass(),
-        ]
-
-        passes: list[BasePass] = []
-        passes.append(QuickPartitioner(3))
-        passes.append(ForEachBlockPass(inner_seq, replace_filter=less_2q_gates))
-        passes.append(UnfoldPass())
-
-        iterative_reopt = WhileLoopPass(
-            GateCountPredicate(CNOTGate()),
-            [
-                ClusteringPartitioner(3, 4),
-                ForEachBlockPass(inner_seq, replace_filter=less_2q_gates),
-                UnfoldPass(),
-            ],
-        )
-
-        passes.append(iterative_reopt)
-        return CompilationTask(circuit, passes)
-
-
-def less_2q_gates(circuit: Circuit, op: Operation) -> bool:
-    """Replace `circuit' with `op` if has less 2 qubit gates."""
-    if not isinstance(op, CircuitGate):
-        return True
-    og_num_2q_gate = len([op for op in op._circuit if op.num_qudits >= 2])
-    new_num_2q_gate = len([op for op in circuit if op.num_qudits >= 2])
-    return new_num_2q_gate > og_num_2q_gate
+        self.circuit = input
+        self.workflow = Workflow(workflow)
+
+        self.data = PassData(input)
+        """The task's data for use in BQSKit passes."""
+
+        self.done = False
+        """True when the task is complete."""
+
+        self.request_data = False
+        """If true, :func:`run` will additionally return the PassData."""
+
+        self.logging_level: int | None = None
+        """A general filter on all logging messages in the system."""
+
+        self.max_logging_depth = -1
+        """No logging for tasks with more than `max_logging_depth` parents."""
+
+    async def run(self) -> Circuit | tuple[Circuit, PassData]:
+        """Execute the task."""
+        await self.workflow.run(self.circuit, self.data)
+
+        if not self.request_data:
+            return self.circuit
+
+        return self.circuit, self.data
diff --git a/bqskit/compiler/workflow.py b/bqskit/compiler/workflow.py
new file mode 100644
index 000000000..a4900eca5
--- /dev/null
+++ b/bqskit/compiler/workflow.py
@@ -0,0 +1,121 @@
+"""This module implements the Workflow class."""
+from __future__ import annotations
+
+import copy
+from typing import Iterable
+from typing import Iterator
+from typing import overload
+from typing import Sequence
+from typing import TYPE_CHECKING
+from typing import Union
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.utils.random import seed_random_sources
+from bqskit.utils.typing import is_iterable
+
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
+
+class Workflow(BasePass, Sequence[BasePass]):
+    """A BQSKit workflow captures a quantum circuit compilation process."""
+
+    def __init__(self, passes: WorkflowLike) -> None:
+        """
+        Initialize a workflow object from a sequence of passes.
+
+        Raises:
+            ValueError: If passes is empty.
+        """
+        if isinstance(passes, Workflow):
+            self._passes: list[BasePass] = copy.deepcopy(passes._passes)
+            return
+
+        if isinstance(passes, BasePass):
+            passes = [passes]
+
+        if not is_iterable(passes):
+            msg = f'Expected Pass or sequence of Passes, got {type(passes)}.'
+            raise TypeError(msg)
+
+        if not all(isinstance(p, BasePass) for p in passes):
+            truth_list = [isinstance(p, BasePass) for p in passes]
+            wrong_type = type(list(passes)[truth_list.index(False)])
+            msg = f'Expected Pass or sequence of Passes, got {wrong_type}.'
+            raise TypeError(msg)
+
+        self._passes = list(passes)
+
+        if len(self._passes) == 0:
+            raise ValueError('Expected at least one pass in workflow.')
+
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        """Perform the pass's operation, see :class:`BasePass` for more."""
+        for pass_obj in self._passes:
+            if data.seed is not None:
+                seed_random_sources(data.seed)
+            await pass_obj.run(circuit, data)
+
+    # @staticmethod
+    # def build_compile_flow() -> Workflow:
+    #     pass
+
+    # @staticmethod
+    # def build_transpile_flow() -> Workflow:
+    #     pass
+
+    # @staticmethod
+    # def build_synthesize_flow() -> Workflow:
+    #     pass
+
+    # @staticmethod
+    # def build_map_flow() -> Workflow:
+    #     pass
+
+    # @staticmethod
+    # def build_prepare_flow() -> Workflow:
+    #     pass
+
+    def save(self, filename: str) -> None:
+        import pickle
+        with open(filename, 'wb') as f:
+            pickle.dump(self, f)
+
+    @staticmethod
+    def load(filename: str) -> Workflow:
+        import pickle
+        with open(filename, 'rb') as f:
+            return pickle.load(f)
+
+    # def __str__(self) -> str:
+    #     pass  # TODO:
+
+    # def __repr__(self) -> str:
+    #     pass  # TODO:
+
+    def __add__(self, other: WorkflowLike) -> Workflow:
+        return Workflow(self._passes + Workflow(other)._passes)
+
+    def __radd__(self, other: WorkflowLike) -> Workflow:
+        return Workflow(Workflow(other)._passes + self._passes)
+
+    def __len__(self) -> int:
+        return self._passes.__len__()
+
+    def __iter__(self) -> Iterator[BasePass]:
+        return self._passes.__iter__()
+
+    @overload
+    def __getitem__(self, _key: int, /) -> BasePass:
+        ...
+
+    @overload
+    def __getitem__(self, _key: slice, /) -> list[BasePass]:
+        ...
+
+    def __getitem__(self, _key: int | slice) -> BasePass | list[BasePass]:
+        return self._passes.__getitem__(_key)
+
+
+WorkflowLike = Union[Workflow, Iterable[BasePass], BasePass]
diff --git a/bqskit/exec/runners/quest.py b/bqskit/exec/runners/quest.py
index af1f8e6e4..d8a09d8ac 100644
--- a/bqskit/exec/runners/quest.py
+++ b/bqskit/exec/runners/quest.py
@@ -9,14 +9,13 @@
 import numpy.typing as npt
 from scipy.optimize import dual_annealing
 
-from bqskit.compiler import CompilationTask
-from bqskit.compiler import Compiler
-from bqskit.compiler import MachineModel
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.machine import MachineModel
 from bqskit.exec.results import RunnerResults
 from bqskit.exec.runner import CircuitRunner
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import CNOTGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.cx import CNOTGate
 from bqskit.ir.operation import Operation
 from bqskit.ir.point import CircuitPoint
 from bqskit.passes.control import ForEachBlockPass
@@ -104,17 +103,17 @@ def run(self, circuit: Circuit) -> RunnerResults:
         if self.model is not None:
             pass_list.insert(0, SetModelPass(self.model))
 
-        task = CompilationTask(circuit.copy(), pass_list)
-
         started = False
         if self.compiler is None:
             self.compiler = Compiler()
             started = True
-        # self.compiler = cast(Compiler, self.compiler)
-        blocked_circuit = self.compiler.compile(task)
+
+        blocked_circuit, data = self.compiler.compile(
+            circuit.copy(), pass_list, True,
+        )
 
         # 2. Gather partial solutions
-        data = self.compiler.analyze(task, ForEachBlockPass.key)
+        data = data[ForEachBlockPass.key]
         psols, pts = self.parse_data(blocked_circuit, data)
         # psols: psols[i] = list[[circuit, dist]] -> block i's partial solutions
         # pts: pts[i] = CircuitPoint -> block i's locations
@@ -358,14 +357,13 @@ def gen_approximate_circuits(
     if model is not None:
         pass_list.insert(0, SetModelPass(model))
 
-    task = CompilationTask(circuit.copy(), pass_list)
-
     started = False
     if compiler is None:
         compiler = Compiler()
         started = True
-    # compiler = cast(Compiler, compiler)
-    blocked_circuit = compiler.compile(task)
+
+    blocked_circuit, data = compiler.compile(circuit.copy(), pass_list, True)
+    data = data[ForEachBlockPass.key]
 
     # 2. Gather partial solutions
     runner = QuestRunner(
@@ -380,7 +378,6 @@ def gen_approximate_circuits(
         instantiate_options,
     )
 
-    data = compiler.analyze(task, ForEachBlockPass.key)
     psols, pts = runner.parse_data(blocked_circuit, data)
     # psols: psols[i] = list[[circuit, dist]] -> block i's partial solutions
     # pts: pts[i] = CircuitPoint -> block i's locations
diff --git a/bqskit/ext/cirq/models.py b/bqskit/ext/cirq/models.py
index 7adc5a6aa..274f14967 100644
--- a/bqskit/ext/cirq/models.py
+++ b/bqskit/ext/cirq/models.py
@@ -3,11 +3,11 @@
 
 from bqskit.compiler.machine import MachineModel
 from bqskit.ir.gate import Gate
-from bqskit.ir.gates import CZGate
-from bqskit.ir.gates import PhasedXZGate
-from bqskit.ir.gates import RZGate
-from bqskit.ir.gates import SqrtISwapGate
-from bqskit.ir.gates import SycamoreGate
+from bqskit.ir.gates.constant.cz import CZGate
+from bqskit.ir.gates.constant.sqrtiswap import SqrtISwapGate
+from bqskit.ir.gates.constant.sycamore import SycamoreGate
+from bqskit.ir.gates.parameterized.phasedxz import PhasedXZGate
+from bqskit.ir.gates.parameterized.rz import RZGate
 from bqskit.qis.graph import CouplingGraph
 
 google_gate_set: set[Gate] = {
diff --git a/bqskit/ext/honeywell.py b/bqskit/ext/honeywell.py
index b5ad28ac4..0ba09171a 100644
--- a/bqskit/ext/honeywell.py
+++ b/bqskit/ext/honeywell.py
@@ -3,8 +3,8 @@
 
 from bqskit.compiler.machine import MachineModel
 from bqskit.ir.gate import Gate
-from bqskit.ir.gates import RZGate
-from bqskit.ir.gates import ZZGate
+from bqskit.ir.gates.constant.zz import ZZGate
+from bqskit.ir.gates.parameterized.rz import RZGate
 from bqskit.ir.gates.parameterized.u1q import U1qPi2Gate
 from bqskit.ir.gates.parameterized.u1q import U1qPiGate
 
diff --git a/bqskit/ext/qiskit/models.py b/bqskit/ext/qiskit/models.py
index 433761bf4..237771390 100644
--- a/bqskit/ext/qiskit/models.py
+++ b/bqskit/ext/qiskit/models.py
@@ -5,7 +5,7 @@
 if TYPE_CHECKING:
     from qiskit.providers import BackendV1
 
-from bqskit.compiler import MachineModel
+from bqskit.compiler.machine import MachineModel
 from bqskit.ir.gate import Gate
 from bqskit.ir.gates.constant.cx import CNOTGate
 from bqskit.ir.gates.constant.cz import CZGate
diff --git a/bqskit/ext/rigetti.py b/bqskit/ext/rigetti.py
index 1db32b9ef..15bba867a 100644
--- a/bqskit/ext/rigetti.py
+++ b/bqskit/ext/rigetti.py
@@ -2,10 +2,10 @@
 from __future__ import annotations
 
 from bqskit.compiler.machine import MachineModel
-from bqskit.ir.gates import CZGate
-from bqskit.ir.gates import RZGate
-from bqskit.ir.gates import SXGate
-from bqskit.ir.gates import XGate
+from bqskit.ir.gates.constant.cz import CZGate
+from bqskit.ir.gates.constant.sx import SXGate
+from bqskit.ir.gates.constant.x import XGate
+from bqskit.ir.gates.parameterized.rz import RZGate
 from bqskit.qis.graph import CouplingGraph
 
 rigetti_gate_set = {SXGate(), XGate(), RZGate(), CZGate()}
diff --git a/bqskit/ir/circuit.py b/bqskit/ir/circuit.py
index d0932f884..47e800fe5 100644
--- a/bqskit/ir/circuit.py
+++ b/bqskit/ir/circuit.py
@@ -3,6 +3,7 @@
 
 import copy
 import logging
+import warnings
 from typing import Any
 from typing import cast
 from typing import Collection
@@ -19,8 +20,6 @@
 
 import numpy as np
 import numpy.typing as npt
-from distributed import get_client
-from distributed import secede
 
 from bqskit.ir.gate import Gate
 from bqskit.ir.gates.circuitgate import CircuitGate
@@ -32,13 +31,11 @@
 from bqskit.ir.location import CircuitLocation
 from bqskit.ir.location import CircuitLocationLike
 from bqskit.ir.operation import Operation
-from bqskit.ir.opt.cost.functions import HilbertSchmidtCostGenerator
 from bqskit.ir.opt.cost.generator import CostFunctionGenerator
 from bqskit.ir.opt.instantiater import Instantiater
 from bqskit.ir.opt.instantiaters import instantiater_order
 from bqskit.ir.opt.minimizers.ceres import CeresMinimizer
 from bqskit.ir.opt.multistartgen import MultiStartGenerator
-from bqskit.ir.opt.multistartgens.random import RandomStartGenerator
 from bqskit.ir.point import CircuitPoint
 from bqskit.ir.point import CircuitPointLike
 from bqskit.ir.region import CircuitRegion
@@ -47,6 +44,7 @@
 from bqskit.qis.state.state import StateLike
 from bqskit.qis.state.state import StateVector
 from bqskit.qis.state.statemap import StateVectorMap
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.differentiable import DifferentiableUnitary
 from bqskit.qis.unitary.unitary import RealVector
 from bqskit.qis.unitary.unitarybuilder import UnitaryBuilder
@@ -57,12 +55,12 @@
 from bqskit.utils.typing import is_integer
 from bqskit.utils.typing import is_iterable
 from bqskit.utils.typing import is_sequence_of_int
-from bqskit.utils.typing import is_square_matrix
 from bqskit.utils.typing import is_valid_radixes
-from bqskit.utils.typing import is_vector
 
 if TYPE_CHECKING:
     from bqskit.ir.opt.cost.function import CostFunction
+    from bqskit.compiler.basepass import BasePass
+    from bqskit.compiler.gateset import GateSet
 
 _logger = logging.getLogger(__name__)
 
@@ -206,6 +204,17 @@ def depth(self) -> int:
             qudit_depths[list(op.location)] = new_depth
         return int(max(qudit_depths))
 
+    @property
+    def multi_qudit_depth(self) -> int:
+        """The length of the critical path excluding single-qudit gates."""
+        qudit_depths = np.zeros(self.num_qudits, dtype=int)
+        for op in self:
+            if op.num_qudits == 1:
+                continue
+            new_depth = max(qudit_depths[list(op.location)]) + 1
+            qudit_depths[list(op.location)] = new_depth
+        return int(max(qudit_depths))
+
     @property
     def parallelism(self) -> float:
         """The amount of parallelism in the circuit."""
@@ -236,9 +245,23 @@ def coupling_graph(self) -> CouplingGraph:
         return CouplingGraph(self._graph_info.keys(), self.num_qudits)
 
     @property
-    def gate_set(self) -> set[Gate]:
+    def gate_set(self) -> GateSet:
         """The set of gates in the circuit."""
-        return set(self._gate_info.keys())
+        from bqskit.compiler.gateset import GateSet
+        return GateSet(set(self._gate_info.keys()))
+
+    @property
+    def gate_set_no_blocks(self) -> GateSet:
+        """The set of gates in the circuit, recurses into circuit gates."""
+        from bqskit.compiler.gateset import GateSet
+        gates: set[Gate] = set()
+        for g, _ in self._gate_info.items():
+            if isinstance(g, CircuitGate):
+                for other_g in g._circuit.gate_set_no_blocks:
+                    gates.add(other_g)
+            else:
+                gates.add(g)
+        return GateSet(gates)
 
     @property
     def gate_counts(self) -> dict[Gate, int]:
@@ -865,6 +888,14 @@ def rear(self) -> set[CircuitPoint]:
                 rear_set.add(point)
         return rear_set
 
+    def last_on(self, qudit: int) -> CircuitPoint | None:
+        """Report the point for the last operation on `qudit` if it exists."""
+        return self._rear[qudit]
+
+    def first_on(self, qudit: int) -> CircuitPoint | None:
+        """Report the point for the first operation on `qudit` if it exists."""
+        return self._front[qudit]
+
     def next(self, point: CircuitPoint) -> set[CircuitPoint]:
         """Return the points of operations dependent on the one at `point`."""
         return {p for p in self._dag[point][1].values() if p is not None}
@@ -2372,14 +2403,14 @@ def get_operations(
                 f'Expected a circuit point iterable, got {type(points)}.',
             )
 
-        ops: set[tuple[int, Operation]] = set()
+        ops: list[tuple[int, Operation]] = list()
         for point in points:
             try:
-                ops.add((point[0], self.get_operation(point)))
+                ops.append((point[0], self.get_operation(point)))
             except IndexError:
                 continue
 
-        return [op_and_cycle[1] for op_and_cycle in ops]
+        return [op_and_cycle[1] for op_and_cycle in list(dict.fromkeys(ops))]
 
     def get_slice(self, points: Sequence[CircuitPointLike]) -> Circuit:
         """Return a copy of a slice of this circuit."""
@@ -2538,10 +2569,48 @@ def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
 
         return utry.get_unitary()
 
-    def get_statevector(self, in_state: StateLike) -> StateVector:
-        """Calculate the output state given the `in_state` input state."""
-        # TODO: Can be made a lot more efficient.
-        return self.get_unitary().get_statevector(in_state)
+    def get_statevector(
+        self,
+        in_state: StateLike,
+        params: RealVector = [],
+    ) -> StateVector:
+        """
+        Return the result of applying `self` to `in_state`
+
+        Args:
+            params (RealVector): Optionally specify parameters
+                overriding the ones stored in the circuit. (Default:
+                use parameters already in circuit.)
+
+        Returns:
+            The StateVector object for the new state after the circuit
+
+        Raises:
+            ValueError: If parameters are specified and invalid.
+
+        Examples:
+            >>> circ = Circuit(1)
+            >>> op = Operation(H(), [0])
+            >>> circ.append(op)
+            >>> V = StateVector([1,0])
+            >>> circ.get_statevector(V).numpy == np.array([1,1])/np.sqrt(2)
+            True
+        """
+        if len(params) != 0:
+            self.check_parameters(params)
+            param_index = 0
+
+        new_state = StateVector(in_state)
+
+        for op in self:
+            if len(params) != 0:
+                gparams = params[param_index:param_index + op.num_params]
+                new_state.apply(op.get_unitary(gparams), op.location)
+                param_index += op.num_params
+            else:
+                new_state.apply(op.get_unitary(), op.location)
+
+        return new_state
 
     def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]:
         """Return the gradient of the circuit."""
@@ -2591,15 +2660,53 @@ def get_unitary_and_grad(
 
         return left.get_unitary(), np.array(full_grads)
 
+    def perform(
+        self,
+        compiler_pass: BasePass,
+        data: dict[str, Any] | None = None,
+    ) -> None:
+        """
+        Execute the provided `compiler_pass` on this circuit.
+
+        This function is necessary since BQSKit Pass objects cannot have
+        their :func:`~bqskit.compiler.basepass.run` function directly called
+        on a circuit.
+
+        Args:
+            compiler_pass (BasePass): The BQSKit pass to perform on this
+                circuit.
+
+            data (dict[str, Any] | None): Optionally provide additional
+                pass data to the compiler pass.
+
+        Note:
+            You cannot perform a pass using this method while a local
+            :class:`~bqskit.compiler.compiler.Compiler` object is live.
+            Rather, you should use the
+            :class:`~bqskit.compiler.compiler.Compiler` directly.
+        """
+        from bqskit.compiler.compiler import Compiler
+        from bqskit.compiler.passdata import PassData
+        from bqskit.compiler.task import CompilationTask
+
+        pass_data = PassData(self)
+        if data is not None:
+            pass_data.update(data)
+
+        with Compiler() as compiler:
+            task = CompilationTask(self, [compiler_pass])
+            task.data = pass_data
+            task_id = compiler.submit(task)
+            self.become(compiler.result(task_id))  # type: ignore
+
     def instantiate(
         self,
-        target: StateLike | UnitaryLike,
+        target: StateLike | UnitaryLike | StateSystem,
         method: str | Instantiater | None = None,
         multistarts: int = 1,
         seed: int | None = None,
-        multistart_gen: MultiStartGenerator = RandomStartGenerator(),
-        score_fn_gen: CostFunctionGenerator = HilbertSchmidtCostGenerator(),
-        parallel: bool = False,
+        multistart_gen: MultiStartGenerator | None = None,
+        score_fn_gen: CostFunctionGenerator | None = None,
         **kwargs: Any,
     ) -> Circuit:
         """
@@ -2610,12 +2717,13 @@ def instantiate(
         state to the target state.
 
         Args:
-            target (StateLike | UnitaryLike): The target unitary or state.
-                If a unitary is specified, the method changes the circuit's
-                parameters in an effort to get closer to implementing the
-                target. If a state is specified, the method changes the
-                circuit's parameters in an effort to get closer to producing
-                the target state when starting from the zero state.
+            target (StateLike | UnitaryLike | StateSystem): The target unitary
+                or state. If a unitary is specified, the method changes
+                the circuit's parameters in an effort to get closer to
+                implementing the target. If a state is specified, the
+                method changes the circuit's parameters in an effort to
+                get closer to producing the target state when starting
+                from the zero state.
 
             method (str | Instantiater | None): The method with which to
                 instantiate the circuit. Currently, `"qfactor"` and
@@ -2624,25 +2732,15 @@ def instantiate(
                 directly through this.
 
             multistarts (int): The number of starting points to sample
-                instantiation with. If `parallel` is True and this is greater
-                than one, will spawn this many Dask tasks. (Default: 1)
+                instantiation with. (Default: 1)
 
             seed (int | None): The seed for any pseudo-random number generators
                 to use. Note that this is not guaranteed to make this method
                 reproducible.
 
-            multistart_gen (MultiStartGenerator): The generator used to
-                generate starting points for instantiation.
-                (Default: RandomStartGenerator())
-
-            score_fn_gen (CostFunctionGenerator): The generator used to produce
-                a cost function, which will be used to evaluate the best result
-                from the different starting points.
-                (Default: HilbertSchmidtCostGenerator())
+            multistart_gen (MultiStartGenerator): (Deprecated)
 
-            parallel (bool): If True and `multistarts` is greater than 1,
-                this will attempt to connect to a dask cluster and submit
-                jobs to be run in parallel. (Default: False)
+            score_fn_gen (CostFunctionGenerator):  (Deprecated)
 
             kwargs (dict[str, Any]): Method specific options, passed
                 directly to method constructor. For more info, see
@@ -2662,6 +2760,23 @@ def instantiate(
 
             ValueError: If `seed` is not an integer or `None`
         """
+        if multistart_gen is not None or score_fn_gen is not None:
+            warnings.warn(
+                'Multistart handling has moved from the `circuit.instantiate`'
+                ' method to the Instantiater class. See'
+                ' `Instantiater.multi_start_instantiate` for more info. If you'
+                ' would like to override how starts are generated or'
+                ' instantiation results are processed, you should subclass'
+                ' an Instantiater. An Instantiatier object can be passed to'
+                ' `circuit.instantiate` through the `method` parameter. It'
+                ' can also be passed to most BQSKit compiler passes through'
+                " the `instantiate_options={'method':...} parameter. The use"
+                ' of `multistart_gen` and `score_fn_gen` parameters in'
+                ' `circuit.instantiate` is deprecated, and this warning'
+                ' will turn into an error in the future.',
+                DeprecationWarning,
+            )
+
         # Set seed if specified
         if seed is not None:
             if not isinstance(seed, int):
@@ -2718,95 +2833,8 @@ def instantiate(
 
         instantiater = cast(Instantiater, instantiater)
 
-        # Check Target
-        if is_square_matrix(target):
-            target = UnitaryMatrix(target)  # type: ignore
-        elif is_vector(target):
-            target = StateVector(target)  # type: ignore
-        else:
-            raise TypeError(
-                'Expected either StateVector or UnitaryMatrix'
-                ' for target, got %s.' % type(target),
-            )
-
-        if target.dim != self.dim:
-            raise ValueError('Target dimension mismatch with circuit.')
-
-        # Generate starting points
-        starts = multistart_gen.gen_starting_points(multistarts, self, target)
-
-        if len(starts) != multistarts:
-            raise ValueError(
-                'Error generating starting points for instantiation.\n'
-                f'Expected {multistarts} starts but got {len(starts)}.',
-            )
-
-        # Generate cost function
-        if not isinstance(score_fn_gen, CostFunctionGenerator):
-            raise TypeError(
-                'Expected CostFunctionGenerator, got %s.' % type(score_fn_gen),
-            )
-
-        cost_fn = score_fn_gen.gen_cost(self, target)
-
-        # Instantiate the circuit
-        if parallel and multistarts > 1:
-            client = get_client()
-
-            def single_start_instantiate(
-                instantiater: Instantiater,
-                circuit: Circuit,
-                target: UnitaryMatrix,
-                start: npt.NDArray[np.float64],
-            ) -> npt.NDArray[np.float64]:
-                return instantiater.instantiate(circuit, target, start)
-
-            def scoring_fn(
-                fn_gen: CostFunctionGenerator,
-                circuit: Circuit,
-                target: UnitaryMatrix,
-                params: npt.NDArray[np.float64],
-            ) -> float:
-                return fn_gen.gen_cost(circuit, target).get_cost(params)
-
-            param_futures = client.map(
-                single_start_instantiate,
-                [instantiater] * multistarts,
-                [self] * multistarts,
-                [target] * multistarts,
-                starts,
-                pure=False,
-            )
-
-            score_futures = client.map(
-                scoring_fn,
-                [score_fn_gen] * multistarts,
-                [self] * multistarts,
-                [target] * multistarts,
-                param_futures,
-                pure=False,
-            )
-
-            # We only want to secede on worker threads, so try to recover if
-            # Circuit.instantiate is called from the main thread
-            try:
-                secede()
-            except ValueError:
-                pass
-
-            scores = client.gather(score_futures)
-            best_index = scores.index(min(scores))
-            params = param_futures[best_index].result()
-
-        else:
-            params_list = [
-                instantiater.instantiate(self, target, start)
-                for start in starts
-            ]
-            params = sorted(params_list, key=lambda x: cost_fn(x))[0]
-
-        # Return best result
-        self.set_params(params)
+        # Instantiate
+        instantiater.multi_start_instantiate_inplace(self, target, multistarts)
         return self
 
     def minimize(self, cost: CostFunction, **kwargs: Any) -> None:
diff --git a/bqskit/ir/gates/__init__.py b/bqskit/ir/gates/__init__.py
index 44883eb11..e385f9a2f 100644
--- a/bqskit/ir/gates/__init__.py
+++ b/bqskit/ir/gates/__init__.py
@@ -23,7 +23,10 @@
     :nosignatures:
     :template: autosummary/gate.rst
 
+    BGate
     CCXGate
+    RCCXGate
+    RC3XGate
     ToffoliGate
     CHGate
     CPIGate
@@ -65,10 +68,13 @@
     :nosignatures:
     :template: autosummary/gate.rst
 
+    ArbitraryCPhaseGate
+    CCPGate
     CPGate
     CRXGate
     CRYGate
     CRZGate
+    CUGate
     FSIMGate
     PauliGate
     PhasedXZGate
@@ -110,9 +116,11 @@
 
     CircuitGate
     MeasurementPlaceholder
+    BarrierPlaceholder
 """
 from __future__ import annotations
 
+from bqskit.ir.gates.barrier import BarrierPlaceholder
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.gates.composed import *  # noqa
 from bqskit.ir.gates.composed import __all__ as composed_all
@@ -120,6 +128,7 @@
 from bqskit.ir.gates.constant import *  # noqa
 from bqskit.ir.gates.constant import __all__ as constant_all
 from bqskit.ir.gates.constantgate import ConstantGate
+from bqskit.ir.gates.generalgate import GeneralGate
 from bqskit.ir.gates.measure import MeasurementPlaceholder
 from bqskit.ir.gates.parameterized import *  # noqa
 from bqskit.ir.gates.parameterized import __all__ as parameterized_all
@@ -128,4 +137,5 @@
 
 __all__ = composed_all + constant_all + parameterized_all
 __all__ += ['ComposedGate', 'QubitGate', 'QutritGate', 'ConstantGate']
-__all__ += ['CircuitGate', 'MeasurementPlaceholder']
+__all__ += ['CircuitGate', 'MeasurementPlaceholder', 'BarrierPlaceholder']
+__all__ += ['GeneralGate']
diff --git a/bqskit/ir/gates/barrier.py b/bqskit/ir/gates/barrier.py
new file mode 100644
index 000000000..8a87b0276
--- /dev/null
+++ b/bqskit/ir/gates/barrier.py
@@ -0,0 +1,33 @@
+"""This module implements the BarrierPlaceholder class."""
+from __future__ import annotations
+
+from typing import Sequence
+
+from bqskit.ir.gate import Gate
+from bqskit.qis.unitary.unitary import RealVector
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+
+
+class BarrierPlaceholder(Gate):
+    """Pseudogate to force break partitioning algorithms."""
+
+    def __init__(self, num_qudits: int, radixes: Sequence[int] = []) -> None:
+        """Construct a BarrierPlaceholder."""
+        self._name = 'barrier'
+        self._qasm_name = 'barrier'
+        self._num_qudits = num_qudits
+        self._radixes = tuple(radixes) if radixes else tuple([2] * num_qudits)
+        self._num_params = 0
+
+    def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
+        return UnitaryMatrix.identity(self.dim, self.radixes)
+
+    def __eq__(self, other: object) -> bool:
+        return (
+            isinstance(other, BarrierPlaceholder)
+            and other.num_qudits == self.num_qudits
+            and other.radixes == self.radixes
+        )
+
+    def __hash__(self) -> int:
+        return hash(('BarrierPlaceholder', self.num_qudits, self.radixes))
diff --git a/bqskit/ir/gates/constant/__init__.py b/bqskit/ir/gates/constant/__init__.py
index e688afe1a..2fd9cf3ef 100644
--- a/bqskit/ir/gates/constant/__init__.py
+++ b/bqskit/ir/gates/constant/__init__.py
@@ -1,6 +1,7 @@
 """This package defines constant gates."""
 from __future__ import annotations
 
+from bqskit.ir.gates.constant.b import BGate
 from bqskit.ir.gates.constant.ccx import CCXGate
 from bqskit.ir.gates.constant.ccx import ToffoliGate
 from bqskit.ir.gates.constant.ch import CHGate
@@ -21,6 +22,9 @@
 from bqskit.ir.gates.constant.itoffoli import IToffoliGate
 from bqskit.ir.gates.constant.pd import PDGate
 from bqskit.ir.gates.constant.permutation import PermutationGate
+from bqskit.ir.gates.constant.rccx import MargolusGate
+from bqskit.ir.gates.constant.rccx import RC3XGate
+from bqskit.ir.gates.constant.rccx import RCCXGate
 from bqskit.ir.gates.constant.s import SGate
 from bqskit.ir.gates.constant.sdg import SdgGate
 from bqskit.ir.gates.constant.shift import ShiftGate
@@ -43,6 +47,9 @@
 
 __all__ = [
     'CCXGate',
+    'RCCXGate',
+    'RC3XGate',
+    'MargolusGate',
     'ToffoliGate',
     'CHGate',
     'ClockGate',
@@ -81,4 +88,5 @@
     'YYGate',
     'ZGate',
     'ZZGate',
+    'BGate',
 ]
diff --git a/bqskit/ir/gates/constant/b.py b/bqskit/ir/gates/constant/b.py
new file mode 100644
index 000000000..9324e94c1
--- /dev/null
+++ b/bqskit/ir/gates/constant/b.py
@@ -0,0 +1,33 @@
+"""This module implements the BGate."""
+from __future__ import annotations
+
+from numpy import pi
+from scipy.linalg import expm
+
+from bqskit.ir.gates.constantgate import ConstantGate
+from bqskit.ir.gates.qubitgate import QubitGate
+from bqskit.qis.pauli import PauliMatrices
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+
+
+class BGate(ConstantGate, QubitGate):
+    """
+    The 2 qubit B gate.
+
+    The B gate is given by the following unitary:
+
+    .. math::
+        \\exp(i * \\pi/4 * \\sigma_{xx}) * \\exp(i * \\pi/8 * \\sigma_{yy})
+
+    Unitary expression taken from: https://arxiv.org/pdf/quant-ph/0312193.pdf
+    """
+    _num_qudits = 2
+    _qasm_name = 'b'
+
+    def __init__(self) -> None:
+        paulis = PauliMatrices(2)
+        xx = paulis[5]
+        yy = paulis[10]
+        self._utry = UnitaryMatrix(
+            expm(1j * pi / 4 * xx) @ expm(1j * pi / 8 * yy),
+        )
diff --git a/bqskit/ir/gates/constant/rccx.py b/bqskit/ir/gates/constant/rccx.py
new file mode 100644
index 000000000..221bc661c
--- /dev/null
+++ b/bqskit/ir/gates/constant/rccx.py
@@ -0,0 +1,65 @@
+"""This module implements the RCCXGate/MargolusGate."""
+from __future__ import annotations
+
+from bqskit.ir.gates.constantgate import ConstantGate
+from bqskit.ir.gates.qubitgate import QubitGate
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+
+
+class RCCXGate(ConstantGate, QubitGate):
+    """
+    The relative phase ccx gate.
+
+    Reference:
+        https://arxiv.org/pdf/1508.03273.pdf
+    """
+
+    _num_qudits = 3
+    _qasm_name = 'rccx'
+    _utry = UnitaryMatrix(
+        [
+            [1, 0, 0, 0, 0, 0, 0, 0],
+            [0, 1, 0, 0, 0, 0, 0, 0],
+            [0, 0, 1, 0, 0, 0, 0, 0],
+            [0, 0, 0, 1, 0, 0, 0, 0],
+            [0, 0, 0, 0, 1, 0, 0, 0],
+            [0, 0, 0, 0, 0, -1, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, -1j],
+            [0, 0, 0, 0, 0, 0, 1j, 0],
+        ],
+    )
+
+
+MargolusGate = RCCXGate
+
+
+class RC3XGate(ConstantGate, QubitGate):
+    """
+    The relative phase c3x gate.
+
+    Reference:
+        https://arxiv.org/pdf/1508.03273.pdf
+    """
+
+    _num_qudits = 4
+    _qasm_name = 'rc3x'
+    _utry = UnitaryMatrix(
+        [
+            [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+            [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+            [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+            [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+            [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+            [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1j, 0, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1j, 0, 0],
+            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
+            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0],
+        ],
+    )
diff --git a/bqskit/ir/gates/generalgate.py b/bqskit/ir/gates/generalgate.py
new file mode 100644
index 000000000..d4e677ca4
--- /dev/null
+++ b/bqskit/ir/gates/generalgate.py
@@ -0,0 +1,37 @@
+"""This module implements the GeneralGate base class."""
+from __future__ import annotations
+
+import abc
+from typing import Sequence
+
+import numpy as np
+import numpy.typing as npt
+import scipy as sp
+
+from bqskit.ir.gate import Gate
+from bqskit.qis.unitary.optimizable import LocallyOptimizableUnitary
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+
+
+class GeneralGate(Gate, LocallyOptimizableUnitary):
+    """An abstract base class for gates that parameterize any unitary."""
+
+    @abc.abstractmethod
+    def calc_params(self, utry: UnitaryMatrix) -> list[float]:
+        """Return the parameters for this gate to implement `utry`."""
+
+    def identity_as_params(self, radixes: Sequence[int]) -> list[float]:
+        """Return the parameters for the gate that implements the identity."""
+        identity = UnitaryMatrix.identity(int(np.prod(radixes)), radixes)
+        return self.calc_params(identity)
+
+    def optimize(self, env_matrix: npt.NDArray[np.complex128]) -> list[float]:
+        """
+        Return the optimal parameters with respect to an environment matrix.
+
+        See :class:`LocallyOptimizableUnitary` for more info.
+        """
+        self.check_env_matrix(env_matrix)
+        U, _, Vh = sp.linalg.svd(env_matrix)
+        new_U = Vh.conj().T @ U.conj().T
+        return self.calc_params(UnitaryMatrix(new_U, self.radixes))
diff --git a/bqskit/ir/gates/parameterized/__init__.py b/bqskit/ir/gates/parameterized/__init__.py
index 06a56f520..9eac7d50a 100644
--- a/bqskit/ir/gates/parameterized/__init__.py
+++ b/bqskit/ir/gates/parameterized/__init__.py
@@ -1,10 +1,13 @@
 """This package contains parameterized gates."""
 from __future__ import annotations
 
+from bqskit.ir.gates.parameterized.ccp import CCPGate
 from bqskit.ir.gates.parameterized.cp import CPGate
+from bqskit.ir.gates.parameterized.cphase import ArbitraryCPhaseGate
 from bqskit.ir.gates.parameterized.crx import CRXGate
 from bqskit.ir.gates.parameterized.cry import CRYGate
 from bqskit.ir.gates.parameterized.crz import CRZGate
+from bqskit.ir.gates.parameterized.cu import CUGate
 from bqskit.ir.gates.parameterized.fsim import FSIMGate
 from bqskit.ir.gates.parameterized.pauli import PauliGate
 from bqskit.ir.gates.parameterized.phasedxz import PhasedXZGate
@@ -24,10 +27,13 @@
 from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
 
 __all__ = [
+    'ArbitraryCPhaseGate',
+    'CCPGate',
     'CPGate',
     'CRXGate',
     'CRYGate',
     'CRZGate',
+    'CUGate',
     'FSIMGate',
     'PauliGate',
     'PhasedXZGate',
diff --git a/bqskit/ir/gates/parameterized/ccp.py b/bqskit/ir/gates/parameterized/ccp.py
new file mode 100644
index 000000000..c8d0faa14
--- /dev/null
+++ b/bqskit/ir/gates/parameterized/ccp.py
@@ -0,0 +1,84 @@
+"""This module implements the CCPGate."""
+from __future__ import annotations
+
+import numpy as np
+import numpy.typing as npt
+
+from bqskit.ir.gates.qubitgate import QubitGate
+from bqskit.qis.unitary.differentiable import DifferentiableUnitary
+from bqskit.qis.unitary.unitary import RealVector
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+from bqskit.utils.cachedclass import CachedClass
+
+
+class CCPGate(
+    QubitGate,
+    DifferentiableUnitary,
+    CachedClass,
+):
+    """
+    A gate representing a controlled controlled phase rotation.
+
+    It is given by the following parameterized unitary:
+
+    .. math::
+
+        \\begin{pmatrix}
+        1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\
+        0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\
+        0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\\\
+        0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\\\
+        0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\\\
+        0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\\\
+        0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\\\
+        0 & 0 & 0 & 0 & 0 & 0 & 0 & \\exp({i\\theta}) \\\\
+        \\end{pmatrix}
+    """
+
+    _num_qudits = 3
+    _num_params = 1
+    _qasm_name = 'ccp'
+
+    def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
+        """Return the unitary for this gate, see :class:`Unitary` for more."""
+        self.check_parameters(params)
+
+        exp = np.exp(1j * params[0])
+
+        return UnitaryMatrix(
+            [
+                [1, 0, 0, 0, 0, 0, 0, 0],
+                [0, 1, 0, 0, 0, 0, 0, 0],
+                [0, 0, 1, 0, 0, 0, 0, 0],
+                [0, 0, 0, 1, 0, 0, 0, 0],
+                [0, 0, 0, 0, 1, 0, 0, 0],
+                [0, 0, 0, 0, 0, 1, 0, 0],
+                [0, 0, 0, 0, 0, 0, 1, 0],
+                [0, 0, 0, 0, 0, 0, 0, exp],
+            ],
+        )
+
+    def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]:
+        """
+        Return the gradient for this gate.
+
+        See :class:`DifferentiableUnitary` for more info.
+        """
+        self.check_parameters(params)
+
+        dexp = 1j * np.exp(1j * params[0])
+
+        return np.array(
+            [
+                [
+                    [0, 0, 0, 0, 0, 0, 0, 0],
+                    [0, 0, 0, 0, 0, 0, 0, 0],
+                    [0, 0, 0, 0, 0, 0, 0, 0],
+                    [0, 0, 0, 0, 0, 0, 0, 0],
+                    [0, 0, 0, 0, 0, 0, 0, 0],
+                    [0, 0, 0, 0, 0, 0, 0, 0],
+                    [0, 0, 0, 0, 0, 0, 0, 0],
+                    [0, 0, 0, 0, 0, 0, 0, dexp],
+                ],
+            ], dtype=np.complex128,
+        )
diff --git a/bqskit/ir/gates/parameterized/cphase.py b/bqskit/ir/gates/parameterized/cphase.py
new file mode 100644
index 000000000..6f9c2b684
--- /dev/null
+++ b/bqskit/ir/gates/parameterized/cphase.py
@@ -0,0 +1,77 @@
+"""This module implements the ArbitraryCPhaseGate."""
+from __future__ import annotations
+
+from typing import Sequence
+
+import numpy as np
+import numpy.typing as npt
+
+from bqskit.ir.gate import Gate
+from bqskit.qis.unitary.differentiable import DifferentiableUnitary
+from bqskit.qis.unitary.optimizable import LocallyOptimizableUnitary
+from bqskit.qis.unitary.unitary import RealVector
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+from bqskit.utils.cachedclass import CachedClass
+from bqskit.utils.typing import is_sequence
+
+
+class ArbitraryCPhaseGate(
+    Gate,
+    DifferentiableUnitary,
+    CachedClass,
+    LocallyOptimizableUnitary,
+):
+    """A gate representing an arbitrary qudit controlled phase rotation."""
+
+    _num_params = 1
+
+    def __init__(self, radixes: Sequence[int] = []) -> None:
+        if len(radixes) == 0:
+            radixes = [2, 2]
+
+        if not is_sequence(radixes):
+            raise TypeError(
+                f'Expected sequence for radixes, got {type(radixes)}.',
+            )
+
+        if any(r <= 1 for r in radixes):
+            raise TypeError('Invalid radixes, all radixes must be >= 2.')
+
+        self._num_qudits = len(radixes)
+        self._radixes = tuple(radixes)
+
+    def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
+        """Return the unitary for this gate, see :class:`Unitary` for more."""
+        self.check_parameters(params)
+        U = np.identity(self.dim, dtype=np.complex128)
+        U[-1, -1] = np.exp(1j * params[0])
+        return UnitaryMatrix(U)
+
+    def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]:
+        """
+        Return the gradient for this gate.
+
+        See :class:`DifferentiableUnitary` for more info.
+        """
+        self.check_parameters(params)
+        dU = np.zeros((1, self.dim, self.dim), dtype=np.complex128)
+        dU[-1, -1, -1] = 1j * np.exp(1j * params[0])
+        return dU
+
+    def optimize(self, env_matrix: npt.NDArray[np.complex128]) -> list[float]:
+        """
+        Return the optimal parameters with respect to an environment matrix.
+
+        See :class:`LocallyOptimizableUnitary` for more info.
+        """
+        p = env_matrix[-1, -1]
+        return [-np.arctan2(p.imag, p.real)]
+
+    def __eq__(self, other: object) -> bool:
+        """Check if `self` equals `other`."""
+        if not isinstance(other, ArbitraryCPhaseGate):
+            return False
+        return self.radixes == other.radixes
+
+    def __hash__(self) -> int:
+        return hash(self.radixes)
diff --git a/bqskit/ir/gates/parameterized/cu.py b/bqskit/ir/gates/parameterized/cu.py
new file mode 100644
index 000000000..fc949859e
--- /dev/null
+++ b/bqskit/ir/gates/parameterized/cu.py
@@ -0,0 +1,115 @@
+"""This module implements the CUGate."""
+from __future__ import annotations
+
+import numpy as np
+import numpy.typing as npt
+
+from bqskit.ir.gates.qubitgate import QubitGate
+from bqskit.qis.unitary.differentiable import DifferentiableUnitary
+from bqskit.qis.unitary.unitary import RealVector
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+from bqskit.utils.cachedclass import CachedClass
+
+
+class CUGate(
+    QubitGate,
+    DifferentiableUnitary,
+    CachedClass,
+):
+    """
+    A gate representing an arbitrary controlled rotation.
+
+    It is given by the following parameterized unitary:
+
+    .. math::
+
+        \\begin{pmatrix}
+        1 & 0 & 0 & 0 \\\\
+        0 & 1 & 0 & 0 \\\\
+        0 & 0 & \\exp{i\\gamma}cos(\\frac{\\theta}{2})
+        & -\\exp{i(\\gamma + \\lambda)}sin(\\frac{\\theta}{2}) \\\\
+        0 & 0 & \\exp{i(\\gamma + \\phi)}sin(\\frac{\\theta}{2})
+        & \\exp{i(\\gamma + \\phi + \\lambda)}cos(\\frac{\\theta}{2}) \\\\
+        \\end{pmatrix}
+    """
+
+    _num_qudits = 2
+    _num_params = 4
+    _qasm_name = 'cu'
+
+    def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
+        """Return the unitary for this gate, see :class:`Unitary` for more."""
+        self.check_parameters(params)
+
+        ct = np.cos(params[0] / 2)
+        st = np.sin(params[0] / 2)
+        cp = np.cos(params[1])
+        sp = np.sin(params[1])
+        cl = np.cos(params[2])
+        sl = np.sin(params[2])
+        cg = np.cos(params[3])
+        sg = np.sin(params[3])
+        el = cl + 1j * sl
+        ep = cp + 1j * sp
+        eg = cg + 1j * sg
+
+        return UnitaryMatrix(
+            [
+                [1, 0, 0, 0],
+                [0, 1, 0, 0],
+                [0, 0, eg * ct, -eg * el * st],
+                [0, 0, eg * ep * st, eg * ep * el * ct],
+            ],
+        )
+
+    def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]:
+        """
+        Return the gradient for this gate.
+
+        See :class:`DifferentiableUnitary` for more info.
+        """
+        self.check_parameters(params)
+
+        ct = np.cos(params[0] / 2)
+        st = np.sin(params[0] / 2)
+        cp = np.cos(params[1])
+        sp = np.sin(params[1])
+        cl = np.cos(params[2])
+        sl = np.sin(params[2])
+        cg = np.cos(params[3])
+        sg = np.sin(params[3])
+        el = cl + 1j * sl
+        ep = cp + 1j * sp
+        eg = cg + 1j * sg
+        del_ = -sl + 1j * cl
+        dep_ = -sp + 1j * cp
+        deg_ = -sg + 1j * cg
+
+        return np.array(
+            [
+                [
+                    [0, 0, 0, 0],
+                    [0, 0, 0, 0],
+                    [0, 0, -0.5 * eg * st, -0.5 * eg * ct * el],
+                    [0, 0, 0.5 * ct * ep * eg, -0.5 * st * el * ep * eg],
+                ],
+                [
+                    [0, 0, 0, 0],
+                    [0, 0, 0, 0],
+                    [0, 0, 0, 0],
+                    [0, 0, eg * st * dep_, eg * ct * el * dep_],
+                ],
+                [
+                    [0, 0, 0, 0],
+                    [0, 0, 0, 0],
+                    [0, 0, 0, -eg * st * del_],
+                    [0, 0, 0, eg * ct * ep * del_],
+                ],
+                [
+                    [0, 0, 0, 0],
+                    [0, 0, 0, 0],
+                    [0, 0, deg_ * ct, -deg_ * el * st],
+                    [0, 0, deg_ * ep * st, deg_ * ep * el * ct],
+                ],
+            ], dtype=np.complex128,
+        )
diff --git a/bqskit/ir/gates/parameterized/pauli.py b/bqskit/ir/gates/parameterized/pauli.py
index d6d900bb5..10ec47c9b 100644
--- a/bqskit/ir/gates/parameterized/pauli.py
+++ b/bqskit/ir/gates/parameterized/pauli.py
@@ -7,10 +7,10 @@
 import numpy.typing as npt
 import scipy as sp
 
+from bqskit.ir.gates.generalgate import GeneralGate
 from bqskit.ir.gates.qubitgate import QubitGate
 from bqskit.qis.pauli import PauliMatrices
 from bqskit.qis.unitary.differentiable import DifferentiableUnitary
-from bqskit.qis.unitary.optimizable import LocallyOptimizableUnitary
 from bqskit.qis.unitary.unitary import RealVector
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 from bqskit.utils.docs import building_docs
@@ -20,7 +20,7 @@
 from bqskit.utils.math import unitary_log_no_i
 
 
-class PauliGate(QubitGate, DifferentiableUnitary, LocallyOptimizableUnitary):
+class PauliGate(QubitGate, DifferentiableUnitary, GeneralGate):
     """
     A gate representing an arbitrary rotation.
 
@@ -93,16 +93,9 @@ def get_unitary_and_grad(
         U, dU = dexpmv(H, self.sigmav)
         return UnitaryMatrix(U, check_arguments=False), dU
 
-    def optimize(self, env_matrix: npt.NDArray[np.complex128]) -> list[float]:
-        """
-        Return the optimal parameters with respect to an environment matrix.
-
-        See :class:`LocallyOptimizableUnitary` for more info.
-        """
-        self.check_env_matrix(env_matrix)
-        U, _, Vh = sp.linalg.svd(env_matrix)
-        utry = Vh.conj().T @ U.conj().T
-        return list(-2 * pauli_expansion(unitary_log_no_i(utry)))
+    def calc_params(self, utry: UnitaryMatrix) -> list[float]:
+        """Return the parameters for this gate to implement `utry`"""
+        return list(-2 * pauli_expansion(unitary_log_no_i(utry.numpy)))
 
     def __eq__(self, o: object) -> bool:
         return isinstance(o, PauliGate) and self.num_qudits == o.num_qudits
diff --git a/bqskit/ir/gates/parameterized/u3.py b/bqskit/ir/gates/parameterized/u3.py
index 97d2fb964..c70ab5830 100644
--- a/bqskit/ir/gates/parameterized/u3.py
+++ b/bqskit/ir/gates/parameterized/u3.py
@@ -4,6 +4,7 @@
 import numpy as np
 import numpy.typing as npt
 
+from bqskit.ir.gates.generalgate import GeneralGate
 from bqskit.ir.gates.qubitgate import QubitGate
 from bqskit.qis.unitary.differentiable import DifferentiableUnitary
 from bqskit.qis.unitary.unitary import RealVector
@@ -11,7 +12,7 @@
 from bqskit.utils.cachedclass import CachedClass
 
 
-class U3Gate(QubitGate, DifferentiableUnitary, CachedClass):
+class U3Gate(QubitGate, DifferentiableUnitary, CachedClass, GeneralGate):
     """
     The U3 single qubit gate.
 
@@ -89,8 +90,7 @@ def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]:
             ], dtype=np.complex128,
         )
 
-    @staticmethod
-    def calc_params(utry: UnitaryMatrix) -> tuple[float, float, float]:
+    def calc_params(self, utry: UnitaryMatrix) -> list[float]:
         """
         Calculate the three parameters to a U3Gate from a given unitary.
 
@@ -99,13 +99,12 @@ def calc_params(utry: UnitaryMatrix) -> tuple[float, float, float]:
                 calculate a U3Gate's parameters for.
 
         Returns:
-            tuple[float, float, float]: The three parameters to a U3Gate,
+            list[float]: The three parameters to a U3Gate,
                 such that, `U3Gate().get_unitary` will return `utry`.
 
         Raises:
             ValueError: If `utry` is not a single-qubit unitary.
         """
-
         if utry.radixes != (2,):
             raise ValueError('Expected single-qubit unitary.')
 
@@ -118,4 +117,4 @@ def calc_params(utry: UnitaryMatrix) -> tuple[float, float, float]:
         theta = 2 * float(np.arctan2(c, d))
         phi = (a + b)
         lamb = (a - b)
-        return theta, phi, lamb
+        return [theta, phi, lamb]
diff --git a/bqskit/ir/gates/parameterized/u8.py b/bqskit/ir/gates/parameterized/u8.py
index af08b1a86..85808f713 100644
--- a/bqskit/ir/gates/parameterized/u8.py
+++ b/bqskit/ir/gates/parameterized/u8.py
@@ -4,6 +4,7 @@
 import numpy as np
 import numpy.typing as npt
 
+from bqskit.ir.gates.generalgate import GeneralGate
 from bqskit.ir.gates.qutritgate import QutritGate
 from bqskit.qis.unitary.differentiable import DifferentiableUnitary
 from bqskit.qis.unitary.unitary import RealVector
@@ -11,7 +12,7 @@
 from bqskit.utils.cachedclass import CachedClass
 
 
-class U8Gate(QutritGate, DifferentiableUnitary, CachedClass):
+class U8Gate(QutritGate, DifferentiableUnitary, CachedClass, GeneralGate):
     """The U8 single qutrit gate."""
 
     _num_qudits = 1
@@ -183,3 +184,38 @@ def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]:
                 ],
             ],
         )
+
+    def calc_params(self, utry: UnitaryMatrix) -> list[float]:
+        """Return the parameters for this gate to implement `utry`"""
+        if utry.radixes != (3,):
+            raise ValueError('Expected single-qutrit unitary.')
+
+        params = [0.0] * 8
+
+        mag = np.linalg.det(utry.numpy) ** (-1 / 3)
+        su = mag * utry
+
+        params[5] = np.arctan2(su[0][1].imag, su[0][1].real)
+
+        s1 = (su[0][1] * np.exp(-1j * params[5])).real
+        params[0] = np.arcsin(s1)
+
+        c2p1 = su[0][0] / np.cos(params[0])
+        params[3] = np.arctan2(c2p1.imag, c2p1.real)
+
+        c2 = (c2p1 * np.exp(-1j * params[3])).real
+        params[1] = np.arccos(c2)
+
+        c3p2 = su[1][1] / np.cos(params[0])
+        params[4] = np.arctan2(c3p2.imag, c3p2.real)
+
+        c3 = (c3p2 * np.exp(-1j * params[4])).real
+        params[2] = np.arccos(c3)
+
+        p4 = su[0][2] / (np.cos(params[0]) * np.sin(params[1]))
+        params[6] = np.arctan2(p4.imag, p4.real)
+
+        p5 = su[2][1] / (np.cos(params[0]) * np.sin(params[2]))
+        params[7] = np.arctan2(p5.imag, p5.real)
+
+        return params
diff --git a/bqskit/ir/gates/parameterized/unitary.py b/bqskit/ir/gates/parameterized/unitary.py
index e571df0e4..28ca883a0 100644
--- a/bqskit/ir/gates/parameterized/unitary.py
+++ b/bqskit/ir/gates/parameterized/unitary.py
@@ -4,21 +4,15 @@
 from typing import Sequence
 
 import numpy as np
-import numpy.typing as npt
-import scipy as sp
 
-from bqskit.ir.gate import Gate
-from bqskit.qis.unitary.optimizable import LocallyOptimizableUnitary
+from bqskit.ir.gates.generalgate import GeneralGate
 from bqskit.qis.unitary.unitary import RealVector
 from bqskit.qis.unitary.unitarymatrix import UnitaryLike
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 from bqskit.utils.typing import is_valid_radixes
 
 
-class VariableUnitaryGate(
-    Gate,
-    LocallyOptimizableUnitary,
-):
+class VariableUnitaryGate(GeneralGate):
     """A Variable n-qudit unitary operator."""
 
     def __init__(self, num_qudits: int, radixes: Sequence[int] = []) -> None:
@@ -65,15 +59,11 @@ def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
         x = real + imag
         return UnitaryMatrix.closest_to(np.reshape(x, self.shape), self.radixes)
 
-    def optimize(self, env_matrix: npt.NDArray[np.complex128]) -> list[float]:
-        """
-        Return the optimal parameters with respect to an environment matrix.
-
-        See :class:`LocallyOptimizableUnitary` for more info.
-        """
-        self.check_env_matrix(env_matrix)
-        U, _, Vh = sp.linalg.svd(env_matrix)
-        x = np.reshape(Vh.conj().T @ U.conj().T, (self.num_params // 2,))
+    def calc_params(self, utry: UnitaryLike) -> list[float]:
+        """Return the parameters for this gate to implement `utry`"""
+        if 2 * len(utry) ** 2 != self.num_params:
+            raise ValueError('Mismatch in unitary and gate dimension.')
+        x = np.reshape(utry, (self.num_params // 2,))
         return list(np.real(x)) + list(np.imag(x))
 
     @staticmethod
diff --git a/bqskit/ir/lang/qasm2/parser.py b/bqskit/ir/lang/qasm2/parser.py
index c5478682d..bb6610efc 100644
--- a/bqskit/ir/lang/qasm2/parser.py
+++ b/bqskit/ir/lang/qasm2/parser.py
@@ -6,7 +6,7 @@
 
 _OPENQASMPARSER = Lark(
     r"""
-ID: /[a-z][A-Za-z0-9_]*/
+ID: /[a-zA-Z][A-Za-z0-9_]*/
 REAL: /([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?/
 NNINTEGER: /[1-9]+[0-9]*|0/
 PI: "pi"
@@ -27,17 +27,19 @@
             | "opaque" ID "(" idlist ")" idlist ";"
             | qop
             | "if" "(" ID "==" NNINTEGER ")" qop
-            | "barrier" anylist ";"
+            | barrier
             | incstmt
 incstmt: "include" ESCAPED_STRING ";"
 decl: qreg | creg
 creg: "creg" ID "[" NNINTEGER "]" ";"
 qreg: "qreg" ID "[" NNINTEGER "]" ";"
+barrier: "barrier" anylist ";"
+barrierp: "barrier" anylist ";"
 gatedecl: "gate" ID idlist "{"
             | "gate" ID "(" ")" idlist "{"
             | "gate" ID "(" idlist ")" idlist "{"
 goplist: uopp
-            | "barrier" idlist ";"
+            | "barrierp" idlist ";"
             | goplist uopp
             | goplist "barrier" idlist ";"
 qop: uop
diff --git a/bqskit/ir/lang/qasm2/visitor.py b/bqskit/ir/lang/qasm2/visitor.py
index 598d879db..4f7c289ba 100644
--- a/bqskit/ir/lang/qasm2/visitor.py
+++ b/bqskit/ir/lang/qasm2/visitor.py
@@ -16,9 +16,11 @@
 
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
+from bqskit.ir.gates.barrier import BarrierPlaceholder
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.gates.composed.controlled import ControlledGate
 from bqskit.ir.gates.composed.daggergate import DaggerGate
+from bqskit.ir.gates.constant.b import BGate
 from bqskit.ir.gates.constant.ccx import CCXGate
 from bqskit.ir.gates.constant.ch import CHGate
 from bqskit.ir.gates.constant.cs import CSGate
@@ -30,6 +32,8 @@
 from bqskit.ir.gates.constant.identity import IdentityGate
 from bqskit.ir.gates.constant.iswap import ISwapGate
 from bqskit.ir.gates.constant.itoffoli import IToffoliGate
+from bqskit.ir.gates.constant.rccx import RC3XGate
+from bqskit.ir.gates.constant.rccx import RCCXGate
 from bqskit.ir.gates.constant.s import SGate
 from bqskit.ir.gates.constant.sdg import SdgGate
 from bqskit.ir.gates.constant.sqrtcnot import SqrtCNOTGate
@@ -46,10 +50,12 @@
 from bqskit.ir.gates.constant.z import ZGate
 from bqskit.ir.gates.constant.zz import ZZGate
 from bqskit.ir.gates.measure import MeasurementPlaceholder
+from bqskit.ir.gates.parameterized.ccp import CCPGate
 from bqskit.ir.gates.parameterized.cp import CPGate
 from bqskit.ir.gates.parameterized.crx import CRXGate
 from bqskit.ir.gates.parameterized.cry import CRYGate
 from bqskit.ir.gates.parameterized.crz import CRZGate
+from bqskit.ir.gates.parameterized.cu import CUGate
 from bqskit.ir.gates.parameterized.fsim import FSIMGate
 from bqskit.ir.gates.parameterized.phasedxz import PhasedXZGate
 from bqskit.ir.gates.parameterized.rx import RXGate
@@ -187,6 +193,7 @@ def fill_gate_defs(self) -> None:
         # Parameterized Gates
         self.gate_defs['p'] = GateDef('p', 1, 1, RZGate())
         self.gate_defs['cp'] = GateDef('cp', 1, 2, CPGate())
+        self.gate_defs['ccp'] = GateDef('ccp', 1, 3, CCPGate())
         self.gate_defs['crx'] = GateDef('crx', 1, 2, CRXGate())
         self.gate_defs['cry'] = GateDef('cry', 1, 2, CRYGate())
         self.gate_defs['crz'] = GateDef('crz', 1, 2, CRZGate())
@@ -199,8 +206,8 @@ def fill_gate_defs(self) -> None:
         self.gate_defs['rz'] = GateDef('rz', 1, 1, RZGate())
         self.gate_defs['rzz'] = GateDef('rzz', 1, 2, RZZGate())
         self.gate_defs['u1'] = GateDef('u1', 1, 1, U1Gate())
-        self.gate_defs['u1q'] = GateDef('u1q', 1, 2, U1qGate())
-        self.gate_defs['U1q'] = GateDef('u1q', 1, 2, U1qGate())
+        self.gate_defs['u1q'] = GateDef('u1q', 2, 1, U1qGate())
+        self.gate_defs['U1q'] = GateDef('u1q', 2, 1, U1qGate())
         self.gate_defs['u2'] = GateDef('u2', 2, 1, U2Gate())
         self.gate_defs['u3'] = GateDef('u3', 3, 1, U3Gate())
         self.gate_defs['U'] = GateDef('U', 3, 1, U3Gate())
@@ -208,8 +215,10 @@ def fill_gate_defs(self) -> None:
         self.gate_defs['cu1'] = GateDef('cu1', 1, 2, ControlledGate(U1Gate()))
         self.gate_defs['cu2'] = GateDef('cu2', 2, 2, ControlledGate(U2Gate()))
         self.gate_defs['cu3'] = GateDef('cu3', 3, 2, ControlledGate(U3Gate()))
+        self.gate_defs['cu'] = GateDef('cu', 4, 2, CUGate())
 
         # Constant Gates
+        self.gate_defs['b'] = GateDef('b', 0, 2, BGate())
         self.gate_defs['ccx'] = GateDef('ccx', 0, 3, CCXGate())
         self.gate_defs['ch'] = GateDef('ch', 0, 2, CHGate())
         self.gate_defs['cs'] = GateDef('cs', 0, 2, CSGate())
@@ -220,6 +229,7 @@ def fill_gate_defs(self) -> None:
         self.gate_defs['cz'] = GateDef('cz', 0, 2, CZGate())
         self.gate_defs['h'] = GateDef('h', 0, 1, HGate())
         self.gate_defs['id'] = GateDef('id', 0, 1, IdentityGate(1))
+        self.gate_defs['u0'] = GateDef('u0', 0, 1, IdentityGate(1))
         self.gate_defs['iswap'] = GateDef('iswap', 0, 2, ISwapGate())
         self.gate_defs['iccx'] = GateDef('iccx', 0, 3, IToffoliGate())
         self.gate_defs['s'] = GateDef('s', 0, 1, SGate())
@@ -228,6 +238,9 @@ def fill_gate_defs(self) -> None:
         self.gate_defs['cv'] = GateDef('cv', 0, 2, SqrtCNOTGate())
         self.gate_defs['sqisw'] = GateDef('sqisw', 0, 2, SqrtISwapGate())
         self.gate_defs['swap'] = GateDef('swap', 0, 2, SwapGate())
+        self.gate_defs['cswap'] = GateDef(
+            'cswap', 0, 3, ControlledGate(SwapGate()),
+        )
         self.gate_defs['sx'] = GateDef('sx', 0, 1, SXGate())
         self.gate_defs['v'] = GateDef('v', 0, 1, SXGate())
         self.gate_defs['syc'] = GateDef('syc', 0, 2, SycamoreGate())
@@ -240,6 +253,13 @@ def fill_gate_defs(self) -> None:
         self.gate_defs['z'] = GateDef('z', 0, 1, ZGate())
         self.gate_defs['zz'] = GateDef('zz', 0, 2, ZZGate())
         self.gate_defs['sxdg'] = GateDef('sxdg', 0, 1, DaggerGate(SXGate()))
+        self.gate_defs['c3x'] = GateDef('c3x', 0, 4, ControlledGate(XGate(), 3))
+        self.gate_defs['c4x'] = GateDef('c4x', 0, 5, ControlledGate(XGate(), 4))
+        self.gate_defs['c3sqrtx'] = GateDef(
+            'c3sqrtx', 0, 4, ControlledGate(SXGate(), 3),
+        )
+        self.gate_defs['rccx'] = GateDef('rccx', 0, 3, RCCXGate())
+        self.gate_defs['rc3x'] = GateDef('rc3x', 0, 4, RC3XGate())
 
     def qreg(self, tree: lark.Tree) -> None:
         """Qubit register node visitor."""
@@ -251,6 +271,19 @@ def qreg(self, tree: lark.Tree) -> None:
         _logger.debug('Qubit register %s declared with size %d.' % reg)
         self.qubit_regs.append(reg)
 
+    def barrier(self, tree: lark.Tree) -> None:
+        """Apply a barrier to the circuit."""
+        qlist = tree.children[-1]
+        location = CircuitLocation(self.convert_qubit_ids_to_indices(qlist))
+        gate = BarrierPlaceholder(len(location))
+        op = Operation(gate, location)
+        self.op_list.append(op)
+
+    def barrierp(self, tree: lark.Tree) -> None:
+        raise LangException(
+            'BQSKit currently does not support barriers in gate declarations.',
+        )
+
     def gate(self, tree: lark.Tree) -> None:
         """Apply a normal gate statement to the circuit."""
         # Parse parameters
@@ -261,15 +294,8 @@ def gate(self, tree: lark.Tree) -> None:
             params = []
 
         # Parse location
-        location = []
-        for reg_name, reg_idx in qubits_from_list(tree.children[-1]):
-            outer_idx = 0
-            for reg in self.qubit_regs:
-                if reg.name == reg_name:
-                    location.append(outer_idx + reg_idx)
-                    break
-                outer_idx += reg.size
-        location = CircuitLocation(location)
+        qlist = tree.children[-1]
+        location = CircuitLocation(self.convert_qubit_ids_to_indices(qlist))
 
         if any(q in self.measurements for q in location):
             raise LangException(
@@ -628,6 +654,68 @@ def reset(self, tree: lark.Tree) -> None:
         """Reset statement node visitor."""
         raise LangException('BQSKit currently does not support resets.')
 
+    def convert_qubit_ids_to_indices(self, qlist: lark.Tree) -> list[int]:
+        if qlist.data == 'anylist':
+            # Anylist is either idlist or mixedlist
+            return self.convert_qubit_ids_to_indices(qlist.children[0])
+
+        if qlist.data == 'idlist':
+            # List of ids, e.g. q, r, but without indices
+            ids = []
+            tree_iter = qlist
+            while len(tree_iter.children) == 2:
+                ids.append(str(tree_iter.children[0]))
+                tree_iter = tree_iter.children[1]
+            ids.append(str(tree_iter.children[0]))
+
+            out_idxs = []
+            for qubit_id in ids:
+                out_idxs.extend(self.convert_qubit_id_to_indices(qubit_id))
+
+            return out_idxs
+
+        if qlist.data == 'argument':
+            # ID | ID "[" NNINTEGER "]"
+            qubit_id = qlist.children[0]
+            if len(qlist.children) == 2:
+                idx = self.convert_qubit_id_to_first_index(qubit_id)
+                return [idx + int(qlist.children[1])]
+
+            return self.convert_qubit_id_to_indices(qubit_id)
+
+        if qlist.data == 'mixedlist':
+            if isinstance(qlist.children[0], lark.Tree):
+                idxs = self.convert_qubit_ids_to_indices(qlist.children[0])
+
+                if len(qlist.children) == 2:
+                    qubit_id = qlist.children[1]
+                    return idxs + self.convert_qubit_id_to_indices(qubit_id)
+
+                idx = self.convert_qubit_id_to_first_index(qlist.children[1])
+                return idxs + [idx + int(qlist.children[2])]
+
+            else:
+                idx = self.convert_qubit_id_to_first_index(qlist.children[0])
+                return [idx + int(qlist.children[1])]
+
+        raise LangException('Incorrect qlist type.')
+
+    def convert_qubit_id_to_first_index(self, qubit_id: str) -> int:
+        outer_idx = 0
+        for reg in self.qubit_regs:
+            if reg.name == qubit_id:
+                return outer_idx
+            outer_idx += reg.size
+        raise LangException(f'Unable to find qubit register id: {qubit_id}.')
+
+    def convert_qubit_id_to_indices(self, qubit_id: str) -> list[int]:
+        outer_idx = 0
+        for reg in self.qubit_regs:
+            if reg.name == qubit_id:
+                return [i + outer_idx for i in range(reg.size)]
+            outer_idx += reg.size
+        raise LangException(f'Unable to find qubit register id: {qubit_id}.')
+
 
 eval_locals = {
     'pi': np.pi,
diff --git a/bqskit/ir/operation.py b/bqskit/ir/operation.py
index ecbedbb19..e53cb1728 100644
--- a/bqskit/ir/operation.py
+++ b/bqskit/ir/operation.py
@@ -7,7 +7,7 @@
 import numpy.typing as npt
 
 from bqskit.ir.gate import Gate
-from bqskit.ir.gates import FrozenParameterGate
+from bqskit.ir.gates.composed.frozenparam import FrozenParameterGate
 from bqskit.ir.location import CircuitLocation
 from bqskit.ir.location import CircuitLocationLike
 from bqskit.qis.unitary.differentiable import DifferentiableUnitary
diff --git a/bqskit/ir/opt/cost/functions/cost/hilbertschmidt.py b/bqskit/ir/opt/cost/functions/cost/hilbertschmidt.py
index 783a72677..d84c8617c 100644
--- a/bqskit/ir/opt/cost/functions/cost/hilbertschmidt.py
+++ b/bqskit/ir/opt/cost/functions/cost/hilbertschmidt.py
@@ -9,6 +9,7 @@
 from bqskit.ir.opt.cost.differentiable import DifferentiableCostFunction
 from bqskit.ir.opt.cost.generator import CostFunctionGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 
 if TYPE_CHECKING:
     from bqskit.ir.circuit import Circuit
@@ -40,7 +41,7 @@ class HilbertSchmidtCostGenerator(CostFunctionGenerator):
     def gen_cost(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> CostFunction:
         """Generate a CostFunction, see CostFunctionGenerator for more info."""
         return HilbertSchmidtCost(circuit, target)
diff --git a/bqskit/ir/opt/cost/functions/residuals/hilbertschmidt.py b/bqskit/ir/opt/cost/functions/residuals/hilbertschmidt.py
index d6799438b..ff5e6e09c 100644
--- a/bqskit/ir/opt/cost/functions/residuals/hilbertschmidt.py
+++ b/bqskit/ir/opt/cost/functions/residuals/hilbertschmidt.py
@@ -8,6 +8,7 @@
 from bqskit.ir.opt.cost.differentiable import DifferentiableResidualsFunction
 from bqskit.ir.opt.cost.generator import CostFunctionGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 
 if TYPE_CHECKING:
     from bqskit.ir.circuit import Circuit
@@ -39,7 +40,7 @@ class HilbertSchmidtResidualsGenerator(CostFunctionGenerator):
     def gen_cost(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> CostFunction:
         """Generate a CostFunction, see CostFunctionGenerator for more info."""
         return HilbertSchmidtResiduals(circuit, target)
diff --git a/bqskit/ir/opt/cost/generator.py b/bqskit/ir/opt/cost/generator.py
index 969badd12..85432f824 100644
--- a/bqskit/ir/opt/cost/generator.py
+++ b/bqskit/ir/opt/cost/generator.py
@@ -4,6 +4,8 @@
 import abc
 from typing import TYPE_CHECKING
 
+from bqskit.qis.state.system import StateSystem
+
 if TYPE_CHECKING:
     from bqskit.ir.opt.cost import CostFunction
     from bqskit.ir.circuit import Circuit
@@ -30,7 +32,7 @@ class CostFunctionGenerator(abc.ABC):
     def gen_cost(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> CostFunction:
         """
         Generate a function from a circuit and target that maps params to cost.
@@ -38,7 +40,8 @@ def gen_cost(
         Args:
             circuit (Circuit): The circuit the cost function is generated for.
 
-            target (UnitaryMatrix | StateVector): The target object.
+            target (UnitaryMatrix | StateVector | StateSystem): The target
+                object.
 
         Returns:
             (CostFunction): The primitive cost function
@@ -49,7 +52,7 @@ def gen_cost(
     def calc_cost(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> float:
         """Generate and calculate the cost from the CostFunction."""
         return self.gen_cost(circuit, target).get_cost(circuit.params)
@@ -57,7 +60,7 @@ def calc_cost(
     def __call__(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> float:
         """Generate and calculate the cost from the CostFunction."""
         return self.calc_cost(circuit, target)
diff --git a/bqskit/ir/opt/instantiater.py b/bqskit/ir/opt/instantiater.py
index 1ee6a3a39..0661f80e7 100644
--- a/bqskit/ir/opt/instantiater.py
+++ b/bqskit/ir/opt/instantiater.py
@@ -7,12 +7,16 @@
 import numpy as np
 import numpy.typing as npt
 
+from bqskit.ir.opt.cost.functions import HilbertSchmidtCostGenerator
+from bqskit.ir.opt.multistartgens.random import RandomStartGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 if TYPE_CHECKING:
     from bqskit.ir.circuit import Circuit
     from bqskit.qis.state.state import StateLike
+    from bqskit.qis.state.system import StateSystemLike
     from bqskit.qis.unitary.unitarymatrix import UnitaryLike
 
 
@@ -28,7 +32,7 @@ class Instantiater(abc.ABC):
     def instantiate(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
         x0: npt.NDArray[np.float64],
     ) -> npt.NDArray[np.float64]:
         """
@@ -37,8 +41,8 @@ def instantiate(
         Args:
             circuit (Circuit): The circuit template to instantiate.
 
-            target (UnitaryMatrix | StateVector): The unitary matrix to
-                implement or state to prepare.
+            target (UnitaryMatrix | StateVector | StateSystem): The unitary
+                matrix to implement or state to prepare.
 
             x0 (np.ndarray): Initial point to use during instantiation.
 
@@ -52,6 +56,91 @@ def instantiate(
             Instantiater object may happen in parallel.
         """
 
+    def multi_start_instantiate(
+        self,
+        circuit: Circuit,
+        target: UnitaryLike | StateLike | StateSystemLike,
+        num_starts: int,
+    ) -> Circuit:
+        """
+        Instantiate `circuit` to best implement `target` with multiple starts.
+
+        Args:
+            circuit (Circuit): The circuit template to instantiate.
+
+            target (UnitaryMatrix | StateVector | StateSystem): The unitary
+                matrix to implement or state to prepare.
+
+            num_starts (int): The number of starting points to attempt
+                instantiation with.
+
+        Returns:
+            (Circuit): A circuit copy with the best parameters with
+                respect to `target`.
+
+        Notes:
+            This method should be side-effect free. This is necessary since
+            many instantiate calls to the same circuit using the same
+            Instantiater object may happen in parallel.
+        """
+        _circuit = circuit.copy()
+        self.multi_start_instantiate_inplace(_circuit, target, num_starts)
+        return _circuit
+
+    def multi_start_instantiate_inplace(
+        self,
+        circuit: Circuit,
+        target: UnitaryLike | StateLike | StateSystemLike,
+        num_starts: int,
+    ) -> None:
+        """
+        Instantiate `circuit` to best implement `target` with multiple starts.
+
+        See :func:`multi_start_instantiate` for more info.
+
+        Notes:
+            This method is a version of :func:`multi_start_instantiate`
+            that modifies `circuit` in place rather than returning a copy.
+        """
+        target = self.check_target(target)
+        start_gen = RandomStartGenerator()
+        starts = start_gen.gen_starting_points(num_starts, circuit, target)
+        cost_fn = HilbertSchmidtCostGenerator().gen_cost(circuit, target)
+        params_list = [self.instantiate(circuit, target, x0) for x0 in starts]
+        params = sorted(params_list, key=lambda x: cost_fn(x))[0]
+        circuit.set_params(params)
+
+    async def multi_start_instantiate_async(
+        self,
+        circuit: Circuit,
+        target: UnitaryLike | StateLike | StateSystemLike,
+        num_starts: int,
+    ) -> Circuit:
+        """
+        Instantiate `circuit` to best implement `target` with multiple starts.
+
+        See :func:`multi_start_instantiate` for more info.
+
+        Notes:
+            This method is an async version of :func:`multi_start_instantiate`
+            and designed to parallelize the instantiation calls in the
+            BQSKit Runtime during pass execution.
+        """
+        from bqskit.runtime import get_runtime
+        target = self.check_target(target)
+        start_gen = RandomStartGenerator()
+        starts = start_gen.gen_starting_points(num_starts, circuit, target)
+        cost_fn = HilbertSchmidtCostGenerator().gen_cost(circuit, target)
+        params_list = await get_runtime().map(
+            self.instantiate,
+            [circuit] * num_starts,
+            [target] * num_starts,
+            starts,
+        )
+        params = sorted(params_list, key=lambda x: cost_fn(x))[0]
+        circuit.set_params(params)
+        return circuit
+
     @staticmethod
     @abc.abstractmethod
     def is_capable(circuit: Circuit) -> bool:
@@ -73,21 +162,33 @@ def get_violation_report(circuit: Circuit) -> str:
 
     def check_target(
         self,
-        target: UnitaryLike | StateLike,
-    ) -> UnitaryMatrix | StateVector:
+        target: UnitaryLike | StateLike | StateSystemLike,
+    ) -> UnitaryMatrix | StateVector | StateSystem:
         """Check `target` to be valid and return it casted."""
+        # Check `target`
         try:
-            typed_target = StateVector(target)  # type: ignore
-        except (ValueError, TypeError):
-            try:
-                typed_target = UnitaryMatrix(target)  # type: ignore
-            except (ValueError, TypeError) as ex:
-                raise TypeError(
-                    'Expected either StateVector, UnitaryMatrix, or'
-                    ' CostFunction for target, got %s.' % type(target),
-                ) from ex
+            if UnitaryMatrix.is_unitary(target):
+                target = UnitaryMatrix(target)
+
+            elif StateVector.is_pure_state(target):
+                target = StateVector(target)
 
-        return typed_target
+            elif StateSystem.is_state_system(target):
+                target = StateSystem(target)
+
+            else:
+                raise TypeError(
+                    'Target is neither a unitary, a state system'
+                    f', nor a state. Got {type(input)}.',
+                )
+        except Exception as e:
+            raise TypeError(
+                'Unable to determine type of input.'
+                ' Ensure that you are trying to compile a valid'
+                ' circuit, unitary, or state.',
+            ) from e
+
+        return target
 
     @staticmethod
     @abc.abstractmethod
diff --git a/bqskit/ir/opt/instantiaters/__init__.py b/bqskit/ir/opt/instantiaters/__init__.py
index 3577fbbd8..c5b516c92 100644
--- a/bqskit/ir/opt/instantiaters/__init__.py
+++ b/bqskit/ir/opt/instantiaters/__init__.py
@@ -6,7 +6,7 @@
 from bqskit.ir.opt.instantiaters.qfactor import QFactor
 
 
-instantiater_order = [QFactor, Minimization]
+instantiater_order = [Minimization, QFactor]
 
 
 __all__ = [
diff --git a/bqskit/ir/opt/instantiaters/minimization.py b/bqskit/ir/opt/instantiaters/minimization.py
index bab584f34..fe4cb089b 100644
--- a/bqskit/ir/opt/instantiaters/minimization.py
+++ b/bqskit/ir/opt/instantiaters/minimization.py
@@ -7,12 +7,14 @@
 import numpy as np
 import numpy.typing as npt
 
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
 from bqskit.ir.opt.cost.functions import HilbertSchmidtResidualsGenerator
 from bqskit.ir.opt.cost.generator import CostFunctionGenerator
 from bqskit.ir.opt.instantiater import Instantiater
 from bqskit.ir.opt.minimizer import Minimizer
 from bqskit.ir.opt.minimizers.ceres import CeresMinimizer
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 if TYPE_CHECKING:
@@ -25,7 +27,7 @@ class Minimization(Instantiater):
     def __init__(
         self,
         cost_fn_gen: CostFunctionGenerator = HilbertSchmidtResidualsGenerator(),
-        minimizer: Minimizer | None = None,
+        minimizer: Minimizer = CeresMinimizer(),
         **kwargs: dict[str, Any],  # TODO: handle dist_tol and other options
     ) -> None:
         """
@@ -36,7 +38,7 @@ def __init__(
                 functions that are minimized.
                 (Default: HilbertSchmidtGenerator())
 
-            minimizer (Minimizer | None): The minimizer to use. If left as
+            minimizer (Minimizer): The minimizer to use. If left as
                 None, attempts to select best one.
         """
 
@@ -45,30 +47,30 @@ def __init__(
                 'Expected CostFunctionGenerator, got %s.' % type(cost_fn_gen),
             )
 
-        if minimizer is not None and not isinstance(minimizer, Minimizer):
+        if not isinstance(minimizer, Minimizer):
             raise TypeError('Expected Minimizer, got %s.' % type(minimizer))
 
-        # Default to the fast CeresMinimizer
-        if minimizer is None:
-            minimizer = CeresMinimizer()
-
         self.cost_fn_gen = cost_fn_gen
         self.minimizer = minimizer
 
     def instantiate(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
         x0: npt.NDArray[np.float64],
     ) -> npt.NDArray[np.float64]:
         """Instantiate `circuit`, see Instantiater for more info."""
         cost = self.cost_fn_gen.gen_cost(circuit, target)
+        # print(x0, circuit.num_params, circuit.gate_counts)
         return self.minimizer.minimize(cost, x0)
 
     @staticmethod
     def is_capable(circuit: Circuit) -> bool:
         """Return true if the circuit can be instantiated."""
-        return True
+        return all(
+            not isinstance(g, VariableUnitaryGate)
+            for g in circuit.gate_set
+        )
 
     @staticmethod
     def get_violation_report(circuit: Circuit) -> str:
@@ -77,7 +79,10 @@ def get_violation_report(circuit: Circuit) -> str:
 
         See Instantiater for more info.
         """
-        raise ValueError('Circuit can be instantiated.')
+        return (
+            'Cannot instantiate a circuit with VariableUnitaryGates'
+            ' via minimization.'
+        )
 
     @staticmethod
     def get_method_name() -> str:
diff --git a/bqskit/ir/opt/instantiaters/qfactor.py b/bqskit/ir/opt/instantiaters/qfactor.py
index f769ea6e0..0b9a86a84 100644
--- a/bqskit/ir/opt/instantiaters/qfactor.py
+++ b/bqskit/ir/opt/instantiaters/qfactor.py
@@ -12,6 +12,7 @@
 
 from bqskit.ir.opt.instantiater import Instantiater
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary import LocallyOptimizableUnitary
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
@@ -38,7 +39,7 @@ def __new__(cls, **kwargs: dict[str, Any]) -> Any:
     def instantiate(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
         x0: npt.NDArray[np.float64],
     ) -> npt.NDArray[np.float64]:
         """Instantiate `circuit`, see Instantiater for more info."""
diff --git a/bqskit/ir/opt/minimizers/scipy.py b/bqskit/ir/opt/minimizers/scipy.py
index f6f125901..5587db9d8 100644
--- a/bqskit/ir/opt/minimizers/scipy.py
+++ b/bqskit/ir/opt/minimizers/scipy.py
@@ -10,7 +10,7 @@
 
 if TYPE_CHECKING:
     from bqskit.ir.opt.cost import CostFunction
-    from bqskit.qis import RealVector
+    from bqskit.qis.unitary.unitary import RealVector
     import numpy.typing as npt
 
 
diff --git a/bqskit/ir/opt/multistartgen.py b/bqskit/ir/opt/multistartgen.py
index 9847318dd..289b54bf3 100644
--- a/bqskit/ir/opt/multistartgen.py
+++ b/bqskit/ir/opt/multistartgen.py
@@ -4,11 +4,13 @@
 import abc
 from typing import TYPE_CHECKING
 
+
 if TYPE_CHECKING:
     import numpy as np
     import numpy.typing as npt
     from bqskit.ir.circuit import Circuit
     from bqskit.qis.state.state import StateVector
+    from bqskit.qis.state.system import StateSystem
     from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
@@ -18,7 +20,7 @@ def gen_starting_points(
         self,
         multistarts: int,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> list[npt.NDArray[np.float64]]:
         """
         Generate `multistarts` starting points for instantiation.
@@ -28,7 +30,7 @@ def gen_starting_points(
 
             circuit (Circuit): The circuit to generate the points for.
 
-            target (UnitaryMatrix | StateVector): The target.
+            target (UnitaryMatrix | StateVector | StateSystem): The target.
 
         Return:
             (list[npt.NDArray[np.float64]]): List of starting inputs for
diff --git a/bqskit/ir/opt/multistartgens/diagonal.py b/bqskit/ir/opt/multistartgens/diagonal.py
index 620797e6f..04544434c 100644
--- a/bqskit/ir/opt/multistartgens/diagonal.py
+++ b/bqskit/ir/opt/multistartgens/diagonal.py
@@ -6,6 +6,7 @@
 import numpy as np
 
 from bqskit.ir.opt.multistartgen import MultiStartGenerator
+from bqskit.qis.state.system import StateSystem
 from bqskit.utils.typing import is_integer
 
 if TYPE_CHECKING:
@@ -22,7 +23,7 @@ def gen_starting_points(
         self,
         multistarts: int,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> list[npt.NDArray[np.float64]]:
         """
         Generate `multistarts` starting points for instantiation.
@@ -32,7 +33,7 @@ def gen_starting_points(
 
             circuit (Circuit): The circuit to generate the points for.
 
-            target (UnitaryMatrix | StateVector): The target.
+            target (UnitaryMatrix | StateVector | StateSystem): The target.
 
         Return:
             (list[npt.NDArray[np.float64]]): List of starting inputs for
diff --git a/bqskit/ir/opt/multistartgens/random.py b/bqskit/ir/opt/multistartgens/random.py
index 54067adc4..968d7bb58 100644
--- a/bqskit/ir/opt/multistartgens/random.py
+++ b/bqskit/ir/opt/multistartgens/random.py
@@ -12,6 +12,7 @@
     import numpy.typing as npt
     from bqskit.ir.circuit import Circuit
     from bqskit.qis.state.state import StateVector
+    from bqskit.qis.state.system import StateSystem
     from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
@@ -22,7 +23,7 @@ def gen_starting_points(
         self,
         multistarts: int,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> list[npt.NDArray[np.float64]]:
         """
         Generate `multistarts` starting points for instantiation.
diff --git a/bqskit/passes/__init__.py b/bqskit/passes/__init__.py
index 253a96f7e..208a098ce 100644
--- a/bqskit/passes/__init__.py
+++ b/bqskit/passes/__init__.py
@@ -45,7 +45,6 @@
     Rebase2QuditGatePass
     ScanningGateRemovalPass
     SubstitutePass
-    WindowOptimizationPass
 
 .. rubric:: Control Passes
 
@@ -111,6 +110,10 @@
     GeneralizedSabreRoutingPass
     SetModelPass
     ApplyPlacement
+    SubtopologySelectionPass
+    PAMLayoutPass
+    PAMRoutingPass
+    EmbedAllPermutationsPass
 
 .. rubric:: Utility Passes
 
@@ -124,6 +127,7 @@
     UnfoldPass
     UpdateDataPass
     ToU3Pass
+    ToVariablePass
     BlockConversionPass
     LogPass
     ExtendBlockSizePass
@@ -192,11 +196,15 @@
 from bqskit.passes.io.intermediate import RestoreIntermediatePass
 from bqskit.passes.io.intermediate import SaveIntermediatePass
 from bqskit.passes.mapping.apply import ApplyPlacement
+from bqskit.passes.mapping.embed import EmbedAllPermutationsPass
+from bqskit.passes.mapping.layout.pam import PAMLayoutPass
 from bqskit.passes.mapping.layout.sabre import GeneralizedSabreLayoutPass
 from bqskit.passes.mapping.placement.greedy import GreedyPlacementPass
 from bqskit.passes.mapping.placement.trivial import TrivialPlacementPass
+from bqskit.passes.mapping.routing.pam import PAMRoutingPass
 from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass
 from bqskit.passes.mapping.setmodel import SetModelPass
+from bqskit.passes.mapping.topology import SubtopologySelectionPass
 from bqskit.passes.measure import ExtractMeasurements
 from bqskit.passes.measure import RestoreMeasurements
 from bqskit.passes.noop import NOOPPass
@@ -210,7 +218,6 @@
 from bqskit.passes.processing.rebase import Rebase2QuditGatePass
 from bqskit.passes.processing.scan import ScanningGateRemovalPass
 from bqskit.passes.processing.substitute import SubstitutePass
-from bqskit.passes.processing.window import WindowOptimizationPass
 from bqskit.passes.rules.ch2cnot import CHToCNOTPass
 from bqskit.passes.rules.cnot2ch import CNOTToCHPass
 from bqskit.passes.rules.cnot2cy import CNOTToCYPass
@@ -233,13 +240,16 @@
 from bqskit.passes.search.heuristics.dijkstra import DijkstraHeuristic
 from bqskit.passes.search.heuristics.greedy import GreedyHeuristic
 from bqskit.passes.synthesis.leap import LEAPSynthesisPass
+from bqskit.passes.synthesis.pas import PermutationAwareSynthesisPass
 from bqskit.passes.synthesis.qfast import QFASTDecompositionPass
 from bqskit.passes.synthesis.qpredict import QPredictDecompositionPass
 from bqskit.passes.synthesis.qsearch import QSearchSynthesisPass
 from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.passes.synthesis.target import SetTargetPass
 from bqskit.passes.util.compress import CompressPass
 from bqskit.passes.util.conversion import BlockConversionPass
 from bqskit.passes.util.converttou3 import ToU3Pass
+from bqskit.passes.util.converttovar import ToVariablePass
 from bqskit.passes.util.extend import ExtendBlockSizePass
 from bqskit.passes.util.fill import FillSingleQuditGatesPass
 from bqskit.passes.util.log import LogErrorPass
@@ -275,7 +285,6 @@
     'UpdateDataPass',
     'ToU3Pass',
     'ScanningGateRemovalPass',
-    'WindowOptimizationPass',
     'SimpleLayerGenerator',
     'AStarHeuristic',
     'GreedyHeuristic',
@@ -329,4 +338,11 @@
     'CNOTToCHPass',
     'CNOTToCYPass',
     'CYToCNOTPass',
+    'SetTargetPass',
+    'PAMLayoutPass',
+    'PAMRoutingPass',
+    'EmbedAllPermutationsPass',
+    'SubtopologySelectionPass',
+    'PermutationAwareSynthesisPass',
+    'ToVariablePass',
 ]
diff --git a/bqskit/passes/alias.py b/bqskit/passes/alias.py
index 5114a0ba4..e5a5faa46 100644
--- a/bqskit/passes/alias.py
+++ b/bqskit/passes/alias.py
@@ -2,10 +2,13 @@
 from __future__ import annotations
 
 import abc
-from typing import Any
+from typing import TYPE_CHECKING
 
 from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
+
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
 
 
 class PassAlias(BasePass):
@@ -15,7 +18,7 @@ class PassAlias(BasePass):
     def get_passes(self) -> list[BasePass]:
         """Return the passes that should be run, when this alias is called."""
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         for bqskit_pass in self.get_passes():
-            bqskit_pass.run(circuit, data)
+            await bqskit_pass.run(circuit, data)
diff --git a/bqskit/passes/control/dothendecide.py b/bqskit/passes/control/dothendecide.py
index b29a3dbbb..274034386 100644
--- a/bqskit/passes/control/dothendecide.py
+++ b/bqskit/passes/control/dothendecide.py
@@ -2,13 +2,18 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
-from typing import Callable
-from typing import Sequence
+from typing import TYPE_CHECKING
 
 from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
-from bqskit.utils.typing import is_sequence
+from bqskit.compiler.workflow import Workflow
+
+if TYPE_CHECKING:
+    from typing import Callable
+
+    from bqskit.compiler.passdata import PassData
+    from bqskit.compiler.workflow import WorkflowLike
+    from bqskit.ir.circuit import Circuit
+
 
 _logger = logging.getLogger(__name__)
 
@@ -17,14 +22,14 @@ class DoThenDecide(BasePass):
     """
     The DoThenDecide class.
 
-    This is a control pass that executes a pass or sequence of passes and then
-    conditionally accepts it or reverts to the original state.
+    This is a control pass that executes a workflow and then conditionally
+    accepts the resulting circuit or reverts to the original state.
     """
 
     def __init__(
         self,
         condition: Callable[[Circuit, Circuit], bool],
-        pass_list: BasePass | Sequence[BasePass],
+        workflow: WorkflowLike,
     ) -> None:
         """
         Construct a DoThenDecide.
@@ -32,47 +37,37 @@ def __init__(
         Args:
             condition (Callable[[Circuit, Circuit], bool]): The condition
                 function that determines if the new circuit
-                (second parameter) after running `pass_list` should replace
+                (second parameter) after running `workflow` should replace
                 the original circuit (first parameter). If the condition
-                returns True, then replaced the original circuit with the
-                new one.
+                returns True, then replace the original circuit with the
+                new one. Note when passing callables to BQSKit passes,
+                they need to be defined at the module level (0-indent)
+                with a name (no lambdas).
 
-            pass_list (BasePass | Sequence[BasePass]): The pass or passes
-                to execute.
-
-        Raises:
-            ValueError: If a Sequence[BasePass] is given, but it is empty.
+            workflow (WorkflowLike): The pass or passes to execute.
         """
-        if not is_sequence(pass_list) and not isinstance(pass_list, BasePass):
-            raise TypeError(
-                'Expected Pass or sequence of Passes, got %s.'
-                % type(pass_list),
-            )
-
-        if is_sequence(pass_list):
-            truth_list = [isinstance(elem, BasePass) for elem in pass_list]
-            if not all(truth_list):
-                raise TypeError(
-                    'Expected Pass or sequence of Passes, got %s.'
-                    % type(pass_list[truth_list.index(False)]),
-                )
-            if len(pass_list) == 0:
-                raise ValueError('Expected at least one pass.')
+        if not callable(condition):
+            bad_type = type(condition)
+            msg = f'Expected callable function for condition, got {bad_type}'
+            raise TypeError(msg)
 
         self.condition = condition
-        self.pass_list = pass_list if is_sequence(pass_list) else [pass_list]
+        self.workflow = Workflow(workflow)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
-        # Perform Work
+        # Store old state
         old_circuit = circuit.copy()
-        _logger.debug('Pass list executing...')
-        for bqskit_pass in self.pass_list:
-            bqskit_pass.run(circuit, data)
+        old_data = data.copy()
+
+        # Execute the workflow
+        await self.workflow.run(circuit, data)
 
+        # Evaluate condition
         if self.condition(old_circuit, circuit):
-            _logger.debug('Accepted circuit.')
+            _logger.info('Accepted circuit.')
 
         else:
             circuit.become(old_circuit)
-            _logger.debug('Rejected circuit.')
+            data.become(old_data)
+            _logger.info('Rejected circuit.')
diff --git a/bqskit/passes/control/dowhileloop.py b/bqskit/passes/control/dowhileloop.py
index a855618f1..6c0464170 100644
--- a/bqskit/passes/control/dowhileloop.py
+++ b/bqskit/passes/control/dowhileloop.py
@@ -2,13 +2,17 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
-from typing import Sequence
+from typing import TYPE_CHECKING
 
 from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
+from bqskit.compiler.workflow import Workflow
 from bqskit.passes.control.predicate import PassPredicate
-from bqskit.utils.typing import is_sequence
+
+if TYPE_CHECKING:
+    from bqskit.ir.circuit import Circuit
+    from bqskit.compiler.passdata import PassData
+    from bqskit.compiler.workflow import WorkflowLike
+
 
 _logger = logging.getLogger(__name__)
 
@@ -17,14 +21,14 @@ class DoWhileLoopPass(BasePass):
     """
     The DoWhileLoopPass class.
 
-    This is a control pass that executes a pass and then conditionally executes
-    it again in a loop.
+    This is a control pass that executes a workflow and then conditionally
+    executes it again in a loop.
     """
 
     def __init__(
         self,
         condition: PassPredicate,
-        loop_body: BasePass | Sequence[BasePass],
+        loop_body: WorkflowLike,
     ) -> None:
         """
         Construct a DoWhileLoopPass.
@@ -32,43 +36,22 @@ def __init__(
         Args:
             condition (PassPredicate): The condition checked.
 
-            loop_body (BasePass | Sequence[BasePass]): The pass or passes
-                to execute while `condition` is true.
-
-        Raises:
-            ValueError: If a Sequence[BasePass] is given, but it is empty.
+            loop_body (WorkflowLike): The pass or passes to execute while
+                `condition` is true.
         """
-
         if not isinstance(condition, PassPredicate):
-            raise TypeError('Expected PassPredicate, got %s.' % type(condition))
-
-        if not is_sequence(loop_body) and not isinstance(loop_body, BasePass):
-            raise TypeError(
-                'Expected Pass or sequence of Passes, got %s.'
-                % type(loop_body),
-            )
-
-        if is_sequence(loop_body):
-            truth_list = [isinstance(elem, BasePass) for elem in loop_body]
-            if not all(truth_list):
-                raise TypeError(
-                    'Expected Pass or sequence of Passes, got %s.'
-                    % type(loop_body[truth_list.index(False)]),
-                )
-            if len(loop_body) == 0:
-                raise ValueError('Expected at least one pass.')
+            raise TypeError(f'Expected PassPredicate, got {type(condition)}.')
 
         self.condition = condition
-        self.loop_body = loop_body if is_sequence(loop_body) else [loop_body]
+        self.workflow = Workflow(loop_body)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
-        # Perform Work
+        # Do
         _logger.debug('Loop body executing...')
-        for loop_pass in self.loop_body:
-            loop_pass.run(circuit, data)
+        await self.workflow.run(circuit, data)
 
+        # While
         while self.condition(circuit, data):
             _logger.debug('Loop body executing...')
-            for loop_pass in self.loop_body:
-                loop_pass.run(circuit, data)
+            await self.workflow.run(circuit, data)
diff --git a/bqskit/passes/control/foreach.py b/bqskit/passes/control/foreach.py
index 9df8f20e1..f6f7f5b97 100644
--- a/bqskit/passes/control/foreach.py
+++ b/bqskit/passes/control/foreach.py
@@ -2,12 +2,14 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 from typing import Callable
-from typing import Sequence
 
+from bqskit.compiler.basepass import _sub_do_work
 from bqskit.compiler.basepass import BasePass
 from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
+from bqskit.compiler.workflow import Workflow
+from bqskit.compiler.workflow import WorkflowLike
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.gates.constant.unitary import ConstantUnitaryGate
@@ -15,8 +17,7 @@
 from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
 from bqskit.ir.operation import Operation
 from bqskit.ir.point import CircuitPoint
-from bqskit.utils.typing import is_integer
-from bqskit.utils.typing import is_sequence
+from bqskit.runtime import get_runtime
 
 _logger = logging.getLogger(__name__)
 
@@ -25,27 +26,29 @@ class ForEachBlockPass(BasePass):
     """
     A pass that executes other passes on each block in the circuit.
 
-    This is a control pass that executes another pass or passes on every block
-    in the circuit. This will be done in parallel if run from a compiler.
+    This is a control pass that executes a workflow on every block in the
+    circuit. This will be done in parallel.
     """
 
     key = 'ForEachBlockPass_data'
     """The key in data, where block data will be put."""
 
+    pass_down_key_prefix = 'ForEachBlockPass_pass_down_'
+    """If a key exists in the pass data with this prefix, pass it to blocks."""
+
     def __init__(
         self,
-        loop_body: BasePass | Sequence[BasePass],
+        loop_body: WorkflowLike,
         calculate_error_bound: bool = False,
         collection_filter: Callable[[Operation], bool] | None = None,
         replace_filter: Callable[[Circuit, Operation], bool] | None = None,
-        batch_size: int = 1,
+        batch_size: int | None = None,
     ) -> None:
         """
         Construct a ForEachBlockPass.
 
         Args:
-            loop_body (BasePass | Sequence[BasePass]): The pass or passes
-                to execute on every block.
+            loop_body (WorkflowLike): The workflow to execute on every block.
 
             calculate_error_bound (bool): If set to true, will calculate
                 errors on blocks after running `loop_body` on them and
@@ -68,61 +71,34 @@ def __init__(
                 operation will be replaced with the new circuit.
                 Defaults to always replace.
 
-            batch_size (int): The amount of blocks to batch in one job.
-                If zero, batch all blocks in one job. (Default: 1).
-
-        Raises:
-            ValueError: If a Sequence[BasePass] is given, but it is empty.
+            batch_size (int): (Deprecated).
         """
-
-        if not is_sequence(loop_body) and not isinstance(loop_body, BasePass):
-            raise TypeError(
-                'Expected Pass or sequence of Passes, got %s.'
-                % type(loop_body),
-            )
-
-        if is_sequence(loop_body):
-            truth_list = [isinstance(elem, BasePass) for elem in loop_body]
-            if not all(truth_list):
-                raise TypeError(
-                    'Expected Pass or sequence of Passes, got %s.'
-                    % type(loop_body[truth_list.index(False)]),
-                )
-            if len(loop_body) == 0:
-                raise ValueError('Expected at least one pass.')
-
-        if not is_integer(batch_size):
-            raise TypeError(
-                'Expected integer for batch_size'
-                f', got {type(batch_size)}',
+        if batch_size is not None:
+            import warnings
+            warnings.warn(
+                'Batch size is no longer supported, this warning will'
+                ' become an error in a future update.',
+                DeprecationWarning,
             )
 
-        if batch_size < 0:
-            raise ValueError(
-                'Expected nonnegative integer for batch_size'
-                f', got {batch_size}',
-            )
-
-        self.loop_body = loop_body if is_sequence(loop_body) else [loop_body]
         self.calculate_error_bound = calculate_error_bound
         self.collection_filter = collection_filter or default_collection_filter
         self.replace_filter = replace_filter or default_replace_filter
-        self.batch_size = batch_size
+        self.workflow = Workflow(loop_body)
 
         if not callable(self.collection_filter):
             raise TypeError(
                 'Expected callable method that maps Operations to booleans for'
-                ' collection_filter, got %s.' % type(self.collection_filter),
+                f' collection_filter, got {type(self.collection_filter)}.',
             )
 
         if not callable(self.replace_filter):
             raise TypeError(
                 'Expected callable method that maps Circuit and Operations to'
-                ' booleans for replace_filter'
-                ', got %s.' % type(self.replace_filter),
+                f' bools for replace_filter, got {type(self.replace_filter)}.',
             )
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         # Make room in data for block data
         if self.key not in data:
@@ -134,15 +110,19 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
             if self.collection_filter(op):
                 blocks.append((cycle, op))
 
+        # No blocks, no work
+        if len(blocks) == 0:
+            data[self.key].append([])
+            return
+
         # Get the machine model
-        model = self.get_model(circuit, data)
-        coupling_graph = self.get_connectivity(circuit, data)
+        model = data.model
+        coupling_graph = data.connectivity
 
         # Preprocess blocks
         subcircuits: list[Circuit] = []
-        block_datas: list[dict[str, Any]] = []
+        block_datas: list[PassData] = []
         for i, (cycle, op) in enumerate(blocks):
-            block_data: dict[str, Any] = {}
 
             # Form Subcircuit
             if isinstance(op.gate, CircuitGate):
@@ -151,7 +131,7 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
             else:
                 subcircuit = Circuit.from_operation(op)
 
-            # Form Subtopology
+            # Form Submodel
             subradixes = [circuit.radixes[q] for q in op.location]
             subnumbering = {op.location[i]: i for i in range(len(op.location))}
             submodel = MachineModel(
@@ -161,44 +141,30 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                 subradixes,
             )
 
-            # Form subdata
+            # Form Subdata
+            block_data: PassData = PassData(subcircuit)
             block_data['subnumbering'] = subnumbering
-            block_data['machine_model'] = submodel
-            block_data['parallel'] = self.in_parallel(data)
+            block_data['model'] = submodel
             block_data['point'] = CircuitPoint(cycle, op.location[0])
             block_data['calculate_error_bound'] = self.calculate_error_bound
+            for key in data:
+                if key.startswith(self.pass_down_key_prefix):
+                    block_data[key] = data[key]
+            block_data.seed = data.seed
 
             subcircuits.append(subcircuit)
             block_datas.append(block_data)
 
-        # Group them into batches
-        if self.batch_size == 0:
-            batched_subcircuits = [subcircuits]
-            batched_block_datas = [block_datas]
-        else:
-            batched_subcircuits = [
-                subcircuits[i:i + self.batch_size]
-                for i in range(0, len(blocks), self.batch_size)
-            ]
-            batched_block_datas = [
-                block_datas[i:i + self.batch_size]
-                for i in range(0, len(blocks), self.batch_size)
-            ]
-
         # Do the work
-        results = self.execute(
-            data,
+        results = await get_runtime().map(
             _sub_do_work,
-            [self.loop_body] * len(batched_subcircuits),
-            batched_subcircuits,
-            batched_block_datas,
+            [self.workflow] * len(subcircuits),
+            subcircuits,
+            block_datas,
         )
 
         # Unpack results
-        completed_subcircuits, completed_block_datas = [], []
-        for batch in results:
-            completed_subcircuits.extend(list(zip(*batch))[0])
-            completed_block_datas.extend(list(zip(*batch))[1])
+        completed_subcircuits, completed_block_datas = zip(*results)
 
         # Postprocess blocks
         points: list[CircuitPoint] = []
@@ -223,7 +189,7 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
 
                 # Calculate Error
                 if self.calculate_error_bound:
-                    error_sum += block_data['error']
+                    error_sum += block_data.error
             else:
                 block_data['replaced'] = False
 
@@ -235,11 +201,8 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
 
         # Record error
         if self.calculate_error_bound:
-            if 'error' in data:
-                data['error'] += error_sum
-            else:
-                data['error'] = error_sum
-            _logger.debug(f"New circuit error is {data['error']}.")
+            data.error = (1 - ((1 - data.error) * (1 - error_sum)))
+            _logger.debug(f'New circuit error is {data.error}.')
 
 
 def default_collection_filter(op: Operation) -> bool:
@@ -255,25 +218,3 @@ def default_collection_filter(op: Operation) -> bool:
 
 def default_replace_filter(circuit: Circuit, op: Operation) -> bool:
     return True
-
-
-def _sub_do_work(
-    loop_body: Sequence[BasePass],
-    subcircuits: list[Circuit],
-    subdatas: list[dict[str, Any]],
-) -> list[tuple[Circuit, dict[str, Any]]]:
-    results = []
-    for subcircuit, subdata in zip(subcircuits, subdatas):
-        if subdata['calculate_error_bound']:
-            old_utry = subcircuit.get_unitary()
-
-        for loop_pass in loop_body:
-            loop_pass.run(subcircuit, subdata)
-
-        if subdata['calculate_error_bound']:
-            new_utry = subcircuit.get_unitary()
-            error = new_utry.get_distance_from(old_utry)
-            subdata['error'] = error
-
-        results.append((subcircuit, subdata))
-    return results
diff --git a/bqskit/passes/control/ifthenelse.py b/bqskit/passes/control/ifthenelse.py
index f3e98e93c..443804d31 100644
--- a/bqskit/passes/control/ifthenelse.py
+++ b/bqskit/passes/control/ifthenelse.py
@@ -2,13 +2,16 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
-from typing import Sequence
+from typing import TYPE_CHECKING
 
 from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
+from bqskit.compiler.workflow import Workflow
 from bqskit.passes.control.predicate import PassPredicate
-from bqskit.utils.typing import is_sequence
+
+if TYPE_CHECKING:
+    from bqskit.ir.circuit import Circuit
+    from bqskit.compiler.passdata import PassData
+    from bqskit.compiler.workflow import WorkflowLike
 
 _logger = logging.getLogger(__name__)
 
@@ -17,14 +20,14 @@ class IfThenElsePass(BasePass):
     """
     The IfThenElsePass class.
 
-    This is a control pass that conditionally executes another pass.
+    This is a control pass that conditionally executes a workflow.
     """
 
     def __init__(
         self,
         condition: PassPredicate,
-        on_true: BasePass | Sequence[BasePass],
-        on_false: BasePass | Sequence[BasePass] | None = None,
+        on_true: WorkflowLike,
+        on_false: WorkflowLike | None = None,
     ) -> None:
         """
         Construct a IfThenElsePass.
@@ -32,68 +35,27 @@ def __init__(
         Args:
             condition (PassPredicate): The condition checked.
 
-            on_true (BasePass | Sequence[BasePass]): The pass or passes
-                to execute if `condition` is true.
+            on_true (WorkflowLike): The pass or passes to execute if
+                `condition` is true.
 
-            on_false (BasePass | Sequence[BasePass] | None): If specified,
+            on_false (WorkflowLike | None): If specified,
                 the pass or passes to execute if `condition` is false.
-
-        Raises:
-            ValueError: If a Sequence[BasePass] is given, but it is empty.
+                Defaults to None, which does is equivalent to a No-Op.
         """
 
         if not isinstance(condition, PassPredicate):
             raise TypeError('Expected PassPredicate, got %s.' % type(condition))
 
-        if not is_sequence(on_true) and not isinstance(on_true, BasePass):
-            raise TypeError(
-                'Expected Pass or sequence of Passes, got %s.' % type(on_true),
-            )
-
-        if is_sequence(on_true):
-            truth_list = [isinstance(elem, BasePass) for elem in on_true]
-            if not all(truth_list):
-                raise TypeError(
-                    'Expected Pass or sequence of Passes, got %s.'
-                    % type(on_true[truth_list.index(False)]),
-                )
-            if len(on_true) == 0:
-                raise ValueError('Expected at least one pass for true branch.')
-
-        if on_false is not None:
-            if not is_sequence(on_false) and not isinstance(on_false, BasePass):
-                raise TypeError(
-                    'Expected Pass or sequence of Passes, got %s.'
-                    % type(on_false),
-                )
-
-            if is_sequence(on_false):
-                truth_list = [isinstance(elem, BasePass) for elem in on_false]
-                if not all(truth_list):
-                    raise TypeError(
-                        'Expected Pass or sequence of Passes, got %s.'
-                        % type(on_false[truth_list.index(False)]),
-                    )
-                if len(on_false) == 0:
-                    raise ValueError(
-                        'Expected at least one pass for false branch.',
-                    )
-
-        if on_false is None:
-            on_false = []
-
         self.condition = condition
-        self.on_true = on_true if is_sequence(on_true) else [on_true]
-        self.on_false = on_false if is_sequence(on_false) else [on_false]
+        self.on_true = Workflow(on_true)
+        self.on_false = Workflow(on_false) if on_false is not None else None
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         if self.condition(circuit, data):
             _logger.debug('True branch taken.')
-            for branch_pass in self.on_true:
-                branch_pass.run(circuit, data)
+            await self.on_true.run(circuit, data)
 
         elif self.on_false is not None:
             _logger.debug('False branch taken.')
-            for branch_pass in self.on_false:
-                branch_pass.run(circuit, data)
+            await self.on_false.run(circuit, data)
diff --git a/bqskit/passes/control/paralleldo.py b/bqskit/passes/control/paralleldo.py
index 36b655628..42b9bbeee 100644
--- a/bqskit/passes/control/paralleldo.py
+++ b/bqskit/passes/control/paralleldo.py
@@ -2,16 +2,21 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 from typing import Callable
-from typing import Sequence
-
-from distributed import as_completed
-from distributed import worker_client
+from typing import Iterable
+from typing import TYPE_CHECKING
 
+from bqskit.compiler.basepass import _sub_do_work
 from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
-from bqskit.utils.typing import is_sequence
+from bqskit.compiler.workflow import Workflow
+from bqskit.runtime import get_runtime
+from bqskit.utils.typing import is_iterable
+
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.compiler.workflow import WorkflowLike
+    from bqskit.ir.circuit import Circuit
+
 
 _logger = logging.getLogger(__name__)
 
@@ -20,12 +25,14 @@ class ParallelDo(BasePass):
     """
     The ParallelDo class.
 
-    This is a control pass that executes a group of pass sequences in parallel.
+    This is a control pass that executes a sequence of workflows in parallel.
+    The branch that is accepted can either be the first to complete or one
+    selected by a provided ordering.
     """
 
     def __init__(
         self,
-        pass_sequences: Sequence[BasePass | Sequence[BasePass]],
+        pass_sequences: Iterable[WorkflowLike],
         less_than: Callable[[Circuit, Circuit], bool],
         pick_fisrt: bool = False,
     ) -> None:
@@ -33,8 +40,8 @@ def __init__(
         Construct a ParallelDo.
 
         Args:
-            pass_sequences (Sequence[BasePass | Sequence[BasePass]]):
-                The group of pass sequences to run in parallel.
+            pass_sequences (Iterable[WorkflowLike]): The group of workflows
+                to run in parallel.
 
             less_than (Callable[[Circuit, Circuit], bool]): Return True
                 if the first circuit is preferred to the second one.
@@ -42,84 +49,53 @@ def __init__(
                 select.
 
             pick_first (bool): If true, then the pass will complete as
-                soon as one of the subsequences finishes and will return
-                the first result.
-
-        Raises:
-            ValueError: If a Sequence[BasePass] is given, but it is empty.
+                soon as one of the workflows finishes and will return
+                the first result. Defaults to False.
         """
-        if not is_sequence(pass_sequences):
-            raise TypeError(
-                'Expected sequence of sequences of passes, got %s.'
-                % type(pass_sequences),
-            )
-
-        self.pass_seqs = []
-        for ps in pass_sequences:
-            if not is_sequence(ps) and not isinstance(ps, BasePass):
-                raise TypeError(
-                    'Expected Pass or sequence of Passes, got %s.'
-                    % type(ps),
-                )
-
-            if is_sequence(ps):
-                truth_list = [isinstance(elem, BasePass) for elem in ps]
-                if not all(truth_list):
-                    raise TypeError(
-                        'Expected Pass or sequence of Passes, got %s.'
-                        % type(ps[truth_list.index(False)]),
-                    )
-                if len(ps) == 0:
-                    raise ValueError('Expected at least one pass.')
-
-            self.pass_seqs.append(ps if is_sequence(ps) else [ps])
+        if not is_iterable(pass_sequences):
+            bad_type = type(pass_sequences)
+            raise TypeError(f'Expected sequence of workflows, got {bad_type}.')
+
+        if not callable(less_than):
+            bad_type = type(less_than)
+            msg = f'Expected callable function for less_than, got {bad_type}'
+            raise TypeError(msg)
 
+        self.workflows = [Workflow(p) for p in pass_sequences]
         self.less_than = less_than
         self.pick_first = pick_fisrt
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+        if len(self.workflows) == 0:
+            raise ValueError('Must specify at least one workflow.')
+
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         _logger.debug('Running pass sequences in parallel.')
 
-        if 'parallel' not in data:
-            raise RuntimeError(
-                'Parallel pass cannot be executed without a compiler.',
-            )
-
-        with worker_client() as client:
-            futures = []
-            for pass_seq in self.pass_seqs:
-                future = client.submit(_sub_do_work, pass_seq, circuit, data)
-                futures.append(future)
-
-            if self.pick_first:
-                _, result = next(as_completed(futures, with_results=True))
-                for future in futures:
-                    if not future.done:
-                        client.cancel(future)
-
-                circuit.become(result[0])
-                data.update(result[1])
-
-            else:
-                best_circ = None
-                best_data = None
-                for future in futures:
-                    _circ, _data = future.result()
-                    if best_circ is None or self.less_than(_circ, best_circ):
-                        best_circ = _circ
-                        best_data = _data
-                if best_circ is None or best_data is None:
-                    raise RuntimeError('No valid circuit found.')
-                circuit.become(best_circ)
-                data.update(best_data)
-
-
-def _sub_do_work(
-    loop_body: Sequence[BasePass],
-    circuit: Circuit,
-    data: dict[str, Any],
-) -> tuple[Circuit, dict[str, Any]]:
-    for loop_pass in loop_body:
-        loop_pass.run(circuit, data)
-    return circuit, data
+        # Submit jobs to the runtime
+        runtime = get_runtime()
+        future = runtime.map(
+            _sub_do_work,
+            self.workflows,
+            circuit=circuit,
+            data=data,
+        )
+
+        # Wait for results
+        if self.pick_first:
+            circuits_and_ids = await runtime.next(future)  # Wake on next result
+            circuits = [x[1] for x in circuits_and_ids]
+            runtime.cancel(future)  # Cancel remaining
+        else:
+            circuits = await future
+
+        # Find the best result
+        best_circ, best_data = circuits[0]
+        for _circ, _data in circuits[1:]:
+            if self.less_than(_circ, best_circ):
+                best_circ = _circ
+                best_data = _data
+
+        # Become best result
+        circuit.become(best_circ)
+        data.become(best_data)
diff --git a/bqskit/passes/control/predicate.py b/bqskit/passes/control/predicate.py
index 9a04be925..9e8854e0a 100644
--- a/bqskit/passes/control/predicate.py
+++ b/bqskit/passes/control/predicate.py
@@ -2,8 +2,8 @@
 from __future__ import annotations
 
 import abc
-from typing import Any
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 
@@ -16,16 +16,16 @@ class PassPredicate(abc.ABC):
     """
 
     @abc.abstractmethod
-    def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate and retrieve the truth value result."""
 
-    def __call__(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def __call__(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate and retrieve the truth value result."""
 
         if not isinstance(circuit, Circuit):
-            raise TypeError('Expected Circuit, got %s.' % type(circuit))
+            raise TypeError(f'Expected Circuit, got {type(circuit)}.')
 
-        if not isinstance(data, dict):
-            raise TypeError('Expected dictionary, got %s.' % type(data))
+        if not isinstance(data, PassData):
+            raise TypeError(f'Expected PassData, got {type(data)}.')
 
         return self.get_truth_value(circuit, data)
diff --git a/bqskit/passes/control/predicates/__init__.py b/bqskit/passes/control/predicates/__init__.py
index 8438a7e9f..2037cb5c3 100644
--- a/bqskit/passes/control/predicates/__init__.py
+++ b/bqskit/passes/control/predicates/__init__.py
@@ -1,10 +1,12 @@
 """This package implements predicates for use in control passes."""
 from __future__ import annotations
 
+from bqskit.passes.control.predicates.andpredicate import AndPredicate
 from bqskit.passes.control.predicates.change import ChangePredicate
 from bqskit.passes.control.predicates.count import GateCountPredicate
 from bqskit.passes.control.predicates.multi import MultiPhysicalPredicate
 from bqskit.passes.control.predicates.notpredicate import NotPredicate
+from bqskit.passes.control.predicates.orpredicate import OrPredicate
 from bqskit.passes.control.predicates.physical import PhysicalPredicate
 from bqskit.passes.control.predicates.single import SinglePhysicalPredicate
 from bqskit.passes.control.predicates.width import WidthPredicate
@@ -12,7 +14,9 @@
 __all__ = [
     'ChangePredicate',
     'GateCountPredicate',
+    'AndPredicate',
     'NotPredicate',
+    'OrPredicate',
     'WidthPredicate',
     'PhysicalPredicate',
     'SinglePhysicalPredicate',
diff --git a/bqskit/passes/control/predicates/andpredicate.py b/bqskit/passes/control/predicates/andpredicate.py
new file mode 100644
index 000000000..fdd4bef80
--- /dev/null
+++ b/bqskit/passes/control/predicates/andpredicate.py
@@ -0,0 +1,41 @@
+"""This module implements the AndPredicate class."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from bqskit.passes.control.predicate import PassPredicate
+
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
+
+class AndPredicate(PassPredicate):
+    """
+    The AndPredicate class.
+
+    The AndPredicate takes two predicates and returns true iff both are true.
+    """
+
+    def __init__(self, p1: PassPredicate, p2: PassPredicate) -> None:
+        """
+        Construct a AndPredicate.
+
+        Args:
+            p1 (PassPredicate): The first predicate.
+
+            p2 (PassPredicate): The second predicate.
+        """
+
+        if not isinstance(p1, PassPredicate):
+            raise TypeError(f'Expected PassPredicate, got {type(p1)}.')
+
+        if not isinstance(p2, PassPredicate):
+            raise TypeError(f'Expected PassPredicate, got {type(p2)}.')
+
+        self.p1 = p1
+        self.p2 = p2
+
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        """Call this predicate, see :class:`PassPredicate` for more info."""
+        return self.p1(circuit, data) and self.p2(circuit, data)
diff --git a/bqskit/passes/control/predicates/change.py b/bqskit/passes/control/predicates/change.py
index 1df3fc0a4..8bf5a2ed2 100644
--- a/bqskit/passes/control/predicates/change.py
+++ b/bqskit/passes/control/predicates/change.py
@@ -2,11 +2,15 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
+from typing import TYPE_CHECKING
 
-from bqskit.ir.circuit import Circuit
 from bqskit.passes.control.predicate import PassPredicate
 
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
+
 _logger = logging.getLogger(__name__)
 
 
@@ -20,7 +24,7 @@ class ChangePredicate(PassPredicate):
 
     key = 'ChangePredicate_circuit_hash'
 
-    def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate, see :class:`PassPredicate` for more info."""
 
         # If first call, record data and return true
@@ -32,6 +36,7 @@ def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
         # Otherwise, check for a change and return accordingly
         new_hash = self.get_hash(circuit)
         if data[self.key] == new_hash:
+            # TODO: hash collisions?
             _logger.debug('Hashes match; no change detected.')
             return False
 
diff --git a/bqskit/passes/control/predicates/count.py b/bqskit/passes/control/predicates/count.py
index bc19b6bdf..c0dfaf37c 100644
--- a/bqskit/passes/control/predicates/count.py
+++ b/bqskit/passes/control/predicates/count.py
@@ -2,14 +2,19 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
-from typing import Sequence
+from typing import TYPE_CHECKING
 
-from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
 from bqskit.passes.control.predicate import PassPredicate
 from bqskit.utils.typing import is_sequence
 
+if TYPE_CHECKING:
+    from typing import Sequence
+
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
+
 _logger = logging.getLogger(__name__)
 
 
@@ -17,8 +22,8 @@ class GateCountPredicate(PassPredicate):
     """
     The GateCountPredicate class.
 
-    The GateCountPredicate returns true if the number of one type of gate has
-    changed in the circuit.
+    The GateCountPredicate returns true if the number of one of more types of
+    gate has changed in the circuit.
     """
 
     key = 'GateCountPredicate_circuit_count'
@@ -34,7 +39,7 @@ def __init__(self, gate: Gate | Sequence[Gate]) -> None:
 
         self.gate = gate
 
-    def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate, see :class:`PassPredicate` for more info."""
         gate_count = sum(circuit.count(g) for g in self.gate)
 
diff --git a/bqskit/passes/control/predicates/multi.py b/bqskit/passes/control/predicates/multi.py
index c841b07e3..8d640f6db 100644
--- a/bqskit/passes/control/predicates/multi.py
+++ b/bqskit/passes/control/predicates/multi.py
@@ -2,12 +2,14 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
+from typing import TYPE_CHECKING
 
-from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
 from bqskit.passes.control.predicate import PassPredicate
 
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
 _logger = logging.getLogger(__name__)
 
 
@@ -19,9 +21,9 @@ class MultiPhysicalPredicate(PassPredicate):
     in the native gate set.
     """
 
-    def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate, see :class:`PassPredicate` for more info."""
-        model = BasePass.get_model(circuit, data)
+        model = data.model
         for gate in circuit.gate_set:
             if gate.num_qudits < 2:
                 continue
diff --git a/bqskit/passes/control/predicates/notpredicate.py b/bqskit/passes/control/predicates/notpredicate.py
index 3164ea9dd..00f8a7560 100644
--- a/bqskit/passes/control/predicates/notpredicate.py
+++ b/bqskit/passes/control/predicates/notpredicate.py
@@ -1,13 +1,13 @@
 """This module implements the NotPredicate class."""
 from __future__ import annotations
 
-import logging
-from typing import Any
+from typing import TYPE_CHECKING
 
-from bqskit.ir.circuit import Circuit
 from bqskit.passes.control.predicate import PassPredicate
 
-_logger = logging.getLogger(__name__)
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
 
 
 class NotPredicate(PassPredicate):
@@ -27,10 +27,10 @@ def __init__(self, predicate: PassPredicate) -> None:
         """
 
         if not isinstance(predicate, PassPredicate):
-            raise TypeError('Expected PassPredicate, got %s.' % type(predicate))
+            raise TypeError(f'Expected PassPredicate, got {type(predicate)}.')
 
         self.predicate = predicate
 
-    def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate, see :class:`PassPredicate` for more info."""
         return not self.predicate(circuit, data)
diff --git a/bqskit/passes/control/predicates/orpredicate.py b/bqskit/passes/control/predicates/orpredicate.py
new file mode 100644
index 000000000..2c15de16a
--- /dev/null
+++ b/bqskit/passes/control/predicates/orpredicate.py
@@ -0,0 +1,41 @@
+"""This module implements the OrPredicate class."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from bqskit.passes.control.predicate import PassPredicate
+
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
+
+class OrPredicate(PassPredicate):
+    """
+    The OrPredicate class.
+
+    The OrPredicate takes two predicates and returns true if either are true.
+    """
+
+    def __init__(self, p1: PassPredicate, p2: PassPredicate) -> None:
+        """
+        Construct a OrPredicate.
+
+        Args:
+            p1 (PassPredicate): The first predicate.
+
+            p2 (PassPredicate): The second predicate.
+        """
+
+        if not isinstance(p1, PassPredicate):
+            raise TypeError(f'Expected PassPredicate, got {type(p1)}.')
+
+        if not isinstance(p2, PassPredicate):
+            raise TypeError(f'Expected PassPredicate, got {type(p2)}.')
+
+        self.p1 = p1
+        self.p2 = p2
+
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        """Call this predicate, see :class:`PassPredicate` for more info."""
+        return self.p1(circuit, data) or self.p2(circuit, data)
diff --git a/bqskit/passes/control/predicates/physical.py b/bqskit/passes/control/predicates/physical.py
index fe62eb5d5..26440025b 100644
--- a/bqskit/passes/control/predicates/physical.py
+++ b/bqskit/passes/control/predicates/physical.py
@@ -1,44 +1,23 @@
 """This module implements the PhysicalPredicate class."""
 from __future__ import annotations
 
-import logging
-from typing import Any
+from typing import TYPE_CHECKING
 
-from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
 from bqskit.passes.control.predicate import PassPredicate
 
-_logger = logging.getLogger(__name__)
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
 
 
 class PhysicalPredicate(PassPredicate):
     """
     The PhysicalPredicate class.
 
-    The PhysicalPredicate returns true if circuit's coupling graph and gate set
-    matches the workflow's machine model.
+    The PhysicalPredicate returns true if the circuit can be executed on the
+    workflow's machine model with the current placement.
     """
 
-    def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate, see :class:`PassPredicate` for more info."""
-        model = BasePass.get_model(circuit, data)
-        for gate in circuit.gate_set:
-            if gate not in model.gate_set:
-                _logger.debug(f'{gate} not in native set: {model.gate_set}.')
-                return False
-
-        if circuit.num_qudits == 1:
-            return True
-
-        try:
-            placement = BasePass.get_placement(circuit, data)
-            subgraph = model.coupling_graph.get_subgraph(placement)
-            if not subgraph.is_fully_connected():
-                _logger.debug('Qudits are disconnected on the machine model.')
-                return False
-
-        except RuntimeError:
-            _logger.debug('Qudits are disconnected on the machine model.')
-            return False
-
-        return True
+        return data.model.is_compatible(circuit, data.placement)
diff --git a/bqskit/passes/control/predicates/single.py b/bqskit/passes/control/predicates/single.py
index 0c38af083..c935862e2 100644
--- a/bqskit/passes/control/predicates/single.py
+++ b/bqskit/passes/control/predicates/single.py
@@ -1,13 +1,15 @@
-"""This module implements the PhysicalPredicate class."""
+"""This module implements the SinglePhysicalPredicate class."""
 from __future__ import annotations
 
 import logging
-from typing import Any
+from typing import TYPE_CHECKING
 
-from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
 from bqskit.passes.control.predicate import PassPredicate
 
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
 _logger = logging.getLogger(__name__)
 
 
@@ -19,9 +21,9 @@ class SinglePhysicalPredicate(PassPredicate):
     in the native gate set.
     """
 
-    def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate, see :class:`PassPredicate` for more info."""
-        model = BasePass.get_model(circuit, data)
+        model = data.model
         for gate in circuit.gate_set:
             if gate.num_qudits > 1:
                 continue
diff --git a/bqskit/passes/control/predicates/width.py b/bqskit/passes/control/predicates/width.py
index 0bcb12d0c..4d9b7c9bb 100644
--- a/bqskit/passes/control/predicates/width.py
+++ b/bqskit/passes/control/predicates/width.py
@@ -1,19 +1,22 @@
 """This module implements the WidthPredicate class."""
 from __future__ import annotations
 
-from typing import Any
+from typing import TYPE_CHECKING
 
-from bqskit.ir.circuit import Circuit
 from bqskit.passes.control.predicate import PassPredicate
 from bqskit.utils.typing import is_integer
 
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
 
 class WidthPredicate(PassPredicate):
     """
     The WidthPredicate class.
 
-    The WidthPredicate class returns True if the circuit's width is less than a
-    specified number.
+    The WidthPredicate class returns True if the circuit's width (number of
+    qudits) is less than a specified number.
     """
 
     def __init__(self, width: int) -> None:
@@ -27,8 +30,8 @@ def __init__(self, width: int) -> None:
         if not is_integer(width):
             raise TypeError(f'Expected int, got {type(width)}')
 
-        self.width = width
+        self.width = int(width)
 
-    def get_truth_value(self, circuit: Circuit, data: dict[str, Any]) -> bool:
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
         """Call this predicate, see :class:`PassPredicate` for more info."""
         return circuit.num_qudits < self.width
diff --git a/bqskit/passes/control/whileloop.py b/bqskit/passes/control/whileloop.py
index 72057d02a..211a8524c 100644
--- a/bqskit/passes/control/whileloop.py
+++ b/bqskit/passes/control/whileloop.py
@@ -2,13 +2,16 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
-from typing import Sequence
+from typing import TYPE_CHECKING
 
 from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
+from bqskit.compiler.workflow import Workflow
 from bqskit.passes.control.predicate import PassPredicate
-from bqskit.utils.typing import is_sequence
+
+if TYPE_CHECKING:
+    from bqskit.ir.circuit import Circuit
+    from bqskit.compiler.passdata import PassData
+    from bqskit.compiler.workflow import WorkflowLike
 
 _logger = logging.getLogger(__name__)
 
@@ -17,13 +20,13 @@ class WhileLoopPass(BasePass):
     """
     The WhileLoopPass class.
 
-    This is a control pass that conditionally executes another pass in a loop.
+    This is a control pass that conditionally executes a workflow in a loop.
     """
 
     def __init__(
         self,
         condition: PassPredicate,
-        loop_body: BasePass | Sequence[BasePass],
+        loop_body: WorkflowLike,
     ) -> None:
         """
         Construct a WhileLoopPass.
@@ -31,38 +34,17 @@ def __init__(
         Args:
             condition (PassPredicate): The condition checked.
 
-            loop_body (BasePass | Sequence[BasePass]): The pass or passes
-                to execute while `condition` is true.
-
-        Raises:
-            ValueError: If a Sequence[BasePass] is given, but it is empty.
+            loop_body (WorkflowLike): The pass or passes to execute
+                while `condition` is true.
         """
-
         if not isinstance(condition, PassPredicate):
-            raise TypeError('Expected PassPredicate, got %s.' % type(condition))
-
-        if not is_sequence(loop_body) and not isinstance(loop_body, BasePass):
-            raise TypeError(
-                'Expected Pass or sequence of Passes, got %s.'
-                % type(loop_body),
-            )
-
-        if is_sequence(loop_body):
-            truth_list = [isinstance(elem, BasePass) for elem in loop_body]
-            if not all(truth_list):
-                raise TypeError(
-                    'Expected Pass or sequence of Passes, got %s.'
-                    % type(loop_body[truth_list.index(False)]),
-                )
-            if len(loop_body) == 0:
-                raise ValueError('Expected at least one pass.')
+            raise TypeError(f'Expected PassPredicate, got {type(condition)}.')
 
         self.condition = condition
-        self.loop_body = loop_body if is_sequence(loop_body) else [loop_body]
+        self.workflow = Workflow(loop_body)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         while self.condition(circuit, data):
             _logger.debug('Loop body executing...')
-            for loop_pass in self.loop_body:
-                loop_pass.run(circuit, data)
+            await self.workflow.run(circuit, data)
diff --git a/bqskit/passes/io/checkpoint.py b/bqskit/passes/io/checkpoint.py
index f9631b66f..7a7dbd776 100644
--- a/bqskit/passes/io/checkpoint.py
+++ b/bqskit/passes/io/checkpoint.py
@@ -3,9 +3,9 @@
 
 import logging
 import pickle
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 _logger = logging.getLogger(__name__)
@@ -27,7 +27,7 @@ def __init__(self, checkpoint_filename: str) -> None:
         """
         self.checkpoint_filename = checkpoint_filename
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see BasePass for more info."""
         with open(self.checkpoint_filename, 'wb') as f:
             pickle.dump((circuit, data), f)
@@ -49,7 +49,7 @@ def __init__(self, checkpoint_filename: str) -> None:
         """
         self.checkpoint_filename = checkpoint_filename
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see BasePass for more info."""
         with open(self.checkpoint_filename, 'rb') as f:
             checkpoint = pickle.load(f)
diff --git a/bqskit/passes/io/intermediate.py b/bqskit/passes/io/intermediate.py
index 2df59b6bc..47c85b9f1 100644
--- a/bqskit/passes/io/intermediate.py
+++ b/bqskit/passes/io/intermediate.py
@@ -7,9 +7,9 @@
 from os import mkdir
 from os.path import exists
 from re import findall
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.lang.qasm2.qasm2 import OPENQASM2Language
@@ -71,7 +71,7 @@ def __init__(
 
         self.as_qasm = save_as_qasm
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see BasePass for more info."""
 
         # Gather and enumerate CircuitGates to save
@@ -102,7 +102,7 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                 block.params,
             )
             subcircuit.unfold((0, 0))
-            ToU3Pass().run(subcircuit, {})
+            await ToU3Pass().run(subcircuit, PassData(subcircuit))
             if self.as_qasm:
                 with open(block_skeleton + f'{enum}.qasm', 'w') as f:
                     f.write(OPENQASM2Language().encode(subcircuit))
@@ -171,7 +171,7 @@ def reload_blocks(self) -> None:
                 'More block files than indicies in `structure.pickle`',
             )
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """
         Perform the pass's operation, see BasePass for more info.
 
diff --git a/bqskit/passes/mapping/__init__.py b/bqskit/passes/mapping/__init__.py
index 6d30aa3e2..7dd744579 100644
--- a/bqskit/passes/mapping/__init__.py
+++ b/bqskit/passes/mapping/__init__.py
@@ -2,11 +2,15 @@
 from __future__ import annotations
 
 from bqskit.passes.mapping.apply import ApplyPlacement
+from bqskit.passes.mapping.embed import EmbedAllPermutationsPass
+from bqskit.passes.mapping.layout.pam import PAMLayoutPass
 from bqskit.passes.mapping.layout.sabre import GeneralizedSabreLayoutPass
 from bqskit.passes.mapping.placement.greedy import GreedyPlacementPass
 from bqskit.passes.mapping.placement.trivial import TrivialPlacementPass
+from bqskit.passes.mapping.routing.pam import PAMRoutingPass
 from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass
 from bqskit.passes.mapping.setmodel import SetModelPass
+from bqskit.passes.mapping.topology import SubtopologySelectionPass
 
 __all__ = [
     'GeneralizedSabreLayoutPass',
@@ -15,4 +19,8 @@
     'GeneralizedSabreRoutingPass',
     'SetModelPass',
     'ApplyPlacement',
+    'PAMLayoutPass',
+    'PAMRoutingPass',
+    'EmbedAllPermutationsPass',
+    'SubtopologySelectionPass',
 ]
diff --git a/bqskit/passes/mapping/apply.py b/bqskit/passes/mapping/apply.py
index 060786778..1dc637986 100644
--- a/bqskit/passes/mapping/apply.py
+++ b/bqskit/passes/mapping/apply.py
@@ -2,9 +2,9 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 _logger = logging.getLogger(__name__)
@@ -13,14 +13,14 @@
 class ApplyPlacement(BasePass):
     """Place the circuit on the machine model."""
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
-        model = self.get_model(circuit, data)
-        placement = self.get_placement(circuit, data)
+        model = data.model
+        placement = data.placement
         physical_circuit = Circuit(model.num_qudits, model.radixes)
         physical_circuit.append_circuit(circuit, placement)
         circuit.become(physical_circuit)
         if 'final_mapping' in data:
             pi = data['final_mapping']
             data['final_mapping'] = [placement[p] for p in pi]
-        data['placement'] = list(i for i in range(model.num_qudits))
+        data.placement = list(i for i in range(model.num_qudits))
diff --git a/bqskit/passes/mapping/embed.py b/bqskit/passes/mapping/embed.py
new file mode 100644
index 000000000..ee98bcbb2
--- /dev/null
+++ b/bqskit/passes/mapping/embed.py
@@ -0,0 +1,209 @@
+"""This module implements the EmbedAllPermutationsPass pass."""
+from __future__ import annotations
+
+import copy
+import itertools as it
+import logging
+from typing import Callable
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.mapping.topology import SubtopologySelectionPass
+from bqskit.passes.synthesis.leap import LEAPSynthesisPass
+from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.qis.graph import CouplingGraph
+from bqskit.qis.permutation import PermutationMatrix
+from bqskit.runtime import get_runtime
+
+
+_logger = logging.getLogger(__name__)
+
+
+def multi_qudit_op_count(circuit: Circuit) -> float:
+    """Counts the number of multi-qudit operations in a circuit."""
+    x = sum(c for g, c in circuit.gate_counts.items() if g.num_qudits >= 2)
+    return float(x)
+
+
+class EmbedAllPermutationsPass(BasePass):
+    """Embed permutation aware synthesis results into a flow for future use."""
+
+    def __init__(
+        self,
+        input_perm: bool = False,
+        output_perm: bool = True,
+        vary_topology: bool = True,
+        inner_synthesis: SynthesisPass = LEAPSynthesisPass(),
+        scoring_fn: Callable[[Circuit], float] = multi_qudit_op_count,
+    ) -> None:
+        """
+        Construct a EmbedAllPermutationsPass.
+
+        Args:
+            input_perm (bool): If true, vary the input permutation
+                during synthesis. (Default: False)
+
+            output_perm (bool): If true, vary the output permutation
+                during synthesis. (Default: True)
+
+            vary_topology (bool): If true, vary the desired coupling graph
+                during synthesis. (Default: True)
+
+            inner_synthesis (SynthesisPass): The synthesis algorithm used
+                on all permutations. (Default: :class:`LEAPSynthesisPass`)
+
+            scoring_fn (Callable[[Circuit], float]): The scoring function
+                used when comparing the original circuit to the synthesized
+                circuit with the same configuration. The smallest score wins.
+                (Default: :func:`multi_qudit_op_count`)
+        """
+        if not isinstance(inner_synthesis, SynthesisPass):
+            bad_type = type(inner_synthesis)
+            raise TypeError(f'Expected SynthesisPass object, got {bad_type}.')
+
+        if not callable(scoring_fn):
+            bad_type = type(scoring_fn)
+            m = f'Expected a function from circuits to scores, got {bad_type}.'
+            raise TypeError(m)
+
+        self.input_perm = input_perm
+        self.output_perm = output_perm
+        self.vary_topology = vary_topology
+        self.inner_synthesis = inner_synthesis
+        self.scoring_fn = scoring_fn
+
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        """Perform the pass's operation, see :class:`BasePass` for more."""
+        utry = data.target
+
+        # Calculate all permuted targets
+        width = utry.num_qudits
+        perms = list(it.permutations(range(width)))
+        no_perm = [tuple(range(width))]
+        Pis = [PermutationMatrix.from_qubit_location(width, p) for p in perms]
+        Pos = [PermutationMatrix.from_qubit_location(width, p) for p in perms]
+
+        if self.input_perm and self.output_perm:
+            permsbyperms = list(it.product(perms, perms))
+            targets = [Po.T @ utry @ Pi for Pi, Po in it.product(Pis, Pos)]
+
+        elif self.input_perm:
+            permsbyperms = list(it.product(perms, no_perm))
+            targets = [utry @ Pi for Pi in Pis]
+
+        elif self.output_perm:
+            permsbyperms = list(it.product(no_perm, perms))
+            targets = [Po.T @ utry for Po in Pos]
+
+        else:
+            _logger.warning('No permutation is being used in PAS.')
+            permsbyperms = list(it.product(no_perm, no_perm))
+            targets = [utry]
+
+        # Calculate all target coupling graphs
+        if self.vary_topology and width != 1:
+            if SubtopologySelectionPass.key not in data:
+                raise RuntimeError(
+                    'Cannot find subtopologies, try running a'
+                    ' SubtopologySelectionPass first.',
+                )
+
+            if width not in data[SubtopologySelectionPass.key]:
+                raise RuntimeError(
+                    'Subtopology information for block size'
+                    f' {width} is not available.',
+                )
+
+            graphs = data[SubtopologySelectionPass.key][width]
+
+        else:
+            graphs = [CouplingGraph.all_to_all(width)]
+
+        datas = []
+        for graph in graphs:
+            model = MachineModel(circuit.num_qudits, graph)
+            target_data = copy.deepcopy(data)
+            target_data.model = model
+            datas.append(target_data)
+
+        # Create parallel arrays for map
+        extended_targets = []
+        extended_datas = []
+        for t, d in it.product(targets, datas):
+            extended_targets.append(t)
+            extended_datas.append(d)
+
+        # Synthesize all permuted targets
+        circuits: list[Circuit] = await get_runtime().map(
+            self.inner_synthesis.synthesize,
+            extended_targets,
+            extended_datas,
+        )
+
+        # Store results
+        perm_data: dict[
+            CouplingGraph,
+            dict[tuple[tuple[int, ...], tuple[int, ...]], Circuit],
+        ] = {}
+        for i, c in enumerate(circuits):
+            graph = graphs[i % len(graphs)]
+            perm = permsbyperms[i // len(graphs)]
+            if graph not in perm_data:
+                perm_data[graph] = {}
+
+            if perm in perm_data[graph]:
+                # Update if it is better than whats already there
+                s1 = self.scoring_fn(perm_data[graph][perm])
+                s2 = self.scoring_fn(c)
+                if s2 < s1:
+                    perm_data[graph][perm] = c
+            else:
+                perm_data[graph][perm] = c
+
+            # Calculate number of multi-qudit gates
+            num_mq_gates = 0
+            for gate, count in c.gate_counts.items():
+                if gate.num_qudits >= 2:
+                    num_mq_gates += count
+
+            # Generate the extra circuits through universal permutations
+            all_perms = list(it.permutations(range(width)))
+            for univ_perm in all_perms[1:]:
+                renumber_c = c.copy()
+                renumber_c.renumber_qudits(univ_perm)
+                new_pi = tuple(univ_perm[i] for i in perm[0])
+                new_pf = tuple(univ_perm[i] for i in perm[1])
+                new_graph = renumber_c.coupling_graph
+                if new_graph not in perm_data:
+                    perm_data[new_graph] = {}
+
+                new_perm = (new_pi, new_pf)
+                if new_perm not in perm_data[new_graph]:
+                    perm_data[new_graph][new_perm] = renumber_c
+                else:
+                    s1 = self.scoring_fn(perm_data[new_graph][new_perm])
+                    s2 = self.scoring_fn(renumber_c)
+                    if s2 < s1:
+                        perm_data[new_graph][new_perm] = renumber_c
+
+        # Override no perm result if original is better and compatible
+        if circuit.gate_set.issubset(data.model.gate_set):
+            for univ_perm in it.permutations(range(width)):
+                # Permute original circuit and override worse results
+                uperm = (univ_perm, univ_perm)
+                renumber_c = circuit.copy()
+                renumber_c.renumber_qudits(univ_perm)
+                new_graph = renumber_c.coupling_graph
+                new_score = self.scoring_fn(renumber_c)
+                for graph, graph_data in perm_data.items():
+                    if all(e in graph for e in new_graph):
+                        if uperm not in graph_data:
+                            graph_data[uperm] = renumber_c
+                        else:
+                            if new_score < self.scoring_fn(graph_data[uperm]):
+                                graph_data[uperm] = renumber_c
+
+        # Record permutation data in the pass data
+        data['permutation_data'] = perm_data
diff --git a/bqskit/passes/mapping/layout/__init__.py b/bqskit/passes/mapping/layout/__init__.py
index c053a05a8..a01cb9b90 100644
--- a/bqskit/passes/mapping/layout/__init__.py
+++ b/bqskit/passes/mapping/layout/__init__.py
@@ -6,6 +6,7 @@
 """
 from __future__ import annotations
 
+from bqskit.passes.mapping.layout.pam import PAMLayoutPass
 from bqskit.passes.mapping.layout.sabre import GeneralizedSabreLayoutPass
 
-__all__ = ['GeneralizedSabreLayoutPass']
+__all__ = ['GeneralizedSabreLayoutPass', 'PAMLayoutPass']
diff --git a/bqskit/passes/mapping/layout/pam.py b/bqskit/passes/mapping/layout/pam.py
new file mode 100644
index 000000000..a0a4bc2e7
--- /dev/null
+++ b/bqskit/passes/mapping/layout/pam.py
@@ -0,0 +1,91 @@
+"""This module implements the PAMLayoutPass."""
+from __future__ import annotations
+
+import logging
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.point import CircuitPoint
+from bqskit.passes.control.foreach import ForEachBlockPass
+from bqskit.passes.mapping.pam import PAMBlockTAPermData
+from bqskit.passes.mapping.pam import PermutationAwareMappingAlgorithm
+
+_logger = logging.getLogger(__name__)
+
+
+class PAMLayoutPass(PermutationAwareMappingAlgorithm, BasePass):
+    """Layout algorithm using permutation-aware mapping."""
+
+    def __init__(
+        self,
+        total_passes: int = 1,
+        gate_count_weight: float = 0.3,
+        decay_delta: float = 0.001,
+        decay_reset_interval: int = 5,
+        decay_reset_on_gate: bool = True,
+        extended_set_size: int = 20,
+        extended_set_weight: float = 0.5,
+    ) -> None:
+        """
+        Construct a PAMLayoutPass.
+
+        Args:
+            total_passes (int): The amount of forward and backward passes
+                to apply before finalizing the layout.
+
+            gate_count_weight (float): See
+                :class:`PermutationAwareMappingAlgorithm` for info.
+                (Default: 0.3)
+
+            decay_delta (float): See :class:`GeneralizedSabreAlgorithm`
+                for info. (Default: 0.001)
+
+            decay_reset_interval (int): See :class:`GeneralizedSabreAlgorithm`
+                for info. (Default: 5)
+
+            decay_reset_on_gate (bool): See :class:`GeneralizedSabreAlgorithm`
+                for info. (Default: True)
+
+            extended_set_size (int): See :class:`GeneralizedSabreAlgorithm`
+                for info. (Default: 20)
+
+            extended_set_weight (float): See :class:`GeneralizedSabreAlgorithm`
+                for info. (Default: 0.5)
+        """
+        if not isinstance(total_passes, int):
+            m = f'Expected int for total_passes, got {type(total_passes)}'
+            raise TypeError(m)
+
+        if total_passes < 1:
+            raise ValueError('Total passes must be a positive integer.')
+
+        self.total_passes = total_passes
+        super().__init__(
+            gate_count_weight,
+            decay_delta,
+            decay_reset_interval,
+            decay_reset_on_gate,
+            extended_set_size,
+            extended_set_weight,
+        )
+
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        """Perform the pass's operation, see :class:`BasePass` for more."""
+        subgraph = data.connectivity
+
+        perm_data: dict[CircuitPoint, PAMBlockTAPermData] = {}
+        block_datas = data[ForEachBlockPass.key][-1]
+        for block_data in block_datas:
+            perm_data[block_data['point']] = block_data['permutation_data']
+
+        if not subgraph.is_fully_connected():
+            raise RuntimeError('Cannot route circuit on disconnected qudits.')
+
+        pi = [i for i in range(circuit.num_qudits)]
+        for _ in range(self.total_passes):
+            self.forward_pass(circuit, pi, subgraph, perm_data)
+            self.backward_pass(circuit, pi, subgraph)
+
+        data['initial_mapping'] = pi
+        _logger.info(f'Found layout: {str(pi)}')
diff --git a/bqskit/passes/mapping/layout/sabre.py b/bqskit/passes/mapping/layout/sabre.py
index c2587bc8a..7cecca197 100644
--- a/bqskit/passes/mapping/layout/sabre.py
+++ b/bqskit/passes/mapping/layout/sabre.py
@@ -2,9 +2,9 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.passes.mapping.sabre import GeneralizedSabreAlgorithm
 
@@ -67,7 +67,7 @@ def __init__(
             extended_set_weight,
         )
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         subgraph = self.get_connectivity(circuit, data)
         if not subgraph.is_fully_connected():
diff --git a/bqskit/passes/mapping/pam.py b/bqskit/passes/mapping/pam.py
new file mode 100644
index 000000000..8af5d42b9
--- /dev/null
+++ b/bqskit/passes/mapping/pam.py
@@ -0,0 +1,364 @@
+"""This module implements the PAMRoutingPass class."""
+from __future__ import annotations
+
+import itertools as it
+import logging
+from typing import Dict
+from typing import Sequence
+from typing import Tuple
+
+import numpy as np
+
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates.constant.swap import SwapGate
+from bqskit.ir.point import CircuitPoint
+from bqskit.passes.mapping.sabre import GeneralizedSabreAlgorithm
+from bqskit.qis.graph import CouplingGraph
+
+_logger = logging.getLogger(__name__)
+
+
+PAMBlockPermData = Dict[Tuple[Tuple[int, ...], Tuple[int, ...]], Circuit]
+PAMBlockTAPermData = Dict[CouplingGraph, PAMBlockPermData]
+
+
+class PermutationAwareMappingAlgorithm(GeneralizedSabreAlgorithm):
+    """Route the circuit with permutation awareness."""
+
+    def __init__(
+        self,
+        gate_count_weight: float = 0.1,
+        decay_delta: float = 0.001,
+        decay_reset_interval: int = 5,
+        decay_reset_on_gate: bool = True,
+        extended_set_size: int = 20,
+        extended_set_weight: float = 0.5,
+    ) -> None:
+        """
+        Construct a PermutationAwareMappingAlgorithm.
+
+        Args:
+            gate_count_weight (float): The weight on block gate count
+                versus mapping score when selecting a permutation.
+
+            decay_delta (float): The amount to adjust the decay factor by
+                each time a swap is applied. Set to zero to disable decay.
+                (Default: 0.001)
+
+            decay_reset_interval (int): The amount of swaps to apply before
+                reseting the decay factors. (Default: 5)
+
+            decay_reset_on_gate (bool): If true, reset decay factors when
+                a logical gate is applied. (Default: True)
+
+            extended_set_size (int): The size of the look-ahead or extended
+                set. Set to zero to disable look ahead. (Default: 20)
+
+            extended_set_weight (float): The weight on the extended set
+                term when scoring potential swaps. (Default: 0.5)
+        """
+        if not isinstance(gate_count_weight, float):
+            bad_type = type(gate_count_weight)
+            m = f'Expected float for gate_count_weight, got {bad_type}'
+            raise TypeError(m)
+
+        self.gate_count_weight = gate_count_weight
+
+        super().__init__(
+            decay_delta,
+            decay_reset_interval,
+            decay_reset_on_gate,
+            extended_set_size,
+            extended_set_weight,
+        )
+
+    def forward_pass(  # type: ignore
+        self,
+        circuit: Circuit,
+        pi: list[int],
+        cg: CouplingGraph,
+        perm_data: dict[CircuitPoint, PAMBlockTAPermData],
+        modify_circuit: bool = False,
+    ) -> None:
+        """
+        Apply a forward pass of the PAM algorithm to `pi`.
+
+        Args:
+            circuit (Circuit): The circuit to pass over.
+
+            pi (list[int]): The input logical-to-physical mapping. This
+                maps logical qudits to physical qudits. So, `pi[l] == p`
+                implies logical qudit `l` is sitting on physical qudit `p`.
+
+            cg (CouplingGraph): The connectivity of the hardware.
+
+            perm_data (dict[CircuitPoint, PAMBlockTAPermData]):
+                Maps each permutation configuration for every block.
+
+            modfiy_circuit (bool): Whether to modify the circuit as the
+                pass is applied or not. (Default: False)
+        """
+        # Preprocessing
+        D = cg.all_pairs_shortest_path()
+        F = circuit.front
+        decay = [1.0 for i in range(circuit.num_qudits)]
+        iter_count = 0
+        prev_executed_counts: dict[CircuitPoint, int] = {n: 0 for n in F}
+        leading_swaps: list[tuple[int, int]] = []
+        _logger.debug(f'Starting forward pam pass with pi: {pi}.')
+
+        if modify_circuit:
+            mapped_circuit = Circuit(circuit.num_qudits, circuit.radixes)
+
+        # Main Loop
+        while len(F) > 0:
+
+            # Retrieve executable gates giving the current mapping `pi`
+            execute_list = [n for n in F if self._can_exe(circuit[n], pi, cg)]
+
+            # Execute the gates and update F
+            if len(execute_list) > 0:
+                leading_swaps = []
+
+                for n in execute_list:
+                    F.remove(n)
+                    prev_executed_counts.pop(n)
+                    _logger.debug(f'Executing gate at point {n}.')
+
+                    for successor in circuit.next(n):
+                        if successor not in prev_executed_counts:
+                            prev_executed_counts[successor] = 1
+                        else:
+                            prev_executed_counts[successor] += 1
+                        num_prev_executed = prev_executed_counts[successor]
+                        total_num_prev = len(circuit.prev(successor))
+                        if num_prev_executed == total_num_prev:
+                            F.add(successor)
+
+                # Permute the qubits on the just executed gates
+                E = self._calc_extended_set(circuit, F)
+                for n in execute_list:
+                    op = circuit[n]
+                    p1, circ, p2 = self._get_best_perm(
+                        circuit,
+                        perm_data[n],
+                        cg,
+                        F,
+                        pi,
+                        D,
+                        E,
+                        op.location,
+                    )
+
+                    self._apply_perm(p1, pi)
+
+                    if modify_circuit:
+                        physical_location = [pi[q] for q in op.location]
+                        mapped_circuit.append_circuit(
+                            circ,
+                            physical_location,
+                            True,
+                        )
+
+                    self._apply_perm(p2, pi)
+
+                # Reset decay if necessary
+                if self.decay_reset_on_gate:
+                    iter_count = 0
+                    for i in range(circuit.num_qudits):
+                        decay[i] = 1.0
+
+                continue  # Restart main loop if we executed at least one gate
+
+            # If execute list is empty, check for local-minima
+            elif len(leading_swaps) > 5 * cg.num_qudits:
+                _logger.debug('Sabre stuck in local minima, backtracking...')
+
+                # Backtrack by removing leading swaps
+                for swap in reversed(leading_swaps):
+                    self._apply_swap(swap, pi, decay)
+                    if modify_circuit:
+                        point = mapped_circuit._rear[swap[0]]
+                        mapped_circuit.pop(point)
+                leading_swaps = []
+
+                # Override heuristic search to progress
+                _logger.debug('Overriding sabre search...')
+                all_logical_qudits = [circuit[n].location for n in F]
+                qudits = min(
+                    all_logical_qudits,
+                    key=lambda qs: self._get_distance(qs, pi, D),
+                )
+                for swap in self._uphill_swaps(qudits, cg, pi, D):
+                    self._apply_swap(swap, pi, decay)
+                    if modify_circuit:
+                        mapped_circuit.append_gate(SwapGate(), swap)
+                _logger.debug('Stopping override.')
+                continue
+
+            # Pick and apply a swap
+            E = self._calc_extended_set(circuit, F)
+            best_swap = self._get_best_swap(circuit, F, E, D, cg, pi, decay)
+            self._apply_swap(best_swap, pi, decay)
+            leading_swaps.append(best_swap)
+
+            if modify_circuit:
+                mapped_circuit.append_gate(SwapGate(), best_swap)
+
+            # Update loop counter and reset decay if necessary
+            iter_count += 1
+            if iter_count % self.decay_reset_interval == 0:
+                for i in range(circuit.num_qudits):
+                    decay[i] = 1.0
+
+        if modify_circuit:
+            circuit.become(mapped_circuit)
+
+    def _get_best_perm(
+        self,
+        circuit: Circuit,
+        perm_data: PAMBlockTAPermData,
+        cg: CouplingGraph,
+        F: set[CircuitPoint],
+        pi: list[int],
+        D: list[list[int]],
+        E: set[CircuitPoint],
+        qudits: Sequence[int],
+    ) -> tuple[tuple[int, ...], Circuit, tuple[int, ...]]:
+        """Return the best permutation to apply before and after a gate."""
+
+        # Local permutations determine how a gate is permuted in it own space
+        local_perms = list(it.permutations(range(len(qudits))))
+
+        # Global perms capture local perms' effect on the full logical space
+        global_perms = [
+            tuple(qudits[i] for i in lperm)
+            for lperm in local_perms
+        ]
+
+        # Inverted Permutations
+        inv_local_perms = [
+            tuple(lperm.index(i) for i in range(len(qudits)))
+            for lperm in local_perms
+        ]
+
+        # Inverted Global Permutations
+        inv_global_perms = [
+            tuple(qudits[i] for i in ilperm)
+            for ilperm in inv_local_perms
+        ]
+
+        # Gather valid pre, circ, post triples
+        pre_circ_post_triples = []
+        perm_iter = zip(local_perms, inv_local_perms, inv_global_perms)
+        for lperm, ilperm, gperm1 in perm_iter:
+            physical_location = [pi[qudits[p]] for p in ilperm]
+            local_graph = cg.get_subgraph(physical_location)
+            if local_graph in perm_data:
+                for perms, circ in perm_data[local_graph].items():
+                    if lperm == perms[0]:
+                        gperm2 = global_perms[local_perms.index(perms[1])]
+                        pre_circ_post_triples.append((gperm1, circ, gperm2))
+
+        if len(pre_circ_post_triples) == 0:
+            raise RuntimeError(
+                'Unable to find any valid permutated circuits.\n'
+                'You must embed proper permutation aware synthesis results'
+                ' first before running this pass.\n'
+                'If you are already running an'
+                ' EmbedAllPermutationsPass, try toggling topology selection.',
+            )
+
+        # For each permutation get the entangling gate count
+        mq_gate_counts = []
+        sq_gate_counts = []
+        for _, circ, _ in pre_circ_post_triples:
+            num_tq_gates = 0
+            num_sq_gates = 0
+            for gate, count in circ.gate_counts.items():
+                if gate.num_qudits >= 2:
+                    num_tq_gates += count
+                else:
+                    num_sq_gates += count
+            mq_gate_counts.append(num_tq_gates)
+            sq_gate_counts.append(num_sq_gates)
+
+        # If no more gates after this one, then pick the shortest circuit
+        if len(F) == 0:
+            best_index = np.argmin(mq_gate_counts)
+            return pre_circ_post_triples[best_index]
+
+        # Calculate best scoring permutation
+        best_triple = pre_circ_post_triples[0]
+        best_perm = (best_triple[0], best_triple[2])
+        mapping_score = self._score_perm(circuit, F, pi, D, best_perm, E)
+        gate_score = mq_gate_counts[0] * self.gate_count_weight / len(F)
+        best_score = mapping_score + gate_score
+
+        for i in range(1, len(pre_circ_post_triples)):
+            gperm = (pre_circ_post_triples[i][0], pre_circ_post_triples[i][2])
+            score = self._score_perm(circuit, F, pi, D, gperm, E)
+            score = mq_gate_counts[i] * self.gate_count_weight / len(F) + score
+            if score < best_score:
+                best_score = score
+                best_perm = gperm
+                best_triple = pre_circ_post_triples[i]
+        return best_triple
+
+    def _score_perm(
+        self,
+        circuit: Circuit,
+        F: set[CircuitPoint],
+        pi: list[int],
+        D: list[list[int]],
+        perm: tuple[Sequence[int], Sequence[int]],
+        E: set[CircuitPoint],
+    ) -> float:
+        """Calculating the routing score after applying `perm`."""
+        pi_bkp = pi.copy()
+        pi_c = {q: pi[perm[0][i]] for i, q in enumerate(sorted(perm[0]))}
+        for q in perm[0]:
+            pi[q] = pi_c[q]
+        pi_c = {q: pi[perm[1][i]] for i, q in enumerate(sorted(perm[1]))}
+        for q in perm[1]:
+            pi[q] = pi_c[q]
+
+        # Front Set Term
+        front = 0.0
+        for n in F:
+            min_term = np.inf
+            for q in circuit[n].location:
+                term = 0.0
+                for p in circuit[n].location:
+                    if p == q:
+                        continue
+                    term += D[pi[q]][pi[p]]
+                min_term = min(term, min_term)
+            front += min_term
+        front /= len(F)
+
+        # Extended Set Term
+        extend = 0.0
+        if len(E) > 0:
+            for n in E:
+                min_term = np.inf
+                for q in circuit[n].location:
+                    term = 0.0
+                    for p in circuit[n].location:
+                        if p == q:
+                            continue
+                        term += D[pi[q]][pi[p]]
+                    min_term = min(term, min_term)
+                extend += min_term
+            extend /= len(E)
+            extend *= self.extended_set_weight
+
+        pi[:] = pi_bkp[:]
+        return front + extend
+
+    def _apply_perm(self, perm: Sequence[int], pi: list[int]) -> None:
+        """Apply the `perm` permutation to the current mapping `pi`."""
+        _logger.debug('applying permutation %s' % str(perm))
+        pi_c = {q: pi[perm[i]] for i, q in enumerate(sorted(perm))}
+        for q in perm:
+            pi[q] = pi_c[q]
diff --git a/bqskit/passes/mapping/placement/greedy.py b/bqskit/passes/mapping/placement/greedy.py
index 7e91a13e6..ac8a58879 100644
--- a/bqskit/passes/mapping/placement/greedy.py
+++ b/bqskit/passes/mapping/placement/greedy.py
@@ -2,11 +2,11 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 import numpy as np
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 _logger = logging.getLogger(__name__)
 
@@ -14,7 +14,7 @@
 class GreedyPlacementPass(BasePass):
     """Find a placement by starting with the most connected qudit."""
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Find physical qudit with highest degree
diff --git a/bqskit/passes/mapping/placement/trivial.py b/bqskit/passes/mapping/placement/trivial.py
index cbea3fb9e..df54d94e5 100644
--- a/bqskit/passes/mapping/placement/trivial.py
+++ b/bqskit/passes/mapping/placement/trivial.py
@@ -2,9 +2,9 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 _logger = logging.getLogger(__name__)
 
@@ -12,7 +12,7 @@
 class TrivialPlacementPass(BasePass):
     """Place the logical qubits on the first n physical qubits."""
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Select the first n physical qubits for placement
diff --git a/bqskit/passes/mapping/routing/__init__.py b/bqskit/passes/mapping/routing/__init__.py
index 56a55b87b..953e50373 100644
--- a/bqskit/passes/mapping/routing/__init__.py
+++ b/bqskit/passes/mapping/routing/__init__.py
@@ -5,6 +5,7 @@
 """
 from __future__ import annotations
 
+from bqskit.passes.mapping.routing.pam import PAMRoutingPass
 from bqskit.passes.mapping.routing.sabre import GeneralizedSabreRoutingPass
 
-__all__ = ['GeneralizedSabreRoutingPass']
+__all__ = ['GeneralizedSabreRoutingPass', 'PAMRoutingPass']
diff --git a/bqskit/passes/mapping/routing/pam.py b/bqskit/passes/mapping/routing/pam.py
new file mode 100644
index 000000000..2bdb8a614
--- /dev/null
+++ b/bqskit/passes/mapping/routing/pam.py
@@ -0,0 +1,42 @@
+"""This module implements the PAMRoutingPass."""
+from __future__ import annotations
+
+import copy
+import logging
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.point import CircuitPoint
+from bqskit.passes.control.foreach import ForEachBlockPass
+from bqskit.passes.mapping.pam import PAMBlockTAPermData
+from bqskit.passes.mapping.pam import PermutationAwareMappingAlgorithm
+
+_logger = logging.getLogger(__name__)
+
+
+class PAMRoutingPass(PermutationAwareMappingAlgorithm, BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        """Perform the pass's operation, see :class:`BasePass` for more."""
+        model = self.get_model(circuit, data)
+        placement = self.get_placement(circuit, data)
+        subgraph = model.coupling_graph.get_subgraph(placement)
+
+        perm_data: dict[CircuitPoint, PAMBlockTAPermData] = {}
+        block_datas = data[ForEachBlockPass.key][-1]
+        for block_data in block_datas:
+            perm_data[block_data['point']] = block_data['permutation_data']
+
+        if not subgraph.is_fully_connected():
+            raise RuntimeError('Cannot route circuit on disconnected qudits.')
+
+        if 'initial_mapping' in data:
+            pi = copy.deepcopy(data['initial_mapping'])
+        else:
+            pi = [i for i in range(circuit.num_qudits)]
+
+        self.forward_pass(circuit, pi, subgraph, perm_data, modify_circuit=True)
+        if 'final_mapping' in data:
+            self._apply_perm(data['final_mapping'], pi)
+        data['final_mapping'] = pi
+        _logger.info(f'Finished routing with layout: {str(pi)}')
diff --git a/bqskit/passes/mapping/routing/sabre.py b/bqskit/passes/mapping/routing/sabre.py
index bb930b6d3..3179a033e 100644
--- a/bqskit/passes/mapping/routing/sabre.py
+++ b/bqskit/passes/mapping/routing/sabre.py
@@ -3,9 +3,9 @@
 
 import copy
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.passes.mapping.sabre import GeneralizedSabreAlgorithm
 
@@ -20,7 +20,7 @@ class GeneralizedSabreRoutingPass(BasePass, GeneralizedSabreAlgorithm):
     See :class:`GeneralizedSabreAlgorithm` for more info.
     """
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         subgraph = self.get_connectivity(circuit, data)
         if not subgraph.is_fully_connected():
diff --git a/bqskit/passes/mapping/sabre.py b/bqskit/passes/mapping/sabre.py
index 83f421e82..d8977b93f 100644
--- a/bqskit/passes/mapping/sabre.py
+++ b/bqskit/passes/mapping/sabre.py
@@ -9,7 +9,7 @@
 import numpy as np
 
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import SwapGate
+from bqskit.ir.gates.constant.swap import SwapGate
 from bqskit.ir.operation import Operation
 from bqskit.ir.point import CircuitPoint
 from bqskit.qis.graph import CouplingGraph
diff --git a/bqskit/passes/mapping/setmodel.py b/bqskit/passes/mapping/setmodel.py
index 36e4435cd..95294df47 100644
--- a/bqskit/passes/mapping/setmodel.py
+++ b/bqskit/passes/mapping/setmodel.py
@@ -2,10 +2,10 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
 from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 _logger = logging.getLogger(__name__)
@@ -27,12 +27,10 @@ def __init__(self, model: MachineModel) -> None:
 
         self.model = model
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
-        if 'machine_model' in data:
-            _logger.info('Overriding existing machine model.')
-
         if self.model.num_qudits < circuit.num_qudits:
             raise RuntimeError('Machine model is too small for circuit.')
 
-        data['machine_model'] = self.model
+        data.model = self.model  # Update Model
+        data.placement = list(range(circuit.num_qudits))  # Reset placement
diff --git a/bqskit/passes/mapping/topology.py b/bqskit/passes/mapping/topology.py
new file mode 100644
index 000000000..9ef146ac0
--- /dev/null
+++ b/bqskit/passes/mapping/topology.py
@@ -0,0 +1,279 @@
+"""This module implements the SubtopologySelectionPass pass."""
+from __future__ import annotations
+
+import copy
+import itertools as it
+from typing import Any
+from typing import Iterable
+from typing import Sequence
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.control.foreach import ForEachBlockPass
+from bqskit.qis.graph import CouplingGraph
+from bqskit.utils.typing import is_integer
+
+
+def powerset(iterable: Iterable[Any]) -> Iterable[Any]:
+    """Powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"""
+    s = list(iterable)
+    all_combos = (it.combinations(s, r) for r in range(len(s) + 1))
+    return it.chain.from_iterable(all_combos)
+
+
+def all_coupling_graphs_of_size(n: int) -> list[CouplingGraph]:
+    """Calculates all valid coupling graphs with `n` qudits."""
+    if not is_integer(n):
+        raise TypeError(f'Expected integer for n, got {type(n)}.')
+
+    if n <= 1:
+        raise ValueError(f'Smallest valid graph is 2, got {n}.')
+
+    graphs: list[list[list[tuple[int, int]]]] = [[[], [(0, 1)]]]
+
+    while len(graphs) + 1 < n:
+        i = len(graphs) + 1
+        extended_graph_list = []
+        for graph in graphs[-1]:
+            for nodes in powerset(range(i)):
+                edges = copy.deepcopy(graph)
+
+                for node in nodes:
+                    edges.append((node, i))
+
+                extended_graph_list.append(edges)
+        graphs.append(extended_graph_list)
+
+    to_ret = []
+    for g in graphs[-1]:
+        cg = CouplingGraph(g, n)
+        if cg.is_fully_connected():
+            to_ret.append(cg)
+
+    return to_ret
+
+
+class GraphDAGNode():
+    """A node in the GraphDAG."""
+
+    def __init__(self, graph_id: int):
+        self.indices: list[int] = [graph_id]
+        self.predecessors: list[GraphDAGNode] = []
+        self.successors: list[GraphDAGNode] = []
+
+    def peek_index(self) -> int:
+        """Provide index of this kind of graph in list."""
+        return self.indices[0]
+
+
+class GraphDAG():
+    """
+    DAG for organizing the embeddability relations of graphs.
+
+    The DAG is organized so that a node is guaranteed to be embedded within all
+    of its successor nodes.
+    """
+
+    def __init__(self, graph_list: Sequence[CouplingGraph]):
+        self.roots: list[GraphDAGNode] = []
+        self.leafs: list[GraphDAGNode] = []
+        self.graph_list = list(graph_list)
+        if not self.graph_list[0]._edges == set():
+            self.graph_list.insert(0, CouplingGraph([]))
+        self._create_DAG()
+        self._insert_node(0)
+
+    def _create_DAG(self) -> None:
+        for graph_id in range(len(self.graph_list)):
+            self._insert_node(graph_id)
+
+    def _forward_path_exists(self, node_a: Any, node_b: Any) -> bool:
+        """Return True if a forward path exists from node_a to node_b."""
+        if node_a is node_b or node_b in node_a.successors:
+            return True
+        to_check = node_a.successors.copy()
+        for node in to_check:
+            if node_b in node.successors:
+                return True
+            else:
+                to_check.extend(node.successors)
+        return False
+
+    def _insert_node(self, graph_id: int) -> None:
+        """
+        Insert a new node corresponding to the graph `graph_list[graph_id]` into
+        the DAG.
+
+        Append it to the "least embeddable" graph that is still embedded within
+        it.
+        """
+        node = GraphDAGNode(graph_id)
+        if len(self.roots) == 0:
+            self.roots.append(node)
+            self.leafs.append(node)
+            return
+
+        graph = self.graph_list[graph_id]
+        nodes_to_check = self.leafs.copy()
+
+        # Check for node to be predessor
+        for other_node in nodes_to_check:
+            other_graph = self.graph_list[
+                other_node.peek_index()
+            ]
+
+            if other_graph.is_embedded_in(graph):
+                # Handle isomorphic case
+                if graph.is_embedded_in(other_graph):
+                    other_node.indices.append(graph_id)
+                    return
+                if not self._forward_path_exists(other_node, node):
+                    other_node.successors.append(node)
+                    node.predecessors.append(other_node)
+
+                # Check if we were at a leaf
+                if other_node in self.leafs:
+                    self.leafs.remove(other_node)
+                if node not in self.leafs:
+                    self.leafs.append(node)
+            else:
+                # Only check predecessors if node is not embedded in
+                # leaf and there is not already a direct forward path
+                # to the node
+                nodes_to_check += other_node.predecessors
+
+    def get_embedded_indices(
+        self,
+        graphs: list[CouplingGraph] | CouplingGraph,
+    ) -> list[int]:
+        """Given a list of graphs, return the indices of subgraphs in the
+        `self.graph_list` list that are embedded in a subgraph in `graphs`."""
+        embedded_indices: set[int] = set()
+        if isinstance(graphs, CouplingGraph):
+            graphs = [graphs]
+
+        for graph in graphs:
+            # Check least embeddable graphs first
+            to_check: list[GraphDAGNode] = self.leafs.copy()
+            for node in to_check:
+                subgraph = self.graph_list[node.peek_index()]
+                if not subgraph.is_embedded_in(graph):
+                    # If not embedded, check predecessors
+                    to_check.extend(node.predecessors)
+                else:
+                    # If embedded, add node's indices and all predecessors
+                    new_id = node.peek_index()
+                    if new_id not in embedded_indices:
+                        embedded_indices = embedded_indices.union(
+                            set(node.indices),
+                        )
+                        preds = node.predecessors
+                        for p in preds:
+                            if p.peek_index() not in embedded_indices:
+                                embedded_indices = embedded_indices.union(
+                                    set(p.indices),
+                                )
+                                preds.extend(p.predecessors)
+        return sorted(list(embedded_indices))
+
+
+def filter_compatible_subgraphs(
+    candidate_subgraphs: Sequence[CouplingGraph],
+    machine: MachineModel,
+    blocksize: int | None = None,
+) -> list[CouplingGraph]:
+    """
+    Filter the candidate subgraphs, returning the ones appearing in `machine`.
+
+    Given a list of candidate synthesis subgraphs and a MachineModel with a
+    valid defined CouplingGraph, return a list of candidate subgraphs that are
+    embedded within the machine.
+
+    Arguments:
+        candidate_subgraphs (list[CouplingGraph]): A list containing subgraphs
+            that should be checked for embeddability within some MachineModel.
+
+        machine (MachineModel): A MachineModel with a valid and specified
+            CouplingGraph.
+
+        blocksize (int | None): Maximum desired subgraph size. If unspecified,
+            the entire CouplingGraph will be searched. It is highly recommended
+            that the blocksize be provided for performance reasons.
+            (Default: None)
+
+    Returns:
+        (list[CouplingGraph]): A list of coupling graphs that are both in the
+            `candidate_subgraphs` list and the `machine`'s CouplingGraph.
+
+    Raises:
+        ValueError: If the CouplingGraph of the `machine` variable is either
+            invalid or unspecified.
+    """
+    graph = machine.coupling_graph
+    if blocksize is None:
+        blocksize = graph.num_qudits
+
+    # Sort graphs by number of edge
+    locations = graph.get_subgraphs_of_size(blocksize)
+    induced_subgraphs: list[CouplingGraph] = sorted(
+        (
+            CouplingGraph(graph.get_induced_subgraph(l)).relabel_subgraph()
+            for l in locations
+        ),
+        key=lambda x: -len(x),
+    )
+    candidates = sorted(candidate_subgraphs, key=lambda x: len(x))
+    dag = GraphDAG(candidates)
+    indices_of_interest = dag.get_embedded_indices(induced_subgraphs)
+
+    to_return = [
+        candidates[i] for i in range(len(candidates))
+        if i in indices_of_interest
+    ]
+    # Do not return empty set used as root
+    if to_return[0]._edges == set():
+        return to_return[1:]
+    return to_return
+
+
+class SubtopologySelectionPass(BasePass):
+    """Pass that selects necessary subtopologies from the model."""
+
+    key = ForEachBlockPass.pass_down_key_prefix + 'sub_topologies'
+
+    def __init__(self, block_size: int) -> None:
+        """
+        Construct a SubtopologySelectionPass.
+
+        Args:
+            block_size (int): The max block size to select subtopologies for.
+
+        Raises:
+            ValueError: If block_size is <= 1.
+        """
+        if not is_integer(block_size):
+            raise TypeError(f'Expected integer, got {type(block_size)}.')
+
+        if block_size <= 1:
+            raise ValueError(f'Expected integer > 1, got {block_size}.')
+
+        self.block_size = block_size
+
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        """Perform the pass's operation, see :class:`BasePass` for more."""
+        model = data.model
+        n = model.num_qudits
+        cg = model.coupling_graph
+        all_to_all = len(cg._edges) == n * (n - 1) / 2
+
+        tops = {}
+        for i in range(2, self.block_size + 1):
+            if all_to_all:
+                tops[i] = [CouplingGraph.all_to_all(i)]
+            else:
+                all_tops = all_coupling_graphs_of_size(i)
+                tops[i] = filter_compatible_subgraphs(all_tops, model, i)
+
+        data[self.key] = tops
diff --git a/bqskit/passes/measure.py b/bqskit/passes/measure.py
index d0a218fe7..9d2dd9869 100644
--- a/bqskit/passes/measure.py
+++ b/bqskit/passes/measure.py
@@ -3,9 +3,9 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.measure import MeasurementPlaceholder
 from bqskit.ir.point import CircuitPoint
@@ -17,7 +17,7 @@ class ExtractMeasurements(BasePass):
 
     key = '__measurement_data__'
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         _logger.info('Extracting measurements from circuit.')
         points_to_remove: list[CircuitPoint] = []
@@ -41,7 +41,7 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
 class RestoreMeasurements(BasePass):
     """A pass that restores the measurements in a circuit."""
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         _logger.info('Restoring measurements to circuit.')
         if ExtractMeasurements.key in data:
diff --git a/bqskit/passes/noop.py b/bqskit/passes/noop.py
index 9d946ef5d..1d8c1d764 100644
--- a/bqskit/passes/noop.py
+++ b/bqskit/passes/noop.py
@@ -1,15 +1,14 @@
 """This module implements the NOOPPass class."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 
 class NOOPPass(BasePass):
     """A pass that does not perform any operation."""
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         return
diff --git a/bqskit/passes/partitioning/cluster.py b/bqskit/passes/partitioning/cluster.py
index 8e3c7f193..1fc0ca0c3 100644
--- a/bqskit/passes/partitioning/cluster.py
+++ b/bqskit/passes/partitioning/cluster.py
@@ -2,11 +2,11 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 import numpy as np
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.utils.typing import is_integer
 
@@ -61,7 +61,7 @@ def __init__(self, block_size: int = 3, num_points: int = 8) -> None:
         self.block_size = block_size
         self.num_points = num_points
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         if self.block_size > circuit.num_qudits:
diff --git a/bqskit/passes/partitioning/greedy.py b/bqskit/passes/partitioning/greedy.py
index 3ede6b2f4..689c9196f 100644
--- a/bqskit/passes/partitioning/greedy.py
+++ b/bqskit/passes/partitioning/greedy.py
@@ -4,9 +4,9 @@
 import bisect
 import logging
 import warnings
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.interval import CycleInterval
@@ -59,7 +59,7 @@ def __init__(self, block_size: int = 3) -> None:
 
         self.block_size = block_size
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         if self.block_size > circuit.num_qudits:
diff --git a/bqskit/passes/partitioning/quick.py b/bqskit/passes/partitioning/quick.py
index 537133989..b2e2ef69e 100644
--- a/bqskit/passes/partitioning/quick.py
+++ b/bqskit/passes/partitioning/quick.py
@@ -2,13 +2,14 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 from typing import cast
 from typing import Sequence
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
+from bqskit.ir.gates.barrier import BarrierPlaceholder
+from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.location import CircuitLocation
 from bqskit.ir.point import CircuitPoint
 from bqskit.utils.typing import is_integer
@@ -48,7 +49,7 @@ def __init__(self, block_size: int = 3) -> None:
 
         self.block_size = block_size
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """
         Partition gates in a circuit into a series of CircuitGates.
 
@@ -60,20 +61,6 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
         # The partitioned circuit that will be built and returned
         partitioned_circuit = Circuit(circuit.num_qudits, circuit.radixes)
 
-        # If block size >= circuit size, return the circuit as a block
-        if self.block_size >= circuit.num_qudits:
-            _logger.debug(
-                'Configured block size is greater than or equal to'
-                'circuit size; blocking entire circuit.',
-            )
-            partitioned_circuit.append_circuit(
-                circuit,
-                list(range(circuit.num_qudits)),
-                True,
-            )
-            circuit.become(partitioned_circuit)
-            return
-
         # Tracks bins with at least one active_qudit
         active_bins: list[Bin | None] = [
             None for _ in range(circuit.num_qudits)
@@ -125,11 +112,15 @@ def process_pending_bins() -> None:
                         loc = list(sorted(bin.qudits))
 
                         # Merge previously placed blocks if possible
-                        merging = True
+                        merging = not isinstance(bin, BarrierBin)
                         while merging:
                             merging = False
                             for p in partitioned_circuit.rear:
-                                qudits = list(partitioned_circuit[p].location)
+                                op = partitioned_circuit[p]
+                                if isinstance(op.gate, BarrierPlaceholder):
+                                    # Don't merge through barriers
+                                    continue
+                                qudits = list(op.location)
 
                                 # if qudits is subset of loc
                                 if all(q in loc for q in qudits):
@@ -161,7 +152,7 @@ def process_pending_bins() -> None:
                         partitioned_circuit.append_circuit(
                             subc,
                             loc,
-                            True,
+                            not isinstance(bin, BarrierBin),
                             True,
                         )
                         for qudit in bin.qudits:
@@ -183,6 +174,16 @@ def process_pending_bins() -> None:
                 if active_bins[q] is not None
             })
 
+            # Barriers close all overlapping bins
+            if isinstance(op.gate, BarrierPlaceholder):
+                for bin in overlapping_bins:
+                    if close_bin_qudits(bin, location, cycle):
+                        num_closed += 1
+
+                # Track the barrier to restore it in partitioned circuit
+                pending_bins.append(BarrierBin(point, location))
+                continue
+
             # Get all the currently active bins that can have op added to them
             admissible_bins = [
                 bin for bin in overlapping_bins
@@ -340,4 +341,17 @@ def can_accommodate(self, loc: CircuitLocation, block_size: int) -> bool:
         return overlapping_qudits_are_active and not too_big
 
 
-1
+class BarrierBin(Bin):
+    """A special bin made to mark and preserve barrier location."""
+
+    def __init__(self, point: CircuitPoint, location: CircuitLocation) -> None:
+        """Initialize a BarrierBin with the point and location of a barrier."""
+        super().__init__()
+
+        # Add the barrier
+        self.add_op(point, location)
+
+        # Close the bin
+        for q in location:
+            self.active_qudits.remove(q)
+            self.ends[q] = point.cycle
diff --git a/bqskit/passes/partitioning/scan.py b/bqskit/passes/partitioning/scan.py
index c7486502e..e760ede42 100644
--- a/bqskit/passes/partitioning/scan.py
+++ b/bqskit/passes/partitioning/scan.py
@@ -2,7 +2,6 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 from typing import Callable
 from typing import Iterator
 from typing import Sequence
@@ -10,6 +9,7 @@
 
 from bqskit.compiler.basepass import BasePass
 from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.operation import Operation
@@ -73,7 +73,7 @@ def __init__(
         self.block_size = block_size
         self.scoring_fn = scoring_fn
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         if self.block_size > circuit.num_qudits:
diff --git a/bqskit/passes/partitioning/single.py b/bqskit/passes/partitioning/single.py
index a59645c36..ce9a431e2 100644
--- a/bqskit/passes/partitioning/single.py
+++ b/bqskit/passes/partitioning/single.py
@@ -1,10 +1,10 @@
 """This module implements the GroupSingleQuditGatePass."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates.barrier import BarrierPlaceholder
 from bqskit.ir.region import CircuitRegion
 
 
@@ -15,7 +15,7 @@ class GroupSingleQuditGatePass(BasePass):
     This pass groups together consecutive single-qudit gates.
     """
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Go through each qudit individually
@@ -28,7 +28,11 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                 if circuit.is_point_idle((c, q)):
                     continue
 
-                if circuit[c, q].num_qudits == 1:
+                op = circuit[c, q]
+                if (
+                    op.num_qudits == 1
+                    and not isinstance(op.gate, BarrierPlaceholder)
+                ):
                     if region_start is None:
                         region_start = c
                 else:
diff --git a/bqskit/passes/processing/__init__.py b/bqskit/passes/processing/__init__.py
index 5cd7c4104..2d7712107 100644
--- a/bqskit/passes/processing/__init__.py
+++ b/bqskit/passes/processing/__init__.py
@@ -6,13 +6,11 @@
 from bqskit.passes.processing.rebase import Rebase2QuditGatePass
 from bqskit.passes.processing.scan import ScanningGateRemovalPass
 from bqskit.passes.processing.substitute import SubstitutePass
-from bqskit.passes.processing.window import WindowOptimizationPass
 
 __all__ = [
     'ExhaustiveGateRemovalPass',
     'IterativeScanningGateRemovalPass',
     'ScanningGateRemovalPass',
     'SubstitutePass',
-    'WindowOptimizationPass',
     'Rebase2QuditGatePass',
 ]
diff --git a/bqskit/passes/processing/exhaustive.py b/bqskit/passes/processing/exhaustive.py
index f2cb7aa0c..64656cf67 100644
--- a/bqskit/passes/processing/exhaustive.py
+++ b/bqskit/passes/processing/exhaustive.py
@@ -8,11 +8,13 @@
 import numpy as np
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.operation import Operation
 from bqskit.ir.opt.cost.functions import HilbertSchmidtResidualsGenerator
 from bqskit.ir.opt.cost.generator import CostFunctionGenerator
 from bqskit.ir.structure import CircuitStructure
+from bqskit.runtime import get_runtime
 from bqskit.utils.typing import is_real_number
 _logger = logging.getLogger(__name__)
 
@@ -104,8 +106,12 @@ def __init__(
         }
         self.instantiate_options.update(instantiate_options)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
+        instantiate_options = self.instantiate_options.copy()
+        if 'seed' not in instantiate_options:
+            instantiate_options['seed'] = data.seed
+
         _logger.debug('Starting exhaustive gate removal.')
 
         target = self.get_target(circuit, data)
@@ -137,12 +143,11 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                         circuits_seen.add(structure)
 
             # Instantiate them all
-            instantiated_circuits = self.execute(
-                data,
+            instantiated_circuits = await get_runtime().map(
                 Circuit.instantiate,
                 expanded_circuits,
                 target=target,
-                **self.instantiate_options,
+                **instantiate_options,
             )
 
             # Process them
diff --git a/bqskit/passes/processing/rebase.py b/bqskit/passes/processing/rebase.py
index 1df832182..4182b0293 100644
--- a/bqskit/passes/processing/rebase.py
+++ b/bqskit/passes/processing/rebase.py
@@ -6,12 +6,14 @@
 from typing import Sequence
 
 from bqskit.compiler.basepass import BasePass
-from bqskit.ir import Gate
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import U3Gate
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
 from bqskit.ir.opt.cost.functions import HilbertSchmidtResidualsGenerator
 from bqskit.ir.opt.cost.generator import CostFunctionGenerator
 from bqskit.ir.point import CircuitPoint as Point
+from bqskit.runtime import get_runtime
 from bqskit.utils.typing import is_integer
 from bqskit.utils.typing import is_real_number
 from bqskit.utils.typing import is_sequence
@@ -151,8 +153,11 @@ def __init__(
         self.sq = single_qudit_gate
         self.generate_new_gate_templates()
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
+        instantiate_options = self.instantiate_options.copy()
+        if 'seed' not in instantiate_options:
+            instantiate_options['seed'] = data.seed
         _logger.debug(f'Rebasing gates from {self.gates} to {self.ngates}.')
 
         target = self.get_target(circuit, data)
@@ -163,6 +168,10 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
             prev_count = circuit.count(g)
 
             while g in circuit.gate_set:
+                # Change the seed every iteration to prevent stalls
+                if instantiate_options['seed'] is not None:
+                    instantiate_options['seed'] += 1
+
                 # Check if we made progress from last loop
                 gates_left = circuit.count(g)
                 if prev_count == gates_left:
@@ -186,12 +195,11 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                     circuit_copy.replace_with_circuit(point, self.overdrive)
                     circuits_with_new_gate.append(circuit_copy)
 
-                instantiated_circuits = self.execute(
-                    data,
+                instantiated_circuits = await get_runtime().map(
                     Circuit.instantiate,
                     circuits_with_new_gate,
                     target=target,
-                    **self.instantiate_options,
+                    **instantiate_options,
                 )
 
                 dists = [self.cost(c, target) for c in instantiated_circuits]
@@ -277,7 +285,7 @@ def generate_new_gate_templates(self) -> None:
         self.circs = []
         self.counts = []
 
-        circ = Circuit(2)
+        circ = Circuit(2, self.ngates[0].radixes)
         circ.append_gate(self.sq, 0)
         circ.append_gate(self.sq, 1)
         self.circs.append(circ)
@@ -285,7 +293,7 @@ def generate_new_gate_templates(self) -> None:
 
         for g in self.ngates:
             for i in range(1, self.max_depth + 1):
-                circ = Circuit(2)
+                circ = Circuit(2, self.ngates[0].radixes)
                 circ.append_gate(self.sq, 0)
                 circ.append_gate(self.sq, 1)
 
diff --git a/bqskit/passes/processing/scan.py b/bqskit/passes/processing/scan.py
index 61cc89bc6..feee2fc20 100644
--- a/bqskit/passes/processing/scan.py
+++ b/bqskit/passes/processing/scan.py
@@ -6,6 +6,7 @@
 from typing import Callable
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.operation import Operation
 from bqskit.ir.opt.cost.functions import HilbertSchmidtResidualsGenerator
@@ -90,11 +91,16 @@ def __init__(
         self.instantiate_options: dict[str, Any] = {
             'dist_tol': self.success_threshold,
             'min_iters': 100,
+            'cost_fn_gen': self.cost,
         }
         self.instantiate_options.update(instantiate_options)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
+        instantiate_options = self.instantiate_options.copy()
+        if 'seed' not in instantiate_options:
+            instantiate_options['seed'] = data.seed
+
         start = 'left' if self.start_from_left else 'right'
         _logger.debug(f'Starting scanning gate removal on the {start}.')
 
@@ -120,13 +126,7 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                 cycle -= idx_shift
 
             working_copy.pop((cycle, op.location[0]))
-            working_copy = self.execute(
-                data,
-                Circuit.instantiate,
-                [working_copy],
-                target=target,
-                **self.instantiate_options,
-            )[0]
+            working_copy.instantiate(target, **instantiate_options)
 
             if self.cost(working_copy, target) < self.success_threshold:
                 _logger.debug('Successfully removed operation.')
diff --git a/bqskit/passes/processing/substitute.py b/bqskit/passes/processing/substitute.py
index dbce50344..eda3743c3 100644
--- a/bqskit/passes/processing/substitute.py
+++ b/bqskit/passes/processing/substitute.py
@@ -7,6 +7,7 @@
 from typing import Callable
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
 from bqskit.ir.operation import Operation
@@ -91,8 +92,11 @@ def __init__(
         }
         self.instantiate_options.update(instantiate_options)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
+        instantiate_options = self.instantiate_options.copy()
+        if 'seed' not in instantiate_options:
+            instantiate_options['seed'] = data.seed
 
         # Collect locations in circuit where self.init_gate exists
         points = [
@@ -125,13 +129,7 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                 _logger.debug(f'Trying location: {loc}')
                 circuit_copy = circuit.copy()
                 circuit_copy.replace_gate(point, self.gate, loc)
-                circuit_copy = self.execute(
-                    data,
-                    Circuit.instantiate,
-                    [circuit_copy],
-                    target=target,
-                    **self.instantiate_options,
-                )[0]
+                circuit_copy.instantiate(target, **instantiate_options)
 
                 if self.cost(circuit_copy, target) < self.success_threshold:
                     _logger.info('Successfully substituted operation.')
diff --git a/bqskit/passes/processing/window.py b/bqskit/passes/processing/window.py
deleted file mode 100644
index 43efac627..000000000
--- a/bqskit/passes/processing/window.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""This module implements the WindowOptimizationPass."""
-from __future__ import annotations
-
-import logging
-import warnings
-from typing import Any
-from typing import Callable
-
-import numpy as np
-
-from bqskit.compiler.basepass import BasePass
-from bqskit.ir.circuit import Circuit
-from bqskit.passes.synthesis.qsearch import QSearchSynthesisPass
-from bqskit.passes.synthesis.synthesis import SynthesisPass
-from bqskit.utils.typing import is_integer
-from bqskit.utils.typing import is_iterable
-
-_logger = logging.getLogger(__name__)
-
-
-class WindowOptimizationPass(BasePass):
-    """
-    The WindowOptimizationPass class.
-
-    Previously run passes can leave window markers. This pass will select a
-    window around that marker and resynthesize, potentially for a depth
-    reduction.
-    """
-
-    def __init__(
-        self,
-        window_size: int = 9,
-        synthesispass: SynthesisPass = QSearchSynthesisPass(),
-        replace_filter: Callable[[Circuit, Circuit], bool] | None = None,
-    ) -> None:
-        """
-        Construct a WindowOptimizationPass.
-
-        Args:
-            window_size (int): The size of the window to surround each
-                marker. (Default: 9)
-
-            synthesispass (SynthesisPass): The configured synthesis algorithm
-                to use during window synthesis.
-
-            replace_filter (Callable[[Circuit, Circuit], bool] | None):
-                A predicate that determines if the synthesis result should
-                replace the original operation. The first parameter is the
-                output from synthesis and the second parameter is the original
-                window. If this returns true, the newly synthesized circuit
-                will replace the original circuit. Defaults to always replace.
-
-        Raises:
-            ValueError: If `window_size` is <= 1.
-
-        Note:
-            This has been deprecated and will removed in a future update.
-            See :class:`ScanningGateRemovalPass` for a replacement
-            post-synthesis optimization pass.
-        """
-        warnings.warn(
-            'WindowOptimizationPass has been deprecated and will be '
-            'removed in a future update. See ScanningGateRemovalpass '
-            'for a replacement post-synthesis optimization pass.',
-            DeprecationWarning,
-        )
-
-        if not is_integer(window_size):
-            raise TypeError(
-                'Expected integer for success_threshold'
-                ', got %s' % type(window_size),
-            )
-
-        if window_size <= 1:
-            raise ValueError(
-                'Expected integer greater than 1 for window_size'
-                ', got %d.' % window_size,
-            )
-
-        if not isinstance(synthesispass, SynthesisPass):
-            raise TypeError(
-                'Expected SynthesisPass, got %s.' % type(synthesispass),
-            )
-
-        self.replace_filter = replace_filter or default_replace_filter
-
-        if not callable(self.replace_filter):
-            raise TypeError(
-                'Expected callable method for replace_filter'
-                ', got %s.' % type(self.replace_filter),
-            )
-
-        self.window_size = window_size
-        self.synthesispass = synthesispass
-
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-        """Perform the pass's operation, see :class:`BasePass` for more."""
-
-        # Check data for windows markers
-        if 'window_markers' not in data:
-            _logger.warning('Did not find any window markers.')
-            return
-
-        window_markers = data['window_markers']
-        _logger.debug('Found window_markers: %s.' % str(window_markers))
-
-        if not is_iterable(window_markers):
-            _logger.debug('Invalid type for window_markers.')
-            return
-
-        if not all(is_integer(marker) for marker in window_markers):
-            _logger.debug('Invalid type for window_markers.')
-            return
-
-        # Resynthesis each window
-        index_shift = 0
-        for marker in window_markers:
-            marker -= index_shift
-
-            # Slice window
-            begin_cycle = int(marker - self.window_size // 2)
-            end_cycle = int(marker + np.ceil(self.window_size / 2))
-
-            if begin_cycle < 0:
-                begin_cycle = 0
-
-            if end_cycle > circuit.num_cycles:
-                end_cycle = circuit.num_cycles - 1
-
-            window = Circuit(circuit.num_qudits, circuit.radixes)
-            window.extend(circuit[begin_cycle:end_cycle])
-
-            _logger.info(
-                'Resynthesizing window from cycle '
-                f'{begin_cycle} to {end_cycle}.',
-            )
-
-            # Synthesize
-            utry = window.get_unitary()
-            new_window = self.synthesispass.synthesize(utry, data)
-
-            # Replace
-            if self.replace_filter(new_window, window):
-                _logger.debug('Replacing window with synthesized circuit.')
-
-                actual_window_size = end_cycle - begin_cycle
-                for _ in range(actual_window_size):
-                    circuit.pop_cycle(begin_cycle)
-
-                circuit.insert_circuit(
-                    begin_cycle,
-                    new_window,
-                    list(range(circuit.num_qudits)),
-                )
-
-                index_shift += actual_window_size - new_window.num_cycles
-
-
-def default_replace_filter(new_circuit: Circuit, og_window: Circuit) -> bool:
-    return True
diff --git a/bqskit/passes/rules/ch2cnot.py b/bqskit/passes/rules/ch2cnot.py
index 2daf582b6..7f008a2bc 100644
--- a/bqskit/passes/rules/ch2cnot.py
+++ b/bqskit/passes/rules/ch2cnot.py
@@ -1,16 +1,15 @@
 """This module implements the CHToCNOTPass."""
 from __future__ import annotations
 
-from typing import Any
-
 import numpy as np
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CHGate
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import RYGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.ch import CHGate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.parameterized.ry import RYGate
 from bqskit.ir.operation import Operation
 
 
@@ -29,7 +28,7 @@ def __init__(self) -> None:
         circuit.append_gate(RYGate(), 1, [-np.pi / 4])
         self.cg = CircuitGate(circuit)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Find all cnots
diff --git a/bqskit/passes/rules/cnot2ch.py b/bqskit/passes/rules/cnot2ch.py
index 89b3974c3..de3512a75 100644
--- a/bqskit/passes/rules/cnot2ch.py
+++ b/bqskit/passes/rules/cnot2ch.py
@@ -1,16 +1,15 @@
 """This module implements the CNOTToCHPass."""
 from __future__ import annotations
 
-from typing import Any
-
 import numpy as np
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CHGate
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import RYGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.ch import CHGate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.parameterized.ry import RYGate
 from bqskit.ir.operation import Operation
 
 
@@ -29,7 +28,7 @@ def __init__(self) -> None:
         circuit.append_gate(RYGate(), 1, [np.pi / 4])
         self.cg = CircuitGate(circuit)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Find all cnots
diff --git a/bqskit/passes/rules/cnot2cy.py b/bqskit/passes/rules/cnot2cy.py
index 45f481883..8b2f0b6e2 100644
--- a/bqskit/passes/rules/cnot2cy.py
+++ b/bqskit/passes/rules/cnot2cy.py
@@ -1,15 +1,14 @@
 """This module implements the CNOTToCYPass."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import CYGate
-from bqskit.ir.gates import SdgGate
-from bqskit.ir.gates import SGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.constant.cy import CYGate
+from bqskit.ir.gates.constant.s import SGate
+from bqskit.ir.gates.constant.sdg import SdgGate
 from bqskit.ir.operation import Operation
 
 
@@ -28,7 +27,7 @@ def __init__(self) -> None:
         circuit.append_gate(SdgGate(), 1)
         self.cg = CircuitGate(circuit)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Find all cnots
diff --git a/bqskit/passes/rules/cnot2cz.py b/bqskit/passes/rules/cnot2cz.py
index 106b5ee4b..64a2a1386 100644
--- a/bqskit/passes/rules/cnot2cz.py
+++ b/bqskit/passes/rules/cnot2cz.py
@@ -1,14 +1,13 @@
 """This module implements the CNOTToCZPass."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import CZGate
-from bqskit.ir.gates import HGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.constant.cz import CZGate
+from bqskit.ir.gates.constant.h import HGate
 from bqskit.ir.operation import Operation
 
 
@@ -27,7 +26,7 @@ def __init__(self) -> None:
         circuit.append_gate(HGate(), 1)
         self.cg = CircuitGate(circuit)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Find all cnots
diff --git a/bqskit/passes/rules/cy2cnot.py b/bqskit/passes/rules/cy2cnot.py
index 6b9d599a4..4abd8e5e0 100644
--- a/bqskit/passes/rules/cy2cnot.py
+++ b/bqskit/passes/rules/cy2cnot.py
@@ -1,15 +1,14 @@
 """This module implements the CYToCNOTPass."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import CYGate
-from bqskit.ir.gates import SdgGate
-from bqskit.ir.gates import SGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.constant.cy import CYGate
+from bqskit.ir.gates.constant.s import SGate
+from bqskit.ir.gates.constant.sdg import SdgGate
 from bqskit.ir.operation import Operation
 
 
@@ -28,7 +27,7 @@ def __init__(self) -> None:
         circuit.append_gate(SGate(), 1)
         self.cg = CircuitGate(circuit)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Find all cnots
diff --git a/bqskit/passes/rules/cz2cnot.py b/bqskit/passes/rules/cz2cnot.py
index 144a7ac3c..1068988cb 100644
--- a/bqskit/passes/rules/cz2cnot.py
+++ b/bqskit/passes/rules/cz2cnot.py
@@ -1,14 +1,13 @@
 """This module implements the CZToCNOTPass."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import CZGate
-from bqskit.ir.gates import HGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.constant.cz import CZGate
+from bqskit.ir.gates.constant.h import HGate
 from bqskit.ir.operation import Operation
 
 
@@ -27,7 +26,7 @@ def __init__(self) -> None:
         circuit.append_gate(HGate(), 1)
         self.cg = CircuitGate(circuit)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Find all cnots
diff --git a/bqskit/passes/rules/swap2cnot.py b/bqskit/passes/rules/swap2cnot.py
index d44ec3490..8296721e0 100644
--- a/bqskit/passes/rules/swap2cnot.py
+++ b/bqskit/passes/rules/swap2cnot.py
@@ -1,13 +1,12 @@
 """This module implements the SwapToCNOTPass."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import SwapGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.constant.swap import SwapGate
 from bqskit.ir.operation import Operation
 
 
@@ -26,7 +25,7 @@ def __init__(self) -> None:
         circuit.append_gate(CNOTGate(), (1, 0))
         self.cg = CircuitGate(circuit)
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Find all cnots
diff --git a/bqskit/passes/rules/u3.py b/bqskit/passes/rules/u3.py
index ea2d8173a..096c50918 100644
--- a/bqskit/passes/rules/u3.py
+++ b/bqskit/passes/rules/u3.py
@@ -1,11 +1,10 @@
 """This module implements the U3Decomposition."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import U3Gate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
 
 
 class U3Decomposition(BasePass):
@@ -15,7 +14,7 @@ class U3Decomposition(BasePass):
     Convert a single-qubit circuit to U3 gate.
     """
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         if circuit.num_qudits != 1:
@@ -30,5 +29,5 @@ def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
 
         utry = circuit.get_unitary()
         new_circuit = Circuit(1)
-        new_circuit.append_gate(U3Gate(), 0, U3Gate.calc_params(utry))
+        new_circuit.append_gate(U3Gate(), 0, U3Gate().calc_params(utry))
         circuit.become(new_circuit)
diff --git a/bqskit/passes/rules/zxzxz.py b/bqskit/passes/rules/zxzxz.py
index 9f85495bf..7ebd6e075 100644
--- a/bqskit/passes/rules/zxzxz.py
+++ b/bqskit/passes/rules/zxzxz.py
@@ -2,14 +2,14 @@
 from __future__ import annotations
 
 import cmath
-from typing import Any
 
 import numpy as np
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import RZGate
-from bqskit.ir.gates import SqrtXGate
+from bqskit.ir.gates.constant.sx import SqrtXGate
+from bqskit.ir.gates.parameterized.rz import RZGate
 
 
 class ZXZXZDecomposition(BasePass):
@@ -19,7 +19,7 @@ class ZXZXZDecomposition(BasePass):
     Convert a single-qubit circuit to ZXZXZ sequence.
     """
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         if circuit.num_qudits != 1:
diff --git a/bqskit/passes/search/frontier.py b/bqskit/passes/search/frontier.py
index e30caa939..629f1666d 100644
--- a/bqskit/passes/search/frontier.py
+++ b/bqskit/passes/search/frontier.py
@@ -9,6 +9,7 @@
 from bqskit.ir.circuit import Circuit
 from bqskit.passes.search.heuristic import HeuristicFunction
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
@@ -25,21 +26,21 @@ class Frontier:
 
     def __init__(
         self,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
         heuristic_function: HeuristicFunction,
     ) -> None:
         """
         Construct an empty frontier.
 
         Args:
-            target (UnitaryMatrix | StateVector): The target to pass to
-                the heuristic_function.
+            target (UnitaryMatrix | StateVector | StateSystem): The target to
+                pass to the heuristic_function.
 
             heuristic_function (HeuristicFunction): The heuristic used
                 to sort the Frontier.
         """
 
-        if not isinstance(target, (UnitaryMatrix, StateVector)):
+        if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
             raise TypeError(
                 'Expected unitary or state, got %s.' % type(target),
             )
diff --git a/bqskit/passes/search/generator.py b/bqskit/passes/search/generator.py
index 81d1f4eea..e9d081a87 100644
--- a/bqskit/passes/search/generator.py
+++ b/bqskit/passes/search/generator.py
@@ -2,10 +2,11 @@
 from __future__ import annotations
 
 import abc
-from typing import Any
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
@@ -20,15 +21,11 @@ class LayerGenerator(abc.ABC):
     @abc.abstractmethod
     def gen_initial_layer(
         self,
-        target: UnitaryMatrix | StateVector,
-        data: dict[str, Any],
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
     ) -> Circuit:
         """Generate the initial layer for search."""
 
     @abc.abstractmethod
-    def gen_successors(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any],
-    ) -> list[Circuit]:
+    def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
         """Generate the successors of a circuit node."""
diff --git a/bqskit/passes/search/generators/fourparam.py b/bqskit/passes/search/generators/fourparam.py
index b1a36d6b1..8e36b7fe1 100644
--- a/bqskit/passes/search/generators/fourparam.py
+++ b/bqskit/passes/search/generators/fourparam.py
@@ -2,17 +2,17 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
-from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import RXGate
-from bqskit.ir.gates import RYGate
-from bqskit.ir.gates import RZGate
-from bqskit.ir.gates import U3Gate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.parameterized.rx import RXGate
+from bqskit.ir.gates.parameterized.ry import RYGate
+from bqskit.ir.gates.parameterized.rz import RZGate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
 from bqskit.passes.search.generator import LayerGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 _logger = logging.getLogger(__name__)
 
@@ -33,8 +33,8 @@ class FourParamGenerator(LayerGenerator):
 
     def gen_initial_layer(
         self,
-        target: UnitaryMatrix | StateVector,
-        data: dict[str, Any],
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
     ) -> Circuit:
         """
         Generate the initial layer, see LayerGenerator for more.
@@ -43,7 +43,7 @@ def gen_initial_layer(
             ValueError: If `target` is not qubit only.
         """
 
-        if not isinstance(target, (UnitaryMatrix, StateVector)):
+        if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
             raise TypeError(
                 'Expected unitary or state, got %s.' % type(target),
             )
@@ -56,11 +56,7 @@ def gen_initial_layer(
             init_circuit.append_gate(U3Gate(), [i])
         return init_circuit
 
-    def gen_successors(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any],
-    ) -> list[Circuit]:
+    def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
         """
         Generate the successors of a circuit node.
 
@@ -75,13 +71,18 @@ def gen_successors(
             raise ValueError('Cannot expand a single-qudit circuit.')
 
         # Get the machine model
-        coupling_graph = BasePass.get_connectivity(circuit, data)
+        coupling_graph = data.connectivity
 
         # Generate successors
         successors = []
         for edge in coupling_graph:
+
+            if self.count_outer_cnots(circuit, edge) >= 3:
+                # No need to build circuits with more than 3 cnots in a row
+                continue
+
             successor = circuit.copy()
-            successor.append_gate(CNOTGate(), [edge[0], edge[1]])
+            successor.append_gate(CNOTGate(), edge)
             successor.append_gate(RYGate(), edge[0])
             successor.append_gate(RZGate(), edge[0])
             successor.append_gate(RYGate(), edge[1])
@@ -89,3 +90,38 @@ def gen_successors(
             successors.append(successor)
 
         return successors
+
+    def count_outer_cnots(self, circuit: Circuit, edge: tuple[int, int]) -> int:
+        """
+        Count how many uninterrupted 4-param cnot blocks are on `edge`.
+
+        This will count backwards from the right-side of the circuit and stop
+        when a cnot is encountered including other qudits.
+        """
+        rear_point = circuit._rear[edge[0]]
+        num_cx_seen = 0
+
+        if rear_point is None or rear_point.cycle != circuit.num_cycles - 1:
+            return num_cx_seen
+
+        while rear_point is not None:
+            rear_points = circuit.prev(rear_point)
+
+            if len(rear_points) == 0:
+                break
+
+            rear_point = rear_points.pop()
+
+            if circuit[rear_point].num_qudits == 1:
+                # Move past single-qubit gates
+                continue
+
+            cx_op = circuit[rear_point]
+
+            if cx_op.location != edge:
+                # If CX is on a different edge stop counting
+                break
+
+            num_cx_seen += 1
+
+        return num_cx_seen
diff --git a/bqskit/passes/search/generators/middleout.py b/bqskit/passes/search/generators/middleout.py
index ce88fc2c5..56f820ca4 100644
--- a/bqskit/passes/search/generators/middleout.py
+++ b/bqskit/passes/search/generators/middleout.py
@@ -3,9 +3,9 @@
 
 import itertools as it
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.passes.search.generators.seed import SeedLayerGenerator
 from bqskit.passes.search.generators.simple import SimpleLayerGenerator
@@ -23,11 +23,7 @@ def __init__(self) -> None:
         self.generated_circuits = set()
         super().__init__()
 
-    def gen_successors(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any],
-    ) -> list[Circuit]:
+    def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
         """
         Generate the successors of a circuit node.
 
diff --git a/bqskit/passes/search/generators/seed.py b/bqskit/passes/search/generators/seed.py
index bbc4b99cf..f8cf8fff4 100644
--- a/bqskit/passes/search/generators/seed.py
+++ b/bqskit/passes/search/generators/seed.py
@@ -2,12 +2,13 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.passes.search.generator import LayerGenerator
 from bqskit.passes.search.generators.simple import SimpleLayerGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 from bqskit.utils.typing import is_integer
 _logger = logging.getLogger(__name__)
@@ -53,8 +54,8 @@ def __init__(
 
     def gen_initial_layer(
         self,
-        target: UnitaryMatrix | StateVector,
-        data: dict[str, Any],
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
     ) -> Circuit:
         """
         Generate the initial layer, see LayerGenerator for more.
@@ -64,7 +65,7 @@ def gen_initial_layer(
                 `self.seed`.
         """
 
-        if not isinstance(target, (UnitaryMatrix, StateVector)):
+        if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
             raise TypeError(
                 'Expected unitary or state, got %s.' % type(target),
             )
@@ -76,11 +77,7 @@ def gen_initial_layer(
 
         return self.seed
 
-    def gen_successors(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any],
-    ) -> list[Circuit]:
+    def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
         """
         Generate the successors of a circuit node.
 
diff --git a/bqskit/passes/search/generators/simple.py b/bqskit/passes/search/generators/simple.py
index 83a062838..ca2823b14 100644
--- a/bqskit/passes/search/generators/simple.py
+++ b/bqskit/passes/search/generators/simple.py
@@ -2,15 +2,15 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
-from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import U3Gate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
 from bqskit.passes.search.generator import LayerGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 _logger = logging.getLogger(__name__)
 
@@ -143,8 +143,8 @@ def __init__(
 
     def gen_initial_layer(
         self,
-        target: UnitaryMatrix | StateVector,
-        data: dict[str, Any],
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
     ) -> Circuit:
         """
         Generate the initial layer, see LayerGenerator for more.
@@ -154,7 +154,7 @@ def gen_initial_layer(
                 `self.initial_layer_gate`.
         """
 
-        if not isinstance(target, (UnitaryMatrix, StateVector)):
+        if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
             raise TypeError(
                 'Expected unitary or state, got %s.' % type(target),
             )
@@ -170,11 +170,7 @@ def gen_initial_layer(
             init_circuit.append_gate(self.initial_layer_gate, [i])
         return init_circuit
 
-    def gen_successors(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any],
-    ) -> list[Circuit]:
+    def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
         """
         Generate the successors of a circuit node.
 
@@ -189,7 +185,7 @@ def gen_successors(
             raise ValueError('Cannot expand a single-qudit circuit.')
 
         # Get the coupling graph
-        coupling_graph = BasePass.get_connectivity(circuit, data)
+        coupling_graph = data.connectivity
 
         # Generate successors
         successors = []
diff --git a/bqskit/passes/search/generators/single.py b/bqskit/passes/search/generators/single.py
index 9ed1336ae..0b0afcfdd 100644
--- a/bqskit/passes/search/generators/single.py
+++ b/bqskit/passes/search/generators/single.py
@@ -2,12 +2,13 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
 from bqskit.passes.search.generator import LayerGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 _logger = logging.getLogger(__name__)
 
@@ -64,8 +65,8 @@ def __init__(self, gates: list[Gate], allow_repeats: bool = False) -> None:
 
     def gen_initial_layer(
         self,
-        target: UnitaryMatrix | StateVector,
-        data: dict[str, Any],
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
     ) -> Circuit:
         """
         Generate the initial layer, see LayerGenerator for more.
@@ -77,7 +78,7 @@ def gen_initial_layer(
             ValueError: If `target` is not single-qudit.
         """
 
-        if not isinstance(target, (UnitaryMatrix, StateVector)):
+        if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
             raise TypeError(
                 'Expected unitary or state, got %s.' % type(target),
             )
@@ -91,11 +92,7 @@ def gen_initial_layer(
         init_circuit = Circuit(target.num_qudits, target.radixes)
         return init_circuit
 
-    def gen_successors(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any],
-    ) -> list[Circuit]:
+    def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
         """
         Generate the successors of a circuit node.
 
diff --git a/bqskit/passes/search/generators/stair.py b/bqskit/passes/search/generators/stair.py
index e7aa66fdd..ec0f18aee 100644
--- a/bqskit/passes/search/generators/stair.py
+++ b/bqskit/passes/search/generators/stair.py
@@ -2,12 +2,13 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
 from bqskit.passes.search.generator import LayerGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
@@ -31,8 +32,8 @@ def __init__(self, gate: Gate) -> None:
 
     def gen_initial_layer(
         self,
-        target: UnitaryMatrix | StateVector,
-        data: dict[str, Any],
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
     ) -> Circuit:
         """
         Generate the initial layer, see LayerGenerator for more.
@@ -42,7 +43,7 @@ def gen_initial_layer(
                 `self.seed`.
         """
 
-        if not isinstance(target, (UnitaryMatrix, StateVector)):
+        if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
             raise TypeError(
                 'Expected unitary or state, got %s.' % type(target),
             )
@@ -56,11 +57,7 @@ def gen_initial_layer(
 
         return init_circuit
 
-    def gen_successors(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any],
-    ) -> list[Circuit]:
+    def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
         """
         Generate the successors of a circuit node.
 
diff --git a/bqskit/passes/search/generators/wide.py b/bqskit/passes/search/generators/wide.py
index 16307c551..1da89ed50 100644
--- a/bqskit/passes/search/generators/wide.py
+++ b/bqskit/passes/search/generators/wide.py
@@ -2,16 +2,17 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
-from typing import Sequence
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.gateset import GateSetLike
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
-from bqskit.ir.gates import IToffoliGate
-from bqskit.ir.gates import U3Gate
+from bqskit.ir.gates.constant.itoffoli import IToffoliGate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
 from bqskit.passes.search.generator import LayerGenerator
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 _logger = logging.getLogger(__name__)
 
@@ -26,7 +27,7 @@ class WideLayerGenerator(LayerGenerator):
 
     def __init__(
         self,
-        multi_qudit_gates: Gate | Sequence[Gate] = IToffoliGate(),
+        multi_qudit_gates: GateSetLike = IToffoliGate(),
         single_qudit_gate: Gate = U3Gate(),
     ) -> None:
         """
@@ -82,8 +83,8 @@ def __init__(
 
     def gen_initial_layer(
         self,
-        target: UnitaryMatrix | StateVector,
-        data: dict[str, Any],
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
     ) -> Circuit:
         """
         Generate the initial layer, see LayerGenerator for more.
@@ -93,7 +94,7 @@ def gen_initial_layer(
                 `self.initial_layer_gate`.
         """
 
-        if not isinstance(target, (UnitaryMatrix, StateVector)):
+        if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
             raise TypeError(
                 'Expected unitary or state, got %s.' % type(target),
             )
@@ -109,11 +110,7 @@ def gen_initial_layer(
             init_circuit.append_gate(self.single_qudit_gate, [i])
         return init_circuit
 
-    def gen_successors(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any],
-    ) -> list[Circuit]:
+    def gen_successors(self, circuit: Circuit, data: PassData) -> list[Circuit]:
         """
         Generate the successors of a circuit node.
 
diff --git a/bqskit/passes/search/heuristic.py b/bqskit/passes/search/heuristic.py
index 22de9a786..cad59b3c7 100644
--- a/bqskit/passes/search/heuristic.py
+++ b/bqskit/passes/search/heuristic.py
@@ -5,6 +5,7 @@
 
 from bqskit.ir.circuit import Circuit
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
@@ -19,14 +20,14 @@ class HeuristicFunction(abc.ABC):
     def get_value(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> float:
         """Return the heuristic's value given `circuit` and `target`."""
 
     def __call__(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> float:
         """Return the heuristic's value given `circuit` and `target`."""
 
@@ -35,7 +36,7 @@ def __call__(
                 'Expected circuit, got %s.' % type(circuit),
             )
 
-        if not isinstance(target, (UnitaryMatrix, StateVector)):
+        if not isinstance(target, (UnitaryMatrix, StateVector, StateSystem)):
             raise TypeError(
                 'Expected unitary or state, got %s.' % type(target),
             )
diff --git a/bqskit/passes/search/heuristics/astar.py b/bqskit/passes/search/heuristics/astar.py
index 4ac58a3f4..9ad4e9a89 100644
--- a/bqskit/passes/search/heuristics/astar.py
+++ b/bqskit/passes/search/heuristics/astar.py
@@ -6,6 +6,7 @@
 from bqskit.ir.opt.cost import HilbertSchmidtCostGenerator
 from bqskit.passes.search.heuristic import HeuristicFunction
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 from bqskit.utils.typing import is_real_number
 
@@ -65,7 +66,7 @@ def __init__(
     def get_value(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> float:
         """Return the heuristic's value, see HeuristicFunction for more info."""
         cost = 0.0
diff --git a/bqskit/passes/search/heuristics/dijkstra.py b/bqskit/passes/search/heuristics/dijkstra.py
index 2608e9f80..d593267f0 100644
--- a/bqskit/passes/search/heuristics/dijkstra.py
+++ b/bqskit/passes/search/heuristics/dijkstra.py
@@ -4,6 +4,7 @@
 from bqskit.ir.circuit import Circuit
 from bqskit.passes.search.heuristic import HeuristicFunction
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
@@ -20,7 +21,7 @@ class DijkstraHeuristic(HeuristicFunction):
     def get_value(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> float:
         """Return the heuristic's value, see HeuristicFunction for more info."""
         cost = 0.0
diff --git a/bqskit/passes/search/heuristics/greedy.py b/bqskit/passes/search/heuristics/greedy.py
index 302e300ac..59d207aba 100644
--- a/bqskit/passes/search/heuristics/greedy.py
+++ b/bqskit/passes/search/heuristics/greedy.py
@@ -6,6 +6,7 @@
 from bqskit.ir.opt.cost import HilbertSchmidtCostGenerator
 from bqskit.passes.search.heuristic import HeuristicFunction
 from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
 
@@ -40,7 +41,7 @@ def __init__(
     def get_value(
         self,
         circuit: Circuit,
-        target: UnitaryMatrix | StateVector,
+        target: UnitaryMatrix | StateVector | StateSystem,
     ) -> float:
         """Return the heuristic's value, see HeuristicFunction for more info."""
         return self.cost_gen.calc_cost(circuit, target)
diff --git a/bqskit/passes/synthesis/__init__.py b/bqskit/passes/synthesis/__init__.py
index 0cc3ebaa3..0d4e49c6b 100644
--- a/bqskit/passes/synthesis/__init__.py
+++ b/bqskit/passes/synthesis/__init__.py
@@ -2,10 +2,12 @@
 from __future__ import annotations
 
 from bqskit.passes.synthesis.leap import LEAPSynthesisPass
+from bqskit.passes.synthesis.pas import PermutationAwareSynthesisPass
 from bqskit.passes.synthesis.qfast import QFASTDecompositionPass
 from bqskit.passes.synthesis.qpredict import QPredictDecompositionPass
 from bqskit.passes.synthesis.qsearch import QSearchSynthesisPass
 from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.passes.synthesis.target import SetTargetPass
 
 __all__ = [
     'LEAPSynthesisPass',
@@ -13,4 +15,6 @@
     'QPredictDecompositionPass',
     'QSearchSynthesisPass',
     'SynthesisPass',
+    'SetTargetPass',
+    'PermutationAwareSynthesisPass',
 ]
diff --git a/bqskit/passes/synthesis/leap.py b/bqskit/passes/synthesis/leap.py
index 42e3911b6..b924c2b8e 100644
--- a/bqskit/passes/synthesis/leap.py
+++ b/bqskit/passes/synthesis/leap.py
@@ -7,6 +7,7 @@
 import numpy as np
 from scipy.stats import linregress
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.opt.cost.functions import HilbertSchmidtResidualsGenerator
 from bqskit.ir.opt.cost.generator import CostFunctionGenerator
@@ -16,7 +17,10 @@
 from bqskit.passes.search.heuristic import HeuristicFunction
 from bqskit.passes.search.heuristics import AStarHeuristic
 from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary import UnitaryMatrix
+from bqskit.runtime import get_runtime
 from bqskit.utils.typing import is_integer
 from bqskit.utils.typing import is_real_number
 
@@ -153,20 +157,20 @@ def __init__(
         self.store_partial_solutions = store_partial_solutions
         self.partials_per_depth = partials_per_depth
 
-    def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
+    async def synthesize(
+        self,
+        utry: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
+    ) -> Circuit:
         """Synthesize `utry`, see :class:`SynthesisPass` for more."""
         frontier = Frontier(utry, self.heuristic_function)
-        data['window_markers'] = []
+        instantiate_options = self.instantiate_options.copy()
+        if 'seed' not in instantiate_options:
+            instantiate_options['seed'] = data.seed
 
         # Seed the search with an initial layer
         initial_layer = self.layer_gen.gen_initial_layer(utry, data)
-        initial_layer = self.execute(
-            data,
-            Circuit.instantiate,
-            [initial_layer],
-            target=utry,
-            **self.instantiate_options,
-        )[0]
+        initial_layer.instantiate(utry, **instantiate_options)
         frontier.add(initial_layer, 0)
 
         # Track best circuit, initially the initial layer
@@ -194,13 +198,15 @@ def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
             # Generate successors
             successors = self.layer_gen.gen_successors(top_circuit, data)
 
+            if len(successors) == 0:
+                continue
+
             # Instantiate successors
-            circuits = self.execute(
-                data,
+            circuits = await get_runtime().map(
                 Circuit.instantiate,
                 successors,
                 target=utry,
-                **self.instantiate_options,
+                **instantiate_options,
             )
 
             # Evaluate successors
@@ -214,9 +220,10 @@ def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
                     return circuit
 
                 if self.check_new_best(layer + 1, dist, best_layer, best_dist):
+                    plural = '' if layer == 0 else 's'
                     _logger.debug(
-                        'New best circuit found with %d layer%s and cost: %e.'
-                        % (layer + 1, '' if layer == 0 else 's', dist),
+                        f'New best circuit found with {layer + 1} layer{plural}'
+                        f' and cost: {dist:.12e}.',
                     )
                     best_dist = dist
                     best_circ = circuit
@@ -232,7 +239,6 @@ def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
                         _logger.debug(f'Prefix formed at {layer + 1} layers.')
                         last_prefix_layer = layer + 1
                         frontier.clear()
-                        data['window_markers'].append(circuit.num_cycles)
                         if self.max_layer is None or layer + 1 < self.max_layer:
                             frontier.add(circuit, layer + 1)
 
diff --git a/bqskit/passes/synthesis/pas.py b/bqskit/passes/synthesis/pas.py
new file mode 100644
index 000000000..564ad7a23
--- /dev/null
+++ b/bqskit/passes/synthesis/pas.py
@@ -0,0 +1,118 @@
+"""This module implements the PermutationAwareSynthesisPass pass."""
+from __future__ import annotations
+
+import itertools as it
+import logging
+from typing import Callable
+
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.synthesis.leap import LEAPSynthesisPass
+from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.qis.permutation import PermutationMatrix
+from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+from bqskit.runtime import get_runtime
+
+
+_logger = logging.getLogger(__name__)
+
+
+def op_count(circuit: Circuit) -> float:
+    """Counts the number of operations in a circuit."""
+    return float(circuit.num_operations)
+
+
+class PermutationAwareSynthesisPass(SynthesisPass):
+    """Synthesis pass considering target permutations."""
+
+    def __init__(
+        self,
+        input_perm: bool = False,
+        output_perm: bool = True,
+        inner_synthesis: SynthesisPass = LEAPSynthesisPass(),
+        scoring_fn: Callable[[Circuit], float] = op_count,
+    ) -> None:
+        """
+        Construct a PermutationAwareSynthesisPass.
+
+        Args:
+            input_perm (bool): If true, vary the input permutation
+                during synthesis. (Default: False)
+
+            output_perm (bool): If true, vary the output permutation
+                during synthesis. (Default: True)
+
+            inner_synthesis (SynthesisPass): The synthesis algorithm used
+                on all permutations. (Default: :class:`LEAPSynthesisPass`)
+
+            scoring_fn (Callable[[Circuit], float]): The function used to
+                score the permuted circuits. The smallest score wins.
+                (Default: :func:`op_count`)
+        """
+        if not isinstance(inner_synthesis, SynthesisPass):
+            bad_type = type(inner_synthesis)
+            raise TypeError(f'Expected SynthesisPass object, got {bad_type}.')
+
+        if not callable(scoring_fn):
+            bad_type = type(scoring_fn)
+            m = f'Expected a function from circuits to scores, got {bad_type}.'
+            raise TypeError(m)
+
+        self.inner_synthesis = inner_synthesis
+        self.scoring_fn = scoring_fn
+        self.input_perm = input_perm
+        self.output_perm = output_perm
+
+    async def synthesize(
+        self,
+        utry: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
+    ) -> Circuit:
+        """Synthesize `utry`, see :class:`SynthesisPass` for more."""
+        # Calculate all permuted targets
+        width = utry.num_qudits
+        perms = list(it.permutations(range(width)))
+        no_perm = [tuple(range(width))]
+        Pis = [PermutationMatrix.from_qubit_location(width, p) for p in perms]
+        Pos = [PermutationMatrix.from_qubit_location(width, p) for p in perms]
+
+        if self.input_perm and self.output_perm:
+            permsbyperms = list(it.product(perms, perms))
+            targets = [Po.T @ utry @ Pi for Pi, Po in it.product(Pis, Pos)]
+
+        elif self.input_perm:
+            permsbyperms = list(it.product(perms, no_perm))
+            targets = [utry @ Pi for Pi in Pis]
+
+        elif self.output_perm:
+            permsbyperms = list(it.product(no_perm, perms))
+            targets = [Po.T @ utry for Po in Pos]
+
+        else:
+            _logger.warning('No permutation is being used in PAS.')
+            permsbyperms = list(it.product(no_perm, no_perm))
+            targets = [utry]
+
+        # Synthesize all permuted targets
+        circuits: list[Circuit] = await get_runtime().map(
+            self.inner_synthesis.synthesize,
+            targets,
+            [data] * len(targets),
+        )
+
+        # Return best circuit
+        best_circuit = circuits[0]
+        best_perm = permsbyperms[0]
+        best_score = self.scoring_fn(circuits[0])
+        for perm, circuit in zip(permsbyperms[1:], circuits[1:]):
+            score = self.scoring_fn(circuit)
+            if score < best_score:
+                best_circuit = circuit
+                best_perm = perm
+                best_score = score
+
+        data['initial_mapping'] = best_perm[0]
+        data['final_mapping'] = best_perm[1]
+        return best_circuit
diff --git a/bqskit/passes/synthesis/qfast.py b/bqskit/passes/synthesis/qfast.py
index f0e47abc6..7e4105770 100644
--- a/bqskit/passes/synthesis/qfast.py
+++ b/bqskit/passes/synthesis/qfast.py
@@ -6,15 +6,18 @@
 from typing import Any
 from typing import Sequence
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
-from bqskit.ir.gates import PauliGate
 from bqskit.ir.gates.composed.vlg import VariableLocationGate
+from bqskit.ir.gates.parameterized.pauli import PauliGate
 from bqskit.ir.location import CircuitLocation
 from bqskit.ir.operation import Operation
 from bqskit.ir.opt.cost import CostFunctionGenerator
 from bqskit.ir.opt.cost import HilbertSchmidtResidualsGenerator
 from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 from bqskit.utils.typing import is_integer
 from bqskit.utils.typing import is_real_number
@@ -123,13 +126,19 @@ def __init__(
         }
         self.instantiate_options.update(instantiate_options)
 
-    def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
+    async def synthesize(
+        self,
+        utry: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
+    ) -> Circuit:
         """Synthesize `utry`, see :class:`SynthesisPass` for more."""
+        instantiate_options = self.instantiate_options.copy()
+        if 'seed' not in instantiate_options:
+            instantiate_options['seed'] = data.seed
 
         # Skip any unitaries too small for the configured gate.
         if self.gate.num_qudits > utry.num_qudits:
-            _logger.warning('Skipping unitary synthesis since gate is larger.')
-            return Circuit.from_unitary(utry)
+            raise RuntimeError('Gate is too small for circuit')
 
         # Create empty circuit with same size and radixes as `utry`.
         circuit = Circuit(utry.num_qudits, utry.radixes)
@@ -149,19 +158,12 @@ def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
         while True:
 
             # Instantiate circuit
-            circuit = self.execute(
-                data,
-                Circuit.instantiate,
-                [circuit],
-                target=utry,
-                **self.instantiate_options,
-            )[0]
-
+            circuit.instantiate(utry, **instantiate_options)
             dist = self.cost.calc_cost(circuit, utry)
             _logger.info(f'Instantiated depth {depth} at {dist} cost.')
 
             if dist < self.success_threshold:
-                self.finalize(circuit, utry, data)
+                self.finalize(circuit, utry, instantiate_options)
                 _logger.info('Successful synthesis.')
                 return circuit
 
@@ -198,8 +200,8 @@ def get_location_of_head(self, circuit: Circuit) -> CircuitLocation:
     def finalize(
         self,
         circuit: Circuit,
-        utry: UnitaryMatrix,
-        data: dict[str, Any],
+        utry: UnitaryMatrix | StateVector | StateSystem,
+        instantiate_options: dict[str, Any],
     ) -> None:
         """Finalize the circuit by replacing the head with self.gate."""
         # Replace Head with self.gate
@@ -211,15 +213,7 @@ def finalize(
         # Reinstantiate
         dist = self.cost.calc_cost(circuit, utry)
         while dist > self.success_threshold:
-            circuit.become(
-                self.execute(
-                    data,
-                    Circuit.instantiate,
-                    [circuit],
-                    target=utry,
-                    **(self.instantiate_options),
-                )[0],
-            )
+            circuit.instantiate(utry, **instantiate_options)
             dist = self.cost.calc_cost(circuit, utry)
 
         _logger.info(f'Final circuit found with cost: {dist}.')
diff --git a/bqskit/passes/synthesis/qpredict.py b/bqskit/passes/synthesis/qpredict.py
index dd2d65403..f4a0170ae 100644
--- a/bqskit/passes/synthesis/qpredict.py
+++ b/bqskit/passes/synthesis/qpredict.py
@@ -7,12 +7,15 @@
 
 import numpy as np
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import VariableUnitaryGate
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
 from bqskit.ir.location import CircuitLocation
 from bqskit.ir.opt.cost import CostFunctionGenerator
 from bqskit.ir.opt.cost import HilbertSchmidtResidualsGenerator
 from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 from bqskit.utils.math import pauli_expansion
 from bqskit.utils.math import unitary_log_no_i
@@ -180,8 +183,18 @@ def __init__(
         }
         self.instantiate_options.update(instantiate_options)
 
-    def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
+    async def synthesize(
+        self,
+        utry: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
+    ) -> Circuit:
         """Synthesize `utry`, see :class:`SynthesisPass` for more."""
+        if isinstance(utry, (StateVector, StateSystem)):
+            raise RuntimeError('Unable to synthesize a state with qpredict.')
+
+        instantiate_options = self.instantiate_options.copy()
+        if data.seed is not None:
+            instantiate_options['seed'] = data.seed
 
         # 0. Skip any unitaries too small for the configured block.
         if self.block_size_start >= utry.num_qudits:
@@ -224,7 +237,7 @@ def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
                 circuit.append_gate(VariableUnitaryGate(len(loc)), loc)
                 circuit.instantiate(
                     utry,
-                    **self.instantiate_options,  # type: ignore
+                    **instantiate_options,  # type: ignore
                 )
                 cost = self.cost.calc_cost(circuit, utry)
                 _logger.info(f'Instantiated; layers: {layer}, cost: {cost:e}.')
diff --git a/bqskit/passes/synthesis/qsearch.py b/bqskit/passes/synthesis/qsearch.py
index f10ccc712..ca8a64b9b 100644
--- a/bqskit/passes/synthesis/qsearch.py
+++ b/bqskit/passes/synthesis/qsearch.py
@@ -4,6 +4,7 @@
 import logging
 from typing import Any
 
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.opt.cost.functions import HilbertSchmidtResidualsGenerator
 from bqskit.ir.opt.cost.generator import CostFunctionGenerator
@@ -13,7 +14,10 @@
 from bqskit.passes.search.heuristic import HeuristicFunction
 from bqskit.passes.search.heuristics import AStarHeuristic
 from bqskit.passes.synthesis.synthesis import SynthesisPass
+from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 from bqskit.qis.unitary import UnitaryMatrix
+from bqskit.runtime import get_runtime
 from bqskit.utils.typing import is_integer
 from bqskit.utils.typing import is_real_number
 
@@ -132,19 +136,21 @@ def __init__(
         self.store_partial_solutions = store_partial_solutions
         self.partials_per_depth = partials_per_depth
 
-    def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
+    async def synthesize(
+        self,
+        utry: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
+    ) -> Circuit:
         """Synthesize `utry`, see :class:`SynthesisPass` for more."""
+        instantiate_options = self.instantiate_options.copy()
+        if 'seed' not in instantiate_options:
+            instantiate_options['seed'] = data.seed
+
         frontier = Frontier(utry, self.heuristic_function)
 
         # Seed the search with an initial layer
         initial_layer = self.layer_gen.gen_initial_layer(utry, data)
-        initial_layer = self.execute(
-            data,
-            Circuit.instantiate,
-            [initial_layer],
-            target=utry,
-            **self.instantiate_options,
-        )[0]
+        initial_layer.instantiate(utry, **instantiate_options)
         frontier.add(initial_layer, 0)
 
         # Track best circuit, initially the initial layer
@@ -169,13 +175,15 @@ def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
             # Generate successors
             successors = self.layer_gen.gen_successors(top_circuit, data)
 
+            if len(successors) == 0:
+                continue
+
             # Instantiate successors
-            circuits = self.execute(
-                data,
+            circuits = await get_runtime().map(
                 Circuit.instantiate,
                 successors,
                 target=utry,
-                **self.instantiate_options,
+                **instantiate_options,
             )
 
             # Evaluate successors
@@ -189,9 +197,10 @@ def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
                     return circuit
 
                 if dist < best_dist:
+                    plural = '' if layer == 0 else 's'
                     _logger.debug(
-                        'New best circuit found with %d layer%s and cost: %e.'
-                        % (layer + 1, '' if layer == 0 else 's', dist),
+                        f'New best circuit found with {layer + 1} layer{plural}'
+                        f' and cost: {dist:.12e}.',
                     )
                     best_dist = dist
                     best_circ = circuit
diff --git a/bqskit/passes/synthesis/synthesis.py b/bqskit/passes/synthesis/synthesis.py
index 03c370f63..a982fce57 100644
--- a/bqskit/passes/synthesis/synthesis.py
+++ b/bqskit/passes/synthesis/synthesis.py
@@ -5,9 +5,11 @@
 from typing import TYPE_CHECKING
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
+from bqskit.qis.state.state import StateVector
+from bqskit.qis.state.system import StateSystem
 
 if TYPE_CHECKING:
-    from typing import Any
     from bqskit.ir.circuit import Circuit
     from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
 
@@ -16,16 +18,20 @@ class SynthesisPass(BasePass):
     """
     SynthesisPass abstract class.
 
-    The SynthesisPass is a base class that exposes an abstract
-    synthesize function. Inherit from this class and implement the
-    synthesize function to create a synthesis tool.
+    The SynthesisPass is a base class that exposes an abstract synthesize
+    function. Inherit from this class and implement the synthesize function to
+    create a synthesis tool.
 
-    A SynthesisPass will synthesize a new circuit targeting the input
-    circuit's unitary.
+    A SynthesisPass will synthesize a new circuit targeting the input circuit's
+    unitary.
     """
 
     @abstractmethod
-    def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
+    async def synthesize(
+        self,
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
+    ) -> Circuit:
         """
         Synthesis abstract method to synthesize a UnitaryMatrix into a Circuit.
 
@@ -41,10 +47,6 @@ def synthesize(self, utry: UnitaryMatrix, data: dict[str, Any]) -> Circuit:
             This function should be self-contained and have no side effects.
         """
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
-        if len(data) == 0:
-            data = dict()
-
-        target_utry = self.get_target(circuit, data)
-        circuit.become(self.synthesize(target_utry, data))
+        circuit.become(await self.synthesize(data.target, data))
diff --git a/bqskit/passes/synthesis/target.py b/bqskit/passes/synthesis/target.py
new file mode 100644
index 000000000..97c2e0a90
--- /dev/null
+++ b/bqskit/passes/synthesis/target.py
@@ -0,0 +1,39 @@
+"""This module implements the SetTargetPass class."""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.qis.state import StateSystem
+from bqskit.qis.state import StateVector
+from bqskit.qis.unitary import UnitaryMatrix
+
+if TYPE_CHECKING:
+    from bqskit.compiler.passdata import PassData
+    from bqskit.ir.circuit import Circuit
+
+
+class SetTargetPass(BasePass):
+    """Sets a synthesis target for future passes."""
+
+    def __init__(
+        self,
+        target: UnitaryMatrix | StateVector | StateSystem,
+    ) -> None:
+        """
+        Construct a SetTargetPass.
+
+        Args:
+            target (UnitaryMatrix | StateVector | StateSystem): The target
+                to synthesize in future passes.
+        """
+        if not isinstance(target, (StateVector, UnitaryMatrix, StateSystem)):
+            bad_type = type(target)
+            m = f'Expected valid unitary or state for target, got {bad_type}.'
+            raise TypeError(m)
+
+        self.target = target
+
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        """Perform the pass's operation, see :class:`BasePass` for more."""
+        data.target = self.target
diff --git a/bqskit/passes/util/__init__.py b/bqskit/passes/util/__init__.py
index d8352fd83..0e58b19b1 100644
--- a/bqskit/passes/util/__init__.py
+++ b/bqskit/passes/util/__init__.py
@@ -4,6 +4,7 @@
 from bqskit.passes.util.compress import CompressPass
 from bqskit.passes.util.conversion import BlockConversionPass
 from bqskit.passes.util.converttou3 import ToU3Pass
+from bqskit.passes.util.converttovar import ToVariablePass
 from bqskit.passes.util.extend import ExtendBlockSizePass
 from bqskit.passes.util.fill import FillSingleQuditGatesPass
 from bqskit.passes.util.log import LogErrorPass
@@ -25,4 +26,5 @@
     'ExtendBlockSizePass',
     'LogErrorPass',
     'FillSingleQuditGatesPass',
+    'ToVariablePass',
 ]
diff --git a/bqskit/passes/util/compress.py b/bqskit/passes/util/compress.py
index ff42e0fe6..df0a79cf6 100644
--- a/bqskit/passes/util/compress.py
+++ b/bqskit/passes/util/compress.py
@@ -1,9 +1,8 @@
 """This module implements the CompressPass class."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 
@@ -15,6 +14,6 @@ class CompressPass(BasePass):
     passes slightly more efficient without actually changing the circuit.
     """
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         circuit.compress()
diff --git a/bqskit/passes/util/conversion.py b/bqskit/passes/util/conversion.py
index 60b75e2b2..beb52e17e 100644
--- a/bqskit/passes/util/conversion.py
+++ b/bqskit/passes/util/conversion.py
@@ -2,13 +2,13 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates import ConstantUnitaryGate
-from bqskit.ir.gates import VariableUnitaryGate
+from bqskit.ir.gates.circuitgate import CircuitGate
+from bqskit.ir.gates.constant.unitary import ConstantUnitaryGate
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
 from bqskit.ir.point import CircuitPoint
 
 _logger = logging.getLogger(__name__)
@@ -68,7 +68,7 @@ def __init__(
         else:
             raise ValueError('Unexpected input for conversion target.')
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
 
         # Variable -> Constant
diff --git a/bqskit/passes/util/converttou3.py b/bqskit/passes/util/converttou3.py
index b2ec0a905..53ad6e497 100644
--- a/bqskit/passes/util/converttou3.py
+++ b/bqskit/passes/util/converttou3.py
@@ -2,13 +2,12 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates.parameterized.pauli import PauliGate
+from bqskit.ir.gates.generalgate import GeneralGate
 from bqskit.ir.gates.parameterized.u3 import U3Gate
-from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
 from bqskit.ir.point import CircuitPoint
 
 _logger = logging.getLogger(__name__)
@@ -17,18 +16,27 @@
 class ToU3Pass(BasePass):
     """Converts single-qubit general unitary gates to U3 Gates."""
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    def __init__(self, convert_all_single_qubit_gates: bool = False) -> None:
+        """
+        Construct a ToU3Pass.
+
+        Args:
+            convert_all_single_qubit_gates (bool): Indicates wheter to convert
+            only the general gates, or every single qubit gate.
+        """
+
+        self.convert_all_single_qubit_gates = convert_all_single_qubit_gates
+
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         _logger.debug('Converting single-qubit general gates to U3Gates.')
         for cycle, op in circuit.operations_with_cycles():
             if (
-                (
-                    isinstance(op.gate, VariableUnitaryGate)
-                    or isinstance(op.gate, PauliGate)
+                op.radixes == (2,) and (
+                    isinstance(op.gate, GeneralGate)
+                    or self.convert_all_single_qubit_gates
                 )
-                and len(op.location) == 1
-                and op.radixes == (2,)
             ):
-                params = U3Gate.calc_params(op.get_unitary())
+                params = U3Gate().calc_params(op.get_unitary())
                 point = CircuitPoint(cycle, op.location[0])
                 circuit.replace_gate(point, U3Gate(), op.location, params)
diff --git a/bqskit/passes/util/converttovar.py b/bqskit/passes/util/converttovar.py
new file mode 100644
index 000000000..9a23e8321
--- /dev/null
+++ b/bqskit/passes/util/converttovar.py
@@ -0,0 +1,46 @@
+"""This module implements the ToVariablePass."""
+from __future__ import annotations
+
+import logging
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates.generalgate import GeneralGate
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
+from bqskit.ir.point import CircuitPoint
+
+_logger = logging.getLogger(__name__)
+
+
+class ToVariablePass(BasePass):
+    """Converts single-qudit general unitary gates to Variable Unitary Gates."""
+
+    def __init__(self, convert_all_single_qudit_gates: bool = False) -> None:
+        """
+        Construct a ToVariablePass.
+
+        Args:
+            convert_all_single_qudit_gates (bool): Indicates wheter to convert
+            only the general gates, or every single qudit gate.
+        """
+        self.convert_all_single_qudit_gates = convert_all_single_qudit_gates
+
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        """Perform the pass's operation, see :class:`BasePass` for more."""
+        _logger.debug(
+            'Converting single-qudit general gates to VariableUnitaryGate.',
+        )
+        for cycle, op in circuit.operations_with_cycles():
+            if (
+                op.gate.num_qudits == 1 and (
+                    isinstance(op.gate, GeneralGate)
+                    or self.convert_all_single_qudit_gates
+                )
+            ):
+                params = VariableUnitaryGate.get_params(op.get_unitary())
+                point = CircuitPoint(cycle, op.location[0])
+                vgate = VariableUnitaryGate(op.num_qudits, op.radixes)
+                circuit.replace_gate(
+                    point, vgate, op.location, params,
+                )
diff --git a/bqskit/passes/util/extend.py b/bqskit/passes/util/extend.py
index 7ba189621..682a30978 100644
--- a/bqskit/passes/util/extend.py
+++ b/bqskit/passes/util/extend.py
@@ -2,10 +2,10 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 from typing import Sequence
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.operation import Operation
@@ -31,7 +31,7 @@ def __init__(self, minimum_size: int) -> None:
 
         self.minimum_size = minimum_size
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         if circuit.num_qudits < self.minimum_size:
             raise RuntimeError('Cannot extend block larger than circuit.')
diff --git a/bqskit/passes/util/fill.py b/bqskit/passes/util/fill.py
index 5a384c1c8..214ddd287 100644
--- a/bqskit/passes/util/fill.py
+++ b/bqskit/passes/util/fill.py
@@ -2,57 +2,67 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates.parameterized.u3 import U3Gate
+from bqskit.ir.point import CircuitPoint
 
 
 _logger = logging.getLogger(__name__)
 
 
 class FillSingleQuditGatesPass(BasePass):
-    """A pass that inserts single-qudit gates around multi-qudit gates."""
+    """
+    A pass that inserts single-qudit gates around multi-qudit gates.
 
-    def __init__(self, success_threshold: float = 1e-10):
-        """
-        Construct a FillSingleQuditGatesPass.
+    This pass will preserve the multi-qudit gates in the circuit and then place
+    single-qudit unitary gates around those gates.
+    """
 
-        Args:
-            success_threshold (bool): Reinstantiate the new filled circuit
-                to be within this distance from initial starting circuit.
-        """
-        self.success_threshold = success_threshold
-
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
-        _logger.debug('Completing circuit with single-qudit gates.')
-        target = self.get_target(circuit, data)
+        _logger.debug('Filling circuit with single-qudit gates.')
 
         complete_circuit = Circuit(circuit.num_qudits, circuit.radixes)
+        sq_gate = data.gate_set.get_general_sq_gate()
+        id_params = sq_gate.identity_as_params((circuit.radixes[0],))
 
-        if target.num_qudits == 1:
-            params = U3Gate.calc_params(target)
-            complete_circuit.append_gate(U3Gate(), 0, params)
-            circuit.become(complete_circuit)
-            return
+        # Add general gate as identity on every qudit to start
+        for qudit_index in range(circuit.num_qudits):
+            complete_circuit.append_gate(sq_gate, qudit_index, id_params)
 
-        for q in range(circuit.num_qudits):
-            complete_circuit.append_gate(U3Gate(), q)
+        for cycle, op in circuit.operations_with_cycles():
 
-        for op in circuit:
+            # Single-qudit gates get converted to general gates
             if op.num_qudits == 1:
-                continue
 
-            complete_circuit.append(op)
-            for q in op.location:
-                complete_circuit.append_gate(U3Gate(), q)
+                # Check for already existing single-qudit gates
+                last_point = complete_circuit.last_on(op.location[0])
+                if last_point is not None:
+                    last_op = complete_circuit[last_point]
+
+                    if last_op.num_qudits == 1:
+                        # Merge single-qudit gates if one already exists
+                        utry = op.get_unitary() @ last_op.get_unitary()
+                        last_op.params = sq_gate.calc_params(utry)
+                        continue
+
+                # Otherwise just add general gate
+                params = sq_gate.calc_params(op.get_unitary())
+                complete_circuit.append_gate(sq_gate, op.location, params)
+
+            else:
+                # Multi-qudit gates get added as is
+                complete_circuit.append(op)
+
+                # Add additional sq gates where there is none between mq gates
+                qudits_needing_single_qudit_gates = set(op.location)
+                for p in circuit.next(CircuitPoint(cycle, op.location[0])):
+                    if circuit[p].num_qudits == 1:
+                        qudits_needing_single_qudit_gates.remove(p.qudit)
 
-        dist = 1.0
-        for i in range(10):
-            complete_circuit.instantiate(target)
-            dist = complete_circuit.get_unitary().get_distance_from(target, 1)
+                for qudit in qudits_needing_single_qudit_gates:
+                    complete_circuit.append_gate(sq_gate, qudit, id_params)
 
-        if dist <= self.success_threshold:
-            circuit.become(complete_circuit)
+        circuit.become(complete_circuit)
diff --git a/bqskit/passes/util/log.py b/bqskit/passes/util/log.py
index 0b8646b57..dc93e2fc5 100644
--- a/bqskit/passes/util/log.py
+++ b/bqskit/passes/util/log.py
@@ -2,11 +2,11 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 import numpy as np
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 
@@ -28,7 +28,7 @@ def __init__(self, msg: str, level: int = logging.INFO) -> None:
         self.msg = msg
         self.level = level
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         _logger.log(self.level, self.msg)
 
@@ -47,7 +47,7 @@ def __init__(self, error_threshold: float | None = None) -> None:
         """
         self.threshold = error_threshold
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         if 'error' in data:
             error = data['error']
diff --git a/bqskit/passes/util/random.py b/bqskit/passes/util/random.py
index 6a0b0a36a..fad5b9c04 100644
--- a/bqskit/passes/util/random.py
+++ b/bqskit/passes/util/random.py
@@ -2,14 +2,10 @@
 from __future__ import annotations
 
 import logging
-import random
-from typing import Any
-
-import numpy as np
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
-from bqskit.utils.random import seed_random_sources
 from bqskit.utils.typing import is_integer
 
 
@@ -23,8 +19,6 @@ class SetRandomSeedPass(BasePass):
     The SetRandomSeedPass sets the random seed.
     """
 
-    key = '_SetRandomSeedPass_random_seed'
-
     def __init__(self, seed: int = 0) -> None:
         """
         Construct a SetRandomSeedPass.
@@ -38,10 +32,7 @@ def __init__(self, seed: int = 0) -> None:
 
         self.seed = seed
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
-        _logger.debug(f'Setting the random seed to {self.seed}.')
-        data[self.key] = self.seed
-        np.random.seed(self.seed)
-        random.seed(self.seed)
-        seed_random_sources(self.seed)
+        _logger.info(f'Setting the random seed to {self.seed}.')
+        data.seed = self.seed
diff --git a/bqskit/passes/util/record.py b/bqskit/passes/util/record.py
index d2574d052..f9650f9e1 100644
--- a/bqskit/passes/util/record.py
+++ b/bqskit/passes/util/record.py
@@ -5,6 +5,7 @@
 from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 _logger = logging.getLogger(__name__)
@@ -19,7 +20,7 @@ class RecordStatsPass(BasePass):
 
     key = 'RecordStatsPass_stats_list'
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         stats: dict[str, Any] = {}
         stats['cycles'] = circuit.num_cycles
diff --git a/bqskit/passes/util/unfold.py b/bqskit/passes/util/unfold.py
index c5aa5528c..5ffad8606 100644
--- a/bqskit/passes/util/unfold.py
+++ b/bqskit/passes/util/unfold.py
@@ -2,9 +2,9 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 
@@ -18,7 +18,7 @@ class UnfoldPass(BasePass):
     The UnfoldPass unfolds all CircuitGate blocks into the circuit.
     """
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         _logger.debug('Unfolding the circuit.')
         circuit.unfold_all()
diff --git a/bqskit/passes/util/update.py b/bqskit/passes/util/update.py
index 02f23dcac..849437061 100644
--- a/bqskit/passes/util/update.py
+++ b/bqskit/passes/util/update.py
@@ -5,6 +5,7 @@
 from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 
@@ -34,7 +35,7 @@ def __init__(self, key: str, val: Any) -> None:
         self.key = key
         self.val = val
 
-    def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         """Perform the pass's operation, see :class:`BasePass` for more."""
         _logger.debug(f'Injecting {self.key}:{self.val} into the data dict.')
         data[self.key] = self.val
diff --git a/bqskit/qis/__init__.py b/bqskit/qis/__init__.py
index d54b3c5a6..b20a61590 100644
--- a/bqskit/qis/__init__.py
+++ b/bqskit/qis/__init__.py
@@ -57,6 +57,8 @@
 from bqskit.qis.state import StateLike
 from bqskit.qis.state import StateVector
 from bqskit.qis.state import StateVectorMap
+from bqskit.qis.state.system import StateSystem
+from bqskit.qis.state.system import StateSystemLike
 from bqskit.qis.unitary import DifferentiableUnitary
 from bqskit.qis.unitary import LocallyOptimizableUnitary
 from bqskit.qis.unitary import RealVector
@@ -80,4 +82,6 @@
     'RealVector',
     'CouplingGraph',
     'CouplingGraphLike',
+    'StateSystem',
+    'StateSystemLike',
 ]
diff --git a/bqskit/qis/graph.py b/bqskit/qis/graph.py
index 1df8c7233..06994ef34 100644
--- a/bqskit/qis/graph.py
+++ b/bqskit/qis/graph.py
@@ -39,7 +39,7 @@ def __init__(
         if isinstance(graph, CouplingGraph):
             self.num_qudits: int = graph.num_qudits
             self._edges: set[tuple[int, int]] = graph._edges
-            self._adj: list[list[int]] = graph._adj
+            self._adj: list[set[int]] = graph._adj
             return
 
         if not CouplingGraph.is_valid_coupling_graph(graph):
@@ -59,10 +59,10 @@ def __init__(
         else:
             self.num_qudits = num_qudits
 
-        self._adj = [[] for _ in range(self.num_qudits)]
+        self._adj = [set() for _ in range(self.num_qudits)]
         for q1, q2 in self._edges:
-            self._adj[q1].append(q2)
-            self._adj[q2].append(q1)
+            self._adj[q1].add(q2)
+            self._adj[q2].add(q1)
 
         self._mat = [
             [np.inf for _ in range(self.num_qudits)]
@@ -94,7 +94,7 @@ def is_fully_connected(self) -> bool:
 
     def get_neighbors_of(self, qudit: int) -> list[int]:
         """Return the qudits adjacent to `qudit`."""
-        return self._adj[qudit]
+        return list(self._adj[qudit])
 
     def __contains__(self, __o: object) -> bool:
         return self._edges.__contains__(__o)
@@ -190,9 +190,10 @@ def get_subgraph(
             renumbering = {q: i for i, q in enumerate(location)}
 
         subgraph = []
-        for q0, q1 in self._edges:
-            if q0 in location and q1 in location:
-                subgraph.append((renumbering[q0], renumbering[q1]))
+        location_set = {loc for loc in location}
+        for q_i in location:
+            for q_i_neighbor in location_set.intersection(self._adj[q_i]):
+                subgraph.append((renumbering[q_i], renumbering[q_i_neighbor]))
         return CouplingGraph(subgraph, len(location))
 
     def get_subgraphs_of_size(self, size: int) -> list[CircuitLocation]:
diff --git a/bqskit/qis/state/__init__.py b/bqskit/qis/state/__init__.py
index 1bdd77323..24785045c 100644
--- a/bqskit/qis/state/__init__.py
+++ b/bqskit/qis/state/__init__.py
@@ -4,9 +4,13 @@
 from bqskit.qis.state.state import StateLike
 from bqskit.qis.state.state import StateVector
 from bqskit.qis.state.statemap import StateVectorMap
+from bqskit.qis.state.system import StateSystem
+from bqskit.qis.state.system import StateSystemLike
 
 __all__ = [
     'StateVector',
     'StateLike',
     'StateVectorMap',
+    'StateSystem',
+    'StateSystemLike',
 ]
diff --git a/bqskit/qis/state/state.py b/bqskit/qis/state/state.py
index 221e26a79..a71bc59b1 100644
--- a/bqskit/qis/state/state.py
+++ b/bqskit/qis/state/state.py
@@ -3,6 +3,7 @@
 
 import logging
 from typing import Any
+from typing import cast
 from typing import Iterator
 from typing import Sequence
 from typing import TYPE_CHECKING
@@ -17,10 +18,10 @@
 from bqskit.utils.typing import is_valid_radixes
 from bqskit.utils.typing import is_vector
 
-
 if TYPE_CHECKING:
     from typing import TypeGuard
-
+    from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+    from bqskit.ir.location import CircuitLocationLike
 
 _logger = logging.getLogger(__name__)
 
@@ -182,12 +183,25 @@ def is_pure_state(V: Any, tol: float = 1e-8) -> TypeGuard[StateLike]:
         if isinstance(V, StateVector):
             return True
 
+        from bqskit.qis.state import StateSystem
+        if isinstance(V, StateSystem):
+            return False
+
         if not np.allclose(np.sum(np.square(np.abs(V))), 1, rtol=0, atol=tol):
             _logger.debug('Failed pure state criteria.')
             return False
 
         return True
 
+    @staticmethod
+    def zero(num_qudits: int, radixes: Sequence[int] = []) -> StateVector:
+        """Prepares the zero state."""
+        if len(radixes) == 0:
+            radixes = [2] * num_qudits
+        state = np.zeros(np.prod(radixes), dtype=np.complex128)
+        state[0] = 1.0
+        return StateVector(state)
+
     @staticmethod
     def random(num_qudits: int, radixes: Sequence[int] = []) -> StateVector:
         """
@@ -230,6 +244,10 @@ def random(num_qudits: int, radixes: Sequence[int] = []) -> StateVector:
         U = unitary_group.rvs(int(np.prod(radixes)))
         return StateVector(U[:, 0], radixes)
 
+    def __hash__(self) -> int:
+        """Hash the state vector."""
+        return hash(tuple(self.numpy))
+
     def __eq__(self, other: object) -> bool:
         """Check if `self` is approximately equal to `other`."""
         if isinstance(other, StateVector):
@@ -240,6 +258,117 @@ def __eq__(self, other: object) -> bool:
 
         return NotImplemented
 
+    def apply(
+        self,
+        utry: UnitaryMatrix,
+        location: CircuitLocationLike,
+        inverse: bool = False,
+        check_arguments: bool = True,
+    ) -> None:
+        """
+
+        Apply the specified unitary on the right of this StateVector.
+
+        ..
+                .---.   .------.
+                |   |---|      |- 0
+                |   |---| utry |- 1
+                .   .   '------'
+                .   .
+                .   .
+                |   |------------ n-1
+                '---'
+
+        Args:
+            utry (UnitaryMatrix): The unitary to apply.
+
+            location (CircuitLocationLike): The qudits to apply the unitary on.
+
+            inverse (bool): If true, apply the inverse of the unitary.
+
+            check_arguments (bool): If true, check the inputs for type and
+                value errors.
+
+        Raises:
+            ValueError: If `utry`'s size does not match the given location.
+
+            ValueError: if `utry`'s radixes does not match the given location.
+
+        Notes:
+            - Applying the unitary on the left is equivalent to multiplying
+              the unitary on the right of the tensor. The notation comes
+              from the quantum circuit perspective.
+
+            - This operation is performed using tensor contraction.
+        """
+        from bqskit.ir.location import CircuitLocation
+        from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+
+        if check_arguments:
+            if not isinstance(utry, UnitaryMatrix):
+                raise TypeError('Expected UnitaryMatrix, got %s', type(utry))
+
+            if not CircuitLocation.is_location(location, self.num_qudits):
+                raise TypeError('Invalid location.')
+
+            location = CircuitLocation(location)
+
+            if len(location) != utry.num_qudits:
+                raise ValueError('Unitary and location size mismatch.')
+
+            for utry_radix, bldr_radix_idx in zip(utry.radixes, location):
+                if utry_radix != self.radixes[bldr_radix_idx]:
+                    raise ValueError('Unitary and location radix mismatch.')
+
+        location = cast(CircuitLocation, location)
+        qudits = list(range(self.num_qudits))
+        identity_action_perm = [
+            x
+            for x in qudits
+            if x not in location
+        ]
+        unitary_action_perm = list(location)
+
+        left_dim = int(
+            np.prod([
+                self.radixes[x]
+                for x in unitary_action_perm
+            ]),
+        )
+
+        utry = utry.dagger if inverse else utry
+
+        perm = unitary_action_perm + identity_action_perm
+        self._vec = self._vec.reshape(self.radixes)
+        self._vec = self._vec.transpose(perm)
+        self._vec = self._vec.reshape((left_dim, -1))
+        self._vec = utry @ self._vec
+
+        shape = list(self.radixes) * 2
+        shape = [shape[p] for p in perm]
+        self._vec = self._vec.reshape(shape)
+        inv_perm = list(np.argsort(perm))
+        self._vec = self._vec.transpose(inv_perm)
+        self._vec = self._vec.reshape(-1)
+
+    def get_distance_from(self, other: StateLike) -> float:
+        """
+        Return the distance between `self` and `other`.
+
+        The distance is given as the infidelity between the two states.
+
+        Args:
+            other (StateLike): The state to measure distance from.
+
+        Returns:
+            float: A value between 1 and 0, where 0 means the two states
+            are equal up to global phase and 1 means the two states are
+            very unsimilar or far apart.
+        """
+        other = StateVector(other)
+        dist = 1 - np.abs(np.conj(self) @ other) ** 2
+        return dist if dist > 0.0 else 0.0
+
     def __array__(
         self,
         dtype: np.typing.DTypeLike = np.complex128,
diff --git a/bqskit/qis/state/system.py b/bqskit/qis/state/system.py
new file mode 100644
index 000000000..b64d7162c
--- /dev/null
+++ b/bqskit/qis/state/system.py
@@ -0,0 +1,121 @@
+"""This module implements the StateSystem class."""
+from __future__ import annotations
+
+from typing import Any
+from typing import Dict
+from typing import Iterator
+from typing import Mapping
+from typing import TYPE_CHECKING
+from typing import Union
+
+import numpy as np
+
+from bqskit.qis.state.state import StateVector
+
+if TYPE_CHECKING:
+    import numpy.typing as npt
+    from typing import TypeGuard
+
+
+class StateSystem(Mapping[StateVector, StateVector]):
+    """A system of input and output states."""
+
+    def __init__(self, system: StateSystemLike) -> None:
+        """Construct a state system."""
+        if isinstance(system, StateSystem):
+            self._system: dict[StateVector, StateVector] = system._system
+            self._radixes: tuple[int, ...] = system._radixes
+            self._dim: int = system._dim
+            self._vec_count: int = system._vec_count
+            self.target: npt.NDArray[np.complex128] = system.target
+            return
+
+        self._system = {
+            StateVector(k): StateVector(v)
+            for k, v in system.items()
+        }
+        self._radixes = list(self._system.keys())[0].radixes
+        self._dim = list(self._system.keys())[0].dim
+        self._vec_count = len(self._system)
+
+        for k, v in system.items():
+            if k.radixes != self.radixes:
+                raise ValueError('States in system have mismatch in dimension.')
+            if v.radixes != self.radixes:
+                raise ValueError('States in system have mismatch in dimension.')
+
+        # Check overlap matrices
+        V = np.column_stack(list(self._system.keys()))
+        W = np.column_stack(list(self._system.values()))
+        Ov = V.conj().T @ V
+        Wv = W.conj().T @ W
+        if not np.allclose(Ov, Wv):
+            raise ValueError(
+                'State system is unsolvable:'
+                ' input and output overlap matrices do not match.',
+            )
+        self.target = W @ V.conj().T
+
+    @property
+    def num_qudits(self) -> int:
+        """The number of qudits in the state."""
+        return len(self.radixes)
+
+    @property
+    def dim(self) -> int:
+        """The vector dimension for this state."""
+        return self._dim
+
+    @property
+    def radixes(self) -> tuple[int, ...]:
+        """The number of orthogonal states for each qudit."""
+        return self._radixes
+
+    def __iter__(self) -> Iterator[StateVector]:
+        return self._system.__iter__()
+
+    def __len__(self) -> int:
+        return self._system.__len__()
+
+    def __getitem__(self, _key: StateVector) -> StateVector:
+        return self._system.__getitem__(_key)
+
+    def __contains__(self, _key: object) -> bool:
+        return self._system.__contains__(_key)
+
+    def is_qubit_only(self) -> bool:
+        """Return true if this unitary can only act on qubits."""
+        return all([radix == 2 for radix in self.radixes])
+
+    def is_qutrit_only(self) -> bool:
+        """Return true if this unitary can only act on qutrits."""
+        return all([radix == 3 for radix in self.radixes])
+
+    @staticmethod
+    def is_state_system(V: Any) -> TypeGuard[StateSystemLike]:
+        """
+        Check if V is a system of pure states.
+
+        Args:
+            V (Any): The vector to check.
+
+        Returns:
+            bool: True if V is a system of pure states.
+        """
+        if isinstance(V, StateSystem):
+            return True
+
+        if not isinstance(V, dict):
+            return False
+
+        for k, v in V.items():
+            if not StateVector.is_pure_state(k):
+                return False
+
+            if not StateVector.is_pure_state(v):
+                return False
+
+        return True
+
+
+StateSystemLike = Union[StateSystem, Dict[Any, Any]]
diff --git a/bqskit/qis/unitary/meta.py b/bqskit/qis/unitary/meta.py
index b6ef80916..3bc4f60e8 100644
--- a/bqskit/qis/unitary/meta.py
+++ b/bqskit/qis/unitary/meta.py
@@ -17,13 +17,13 @@ def __instancecheck__(cls, instance: Any) -> bool:
         Check if an instance is a `Unitary` instance.
 
         Additional checks for DifferentiableUnitary and
-        LocallyOptimizableUnitary. We check if the object has
-        the is_differentiable or is_locally_optimizable callable, an
-        instance method that maps nothing to a bool. If the object has
-        the method, then it must return true for isinstance to pass.
+        LocallyOptimizableUnitary. We check if the object has the
+        is_differentiable or is_locally_optimizable callable, an instance method
+        that maps nothing to a bool. If the object has the method, then it must
+        return true for isinstance to pass.
 
-        This can be used with composed classes to implement
-        conditional inheritance.
+        This can be used with composed classes to implement conditional
+        inheritance.
         """
         if cls.__name__ == 'DifferentiableUnitary':
             if hasattr(instance, 'is_differentiable'):
diff --git a/bqskit/qis/unitary/unitarymatrix.py b/bqskit/qis/unitary/unitarymatrix.py
index e77e1c6bb..c54b0338a 100644
--- a/bqskit/qis/unitary/unitarymatrix.py
+++ b/bqskit/qis/unitary/unitarymatrix.py
@@ -383,6 +383,10 @@ def is_unitary(U: Any, tol: float = 1e-8) -> TypeGuard[UnitaryLike]:
         if isinstance(U, UnitaryMatrix):
             return True
 
+        from bqskit.qis.state import StateSystem
+        if isinstance(U, StateSystem):
+            return False
+
         if not isinstance(U, np.ndarray):
             U = np.array(U)
 
diff --git a/bqskit/runtime/__init__.py b/bqskit/runtime/__init__.py
new file mode 100644
index 000000000..df16959a3
--- /dev/null
+++ b/bqskit/runtime/__init__.py
@@ -0,0 +1,189 @@
+"""
+=======================================================
+A Runtime for the Compile-time (:mod:`bqskit.runtime`)
+=======================================================
+
+.. currentmodule:: bqskit.runtime
+
+This package provides an execution environment for compiler passes,
+enabling them to parallelize and distribute their workload efficiently.
+
+Launching a Server
+##################
+
+To parallelize BQSKit compilations, you can launch a BQSKit Runtime
+server in either attached or detached mode, and submit a
+:class:`~bqskit.compiler.task.CompilationTask` through a connected
+:class:`~bqskit.compiler.compiler.Compiler`.
+
+By default, a compiler will automatically start and connect to an attached
+runtime server during initialization. In this mode, the compiler
+starts up and shuts down the server automatically and transparently to
+the end user. See :class:`~bqskit.runtime.attached.AttachedServer` for
+more info.
+
+A server running in attached mode is limited to a single machine. If you
+would like to parallelize BQSKit across multiple nodes over a network,
+you can launch a server independently in detached mode. Here, you first
+start :class:`~bqskit.runtime.manager.Manager` processes on all machines.
+Then link them all together by running a
+:class:`~bqskit.runtime.detached.DetachedServer` process, which Compilers'
+can connect to by passing the appropriate address in their constructor.
+
+Upon installing BQSKit, two shell scripts are added to your environment
+that can spawn managers and detached servers::
+
+    bqskit-manager
+
+and::
+
+    bqskit-server <address_of_bqskit_managers>
+
+Typically, you start managers first on all nodes in the desired cluster.
+Then you can start the server with a comma-separated list of all managers
+ip address and optionally ports. Once a server is started and has connected
+to the requested managers, no more managers can be added. You can see
+the `-h` option of each command or the
+:func:`~bqskit.runtime.detached.start_server` and
+:func:`~bqskit.runtime.manager.start_manager` entry points for
+more information.
+
+For more information on how to manage jobs submitted to a runtime server,
+see the :obj:`~bqskit.compiler` documentation.
+
+.. autosummary::
+    :toctree: autogen
+    :recursive:
+
+    ~bqskit.runtime.attached.AttachedServer
+    ~bqskit.runtime.detached.DetachedServer
+    ~bqskit.runtime.manager.Manager
+    ~bqskit.runtime.detached.start_server
+    ~bqskit.runtime.manager.start_manager
+
+Parallelize Pass Computation
+############################
+
+When developing a BQSKit compiler pass, you can hook into the active
+runtime server through the :func:`get_runtime` function. This returns a
+:class:`RuntimeHandle`, which you can use to submit, map, wait on, and
+cancel tasks in the execution environment.
+
+For more information on how to design a custom pass, see this (TODO, sorry,
+you can look at the source code of existing
+`passes <https://github.com/BQSKit/bqskit/blob/master/bqskit/passes>`_
+for a good example for the time being).
+
+.. autosummary::
+    :toctree: autogen
+    :recursive:
+
+    ~bqskit.runtime.get_runtime
+    ~bqskit.runtime.RuntimeHandle
+    ~bqskit.runtime.future.RuntimeFuture
+
+Standard Logging Interface
+##########################
+
+This system uses the standard python logging module to provide a familiar
+logging interface. On client processes -- the ones where you create the
+Compiler object -- you can configure python loggers like normal before
+submitting tasks and have the entire system honor that configuration.
+"""
+from __future__ import annotations
+
+import faulthandler
+import os
+from typing import Any
+from typing import Callable
+from typing import Protocol
+from typing import TYPE_CHECKING
+
+# Enable low-level fault handling: system crashes print a minimal trace.
+faulthandler.enable()
+
+
+# Disable multi-threading in BLAS libraries.
+os.environ['OMP_NUM_THREADS'] = '1'
+os.environ['OPENBLAS_NUM_THREADS'] = '1'
+os.environ['MKL_NUM_THREADS'] = '1'
+os.environ['NUMEXPR_NUM_THREADS'] = '1'
+os.environ['VECLIB_MAXIMUM_THREADS'] = '1'
+os.environ['RUST_BACKTRACE'] = '1'
+
+
+if TYPE_CHECKING:
+    from bqskit.runtime.future import RuntimeFuture
+
+
+class RuntimeHandle(Protocol):
+    """
+    A structural type capturing BQSKit Runtime capabilities.
+
+    A compiler pass designer can effortlessly parallelize certain aspects
+    of compilation using the :func:`submit` and :func:`map` methods.
+    These both will return :class:`~bqskit.runtime.future.RuntimeFuture`
+    objects, which can be awaited on using the standard python `await`
+    keyword. This can also be used to cancel tasks and wait on specific
+    events.
+
+    This should never be constructed directly and only accessed through
+    the :func:`get_runtime` function, since this is a structural type
+    definition and does not contain any implementation.
+    """
+
+    def submit(
+        self,
+        fn: Callable[..., Any],
+        *args: Any,
+        **kwargs: Any,
+    ) -> RuntimeFuture:
+        """Submit a `fn` to the runtime."""
+        ...
+
+    def map(
+        self,
+        fn: Callable[..., Any],
+        *args: Any,
+        **kwargs: Any,
+    ) -> RuntimeFuture:
+        """Map `fn` over the input arguments distributed across the runtime."""
+        ...
+
+    def cancel(self, future: RuntimeFuture) -> None:
+        """
+        Cancel all tasks associated with `future`.
+
+        This is asynchronous and non-pre-emptive. The cancel will return
+        immediately, the future it represents will become dead, and at some
+        point in the future, all tasks and data associated with the cancelled
+        one will be removed from the system.
+        """
+        ...
+
+    async def next(self, future: RuntimeFuture) -> list[tuple[int, Any]]:
+        """
+        Wait for and return the next batch of results from a map task.
+
+        Returns:
+            (list[tuple[int, Any]]): A list of the results that arrived
+                while the task was waiting. Each result is paired with
+                the index of its arguments in the original map call.
+        """
+        ...
+
+
+def get_runtime() -> RuntimeHandle:
+    """
+    Return a handle on the active runtime.
+
+    See :class:`RuntimeHandle` for more info.
+    """
+    from bqskit.runtime.worker import get_worker
+    return get_worker()
+
+
+default_server_port = 7472
+default_manager_port = 7473
+default_worker_port = 7474
+__all__ = ['get_runtime']
diff --git a/bqskit/runtime/address.py b/bqskit/runtime/address.py
new file mode 100644
index 000000000..8535588f3
--- /dev/null
+++ b/bqskit/runtime/address.py
@@ -0,0 +1,18 @@
+"""This module implements the RuntimeAddress NamedTuple."""
+from __future__ import annotations
+
+from typing import NamedTuple
+
+
+class RuntimeAddress(NamedTuple):
+    """
+    An address to a mailbox on a server or worker.
+
+    These are also used as unique identification for a task, since in this
+    system, a mailbox slot has a one-to-one relationship with a task. A task
+    will ship its result to its return address. That slot will only accept a
+    package from that task.
+    """
+    worker_id: int
+    mailbox_index: int
+    mailbox_slot: int
diff --git a/bqskit/runtime/attached.py b/bqskit/runtime/attached.py
new file mode 100644
index 000000000..79ae57da4
--- /dev/null
+++ b/bqskit/runtime/attached.py
@@ -0,0 +1,95 @@
+"""This module implements the AttachedServer runtime."""
+from __future__ import annotations
+
+import logging
+import selectors
+import uuid
+from multiprocessing.connection import Connection
+from typing import Any
+
+from bqskit.runtime import default_server_port
+from bqskit.runtime import default_worker_port
+from bqskit.runtime.base import ServerBase
+from bqskit.runtime.detached import DetachedServer
+from bqskit.runtime.detached import ServerMailbox
+from bqskit.runtime.direction import MessageDirection
+
+
+class AttachedServer(DetachedServer):
+    """
+    BQSKit Runtime Server in attached mode.
+
+    In attached mode, the runtime is started by the client. The client owns the
+    server and there is only one client on the server. Additionally, the client
+    is responsible for shutting down the server. There are no managers in the
+    attached architecture, only workers directly managed by a single server.
+    This architecture is designed for single-machine shared-memory settings.
+    BQSKit will, by default, create, manage, and shutdown an AttachedServer when
+    a :class:`~bqskit.compiler.compiler.Compiler` object is created.
+    """
+
+    def __init__(
+        self,
+        num_workers: int = -1,
+        port: int = default_server_port,
+        worker_port: int = default_worker_port,
+    ) -> None:
+        """
+        Create a server with `num_workers` workers.
+
+        Args:
+            num_workers (int): The number of workers to spawn. If -1,
+                then spawn as many workers as CPUs on the system.
+                (Default: -1).
+
+            port (int): The port this server will listen for clients on.
+                Default can be found in the
+                :obj:`~bqskit.runtime.default_server_port` global variable.
+
+            worker_port (int): The port this server will listen for workers
+                on. Default can be found in the
+                :obj:`~bqskit.runtime.default_worker_port` global variable.
+        """
+        ServerBase.__init__(self)
+
+        # See DetachedServer for more info on the following fields:
+        self.clients: dict[Connection, set[uuid.UUID]] = {}
+        self.tasks: dict[uuid.UUID, tuple[int, Connection]] = {}
+        self.mailbox_to_task_dict: dict[int, uuid.UUID] = {}
+        self.mailboxes: dict[int, ServerMailbox] = {}
+        self.mailbox_counter = 0
+
+        # Start workers
+        self.spawn_workers(num_workers, worker_port)
+
+        # Connect to client
+        client_conn = self.listen_once(port)
+        self.clients[client_conn] = set()
+        self.sel.register(
+            client_conn,
+            selectors.EVENT_READ,
+            MessageDirection.CLIENT,
+        )
+        self.logger.info('Connected to client.')
+
+    def handle_disconnect(self, conn: Connection) -> None:
+        """A client disconnect in attached mode is equal to a shutdown."""
+        self.handle_shutdown()
+
+
+def start_attached_server(
+    num_workers: int,
+    log_level: int,
+    **kwargs: Any,
+) -> None:
+    """Start a runtime server in attached mode."""
+    # Initialize runtime logging
+    _logger = logging.getLogger('bqskit-runtime')
+    _logger.setLevel(log_level)
+    _logger.addHandler(logging.StreamHandler())
+
+    # Initialize the server
+    server = AttachedServer(num_workers, **kwargs)
+
+    # Run the server
+    server.run()
diff --git a/bqskit/runtime/base.py b/bqskit/runtime/base.py
new file mode 100644
index 000000000..52c3b0d70
--- /dev/null
+++ b/bqskit/runtime/base.py
@@ -0,0 +1,616 @@
+"""This module implements the NodeBase abstract class."""
+from __future__ import annotations
+
+import abc
+import functools
+import logging
+import os
+import random
+import selectors
+import signal
+import socket
+import sys
+import time
+import traceback
+from multiprocessing import Process
+from multiprocessing.connection import Client
+from multiprocessing.connection import Connection
+from multiprocessing.connection import Listener
+from queue import Queue
+from threading import Thread
+from types import FrameType
+from typing import Any
+from typing import cast
+from typing import Sequence
+
+from bqskit.runtime import default_manager_port
+from bqskit.runtime import default_worker_port
+from bqskit.runtime.address import RuntimeAddress
+from bqskit.runtime.direction import MessageDirection
+from bqskit.runtime.message import RuntimeMessage
+from bqskit.runtime.result import RuntimeResult
+from bqskit.runtime.task import RuntimeTask
+from bqskit.runtime.worker import start_worker
+
+
+class RuntimeEmployee:
+    """Data structure for a boss's view of an employee."""
+
+    def __init__(
+        self,
+        conn: Connection,
+        total_workers: int,
+        process: Process | None = None,
+        num_tasks: int = 0,
+    ) -> None:
+        """Construct an employee with all resources idle."""
+        self.conn: Connection = conn
+        self.total_workers = total_workers
+        self.process = process
+        self.num_tasks = num_tasks
+        self.num_idle_workers = total_workers
+
+    def shutdown(self) -> None:
+        """Shutdown the employee."""
+        try:
+            self.conn.send((RuntimeMessage.SHUTDOWN, None))
+        except Exception:
+            pass
+
+        if self.process is not None:
+            self.process.join()
+
+        self.process = None
+        self.conn.close()
+
+    @property
+    def has_idle_resources(self) -> bool:
+        return self.num_idle_workers > 0
+
+
+def send_outgoing(node: ServerBase) -> None:
+    """Outgoing thread forwards messages as they are created."""
+    while True:
+        outgoing = node.outgoing.get()
+
+        if not node.running:
+            # NodeBase's handle_shutdown will put a dummy value in the
+            # queue to wake the thread up so it can exit safely.
+            # Hence the node.running check now rather than in the
+            # while condition.
+            break
+
+        outgoing[0].send((outgoing[1], outgoing[2]))
+        node.logger.debug(f'Sent message {outgoing[1].name}.')
+        node.logger.log(1, f'{outgoing[2]}\n')
+
+
+def sigint_handler(signum: int, _: FrameType | None, node: ServerBase) -> None:
+    """Interrupt the node."""
+    if not node.running:
+        return
+
+    node.running = False
+    node.terminate_hotline.send(b'\0')
+    node.logger.info('Server interrupted.')
+
+
+class ServerBase:
+    """Base class for all non-worker process nodes in the BQSKit Runtime."""
+
+    def __init__(self) -> None:
+        """Initialize a runtime node component."""
+
+        self.lower_id_bound = 0
+        self.upper_id_bound = int(2 ** 30)
+        """
+        The node starts with an ID range from 0 -> 2^30. ID ranges are then
+        assigned to managers by evenly splitting this range.
+
+        Managers then recursively split their range when connecting the sub-
+        managers. Finally, workers are assigned specific ids from within this
+        range.
+        """
+
+        self.running = True
+        """True while the node is running."""
+
+        self.sel = selectors.DefaultSelector()
+        """Used to efficiently idle and wake when communication is ready."""
+
+        p, self.terminate_hotline = socket.socketpair()
+        self.sel.register(p, selectors.EVENT_READ, MessageDirection.SIGNAL)
+        """Terminate hotline is used to unblock select while running."""
+
+        self.logger = logging.getLogger('bqskit-runtime')
+        """Logger used to print operational log messages."""
+
+        self.employees: list[RuntimeEmployee] = []
+        """Tracks this node's employees, which are managers or workers."""
+
+        self.conn_to_employee_dict: dict[Connection, RuntimeEmployee] = {}
+        """Used to find the employee associated with a message."""
+
+        # Safely and immediately exit on interrupt signals
+        handle = functools.partial(sigint_handler, node=self)
+        signal.signal(signal.SIGINT, handle)
+
+        # Start outgoing thread
+        self.outgoing: Queue[tuple[Connection, RuntimeMessage, Any]] = Queue()
+        self.outgoing_thread = Thread(target=send_outgoing, args=(self,))
+        self.outgoing_thread.start()
+        self.logger.info('Started outgoing thread.')
+
+    def connect_to_managers(self, ipports: Sequence[tuple[str, int]]) -> None:
+        """Connect to all managers given by endpoints in `ipports`."""
+        d = len(ipports)
+        self.step_size = (self.upper_id_bound - self.lower_id_bound) // d
+
+        # Establish connections with all managers
+        manager_conns = []
+        for i, (ip, port) in enumerate(ipports):
+            lb = self.lower_id_bound + (i * self.step_size)
+            ub = min(
+                self.lower_id_bound + ((i + 1) * self.step_size),
+                self.upper_id_bound,
+            )
+            manager_conns.append(self.connect_to_manager(ip, port, lb, ub))
+            self.logger.info(f'Connected to manager {i} at {ip}:{port}.')
+            self.logger.debug(f'Gave bounds {lb=} and {ub=} to manager {i}.')
+
+        # Wait for started messages from all managers and register them
+        self.total_workers = 0
+        for i, conn in enumerate(manager_conns):
+            msg, num_workers = conn.recv()
+            assert msg == RuntimeMessage.STARTED
+            self.employees.append(RuntimeEmployee(conn, num_workers))
+            self.conn_to_employee_dict[conn] = self.employees[-1]
+            self.sel.register(
+                conn,
+                selectors.EVENT_READ,
+                MessageDirection.BELOW,
+            )
+            self.logger.info(f'Registered manager {i} with {num_workers = }.')
+            self.total_workers += num_workers
+        self.num_idle_workers = self.total_workers
+
+        self.logger.info(f'Node has {self.total_workers} total workers.')
+
+    def connect_to_manager(
+        self,
+        ip: str,
+        port: int,
+        lb: int,
+        ub: int,
+    ) -> Connection:
+        """
+        Connect to a manager at the endpoint given by `ip` and `port`.
+
+        Args:
+            ip (str): The IP address where the manager is expected to be
+                listening.
+
+            port (int): The port number on which the manager is expected
+                to be listening.
+
+            lb (int): The ID lower bound to send to the manager.
+
+            ub (int): The ID upper bound to send to the manager.
+        """
+        max_retries = 5
+        wait_time = .25
+
+        for _ in range(max_retries):
+            try:
+                conn = Client((ip, port))
+            except ConnectionRefusedError:
+                time.sleep(wait_time)
+                wait_time *= 2
+            else:
+                conn.send((RuntimeMessage.CONNECT, (lb, ub)))
+                return conn
+
+        raise RuntimeError(f'Manager connection refused at {ip}:{port}')
+
+    def spawn_workers(
+        self,
+        num_workers: int = -1,
+        port: int = default_worker_port,
+    ) -> None:
+        """
+        Spawn worker processes.
+
+        Args:
+            num_workers (int): The number of workers to spawn. If -1,
+                then spawn as many workers as CPUs on the system.
+                (Default: -1).
+
+            port (int): The port this server will listen for workers on.
+                Default can be found in the
+                :obj:`~bqskit.runtime.default_worker_port` global variable.
+        """
+        if num_workers == -1:
+            oscount = os.cpu_count()
+            num_workers = oscount if oscount else 1
+
+        if self.lower_id_bound + num_workers >= self.upper_id_bound:
+            raise RuntimeError('Insufficient id range for workers.')
+
+        # Create and start all worker processes
+        procs = {}
+        for i in range(num_workers):
+            w_id = self.lower_id_bound + i
+            procs[w_id] = Process(target=start_worker, args=(w_id, port))
+            procs[w_id].start()
+            self.logger.debug(f'Stated worker process {i}.')
+
+        # Listen for the worker connections
+        family = 'AF_INET' if sys.platform == 'win32' else None
+        listener = Listener(('localhost', port), family, backlog=num_workers)
+        conns = [listener.accept() for _ in range(num_workers)]
+        listener.close()
+
+        # Organize all workers into the employees data structure
+        temp_reorder = {}
+        for i, conn in enumerate(conns):
+            msg, w_id = conn.recv()
+            assert msg == RuntimeMessage.STARTED
+            employee = RuntimeEmployee(conn, 1, procs[w_id])
+            temp_reorder[w_id - self.lower_id_bound] = employee
+            self.conn_to_employee_dict[conn] = employee
+
+        # The employess list needs to be sorted according to the IDs
+        for i in range(num_workers):
+            self.employees.append(temp_reorder[i])
+
+        # Register employee communication
+        for i, employee in enumerate(self.employees):
+            self.sel.register(
+                employee.conn,
+                selectors.EVENT_READ,
+                MessageDirection.BELOW,
+            )
+            self.logger.info(f'Registered worker {i}.')
+
+        self.step_size = 1
+        self.total_workers = num_workers
+        self.num_idle_workers = num_workers
+        self.logger.info(f'Node has spawned {num_workers} workers.')
+
+    def connect_to_workers(
+        self,
+        num_workers: int = -1,
+        port: int = default_worker_port,
+    ) -> None:
+        """
+        Connect to worker processes.
+
+        Args:
+            num_workers (int): The number of workers to expect. If -1,
+                then expect as many workers as CPUs on the system.
+                (Default: -1).
+
+            port (int): The port this server will listen for workers on.
+                Default can be found in the
+                :obj:`~bqskit.runtime.default_worker_port` global variable.
+        """
+        if num_workers == -1:
+            oscount = os.cpu_count()
+            num_workers = oscount if oscount else 1
+
+        self.logger.info(f'Expecting {num_workers} worker connections.')
+
+        if self.lower_id_bound + num_workers >= self.upper_id_bound:
+            raise RuntimeError('Insufficient id range for workers.')
+
+        # Listen for the worker connections
+        family = 'AF_INET' if sys.platform == 'win32' else None
+        listener = Listener(('localhost', port), family, backlog=num_workers)
+        conns = [listener.accept() for _ in range(num_workers)]
+        listener.close()
+
+        for i, conn in enumerate(conns):
+            w_id = self.lower_id_bound + i
+            self.outgoing.put((conn, RuntimeMessage.STARTED, w_id))
+            employee = RuntimeEmployee(conn, 1)
+            self.employees.append(employee)
+            self.conn_to_employee_dict[conn] = employee
+
+        # Register employee communication
+        for i, employee in enumerate(self.employees):
+            w_id = self.lower_id_bound + i
+            assert employee.conn.recv() == (RuntimeMessage.STARTED, w_id)
+            self.sel.register(
+                employee.conn,
+                selectors.EVENT_READ,
+                MessageDirection.BELOW,
+            )
+            self.logger.info(f'Registered worker {i}.')
+
+        self.step_size = 1
+        self.total_workers = num_workers
+        self.num_idle_workers = num_workers
+        self.logger.info(f'Node has connected to {num_workers} workers.')
+
+    def listen_once(self, port: int) -> Connection:
+        """Listen on `port` for a connection and return on first one."""
+        family = 'AF_INET' if sys.platform == 'win32' else None
+        listener = Listener(('0.0.0.0', port), family)
+        conn = listener.accept()
+        listener.close()
+        return conn
+
+    def run(self) -> None:
+        """Main loop."""
+        self.logger.info(f'{self.__class__.__name__} running...')
+
+        try:
+            while self.running:
+                # Wait for messages
+                events = self.sel.select()  # Say that 5 times fast
+
+                for key, _ in events:
+                    # Unpack message, payload, and direction.
+                    conn = cast(Connection, key.fileobj)
+                    direction = cast(MessageDirection, key.data)
+
+                    # If interrupted by signal, shutdown and exit
+                    if direction == MessageDirection.SIGNAL:
+                        self.logger.debug('Received interrupt signal.')
+                        self.handle_shutdown()
+                        return
+
+                    # Unpack and Log message
+                    try:
+                        msg, payload = conn.recv()
+                    except (EOFError, ConnectionResetError):
+                        self.handle_disconnect(conn)
+                        continue
+                    log = f'Received message {msg.name} from {direction.name}.'
+                    self.logger.debug(log)
+                    self.logger.log(1, f'{payload}\n')
+
+                    # Handle message
+                    self.handle_message(msg, direction, conn, payload)
+
+        except Exception:
+            exc_info = sys.exc_info()
+            error_str = ''.join(traceback.format_exception(*exc_info))
+            self.logger.error(error_str)
+            self.handle_system_error(error_str)
+
+        finally:
+            self.handle_shutdown()
+
+    @abc.abstractmethod
+    def handle_message(
+        self,
+        msg: RuntimeMessage,
+        direction: MessageDirection,
+        conn: Connection,
+        payload: Any,
+    ) -> None:
+        """
+        Process the message coming from `direction`.
+
+        Args:
+            msg (RuntimeMessage): The message type to handle.
+
+            direction (MessageDirection): The direction the message came from.
+
+            conn (Connection): The connection object where this came from.
+
+            payload (Any): The message data.
+        """
+
+    @abc.abstractmethod
+    def handle_system_error(self, error_str: str) -> None:
+        """
+        Handle an error in runtime code as opposed to client code.
+
+        This is called when an error arises in runtime code not in a
+        RuntimeTask's coroutine code.
+        """
+
+    def handle_shutdown(self) -> None:
+        """Shutdown the node and release resources."""
+        # Stop running
+        self.logger.info('Shutting down node.')
+        self.running = False
+
+        # Instruct employees to shutdown
+        for employee in self.employees:
+            employee.shutdown()
+        self.employees.clear()
+        self.logger.debug('Shutdown employees.')
+
+        # Close selector
+        self.sel.close()
+        self.logger.debug('Cleared selector.')
+
+        # Close outgoing thread
+        if self.outgoing_thread.is_alive():
+            self.outgoing.put(b'\0')  # type: ignore
+            self.outgoing_thread.join()
+            self.logger.debug('Joined outgoing thread.')
+            assert not self.outgoing_thread.is_alive()
+
+    def handle_disconnect(self, conn: Connection) -> None:
+        """Remove `conn` from the server."""
+        self.sel.unregister(conn)
+        conn.close()
+
+        # If one of my employees crashed/shutdown/disconnected, I shutdown
+        if conn in self.conn_to_employee_dict:
+            self.handle_shutdown()
+
+    def __del__(self) -> None:
+        """Ensure resources are cleaned up."""
+        self.handle_shutdown()
+
+    def assign_tasks(
+        self,
+        tasks: Sequence[RuntimeTask],
+    ) -> list[list[RuntimeTask]]:
+        """
+        Go through the tasks and assign each one to an employee.
+
+        Strategy:
+            - Assign first to idle workers evenly and randomly
+            - Employees with more idle workers get prioritized
+            - Assign remaining tasks to employees with the fewest tasks
+        """
+        # assignments will contain the assigned task list for each employee
+        assignments: list[list[RuntimeTask]] = [[] for _ in self.employees]
+
+        # Every employee's id repeated as many time as it has idle workers:
+        idle_id_repeated_list: list[int] = sum(
+            (
+                [i] * e.num_idle_workers
+                for i, e in enumerate(self.employees)
+            ), [],
+        )
+
+        # Shuffle to reduce chance of inefficiency described in handle_waiting
+        random.shuffle(idle_id_repeated_list)
+
+        # Assign tasks to idle workers in random order
+        for idle_employee_id, task in zip(idle_id_repeated_list, tasks):
+            assignments[idle_employee_id].append(task)
+
+        # Check if there are more tasks to be assigned
+        num_remaining_tasks = len(tasks) - len(idle_id_repeated_list)
+
+        if num_remaining_tasks <= 0:
+            return assignments
+
+        remaining_tasks = list(tasks[-num_remaining_tasks:])
+
+        # Sort the employees by how many tasks they have
+        ntasks = sorted([
+            (
+                e.num_tasks + len(assignments[i]),  # Consider idle assignments
+                random.random(),  # Random value for tie breaker
+                i,
+            )
+            for i, e in enumerate(self.employees)
+        ])
+
+        while len(remaining_tasks) > 0:
+            num_tasks, r, employee_id = ntasks[0]
+            assignments[employee_id].append(remaining_tasks.pop())
+            ntasks[0] = (num_tasks + 1, r, employee_id)
+
+            # Maintain sorted order by swapping up the updated count
+            idx = 0
+            while idx + 1 < len(ntasks) and ntasks[idx] > ntasks[idx + 1]:
+                ntasks[idx + 1], ntasks[idx] = ntasks[idx], ntasks[idx + 1]
+                idx += 1
+
+        return assignments
+
+    def schedule_tasks(self, tasks: Sequence[RuntimeTask]) -> None:
+        """Schedule tasks between this node's employees."""
+        if len(tasks) == 0:
+            return
+
+        assignments = self.assign_tasks(tasks)
+
+        for e, assignment in zip(self.employees, assignments):
+            num_tasks = len(assignment)
+
+            if num_tasks == 0:
+                continue
+
+            self.outgoing.put((e.conn, RuntimeMessage.SUBMIT_BATCH, assignment))
+
+            e.num_tasks += num_tasks
+            e.num_idle_workers -= min(num_tasks, e.num_idle_workers)
+
+        self.num_idle_workers = sum(e.num_idle_workers for e in self.employees)
+
+    def send_result_down(self, result: RuntimeResult) -> None:
+        """Send the `result` to the appropriate employee."""
+        dest_worker_id = result.return_address.worker_id
+
+        if not self.is_my_worker(dest_worker_id):
+            raise RuntimeError('Cannot send result to unmanaged worker.')
+
+        employee = self.get_employee_responsible_for(dest_worker_id)
+        self.outgoing.put((employee.conn, RuntimeMessage.RESULT, result))
+
+    def is_my_worker(self, worker_id: int) -> bool:
+        """Return true if `worker_id` is one of my workers (recursively)."""
+        employee_id = (worker_id - self.lower_id_bound) // self.step_size
+        return 0 <= employee_id < len(self.employees)
+
+    def get_employee_responsible_for(self, worker_id: int) -> RuntimeEmployee:
+        """Return the employee that manages `worker_id`."""
+        employee_id = (worker_id - self.lower_id_bound) // self.step_size
+        return self.employees[employee_id]
+
+    def broadcast_cancel(self, addr: RuntimeAddress) -> None:
+        """Broadcast a cancel message to my employees."""
+        for employee in self.employees:
+            self.outgoing.put((employee.conn, RuntimeMessage.CANCEL, addr))
+
+    def handle_waiting(self, conn: Connection, new_idle_count: int) -> None:
+        """
+        Record that an employee is idle with nothing to do.
+
+        There is a race condition here that is allowed. If an employee
+        sends a waiting message at the same time that this sends it a
+        task, it will still be marked waiting even though it is running
+        a task. We allow this for two reasons. First, the consequences are
+        minimal: this situation can only lead to one extra task assigned
+        to the worker that could otherwise go to a truly idle worker.
+        Second, it is unlikely in the common BQSKit workflows, which have
+        wide and shallow task graphs and each leaf task can require seconds
+        of runtime.
+        """
+        old_count = self.conn_to_employee_dict[conn].num_idle_workers
+        self.conn_to_employee_dict[conn].num_idle_workers = new_idle_count
+        self.num_idle_workers += (new_idle_count - old_count)
+        assert 0 <= self.num_idle_workers <= self.total_workers
+
+
+def parse_ipports(ipports_str: Sequence[str]) -> list[tuple[str, int]]:
+    """Parse command line ip and port inputs."""
+    ipports = []
+    for ipport_group in ipports_str:
+        for ipport in ipport_group.split(','):
+            if ipport.strip() == '':
+                continue
+            comps = ipport.strip().split(':')
+
+            if len(comps) == 1:
+                ip, port = comps[0], str(default_manager_port)
+                # Expect only managers to be listening on these ips
+                # so default port is manager's default port.
+
+            elif len(comps) == 2:
+                ip, port = comps
+
+            else:
+                raise ValueError(f'Invalid manager address: {ipport}.')
+
+            if not (0 <= int(port) < 65536):
+                raise ValueError(f'Invalid port number: {ipport}')
+
+            ipports.append((ip, int(port)))
+    return ipports
+
+
+def import_tests_package() -> None:
+    """
+    Import tests package recursively during detached architecture testing.
+
+    This should only be run by the CI test suite from the root bqskit folder.
+
+    credit: https://www.youtube.com/watch?v=t43zBsVcva0
+    """
+    sys.path.append(os.path.join(os.getcwd()))
+    import tests
+    import pkgutil
+    for mod in pkgutil.walk_packages(tests.__path__, f'{tests.__name__}.'):
+        __import__(mod.name, fromlist=['_trash'])
diff --git a/bqskit/runtime/detached.py b/bqskit/runtime/detached.py
new file mode 100644
index 000000000..3b817ef1a
--- /dev/null
+++ b/bqskit/runtime/detached.py
@@ -0,0 +1,428 @@
+"""This module implements the DetachedServer runtime."""
+from __future__ import annotations
+
+import argparse
+import logging
+import selectors
+import socket
+import time
+import uuid
+from dataclasses import dataclass
+from logging import LogRecord
+from multiprocessing.connection import Connection
+from multiprocessing.connection import Listener
+from threading import Thread
+from typing import Any
+from typing import cast
+from typing import List
+from typing import Sequence
+
+from bqskit.compiler.status import CompilationStatus
+from bqskit.compiler.task import CompilationTask
+from bqskit.runtime import default_server_port
+from bqskit.runtime.address import RuntimeAddress
+from bqskit.runtime.base import import_tests_package
+from bqskit.runtime.base import parse_ipports
+from bqskit.runtime.base import ServerBase
+from bqskit.runtime.direction import MessageDirection
+from bqskit.runtime.message import RuntimeMessage
+from bqskit.runtime.result import RuntimeResult
+from bqskit.runtime.task import RuntimeTask
+
+
+def listen(server: DetachedServer, port: int) -> None:
+    """Listening thread listens for client connections."""
+    listener = Listener(('0.0.0.0', port))
+    while server.running:
+        client = listener.accept()
+
+        if server.running:
+            # We check again that the server is running before registering
+            # the client because dummy data is sent to unblock
+            # listener.accept() during server shutdown
+            server.clients[client] = set()
+            server.sel.register(
+                client,
+                selectors.EVENT_READ,
+                MessageDirection.CLIENT,
+            )
+            server.logger.debug('Connected and registered new client.')
+
+    listener.close()
+
+
+@dataclass
+class ServerMailbox:
+    """A mailbox on a server is a final destination for a compilation task."""
+
+    result: Any = None
+    """Where the mailbox will store the result once it arrives."""
+
+    client_waiting: bool = False
+    """If true, the server knows a client is blocked waiting on this result."""
+
+    @property
+    def ready(self) -> bool:
+        """Return true if the mailbox result is ready and waiting delivery."""
+        return self.result is not None
+
+
+class DetachedServer(ServerBase):
+    """
+    BQSKit Runtime Server in detached mode.
+
+    In detached mode, the runtime is started separately from the client. Clients
+    can connect and disconnect while not shutting down the server. This
+    architecture is designed for the distributed setting, where managers manage
+    workers in shared memory and communicate with the server over a network.
+    """
+
+    def __init__(
+        self,
+        ipports: Sequence[tuple[str, int]],
+        port: int = default_server_port,
+    ) -> None:
+        """
+        Create a server and connect to the managers at `ipports`.
+
+        Args:
+            ipports (list[tuple[str, int]]): The ip and port pairs were
+                managers are expected to be listening for server connections.
+
+            port (int): The port this server will listen for clients on.
+                Default can be found in the
+                :obj:`~bqskit.runtime.default_server_port` global variable.
+        """
+        super().__init__()
+
+        self.clients: dict[Connection, set[uuid.UUID]] = {}
+        """Tracks all connected clients and all the tasks they have created."""
+
+        self.tasks: dict[uuid.UUID, tuple[int, Connection]] = {}
+        """Tracks all active CompilationTasks submitted to the cluster."""
+
+        self.mailbox_to_task_dict: dict[int, uuid.UUID] = {}
+        """Used to convert internal RuntimeTasks to client CompilationTasks."""
+
+        self.mailboxes: dict[int, ServerMailbox] = {}
+        """Mapping from mailbox ids to mailboxes."""
+
+        self.mailbox_counter = 0
+        """Counter to ensure all mailboxes have unique IDs."""
+
+        # Connect to managers
+        self.connect_to_managers(ipports)
+
+        # Start client listener
+        self.port = port
+        self.listen_thread = Thread(target=listen, args=(self, port))
+        self.listen_thread.start()
+        self.logger.info(f'Started client listener on port {self.port}.')
+
+    def handle_message(
+        self,
+        msg: RuntimeMessage,
+        direction: MessageDirection,
+        conn: Connection,
+        payload: Any,
+    ) -> None:
+        """Process the message coming from `direction`."""
+        if direction == MessageDirection.CLIENT:
+
+            if msg == RuntimeMessage.CONNECT:
+                pass
+
+            elif msg == RuntimeMessage.DISCONNECT:
+                self.handle_disconnect(conn)
+
+            elif msg == RuntimeMessage.SUBMIT:
+                ctask = cast(CompilationTask, payload)
+                self.handle_new_comp_task(conn, ctask)
+
+            elif msg == RuntimeMessage.REQUEST:
+                request = cast(uuid.UUID, payload)
+                self.handle_request(conn, request)
+
+            elif msg == RuntimeMessage.STATUS:
+                request = cast(uuid.UUID, payload)
+                self.handle_status(conn, request)
+
+            elif msg == RuntimeMessage.CANCEL:
+                request = cast(uuid.UUID, payload)
+                self.handle_cancel_comp_task(request)
+
+            else:
+                raise RuntimeError(f'Unexpected message type: {msg.name}')
+
+        elif direction == MessageDirection.BELOW:
+
+            if msg == RuntimeMessage.SUBMIT:
+                rtask = cast(RuntimeTask, payload)
+                self.schedule_tasks([rtask])
+
+            elif msg == RuntimeMessage.SUBMIT_BATCH:
+                rtasks = cast(List[RuntimeTask], payload)
+                self.schedule_tasks(rtasks)
+
+            elif msg == RuntimeMessage.RESULT:
+                result = cast(RuntimeResult, payload)
+                self.handle_result(result)
+
+            elif msg == RuntimeMessage.ERROR:
+                self.handle_error(payload)
+                return
+
+            elif msg == RuntimeMessage.LOG:
+                self.handle_log(payload)
+
+            elif msg == RuntimeMessage.CANCEL:
+                self.broadcast_cancel(payload)
+
+            elif msg == RuntimeMessage.SHUTDOWN:
+                self.handle_shutdown()
+
+            elif msg == RuntimeMessage.WAITING:
+                num_idle = cast(int, payload)
+                self.handle_waiting(conn, num_idle)
+
+            elif msg == RuntimeMessage.UPDATE:
+                task_diff = cast(int, payload)
+                self.conn_to_employee_dict[conn].num_tasks += task_diff
+
+            else:
+                raise RuntimeError(f'Unexpected message type: {msg.name}')
+
+        else:
+            raise RuntimeError(f'Unexpected message from {direction.name}.')
+
+    def handle_system_error(self, error_str: str) -> None:
+        """
+        Handle an error in runtime code as opposed to client code.
+
+        This is called when an error arises in runtime code not in a
+        RuntimeTask's coroutine code.
+        """
+        for client in self.clients.keys():
+            client.send((RuntimeMessage.ERROR, error_str))
+
+        # Sleep to ensure clients receive error message before shutdown
+        time.sleep(1)
+
+    def handle_shutdown(self) -> None:
+        """Shutdown the runtime."""
+        super().handle_shutdown()
+
+        # Close client connections
+        for client in self.clients.keys():
+            try:
+                client.close()
+            except Exception:
+                pass
+        self.clients.clear()
+        self.logger.debug('Cleared clients.')
+
+        # Close listener (hasattr checked for attachedserver shutdown)
+        if hasattr(self, 'listen_thread') and self.listen_thread.is_alive():
+            # Listener will be blocked on the accept call
+            # Create a dummy connection to unblock it
+            # Workaround credit: https://stackoverflow.com/questions/16734534
+            # Related bug: https://github.com/python/cpython/issues/76425
+            dummy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            dummy_socket.connect(('localhost', self.port))
+            dummy_socket.close()
+            self.listen_thread.join()
+            self.logger.debug('Joined listening thread.')
+
+    def handle_disconnect(self, conn: Connection) -> None:
+        """Disconnect a client connection from the runtime."""
+        super().handle_disconnect(conn)
+        tasks = self.clients.pop(conn)
+        for task_id in tasks:
+            self.handle_cancel_comp_task(task_id)
+        self.logger.info('Unregistered client.')
+
+    def handle_new_comp_task(
+        self,
+        conn: Connection,
+        task: CompilationTask,
+    ) -> None:
+        """Convert a :class:`CompilationTask` into an internal one."""
+        mailbox_id = self._get_new_mailbox_id()
+        self.tasks[task.task_id] = (mailbox_id, conn)
+        self.mailbox_to_task_dict[mailbox_id] = task.task_id
+        self.mailboxes[mailbox_id] = ServerMailbox()
+        self.logger.info(f'New CompilationTask: {task.task_id}.')
+
+        self.clients[conn].add(task.task_id)
+
+        internal_task = RuntimeTask(
+            (CompilationTask.run, (task,), {}),
+            RuntimeAddress(-1, mailbox_id, 0),
+            mailbox_id,
+            tuple(),
+            task.logging_level,
+            task.max_logging_depth,
+        )
+
+        self.schedule_tasks([internal_task])
+
+    def handle_request(self, conn: Connection, request: uuid.UUID) -> None:
+        """Record the requested task, and ship it as soon as it's ready."""
+        if request not in self.clients[conn] or request not in self.tasks:
+            self.outgoing.put((conn, RuntimeMessage.ERROR, 'Unknown task.'))
+            self.handle_disconnect(conn)  # Bad client
+            return
+
+        # Get the mailbox associated with this task.
+        mailbox_id = self.tasks[request][0]
+        box = self.mailboxes[mailbox_id]
+
+        if box.ready:
+            # If the result has already arrived, ship it to the client.
+            self.logger.info(f'Responding to request for task {request}.')
+            self.outgoing.put((conn, RuntimeMessage.RESULT, box.result))
+            self.mailboxes.pop(mailbox_id)
+            self.clients[conn].remove(request)
+            # t_id is not removed from self.tasks or
+            # self.mailbox_to_task_dict incase there are left
+            # over log messages arriving.
+
+        else:
+            # Otherwise, note that the client is waiting on this result.
+            # This is so the result will be shipped the instant it arrives.
+            box.client_waiting = True
+
+    def handle_status(self, conn: Connection, request: uuid.UUID) -> None:
+        """Inform the client if the task is finished or not."""
+        if request not in self.clients[conn] or request not in self.tasks:
+            # This task is unknown to the system
+            m = (conn, RuntimeMessage.STATUS, CompilationStatus.UNKNOWN)
+            self.outgoing.put(m)
+
+        # Get the mailbox associated with this task.
+        mailbox_id = self.tasks[request][0]
+        box = self.mailboxes[mailbox_id]
+
+        # Send status
+        s = CompilationStatus.DONE if box.ready else CompilationStatus.RUNNING
+        self.outgoing.put((conn, RuntimeMessage.STATUS, s))
+
+    def handle_cancel_comp_task(self, request: uuid.UUID) -> None:
+        """Cancel a compilation task in the system."""
+        self.logger.info(f'Cancelling: {request}.')
+
+        # Remove task from server data
+        mailbox_id, client_conn = self.tasks[request]
+        self.mailboxes.pop(mailbox_id)
+        if client_conn in self.clients:
+            self.clients[client_conn].remove(request)
+
+        # Forward internal cancel messages
+        addr = RuntimeAddress(-1, mailbox_id, 0)
+        self.broadcast_cancel(addr)
+
+        # Acknowledge the client's cancel request
+        if not client_conn.closed:
+            # Check if it closed first since the client may have disconnected
+            self.outgoing.put((client_conn, RuntimeMessage.CANCEL, None))
+
+    def handle_result(self, result: RuntimeResult) -> None:
+        """Either store the result here or ship it to the destination worker."""
+        # Record a task has been completed
+        self.get_employee_responsible_for(result.completed_by).num_tasks -= 1
+
+        # Check if the result is for a client
+        if result.return_address.worker_id == -1:
+            mailbox_id = result.return_address.mailbox_index
+            if mailbox_id not in self.mailboxes:
+                return  # Silently discard results from cancelled tasks
+
+            box = self.mailboxes[mailbox_id]
+            box.result = result.result
+            t_id = self.mailbox_to_task_dict[mailbox_id]
+            self.logger.info(f'Finished: {t_id}.')
+
+            if box.client_waiting:
+                self.logger.info(f'Responding to request for task {t_id}.')
+                m = (self.tasks[t_id][1], RuntimeMessage.RESULT, box.result)
+                self.outgoing.put(m)
+                self.clients[self.tasks[t_id][1]].remove(t_id)
+                self.mailboxes.pop(mailbox_id)
+                # t_id is not removed from self.tasks or
+                # self.mailbox_to_task_dict incase there are left
+                # over log messages arriving.
+
+        else:
+            self.send_result_down(result)
+
+    def handle_error(self, error_payload: tuple[int, str]) -> None:
+        """Forward an error to the appropriate client and disconnect it."""
+        if not isinstance(error_payload, tuple):
+            # Internal errors may bubble up without a task_id
+            assert isinstance(error_payload, str)
+            self.handle_system_error(error_payload)
+            self.handle_shutdown()
+            raise RuntimeError(error_payload)
+
+        tid = error_payload[0]
+        conn = self.tasks[self.mailbox_to_task_dict[tid]][1]
+        self.outgoing.put((conn, RuntimeMessage.ERROR, error_payload[1]))
+
+    def handle_log(self, log_payload: tuple[int, LogRecord]) -> None:
+        """Forward logs to appropriate client."""
+        tid = log_payload[0]
+        conn = self.tasks[self.mailbox_to_task_dict[tid]][1]
+        self.outgoing.put((conn, RuntimeMessage.LOG, log_payload[1]))
+
+    def _get_new_mailbox_id(self) -> int:
+        """Unique mailbox id counter."""
+        new_id = self.mailbox_counter
+        self.mailbox_counter += 1
+        return new_id
+
+
+def start_server() -> None:
+    """Entry point for a detached runtime server process."""
+    parser = argparse.ArgumentParser(
+        prog='bqskit-server',
+        description='Launch a BQSKit runtime server process.',
+    )
+    parser.add_argument(
+        'managers',
+        nargs='+',
+        help='The ip and port pairs were managers are expected to be waiting.',
+    )
+    parser.add_argument(
+        '-p', '--port',
+        type=int,
+        default=default_server_port,
+        help='The port this server will listen for clients on.',
+    )
+    parser.add_argument(
+        '--verbose', '-v',
+        action='count',
+        default=0,
+        help='Enable logging of increasing verbosity, either -v, -vv, or -vvv.',
+    )
+    parser.add_argument(
+        '--import-tests', '-i',
+        action='store_true',
+        help='Import the bqskit tests package; used during testing.',
+    )
+    args = parser.parse_args()
+    ipports = parse_ipports(args.managers)
+
+    # Set up logging
+    _logger = logging.getLogger('bqskit-runtime')
+    _logger.setLevel([30, 20, 10, 1][min(args.verbose, 3)])
+    _logger.addHandler(logging.StreamHandler())
+
+    # Import tests package recursively
+    if args.import_tests:
+        import_tests_package()
+
+    # Create the server
+    server = DetachedServer(ipports, args.port)
+
+    # Start the server
+    server.run()
diff --git a/bqskit/runtime/direction.py b/bqskit/runtime/direction.py
new file mode 100644
index 000000000..affffda5e
--- /dev/null
+++ b/bqskit/runtime/direction.py
@@ -0,0 +1,20 @@
+"""This module implements the MessageDirection enum."""
+from __future__ import annotations
+
+from enum import IntEnum
+
+
+class MessageDirection(IntEnum):
+    """The direction a message came from."""
+
+    BELOW = 0
+    """This describes messages coming from a child manager/worker."""
+
+    ABOVE = 1
+    """This describes messages coming from a parent server/manager."""
+
+    CLIENT = 2
+    """This describes messages coming from a client."""
+
+    SIGNAL = 3
+    """This describes internal directives created by a signal handler."""
diff --git a/bqskit/runtime/future.py b/bqskit/runtime/future.py
new file mode 100644
index 000000000..70f6ac2cc
--- /dev/null
+++ b/bqskit/runtime/future.py
@@ -0,0 +1,56 @@
+"""This module implements the RuntimeFuture class."""
+from __future__ import annotations
+
+from typing import Any
+
+
+class RuntimeFuture:
+    """
+    An awaitable future.
+
+    These future objects must be awaited in the same task that created them.
+    They cannot be used as input to other tasks.
+    """
+
+    def __init__(self, mailbox_id: int) -> None:
+        """Initialize a future tied to a local mailbox."""
+
+        self.mailbox_id = mailbox_id
+        """The mailbox id where this future's result will be shipped."""
+
+        self._next_flag = False
+        """If flag is set, then awaiting returns next result."""
+
+    def __await__(self) -> Any:
+        """
+        Wait on a result to be delivered.
+
+        Informs the event loop which mailbox this is waiting on.
+        """
+        if self._next_flag:
+            return (yield self)
+
+        return (yield self)
+
+    def __getstate__(self) -> Any:
+        """Prevent a future from being sent to another process."""
+        raise NotImplementedError(
+            'These future objects must be awaited in the same task that'
+            ' created them. They cannot be inputted to other tasks.',
+        )
+
+    @property
+    def _done(self) -> bool:
+        """
+        Return true if the future is ready.
+
+        Warning: Polling on futures in a busy-wait loop can cause deadlock.
+        It is best to await on futures rather than continuously polling.
+        Use at your own risk.
+        """
+        from bqskit.runtime.worker import get_worker
+
+        if self.mailbox_id not in get_worker()._mailboxes:
+            return True
+
+        return get_worker()._mailboxes[self.mailbox_id].ready
diff --git a/bqskit/runtime/manager.py b/bqskit/runtime/manager.py
new file mode 100644
index 000000000..9d2469cc9
--- /dev/null
+++ b/bqskit/runtime/manager.py
@@ -0,0 +1,331 @@
+"""This module implements the Manager class."""
+from __future__ import annotations
+
+import argparse
+import logging
+import selectors
+import time
+from multiprocessing.connection import Connection
+from typing import Any
+from typing import cast
+from typing import List
+from typing import Sequence
+
+from bqskit.runtime import default_manager_port
+from bqskit.runtime import default_worker_port
+from bqskit.runtime.address import RuntimeAddress
+from bqskit.runtime.base import import_tests_package
+from bqskit.runtime.base import parse_ipports
+from bqskit.runtime.base import ServerBase
+from bqskit.runtime.direction import MessageDirection
+from bqskit.runtime.message import RuntimeMessage
+from bqskit.runtime.result import RuntimeResult
+from bqskit.runtime.task import RuntimeTask
+
+
+class Manager(ServerBase):
+    """
+    BQSKit Runtime Manager.
+
+    A Manager is a middle node in the process hierarchy and is responsible for
+    managing workers or other managers. The manager is part of the detached
+    architecture. Here managers are started individually as separate processes,
+    which in turn start their own workers. Then, if necessary, more managers can
+    be started to manage the level-1 managers, and so on, until finally, a
+    detached server is started and clients can connect.
+    """
+
+    def __init__(
+        self,
+        port: int = default_manager_port,
+        num_workers: int = -1,
+        ipports: list[tuple[str, int]] | None = None,
+        worker_port: int = default_worker_port,
+        only_connect: bool = False,
+    ) -> None:
+        """
+        Create a manager instance in one of two ways:
+
+        1) Leave all options default and start a manager which spawns and
+            manages as many worker processes as there are os threads.
+            You can also specify the number of workers to spawn via
+            the `num_workers` parameter. In this mode, the manager
+            is a level-1 manager and manages workers.
+
+        2) Specify ip and port pairs, then assume at each endpoint there
+            is a listening manager and attempt to establish a connection.
+            In this mode, the manager will not spawn any workers and just
+            manage the specified managers.
+
+        In either case, if any problems arise during startup, no recovery
+        is attempted and the manager terminates.
+
+        Args:
+            port (int): The port this manager listens for server connections.
+                Default can be found in the
+                :obj:`~bqskit.runtime.default_manager_port` global variable.
+
+            num_workers (int): The number of workers to spawn. If -1,
+                then spawn as many workers as CPUs on the system.
+                (Default: -1). Ignored if `ipports` is not None.
+
+            ipports (list[tuple[str, int]] | None): If not None, then all
+                the addresses and ports of running managers to connect to.
+
+            worker_port (int): The port this server will listen for workers
+                on. Default can be found in the
+                :obj:`~bqskit.runtime.default_worker_port` global variable.
+
+            only_connect (bool): If true, do not spawn workers, only connect
+                to already spawned workers.
+        """
+        super().__init__()
+
+        # Connect upstream
+        self.upstream = self.listen_once(port)
+
+        # Handshake with upstream
+        msg, payload = self.upstream.recv()
+        assert msg == RuntimeMessage.CONNECT
+        self.lower_id_bound = payload[0]
+        self.upper_id_bound = payload[1]
+        self.sel.register(
+            self.upstream,
+            selectors.EVENT_READ,
+            MessageDirection.ABOVE,
+        )
+
+        # Case 1: spawn and manage workers
+        if ipports is None:
+            if only_connect:
+                self.connect_to_workers(num_workers, worker_port)
+            else:
+                self.spawn_workers(num_workers, worker_port)
+
+        # Case 2: Connect to managers at ipports
+        else:
+            self.connect_to_managers(ipports)
+
+        # Track info on sent messages to reduce redundant messages:
+        self.last_num_idle_sent_up = self.total_workers
+
+        # Inform upstream we are starting
+        msg = (self.upstream, RuntimeMessage.STARTED, self.total_workers)
+        self.outgoing.put(msg)
+        self.logger.info('Sent start message upstream.')
+
+    def handle_message(
+        self,
+        msg: RuntimeMessage,
+        direction: MessageDirection,
+        conn: Connection,
+        payload: Any,
+    ) -> None:
+        """Process the message coming from `direction`."""
+        if direction == MessageDirection.ABOVE:
+
+            if msg == RuntimeMessage.SUBMIT:
+                rtask = cast(RuntimeTask, payload)
+                self.schedule_tasks([rtask])
+                self.update_upstream_idle_workers()
+
+            elif msg == RuntimeMessage.SUBMIT_BATCH:
+                rtasks = cast(List[RuntimeTask], payload)
+                self.schedule_tasks(rtasks)
+                self.update_upstream_idle_workers()
+
+            elif msg == RuntimeMessage.RESULT:
+                result = cast(RuntimeResult, payload)
+                self.send_result_down(result)
+
+            elif msg == RuntimeMessage.CANCEL:
+                addr = cast(RuntimeAddress, payload)
+                self.broadcast_cancel(addr)
+
+            elif msg == RuntimeMessage.SHUTDOWN:
+                self.handle_shutdown()
+
+            else:
+                raise RuntimeError(f'Unexpected message type: {msg.name}')
+
+        elif direction == MessageDirection.BELOW:
+
+            if msg == RuntimeMessage.SUBMIT:
+                rtask = cast(RuntimeTask, payload)
+                self.send_up_or_schedule_tasks([rtask])
+                self.update_upstream_idle_workers()
+
+            elif msg == RuntimeMessage.SUBMIT_BATCH:
+                rtasks = cast(List[RuntimeTask], payload)
+                self.send_up_or_schedule_tasks(rtasks)
+                self.update_upstream_idle_workers()
+
+            elif msg == RuntimeMessage.RESULT:
+                result = cast(RuntimeResult, payload)
+                self.handle_result_from_below(result)
+
+            elif msg == RuntimeMessage.WAITING:
+                num_idle = cast(int, payload)
+                self.handle_waiting(conn, num_idle)
+                self.update_upstream_idle_workers()
+
+            elif msg == RuntimeMessage.UPDATE:
+                task_diff = cast(int, payload)
+                self.handle_update(conn, task_diff)
+
+            else:
+                # Forward all other messages up
+                self.outgoing.put((self.upstream, msg, payload))
+
+        else:
+            raise RuntimeError(f'Unexpected message from {direction.name}.')
+
+    def handle_system_error(self, error_str: str) -> None:
+        """
+        Handle an error in runtime code as opposed to client code.
+
+        This is called when an error arises in runtime code not in a
+        RuntimeTask's coroutine code.
+        """
+        try:
+            self.upstream.send((RuntimeMessage.ERROR, error_str))
+
+            # Sleep to ensure server receives error message before shutdown
+            time.sleep(1)
+
+        except Exception:
+            # If server has crashed then just exit
+            pass
+
+    def handle_shutdown(self) -> None:
+        """Shutdown the manager and clean up spawned processes."""
+        super().handle_shutdown()
+
+        # Forward shutdown message upwards if possible
+        try:
+            self.upstream.send((RuntimeMessage.SHUTDOWN, None))
+            self.upstream.close()
+
+        except Exception:
+            # If server has already shutdown or crashed, just exit
+            pass
+
+    def send_up_or_schedule_tasks(self, tasks: Sequence[RuntimeTask]) -> None:
+        """Either send the tasks upstream or schedule them downstream."""
+        num_idle = self.num_idle_workers
+
+        if num_idle != 0:
+            self.outgoing.put((self.upstream, RuntimeMessage.UPDATE, num_idle))
+            self.schedule_tasks(tasks[:num_idle])
+
+        if len(tasks) > num_idle:
+            self.outgoing.put((
+                self.upstream,
+                RuntimeMessage.SUBMIT_BATCH,
+                tasks[num_idle:],
+            ))
+
+    def handle_result_from_below(self, result: RuntimeResult) -> None:
+        """Forward the result to its destination and track the completion."""
+        # Record a task has been completed
+        self.get_employee_responsible_for(result.completed_by).num_tasks -= 1
+
+        # Forward result to final destination
+        if self.is_my_worker(result.return_address.worker_id):
+            self.send_result_down(result)
+            self.outgoing.put((self.upstream, RuntimeMessage.UPDATE, -1))
+
+        else:
+            # If its destination worker is not an employee of mine,
+            # then my boss will know where to send this result.
+            self.outgoing.put((self.upstream, RuntimeMessage.RESULT, result))
+
+    def update_upstream_idle_workers(self) -> None:
+        """Update the total number of idle workers upstream."""
+        if self.num_idle_workers != self.last_num_idle_sent_up:
+            self.last_num_idle_sent_up = self.num_idle_workers
+            m = (self.upstream, RuntimeMessage.WAITING, self.num_idle_workers)
+            self.outgoing.put(m)
+
+    def handle_update(self, conn: Connection, task_diff: int) -> None:
+        """Handle a task count update from a lower level manager or worker."""
+        self.conn_to_employee_dict[conn].num_tasks += task_diff
+        self.outgoing.put((self.upstream, RuntimeMessage.UPDATE, task_diff))
+
+
+def start_manager() -> None:
+    """Entry point for runtime manager processes."""
+    parser = argparse.ArgumentParser(
+        prog='bqskit-manager',
+        description='Launch a BQSKit runtime manager process.',
+    )
+    parser.add_argument(
+        '-n', '--num-workers',
+        default=-1,
+        type=int,
+        help='The number of workers to spawn. If negative, will spawn'
+        ' one worker for each available CPU. Defaults to -1.',
+    )
+    parser.add_argument(
+        '-m', '--managers',
+        nargs='+',
+        help='The ip and port pairs were managers are expected to be waiting.',
+    )
+    parser.add_argument(
+        '-p', '--port',
+        type=int,
+        default=default_manager_port,
+        help='The port this manager will listen for servers on.',
+    )
+    parser.add_argument(
+        '-w', '--worker-port',
+        type=int,
+        default=default_worker_port,
+        help='The port this manager will listen for workers on.',
+    )
+    parser.add_argument(
+        '-x', '--only-connect',
+        action='store_true',
+        help='Do not spawn workers, only connect to them.',
+    )
+    parser.add_argument(
+        '-v', '--verbose',
+        action='count',
+        default=0,
+        help='Enable logging of increasing verbosity, either -v, -vv, or -vvv.',
+    )
+    parser.add_argument(
+        '-i', '--import-tests',
+        action='store_true',
+        help='Import the bqskit tests package; used during testing.',
+    )
+    args = parser.parse_args()
+
+    # If ips and ports were provided parse them
+    ipports = None if args.managers is None else parse_ipports(args.managers)
+
+    # Set up logging
+    _logger = logging.getLogger('bqskit-runtime')
+    _logger.setLevel([30, 20, 10, 1][min(args.verbose, 3)])
+    _logger.addHandler(logging.StreamHandler())
+
+    # Import tests package recursively
+    if args.import_tests:
+        import_tests_package()
+
+    if args.num_workers < -1:
+        num_workers = -1
+    else:
+        num_workers = args.num_workers
+
+    # Create the manager
+    manager = Manager(
+        args.port,
+        num_workers,
+        ipports,
+        args.worker_port,
+        args.only_connect,
+    )
+
+    # Start the manager
+    manager.run()
diff --git a/bqskit/runtime/message.py b/bqskit/runtime/message.py
new file mode 100644
index 000000000..63f687048
--- /dev/null
+++ b/bqskit/runtime/message.py
@@ -0,0 +1,22 @@
+"""This module implements the RuntimeMessage enum."""
+from __future__ import annotations
+
+from enum import IntEnum
+
+
+class RuntimeMessage(IntEnum):
+    """An message sent between processes in the BQSKit runtime."""
+    CONNECT = 0
+    DISCONNECT = 1
+    STARTED = 2
+    SHUTDOWN = 3
+    ERROR = 4
+    REQUEST = 5
+    RESULT = 6
+    SUBMIT = 7
+    SUBMIT_BATCH = 8
+    STATUS = 9
+    LOG = 10
+    CANCEL = 11
+    WAITING = 12
+    UPDATE = 13
diff --git a/bqskit/runtime/result.py b/bqskit/runtime/result.py
new file mode 100644
index 000000000..9dc2725d8
--- /dev/null
+++ b/bqskit/runtime/result.py
@@ -0,0 +1,14 @@
+"""This module implements the RuntimeResult NamedTuple."""
+from __future__ import annotations
+
+from typing import Any
+from typing import NamedTuple
+
+from bqskit.runtime.address import RuntimeAddress
+
+
+class RuntimeResult(NamedTuple):
+    """The result of a task, ready to be shipped to its destination."""
+    return_address: RuntimeAddress
+    result: Any
+    completed_by: int
diff --git a/bqskit/runtime/task.py b/bqskit/runtime/task.py
new file mode 100644
index 000000000..b55037a87
--- /dev/null
+++ b/bqskit/runtime/task.py
@@ -0,0 +1,112 @@
+"""This module implements the RuntimeTask class."""
+from __future__ import annotations
+
+import inspect
+import logging
+from typing import Any
+from typing import Coroutine
+
+from bqskit.runtime.address import RuntimeAddress
+
+
+class RuntimeTask:
+    """
+    BQSKit Runtime's internal task structure.
+
+    The task exists in two phases:
+        1. In the initial phase, the task is a context-less function
+        and arguments grouped with a return address. It is expected
+        when the task is finished executing, to ship the result to
+        the return address.
+
+        2. When the task has been scheduled to run on a worker, it is
+        initialized into a coroutine that can be executed in steps.
+    """
+
+    task_counter = 0
+    """Per-process task id counter."""
+
+    def __init__(
+        self,
+        fnargs: tuple[Any, Any, Any],
+        return_address: RuntimeAddress,
+        comp_task_id: int,
+        breadcrumbs: tuple[RuntimeAddress, ...],
+        logging_level: int | None = None,
+        max_logging_depth: int = -1,
+    ) -> None:
+        """Create the task with a new id and return address."""
+        RuntimeTask.task_counter += 1
+        self.task_id = RuntimeTask.task_counter
+
+        self.fnargs = fnargs
+        """Tuple of function pointer, arguments, and keyword arguments."""
+
+        self.return_address = return_address
+        """Where the result of this task should be sent."""
+
+        self.logging_level = logging_level
+        """Logs with levels >= to this get emitted, if None always emit."""
+
+        self.comp_task_id = comp_task_id
+        """The mailbox id of the this task's root task."""
+
+        self.breadcrumbs = breadcrumbs
+        """All of this task's parent tasks' addresses in order."""
+
+        self.max_logging_depth = max_logging_depth
+        """Logs are not emitted for tasks with this many or more parents."""
+
+        self.coro: Coroutine[Any, Any, Any] | None = None
+        """The coroutine containing this tasks code."""
+
+        # self.send: Any = None
+        # """A register that both the coroutine and task have access to."""
+
+        self.desired_box_id: int | None = None
+        """When waiting on a mailbox, this stores that mailbox's id."""
+
+        self.owned_mailboxes: list[int] = []
+        """The mailbox ids that this task owns."""
+
+        self.wake_on_next: bool = False
+        """Set to true if this task should wake immediately on a result."""
+
+    def step(self, send_val: Any = None) -> Any:
+        """Execute one step of the task."""
+        if self.coro is None:
+            raise RuntimeError('Task has not been initialized.')
+
+        # Reset previously set await flags
+        self.wake_on_next = False
+        self.desired_box_id = None
+
+        # Set logging level
+        old_level = logging.getLogger().getEffectiveLevel()
+        if (
+            self.max_logging_depth < 0
+            or len(self.breadcrumbs) <= self.max_logging_depth
+        ):
+            logging.getLogger().setLevel(0)
+
+        # Execute a task step
+        to_return = self.coro.send(send_val)
+
+        # Reset logging
+        logging.getLogger().setLevel(old_level)
+
+        return to_return
+
+    def start(self) -> None:
+        """Initialize the task."""
+        self.coro = self.run()
+
+    async def run(self) -> Any:
+        """Task coroutine wrapper."""
+        if inspect.iscoroutinefunction(self.fnargs[0]):
+            return await self.fnargs[0](*self.fnargs[1], **self.fnargs[2])
+        return self.fnargs[0](*self.fnargs[1], **self.fnargs[2])
+
+    def is_descendant_of(self, addr: RuntimeAddress) -> bool:
+        """Return true if `addr` identifies a parent (or this) task."""
+        return addr == self.return_address or addr in self.breadcrumbs
diff --git a/bqskit/runtime/worker.py b/bqskit/runtime/worker.py
new file mode 100644
index 000000000..081e41dfc
--- /dev/null
+++ b/bqskit/runtime/worker.py
@@ -0,0 +1,703 @@
+"""This module implements BQSKit Runtime's Worker."""
+from __future__ import annotations
+
+import argparse
+import logging
+import os
+import signal
+import sys
+import time
+import traceback
+from collections import OrderedDict
+from dataclasses import dataclass
+from multiprocessing import Process
+from multiprocessing.connection import Client
+from multiprocessing.connection import Connection
+from multiprocessing.connection import wait
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import List
+
+from bqskit.runtime import default_worker_port
+from bqskit.runtime.address import RuntimeAddress
+from bqskit.runtime.future import RuntimeFuture
+from bqskit.runtime.message import RuntimeMessage
+from bqskit.runtime.result import RuntimeResult
+from bqskit.runtime.task import RuntimeTask
+
+
+class WorkerQueue():
+    """The worker's task FIFO queue."""
+
+    def __init__(self) -> None:
+        """
+        Initialize the worker queue.
+
+        An OrderedDict is used to internally store the task. This prevents the
+        same task appearing multiple times in the queue, while also ensuring
+        O(1) operations.
+        """
+        self._queue: OrderedDict[RuntimeAddress, None] = OrderedDict()
+
+    def put(self, addr: RuntimeAddress) -> None:
+        """Enqueue a task by its address."""
+        if addr not in self._queue:
+            self._queue[addr] = None
+
+    def get(self) -> RuntimeAddress:
+        """Get the next task to run."""
+        return self._queue.popitem(last=False)[0]
+
+    def empty(self) -> bool:
+        """Check if the queue is empty."""
+        return len(self._queue) == 0
+
+
+@dataclass
+class WorkerMailbox:
+    """
+    A mailbox on a worker is a final destination for a task's result.
+
+    When a task is created, a mailbox is also created with an associated future.
+    The parent task can await on the future, letting the worker's event loop
+    know it is waiting on the associated result. When a result arrives, it is
+    placed in the appropriate mailbox and the waiting task is placed into the
+    ready queue.
+    """
+    expecting_single_result: bool = False
+    expected_num_results: int = 0
+    result: Any = None
+    num_results: int = 0
+    dest_addr: RuntimeAddress | None = None
+    fresh_results: list[Any] | None = None
+
+    @property
+    def ready(self) -> bool:
+        """Return true if the mailbox has all expected results."""
+        return (
+            self.num_results >= self.expected_num_results
+            and self.num_results != 0
+        )
+
+    @property
+    def has_task_waiting(self) -> bool:
+        """Return True if a task is waiting on the result of this box."""
+        return self.dest_addr is not None
+
+    @staticmethod
+    def new_mailbox(num_results: int | None = None) -> WorkerMailbox:
+        """
+        Create a new mailbox with `num_results` slots.
+
+        If `num_results` is None (by default), then the mailbox will only have
+        one slot and expect one result.
+        """
+        if num_results is None:
+            return WorkerMailbox(True, 1)
+
+        return WorkerMailbox(False, num_results, [None] * num_results)
+
+    def get_new_results(self) -> list[tuple[int, Any]]:
+        """Return and reset the results that have come in since previous
+        call."""
+        assert self.fresh_results is not None
+        out = self.fresh_results
+        self.fresh_results = []
+        return out
+
+    def deposit_result(self, result: RuntimeResult) -> None:
+        """Store the result in the mailbox."""
+        self.num_results += 1
+        slot_id = result.return_address.mailbox_slot
+
+        # Record as fresh result
+        if self.fresh_results is None:
+            self.fresh_results = []
+        self.fresh_results.append((slot_id, result.result))
+
+        if self.expecting_single_result:
+            self.result = result.result
+        else:
+            self.result[slot_id] = result.result
+
+
+class Worker:
+    """
+    BQSKit Runtime's Worker.
+
+    BQSKit Runtime utilizes a single-threaded worker to accept, execute,
+    pause, spawn, resume, and complete tasks in a custom event loop built
+    with python's async await mechanisms. Each worker receives and sends
+    tasks and results to the greater system through a single duplex
+    connection with a runtime server or manager.
+
+    At start-up, the worker receives an ID and waits for its first task.
+    An executing task may use the `submit` and `map` methods to spawn child
+    tasks and distribute them across the whole system. Once completed,
+    those child tasks will have their results shipped back to the worker
+    who created them. When a task awaits a child task, it is removed from
+    the ready queue until the desired results come in.
+
+    All created log records are shipped back to the client's process.
+    This feature ensures compatibility with applications like jupyter
+    that only print messages from the client process's stdout. Additionally,
+    it allows BQSKit users seamless integration with the standard python
+    logging module. From a user's perspective, they can configure any
+    standard python logger from their process like usual and have the
+    entire system honor that configuration. Lastly, we do support an
+    additional logging option for maximum task depth. Tasks with more
+    ancestors than the maximum logging depth will not produce any logs.
+
+    Workers handle python errors by capturing and bubbling them up. For
+    operating system-level crashes and errors -- such as seg-faults in
+    client code -- the worker will attempt to print a stack trace and
+    initiate a system-wide shutdown. However, note that these issues
+    can go unhandled and cause the runtime to deadlock or crash.
+
+    Workers perform very minimal scheduling of tasks. Newly created tasks
+    are directly forwarded upwards to a manager or server, which in turn
+    assigns them to workers. New tasks from above are commonly received
+    in batches. In this case, all but one task from a batch is delayed.
+    Delayed tasks are moved (in LIFO order) to the ready queue if a worker
+    has no other work to complete. This mechanism encourages completing
+    deeply-nested tasks first and prevents flooding the system with active
+    tasks, which usually require much more memory than delayed ones.
+    """
+
+    def __init__(self, id: int, conn: Connection) -> None:
+        """
+        Initialize a worker with no tasks.
+
+        Args:
+            id (int): This worker's id.
+
+            conn (Connection): This worker's duplex channel to a manager
+                or a server.
+        """
+        self._id = id
+        self._conn = conn
+
+        self._outgoing: list[tuple[RuntimeMessage, Any]] = []
+        """Stores outgoing messages to be handled by the event loop."""
+
+        self._tasks: dict[RuntimeAddress, RuntimeTask] = {}
+        """Tracks all started, unfinished tasks on this worker."""
+
+        self._delayed_tasks: list[RuntimeTask] = []
+        """Store all delayed tasks in LIFO order."""
+
+        self._ready_task_ids: WorkerQueue = WorkerQueue()
+        """Tasks queued up for execution."""
+
+        self._cancelled_task_ids: set[RuntimeAddress] = set()
+        """To ensure newly-received cancelled tasks are never started."""
+
+        self._active_task: RuntimeTask | None = None
+        """The currently executing task if one is running."""
+
+        self._running = False
+        """Controls if the event loop is running."""
+
+        self._mailboxes: dict[int, WorkerMailbox] = {}
+        """Map from mailbox ids to worker mailboxes."""
+
+        self._mailbox_counter = 0
+        """This count ensures every mailbox has a unique id."""
+
+        # Send out every emitted log message upstream
+        old_factory = logging.getLogRecordFactory()
+
+        def record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
+            record = old_factory(*args, **kwargs)
+            active_task = get_worker()._active_task
+            if active_task is not None:
+                lvl = active_task.logging_level
+                if lvl is None or lvl <= record.levelno:
+                    tid = active_task.comp_task_id
+                    self._outgoing.append((RuntimeMessage.LOG, (tid, record)))
+            return record
+
+        logging.setLogRecordFactory(record_factory)
+
+        # Communicate that this worker is ready
+        self._conn.send((RuntimeMessage.STARTED, self._id))
+
+    def _loop(self) -> None:
+        """Main worker event loop."""
+        self._running = True
+        while self._running:
+            self._try_step_next_ready_task()
+            self._try_idle()
+            self._handle_comms()
+
+    def _try_idle(self) -> None:
+        """If there is nothing to do, wait until we receive a message."""
+        empty_outgoing = len(self._outgoing) == 0
+        no_ready_tasks = self._ready_task_ids.empty()
+        no_delayed_tasks = len(self._delayed_tasks) == 0
+
+        if empty_outgoing and no_ready_tasks and no_delayed_tasks:
+            self._conn.send((RuntimeMessage.WAITING, 1))
+            wait([self._conn])
+
+    def _handle_comms(self) -> None:
+        """Handle all incoming and outgoing messages."""
+
+        # Handle outgoing communication
+        for out_msg in self._outgoing:
+            self._conn.send(out_msg)
+        self._outgoing.clear()
+
+        # Handle incomming communication
+        while self._conn.poll():
+            msg, payload = self._conn.recv()
+
+            # Process message
+            if msg == RuntimeMessage.SHUTDOWN:
+                self._running = False
+                return
+
+            elif msg == RuntimeMessage.SUBMIT:
+                task = cast(RuntimeTask, payload)
+                self._add_task(task)
+
+            elif msg == RuntimeMessage.SUBMIT_BATCH:
+                tasks = cast(List[RuntimeTask], payload)
+                self._add_task(tasks.pop())  # Submit one task
+                self._delayed_tasks.extend(tasks)  # Delay rest
+                # Delayed tasks have no context and are stored (more-or-less)
+                # as a function pointer together with the arguments.
+                # When it gets started, it consumes much more memory,
+                # so we delay the task start until necessary (at no cost)
+
+            elif msg == RuntimeMessage.RESULT:
+                result = cast(RuntimeResult, payload)
+                self._handle_result(result)
+
+            elif msg == RuntimeMessage.CANCEL:
+                addr = cast(RuntimeAddress, payload)
+                self._handle_cancel(addr)
+
+    def _add_task(self, task: RuntimeTask) -> None:
+        """Start a task and add it to the loop."""
+        self._tasks[task.return_address] = task
+        task.start()
+        self._ready_task_ids.put(task.return_address)
+
+    def _handle_result(self, result: RuntimeResult) -> None:
+        """Insert result into appropriate mailbox and wake waiting task."""
+        mailbox_id = result.return_address.mailbox_index
+        assert result.return_address.worker_id == self._id
+        if mailbox_id not in self._mailboxes:
+            # If the mailbox has been dropped due to a cancel, ignore result
+            return
+
+        box = self._mailboxes[mailbox_id]
+        box.deposit_result(result)
+
+        if box.has_task_waiting:
+            assert box.dest_addr is not None
+            task = self._tasks[box.dest_addr]
+
+            if task.wake_on_next or box.ready:
+                self._ready_task_ids.put(box.dest_addr)  # Wake it
+
+    def _handle_cancel(self, addr: RuntimeAddress) -> None:
+        """
+        Remove `addr` and its children tasks from this worker.
+
+        Notes:
+            Since `self._ready_task_ids' is a queue, it is more efficient
+            to discard cancelled tasks when popping from it. Therefore, we
+            do not do anything with `self._ready_task_ids` here.
+
+            Also, we also don't need to send out cancel messages for
+            cancelled children tasks since other workers can evaluate that
+            for themselves using breadcrumbs and the original `addr` cancel
+            message.
+        """
+        self._cancelled_task_ids.add(addr)
+
+        # Remove all tasks that are children of `addr` from initialized tasks
+        for key, task in self._tasks.items():
+            if task.is_descendant_of(addr):
+                for mailbox_id in self._tasks[key].owned_mailboxes:
+                    self._mailboxes.pop(mailbox_id)
+        self._tasks = {
+            a: t for a, t in self._tasks.items()
+            if not t.is_descendant_of(addr)
+        }
+
+        # Remove all tasks that are children of `addr` from delayed tasks
+        self._delayed_tasks = [
+            t for t in self._delayed_tasks
+            if not t.is_descendant_of(addr)
+        ]
+
+    def _get_next_ready_task(self) -> RuntimeTask | None:
+        """Return the next ready task if one exists, otherwise None."""
+        while True:
+            if self._ready_task_ids.empty():
+                if len(self._delayed_tasks) > 0:
+                    self._add_task(self._delayed_tasks.pop())
+                    continue
+                return None
+
+            addr = self._ready_task_ids.get()
+
+            if addr in self._cancelled_task_ids or addr not in self._tasks:
+                # When a task is cancelled on the worker it is not removed
+                # from the ready queue because it is much cheaper to just
+                # discard cancelled tasks as they come out.
+                continue
+
+            task = self._tasks[addr]
+
+            if any(bcb in self._cancelled_task_ids for bcb in task.breadcrumbs):
+                # If any of the selected tasks ancestor tasks are cancelled
+                # then discard this one too. Each breadcrumb (bcb) is a
+                # task address (unique system-wide task id) of an ancestor
+                # task.
+                continue
+
+            return task
+
+    def _try_step_next_ready_task(self) -> None:
+        """Select a task to run, and advance it one step."""
+        task = self._get_next_ready_task()
+
+        if task is None:
+            # Nothing to do
+            return
+
+        try:
+            self._active_task = task
+
+            # Perform a step of the task and get the future it awaits on
+            future = task.step(self._get_desired_result(task))
+
+            self._process_await(task, future)
+
+        except StopIteration as e:
+            self._process_task_completion(task, e.value)
+
+        except Exception:
+            assert self._active_task is not None  # for type checker
+
+            # Bubble up errors
+            exc_info = sys.exc_info()
+            error_str = ''.join(traceback.format_exception(*exc_info))
+            error_payload = (self._active_task.comp_task_id, error_str)
+            self._outgoing.append((RuntimeMessage.ERROR, error_payload))
+
+        finally:
+            self._active_task = None
+
+    def _process_await(self, task: RuntimeTask, future: RuntimeFuture) -> None:
+        """Process a task's await request."""
+        if not isinstance(future, RuntimeFuture):
+            raise RuntimeError('Can only await on a BQSKit RuntimeFuture.')
+
+        if future.mailbox_id not in self._mailboxes:
+            raise RuntimeError('Cannot await on a canceled task.')
+
+        box = self._mailboxes[future.mailbox_id]
+
+        # Let the mailbox know this task is waiting
+        box.dest_addr = task.return_address
+        task.desired_box_id = future.mailbox_id
+
+        if future._next_flag:
+            # Set from Worker.next, implies the task wants the next result
+            if box.ready:
+                m = 'Cannot wait for next results on a complete task.'
+                raise RuntimeError(m)
+            task.wake_on_next = True
+
+        elif box.ready:
+            self._ready_task_ids.put(task.return_address)
+
+    def _process_task_completion(self, task: RuntimeTask, result: Any) -> None:
+        """Package and send out task result."""
+        assert task is self._active_task
+        packaged_result = RuntimeResult(task.return_address, result, self._id)
+
+        if task.return_address.worker_id == self._id:
+            self._handle_result(packaged_result)
+            self._outgoing.append((RuntimeMessage.UPDATE, -1))
+            # Let manager know this worker has one less task
+            # without sending a result
+        else:
+            self._outgoing.append((RuntimeMessage.RESULT, packaged_result))
+
+        # Remove task
+        self._tasks.pop(task.return_address)
+
+        # Cancel any open tasks
+        for mailbox_id in self._active_task.owned_mailboxes:
+            # If task is complete, simply discard result
+            if mailbox_id in self._mailboxes:
+                if self._mailboxes[mailbox_id].ready:
+                    self._mailboxes.pop(mailbox_id)
+                    continue
+
+            # Otherwise send a cancel message
+            self.cancel(RuntimeFuture(mailbox_id))
+
+        # Start delayed task
+        if self._ready_task_ids.empty() and len(self._delayed_tasks) > 0:
+            self._add_task(self._delayed_tasks.pop())
+
+    def _get_desired_result(self, task: RuntimeTask) -> Any:
+        """Retrieve the task's desired result from the mailboxes."""
+        if task.desired_box_id is None:
+            return None
+
+        box = self._mailboxes[task.desired_box_id]
+
+        if task.wake_on_next:
+            fresh_results = box.get_new_results()
+            assert len(fresh_results) > 0
+            return fresh_results
+
+        assert box.ready
+        task.owned_mailboxes.remove(task.desired_box_id)
+        return self._mailboxes.pop(task.desired_box_id).result
+
+    def _get_new_mailbox_id(self) -> int:
+        """Return a new unique mailbox id."""
+        new_id = self._mailbox_counter
+        self._mailbox_counter += 1
+        return new_id
+
+    def submit(
+        self,
+        fn: Callable[..., Any],
+        *args: Any,
+        **kwargs: Any,
+    ) -> RuntimeFuture:
+        """Submit `fn` as a task to the runtime."""
+        assert self._active_task is not None
+        # Group fnargs together
+        fnarg = (fn, args, kwargs)
+
+        # Create a new mailbox
+        mailbox_id = self._get_new_mailbox_id()
+        self._mailboxes[mailbox_id] = WorkerMailbox.new_mailbox()
+        self._active_task.owned_mailboxes.append(mailbox_id)
+
+        # Create the task
+        task = RuntimeTask(
+            fnarg,
+            RuntimeAddress(self._id, mailbox_id, 0),
+            self._active_task.comp_task_id,
+            self._active_task.breadcrumbs + (self._active_task.return_address,),
+            self._active_task.logging_level,
+            self._active_task.max_logging_depth,
+        )
+
+        # Submit the task (on the next cycle)
+        self._outgoing.append((RuntimeMessage.SUBMIT, task))
+
+        # Return future pointing to the mailbox
+        return RuntimeFuture(mailbox_id)
+
+    def map(
+        self,
+        fn: Callable[..., Any],
+        *args: Any,
+        **kwargs: Any,
+    ) -> RuntimeFuture:
+        """Map `fn` over the input arguments distributed across the runtime."""
+        assert self._active_task is not None
+        # Group fnargs together
+        fnargs = []
+        if len(args) == 1:
+            for arg in args[0]:
+                fnargs.append((fn, (arg,), kwargs))
+
+        else:
+            for subargs in zip(*args):
+                fnargs.append((fn, subargs, kwargs))
+
+        if len(fnargs) == 0:
+            raise RuntimeError('Unable to map 0 tasks.')
+
+        # Create a new mailbox
+        mailbox_id = self._get_new_mailbox_id()
+        self._mailboxes[mailbox_id] = WorkerMailbox.new_mailbox(len(fnargs))
+        self._active_task.owned_mailboxes.append(mailbox_id)
+
+        # Create the tasks
+        breadcrumbs = self._active_task.breadcrumbs
+        breadcrumbs += (self._active_task.return_address,)
+        tasks = [
+            RuntimeTask(
+                fnarg,
+                RuntimeAddress(self._id, mailbox_id, i),
+                self._active_task.comp_task_id,
+                breadcrumbs,
+                self._active_task.logging_level,
+                self._active_task.max_logging_depth,
+            )
+            for i, fnarg in enumerate(fnargs)
+        ]
+
+        # Submit the tasks
+        self._outgoing.append((RuntimeMessage.SUBMIT_BATCH, tasks))
+
+        # Return future pointing to the mailbox
+        return RuntimeFuture(mailbox_id)
+
+    def cancel(self, future: RuntimeFuture) -> None:
+        """Cancel all tasks associated with `future`."""
+        assert self._active_task is not None
+        num_slots = self._mailboxes[future.mailbox_id].expected_num_results
+        self._active_task.owned_mailboxes.remove(future.mailbox_id)
+        self._mailboxes.pop(future.mailbox_id)
+        addrs = [
+            RuntimeAddress(self._id, future.mailbox_id, slot_id)
+            for slot_id in range(num_slots)
+        ]
+        msgs = [(RuntimeMessage.CANCEL, addr) for addr in addrs]
+        self._outgoing.extend(msgs)
+
+    async def next(self, future: RuntimeFuture) -> list[tuple[int, Any]]:
+        """
+        Wait for and return the next batch of results from a map task.
+
+        Returns:
+            (list[tuple[int, Any]]): A list of the results that arrived
+                since the last time this was called. On the first call,
+                all results that have arrived since the task started are
+                returned. Each result is paired with the index of its
+                arguments in the original map call.
+        """
+        if future._done:
+            raise RuntimeError('Cannot wait on an already completed result.')
+
+        future._next_flag = True
+        next_result_batch = await future
+        future._next_flag = False
+        return next_result_batch
+
+
+# Global variable containing reference to this process's worker object.
+_worker = None
+
+
+def start_worker(w_id: int | None, port: int, cpu: int | None = None) -> None:
+    """Start this process's worker."""
+    if w_id is not None:
+        # Ignore interrupt signals on workers, boss will handle it for us
+        # If w_id is None, then we are being spawned separately.
+        signal.signal(signal.SIGINT, signal.SIG_IGN)
+
+    # Purge all standard python logging configurations
+    for _, logger in logging.Logger.manager.loggerDict.items():
+        if isinstance(logger, logging.PlaceHolder):
+            continue
+        logger.handlers.clear()
+    logging.Logger.manager.loggerDict = {}
+
+    if cpu is not None:
+        if sys.platform == 'win32':
+            raise RuntimeError('Cannot pin worker to cpu on windows.')
+        os.sched_setaffinity(0, [cpu])
+
+    max_retries = 7
+    wait_time = .1
+    conn: Connection | None = None
+    family = 'AF_INET' if sys.platform == 'win32' else None
+    for _ in range(max_retries):
+        try:
+            conn = Client(('localhost', port), family)
+        except (ConnectionRefusedError, TimeoutError):
+            time.sleep(wait_time)
+            wait_time *= 2
+        else:
+            break
+
+    if conn is None:
+        raise RuntimeError('Unable to establish connection with manager.')
+
+    if w_id is None:
+        msg, w_id = conn.recv()
+        assert msg == RuntimeMessage.STARTED
+
+    global _worker
+    _worker = Worker(w_id, conn)
+    _worker._loop()
+
+
+def get_worker() -> Worker:
+    """Return a handle on this process's worker."""
+    if _worker is None:
+        raise RuntimeError('Worker has not been started.')
+    return _worker
+
+
+def _check_positive(value: str) -> int:
+    ivalue = int(value)
+    if ivalue <= 0:
+        raise argparse.ArgumentTypeError(
+            '%s is an invalid positive int value' % value,
+        )
+    return ivalue
+
+
+def start_worker_rank() -> None:
+    """Entry point for spawning a rank of runtime worker processes."""
+    parser = argparse.ArgumentParser(
+        prog='bqskit-worker',
+        description='Launch a rank of BQSKit runtime worker processes.',
+    )
+    parser.add_argument(
+        'num_workers',
+        type=_check_positive,
+        help='The number of workers to spawn.',
+    )
+    parser.add_argument(
+        '--cpus', '-c',
+        nargs='+',
+        type=int,
+        help='Either one number or a list of numbers equal in length to the'
+        ' number of workers. The workers will be pinned to specified logical'
+        ' cpus. If a single-number is given, then all cpu indices are'
+        ' enumerated starting at that number.',
+    )
+    parser.add_argument(
+        '-p', '--port',
+        type=int,
+        default=default_worker_port,
+        help='The port the workers will try to connect to a manager on.',
+    )
+    args = parser.parse_args()
+
+    if args.cpus is not None:
+        if len(args.cpus) == 1:
+            cpus = [args.cpus[0] + i for i in range(args.num_workers)]
+
+        elif len(args.cpus) == args.num_workers:
+            cpus = args.cpus
+
+        else:
+            raise RuntimeError(
+                'The specified logical cpus are invalid. Expected either'
+                ' a single number or a list of numbers equal in length to'
+                ' the number of workers.',
+            )
+
+    else:
+        cpus = [None for _ in range(args.num_workers)]
+
+    # Spawn worker process
+    procs = []
+    for cpu in cpus:
+        procs.append(Process(target=start_worker, args=(None, args.port, cpu)))
+        procs[-1].start()
+
+    # Join them
+    for proc in procs:
+        proc.join()
diff --git a/bqskit/utils/math.py b/bqskit/utils/math.py
index 00f85d7af..63dbc9c55 100644
--- a/bqskit/utils/math.py
+++ b/bqskit/utils/math.py
@@ -38,7 +38,8 @@ def dexpmv(
         (2008): 17-24.
     """
 
-    e = np.log2(np.linalg.norm(M, np.inf))
+    norm = np.linalg.norm(M, np.inf)
+    e = np.log2(norm) if norm != 0 else -np.inf
     r = int(max(0, e + 1))
     M = M / (2 ** r)
     dM = dM / (2 ** r)
diff --git a/bqskit/utils/random.py b/bqskit/utils/random.py
index a53c1d95c..a738a1ad7 100644
--- a/bqskit/utils/random.py
+++ b/bqskit/utils/random.py
@@ -2,6 +2,7 @@
 from __future__ import annotations
 
 import ctypes
+import random
 from ctypes.util import find_library
 from sys import platform
 
@@ -16,3 +17,4 @@ def seed_random_sources(seed: int) -> None:
         libc.srand(seed)
     # set numpy seed
     np.random.seed(seed)
+    random.seed(seed)
diff --git a/bqskit/utils/typing.py b/bqskit/utils/typing.py
index 4e2f935ce..17cf00847 100644
--- a/bqskit/utils/typing.py
+++ b/bqskit/utils/typing.py
@@ -142,11 +142,11 @@ def is_matrix(M: np.typing.ArrayLike) -> bool:
         M = np.array(M)
 
     if len(M.shape) != 2:
-        _logger.debug('M is not an 2-dimensional array.')
+        # _logger.debug('M is not an 2-dimensional array.')
         return False
 
     if M.dtype.kind not in 'biufc':
-        _logger.debug('M is not a numeric array.')
+        # _logger.debug('M is not a numeric array.')
         return False
 
     return True
diff --git a/bqskit/version.py b/bqskit/version.py
new file mode 100644
index 000000000..9c70476c9
--- /dev/null
+++ b/bqskit/version.py
@@ -0,0 +1,4 @@
+"""This module contains the version information for BQSKit."""
+from __future__ import annotations
+__version_info__ = ('1', '1', '0', 'a5')
+__version__ = '.'.join(__version_info__[:3]) + ''.join(__version_info__[3:])
diff --git a/docs/conf.py b/docs/conf.py
index f9ab6857d..dd30c06b2 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -93,13 +93,10 @@
 myst_enable_extensions = ['dollarmath', 'amsmath']
 autodoc_mock_imports = [
     'bqskitrs',
-    'threadpoolctl',
-    'dask',
     'scipy',
     'numpy',
     'qiskit',
     'lark-parser',
-    'distributed',
     'hypothesis',
     'pytket',
     'cirq',
diff --git a/docs/index.rst b/docs/index.rst
index 8ca903aa5..bb7d6a096 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -31,3 +31,4 @@ our `tutorial series. <https://github.com/BQSKit/bqskit-tutorial/>`_
    source/qis
    source/utils
    source/exec
+   source/runtime
diff --git a/docs/source/runtime.rst b/docs/source/runtime.rst
new file mode 100644
index 000000000..5a87280d3
--- /dev/null
+++ b/docs/source/runtime.rst
@@ -0,0 +1,4 @@
+.. automodule:: bqskit.runtime
+   :no-members:
+   :no-inherited-members:
+   :no-special-members:
diff --git a/examples/pas.py b/examples/pas.py
new file mode 100644
index 000000000..980325dc2
--- /dev/null
+++ b/examples/pas.py
@@ -0,0 +1,18 @@
+"""This script is synthesizes a unitary with PAS."""
+from __future__ import annotations
+
+from bqskit import enable_logging
+from bqskit.compiler import Compiler
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.synthesis.pas import PermutationAwareSynthesisPass
+enable_logging()
+
+circuit = Circuit.from_file('cxx.qasm')
+
+# Finally let's create create the compiler and execute the CompilationTask.
+with Compiler() as compiler:
+    compiled_circuit = compiler.compile(
+        circuit,
+        PermutationAwareSynthesisPass(),
+    )
+    print(compiled_circuit.gate_counts)
diff --git a/examples/qfast.py b/examples/qfast.py
index 8124c4bf7..4e4964f49 100644
--- a/examples/qfast.py
+++ b/examples/qfast.py
@@ -1,9 +1,8 @@
 """This script is contains a simple use case of the QFAST synthesis method."""
 from __future__ import annotations
 
-from bqskit.compiler import CompilationTask
 from bqskit.compiler import Compiler
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.passes import ForEachBlockPass
 from bqskit.passes import LEAPSynthesisPass
 from bqskit.passes import QFASTDecompositionPass
@@ -15,16 +14,17 @@
 # circuit.
 circuit = Circuit.from_unitary(UnitaryMatrix.random(3))
 
-# We will now define the CompilationTask we want to run.
-task = CompilationTask(
-    circuit, [
-        QFASTDecompositionPass(),
-        ForEachBlockPass([LEAPSynthesisPass(), ScanningGateRemovalPass()]),
-        UnfoldPass(),
-    ],
-)
+# We now define our synthesis workflow utilizing the QFAST algorithm.
+workflow = [
+    QFASTDecompositionPass(),
+    ForEachBlockPass([
+        LEAPSynthesisPass(),  # LEAP performs native gate instantiation
+        ScanningGateRemovalPass(),  # Gate removal optimizing gate counts
+    ]),
+    UnfoldPass(),
+]
 
 # Finally let's create create the compiler and execute the CompilationTask.
 with Compiler() as compiler:
-    compiled_circuit = compiler.compile(task)
+    compiled_circuit = compiler.compile(circuit, workflow)
     print(compiled_circuit.gate_counts)
diff --git a/examples/quest.py b/examples/quest.py
index 8ef008043..2d0107dd6 100644
--- a/examples/quest.py
+++ b/examples/quest.py
@@ -3,11 +3,10 @@
 
 import logging
 
-from bqskit.compiler import CompilationTask
 from bqskit.compiler import Compiler
 from bqskit.exec.runners.quest import QuestRunner
 from bqskit.exec.runners.sim import SimulationRunner
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.passes import ForEachBlockPass
 from bqskit.passes import LEAPSynthesisPass
 from bqskit.passes import QFASTDecompositionPass
@@ -23,18 +22,15 @@
     # circuit.
     circuit = Circuit.from_unitary(UnitaryMatrix.random(4))
 
-    # We will now define the CompilationTask we want to run.
-    task = CompilationTask(
-        circuit, [
-            QFASTDecompositionPass(),
-            ForEachBlockPass([LEAPSynthesisPass(), ScanningGateRemovalPass()]),
-            UnfoldPass(),
-        ],
-    )
+    workflow = [
+        QFASTDecompositionPass(),
+        ForEachBlockPass([LEAPSynthesisPass(), ScanningGateRemovalPass()]),
+        UnfoldPass(),
+    ]
 
     # Finally let's create create the compiler and execute the CompilationTask.
     with Compiler() as compiler:
-        compiled_circuit = compiler.compile(task)
+        compiled_circuit = compiler.compile(circuit, workflow)
 
         # Execute the circuit with Quest
         quest_runner = QuestRunner(SimulationRunner(), compiler=compiler)
diff --git a/examples/tutorials/Partitioning and Optimization.ipynb b/examples/tutorials/Partitioning and Optimization.ipynb
index 4c6f72743..b7b16e40b 100644
--- a/examples/tutorials/Partitioning and Optimization.ipynb
+++ b/examples/tutorials/Partitioning and Optimization.ipynb
@@ -39,11 +39,12 @@
    ]
   },
   {
+   "attachments": {},
    "cell_type": "markdown",
    "id": "563b666f",
    "metadata": {},
    "source": [
-    "We will partition the circuit and then use the `ForEachBlockPass` to perform operations on the individual blocks. Note the `ForEachBlockPass` will run the sub tasks in parallel using dask."
+    "We will partition the circuit and then use the `ForEachBlockPass` to perform operations on the individual blocks. Note the `ForEachBlockPass` will run the sub tasks in parallel."
    ]
   },
   {
diff --git a/pyproject.toml b/pyproject.toml
index a033506a7..4dbcd8800 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,3 +4,51 @@ requires = [
     "setuptools>=40.1.0",
     "oldest-supported-numpy",
 ]
+
+[tool.tox]
+legacy_tox_ini = """
+[tox]
+minversion = 3.3.0
+envlist = pre-commit,py38,py39,py310,py311
+skip_missing_interpreters = true
+
+[testenv]
+passenv =
+    TERM
+deps =
+    numpy>=1.22
+    pytest>=2.8
+    hypothesis[numpy,zoneinfo]>=6.14.5
+    qiskit>=0.37.1
+    pytket>=1.5.0
+    cirq>=1.0.0
+    qutip>=4.7.0
+    pytket-qiskit
+    tzdata
+commands =
+    pytest {posargs}
+
+[testenv:pre-commit]
+skip_install = true
+deps = pre-commit
+commands = pre-commit run --all-files --show-diff-on-failure
+basepython=py311
+"""
+
+[tool.pytest.ini_options]
+minversion = "2.8"
+testpaths = ["tests/"]
+python_files = "test*.py"
+norecursedirs = ".env"
+
+[tool.mypy]
+check_untyped_defs = true
+disallow_any_generics = true
+disallow_incomplete_defs = true
+disallow_untyped_defs = true
+no_implicit_optional = true
+allow_redefinition = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+pretty = true
+plugins = "numpy.typing.mypy_plugin"
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index b3fac1c16..000000000
--- a/setup.cfg
+++ /dev/null
@@ -1,115 +0,0 @@
-[metadata]
-name = bqskit
-version = 1.0.4
-description = Berkeley Quantum Synthesis Toolkit
-long_description = file: README.md
-long_description_content_type = text/markdown
-url = https://github.com/BQSKit/bqskit
-author = LBNL - BQSKit developers
-author_email = edyounis@lbl.gov
-license = BSD 3-Clause License
-license_file = LICENSE
-classifiers =
-    Development Status :: 5 - Production/Stable
-    Environment :: Console
-    Intended Audience :: Developers
-    Intended Audience :: Education
-    Intended Audience :: Science/Research
-    License :: OSI Approved :: BSD License
-    Operating System :: MacOS
-    Operating System :: Microsoft :: Windows
-    Operating System :: POSIX :: Linux
-    Programming Language :: Python :: 3 :: Only
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.9
-    Programming Language :: Python :: 3.10
-    Programming Language :: Python :: 3.11
-    Topic :: Scientific/Engineering
-    Topic :: Scientific/Engineering :: Mathematics
-    Topic :: Scientific/Engineering :: Physics
-    Topic :: Software Development :: Compilers
-    Typing :: Typed
-keywords = berkeley quantum synthesis toolkit partitioning
-project_urls =
-    Bug Tracker = https://github.com/BQSKit/bqskit/issues
-    Source Code = https://github.com/BQSKit/bqskit
-    Documentation = https://bqskit.readthedocs.io/en/latest
-
-[options]
-packages = find:
-install_requires =
-    bqskitrs>=0.3.0
-    dask[distributed]>=2022.07.01,<=2022.12.1
-    lark-parser
-    numpy>=1.22.0
-    scipy>=1.8.0
-    threadpoolctl
-    typing-extensions>=4.0.0
-python_requires = >=3.8, <4
-
-[options.packages.find]
-exclude =
-    tests
-    examples
-
-[options.extras_require]
-dev =
-    hypothesis
-    mypy
-    pre-commit
-    pytest
-    pytket-qiskit
-    tox
-    tzdata
-ext =
-    cirq>=1.0.0
-    pytket>=1.5.0
-    qiskit>=0.36.2
-    qutip>=4.7.0
-
-[bdist_wheel]
-universal = 1
-
-[tool:pytest]
-minversion = 2.8
-testpaths = tests/
-python_files = test*.py
-norecursedirs = .env
-
-[mypy]
-check_untyped_defs = true
-disallow_any_generics = true
-disallow_incomplete_defs = true
-disallow_untyped_defs = true
-no_implicit_optional = true
-allow_redefinition = true
-warn_redundant_casts = true
-warn_unused_ignores = true
-pretty = true
-plugins = numpy.typing.mypy_plugin
-
-[tox:tox]
-minversion = 3.3.0
-envlist = pre-commit,py38,py39,py310,py311
-skip_missing_interpreters = true
-
-[testenv]
-passenv =
-    TERM
-deps =
-    numpy>=1.22
-    pytest>=2.8
-    hypothesis[numpy,zoneinfo]>=6.14.5
-    qiskit>=0.37.1
-    pytket>=1.5.0
-    cirq>=1.0.0
-    qutip>=4.7.0
-    pytket-qiskit
-    tzdata
-commands =
-    pytest {posargs}
-
-[testenv:pre-commit]
-skip_install = true
-deps = pre-commit
-commands = pre-commit run --all-files --show-diff-on-failure
diff --git a/setup.py b/setup.py
index abbaa429a..46ded280d 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,92 @@
+"""The BQSKit Quantum Compiler Framework Setup File."""
 from __future__ import annotations
 
+import os
+
+from setuptools import find_packages
 from setuptools import setup
-# Everything is in setup.cfg, this is for compatibility
-# pip install works the same.
-setup()
+
+root_dir_path = os.path.abspath(os.path.dirname(__file__))
+pkg_dir_path = os.path.join(root_dir_path, 'bqskit')
+readme_path = os.path.join(root_dir_path, 'README.md')
+version_path = os.path.join(pkg_dir_path, 'version.py')
+
+# Load Version Number
+with open(version_path) as version_file:
+    exec(version_file.read())
+
+# Load Readme
+with open(readme_path) as readme_file:
+    long_description = readme_file.read()
+
+setup(
+    name='bqskit',
+    version=__version__,  # type: ignore # noqa # Defined in version.py loaded above
+    description='Berkeley Quantum Synthesis Toolkit',
+    long_description=long_description,
+    long_description_content_type='text/markdown',
+    url='https://github.com/BQSKit/bqskit',
+    author='LBNL - BQSKit developers',
+    author_email='edyounis@lbl.gov',
+    license='BSD 3-Clause License',
+    license_files=['LICENSE'],
+    classifiers=[
+        'Development Status :: 5 - Production/Stable',
+        'Environment :: Console',
+        'Intended Audience :: Developers',
+        'Intended Audience :: Education',
+        'Intended Audience :: Science/Research',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: MacOS',
+        'Operating System :: Microsoft :: Windows',
+        'Operating System :: POSIX :: Linux',
+        'Programming Language :: Python :: 3 :: Only',
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
+        'Programming Language :: Python :: 3.11',
+        'Topic :: Scientific/Engineering',
+        'Topic :: Scientific/Engineering :: Mathematics',
+        'Topic :: Scientific/Engineering :: Physics',
+        'Topic :: Software Development :: Compilers',
+        'Typing :: Typed',
+    ],
+    keywords=[
+        'berkeley',
+        'quantum',
+        'synthesis',
+        'toolkit',
+        'partitioning',
+    ],
+    project_urls={
+        'Bug Tracker': 'https://github.com/BQSKit/bqskit/issues',
+        'Source Code': 'https://github.com/BQSKit/bqskit',
+        'Documentation': 'https://bqskit.readthedocs.io/en/latest',
+    },
+    packages=find_packages(exclude=['examples*', 'test*']),
+    install_requires=[
+        'bqskitrs>=0.4.0rc5',
+        'lark-parser',
+        'numpy>=1.22.0',
+        'scipy>=1.8.0',
+        'typing-extensions>=4.0.0',
+    ],
+    python_requires='>=3.8, <4',
+    entry_points={
+        'console_scripts': [
+            'bqskit-server = bqskit.runtime.detached:start_server',
+            'bqskit-manager = bqskit.runtime.manager:start_manager',
+            'bqskit-worker = bqskit.runtime.worker:start_worker_rank',
+        ],
+    },
+    extras_require={
+        'dev': [
+            'hypothesis[zoneinfo]',
+            'mypy',
+            'pre-commit',
+            'psutil',
+            'pytest',
+            'tox',
+        ],
+    },
+)


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 04aa57b7a..15adf2dc8 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,6 +8,7 @@ on:
 jobs:
   tests:
     strategy:
+      fail-fast: false
       matrix:
         python-version: ['3.8', '3.9', '3.10', '3.11']
         os: [macOS-latest, windows-latest, ubuntu-latest]
@@ -20,10 +21,12 @@ jobs:
         python-version: ${{ matrix.python-version }}
     - name: Upgrade python environment
       run: python -m pip install --upgrade virtualenv setuptools pip
+    - name: Upgrade test dependencies
+      run: python -m pip install psutil pytest 'hypothesis[zoneinfo]' qiskit
     - name: Install BQSKit
       env:
         SYSTEM_VERSION_COMPAT: 0
         NUMBER_RANDOM_CIRCUITS: 100
-      run: pip install '.[ext,dev]'
+      run: pip install .
     - name: Run tests
       run: pytest
diff --git a/bqskit/utils/test/strategies.py b/bqskit/utils/test/strategies.py
index e71db8f36..de0e5e2dc 100644
--- a/bqskit/utils/test/strategies.py
+++ b/bqskit/utils/test/strategies.py
@@ -22,7 +22,7 @@
 from hypothesis.strategies import tuples
 
 import bqskit.ir.gates
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
 from bqskit.ir.gates.composed.daggergate import DaggerGate
 from bqskit.ir.gates.composed.frozenparam import FrozenParameterGate
diff --git a/tests/bqskitrs/__init__.py b/tests/bqskitrs/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/compiler/__init__.py b/tests/compiler/__init__.py
index e69de29bb..c96f8be82 100644
--- a/tests/compiler/__init__.py
+++ b/tests/compiler/__init__.py
@@ -0,0 +1,2 @@
+# TODO: Test compiler.submit with no circuit/comptask sent
+from __future__ import annotations
diff --git a/tests/compiler/compile/__init__.py b/tests/compiler/compile/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/compiler/compile/conftest.py b/tests/compiler/compile/conftest.py
index b553c0ed9..b2e4cf863 100644
--- a/tests/compiler/compile/conftest.py
+++ b/tests/compiler/compile/conftest.py
@@ -5,7 +5,7 @@
 import pytest
 
 
-@pytest.fixture(params=[1, 2, 3])
+@pytest.fixture(params=[1, 2, 3, 4])
 def optimization_level(request: Any) -> int:
     """All valid optimization_levels for the `compile` function."""
     return request.param
diff --git a/tests/compiler/compile/test_blocked_input.py b/tests/compiler/compile/test_blocked_input.py
new file mode 100644
index 000000000..c71f1473b
--- /dev/null
+++ b/tests/compiler/compile/test_blocked_input.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+from bqskit import compile
+from bqskit import MachineModel
+from bqskit.compiler.compiler import Compiler
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CircuitGate
+from bqskit.ir.gates import CNOTGate
+from bqskit.ir.gates import CZGate
+from bqskit.ir.gates import U3Gate
+
+
+def test_compile_blocked_input_circuit_unfold_rebase_correctly(
+        compiler: Compiler,
+) -> None:
+    circuit = Circuit(2)
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    cg = CircuitGate(circuit, True)
+    blocked_circuit = Circuit(2)
+    blocked_circuit.append_gate(cg, (0, 1))
+    model = MachineModel(2, gate_set={CZGate(), U3Gate()})
+    out_circuit = compile(blocked_circuit, model, compiler=compiler)
+    assert len(out_circuit.gate_set) == 2
+    assert CZGate() in out_circuit.gate_set
+    assert U3Gate() in out_circuit.gate_set
+    assert CNOTGate() not in out_circuit.gate_set
diff --git a/tests/compiler/compile/test_prep.py b/tests/compiler/compile/test_prep.py
new file mode 100644
index 000000000..9bb1fd948
--- /dev/null
+++ b/tests/compiler/compile/test_prep.py
@@ -0,0 +1,107 @@
+from __future__ import annotations
+
+import pytest
+
+from bqskit import compile
+from bqskit.compiler.compiler import Compiler
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CXGate
+from bqskit.ir.gates import U3Gate
+from bqskit.ir.opt.cost import HilbertSchmidtCostGenerator
+from bqskit.ir.opt.minimizers import LBFGSMinimizer
+from bqskit.qis.state import StateVector
+from bqskit.qis.state.system import StateSystem
+from bqskit.qis.unitary import UnitaryMatrix
+
+
+def test_state_prep(compiler: Compiler) -> None:
+    state = StateVector.random(3)
+    out_circuit = compile(state, compiler=compiler)
+    out_state = out_circuit.get_statevector(StateVector.zero(3))
+    assert out_state.get_distance_from(state) < 1e-8
+
+
+def test_state_inst_2() -> None:
+    state = StateVector.random(2)
+    circuit = Circuit(2)
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.instantiate(
+        state,
+        minimizer=LBFGSMinimizer(),
+        cost_fn_gen=HilbertSchmidtCostGenerator(),
+    )
+    out_state = circuit.get_statevector(StateVector.zero(2))
+    assert out_state.get_distance_from(state) < 1e-8
+
+
+def test_state_inst_3() -> None:
+    state = StateVector.random(3)
+    circuit = Circuit(3)
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(U3Gate(), 2)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(CXGate(), (1, 2))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(U3Gate(), 2)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(CXGate(), (1, 2))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(U3Gate(), 2)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(CXGate(), (1, 2))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(U3Gate(), 2)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(CXGate(), (1, 2))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(U3Gate(), 2)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(CXGate(), (1, 2))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(U3Gate(), 2)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(CXGate(), (1, 2))
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    circuit.append_gate(U3Gate(), 2)
+    circuit.instantiate(
+        state,
+        minimizer=LBFGSMinimizer(),
+        cost_fn_gen=HilbertSchmidtCostGenerator(),
+    )
+    out_state = circuit.get_statevector(StateVector.zero(3))
+    assert out_state.get_distance_from(state) < 1e-8
+
+
+@pytest.mark.skip(reason='Tests run for 4 hours, and falls sometimes')
+def test_state_map(compiler: Compiler) -> None:
+    num_qudits = 3
+    utry = UnitaryMatrix.random(num_qudits)
+    state_map = {}
+    for i in range(2):
+        in_state = StateVector.random(num_qudits)
+        out_state = utry.get_statevector(in_state)
+        state_map[in_state] = out_state
+    system = StateSystem(state_map)
+    out_circuit = compile(system, compiler=compiler)
+    print(out_circuit.gate_counts)
+
+    for in_state, out_state in state_map.items():
+        test_out_state = out_circuit.get_statevector(in_state)
+        assert out_state.get_distance_from(test_out_state) < 1e-5
diff --git a/tests/compiler/compile/test_seed.py b/tests/compiler/compile/test_seed.py
new file mode 100644
index 000000000..954998178
--- /dev/null
+++ b/tests/compiler/compile/test_seed.py
@@ -0,0 +1,32 @@
+from __future__ import annotations
+
+from random import random
+
+from bqskit import compile
+from bqskit.compiler.compiler import Compiler
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CNOTGate
+from bqskit.ir.gates import U3Gate
+
+
+def test_same_seed_same_result(compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    circuit.append_gate(U3Gate(), 0, [random() for _ in range(3)])
+    circuit.append_gate(U3Gate(), 1, [random() for _ in range(3)])
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(U3Gate(), 0, [random() for _ in range(3)])
+    circuit.append_gate(U3Gate(), 1, [random() for _ in range(3)])
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(U3Gate(), 0, [random() for _ in range(3)])
+    circuit.append_gate(U3Gate(), 1, [random() for _ in range(3)])
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(U3Gate(), 0, [random() for _ in range(3)])
+    circuit.append_gate(U3Gate(), 1, [random() for _ in range(3)])
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(U3Gate(), 0, [random() for _ in range(3)])
+    circuit.append_gate(U3Gate(), 1, [random() for _ in range(3)])
+
+    for i in range(5):
+        out_circuit1 = compile(circuit.copy(), seed=i, compiler=compiler)
+        out_circuit2 = compile(circuit.copy(), seed=i, compiler=compiler)
+        assert out_circuit1 == out_circuit2
diff --git a/tests/compiler/compile/test_synthesis.py b/tests/compiler/compile/test_synthesis.py
index 0cc031b25..0e32195a6 100644
--- a/tests/compiler/compile/test_synthesis.py
+++ b/tests/compiler/compile/test_synthesis.py
@@ -1,11 +1,13 @@
 from __future__ import annotations
 
+import itertools as it
+
 import pytest
 
 from bqskit import compile
 from bqskit import MachineModel
 from bqskit.compiler.compiler import Compiler
-from bqskit.compiler.machine import default_gate_set
+from bqskit.compiler.gateset import GateSet
 from bqskit.ext.cirq.models import google_gate_set
 from bqskit.ext.honeywell import honeywell_gate_set
 from bqskit.ext.rigetti import rigetti_gate_set
@@ -17,16 +19,30 @@
 from bqskit.ir.gates import U1qGate
 from bqskit.ir.gates import U3Gate
 from bqskit.ir.gates import XGate
+from bqskit.qis import PermutationMatrix
 from bqskit.qis import UnitaryMatrix
 
 
+def get_distance_from_pa(U: UnitaryMatrix, V: UnitaryMatrix) -> float:
+    """Get distance between U and V up to a permutation."""
+    width = U.num_qudits
+    perms = list(it.permutations(range(width)))
+    Pis = [PermutationMatrix.from_qubit_location(width, p) for p in perms]
+    Pos = [PermutationMatrix.from_qubit_location(width, p) for p in perms]
+    dists = [
+        U.get_distance_from(Po.T @ V @ Pi, 1)
+        for Pi, Po in it.product(Pis, Pos)
+    ]
+    return min(dists)
+
+
 @pytest.mark.parametrize('sq_utry', [UnitaryMatrix.random(1) for i in range(5)])
 @pytest.mark.parametrize(
     'gate_set', [
         {U3Gate()},
         {PhasedXZGate()},
         {U1qGate(), XGate()},
-        default_gate_set,
+        GateSet.default_gate_set(),
         rigetti_gate_set,
         honeywell_gate_set,
         google_gate_set,
@@ -50,13 +66,13 @@ def test_single_qudit_synthesis(
         assert out_circuit.num_operations == 1
     assert out_circuit.num_qudits == 1
     assert len(out_circuit.gate_set.difference(gate_set)) == 0
-    assert out_circuit.get_unitary().get_distance_from(sq_utry, 1) < 1e-10
+    assert out_circuit.get_unitary().get_distance_from(sq_utry, 1) < 1e-8
 
 
 @pytest.mark.parametrize('tq_utry', [UnitaryMatrix.random(2) for i in range(5)])
 @pytest.mark.parametrize(
     'gate_set', [
-        default_gate_set,
+        GateSet.default_gate_set(),
         rigetti_gate_set,
         honeywell_gate_set,
         google_gate_set,
@@ -77,12 +93,18 @@ def test_two_qudit_synthesis(
     )
     assert out_circuit.num_qudits == 2
     assert len(out_circuit.gate_set.difference(gate_set)) == 0
-    assert out_circuit.get_unitary().get_distance_from(tq_utry, 1) < 1e-10
+    out_utry = out_circuit.get_unitary()
+
+    if optimization_level == 4:
+        assert get_distance_from_pa(out_utry, tq_utry) < 1e-8
+
+    else:
+        assert out_utry.get_distance_from(tq_utry, 1) < 1e-8
 
 
 @pytest.mark.parametrize(
     'gate_set', [
-        default_gate_set,
+        GateSet.default_gate_set(),
         rigetti_gate_set,
         honeywell_gate_set,
         {IToffoliGate(), U3Gate()},
@@ -103,8 +125,13 @@ def test_three_qudit_synthesis(
     )
     assert out_circuit.num_qudits == 3
     assert len(out_circuit.gate_set.difference(gate_set)) == 0
-    utry = toffoli_unitary
-    assert out_circuit.get_unitary().get_distance_from(utry, 1) < 1e-10
+    out_utry = out_circuit.get_unitary()
+
+    if optimization_level == 4:
+        assert get_distance_from_pa(out_utry, toffoli_unitary) < 1e-8
+
+    else:
+        assert out_utry.get_distance_from(toffoli_unitary, 1) < 1e-8
 
 
 def test_fail_on_larger_max_synthesis_size(compiler: Compiler) -> None:
@@ -126,6 +153,20 @@ def test_identity_synthesis(
     )
     assert out_circuit.get_unitary().get_distance_from(
         UnitaryMatrix.identity(dim), 1,
-    ) < 1e-10
+    ) < 1e-8
     if optimization_level == 3:
         assert out_circuit.num_operations <= 3
+
+
+# @pytest.mark.parametrize('qudits', [1, 2])
+# def test_qutrit_synthesis(optimization_level: int, qudits: int) -> None:
+#     in_utry = UnitaryMatrix.random(qudits, [3] * qudits)
+#     out_circuit = compile(in_utry, optimization_level=optimization_level)
+#     assert out_circuit.get_unitary().get_distance_from(in_utry, 1) < 1e-8
+
+
+# @pytest.mark.parametrize('qudits', [1, 2])
+# def test_ququart_synthesis(optimization_level: int, qudits: int) -> None:
+#     in_utry = UnitaryMatrix.random(qudits, [4] * qudits)
+#     out_circuit = compile(in_utry, optimization_level=optimization_level)
+#     assert out_circuit.get_unitary().get_distance_from(in_utry, 1) < 1e-8
diff --git a/tests/compiler/test_compiler.py b/tests/compiler/test_compiler.py
index da0220fa8..1087a1905 100644
--- a/tests/compiler/test_compiler.py
+++ b/tests/compiler/test_compiler.py
@@ -2,37 +2,55 @@
 from __future__ import annotations
 
 import logging
+import traceback
 from io import StringIO
-from typing import Any
-from unittest.mock import patch
 
 import pytest
 
 from bqskit.compiler.basepass import BasePass
 from bqskit.compiler.compiler import Compiler
-from bqskit.compiler.task import CompilationTask
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 
-def test_errors_raised_locally(compiler: Compiler) -> None:
-    class ErrorPass(BasePass):
-        def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-            raise RuntimeError()
+class ErrorPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        raise RuntimeError('TestError')
 
-    task = CompilationTask(Circuit(1), [ErrorPass()])
 
-    with pytest.raises(RuntimeError):
-        compiler.compile(task)
+class LogPass(BasePass):
+    async def run(
+        self,
+        circuit: Circuit,
+        data: PassData,
+    ) -> None:
+        logging.getLogger('bqskit.dummy').debug('Test.')
 
 
-def test_log_msg_printed_locally(compiler: Compiler) -> None:
-    class LogPass(BasePass):
-        def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-            logging.getLogger('bqskit.dummy').debug('Test.')
+def test_errors_raised_locally() -> None:
+    with pytest.raises(RuntimeError) as exc_info:
+        with Compiler() as compiler:
+            compiler.compile(Circuit(1), [ErrorPass()])
 
-    task = CompilationTask(Circuit(1), [LogPass()])
-    logging.getLogger('bqskit').setLevel(logging.DEBUG)
-    with patch('sys.stdout', new_callable=StringIO) as mock_out:
-        compiler.compile(task)
-        print(mock_out.getvalue())
-    logging.getLogger('bqskit').setLevel(logging.WARNING)
+    error_str = ''.join(traceback.format_exception(*exc_info._excinfo))
+    assert 'TestError' in error_str
+
+
+def test_simple_creation() -> None:
+    com = Compiler(num_workers=6)
+    com.close()
+
+    assert True
+
+
+def test_log_msg_printed_locally() -> None:
+    logger = logging.getLogger('bqskit')
+    logger.setLevel(logging.DEBUG)
+    handler = logging.StreamHandler(StringIO())
+    handler.setLevel(logging.DEBUG)
+    logger.addHandler(handler)
+    with Compiler() as compiler:
+        compiler.compile(Circuit(1), [LogPass()])
+    assert 'Test.' in handler.stream.getvalue()
+    logger.removeHandler(handler)
+    logger.setLevel(logging.WARNING)
diff --git a/tests/compiler/test_gateset.py b/tests/compiler/test_gateset.py
new file mode 100644
index 000000000..a0ba90e6c
--- /dev/null
+++ b/tests/compiler/test_gateset.py
@@ -0,0 +1,523 @@
+"""This file tests the GateSet class."""
+from __future__ import annotations
+
+import pytest
+from hypothesis import given
+from hypothesis.strategies import builds
+from hypothesis.strategies import sets
+
+from bqskit.compiler.gateset import GateSet
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates.constant.ccx import ToffoliGate
+from bqskit.ir.gates.constant.cx import CNOTGate
+from bqskit.ir.gates.constant.cz import CZGate
+from bqskit.ir.gates.constant.sx import SXGate
+from bqskit.ir.gates.constant.x import XGate
+from bqskit.ir.gates.constant.y import YGate
+from bqskit.ir.gates.parameterized.cphase import ArbitraryCPhaseGate
+from bqskit.ir.gates.parameterized.pauli import PauliGate
+from bqskit.ir.gates.parameterized.rz import RZGate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
+from bqskit.ir.gates.parameterized.u8 import U8Gate
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
+from bqskit.passes.search.generators.fourparam import FourParamGenerator
+from bqskit.passes.search.generators.simple import SimpleLayerGenerator
+from bqskit.passes.search.generators.wide import WideLayerGenerator
+from bqskit.utils.test.strategies import gates
+
+
+def test_gate_set_init() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert len(gate_set) == 2
+    assert CNOTGate() in gate_set
+    assert U3Gate() in gate_set
+    assert XGate() not in gate_set
+    assert YGate() not in gate_set
+
+
+def test_gate_set_empty() -> None:
+    gate_set = GateSet({})
+    assert len(gate_set) == 0
+    assert gate_set.radix_set == set()
+    with pytest.raises(RuntimeError):
+        gate_set.build_layer_generator()
+
+    with pytest.raises(RuntimeError):
+        gate_set.build_mq_layer_generator()
+
+    with pytest.raises(RuntimeError):
+        gate_set.get_general_sq_gate()
+
+
+def test_gate_set_build_layer_generator_simple() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    layergen = gate_set.build_layer_generator()
+    assert isinstance(layergen, SimpleLayerGenerator)
+    assert layergen.two_qudit_gate == CNOTGate()
+    assert layergen.initial_layer_gate == U3Gate()
+    assert layergen.single_qudit_gate_1 == U3Gate()
+    assert layergen.single_qudit_gate_2 == U3Gate()
+
+
+def test_gate_set_build_layer_generator_2q_basic() -> None:
+    gate_set = GateSet({CZGate(), PauliGate(1), XGate()})
+    layergen = gate_set.build_layer_generator()
+    assert isinstance(layergen, SimpleLayerGenerator)
+    assert layergen.two_qudit_gate == CZGate()
+    assert layergen.initial_layer_gate == PauliGate(1)
+    assert layergen.single_qudit_gate_1 == PauliGate(1)
+    assert layergen.single_qudit_gate_2 == PauliGate(1)
+
+
+def test_gate_set_build_layer_generator_3q_basic() -> None:
+    gate_set = GateSet({ToffoliGate(), CZGate(), U3Gate()})
+    layergen = gate_set.build_layer_generator()
+    assert isinstance(layergen, WideLayerGenerator)
+    assert ToffoliGate() in layergen.multi_qudit_gates
+    assert CZGate() in layergen.multi_qudit_gates
+    assert layergen.single_qudit_gate == U3Gate()
+
+
+def test_gate_set_build_layer_generator_qutrit() -> None:
+    gate_set = GateSet.default_gate_set(3)
+    layergen = gate_set.build_layer_generator()
+    assert isinstance(layergen, SimpleLayerGenerator)
+    assert layergen.two_qudit_gate == ArbitraryCPhaseGate([3, 3])
+    assert layergen.initial_layer_gate == VariableUnitaryGate(1, [3])
+    assert layergen.single_qudit_gate_1 == VariableUnitaryGate(1, [3])
+    assert layergen.single_qudit_gate_2 == VariableUnitaryGate(1, [3])
+
+# TODO: Add tests for build_layer_generator with hybrid qudits
+
+
+def test_gate_set_build_mq_layer_generator_simple() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    layergen = gate_set.build_mq_layer_generator()
+    assert isinstance(layergen, FourParamGenerator)
+
+
+def test_gate_set_build_mq_layer_generator_2q_basic() -> None:
+    gate_set = GateSet({CZGate(), RZGate(), SXGate()})
+    layergen = gate_set.build_mq_layer_generator()
+    assert isinstance(layergen, SimpleLayerGenerator)
+    assert layergen.two_qudit_gate == CZGate()
+    assert layergen.initial_layer_gate == U3Gate()
+    assert layergen.single_qudit_gate_1 == U3Gate()
+    assert layergen.single_qudit_gate_2 == U3Gate()
+
+
+def test_gate_set_build_mq_layer_generator_qutrit() -> None:
+    gate_set = GateSet.default_gate_set(3)
+    layergen = gate_set.build_mq_layer_generator()
+    assert isinstance(layergen, SimpleLayerGenerator)
+    assert layergen.two_qudit_gate == ArbitraryCPhaseGate([3, 3])
+    assert layergen.initial_layer_gate == VariableUnitaryGate(1, [3])
+    assert layergen.single_qudit_gate_1 == VariableUnitaryGate(1, [3])
+    assert layergen.single_qudit_gate_2 == VariableUnitaryGate(1, [3])
+
+
+def test_gate_set_get_general_sq_gate() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set.get_general_sq_gate() == U3Gate()
+
+    gate_set = GateSet({CNOTGate(), SXGate(), RZGate()})
+    assert gate_set.get_general_sq_gate() == U3Gate()
+
+    gate_set = GateSet({ArbitraryCPhaseGate([3, 3]), U8Gate()})
+    assert gate_set.get_general_sq_gate() == U8Gate()
+
+    gate_set = GateSet({
+        ArbitraryCPhaseGate(
+            [3, 3],
+        ), VariableUnitaryGate(1, [3]),
+    })
+    assert gate_set.get_general_sq_gate() == VariableUnitaryGate(1, [3])
+
+
+def test_gate_set_default_gate_set() -> None:
+    gate_set = GateSet.default_gate_set()
+    assert len(gate_set) == 2
+    assert CNOTGate() in gate_set
+    assert U3Gate() in gate_set
+
+
+def test_gate_set_default_gate_set_qutrits() -> None:
+    gate_set = GateSet.default_gate_set(3)
+    assert len(gate_set) == 2
+    assert VariableUnitaryGate(1, [3]) in gate_set
+    assert ArbitraryCPhaseGate([3, 3]) in gate_set
+
+
+def test_gate_set_default_gate_set_hybrid() -> None:
+    gate_set = GateSet.default_gate_set([2, 3])
+    assert len(gate_set) == 5
+    assert VariableUnitaryGate(1, [2]) in gate_set
+    assert VariableUnitaryGate(1, [3]) in gate_set
+    assert ArbitraryCPhaseGate([2, 2]) in gate_set
+    assert ArbitraryCPhaseGate([2, 3]) in gate_set
+    assert ArbitraryCPhaseGate([3, 3]) in gate_set
+
+
+def test_gate_set_single_qudit_gates() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert len(gate_set.single_qudit_gates) == 1
+    assert U3Gate() in gate_set.single_qudit_gates
+    assert CNOTGate() not in gate_set.single_qudit_gates
+
+
+def test_gate_set_two_qudit_gates() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert len(gate_set.two_qudit_gates) == 1
+    assert CNOTGate() in gate_set.two_qudit_gates
+    assert U3Gate() not in gate_set.two_qudit_gates
+
+
+def test_gate_set_many_qudit_gates() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert len(gate_set.many_qudit_gates) == 1
+    assert CNOTGate() not in gate_set.many_qudit_gates
+    assert U3Gate() not in gate_set.many_qudit_gates
+    assert ToffoliGate() in gate_set.many_qudit_gates
+
+
+def test_gate_set_multi_qudit_gates() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert len(gate_set.many_qudit_gates) == 1
+    assert CNOTGate() not in gate_set.many_qudit_gates
+    assert U3Gate() not in gate_set.many_qudit_gates
+    assert ToffoliGate() in gate_set.many_qudit_gates
+
+
+@given(builds(GateSet, sets(gates())))
+def test_gate_set_properties(gate_set: GateSet) -> None:
+    assert all(g.num_qudits == 1 for g in gate_set.single_qudit_gates)
+    assert all(g.num_qudits == 2 for g in gate_set.two_qudit_gates)
+    assert all(g.num_qudits > 2 for g in gate_set.many_qudit_gates)
+    assert all(g.num_qudits >= 2 for g in gate_set.multi_qudit_gates)
+
+
+def test_gate_set_union() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set.union({CNOTGate()}) == {CNOTGate(), U3Gate()}
+    assert gate_set.union({U3Gate()}) == {CNOTGate(), U3Gate()}
+    assert gate_set.union({CNOTGate(), U3Gate()}) == {CNOTGate(), U3Gate()}
+    assert len(GateSet({U3Gate()}).union(gate_set)) == 2
+    assert len(GateSet({CNOTGate()}).union(gate_set)) == 2
+
+
+def test_gate_set_intesection() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set.intersection({CNOTGate()}) == {CNOTGate()}
+    assert gate_set.intersection({U3Gate()}) == {U3Gate()}
+    assert gate_set.intersection({CNOTGate(), U3Gate()}) == {
+        CNOTGate(), U3Gate(),
+    }
+    assert len(GateSet({U3Gate()}).intersection(gate_set)) == 1
+
+
+def test_gate_set_difference() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set.difference({CNOTGate()}) == {U3Gate()}
+    assert gate_set.difference({U3Gate()}) == {CNOTGate()}
+    assert gate_set.difference({CNOTGate(), U3Gate()}) == set()
+    assert len(GateSet({U3Gate()}).difference(gate_set)) == 0
+
+
+def test_gate_set_symmetric_difference() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set.symmetric_difference({CNOTGate()}) == {U3Gate()}
+    assert gate_set.symmetric_difference({U3Gate()}) == {CNOTGate()}
+    assert gate_set.symmetric_difference({CNOTGate(), U3Gate()}) == set()
+    assert len(GateSet({U3Gate()}).symmetric_difference(gate_set)) == 1
+
+    gate_set2 = GateSet({XGate(), YGate()})
+    assert gate_set.symmetric_difference(
+        gate_set2,
+    ) == {CNOTGate(), U3Gate(), XGate(), YGate()}
+
+
+def test_gate_set_issubset() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert not gate_set.issubset({CNOTGate()})
+    assert not gate_set.issubset({U3Gate()})
+    assert gate_set.issubset({CNOTGate(), U3Gate()})
+    assert GateSet({U3Gate()}).issubset(gate_set)
+    assert GateSet({CNOTGate()}).issubset(gate_set)
+
+
+def test_gate_set_issuperset() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set.issuperset({CNOTGate()})
+    assert gate_set.issuperset({U3Gate()})
+    assert gate_set.issuperset({CNOTGate(), U3Gate()})
+    assert not gate_set.issuperset({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert not GateSet({U3Gate()}).issuperset(gate_set)
+    assert not GateSet({CNOTGate()}).issuperset(gate_set)
+
+
+def test_gate_set_isdisjoint() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert not gate_set.isdisjoint({CNOTGate()})
+    assert not gate_set.isdisjoint({U3Gate()})
+    assert not gate_set.isdisjoint({CNOTGate(), U3Gate()})
+    assert gate_set.isdisjoint({ToffoliGate()})
+
+
+def test_gate_set_iter() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert len(list(gate_set)) == 2
+    assert CNOTGate() in gate_set
+    assert U3Gate() in gate_set
+    for gate in gate_set:
+        assert gate in gate_set
+
+
+def test_gate_set_len() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert len(gate_set) == 2
+
+
+def test_gate_set_contains() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert CNOTGate() in gate_set
+    assert U3Gate() in gate_set
+    assert ToffoliGate() not in gate_set
+
+
+def test_gate_set_eq_other_gate_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set == gate_set
+    assert gate_set == GateSet({CNOTGate(), U3Gate()})
+    assert gate_set != GateSet({CNOTGate()})
+    assert gate_set != GateSet({U3Gate()})
+    assert gate_set != GateSet({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert gate_set != GateSet({ToffoliGate()})
+
+
+def test_gate_set_eq_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set == {CNOTGate(), U3Gate()}
+    assert gate_set != {CNOTGate()}
+    assert gate_set != {U3Gate()}
+    assert gate_set != {CNOTGate(), U3Gate(), ToffoliGate()}
+    assert gate_set != {ToffoliGate()}
+
+
+def test_gate_set_ne() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert not gate_set != gate_set
+    assert not gate_set != GateSet({CNOTGate(), U3Gate()})
+    assert gate_set != GateSet({CNOTGate()})
+    assert gate_set != GateSet({U3Gate()})
+    assert gate_set != GateSet({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert gate_set != GateSet({ToffoliGate()})
+
+
+def test_gate_set_ne_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert not gate_set != {CNOTGate(), U3Gate()}
+    assert gate_set != {CNOTGate()}
+    assert gate_set != {U3Gate()}
+    assert gate_set != {CNOTGate(), U3Gate(), ToffoliGate()}
+    assert gate_set != {ToffoliGate()}
+
+
+def test_gate_set_le() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set <= gate_set
+    assert gate_set <= GateSet({CNOTGate(), U3Gate()})
+    assert gate_set <= GateSet({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert not gate_set <= GateSet({CNOTGate()})
+    assert not gate_set <= GateSet({U3Gate()})
+    assert not gate_set <= GateSet({ToffoliGate()})
+
+
+def test_gate_set_le_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set <= {CNOTGate(), U3Gate()}
+    assert gate_set <= {CNOTGate(), U3Gate(), ToffoliGate()}
+    assert not gate_set <= {CNOTGate()}
+    assert not gate_set <= {U3Gate()}
+    assert not gate_set <= {ToffoliGate()}
+
+
+def test_gate_set_ge() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set >= gate_set
+    assert gate_set >= GateSet({CNOTGate(), U3Gate()})
+    assert gate_set >= GateSet({CNOTGate()})
+    assert gate_set >= GateSet({U3Gate()})
+    assert not gate_set >= GateSet({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert not gate_set >= GateSet({ToffoliGate()})
+
+
+def test_gate_set_ge_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set >= {CNOTGate(), U3Gate()}
+    assert gate_set >= {CNOTGate()}
+    assert gate_set >= {U3Gate()}
+    assert not gate_set >= {CNOTGate(), U3Gate(), ToffoliGate()}
+    assert not gate_set >= {ToffoliGate()}
+
+
+def test_gate_set_lt() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert not gate_set < gate_set
+    assert not gate_set < GateSet({CNOTGate(), U3Gate()})
+    assert gate_set < GateSet({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert not gate_set < GateSet({CNOTGate()})
+    assert not gate_set < GateSet({U3Gate()})
+    assert not gate_set < GateSet({ToffoliGate()})
+
+
+def test_gate_set_lt_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert not gate_set < {CNOTGate(), U3Gate()}
+    assert gate_set < {CNOTGate(), U3Gate(), ToffoliGate()}
+    assert not gate_set < {CNOTGate()}
+    assert not gate_set < {U3Gate()}
+    assert not gate_set < {ToffoliGate()}
+
+
+def test_gate_set_gt() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert not gate_set > gate_set
+    assert not gate_set > GateSet({CNOTGate(), U3Gate()})
+    assert gate_set > GateSet({CNOTGate()})
+    assert gate_set > GateSet({U3Gate()})
+    assert not gate_set > GateSet({CNOTGate(), U3Gate(), ToffoliGate()})
+    assert not gate_set > GateSet({ToffoliGate()})
+
+
+def test_gate_set_gt_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert not gate_set > {CNOTGate(), U3Gate()}
+    assert gate_set > {CNOTGate()}
+    assert gate_set > {U3Gate()}
+    assert not gate_set > {CNOTGate(), U3Gate(), ToffoliGate()}
+    assert not gate_set > {ToffoliGate()}
+
+
+def test_gate_set_and() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set & gate_set == gate_set
+    assert gate_set & GateSet({CNOTGate(), U3Gate()}) == gate_set
+    assert gate_set & GateSet({CNOTGate()}) == GateSet({CNOTGate()})
+    assert gate_set & GateSet({U3Gate()}) == GateSet({U3Gate()})
+    assert gate_set & GateSet({CNOTGate(), U3Gate(), ToffoliGate()}) == gate_set
+    assert gate_set & GateSet({ToffoliGate()}) == GateSet({})
+    assert gate_set & GateSet({}) == GateSet({})
+
+
+def test_gate_set_and_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set & {CNOTGate(), U3Gate()} == gate_set
+    assert gate_set & {CNOTGate()} == GateSet({CNOTGate()})
+    assert gate_set & {U3Gate()} == GateSet({U3Gate()})
+    assert gate_set & {CNOTGate(), U3Gate(), ToffoliGate()} == gate_set
+    assert gate_set & {ToffoliGate()} == GateSet({})
+    assert gate_set & set() == GateSet({})
+
+
+def test_gate_set_or() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set | gate_set == gate_set
+    assert gate_set | GateSet({CNOTGate(), U3Gate()}) == gate_set
+    assert gate_set | GateSet({CNOTGate()}) == gate_set
+    assert gate_set | GateSet({U3Gate()}) == gate_set
+    assert gate_set | GateSet({CNOTGate(), U3Gate(), ToffoliGate()}) == GateSet(
+        {CNOTGate(), U3Gate(), ToffoliGate()},
+    )
+    assert gate_set | GateSet({ToffoliGate()}) == GateSet(
+        {CNOTGate(), U3Gate(), ToffoliGate()},
+    )
+    assert gate_set | GateSet({}) == gate_set
+
+
+def test_gate_set_or_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set | {CNOTGate(), U3Gate()} == gate_set
+    assert gate_set | {CNOTGate()} == gate_set
+    assert gate_set | {U3Gate()} == gate_set
+    assert gate_set | {CNOTGate(), U3Gate(), ToffoliGate()} == GateSet({
+        CNOTGate(), U3Gate(), ToffoliGate(),
+    })
+    assert gate_set | {ToffoliGate()} == GateSet(
+        {CNOTGate(), U3Gate(), ToffoliGate()},
+    )
+    assert gate_set | set() == gate_set
+
+
+def test_gate_set_xor() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set ^ gate_set == GateSet({})
+    assert gate_set ^ GateSet({CNOTGate(), U3Gate()}) == GateSet({})
+    assert gate_set ^ GateSet({CNOTGate()}) == GateSet({U3Gate()})
+    assert gate_set ^ GateSet({U3Gate()}) == GateSet({CNOTGate()})
+    assert gate_set ^ GateSet(
+        {CNOTGate(), U3Gate(), ToffoliGate()},
+    ) == GateSet({ToffoliGate()})
+    assert gate_set ^ GateSet({ToffoliGate()}) == GateSet(
+        {CNOTGate(), U3Gate(), ToffoliGate()},
+    )
+    assert gate_set ^ GateSet({}) == gate_set
+
+
+def test_gate_set_xor_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set ^ {CNOTGate(), U3Gate()} == GateSet({})
+    assert gate_set ^ {CNOTGate()} == GateSet({U3Gate()})
+    assert gate_set ^ {U3Gate()} == GateSet({CNOTGate()})
+    assert gate_set ^ {
+        CNOTGate(), U3Gate(), ToffoliGate(),
+    } == GateSet({ToffoliGate()})
+    assert gate_set ^ {ToffoliGate()} == GateSet(
+        {CNOTGate(), U3Gate(), ToffoliGate()},
+    )
+    assert gate_set ^ set() == gate_set
+
+
+def test_gate_set_sub() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set - gate_set == GateSet({})
+    assert gate_set - GateSet({CNOTGate(), U3Gate()}) == GateSet({})
+    assert gate_set - GateSet({CNOTGate()}) == GateSet({U3Gate()})
+    assert gate_set - GateSet({U3Gate()}) == GateSet({CNOTGate()})
+    assert gate_set - \
+        GateSet({CNOTGate(), U3Gate(), ToffoliGate()}) == GateSet({})
+    assert gate_set - GateSet({ToffoliGate()}) == gate_set
+    assert gate_set - GateSet({}) == gate_set
+
+
+def test_gate_set_sub_other_set() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert gate_set - {CNOTGate(), U3Gate()} == GateSet({})
+    assert gate_set - {CNOTGate()} == GateSet({U3Gate()})
+    assert gate_set - {U3Gate()} == GateSet({CNOTGate()})
+    assert gate_set - {CNOTGate(), U3Gate(), ToffoliGate()} == GateSet({})
+    assert gate_set - {ToffoliGate()} == gate_set
+    assert gate_set - set() == gate_set
+
+
+def test_gate_set_set_update() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    x: set[Gate] = set()
+    x.update(gate_set)
+    assert x == {CNOTGate(), U3Gate()}
+    assert x == gate_set
+
+
+def test_gate_set_str() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert (
+        str(gate_set) == 'GateSet({CNOTGate, U3Gate})'
+        or str(gate_set) == 'GateSet({U3Gate, CNOTGate})'
+    )
+
+
+def test_gate_set_repr() -> None:
+    gate_set = GateSet({CNOTGate(), U3Gate()})
+    assert (
+        repr(gate_set) == 'GateSet({CNOTGate, U3Gate})'
+        or repr(gate_set) == 'GateSet({U3Gate, CNOTGate})'
+    )
diff --git a/tests/compiler/test_machine.py b/tests/compiler/test_machine.py
index 586b7e40e..8c6e4702f 100644
--- a/tests/compiler/test_machine.py
+++ b/tests/compiler/test_machine.py
@@ -1,3 +1,4 @@
+# TODO: is_compatabile
 from __future__ import annotations
 
 import pytest
diff --git a/tests/conftest.py b/tests/conftest.py
index 812bb6a48..18104b002 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -19,7 +19,7 @@
 from hypothesis import settings
 from scipy.stats import unitary_group
 
-from bqskit.compiler.compiler import Compiler
+from bqskit.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
 from bqskit.ir.gates import CNOTGate
@@ -34,6 +34,7 @@
 from bqskit.ir.gates import HGate
 from bqskit.ir.gates import IdentityGate
 from bqskit.ir.gates import ISwapGate
+from bqskit.ir.gates import PauliGate
 from bqskit.ir.gates import RXGate
 from bqskit.ir.gates import RYGate
 from bqskit.ir.gates import RZGate
@@ -50,6 +51,7 @@
 from bqskit.ir.gates import U2Gate
 from bqskit.ir.gates import U3Gate
 from bqskit.ir.gates import U8Gate
+from bqskit.ir.gates import VariableUnitaryGate
 from bqskit.ir.gates import XGate
 from bqskit.ir.gates import XXGate
 from bqskit.ir.gates import YGate
@@ -58,9 +60,7 @@
 from bqskit.utils.typing import is_sequence
 # from bqskit.ir.gates import CircuitGate
 # from bqskit.ir.gates import ControlledGate
-# from bqskit.ir.gates import PauliGate
 # from bqskit.ir.gates import PermutationGate
-# from bqskit.ir.gates import VariableUnitaryGate
 
 # region Hypothesis Settings
 
@@ -140,6 +140,13 @@ def compiler() -> Iterator[Compiler]:
     compiler.close()
 
 
+@pytest.fixture
+def fresh_compiler() -> Iterator[Compiler]:
+    compiler = Compiler()
+    yield compiler
+    compiler.close()
+
+
 @pytest.fixture
 def toffoli_unitary() -> UnitaryMatrix:
     return UnitaryMatrix(TOFFOLI)
@@ -291,10 +298,14 @@ def gen_invalid_utry_np(
     g for g in BQSKIT_GATES
     if g.is_qubit_only() and g.num_qudits == 1
 ]
+SINGLE_QUBIT_GENERAL_GATES = [
+    U3Gate(), VariableUnitaryGate(1, [2]), PauliGate(1),
+]
 SINGLE_QUTRIT_GATES = [
     g for g in BQSKIT_GATES
     if g.is_qutrit_only() and g.num_qudits == 1
 ]
+SINGLE_QUTRIT_GENERAL_GATES = [U8Gate(), VariableUnitaryGate(1, [3])]
 TWO_QUBIT_GATES = [
     g for g in BQSKIT_GATES
     if g.is_qubit_only() and g.num_qudits == 2
@@ -349,12 +360,24 @@ def single_qubit_gate(request: Any) -> Gate:
     return request.param
 
 
+@pytest.fixture(params=SINGLE_QUBIT_GENERAL_GATES, ids=lambda gate: repr(gate))
+def single_qubit_general_gate(request: Any) -> Gate:
+    """Provides all of SINGLE_QUBIT_GENERAL_GATES as a gate fixture."""
+    return request.param
+
+
 @pytest.fixture(params=SINGLE_QUTRIT_GATES, ids=lambda gate: repr(gate))
 def single_qutrit_gate(request: Any) -> Gate:
     """Provides all of SINGLE_QUTRIT_GATES as a gate fixture."""
     return request.param
 
 
+@pytest.fixture(params=SINGLE_QUTRIT_GENERAL_GATES, ids=lambda gate: repr(gate))
+def single_qutrit_general_gate(request: Any) -> Gate:
+    """Provides all of SINGLE_QUTRIT_GENERAL_GATES as a gate fixture."""
+    return request.param
+
+
 @pytest.fixture(params=TWO_QUBIT_GATES, ids=lambda gate: repr(gate))
 def two_qubit_gate(request: Any) -> Gate:
     """Provides all of two_QUBIT_GATES as a gate fixture."""
diff --git a/tests/exec/__init__.py b/tests/exec/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/ext/test_cirq.py b/tests/ext/test_cirq.py
index f3eff355e..f68bb557b 100644
--- a/tests/ext/test_cirq.py
+++ b/tests/ext/test_cirq.py
@@ -4,17 +4,16 @@
 import pytest
 pytest.importorskip('cirq')
 
-import cirq
-from cirq import Circuit as QuantumCircuit
-
-from bqskit.compiler.compile import compile
-from bqskit.compiler.compiler import Compiler
-from bqskit.ext import bqskit_to_cirq
-from bqskit.ext import cirq_to_bqskit
-from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import HGate
 from bqskit.qis import UnitaryMatrix
+from bqskit.ir.gates import HGate
+from bqskit.ir.gates import CNOTGate
+from bqskit.ir.circuit import Circuit
+from bqskit.ext import cirq_to_bqskit
+from bqskit.ext import bqskit_to_cirq
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.compile import compile
+from cirq import Circuit as QuantumCircuit
+import cirq
 
 
 class TestTranslate:
diff --git a/tests/ext/test_pytket.py b/tests/ext/test_pytket.py
index 2fc388935..0124051ca 100644
--- a/tests/ext/test_pytket.py
+++ b/tests/ext/test_pytket.py
@@ -3,20 +3,20 @@
 
 import pytest
 pytest.importorskip('pytket')
+pytest.importorskip('pytket.extensions.qiskit')
 
-import numpy as np
-from pytket import OpType
-from pytket.circuit import Circuit as QubitCircuit
-from pytket.extensions.qiskit import AerUnitaryBackend
-
-from bqskit.compiler.compile import compile
-from bqskit.compiler.compiler import Compiler
-from bqskit.ext import bqskit_to_pytket
-from bqskit.ext import pytket_to_bqskit
-from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import U3Gate
 from bqskit.qis import UnitaryMatrix
+from bqskit.ir.gates import U3Gate
+from bqskit.ir.gates import CNOTGate
+from bqskit.ir.circuit import Circuit
+from bqskit.ext import pytket_to_bqskit
+from bqskit.ext import bqskit_to_pytket
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.compile import compile
+# from pytket.extensions.qiskit import AerUnitaryBackend
+from pytket.circuit import Circuit as QubitCircuit
+from pytket import OpType
+import numpy as np
 
 
 class TestTranslate:
@@ -67,10 +67,7 @@ def pytket_circuit(self) -> QubitCircuit:
         return circuit
 
     def get_unitary(self, qc: QubitCircuit) -> np.ndarray:  # type: ignore
-        backend = AerUnitaryBackend()
-        handle = backend.process_circuit(qc)
-        result = backend.get_result(handle)
-        return result.get_unitary()
+        return qc.get_unitary()
 
     def test_bqskit_to_bqskit(self, bqskit_circuit: Circuit) -> None:
         in_utry = bqskit_circuit.get_unitary()
@@ -85,23 +82,19 @@ def test_pytket_to_pytket(self, pytket_circuit: QubitCircuit) -> None:
         assert in_utry.get_distance_from(out_utry) < 1e-7
 
     def test_compile_bqskit(
-        self,
-        pytket_circuit: QubitCircuit,
-        compiler: Compiler,
+        self, pytket_circuit: QubitCircuit,
+        compiler:Compiler,
     ) -> None:
         in_utry = UnitaryMatrix(self.get_unitary(pytket_circuit))
         bqskit_circuit = pytket_to_bqskit(pytket_circuit)
-        bqskit_out_circuit = compile(
-            bqskit_circuit, max_synthesis_size=2, compiler=compiler,
-        )
+        bqskit_out_circuit = compile(bqskit_circuit, max_synthesis_size=2, compiler=compiler)
         out_circuit = bqskit_to_pytket(bqskit_out_circuit)
         out_utry = UnitaryMatrix(self.get_unitary(out_circuit))
         assert in_utry.get_distance_from(out_utry) < 1e-5
 
     def test_synthesis_bqskit(
-        self,
-        pytket_circuit: QubitCircuit,
-        compiler: Compiler,
+        self, pytket_circuit: QubitCircuit,
+        compiler:Compiler,
     ) -> None:
         in_utry = UnitaryMatrix(self.get_unitary(pytket_circuit))
         bqskit_circuit = pytket_to_bqskit(pytket_circuit)
diff --git a/tests/ext/test_qiskit.py b/tests/ext/test_qiskit.py
index e2151f2c5..54252ae46 100644
--- a/tests/ext/test_qiskit.py
+++ b/tests/ext/test_qiskit.py
@@ -1,21 +1,20 @@
 # flake8: noqa
 from __future__ import annotations
+from bqskit.compiler.compiler import Compiler
 
 import pytest
 pytest.importorskip('qiskit')
 
-import qiskit.quantum_info as qi
-from qiskit import QuantumCircuit
-from qiskit import transpile
-
-from bqskit.compiler.compile import compile
-from bqskit.compiler.compiler import Compiler
-from bqskit.ext import bqskit_to_qiskit
-from bqskit.ext import qiskit_to_bqskit
-from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import U3Gate
 from bqskit.qis import UnitaryMatrix
+from bqskit.ir.gates import U3Gate
+from bqskit.ir.gates import CNOTGate
+from bqskit.ir.circuit import Circuit
+from bqskit.ext import qiskit_to_bqskit
+from bqskit.ext import bqskit_to_qiskit
+from bqskit.compiler.compile import compile
+from qiskit import transpile
+from qiskit import QuantumCircuit
+import qiskit.quantum_info as qi
 
 
 class TestTranslate:
@@ -52,7 +51,7 @@ def qiskit_circuit(self) -> QuantumCircuit:
         circuit.cnot(0, 1)
         circuit.cnot(0, 2)
         circuit.cnot(0, 2)
-        circuit.u3(1, 2.4, 3, 0)
+        circuit.u(1, 2.4, 3, 0)
         circuit.u(1, 2.2, 3, 1)
         circuit.u(1, 2.1, 3, 2)
         circuit.u(1, 2.1, 3, 2)
@@ -79,24 +78,23 @@ def test_qiskit_to_qiskit(self, qiskit_circuit: QuantumCircuit) -> None:
         assert in_utry.get_distance_from(out_utry) < 1e-7
 
     def test_compile_bqskit(
-        self,
-        qiskit_circuit: QuantumCircuit,
-        compiler: Compiler,
+        self, qiskit_circuit: QuantumCircuit,
+        compiler:Compiler,
     ) -> None:
         qc = qiskit_circuit
         in_utry = UnitaryMatrix(qi.Operator(qc).data)
         bqskit_circuit = qiskit_to_bqskit(qc)
         bqskit_out_circuit = compile(
-            bqskit_circuit, max_synthesis_size=2, compiler=compiler,
+            bqskit_circuit, max_synthesis_size=2,
+            compiler=compiler,
         )
         out_circuit = bqskit_to_qiskit(bqskit_out_circuit)
         out_utry = UnitaryMatrix(qi.Operator(out_circuit).data)
         assert in_utry.get_distance_from(out_utry) < 1e-5
 
     def test_synthesis_bqskit(
-        self,
-        qiskit_circuit: QuantumCircuit,
-        compiler: Compiler,
+        self, qiskit_circuit: QuantumCircuit,
+        compiler:Compiler,
     ) -> None:
         qc = qiskit_circuit
         in_utry = UnitaryMatrix(qi.Operator(qc).data)
diff --git a/tests/ext/test_qutip.py b/tests/ext/test_qutip.py
index 78204a3c4..5851a28e5 100644
--- a/tests/ext/test_qutip.py
+++ b/tests/ext/test_qutip.py
@@ -4,17 +4,16 @@
 import pytest
 pytest.importorskip('qutip')
 
-from qutip import CircuitSimulator
-from qutip import QubitCircuit
-
-from bqskit.compiler.compile import compile
-from bqskit.compiler.compiler import Compiler
-from bqskit.ext import bqskit_to_qutip
-from bqskit.ext import qutip_to_bqskit
-from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CNOTGate
-from bqskit.ir.gates import U3Gate
 from bqskit.qis import UnitaryMatrix
+from bqskit.ir.gates import U3Gate
+from bqskit.ir.gates import CNOTGate
+from bqskit.ir.circuit import Circuit
+from bqskit.ext import qutip_to_bqskit
+from bqskit.ext import bqskit_to_qutip
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.compile import compile
+from qutip.qip.circuit import QubitCircuit
+from qutip.qip.circuit import CircuitSimulator
 
 
 class TestTranslate:
diff --git a/tests/ir/__init__.py b/tests/ir/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/ir/circuit/test_batch_replace.py b/tests/ir/circuit/test_batch_replace.py
index 501621d56..9e7c1cc1f 100644
--- a/tests/ir/circuit/test_batch_replace.py
+++ b/tests/ir/circuit/test_batch_replace.py
@@ -3,6 +3,7 @@
 
 from random import randint
 
+from bqskit.compiler.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.constant.cx import CNOTGate
 from bqskit.ir.operation import Operation
@@ -106,7 +107,7 @@ def test_batch_replace(self) -> None:
             (3, 1),
         ) == op_5b
 
-    def test_random_batch_replace(self) -> None:
+    def test_random_batch_replace(self, compiler: Compiler) -> None:
         num_gates = 200
         num_q = 10
 
@@ -118,7 +119,7 @@ def test_random_batch_replace(self) -> None:
                 b = (b + 1) % num_q
             circ.append_gate(CNOTGate(), [a, b])
 
-        ScanPartitioner(2).run(circ, {})
+        circ = compiler.compile(circ, [ScanPartitioner(2)])
 
         points = []
         ops = []
diff --git a/tests/ir/circuit/test_properties.py b/tests/ir/circuit/test_properties.py
index 4ea26294f..2d9cc8f9a 100644
--- a/tests/ir/circuit/test_properties.py
+++ b/tests/ir/circuit/test_properties.py
@@ -1,12 +1,12 @@
 """
 This test module verifies all circuit properties.
 
-The Circuit class defines multiple properties, but also inherits many
-from the Unitary base class.
+The Circuit class defines multiple properties, but also inherits many from the
+Unitary base class.
 
-This test is broken down into multiple parts. First, a few simple known
-circuits have their properties tested. Then, each property is tested
-in depth individually.
+This test is broken down into multiple parts. First, a few simple known circuits
+have their properties tested. Then, each property is tested in depth
+individually.
 """
 from __future__ import annotations
 
@@ -15,6 +15,7 @@
 import numpy as np
 import pytest
 
+from bqskit.compiler.gateset import GateSet
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gate import Gate
 from bqskit.ir.gates import CNOTGate
@@ -88,7 +89,7 @@ def test_coupling_graph(self, simple_circuit: Circuit) -> None:
 
     def test_gate_set(self, simple_circuit: Circuit) -> None:
         gate_set = simple_circuit.gate_set
-        assert isinstance(gate_set, set)
+        assert isinstance(gate_set, GateSet)
         assert len(gate_set) == 2
         assert XGate() in gate_set
         assert CNOTGate() in gate_set
@@ -155,7 +156,7 @@ def test_coupling_graph(self, swap_circuit: Circuit) -> None:
 
     def test_gate_set(self, swap_circuit: Circuit) -> None:
         gate_set = swap_circuit.gate_set
-        assert isinstance(gate_set, set)
+        assert isinstance(gate_set, GateSet)
         assert len(gate_set) == 1
         assert CNOTGate() in gate_set
 
@@ -223,7 +224,7 @@ def test_coupling_graph(self, toffoli_circuit: Circuit) -> None:
 
     def test_gate_set(self, toffoli_circuit: Circuit) -> None:
         gate_set = toffoli_circuit.gate_set
-        assert isinstance(gate_set, set)
+        assert isinstance(gate_set, GateSet)
         assert len(gate_set) == 4
         assert CNOTGate() in gate_set
         assert HGate() in gate_set
@@ -918,7 +919,7 @@ class TestGetGateSet:
     """This tests `circuit.gate_set`."""
 
     def test_type(self, r6_qudit_circuit: Circuit) -> None:
-        assert isinstance(r6_qudit_circuit.gate_set, set)
+        assert isinstance(r6_qudit_circuit.gate_set, GateSet)
         assert all(
             isinstance(gate, Gate)
             for gate in r6_qudit_circuit.gate_set
@@ -927,7 +928,7 @@ def test_type(self, r6_qudit_circuit: Circuit) -> None:
     def test_empty(self) -> None:
         circuit = Circuit(4)
         assert len(circuit.gate_set) == 0
-        assert isinstance(circuit.gate_set, set)
+        assert isinstance(circuit.gate_set, GateSet)
 
     def test_adding_gate(self) -> None:
         circuit = Circuit(1)
diff --git a/tests/ir/circuit/test_region_methods.py b/tests/ir/circuit/test_region_methods.py
index 83c189480..18c34c86c 100644
--- a/tests/ir/circuit/test_region_methods.py
+++ b/tests/ir/circuit/test_region_methods.py
@@ -240,6 +240,33 @@ def test_parameters(self) -> None:
 
         assert after_fold == before_fold
 
+    def test_deterministic_gate_ordering(self) -> None:
+        def create_circuit(
+            coupling_graph: list[tuple[int, int]],
+            regions: list[list[int]],
+            iterations: int,
+        ) -> Circuit:
+            circuit = Circuit(4)
+            for location in regions * iterations:
+                circuit.append_gate(CNOTGate(), location)
+                circuit.append_gate(U3Gate(), [location[0]])
+                circuit.append_gate(U3Gate(), [location[1]])
+            return circuit
+
+        coupling_graph = [(0, 1), (1, 2), (2, 3)]
+        circuits = [
+            create_circuit(
+                coupling_graph, [[0, 1], [2, 3]], 4,
+            ) for x in range(10)
+        ]
+
+        for (a, b) in coupling_graph:
+            region = {a: (0, circuits[0].depth), b: (0, circuits[0].depth)}
+
+            operations = circuits[0][region]
+            for circuit in circuits[1:]:
+                assert circuit[region] == operations
+
 
 class TestSurround:
     """This tests circuit.surround."""
diff --git a/tests/ir/gates/constant/test_bgate.py b/tests/ir/gates/constant/test_bgate.py
new file mode 100644
index 000000000..08b2e5d68
--- /dev/null
+++ b/tests/ir/gates/constant/test_bgate.py
@@ -0,0 +1,19 @@
+"""This module tests the IdentityGate class."""
+from __future__ import annotations
+
+import numpy as np
+
+from bqskit.ir.gates.constant.b import BGate
+
+
+def test_identity() -> None:
+    B = BGate()
+    U = np.array([
+        [0.92387953 + 0.j, 0. + 0.j, 0. + 0.j, 0. + 0.38268343j],
+        [0. + 0.j, 0.38268343 + 0.j, 0. + 0.92387953j, 0. + 0.j],
+        [0. + 0.j, 0. + 0.92387953j, 0.38268343 + 0.j, 0. + 0.j],
+        [0. + 0.38268343j, 0. + 0.j, 0. + 0.j, 0.92387953 + 0.j],
+    ])
+    assert B.num_qudits == 2
+    assert B.num_params == 0
+    assert B.get_unitary() == U
diff --git a/tests/ir/gates/constant/test_rccx.py b/tests/ir/gates/constant/test_rccx.py
new file mode 100644
index 000000000..187d1ba15
--- /dev/null
+++ b/tests/ir/gates/constant/test_rccx.py
@@ -0,0 +1,49 @@
+from __future__ import annotations
+
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CXGate
+from bqskit.ir.gates import HGate
+from bqskit.ir.gates import RC3XGate
+from bqskit.ir.gates import RCCXGate
+from bqskit.ir.gates import TdgGate
+from bqskit.ir.gates import TGate
+
+
+def test_margolus() -> None:
+    c = Circuit(3)
+    c.append_gate(HGate(), 2)
+    c.append_gate(TGate(), 2)
+    c.append_gate(CXGate(), (1, 2))
+    c.append_gate(TdgGate(), 2)
+    c.append_gate(CXGate(), (0, 2))
+    c.append_gate(TGate(), 2)
+    c.append_gate(CXGate(), (1, 2))
+    c.append_gate(TdgGate(), 2)
+    c.append_gate(HGate(), 2)
+    assert c.get_unitary().get_distance_from(RCCXGate().get_unitary()) < 1e-8
+
+
+def test_rc3x() -> None:
+    c = Circuit(4)
+    c.append_gate(HGate(), 3)
+    c.append_gate(TGate(), 3)
+    c.append_gate(CXGate(), (2, 3))
+    c.append_gate(TdgGate(), 3)
+    c.append_gate(HGate(), 3)
+    c.append_gate(CXGate(), (0, 3))
+    c.append_gate(TGate(), 3)
+    c.append_gate(CXGate(), (1, 3))
+    c.append_gate(TdgGate(), 3)
+    c.append_gate(CXGate(), (0, 3))
+    c.append_gate(TGate(), 3)
+    c.append_gate(CXGate(), (1, 3))
+    c.append_gate(TdgGate(), 3)
+    c.append_gate(HGate(), 3)
+    c.append_gate(TGate(), 3)
+    c.append_gate(CXGate(), (2, 3))
+    c.append_gate(TdgGate(), 3)
+    c.append_gate(HGate(), 3)
+    import numpy as np
+    print(np.round(c.get_unitary(), 1))
+
+    assert c.get_unitary().get_distance_from(RC3XGate().get_unitary()) < 1e-8
diff --git a/tests/ir/gates/parameterized/test_ccp.py b/tests/ir/gates/parameterized/test_ccp.py
new file mode 100644
index 000000000..530d726a2
--- /dev/null
+++ b/tests/ir/gates/parameterized/test_ccp.py
@@ -0,0 +1,30 @@
+"""This module tests the U1Gate class."""
+from __future__ import annotations
+
+import numpy as np
+from hypothesis import given
+from hypothesis.strategies import floats
+
+from bqskit.ir.gates import CCPGate
+from bqskit.qis.unitary import UnitaryMatrix
+
+
+@given(floats(allow_nan=False, allow_infinity=False, width=32))
+def test_get_unitary(angle: float) -> None:
+    utry = UnitaryMatrix(
+        [
+            [1, 0, 0, 0, 0, 0, 0, 0],
+            [0, 1, 0, 0, 0, 0, 0, 0],
+            [0, 0, 1, 0, 0, 0, 0, 0],
+            [0, 0, 0, 1, 0, 0, 0, 0],
+            [0, 0, 0, 0, 1, 0, 0, 0],
+            [0, 0, 0, 0, 0, 1, 0, 0],
+            [0, 0, 0, 0, 0, 0, 1, 0],
+            [0, 0, 0, 0, 0, 0, 0, np.exp(1j * angle)],
+        ],
+    )
+    ccp = CCPGate()
+    assert ccp.num_params == 1
+    assert ccp.num_qudits == 3
+    dist = ccp.get_unitary([angle]).get_distance_from(utry)
+    assert dist < 1e-7
diff --git a/tests/ir/gates/parameterized/test_cp.py b/tests/ir/gates/parameterized/test_cp.py
new file mode 100644
index 000000000..1832f1917
--- /dev/null
+++ b/tests/ir/gates/parameterized/test_cp.py
@@ -0,0 +1,26 @@
+"""This module tests the U1Gate class."""
+from __future__ import annotations
+
+import numpy as np
+from hypothesis import given
+from hypothesis.strategies import floats
+
+from bqskit.ir.gates import CPGate
+from bqskit.qis.unitary import UnitaryMatrix
+
+
+@given(floats(allow_nan=False, allow_infinity=False, width=32))
+def test_get_unitary(angle: float) -> None:
+    utry = UnitaryMatrix(
+        [
+            [1, 0, 0, 0],
+            [0, 1, 0, 0],
+            [0, 0, 1, 0],
+            [0, 0, 0, np.exp(1j * angle)],
+        ],
+    )
+    cp = CPGate()
+    assert cp.num_params == 1
+    assert cp.num_qudits == 2
+    dist = cp.get_unitary([angle]).get_distance_from(utry)
+    assert dist < 1e-7
diff --git a/tests/ir/gates/parameterized/test_u3.py b/tests/ir/gates/parameterized/test_u3.py
index 417618ad4..75e9f13d8 100644
--- a/tests/ir/gates/parameterized/test_u3.py
+++ b/tests/ir/gates/parameterized/test_u3.py
@@ -29,5 +29,5 @@ def test_get_unitary(angle1: float, angle2: float, angle3: float) -> None:
 
 @given(unitaries(1, (2,)))
 def test_calc_params(utry: UnitaryMatrix) -> None:
-    params = U3Gate.calc_params(utry)
+    params = U3Gate().calc_params(utry)
     assert U3Gate().get_unitary(params).get_distance_from(utry) < 1e-7
diff --git a/tests/ir/gates/parameterized/test_u8.py b/tests/ir/gates/parameterized/test_u8.py
new file mode 100644
index 000000000..faac54c9a
--- /dev/null
+++ b/tests/ir/gates/parameterized/test_u8.py
@@ -0,0 +1,12 @@
+"""This module tests the U3Gate class."""
+from __future__ import annotations
+
+from bqskit.ir.gates import U8Gate
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+
+
+def test_calc_params() -> None:
+    for _ in range(100):
+        U = UnitaryMatrix.random(1, [3])
+        params = U8Gate().calc_params(U)
+        assert U8Gate().get_unitary(params).get_distance_from(U) < 1e-7
diff --git a/tests/ir/lang/__init__.py b/tests/ir/lang/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/ir/lang/test_qasm_decode.py b/tests/ir/lang/test_qasm_decode.py
index 0a41689fd..c912705b6 100644
--- a/tests/ir/lang/test_qasm_decode.py
+++ b/tests/ir/lang/test_qasm_decode.py
@@ -7,10 +7,12 @@
 import pytest
 
 from bqskit.ext.qiskit import qiskit_to_bqskit
+from bqskit.ir.gates.barrier import BarrierPlaceholder
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.gates.constant.cx import CNOTGate
 from bqskit.ir.gates.measure import MeasurementPlaceholder
 from bqskit.ir.gates.parameterized.u1 import U1Gate
+from bqskit.ir.gates.parameterized.u1q import U1qGate
 from bqskit.ir.gates.parameterized.u2 import U2Gate
 from bqskit.ir.gates.parameterized.u3 import U3Gate
 from bqskit.ir.lang.language import LangException
@@ -360,3 +362,78 @@ def test_CX_gate() -> None:
     circuit = OPENQASM2Language().decode(input)
     assert circuit.num_operations == 1
     assert circuit[0, 0].gate == CNOTGate()
+
+
+def test_barrier_full_register() -> None:
+    input = """
+        OPENQASM 2.0;
+        qreg q[2];
+        CX q[0],q[1];
+        barrier q;
+        CX q[0],q[1];
+    """
+    circuit = OPENQASM2Language().decode(input)
+    assert circuit.num_operations == 3
+    assert circuit[0, 0].gate == CNOTGate()
+    assert circuit[1, 0].gate == BarrierPlaceholder(2)
+    assert circuit[2, 0].gate == CNOTGate()
+
+
+def test_barrier_indiviual_qubits() -> None:
+    input = """
+        OPENQASM 2.0;
+        qreg q[2];
+        CX q[0],q[1];
+        barrier q[0], q[1];
+        CX q[0],q[1];
+    """
+    circuit = OPENQASM2Language().decode(input)
+    assert circuit.num_operations == 3
+    assert circuit[0, 0].gate == CNOTGate()
+    assert circuit[1, 0].gate == BarrierPlaceholder(2)
+    assert circuit[2, 0].gate == CNOTGate()
+
+
+def test_barrier_mixed() -> None:
+    input = """
+        OPENQASM 2.0;
+        qreg q[2];
+        qreg r[2];
+        CX q[0],q[1];
+        barrier q, r[0];
+        CX q[0],q[1];
+    """
+    circuit = OPENQASM2Language().decode(input)
+    assert circuit.num_operations == 3
+    assert circuit[0, 0].gate == CNOTGate()
+    assert circuit[1, 0].gate == BarrierPlaceholder(3)
+    assert circuit[2, 0].gate == CNOTGate()
+
+
+def test_barrier_mixed_three() -> None:
+    input = """
+        OPENQASM 2.0;
+        qreg q[2];
+        qreg r[2];
+        CX q[0],q[1];
+        barrier q, r[0], r[1];
+        CX q[0],q[1];
+    """
+    circuit = OPENQASM2Language().decode(input)
+    assert circuit.num_operations == 3
+    assert circuit[0, 0].gate == CNOTGate()
+    assert circuit[1, 0].gate == BarrierPlaceholder(4)
+    assert circuit[2, 0].gate == CNOTGate()
+
+
+def test_U1Q_gate() -> None:
+    input = """
+        OPENQASM 2.0;
+        qreg q[1];
+        U1q(3.141592653589793, -0.8510194827063557) q[0];
+        u1q(3.141592653589793, -0.8510194827063557) q[0];
+    """
+    circuit = OPENQASM2Language().decode(input)
+    assert circuit.num_operations == 2
+    assert circuit[0, 0].gate == U1qGate()
+    assert circuit[1, 0].gate == U1qGate()
diff --git a/tests/ir/lang/test_qasm_encode.py b/tests/ir/lang/test_qasm_encode.py
index 272dbd541..182540f39 100644
--- a/tests/ir/lang/test_qasm_encode.py
+++ b/tests/ir/lang/test_qasm_encode.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CNOTGate
 from bqskit.ir.gates import U3Gate
 from bqskit.ir.lang.qasm2 import OPENQASM2Language
diff --git a/tests/ir/opt/minimizers/test_bfgs.py b/tests/ir/opt/minimizers/test_bfgs.py
index 8ef3ca4f1..60cac7efa 100644
--- a/tests/ir/opt/minimizers/test_bfgs.py
+++ b/tests/ir/opt/minimizers/test_bfgs.py
@@ -2,7 +2,7 @@
 
 import numpy as np
 
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.constant import XGate
 from bqskit.ir.gates.parameterized import RXGate
 from bqskit.ir.opt.cost import HilbertSchmidtCostGenerator
diff --git a/tests/ir/opt/minimizers/test_ceres.py b/tests/ir/opt/minimizers/test_ceres.py
index 648c6087b..be29e80b1 100644
--- a/tests/ir/opt/minimizers/test_ceres.py
+++ b/tests/ir/opt/minimizers/test_ceres.py
@@ -2,7 +2,7 @@
 
 import numpy as np
 
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.constant import XGate
 from bqskit.ir.gates.parameterized import RXGate
 from bqskit.ir.opt.cost import HilbertSchmidtResidualsGenerator
diff --git a/tests/ir/opt/minimizers/test_scipy.py b/tests/ir/opt/minimizers/test_scipy.py
index d2872f126..0beac2d33 100644
--- a/tests/ir/opt/minimizers/test_scipy.py
+++ b/tests/ir/opt/minimizers/test_scipy.py
@@ -1,7 +1,7 @@
 """This module tests the ScipyMinimizer class."""
 from __future__ import annotations
 
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import RZGate
 from bqskit.ir.gates import SXGate
 from bqskit.ir.opt import HilbertSchmidtCostGenerator
diff --git a/tests/ir/test_iterator.py b/tests/ir/test_iterator.py
index bc12347ec..3f55565b8 100644
--- a/tests/ir/test_iterator.py
+++ b/tests/ir/test_iterator.py
@@ -5,8 +5,8 @@
 
 from hypothesis import given
 
-from bqskit.ir import Circuit
 from bqskit.ir import CircuitIterator
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CNOTGate
 from bqskit.ir.gates import HGate
 from bqskit.ir.operation import Operation
diff --git a/tests/ir/test_structure.py b/tests/ir/test_structure.py
index dd50ceabc..af0f910d1 100644
--- a/tests/ir/test_structure.py
+++ b/tests/ir/test_structure.py
@@ -1,7 +1,7 @@
 """This module tests the CircuitStructure class."""
 from __future__ import annotations
 
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CNOTGate
 from bqskit.ir.gates import U3Gate
 from bqskit.ir.structure import CircuitStructure
diff --git a/tests/passes/control/__init__.py b/tests/passes/control/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/control/predicates/__init__.py b/tests/passes/control/predicates/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/control/predicates/test_andpredicate.py b/tests/passes/control/predicates/test_andpredicate.py
new file mode 100644
index 000000000..96bedaf53
--- /dev/null
+++ b/tests/passes/control/predicates/test_andpredicate.py
@@ -0,0 +1,44 @@
+from __future__ import annotations
+
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.control.predicate import PassPredicate
+from bqskit.passes.control.predicates import AndPredicate
+
+
+class TruePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return True
+
+
+class FalsePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return False
+
+
+def test_true_and_true() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    pred = AndPredicate(TruePredicate(), TruePredicate())
+    assert pred.get_truth_value(circuit, data)
+
+
+def test_true_and_false() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    pred = AndPredicate(TruePredicate(), FalsePredicate())
+    assert not pred.get_truth_value(circuit, data)
+
+
+def test_false_and_true() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    pred = AndPredicate(FalsePredicate(), TruePredicate())
+    assert not pred.get_truth_value(circuit, data)
+
+
+def test_false_and_false() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    pred = AndPredicate(FalsePredicate(), FalsePredicate())
+    assert not pred.get_truth_value(circuit, data)
diff --git a/tests/passes/control/predicates/test_change.py b/tests/passes/control/predicates/test_change.py
new file mode 100644
index 000000000..dc2d939bc
--- /dev/null
+++ b/tests/passes/control/predicates/test_change.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CXGate
+from bqskit.ir.gates import HGate
+from bqskit.passes.control.predicates import ChangePredicate
+
+
+def test_change_single_gate() -> None:
+    pred = ChangePredicate()
+    circuit = Circuit(2)
+    data = PassData(circuit)
+
+    assert pred.get_truth_value(circuit, data)
+
+    for _ in range(3):
+        assert not pred.get_truth_value(circuit, data)
+
+    for _ in range(3):
+        circuit.append_gate(CXGate(), (0, 1))
+
+        assert pred.get_truth_value(circuit, data)
+        for _ in range(3):
+            assert not pred.get_truth_value(circuit, data)
+
+
+def test_change_two_gate() -> None:
+    pred = ChangePredicate()
+    circuit = Circuit(2)
+    data = PassData(circuit)
+
+    assert pred.get_truth_value(circuit, data)
+
+    for _ in range(3):
+        assert not pred.get_truth_value(circuit, data)
+
+    for _ in range(3):
+        circuit.append_gate(CXGate(), (0, 1))
+
+        assert pred.get_truth_value(circuit, data)
+        for _ in range(3):
+            assert not pred.get_truth_value(circuit, data)
+
+        circuit.append_gate(HGate(), 0)
+
+        assert pred.get_truth_value(circuit, data)
+        for _ in range(3):
+            assert not pred.get_truth_value(circuit, data)
+
+
+def test_change_two_gate_plus_minus() -> None:
+    pred = ChangePredicate()
+    circuit = Circuit(2)
+    data = PassData(circuit)
+
+    circuit.append_gate(HGate(), 0)
+    circuit.append_gate(CXGate(), (0, 1))
+
+    assert pred.get_truth_value(circuit, data)
+
+    circuit.remove(CXGate())
+    circuit.append_gate(HGate(), 0)
+
+    assert pred.get_truth_value(circuit, data)
diff --git a/tests/passes/control/predicates/test_count.py b/tests/passes/control/predicates/test_count.py
new file mode 100644
index 000000000..cb4ca0298
--- /dev/null
+++ b/tests/passes/control/predicates/test_count.py
@@ -0,0 +1,69 @@
+from __future__ import annotations
+
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CXGate
+from bqskit.ir.gates import HGate
+from bqskit.passes.control.predicates import GateCountPredicate
+
+
+def test_count_single_gate() -> None:
+    pred = GateCountPredicate(CXGate())
+    circuit = Circuit(2)
+    data = PassData(circuit)
+
+    assert pred.get_truth_value(circuit, data)
+
+    for _ in range(3):
+        assert not pred.get_truth_value(circuit, data)
+
+    for _ in range(3):
+        circuit.append_gate(CXGate(), (0, 1))
+
+        assert pred.get_truth_value(circuit, data)
+        for _ in range(3):
+            assert not pred.get_truth_value(circuit, data)
+
+
+def test_count_two_gate() -> None:
+    pred = GateCountPredicate((CXGate(), HGate()))
+    circuit = Circuit(2)
+    data = PassData(circuit)
+
+    assert pred.get_truth_value(circuit, data)
+
+    for _ in range(3):
+        assert not pred.get_truth_value(circuit, data)
+
+    for _ in range(3):
+        circuit.append_gate(CXGate(), (0, 1))
+
+        assert pred.get_truth_value(circuit, data)
+        for _ in range(3):
+            assert not pred.get_truth_value(circuit, data)
+
+        circuit.append_gate(HGate(), 0)
+
+        assert pred.get_truth_value(circuit, data)
+        for _ in range(3):
+            assert not pred.get_truth_value(circuit, data)
+
+
+def test_count_two_gate_plus_minus() -> None:
+    pred = GateCountPredicate((CXGate(), HGate()))
+    circuit = Circuit(2)
+    data = PassData(circuit)
+
+    circuit.append_gate(HGate(), 0)
+    circuit.append_gate(CXGate(), (0, 1))
+
+    assert pred.get_truth_value(circuit, data)
+
+    circuit.remove(CXGate())
+    circuit.append_gate(HGate(), 0)
+
+    assert not pred.get_truth_value(circuit, data)
+    # This is desired behavior
+    # If you want a true if either gate count has changed
+    # rather than use one GateCountPredicate, use an OrPredicate
+    # of two GateCountPredicates, or a ChangePredicate
diff --git a/tests/passes/control/predicates/test_multi.py b/tests/passes/control/predicates/test_multi.py
new file mode 100644
index 000000000..98dd21a01
--- /dev/null
+++ b/tests/passes/control/predicates/test_multi.py
@@ -0,0 +1,49 @@
+from __future__ import annotations
+
+import pytest
+
+from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates import CXGate
+from bqskit.ir.gates import CZGate
+from bqskit.ir.gates import XGate
+from bqskit.passes.control.predicates.multi import MultiPhysicalPredicate
+
+
+@pytest.mark.parametrize(
+    'gate_set', [
+        set(),
+        {CZGate()},
+        {CXGate()},
+        {CZGate(), CXGate()},
+    ],
+)
+def test_multi_physical(gate_set: set[Gate]) -> None:
+    pred = MultiPhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(CXGate(), (0, 1))
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set=gate_set)
+    assert pred.get_truth_value(circuit, data) == (CXGate() in gate_set)
+
+
+def test_multi_physical_sq_not_in() -> None:
+    pred = MultiPhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(XGate(), 0)
+    circuit.append_gate(CXGate(), (0, 1))
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set={CXGate()})
+    assert pred.get_truth_value(circuit, data)
+
+
+def test_multi_physical_mq_in() -> None:
+    pred = MultiPhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(XGate(), 0)
+    circuit.append_gate(CXGate(), (0, 1))
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set={XGate(), CXGate()})
+    assert pred.get_truth_value(circuit, data)
diff --git a/tests/passes/control/predicates/test_notpredicate.py b/tests/passes/control/predicates/test_notpredicate.py
new file mode 100644
index 000000000..0321da06c
--- /dev/null
+++ b/tests/passes/control/predicates/test_notpredicate.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.control.predicate import PassPredicate
+from bqskit.passes.control.predicates import NotPredicate
+
+
+class TruePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return True
+
+
+class FalsePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return False
+
+
+def test_not_true() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    assert not NotPredicate(TruePredicate()).get_truth_value(circuit, data)
+
+
+def test_not_false() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    assert NotPredicate(FalsePredicate()).get_truth_value(circuit, data)
diff --git a/tests/passes/control/predicates/test_orpredicate.py b/tests/passes/control/predicates/test_orpredicate.py
new file mode 100644
index 000000000..cfeecbf42
--- /dev/null
+++ b/tests/passes/control/predicates/test_orpredicate.py
@@ -0,0 +1,44 @@
+from __future__ import annotations
+
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.control.predicate import PassPredicate
+from bqskit.passes.control.predicates import OrPredicate
+
+
+class TruePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return True
+
+
+class FalsePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return False
+
+
+def test_true_and_true() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    pred = OrPredicate(TruePredicate(), TruePredicate())
+    assert pred.get_truth_value(circuit, data)
+
+
+def test_true_and_false() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    pred = OrPredicate(TruePredicate(), FalsePredicate())
+    assert pred.get_truth_value(circuit, data)
+
+
+def test_false_and_true() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    pred = OrPredicate(FalsePredicate(), TruePredicate())
+    assert pred.get_truth_value(circuit, data)
+
+
+def test_false_and_false() -> None:
+    circuit = Circuit(1)
+    data = PassData(circuit)
+    pred = OrPredicate(FalsePredicate(), FalsePredicate())
+    assert not pred.get_truth_value(circuit, data)
diff --git a/tests/passes/control/predicates/test_physical.py b/tests/passes/control/predicates/test_physical.py
new file mode 100644
index 000000000..31b61d4a9
--- /dev/null
+++ b/tests/passes/control/predicates/test_physical.py
@@ -0,0 +1,49 @@
+from __future__ import annotations
+
+import pytest
+
+from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates import CXGate
+from bqskit.ir.gates import HGate
+from bqskit.ir.gates import XGate
+from bqskit.passes.control.predicates import PhysicalPredicate
+
+
+@pytest.mark.parametrize(
+    'gate_set', [
+        set(),
+        {HGate()},
+        {XGate()},
+        {HGate(), XGate()},
+    ],
+)
+def test_physical(gate_set: set[Gate]) -> None:
+    pred = PhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(XGate(), 0)
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set=gate_set)
+    assert pred.get_truth_value(circuit, data) == (XGate() in gate_set)
+
+
+def test_physical_mq_not_in() -> None:
+    pred = PhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(XGate(), 0)
+    circuit.append_gate(CXGate(), (0, 1))
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set={XGate()})
+    assert not pred.get_truth_value(circuit, data)
+
+
+def test_physical_mq_in() -> None:
+    pred = PhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(XGate(), 0)
+    circuit.append_gate(CXGate(), (0, 1))
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set={XGate(), CXGate()})
+    assert pred.get_truth_value(circuit, data)
diff --git a/tests/passes/control/predicates/test_singlephy.py b/tests/passes/control/predicates/test_singlephy.py
new file mode 100644
index 000000000..efbf4169c
--- /dev/null
+++ b/tests/passes/control/predicates/test_singlephy.py
@@ -0,0 +1,49 @@
+from __future__ import annotations
+
+import pytest
+
+from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates import CXGate
+from bqskit.ir.gates import HGate
+from bqskit.ir.gates import XGate
+from bqskit.passes.control.predicates.single import SinglePhysicalPredicate
+
+
+@pytest.mark.parametrize(
+    'gate_set', [
+        set(),
+        {HGate()},
+        {XGate()},
+        {HGate(), XGate()},
+    ],
+)
+def test_single_physical(gate_set: set[Gate]) -> None:
+    pred = SinglePhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(XGate(), 0)
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set=gate_set)
+    assert pred.get_truth_value(circuit, data) == (XGate() in gate_set)
+
+
+def test_single_physical_mq_not_in() -> None:
+    pred = SinglePhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(XGate(), 0)
+    circuit.append_gate(CXGate(), (0, 1))
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set={XGate()})
+    assert pred.get_truth_value(circuit, data)
+
+
+def test_single_physical_mq_in() -> None:
+    pred = SinglePhysicalPredicate()
+    circuit = Circuit(2)
+    circuit.append_gate(XGate(), 0)
+    circuit.append_gate(CXGate(), (0, 1))
+    data = PassData(circuit)
+    data.model = MachineModel(2, gate_set={XGate(), CXGate()})
+    assert pred.get_truth_value(circuit, data)
diff --git a/tests/passes/control/predicates/test_width.py b/tests/passes/control/predicates/test_width.py
new file mode 100644
index 000000000..5816510e2
--- /dev/null
+++ b/tests/passes/control/predicates/test_width.py
@@ -0,0 +1,14 @@
+from __future__ import annotations
+
+from hypothesis import given
+from hypothesis.strategies import integers
+
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.control.predicates.width import WidthPredicate
+
+
+@given(integers(0, 32), integers(1, 32))
+def test_width_predicate(x: int, y: int) -> None:
+    pred = WidthPredicate(x)
+    assert pred.get_truth_value(Circuit(y), PassData(Circuit(y))) == (y < x)
diff --git a/tests/passes/control/test_dothendecide.py b/tests/passes/control/test_dothendecide.py
index 2ad5a40ba..19a71cd87 100644
--- a/tests/passes/control/test_dothendecide.py
+++ b/tests/passes/control/test_dothendecide.py
@@ -1,52 +1,44 @@
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates.constant.cx import CNOTGate
 from bqskit.passes.control import DoThenDecide
 
 
-def test_dothendecide_accept() -> None:
-    def accept_always(c1: Circuit, c2: Circuit) -> bool:
-        return True
+def accept_always(c1: Circuit, c2: Circuit) -> bool:
+    return True
 
-    class AddCNOTPass(BasePass):
-        def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-            circuit.append_gate(CNOTGate(), (0, 1))
 
-    circuit = Circuit(2)
-    dtd_pass = DoThenDecide(accept_always, AddCNOTPass())
-    dtd_pass.run(circuit)
-    assert circuit.num_operations == 1
-    assert circuit.gate_set == {CNOTGate()}
+def reject_always(c1: Circuit, c2: Circuit) -> bool:
+    return False
 
 
-def test_dothendecide_reject() -> None:
-    def reject_always(c1: Circuit, c2: Circuit) -> bool:
-        return False
+class AddCNOTPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        circuit.append_gate(CNOTGate(), (0, 1))
 
-    class AddCNOTPass(BasePass):
-        def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-            circuit.append_gate(CNOTGate(), (0, 1))
 
+def test_dothendecide_accept(compiler: Compiler) -> None:
     circuit = Circuit(2)
-    dtd_pass = DoThenDecide(reject_always, AddCNOTPass())
-    dtd_pass.run(circuit)
-    assert circuit.num_operations == 0
+    dtd_pass = DoThenDecide(accept_always, AddCNOTPass())
+    out_circuit = compiler.compile(circuit, dtd_pass)
+    assert out_circuit.num_operations == 1
+    assert out_circuit.gate_set == {CNOTGate()}
 
 
-def test_dothendecide_list() -> None:
-    def accept_always(c1: Circuit, c2: Circuit) -> bool:
-        return True
+def test_dothendecide_reject(compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    dtd_pass = DoThenDecide(reject_always, AddCNOTPass())
+    out_circuit = compiler.compile(circuit, dtd_pass)
+    assert out_circuit.num_operations == 0
 
-    class AddCNOTPass(BasePass):
-        def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-            circuit.append_gate(CNOTGate(), (0, 1))
 
+def test_dothendecide_list(compiler: Compiler) -> None:
     circuit = Circuit(2)
     dtd_pass = DoThenDecide(accept_always, [AddCNOTPass(), AddCNOTPass()])
-    dtd_pass.run(circuit)
-    assert circuit.num_operations == 2
-    assert circuit.gate_set == {CNOTGate()}
+    out_circuit = compiler.compile(circuit, dtd_pass)
+    assert out_circuit.num_operations == 2
+    assert out_circuit.gate_set == {CNOTGate()}
diff --git a/tests/passes/control/test_dowhileloop.py b/tests/passes/control/test_dowhileloop.py
index e69de29bb..67e5df36d 100644
--- a/tests/passes/control/test_dowhileloop.py
+++ b/tests/passes/control/test_dowhileloop.py
@@ -0,0 +1,70 @@
+from __future__ import annotations
+
+import pytest
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.control.dowhileloop import DoWhileLoopPass
+from bqskit.passes.control.predicate import PassPredicate
+
+
+class FalsePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return False
+
+
+class TrueX2Predicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        if '__true_x2_counter' not in data:
+            data['__true_x2_counter'] = 1
+            return True
+
+        data['__true_x2_counter'] += 1
+        return data['__true_x2_counter'] <= 2
+
+
+class TrueX3Predicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        if '__true_x3_counter' not in data:
+            data['__true_x3_counter'] = 1
+            return True
+
+        data['__true_x3_counter'] += 1
+        return data['__true_x3_counter'] <= 3
+
+
+class RunsOnce(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        if '_runs_once_flag' in data:
+            assert False, 'Should never have been executed.'
+
+        data['_runs_once_flag'] = True
+
+
+class RunsThrice(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        if '_runs_thrice_counter' not in data:
+            data['_runs_thrice_counter'] = 1
+            return
+
+        data['_runs_thrice_counter'] += 1
+        if data['_runs_thrice_counter'] > 3:
+            assert False, 'Should never have been executed.'
+
+
+def test_dowhile_runs_once_on_false(compiler: Compiler) -> None:
+    dwl_pass = DoWhileLoopPass(FalsePredicate(), RunsOnce())
+    compiler.compile(Circuit(1), [dwl_pass])
+
+
+def test_dowhile_runs_while_true(compiler: Compiler) -> None:
+    dwl_pass = DoWhileLoopPass(TrueX2Predicate(), RunsThrice())
+    compiler.compile(Circuit(1), [dwl_pass])
+
+
+def test_dowhile_runs_while_true_xfail(fresh_compiler: Compiler) -> None:
+    dwl_pass = DoWhileLoopPass(TrueX3Predicate(), RunsThrice())
+    with pytest.raises(RuntimeError):
+        fresh_compiler.compile(Circuit(1), [dwl_pass])
diff --git a/tests/passes/control/test_foreachblock.py b/tests/passes/control/test_foreachblock.py
new file mode 100644
index 000000000..605392b85
--- /dev/null
+++ b/tests/passes/control/test_foreachblock.py
@@ -0,0 +1,132 @@
+from __future__ import annotations
+
+import pytest
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CircuitGate
+from bqskit.ir.gates import HGate
+from bqskit.ir.gates import XGate
+from bqskit.ir.gates import YGate
+from bqskit.ir.gates import ZGate
+from bqskit.ir.operation import Operation
+from bqskit.passes import UnfoldPass
+from bqskit.passes.control.foreach import ForEachBlockPass
+
+
+@pytest.fixture
+def blk_circuit() -> Circuit:
+    circuit = Circuit(3)
+
+    for gate, loc in [(XGate(), (0, 1)), (YGate(), (1, 2)), (ZGate(), (0, 1))]:
+        block = Circuit(2)
+        block.append_gate(gate, 0)
+        block.append_gate(gate, 1)
+        block_gate = CircuitGate(block)
+
+        circuit.append_gate(block_gate, loc)
+
+    return circuit
+
+
+class RecordGateSetPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        data['gate_set'] = circuit.gate_set
+
+
+class ReplaceXwithHPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        if XGate() in circuit.gate_set:
+            circuit.pop()
+            circuit.pop()
+            circuit.append_gate(HGate(), 0)
+            circuit.append_gate(HGate(), 1)
+            circuit.append_gate(HGate(), 0)
+            circuit.append_gate(HGate(), 1)
+
+
+def never_replace(c: Circuit, o: Operation) -> bool:
+    return False
+
+
+class RemoveXGatePass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        if XGate() in circuit.gate_set:
+            circuit.pop()
+            circuit.pop()
+
+
+def only_x(o: Operation) -> bool:
+    return o.gate == XGate()
+
+
+class AssertXGatePass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        assert XGate() in circuit.gate_set
+        assert len(circuit.gate_set) == 1
+
+
+def empty_coll(_: Operation) -> bool:
+    return False
+
+
+def test_every_block_touched(blk_circuit: Circuit, compiler: Compiler) -> None:
+    feb_pass = ForEachBlockPass(RecordGateSetPass())
+    _, out_data = compiler.compile(blk_circuit, feb_pass, True)
+    feb_data = out_data[ForEachBlockPass.key]
+    assert len(feb_data) == 1
+    assert len(feb_data[0]) == 3
+    touched_gates = set()
+    for x in feb_data[0]:
+        assert len(x['gate_set']) == 1
+        touched_gates.update(x['gate_set'])
+    assert len(touched_gates) == 3
+    assert all(g in touched_gates for g in [XGate(), YGate(), ZGate()])
+
+
+def test_replacement_happens(blk_circuit: Circuit, compiler: Compiler) -> None:
+    feb_pass = ForEachBlockPass(ReplaceXwithHPass())
+    out_circuit = compiler.compile(blk_circuit, [feb_pass, UnfoldPass()])
+    assert len(out_circuit) == 8
+    assert len(out_circuit.gate_set) == 3
+    assert all(g in out_circuit.gate_set for g in [HGate(), YGate(), ZGate()])
+
+
+def test_replace_filter(blk_circuit: Circuit, compiler: Compiler) -> None:
+    feb_pass = ForEachBlockPass(
+        ReplaceXwithHPass(), replace_filter=never_replace,
+    )
+    out_circuit = compiler.compile(blk_circuit, [feb_pass, UnfoldPass()])
+    assert len(out_circuit) == 6
+    assert len(out_circuit.gate_set) == 3
+    assert all(g in out_circuit.gate_set for g in [XGate(), YGate(), ZGate()])
+
+
+def test_calculate_error(blk_circuit: Circuit, compiler: Compiler) -> None:
+    in_utry = blk_circuit.get_unitary()
+    feb_pass = ForEachBlockPass(RemoveXGatePass(), calculate_error_bound=True)
+    workflow = [feb_pass, UnfoldPass()]
+    out_circuit, data = compiler.compile(blk_circuit, workflow, True)
+    out_utry = out_circuit.get_unitary()
+    dist = out_utry.get_distance_from(in_utry)
+    assert data.error == dist
+
+
+def test_collection_filter(blk_circuit: Circuit, compiler: Compiler) -> None:
+    blk_circuit.unfold_all()
+    feb_pass = ForEachBlockPass(AssertXGatePass(), collection_filter=only_x)
+    compiler.compile(blk_circuit, feb_pass)
+
+
+def test_no_hang_on_empty_circuit(compiler: Compiler) -> None:
+    feb_pass = ForEachBlockPass(RemoveXGatePass(), collection_filter=empty_coll)
+    compiler.compile(Circuit(1), feb_pass)
+
+
+def test_no_hang_on_empty_collection(compiler: Compiler) -> None:
+    circuit = Circuit(1)
+    circuit.append_gate(XGate(), 0)
+    feb_pass = ForEachBlockPass(RemoveXGatePass(), collection_filter=empty_coll)
+    compiler.compile(circuit, feb_pass)
diff --git a/tests/passes/control/test_ifthenelse.py b/tests/passes/control/test_ifthenelse.py
new file mode 100644
index 000000000..caec18713
--- /dev/null
+++ b/tests/passes/control/test_ifthenelse.py
@@ -0,0 +1,47 @@
+from __future__ import annotations
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CXGate
+from bqskit.passes.control.ifthenelse import IfThenElsePass
+from bqskit.passes.control.predicate import PassPredicate
+
+
+class FalsePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return False
+
+
+class TruePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return True
+
+
+class AddCXGate(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        circuit.append_gate(CXGate(), (0, 1))
+
+
+class NeverRuns(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        assert False, 'Should never have been executed.'
+
+
+def test_ifthenelse_on_true(compiler: Compiler) -> None:
+    ite_pass = IfThenElsePass(TruePredicate(), AddCXGate())
+    out_circuit = compiler.compile(Circuit(2), ite_pass)
+    assert CXGate() in out_circuit.gate_set
+
+
+def test_ifthenelse_on_true_with_false(compiler: Compiler) -> None:
+    ite_pass = IfThenElsePass(TruePredicate(), AddCXGate(), NeverRuns())
+    out_circuit = compiler.compile(Circuit(2), ite_pass)
+    assert CXGate() in out_circuit.gate_set
+
+
+def test_ifthenelse_on_false(compiler: Compiler) -> None:
+    ite_pass = IfThenElsePass(FalsePredicate(), NeverRuns(), AddCXGate())
+    out_circuit = compiler.compile(Circuit(2), ite_pass)
+    assert CXGate() in out_circuit.gate_set
diff --git a/tests/passes/control/test_paralleldo.py b/tests/passes/control/test_paralleldo.py
new file mode 100644
index 000000000..2d69b5e70
--- /dev/null
+++ b/tests/passes/control/test_paralleldo.py
@@ -0,0 +1,72 @@
+from __future__ import annotations
+
+import time
+
+import pytest
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import XGate
+from bqskit.ir.gates import YGate
+from bqskit.ir.gates import ZGate
+from bqskit.passes.control.paralleldo import ParallelDo
+
+
+class AddXGate(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        circuit.append_gate(XGate(), 0)
+        data['key'] = 'x'
+
+
+class AddYGate(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        circuit.append_gate(YGate(), 0)
+        data['key'] = 'y'
+
+
+class AddZGate(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        circuit.append_gate(ZGate(), 0)
+        data['key'] = 'z'
+
+
+class Sleep1Pass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        time.sleep(0.1)
+        data['key'] = '1'
+
+
+class Sleep3Pass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        circuit.append_gate(ZGate(), 0)
+        time.sleep(0.3)
+        data['key'] = '3'
+
+
+def pick_z(c1: Circuit, c2: Circuit) -> bool:
+    if ZGate() in c1.gate_set:
+        return True
+    else:
+        return False
+
+
+def test_parallel_do_chooses_correct_result(compiler: Compiler) -> None:
+    pd_pass = ParallelDo([[AddXGate()], [AddYGate()], [AddZGate()]], pick_z)
+    out_circuit, data = compiler.compile(Circuit(1), pd_pass, True)
+    assert ZGate() in out_circuit.gate_set
+    assert len(out_circuit.gate_set) == 1
+    assert data['key'] == 'z'
+
+
+def test_parallel_do_no_passes() -> None:
+    with pytest.raises(ValueError):
+        ParallelDo([], pick_z)
+
+
+def test_parallel_do_pick_first(compiler: Compiler) -> None:
+    passes: list[list[BasePass]] = [[Sleep3Pass()], [Sleep1Pass()]]
+    pd_pass = ParallelDo(passes, pick_z, True)
+    _, data = compiler.compile(Circuit(1), pd_pass, True)
+    assert data['key'] == '1'
diff --git a/tests/passes/control/test_whileloop.py b/tests/passes/control/test_whileloop.py
new file mode 100644
index 000000000..cef893516
--- /dev/null
+++ b/tests/passes/control/test_whileloop.py
@@ -0,0 +1,49 @@
+from __future__ import annotations
+
+from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.passes.control.predicate import PassPredicate
+from bqskit.passes.control.whileloop import WhileLoopPass
+
+
+class FalsePredicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        return False
+
+
+class TrueX3Predicate(PassPredicate):
+    def get_truth_value(self, circuit: Circuit, data: PassData) -> bool:
+        if '__true_x3_counter' not in data:
+            data['__true_x3_counter'] = 1
+            return True
+
+        data['__true_x3_counter'] += 1
+        return data['__true_x3_counter'] <= 3
+
+
+class NeverRuns(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        assert False, 'Should never have been executed.'
+
+
+class RunsThrice(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        if '_runs_thrice_counter' not in data:
+            data['_runs_thrice_counter'] = 1
+            return
+
+        data['_runs_thrice_counter'] += 1
+        if data['_runs_thrice_counter'] > 3:
+            assert False, 'Should never have been executed.'
+
+
+def test_while_never_runs_on_false(compiler: Compiler) -> None:
+    wl_pass = WhileLoopPass(FalsePredicate(), NeverRuns())
+    compiler.compile(Circuit(1), wl_pass)
+
+
+def test_while_runs_while_true(compiler: Compiler) -> None:
+    wl_pass = WhileLoopPass(TrueX3Predicate(), RunsThrice())
+    compiler.compile(Circuit(1), wl_pass)
diff --git a/tests/passes/mapping/__init__.py b/tests/passes/mapping/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/mapping/test_apply.py b/tests/passes/mapping/test_apply.py
index d47f43f4d..b4c238122 100644
--- a/tests/passes/mapping/test_apply.py
+++ b/tests/passes/mapping/test_apply.py
@@ -1,20 +1,16 @@
 from __future__ import annotations
 
-from typing import Any
-
+from bqskit.compiler import Compiler
 from bqskit.compiler import MachineModel
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.passes import ApplyPlacement
 from bqskit.passes import GreedyPlacementPass
 from bqskit.passes import SetModelPass
 
 
-def test_apply_placement(r3_qubit_circuit: Circuit) -> None:
+def test_apply_placement(r3_qubit_circuit: Circuit, compiler: Compiler) -> None:
     model = MachineModel(6, [(0, 1), (1, 2), (2, 3), (2, 4), (2, 5), (3, 5)])
-    data: dict[str, Any] = {}
-    SetModelPass(model).run(r3_qubit_circuit, data)
-    GreedyPlacementPass().run(r3_qubit_circuit, data)
-    print(data)
-    ApplyPlacement().run(r3_qubit_circuit, data)
-    assert r3_qubit_circuit.num_qudits == 6
-    assert r3_qubit_circuit.active_qudits == [2, 3, 5]
+    workflow = [SetModelPass(model), GreedyPlacementPass(), ApplyPlacement()]
+    out_circuit = compiler.compile(r3_qubit_circuit, workflow)
+    assert out_circuit.num_qudits == 6
+    assert out_circuit.active_qudits == [2, 3, 5]
diff --git a/tests/passes/mapping/test_local_min.py b/tests/passes/mapping/test_local_min.py
index 85dbfdfc4..e071eb632 100644
--- a/tests/passes/mapping/test_local_min.py
+++ b/tests/passes/mapping/test_local_min.py
@@ -70,7 +70,7 @@ def test_escape_min_route(swaps: int, local_mins: int) -> None:
     circuit = looping_circuit(swaps, local_mins)
     cg = CouplingGraph.linear(circuit.num_qudits)
     data = {'machine_model': MachineModel(circuit.num_qudits, cg)}
-    GeneralizedSabreRoutingPass().run(circuit, data)
+    circuit.perform(GeneralizedSabreRoutingPass(), data)
     assert all(e in cg for e in circuit.coupling_graph)
 
 
@@ -82,4 +82,4 @@ def test_escape_min_layout(swaps: int, local_mins: int) -> None:
     circuit = looping_circuit(swaps, local_mins)
     cg = CouplingGraph.linear(circuit.num_qudits)
     data = {'machine_model': MachineModel(circuit.num_qudits, cg)}
-    GeneralizedSabreLayoutPass(3).run(circuit, data)
+    circuit.perform(GeneralizedSabreLayoutPass(3), data)
diff --git a/tests/passes/mapping/test_sabre.py b/tests/passes/mapping/test_sabre.py
index 769e19f0d..9c91c0047 100644
--- a/tests/passes/mapping/test_sabre.py
+++ b/tests/passes/mapping/test_sabre.py
@@ -1,9 +1,8 @@
 from __future__ import annotations
 
-from bqskit.compiler import CompilationTask
 from bqskit.compiler import Compiler
 from bqskit.compiler import MachineModel
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CNOTGate
 from bqskit.ir.gates import U3Gate
 from bqskit.passes import GeneralizedSabreLayoutPass
@@ -21,12 +20,12 @@ def test_simple(compiler: Compiler) -> None:
     for i in range(4):
         circuit.append_gate(CNOTGate(), (4, i))
         circuit.append_gate(
-            U3Gate(), 4, U3Gate.calc_params(
+            U3Gate(), 4, U3Gate().calc_params(
                 UnitaryMatrix.random(1),
             ),
         )
         circuit.append_gate(
-            U3Gate(), i, U3Gate.calc_params(
+            U3Gate(), i, U3Gate().calc_params(
                 UnitaryMatrix.random(1),
             ),
         )
@@ -34,19 +33,16 @@ def test_simple(compiler: Compiler) -> None:
 
     in_utry = circuit.get_unitary()
 
-    task = CompilationTask(
-        circuit,
-        [
-            SetModelPass(model),
-            GreedyPlacementPass(),
-            GeneralizedSabreLayoutPass(),
-            GeneralizedSabreRoutingPass(),
-        ],
-    )
+    workflow = [
+        SetModelPass(model),
+        GreedyPlacementPass(),
+        GeneralizedSabreLayoutPass(),
+        GeneralizedSabreRoutingPass(),
+    ]
 
-    cc = compiler.compile(task)
-    pi = compiler.analyze(task, 'initial_mapping')
-    pf = compiler.analyze(task, 'final_mapping')
+    cc, data = compiler.compile(circuit, workflow, True)
+    pi = data['initial_mapping']
+    pf = data['final_mapping']
     PI = PermutationMatrix.from_qubit_location(5, pi)
     PF = PermutationMatrix.from_qubit_location(5, pf)
     assert cc.get_unitary().get_distance_from(PF.T @ in_utry @ PI) < 1e-7
diff --git a/tests/passes/partitioning/__init__.py b/tests/passes/partitioning/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/partitioning/test_barrier.py b/tests/passes/partitioning/test_barrier.py
new file mode 100644
index 000000000..0e4967e5a
--- /dev/null
+++ b/tests/passes/partitioning/test_barrier.py
@@ -0,0 +1,52 @@
+from __future__ import annotations
+
+from bqskit import compile
+from bqskit.compiler.compiler import Compiler
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import BarrierPlaceholder
+from bqskit.ir.gates import CircuitGate
+from bqskit.ir.gates import CXGate
+from bqskit.ir.gates import U3Gate
+from bqskit.passes.partitioning.quick import QuickPartitioner
+
+
+def test_barrier_still_there_after_partitioning(compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(BarrierPlaceholder(2), (0, 1))
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit = compiler.compile(circuit, [QuickPartitioner(3)])
+    assert circuit.num_operations == 3
+    assert isinstance(circuit[0, 0].gate, CircuitGate)
+    assert isinstance(circuit[1, 0].gate, BarrierPlaceholder)
+    assert isinstance(circuit[2, 0].gate, CircuitGate)
+    circuit.unfold_all()
+    assert isinstance(circuit[0, 0].gate, CXGate)
+    assert isinstance(circuit[1, 0].gate, BarrierPlaceholder)
+    assert isinstance(circuit[2, 0].gate, CXGate)
+
+
+def test_barrier_stop_partitioning_across_some_circuit(
+        compiler: Compiler,
+) -> None:
+    circuit = Circuit(4)
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(BarrierPlaceholder(2), (0, 1))
+    circuit.append_gate(CXGate(), (0, 1))
+    circuit.append_gate(CXGate(), (2, 3))
+    circuit.append_gate(CXGate(), (2, 3))
+    circuit = compiler.compile(circuit, [QuickPartitioner(2)])
+    assert circuit.num_operations == 4
+
+
+def test_barrier_end_to_end_single_qubit_syn(compiler: Compiler) -> None:
+    circuit = Circuit(1)
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(BarrierPlaceholder(1), 0)
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(BarrierPlaceholder(1), 0)
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(BarrierPlaceholder(1), 0)
+    circuit.append_gate(U3Gate(), 0)
+    out = compile(circuit, compiler=compiler)
+    assert out.num_operations == 7
diff --git a/tests/passes/partitioning/test_cluster.py b/tests/passes/partitioning/test_cluster.py
index 33d0898c3..ad4c7f7a7 100644
--- a/tests/passes/partitioning/test_cluster.py
+++ b/tests/passes/partitioning/test_cluster.py
@@ -1,14 +1,20 @@
 from __future__ import annotations
 
+from bqskit.compiler.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CircuitGate
 from bqskit.passes.partitioning.cluster import ClusteringPartitioner
 
 
 class TestClusteringPartitioner:
-    def test_run_r6(self, r6_qudit_circuit: Circuit) -> None:
+    def test_run_r6(
+        self, r6_qudit_circuit: Circuit,
+        compiler: Compiler,
+    ) -> None:
         utry = r6_qudit_circuit.get_unitary()
-        ClusteringPartitioner(3, 2).run(r6_qudit_circuit, {})
+        r6_qudit_circuit = compiler.compile(
+            r6_qudit_circuit, [ClusteringPartitioner(3, 2)],
+        )
 
         assert any(
             isinstance(op.gate, CircuitGate)
diff --git a/tests/passes/partitioning/test_greedy.py b/tests/passes/partitioning/test_greedy.py
deleted file mode 100644
index b30d1bcfa..000000000
--- a/tests/passes/partitioning/test_greedy.py
+++ /dev/null
@@ -1,588 +0,0 @@
-from __future__ import annotations
-
-from bqskit.ir.circuit import Circuit
-from bqskit.ir.gates import CircuitGate
-from bqskit.ir.gates.constant.unitary import ConstantUnitaryGate
-from bqskit.passes.partitioning.greedy import GreedyPartitioner
-from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
-
-
-class TestGreedyPartitioner:
-    def test_run_r6(self, r6_qudit_circuit: Circuit) -> None:
-        utry = r6_qudit_circuit.get_unitary()
-        GreedyPartitioner(3).run(r6_qudit_circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in r6_qudit_circuit
-        )
-        assert r6_qudit_circuit.get_unitary() == utry
-        for cycle_index in range(r6_qudit_circuit.num_cycles):
-            assert not r6_qudit_circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_1(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                1, 4,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 0,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(4),
-            ), [
-                2, 0, 3, 5,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_2(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                3, 0,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 0,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(4),
-            ), [
-                4, 0, 1, 2,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                5, 0, 1,
-            ],
-        )
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_3(self) -> None:
-        circuit = Circuit(6)
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 1,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                5, 1, 3,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                4, 1,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                1, 3,
-            ],
-        )
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_4(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                4, 0,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                2, 0,
-            ],
-        )
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 3,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                1, 0,
-            ],
-        )
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_5(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                4, 1,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(4),
-            ), [
-                4, 1, 2, 3,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                4, 1,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 1,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                2, 1,
-            ],
-        )
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_6(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                1, 5,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                4, 0, 2,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 1,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(4),
-            ), [
-                0, 2, 3, 4,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                1, 5,
-            ],
-        )
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_7(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                0, 1,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                0, 1, 3,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 0,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                0, 1,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                4, 0, 1,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                1, 0, 3,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                4, 0,
-            ],
-        )
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_8(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 0,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                1, 0, 2,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                5, 0, 1,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                4, 0,
-            ],
-        )
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_9(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(3),
-            ), [
-                2, 1, 4,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                5, 1,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(4),
-            ), [
-                5, 1, 2, 4,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                4, 1,
-            ],
-        )
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
-
-    def test_corner_case_10(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                1, 2,
-            ],
-        )
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                4, 1,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(2),
-            ), [
-                3, 1,
-            ],
-        )
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(4),
-            ), [
-                1, 2, 3, 4,
-            ],
-        )
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-
-        utry = circuit.get_unitary()
-        GreedyPartitioner(3).run(circuit, {})
-
-        assert all(
-            isinstance(op.gate, CircuitGate)
-            or len(op.location) > 3
-            for op in circuit
-        )
-        assert circuit.get_unitary() == utry
-        for cycle_index in range(circuit.num_cycles):
-            assert not circuit._is_cycle_idle(cycle_index)
diff --git a/tests/passes/partitioning/test_parts.py b/tests/passes/partitioning/test_parts.py
index ba3e06db2..5d315f983 100644
--- a/tests/passes/partitioning/test_parts.py
+++ b/tests/passes/partitioning/test_parts.py
@@ -6,13 +6,20 @@
 import pytest
 
 from bqskit import Circuit
+from bqskit.compiler.compiler import Compiler
 from bqskit.ir.point import CircuitPoint
 from bqskit.passes import QuickPartitioner
 from bqskit.passes import UnfoldPass
 
 
+if os.path.isdir(os.path.join(os.path.dirname(__file__), '_data')):
+    params = os.listdir(os.path.join(os.path.dirname(__file__), '_data'))
+else:
+    params = []
+
+
 @pytest.fixture(
-    params=os.listdir(os.path.join(os.path.dirname(__file__), '_data')),
+    params=params,
     ids=lambda qasm_file: os.path.splitext(os.path.basename(qasm_file))[0],
 )
 def big_qasm_file(request: Any) -> str:
@@ -22,39 +29,39 @@ def big_qasm_file(request: Any) -> str:
     return os.path.join(path, request.param)
 
 
-def test_parters(big_qasm_file: str) -> None:
+def test_parters(big_qasm_file: str, compiler: Compiler) -> None:
     c = Circuit.from_file(big_qasm_file)
-    wc = c.copy()
-    QuickPartitioner(3).run(wc)
-    UnfoldPass().run(wc)
+    for block_size in [2, 3, 4, 5]:
+        workflow = [QuickPartitioner(block_size), UnfoldPass()]
+        wc = compiler.compile(c.copy(), workflow)
 
-    for q in range(c.num_qudits):
-        gate_list1 = list(c.operations_with_cycles(qudits_or_region=[q]))
-        gate_list2 = list(wc.operations_with_cycles(qudits_or_region=[q]))
+        for q in range(c.num_qudits):
+            gate_list1 = list(c.operations_with_cycles(qudits_or_region=[q]))
+            gate_list2 = list(wc.operations_with_cycles(qudits_or_region=[q]))
 
-        # Each qudit should have same number of gates on them
-        assert len(gate_list1) == len(gate_list2)
+            # Each qudit should have same number of gates on them
+            assert len(gate_list1) == len(gate_list2)
 
-        for g1, g2 in zip(gate_list1, gate_list2):
-            # Order of operations on one qudit should be same
-            assert g1[1] == g2[1]
+            for g1, g2 in zip(gate_list1, gate_list2):
+                # Order of operations on one qudit should be same
+                assert g1[1] == g2[1]
 
-            # Each operation should have the same previous ops
-            prevs1 = c.prev(CircuitPoint(g1[0], g1[1].location[0]))
-            prevs2 = wc.prev(CircuitPoint(g2[0], g2[1].location[0]))
+                # Each operation should have the same previous ops
+                prevs1 = c.prev(CircuitPoint(g1[0], g1[1].location[0]))
+                prevs2 = wc.prev(CircuitPoint(g2[0], g2[1].location[0]))
 
-            prev_ops1 = [c[prev] for prev in prevs1]
-            prev_ops2 = [c[prev] for prev in prevs2]
+                prev_ops1 = [c[prev] for prev in prevs1]
+                prev_ops2 = [c[prev] for prev in prevs2]
 
-            assert all(o in prev_ops2 for o in prev_ops1)
-            assert all(o in prev_ops1 for o in prev_ops2)
+                assert all(o in prev_ops2 for o in prev_ops1)
+                assert all(o in prev_ops1 for o in prev_ops2)
 
-            # Each operation should have the same next ops
-            nexts1 = c.next(CircuitPoint(g1[0], g1[1].location[0]))
-            nexts2 = wc.next(CircuitPoint(g2[0], g2[1].location[0]))
+                # Each operation should have the same next ops
+                nexts1 = c.next(CircuitPoint(g1[0], g1[1].location[0]))
+                nexts2 = wc.next(CircuitPoint(g2[0], g2[1].location[0]))
 
-            next_ops1 = [c[next] for next in nexts1]
-            next_ops2 = [c[next] for next in nexts2]
+                next_ops1 = [c[next] for next in nexts1]
+                next_ops2 = [c[next] for next in nexts2]
 
-            assert all(o in next_ops2 for o in next_ops1)
-            assert all(o in next_ops1 for o in next_ops2)
+                assert all(o in next_ops2 for o in next_ops1)
+                assert all(o in next_ops1 for o in next_ops2)
diff --git a/tests/passes/partitioning/test_quick.py b/tests/passes/partitioning/test_quick.py
index 473ed955c..303f466a7 100644
--- a/tests/passes/partitioning/test_quick.py
+++ b/tests/passes/partitioning/test_quick.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+from bqskit.compiler.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CircuitGate
 from bqskit.ir.gates.constant.cx import CNOTGate
@@ -9,9 +10,14 @@
 
 
 class TestQuickPartitioner:
-    def test_run_r6(self, r6_qudit_circuit: Circuit) -> None:
+    def test_run_r6(
+        self, r6_qudit_circuit: Circuit,
+        compiler: Compiler,
+    ) -> None:
         utry = r6_qudit_circuit.get_unitary()
-        QuickPartitioner(3).run(r6_qudit_circuit, {})
+        r6_qudit_circuit = compiler.compile(
+            r6_qudit_circuit, [QuickPartitioner(3)],
+        )
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -22,7 +28,7 @@ def test_run_r6(self, r6_qudit_circuit: Circuit) -> None:
         for cycle_index in range(r6_qudit_circuit.num_cycles):
             assert not r6_qudit_circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_1(self) -> None:
+    def test_corner_case_1(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -58,7 +64,7 @@ def test_corner_case_1(self) -> None:
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -69,7 +75,7 @@ def test_corner_case_1(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_2(self) -> None:
+    def test_corner_case_2(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
@@ -113,7 +119,7 @@ def test_corner_case_2(self) -> None:
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -124,7 +130,7 @@ def test_corner_case_2(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_3(self) -> None:
+    def test_corner_case_3(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
         circuit.append_gate(
@@ -165,7 +171,7 @@ def test_corner_case_3(self) -> None:
             ],
         )
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -176,7 +182,7 @@ def test_corner_case_3(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_4(self) -> None:
+    def test_corner_case_4(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -218,7 +224,7 @@ def test_corner_case_4(self) -> None:
         )
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -229,7 +235,7 @@ def test_corner_case_4(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_5(self) -> None:
+    def test_corner_case_5(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -280,7 +286,7 @@ def test_corner_case_5(self) -> None:
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -291,7 +297,7 @@ def test_corner_case_5(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_6(self) -> None:
+    def test_corner_case_6(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -340,7 +346,7 @@ def test_corner_case_6(self) -> None:
         )
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -351,7 +357,7 @@ def test_corner_case_6(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_7(self) -> None:
+    def test_corner_case_7(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -414,7 +420,7 @@ def test_corner_case_7(self) -> None:
         )
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -425,7 +431,7 @@ def test_corner_case_7(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_8(self) -> None:
+    def test_corner_case_8(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -469,7 +475,7 @@ def test_corner_case_8(self) -> None:
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -480,7 +486,7 @@ def test_corner_case_8(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_9(self) -> None:
+    def test_corner_case_9(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
@@ -522,7 +528,7 @@ def test_corner_case_9(self) -> None:
         )
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -533,7 +539,7 @@ def test_corner_case_9(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_10(self) -> None:
+    def test_corner_case_10(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
@@ -577,7 +583,7 @@ def test_corner_case_10(self) -> None:
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -588,7 +594,7 @@ def test_corner_case_10(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_11(self) -> None:
+    def test_corner_case_11(self, compiler: Compiler) -> None:
         circuit = Circuit(4)
 
         circuit.append_gate(CNOTGate(), (0, 1))
@@ -598,7 +604,7 @@ def test_corner_case_11(self) -> None:
         circuit.append_gate(CNOTGate(), (1, 2))
 
         utry = circuit.get_unitary()
-        QuickPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [QuickPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
diff --git a/tests/passes/partitioning/test_scan.py b/tests/passes/partitioning/test_scan.py
index 43cbe6eae..3e5927bc0 100644
--- a/tests/passes/partitioning/test_scan.py
+++ b/tests/passes/partitioning/test_scan.py
@@ -2,6 +2,7 @@
 
 import pytest
 
+from bqskit.compiler.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CircuitGate
 from bqskit.ir.gates import CNOTGate
@@ -13,7 +14,7 @@
 
 
 class TestScanPartitioner:
-    def test_run(self) -> None:
+    def test_run(self, compiler: Compiler) -> None:
         """Test run with a linear topology."""
         #     0  1  2  3  4        #########
         # 0 --o-----o--P--P--    --#-o---o-#-----#######--
@@ -33,7 +34,7 @@ def test_run(self) -> None:
         circ.append_gate(CNOTGate(), [1, 2])
         circ.append_gate(CNOTGate(), [2, 3])
         utry = circ.get_unitary()
-        ScanPartitioner(3).run(circ, {})
+        circ = compiler.compile(circ, [ScanPartitioner(3)])
 
         assert len(circ) == 3
         assert all(isinstance(op.gate, CircuitGate) for op in circ)
@@ -43,9 +44,14 @@ def test_run(self) -> None:
         for cycle_index in range(circ.num_cycles):
             assert not circ._is_cycle_idle(cycle_index)
 
-    def test_run_r6(self, r6_qudit_circuit: Circuit) -> None:
+    def test_run_r6(
+        self, r6_qudit_circuit: Circuit,
+        compiler: Compiler,
+    ) -> None:
         utry = r6_qudit_circuit.get_unitary()
-        ScanPartitioner(3).run(r6_qudit_circuit, {})
+        r6_qudit_circuit = compiler.compile(
+            r6_qudit_circuit, [ScanPartitioner(3)],
+        )
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -61,7 +67,7 @@ def test_run_r6(self, r6_qudit_circuit: Circuit) -> None:
         for cycle_index in range(r6_qudit_circuit.num_cycles):
             assert not r6_qudit_circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_3(self) -> None:
+    def test_corner_case_3(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
         circuit.append_gate(
@@ -102,7 +108,7 @@ def test_corner_case_3(self) -> None:
             ],
         )
         utry = circuit.get_unitary()
-        ScanPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [ScanPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -118,7 +124,7 @@ def test_corner_case_3(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_4(self) -> None:
+    def test_corner_case_4(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -160,7 +166,7 @@ def test_corner_case_4(self) -> None:
         )
 
         utry = circuit.get_unitary()
-        ScanPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [ScanPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -176,7 +182,7 @@ def test_corner_case_4(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_7(self) -> None:
+    def test_corner_case_7(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -239,7 +245,7 @@ def test_corner_case_7(self) -> None:
         )
 
         utry = circuit.get_unitary()
-        ScanPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [ScanPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -255,7 +261,7 @@ def test_corner_case_7(self) -> None:
         for cycle_index in range(circuit.num_cycles):
             assert not circuit._is_cycle_idle(cycle_index)
 
-    def test_corner_case_8(self) -> None:
+    def test_corner_case_8(self, compiler: Compiler) -> None:
         circuit = Circuit(6)
 
         circuit.append_gate(
@@ -299,7 +305,7 @@ def test_corner_case_8(self) -> None:
         circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
 
         utry = circuit.get_unitary()
-        ScanPartitioner(3).run(circuit, {})
+        circuit = compiler.compile(circuit, [ScanPartitioner(3)])
 
         assert all(
             isinstance(op.gate, CircuitGate)
@@ -316,21 +322,48 @@ def test_corner_case_8(self) -> None:
             assert not circuit._is_cycle_idle(cycle_index)
 
     def test_bigger_block(self) -> None:
-        circuit = Circuit(6)
-
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [0])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [1])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [2])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [3])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [4])
-        circuit.append_gate(ConstantUnitaryGate(UnitaryMatrix.random(1)), [5])
-
-        circuit.append_gate(
-            ConstantUnitaryGate(
-                UnitaryMatrix.random(4),
-            ), [
-                1, 2, 3, 4,
-            ],
-        )
-        with pytest.raises(RuntimeError):
-            ScanPartitioner(3).run(circuit)
+        # In this test we need to create its own compiler as its state is
+        # unstable at the end of the test
+        with Compiler() as compiler:
+            circuit = Circuit(6)
+
+            circuit.append_gate(
+                ConstantUnitaryGate(
+                    UnitaryMatrix.random(1),
+                ), [0],
+            )
+            circuit.append_gate(
+                ConstantUnitaryGate(
+                    UnitaryMatrix.random(1),
+                ), [1],
+            )
+            circuit.append_gate(
+                ConstantUnitaryGate(
+                    UnitaryMatrix.random(1),
+                ), [2],
+            )
+            circuit.append_gate(
+                ConstantUnitaryGate(
+                    UnitaryMatrix.random(1),
+                ), [3],
+            )
+            circuit.append_gate(
+                ConstantUnitaryGate(
+                    UnitaryMatrix.random(1),
+                ), [4],
+            )
+            circuit.append_gate(
+                ConstantUnitaryGate(
+                    UnitaryMatrix.random(1),
+                ), [5],
+            )
+
+            circuit.append_gate(
+                ConstantUnitaryGate(
+                    UnitaryMatrix.random(4),
+                ), [
+                    1, 2, 3, 4,
+                ],
+            )
+            with pytest.raises(RuntimeError):
+                circuit = compiler.compile(circuit, [ScanPartitioner(3)])
diff --git a/tests/passes/partitioning/test_single.py b/tests/passes/partitioning/test_single.py
index 9cf994d7a..ccd3f138b 100644
--- a/tests/passes/partitioning/test_single.py
+++ b/tests/passes/partitioning/test_single.py
@@ -1,13 +1,19 @@
 from __future__ import annotations
 
-from bqskit.ir import Circuit
+from bqskit.compiler.compiler import Compiler
 from bqskit.ir import Operation
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CircuitGate
 from bqskit.passes import GroupSingleQuditGatePass
 
 
-def test_single_qudit_grouper(r6_qudit_circuit: Circuit) -> None:
-    GroupSingleQuditGatePass().run(r6_qudit_circuit)
+def test_single_qudit_grouper(
+    r6_qudit_circuit: Circuit,
+    compiler: Compiler,
+) -> None:
+    r6_qudit_circuit = compiler.compile(
+        r6_qudit_circuit, [GroupSingleQuditGatePass()],
+    )
 
     # All single-qudit gates should be in a CircuitGate
     for gate in r6_qudit_circuit.gate_set:
diff --git a/tests/passes/processing/__init__.py b/tests/passes/processing/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/processing/test_substitute.py b/tests/passes/processing/test_substitute.py
index 0b1f6e2f7..034951375 100644
--- a/tests/passes/processing/test_substitute.py
+++ b/tests/passes/processing/test_substitute.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-from bqskit.compiler import CompilationTask
 from bqskit.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import VariableUnitaryGate
@@ -9,34 +8,20 @@
 from bqskit.qis import UnitaryMatrix
 
 
-class TestSubstitute:
-    def test_small_qubit(self) -> None:
-        utry = UnitaryMatrix.identity(4)
-        circuit = Circuit(2)
-        circuit.append_gate(VariableUnitaryGate(2), [0, 1])
-        circuit.instantiate(utry)
-        assert circuit.get_unitary().get_distance_from(utry) < 1e-5
+def is_variable(op: Operation) -> bool:
+    return isinstance(op.gate, VariableUnitaryGate)
 
-        def is_variable(op: Operation) -> bool:
-            return isinstance(op.gate, VariableUnitaryGate)
-        substitute = SubstitutePass(is_variable, VariableUnitaryGate(1))
-        substitute.run(circuit)
-        dist = circuit.get_unitary().get_distance_from(utry)
-        assert dist <= 1e-5
-        assert circuit.num_operations == 1
-        assert circuit[0, 0].num_qudits == 1
 
-    def test_small_qubit_with_compiler(self, compiler: Compiler) -> None:
+class TestSubstitute:
+    def test_small_qubit(self, compiler: Compiler) -> None:
         utry = UnitaryMatrix.identity(4)
         circuit = Circuit(2)
         circuit.append_gate(VariableUnitaryGate(2), [0, 1])
         circuit.instantiate(utry)
         assert circuit.get_unitary().get_distance_from(utry) < 1e-5
 
-        def is_variable(op: Operation) -> bool:
-            return isinstance(op.gate, VariableUnitaryGate)
         substitute = SubstitutePass(is_variable, VariableUnitaryGate(1))
-        circuit = compiler.compile(CompilationTask(circuit, [substitute]))
+        circuit = compiler.compile(circuit, [substitute])
         dist = circuit.get_unitary().get_distance_from(utry)
         assert dist <= 1e-5
         assert circuit.num_operations == 1
diff --git a/tests/passes/rules/__init__.py b/tests/passes/rules/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/rules/test_cnot2cz.py b/tests/passes/rules/test_cnot2cz.py
index 1a1b3943b..e6bd06f96 100644
--- a/tests/passes/rules/test_cnot2cz.py
+++ b/tests/passes/rules/test_cnot2cz.py
@@ -1,26 +1,27 @@
 from __future__ import annotations
 
-from bqskit.ir import Circuit
+from bqskit.compiler.compiler import Compiler
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CNOTGate
 from bqskit.ir.gates import CZGate
 from bqskit.ir.gates import U3Gate
 from bqskit.passes import CNOTToCZPass
 
 
-def test_cnot2cz_only_cnots() -> None:
+def test_cnot2cz_only_cnots(compiler: Compiler) -> None:
     circuit = Circuit(3)
     for i in range(100):
         circuit.append_gate(CNOTGate(), (0, 1))
         circuit.append_gate(CNOTGate(), (1, 2))
 
     utry = circuit.get_unitary()
-    CNOTToCZPass().run(circuit)
+    circuit = compiler.compile(circuit, [CNOTToCZPass()])
     assert CNOTGate() not in circuit.gate_set
     assert CZGate() in circuit.gate_set
     assert circuit.get_unitary().get_distance_from(utry) < 5e-8
 
 
-def test_cnot2cz_with_single_qubit() -> None:
+def test_cnot2cz_with_single_qubit(compiler: Compiler) -> None:
     circuit = Circuit(3)
     for i in range(100):
         circuit.append_gate(U3Gate(), 0)
@@ -35,7 +36,7 @@ def test_cnot2cz_with_single_qubit() -> None:
         circuit.append_gate(U3Gate(), 2)
 
     utry = circuit.get_unitary()
-    CNOTToCZPass().run(circuit)
+    circuit = compiler.compile(circuit, [CNOTToCZPass()])
     assert CNOTGate() not in circuit.gate_set
     assert CZGate() in circuit.gate_set
     assert circuit.get_unitary().get_distance_from(utry) < 5e-8
diff --git a/tests/passes/rules/test_zxzxz.py b/tests/passes/rules/test_zxzxz.py
index 51c767af8..c8567cb01 100644
--- a/tests/passes/rules/test_zxzxz.py
+++ b/tests/passes/rules/test_zxzxz.py
@@ -1,13 +1,14 @@
 from __future__ import annotations
 
-from bqskit.ir import Circuit
+from bqskit.compiler.compiler import Compiler
+from bqskit.ir.circuit import Circuit
 from bqskit.passes import ZXZXZDecomposition
 from bqskit.qis import UnitaryMatrix
 
 
-def test_zxzxz_decomposition() -> None:
-    for i in range(1000):
+def test_zxzxz_decomposition(compiler: Compiler) -> None:
+    for i in range(100):
         test_utry = UnitaryMatrix.random(1)
         test_circuit = Circuit.from_unitary(test_utry)
-        ZXZXZDecomposition().run(test_circuit)
-        assert test_circuit.get_unitary().get_distance_from(test_utry) < 5e-8
+        out_circuit = compiler.compile(test_circuit, ZXZXZDecomposition())
+        assert out_circuit.get_unitary().get_distance_from(test_utry) < 5e-8
diff --git a/tests/passes/search/__init__.py b/tests/passes/search/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/search/generators/__init__.py b/tests/passes/search/generators/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/search/generators/test_four_param.py b/tests/passes/search/generators/test_four_param.py
new file mode 100644
index 000000000..8444d3a2c
--- /dev/null
+++ b/tests/passes/search/generators/test_four_param.py
@@ -0,0 +1,83 @@
+from __future__ import annotations
+
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import CNOTGate
+from bqskit.ir.gates import RXGate
+from bqskit.ir.gates import RYGate
+from bqskit.ir.gates import RZGate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
+from bqskit.passes.search.generators import FourParamGenerator
+
+
+def test_fringe_cnot_count_empty_circuit() -> None:
+    gen = FourParamGenerator()
+    assert gen.count_outer_cnots(Circuit(1), (0, 1)) == 0
+
+
+def test_fringe_cnot_count_2q_circuit() -> None:
+    gen = FourParamGenerator()
+    circuit = Circuit(2)
+    circuit.append_gate(U3Gate(), 0)
+    circuit.append_gate(U3Gate(), 1)
+    for i in range(1, 10):
+        circuit.append_gate(CNOTGate(), (0, 1))
+        circuit.append_gate(RYGate(), 0)
+        circuit.append_gate(RZGate(), 0)
+        circuit.append_gate(RYGate(), 1)
+        circuit.append_gate(RXGate(), 1)
+        assert gen.count_outer_cnots(circuit, (0, 1)) == i
+
+
+def test_fringe_cnot_count_4q_circuit() -> None:
+    gen = FourParamGenerator()
+    circuit = Circuit(4)
+    for i in range(4):
+        circuit.append_gate(U3Gate(), i)
+
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(RYGate(), 0)
+    circuit.append_gate(RZGate(), 0)
+    circuit.append_gate(RYGate(), 1)
+    circuit.append_gate(RXGate(), 1)
+    circuit.append_gate(CNOTGate(), (2, 3))
+    circuit.append_gate(RYGate(), 2)
+    circuit.append_gate(RZGate(), 2)
+    circuit.append_gate(RYGate(), 3)
+    circuit.append_gate(RXGate(), 3)
+
+    for i in range(1, 10):
+        circuit.append_gate(CNOTGate(), (1, 2))
+        circuit.append_gate(RYGate(), 1)
+        circuit.append_gate(RZGate(), 1)
+        circuit.append_gate(RYGate(), 2)
+        circuit.append_gate(RXGate(), 2)
+        assert gen.count_outer_cnots(circuit, (1, 2)) == i
+
+
+def test_fringe_cnot_count_3q_hidden() -> None:
+    gen = FourParamGenerator()
+    circuit = Circuit(3)
+    for i in range(3):
+        circuit.append_gate(U3Gate(), i)
+
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(RYGate(), 0)
+    circuit.append_gate(RZGate(), 0)
+    circuit.append_gate(RYGate(), 1)
+    circuit.append_gate(RXGate(), 1)
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(RYGate(), 0)
+    circuit.append_gate(RZGate(), 0)
+    circuit.append_gate(RYGate(), 1)
+    circuit.append_gate(RXGate(), 1)
+    circuit.append_gate(CNOTGate(), (0, 1))
+    circuit.append_gate(RYGate(), 0)
+    circuit.append_gate(RZGate(), 0)
+    circuit.append_gate(RYGate(), 1)
+    circuit.append_gate(RXGate(), 1)
+    circuit.append_gate(CNOTGate(), (1, 2))
+    circuit.append_gate(RYGate(), 1)
+    circuit.append_gate(RZGate(), 1)
+    circuit.append_gate(RYGate(), 2)
+    circuit.append_gate(RXGate(), 2)
+    assert gen.count_outer_cnots(circuit, (0, 1)) == 0
diff --git a/tests/passes/search/generators/test_seed.py b/tests/passes/search/generators/test_seed.py
index 234a2257c..2167524d3 100644
--- a/tests/passes/search/generators/test_seed.py
+++ b/tests/passes/search/generators/test_seed.py
@@ -2,7 +2,7 @@
 
 from hypothesis import given
 
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import HGate
 from bqskit.passes.search.generators import SeedLayerGenerator
 from bqskit.utils.test.strategies import circuits
diff --git a/tests/passes/synthesis/__init__.py b/tests/passes/synthesis/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/synthesis/test_leap.py b/tests/passes/synthesis/test_leap.py
index a1e685b86..9d3f76473 100644
--- a/tests/passes/synthesis/test_leap.py
+++ b/tests/passes/synthesis/test_leap.py
@@ -1,37 +1,29 @@
 from __future__ import annotations
 
-from bqskit.compiler import CompilationTask
 from bqskit.compiler import Compiler
 from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates.constant.cpi import CPIGate
+from bqskit.ir.gates.parameterized.u8 import U8Gate
 from bqskit.passes import LEAPSynthesisPass
+from bqskit.passes.search.generators.simple import SimpleLayerGenerator
 from bqskit.qis import UnitaryMatrix
-# from bqskit.ir.gates import CPIGate
-# from bqskit.ir.gates import U8Gate
-# from bqskit.passes import SimpleLayerGenerator
 
 
 class TestLeap:
-    def test_small_qubit(self) -> None:
-        utry = UnitaryMatrix.random(2)
-        circuit = Circuit.from_unitary(utry)
-        leap = LEAPSynthesisPass()
-        leap.run(circuit)
-        dist = circuit.get_unitary().get_distance_from(utry)
-        assert dist <= 1e-5
 
-    def test_small_qubit_with_compiler(self, compiler: Compiler) -> None:
+    def test_small_qubit(self, compiler: Compiler) -> None:
         utry = UnitaryMatrix.random(2)
         circuit = Circuit.from_unitary(utry)
         leap = LEAPSynthesisPass()
-        circuit = compiler.compile(CompilationTask(circuit, [leap]))
+        circuit = compiler.compile(circuit, [leap])
         dist = circuit.get_unitary().get_distance_from(utry)
         assert dist <= 1e-5
 
-    # def test_small_qutrit(self) -> None:
-    #     utry = UnitaryMatrix.random(2, [3, 3])
-    #     circuit = Circuit.from_unitary(utry)
-    #     layer_gen = SimpleLayerGenerator(CPIGate(), U8Gate())
-    #     qsearch = QSearchSynthesisPass(layer_generator=layer_gen)
-    #     qsearch.run(circuit)
-    #     dist = circuit.get_unitary().get_distance_from(utry)
-    #     assert dist <= qsearch.success_threshold
+    def test_small_qutrit(self) -> None:
+        utry = UnitaryMatrix.random(2, [3, 3])
+        circuit = Circuit.from_unitary(utry)
+        layer_gen = SimpleLayerGenerator(CPIGate(), U8Gate())
+        leap = LEAPSynthesisPass(layer_generator=layer_gen)
+        circuit.perform(leap)
+        dist = circuit.get_unitary().get_distance_from(utry, 1)
+        assert dist <= leap.success_threshold
diff --git a/tests/passes/synthesis/test_qfast.py b/tests/passes/synthesis/test_qfast.py
index e32c80e8e..0ea0b500a 100644
--- a/tests/passes/synthesis/test_qfast.py
+++ b/tests/passes/synthesis/test_qfast.py
@@ -1,8 +1,5 @@
 from __future__ import annotations
 
-import pytest
-
-from bqskit.compiler import CompilationTask
 from bqskit.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CircuitGate
@@ -13,39 +10,22 @@
 
 
 class TestQFAST:
-    def test_small_qubit(self) -> None:
-        utry = UnitaryMatrix.random(2)
-        circuit = Circuit.from_unitary(utry)
-        qfast = QFASTDecompositionPass()
-        qfast.run(circuit)
-        dist = circuit.get_unitary().get_distance_from(utry)
-        assert dist <= 1e-5
-
-    def test_small_qubit_with_compiler(self, compiler: Compiler) -> None:
+    def test_small_qubit(self, compiler: Compiler) -> None:
         utry = UnitaryMatrix.random(2)
         circuit = Circuit.from_unitary(utry)
         qfast = QFASTDecompositionPass()
-        circuit = compiler.compile(CompilationTask(circuit, [qfast]))
-        dist = circuit.get_unitary().get_distance_from(utry)
-        assert dist <= 1e-5
-
-    def test_3_qubit(self) -> None:
-        utry = UnitaryMatrix.random(3)
-        circuit = Circuit.from_unitary(utry)
-        qfast = QFASTDecompositionPass()
-        qfast.run(circuit)
+        circuit = compiler.compile(circuit, [qfast])
         dist = circuit.get_unitary().get_distance_from(utry)
         assert dist <= 1e-5
 
-    def test_3_qubit_with_compiler(self, compiler: Compiler) -> None:
+    def test_3_qubit(self, compiler: Compiler) -> None:
         utry = UnitaryMatrix.random(3)
         circuit = Circuit.from_unitary(utry)
         qfast = QFASTDecompositionPass()
-        circuit = compiler.compile(CompilationTask(circuit, [qfast]))
+        circuit = compiler.compile(circuit, [qfast])
         dist = circuit.get_unitary().get_distance_from(utry)
         assert dist <= 1e-5
 
-    @pytest.mark.skip(reason='Issue in bqskitrs supporting CircuitGates.')
     def test_3_qubit_with_cnot_block(self) -> None:
         circuit = Circuit(2)
         circuit.append_gate(CNOTGate(), (0, 1))
@@ -56,6 +36,6 @@ def test_3_qubit_with_cnot_block(self) -> None:
         utry = UnitaryMatrix.random(3)
         circuit = Circuit.from_unitary(utry)
         qfast = QFASTDecompositionPass(cg)
-        qfast.run(circuit)
+        circuit.perform(qfast)
         dist = circuit.get_unitary().get_distance_from(utry)
         assert dist <= 1e-5
diff --git a/tests/passes/synthesis/test_qpredict.py b/tests/passes/synthesis/test_qpredict.py
index 3637001e1..1d08839b0 100644
--- a/tests/passes/synthesis/test_qpredict.py
+++ b/tests/passes/synthesis/test_qpredict.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-from bqskit.compiler import CompilationTask
 from bqskit.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.passes import QPredictDecompositionPass
@@ -8,34 +7,19 @@
 
 
 class TestQPredict:
-    def test_small_qubit(self) -> None:
-        utry = UnitaryMatrix.random(2)
-        circuit = Circuit.from_unitary(utry)
-        qpredict = QPredictDecompositionPass()
-        qpredict.run(circuit)
-        dist = circuit.get_unitary().get_distance_from(utry)
-        assert dist <= 1e-3
 
-    def test_small_qubit_with_compiler(self, compiler: Compiler) -> None:
+    def test_small_qubit(self, compiler: Compiler) -> None:
         utry = UnitaryMatrix.random(2)
         circuit = Circuit.from_unitary(utry)
         qpredict = QPredictDecompositionPass()
-        circuit = compiler.compile(CompilationTask(circuit, [qpredict]))
-        dist = circuit.get_unitary().get_distance_from(utry)
-        assert dist <= 1e-3
-
-    def test_3_qubit(self) -> None:
-        utry = UnitaryMatrix.random(3)
-        circuit = Circuit.from_unitary(utry)
-        qpredict = QPredictDecompositionPass()
-        qpredict.run(circuit)
+        circuit = compiler.compile(circuit, [qpredict])
         dist = circuit.get_unitary().get_distance_from(utry)
         assert dist <= 1e-3
 
-    def test_3_qubit_with_compiler(self, compiler: Compiler) -> None:
+    def test_3_qubit(self, compiler: Compiler) -> None:
         utry = UnitaryMatrix.random(3)
         circuit = Circuit.from_unitary(utry)
         qpredict = QPredictDecompositionPass()
-        circuit = compiler.compile(CompilationTask(circuit, [qpredict]))
+        circuit = compiler.compile(circuit, [qpredict])
         dist = circuit.get_unitary().get_distance_from(utry)
         assert dist <= 1e-3
diff --git a/tests/passes/synthesis/test_qsearch.py b/tests/passes/synthesis/test_qsearch.py
index deed27f4c..fdc6ce353 100644
--- a/tests/passes/synthesis/test_qsearch.py
+++ b/tests/passes/synthesis/test_qsearch.py
@@ -1,9 +1,11 @@
 from __future__ import annotations
 
-from bqskit.compiler import CompilationTask
 from bqskit.compiler import Compiler
 from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates.constant.cpi import CPIGate
+from bqskit.ir.gates.parameterized.u8 import U8Gate
 from bqskit.passes import QSearchSynthesisPass
+from bqskit.passes.search.generators.simple import SimpleLayerGenerator
 from bqskit.qis import UnitaryMatrix
 # from bqskit.ir.gates import CPIGate
 # from bqskit.ir.gates import U8Gate
@@ -11,27 +13,20 @@
 
 
 class TestQSearch:
-    def test_small_qubit(self) -> None:
-        utry = UnitaryMatrix.random(2)
-        circuit = Circuit.from_unitary(utry)
-        qsearch = QSearchSynthesisPass()
-        qsearch.run(circuit)
-        dist = circuit.get_unitary().get_distance_from(utry)
-        assert dist <= 1e-5
 
-    def test_small_qubit_with_compiler(self, compiler: Compiler) -> None:
+    def test_small_qubit(self, compiler: Compiler) -> None:
         utry = UnitaryMatrix.random(2)
         circuit = Circuit.from_unitary(utry)
         qsearch = QSearchSynthesisPass()
-        circuit = compiler.compile(CompilationTask(circuit, [qsearch]))
-        dist = circuit.get_unitary().get_distance_from(utry)
+        circuit = compiler.compile(circuit, [qsearch])
+        dist = circuit.get_unitary().get_distance_from(utry, 1)
         assert dist <= 1e-5
 
-    # def test_small_qutrit(self) -> None:
-    #     utry = UnitaryMatrix.random(2, [3, 3])
-    #     circuit = Circuit.from_unitary(utry)
-    #     layer_gen = SimpleLayerGenerator(CPIGate(), U8Gate())
-    #     qsearch = QSearchSynthesisPass(layer_generator=layer_gen)
-    #     qsearch.run(circuit)
-    #     dist = circuit.get_unitary().get_distance_from(utry)
-    #     assert dist <= qsearch.success_threshold
+    def test_small_qutrit(self) -> None:
+        utry = UnitaryMatrix.random(2, [3, 3])
+        circuit = Circuit.from_unitary(utry)
+        layer_gen = SimpleLayerGenerator(CPIGate(), U8Gate())
+        leap = QSearchSynthesisPass(layer_generator=layer_gen)
+        circuit.perform(leap)
+        dist = circuit.get_unitary().get_distance_from(utry, 1)
+        assert dist <= leap.success_threshold
diff --git a/tests/passes/util/__init__.py b/tests/passes/util/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/util/test_extend.py b/tests/passes/util/test_extend.py
index 24f07e17f..2eb7da052 100644
--- a/tests/passes/util/test_extend.py
+++ b/tests/passes/util/test_extend.py
@@ -1,25 +1,30 @@
 from __future__ import annotations
 
-from bqskit.ir import Circuit
+from bqskit.compiler.compiler import Compiler
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CircuitGate
 from bqskit.passes import ExtendBlockSizePass
 from bqskit.passes import QuickPartitioner
 
 
-def test_extend_3(r6_qudit_circuit: Circuit) -> None:
+def test_extend_3(r6_qudit_circuit: Circuit, compiler: Compiler) -> None:
     in_utry = r6_qudit_circuit.get_unitary()
-    QuickPartitioner(3).run(r6_qudit_circuit)
-    ExtendBlockSizePass(3).run(r6_qudit_circuit)
+    r6_qudit_circuit = compiler.compile(r6_qudit_circuit, [QuickPartitioner(3)])
+    r6_qudit_circuit = compiler.compile(
+        r6_qudit_circuit, [ExtendBlockSizePass(3)],
+    )
     out_utry = r6_qudit_circuit.get_unitary()
     assert all(isinstance(op.gate, CircuitGate) for op in r6_qudit_circuit)
     assert all(op.num_qudits == 3 for op in r6_qudit_circuit)
     assert in_utry == out_utry
 
 
-def test_extend_4(r6_qudit_circuit: Circuit) -> None:
+def test_extend_4(r6_qudit_circuit: Circuit, compiler: Compiler) -> None:
     in_utry = r6_qudit_circuit.get_unitary()
-    QuickPartitioner(3).run(r6_qudit_circuit)
-    ExtendBlockSizePass(4).run(r6_qudit_circuit)
+    r6_qudit_circuit = compiler.compile(r6_qudit_circuit, [QuickPartitioner(3)])
+    r6_qudit_circuit = compiler.compile(
+        r6_qudit_circuit, [ExtendBlockSizePass(4)],
+    )
     out_utry = r6_qudit_circuit.get_unitary()
     assert all(isinstance(op.gate, CircuitGate) for op in r6_qudit_circuit)
     assert all(op.num_qudits == 4 for op in r6_qudit_circuit)
diff --git a/tests/passes/util/test_random.py b/tests/passes/util/test_random.py
index 8069ac6ee..1c47f730a 100644
--- a/tests/passes/util/test_random.py
+++ b/tests/passes/util/test_random.py
@@ -1,88 +1,33 @@
 from __future__ import annotations
 
-from typing import Any
-
-import pytest
-
 from bqskit.compiler import Compiler
-from bqskit.compiler.task import CompilationTask
-from bqskit.ir import Circuit
+from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CCXGate
 from bqskit.passes import QSearchSynthesisPass
 from bqskit.passes import SetRandomSeedPass
 from bqskit.qis import UnitaryMatrix
 
 
-def test_two_qubit_syn_with_seed_no_dask() -> None:
+def test_two_qubit_syn_with_seed(compiler: Compiler) -> None:
     in_utry = UnitaryMatrix.random(2)
 
     circ1 = Circuit.from_unitary(in_utry)
-    data: dict[str, Any] = {}
-    SetRandomSeedPass(0).run(circ1, data)
-    QSearchSynthesisPass().run(circ1, data)
-
     circ2 = Circuit.from_unitary(in_utry)
-    data: dict[str, Any] = {}
-    SetRandomSeedPass(0).run(circ2, data)
-    QSearchSynthesisPass().run(circ2, data)
+    workflow = [SetRandomSeedPass(0), QSearchSynthesisPass()]
+    circ1 = compiler.compile(circ1, workflow)
+    circ2 = compiler.compile(circ2, workflow)
 
     for op1, op2 in zip(circ1, circ2):
         assert op1 == op2
 
 
-def test_three_qubit_syn_with_seed_no_dask() -> None:
+def test_three_qubit_syn_with_seed(compiler: Compiler) -> None:
     in_utry = CCXGate().get_unitary()
-
     circ1 = Circuit.from_unitary(in_utry)
-    data: dict[str, Any] = {}
-    SetRandomSeedPass(0).run(circ1, data)
-    QSearchSynthesisPass().run(circ1, data)
-
     circ2 = Circuit.from_unitary(in_utry)
-    data: dict[str, Any] = {}
-    SetRandomSeedPass(0).run(circ2, data)
-    QSearchSynthesisPass().run(circ2, data)
-
-    for op1, op2 in zip(circ1, circ2):
-        assert op1 == op2
-
-
-def test_two_qubit_syn_with_seed_and_dask(compiler: Compiler) -> None:
-    in_utry = UnitaryMatrix.random(2)
-
-    circ1 = Circuit.from_unitary(in_utry)
-    task1 = CompilationTask(
-        circ1, [SetRandomSeedPass(0), QSearchSynthesisPass()],
-    )
-
-    circ2 = Circuit.from_unitary(in_utry)
-    task2 = CompilationTask(
-        circ2, [SetRandomSeedPass(0), QSearchSynthesisPass()],
-    )
-
-    circ1 = compiler.compile(task1)
-    circ2 = compiler.compile(task2)
-
-    for op1, op2 in zip(circ1, circ2):
-        assert op1 == op2
-
-
-@pytest.mark.xfail(reason='Bug #107')
-def test_three_qubit_syn_with_seed_and_dask(compiler: Compiler) -> None:
-    in_utry = CCXGate().get_unitary()
-
-    circ1 = Circuit.from_unitary(in_utry)
-    task1 = CompilationTask(
-        circ1, [SetRandomSeedPass(0), QSearchSynthesisPass()],
-    )
-
-    circ2 = Circuit.from_unitary(in_utry)
-    task2 = CompilationTask(
-        circ2, [SetRandomSeedPass(0), QSearchSynthesisPass()],
-    )
-
-    circ1 = compiler.compile(task1)
-    circ2 = compiler.compile(task2)
+    workflow = [SetRandomSeedPass(0), QSearchSynthesisPass()]
+    circ1 = compiler.compile(circ1, workflow)
+    circ2 = compiler.compile(circ2, workflow)
 
     for op1, op2 in zip(circ1, circ2):
         assert op1 == op2
diff --git a/tests/passes/util/test_to_u3.py b/tests/passes/util/test_to_u3.py
new file mode 100644
index 000000000..8010b2b2d
--- /dev/null
+++ b/tests/passes/util/test_to_u3.py
@@ -0,0 +1,105 @@
+from __future__ import annotations
+
+from typing import Callable
+from typing import Sequence
+
+import numpy as np
+import numpy.typing as npt
+
+from bqskit.compiler import Compiler
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates.generalgate import GeneralGate
+from bqskit.ir.gates.parameterized.u3 import U3Gate
+from bqskit.passes.util.converttou3 import ToU3Pass
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+
+
+def test_single_qubit_general_gate_conversion(
+        compiler: Compiler,
+        single_qubit_general_gate: GeneralGate,
+        gen_random_utry_np:
+        Callable[[int | Sequence[int]], npt.NDArray[np.complex128]],
+) -> None:
+
+    circuit = Circuit(1)
+    gate_utry = UnitaryMatrix(gen_random_utry_np(2))
+    circuit.append_gate(
+        single_qubit_general_gate, (0,),
+        single_qubit_general_gate.calc_params(gate_utry),
+    )
+
+    circuit = compiler.compile(circuit, [ToU3Pass()])
+    dist = circuit.get_unitary().get_distance_from(gate_utry)
+    assert dist <= 1e-7
+    assert isinstance(circuit[0][0].gate, U3Gate)
+
+
+def test_skiping_non_general_gate(
+        compiler: Compiler,
+        single_qubit_gate: Gate,
+) -> None:
+    circuit = Circuit(1)
+    params = np.random.random(single_qubit_gate.num_params)
+
+    circuit.append_gate(single_qubit_gate, (0,), params)
+
+    circuit = compiler.compile(
+        circuit, [ToU3Pass(convert_all_single_qubit_gates=False)],
+    )
+    dist = circuit.get_unitary().get_distance_from(
+        single_qubit_gate.get_unitary(params),
+    )
+
+    assert dist <= 1e-7
+    if isinstance(single_qubit_gate, GeneralGate):
+        assert isinstance(circuit[0][0].gate, U3Gate)
+    else:
+        assert isinstance(circuit[0][0].gate, type(single_qubit_gate))
+
+
+def test_all_single_qubit_gate_conversion(
+        compiler: Compiler,
+        single_qubit_gate: Gate,
+) -> None:
+
+    circuit = Circuit(1)
+    params = np.random.random(single_qubit_gate.num_params)
+
+    circuit.append_gate(single_qubit_gate, (0,), params)
+
+    circuit = compiler.compile(
+        circuit, [ToU3Pass(convert_all_single_qubit_gates=True)],
+    )
+
+    dist = circuit.get_unitary().get_distance_from(
+        single_qubit_gate.get_unitary(params),
+    )
+
+    assert dist <= 1e-7
+    assert isinstance(circuit[0][0].gate, U3Gate)
+
+
+def test_all_gates_conversion(
+        compiler: Compiler,
+        gate: Gate,
+) -> None:
+
+    circuit = Circuit(gate.num_qudits, gate.radixes)
+    params = np.random.random(gate.num_params)
+
+    circuit.append_gate(gate, range(gate.num_qudits), params)
+
+    circuit = compiler.compile(
+        circuit, [ToU3Pass(convert_all_single_qubit_gates=True)],
+    )
+
+    dist = circuit.get_unitary().get_distance_from(
+        gate.get_unitary(params),
+    )
+
+    assert dist <= 1e-7
+    if gate.num_qudits == 1 and not gate.is_qutrit_only():
+        assert isinstance(circuit[0][0].gate, U3Gate)
+    else:
+        assert isinstance(circuit[0][0].gate, type(gate))
diff --git a/tests/passes/util/test_to_varutry.py b/tests/passes/util/test_to_varutry.py
new file mode 100644
index 000000000..65bf8065a
--- /dev/null
+++ b/tests/passes/util/test_to_varutry.py
@@ -0,0 +1,147 @@
+from __future__ import annotations
+
+from typing import Callable
+from typing import Sequence
+
+import numpy as np
+import numpy.typing as npt
+
+from bqskit.compiler import Compiler
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gate import Gate
+from bqskit.ir.gates.generalgate import GeneralGate
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
+from bqskit.passes.util.converttovar import ToVariablePass
+from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
+
+
+def test_single_qubit_general_gate_conversion(
+        compiler: Compiler,
+        single_qubit_general_gate: GeneralGate,
+        gen_random_utry_np:
+        Callable[[int | Sequence[int]], npt.NDArray[np.complex128]],
+) -> None:
+
+    circuit = Circuit(1)
+    gate_utry = UnitaryMatrix(gen_random_utry_np(2))
+    circuit.append_gate(
+        single_qubit_general_gate, (0,),
+        single_qubit_general_gate.calc_params(gate_utry),
+    )
+
+    circuit = compiler.compile(circuit, [ToVariablePass()])
+    dist = circuit.get_unitary().get_distance_from(gate_utry)
+    assert dist <= 1e-7
+    assert isinstance(circuit[0][0].gate, VariableUnitaryGate)
+
+
+def test_skiping_non_general_gate(
+        compiler: Compiler,
+        single_qubit_gate: Gate,
+) -> None:
+    circuit = Circuit(1)
+    params = np.random.random(single_qubit_gate.num_params)
+
+    circuit.append_gate(single_qubit_gate, (0,), params)
+
+    circuit = compiler.compile(
+        circuit, [ToVariablePass(convert_all_single_qudit_gates=False)],
+    )
+    dist = circuit.get_unitary().get_distance_from(
+        single_qubit_gate.get_unitary(params),
+    )
+
+    assert dist <= 1e-7
+    if isinstance(single_qubit_gate, GeneralGate):
+        assert isinstance(circuit[0][0].gate, VariableUnitaryGate)
+    else:
+        assert isinstance(circuit[0][0].gate, type(single_qubit_gate))
+
+
+def test_all_single_qubit_gate_conversion(
+        compiler: Compiler,
+        single_qubit_gate: Gate,
+) -> None:
+
+    circuit = Circuit(1)
+    params = np.random.random(single_qubit_gate.num_params)
+
+    circuit.append_gate(single_qubit_gate, (0,), params)
+
+    circuit = compiler.compile(
+        circuit, [ToVariablePass(convert_all_single_qudit_gates=True)],
+    )
+
+    dist = circuit.get_unitary().get_distance_from(
+        single_qubit_gate.get_unitary(params),
+    )
+
+    assert dist <= 1e-7
+    assert isinstance(circuit[0][0].gate, VariableUnitaryGate)
+
+
+def test_single_qutrit_general_gate_conversion(
+        compiler: Compiler,
+        single_qutrit_general_gate: GeneralGate,
+        gen_random_utry_np:
+        Callable[[int | Sequence[int]], npt.NDArray[np.complex128]],
+) -> None:
+
+    circuit = Circuit(1, [3])
+    gate_utry = UnitaryMatrix(gen_random_utry_np(3))
+    circuit.append_gate(
+        single_qutrit_general_gate, (0,),
+        single_qutrit_general_gate.calc_params(gate_utry),
+    )
+
+    circuit = compiler.compile(circuit, [ToVariablePass()])
+    dist = circuit.get_unitary().get_distance_from(gate_utry)
+    assert dist <= 1e-7
+    assert isinstance(circuit[0][0].gate, VariableUnitaryGate)
+
+
+def test_all_single_qutrit_gate_conversion(
+        compiler: Compiler,
+        single_qutrit_gate: Gate,
+) -> None:
+
+    circuit = Circuit(1, [3])
+    params = np.random.random(single_qutrit_gate.num_params)
+
+    circuit.append_gate(single_qutrit_gate, (0,), params)
+
+    circuit = compiler.compile(
+        circuit, [ToVariablePass(convert_all_single_qudit_gates=True)],
+    )
+
+    dist = circuit.get_unitary().get_distance_from(
+        single_qutrit_gate.get_unitary(params),
+    )
+
+    assert dist <= 1e-7
+    assert isinstance(circuit[0][0].gate, VariableUnitaryGate)
+
+
+def test_all_gates_conversion(
+        compiler: Compiler,
+        gate: Gate,
+) -> None:
+
+    circuit = Circuit(gate.num_qudits, gate.radixes)
+    params = np.random.random(gate.num_params)
+
+    circuit.append_gate(gate, range(gate.num_qudits), params)
+
+    circuit = compiler.compile(
+        circuit, [ToVariablePass(convert_all_single_qudit_gates=True)],
+    )
+
+    dist = circuit.get_unitary().get_distance_from(
+        gate.get_unitary(params),
+    )
+
+    assert dist <= 1e-7
+    if gate.num_qudits == 1:
+        assert isinstance(circuit[0][0].gate, VariableUnitaryGate)
+    else:
+        assert isinstance(circuit[0][0].gate, type(gate))
diff --git a/tests/qis/__init__.py b/tests/qis/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/runtime/__init__.py b/tests/runtime/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/runtime/conftest.py b/tests/runtime/conftest.py
new file mode 100644
index 000000000..f3a5a77f2
--- /dev/null
+++ b/tests/runtime/conftest.py
@@ -0,0 +1,35 @@
+from __future__ import annotations
+
+import signal
+import subprocess
+import sys
+from typing import Any
+from typing import Iterator
+
+import pytest
+
+from bqskit.compiler import Compiler
+
+params = ['attached', 'detached'] if sys.platform != 'win32' else ['attached']
+
+
+@pytest.fixture(params=params)
+def server_compiler(request: Any) -> Iterator[Compiler]:
+    if request.param == 'detached':
+        manager = subprocess.Popen(['bqskit-manager', '-n2', '-i'])
+        server = subprocess.Popen(['bqskit-server', 'localhost', '-i'])
+        compiler = Compiler('localhost')
+    else:
+        compiler = Compiler(num_workers=2, runtime_log_level=1)
+
+    yield compiler
+
+    if request.param == 'detached':
+        compiler.close()
+        server.send_signal(signal.SIGINT)
+        server.wait()
+        manager.wait()
+        manager.kill()
+        server.kill()
+    else:
+        compiler.close()
diff --git a/tests/runtime/test_attached.py b/tests/runtime/test_attached.py
new file mode 100644
index 000000000..0c2ecb67e
--- /dev/null
+++ b/tests/runtime/test_attached.py
@@ -0,0 +1,173 @@
+"""Checks the attached runtime's process management and ability to cleanup."""
+from __future__ import annotations
+
+import os
+import signal
+import subprocess
+import sys
+import time
+
+import psutil
+import pytest
+
+from bqskit.compiler import BasePass
+from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.runtime import get_runtime
+
+
+@pytest.mark.parametrize('num_workers', [1, -1])
+def test_startup_shutdown_transparently(num_workers: int) -> None:
+    in_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    compiler = Compiler(num_workers=num_workers)
+    assert compiler.p is not None
+    compiler.__del__()
+    if sys.platform == 'win32':
+        time.sleep(1)
+    out_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    assert in_num_childs == out_num_childs
+
+
+@pytest.mark.parametrize('num_workers', [1, -1])
+def test_cleanup_close(num_workers: int) -> None:
+    in_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    compiler = Compiler(num_workers=num_workers)
+    compiler.close()
+    if sys.platform == 'win32':
+        time.sleep(1)
+    out_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    assert in_num_childs == out_num_childs
+
+
+@pytest.mark.parametrize('num_workers', [1, -1])
+def test_cleanup_with_clause(num_workers: int) -> None:
+    in_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    with Compiler(num_workers=num_workers) as _:
+        pass
+    if sys.platform == 'win32':
+        time.sleep(1)
+    out_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    assert in_num_childs == out_num_childs
+
+
+@pytest.mark.parametrize('num_workers', [1, 2, 4])
+def test_create_workers(num_workers: int) -> None:
+    compiler = Compiler(num_workers=num_workers)
+    assert compiler.p is not None
+    expected = [num_workers, num_workers + 1]  # Some OS create a spawn server
+    assert len(psutil.Process(compiler.p.pid).children()) in expected
+    compiler.close()
+
+
+def test_one_thread_per_worker() -> None:
+    # On windows we aren't sure how the threads are handeled
+    if sys.platform == 'win32':
+        return
+
+    compiler = Compiler(num_workers=1)
+    assert compiler.p is not None
+    assert len(psutil.Process(compiler.p.pid).children()) in [1, 2]
+    assert psutil.Process(compiler.p.pid).children()[0].num_threads() == 1
+    compiler.close()
+
+
+def test_double_close() -> None:
+    compiler = Compiler(num_workers=1)
+    assert compiler.p is not None
+    compiler.close()
+    assert compiler.p is None
+    compiler.close()
+    assert compiler.p is None
+
+
+def test_interrupt_handling() -> None:
+    if sys.platform == 'win32':
+        sig = signal.SIGTERM
+    else:
+        sig = signal.SIGINT
+
+    in_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    p = subprocess.Popen([
+        'python', '-c',
+        """
+        import time
+        from bqskit.compiler import Compiler
+        compiler = Compiler(num_workers=1)
+        time.sleep(10)
+        """,
+    ])
+    p.send_signal(sig)
+
+    if sys.platform != 'win32':
+        out_num_childs = len(
+            psutil.Process(
+                os.getpid(),
+            ).children(recursive=True),
+        )
+        assert in_num_childs + 1 == out_num_childs
+
+    p.wait()
+    out_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    assert in_num_childs == out_num_childs
+
+
+class TestErrorPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        raise RuntimeError('Boo!')
+
+
+def raise_error() -> None:
+    raise RuntimeError('Boo!')
+
+
+class TestNestedErrorPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        await get_runtime().submit(raise_error)
+
+
+class TestWorkerExitPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        exit()
+
+
+def test_errors_shutdown_system() -> None:
+    in_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    try:
+        compiler = Compiler(num_workers=1)
+        compiler.compile(Circuit(2), [TestErrorPass()])
+    except RuntimeError:
+        if sys.platform == 'win32':
+            time.sleep(1)
+        num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+        assert in_num_childs == num_childs
+    else:
+        assert False, 'No error caught.'
+
+
+def test_errors_shutdown_system_nested() -> None:
+    in_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    try:
+        compiler = Compiler(num_workers=1)
+        compiler.compile(Circuit(2), [TestNestedErrorPass()])
+    except RuntimeError:
+        if sys.platform == 'win32':
+            time.sleep(1)
+        num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+        assert in_num_childs == num_childs
+    else:
+        assert False, 'No error caught.'
+
+
+def test_worker_fail_shutdown_system() -> None:
+    in_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+    try:
+        compiler = Compiler(num_workers=2)
+        compiler.compile(Circuit(2), [TestWorkerExitPass()])
+    except RuntimeError:
+        if sys.platform == 'win32':
+            time.sleep(1)
+        num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
+        assert in_num_childs == num_childs
+    else:
+        assert False, 'No error caught.'
diff --git a/tests/runtime/test_cancel.py b/tests/runtime/test_cancel.py
new file mode 100644
index 000000000..9557422d4
--- /dev/null
+++ b/tests/runtime/test_cancel.py
@@ -0,0 +1,44 @@
+from __future__ import annotations
+
+import time
+from typing import Any
+
+import pytest
+
+from bqskit.compiler import BasePass
+from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.runtime import get_runtime
+
+
+def iden(i: Any) -> Any:
+    return i
+
+
+def sleep1() -> None:
+    time.sleep(1)
+
+
+class TestCancelTask(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        future = get_runtime().submit(sleep1)
+        get_runtime().cancel(future)
+
+
+class TestCantAwaitCancelTask(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        future = get_runtime().submit(sleep1)
+        get_runtime().cancel(future)
+        await future
+
+
+def test_simple_cancel(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    server_compiler.compile(circuit, [TestCancelTask()])
+
+
+def test_cant_await_cancel(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    with pytest.raises(RuntimeError):
+        server_compiler.compile(circuit, [TestCantAwaitCancelTask()])
diff --git a/tests/runtime/test_execution.py b/tests/runtime/test_execution.py
new file mode 100644
index 000000000..66aea25e1
--- /dev/null
+++ b/tests/runtime/test_execution.py
@@ -0,0 +1,90 @@
+"""Checks the runtime's ability and correctness when executing tasks."""
+from __future__ import annotations
+
+from bqskit.compiler import BasePass
+from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.ir.gates import HGate
+from bqskit.ir.gates import XGate
+from bqskit.runtime import get_runtime
+
+
+def iden(i: int) -> int:
+    return i
+
+
+async def parent(i: int) -> tuple[int, int]:
+    return await get_runtime().map(iden, [2 * i, 2 * i + 1])
+
+
+async def add_hgate_to_circuit(c: Circuit) -> Circuit:
+    c.append_gate(HGate(), 0)
+    return c
+
+
+async def add_xgate_to_circuit(c: Circuit) -> Circuit:
+    c.append_gate(XGate(), 0)
+    return c
+
+
+class TestPass1(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        results = await get_runtime().map(iden, list(range(10)))
+        assert results == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+
+
+class TestPass2(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        results = await get_runtime().map(parent, list(range(1000)))
+        assert len(results) == 1000
+        assert all(r == [2 * i, 2 * i + 1] for i, r in enumerate(results))
+
+
+class TestPass3(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        out = await get_runtime().submit(add_hgate_to_circuit, circuit)
+        circuit.become(out)
+
+
+class TestPass4(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        out = await get_runtime().submit(add_xgate_to_circuit, circuit)
+        circuit.become(out)
+
+
+def test_simple_map(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    server_compiler.compile(circuit, [TestPass1()])
+
+
+def test_2level_map(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    server_compiler.compile(circuit, [TestPass2()])
+
+
+def test_circuit_change(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    out_circuit = server_compiler.compile(circuit, [TestPass3()])
+    assert HGate() in out_circuit.gate_set
+    assert out_circuit.num_operations == 1
+
+
+def test_reuse(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    server_compiler.compile(circuit, [TestPass1()])
+    server_compiler.compile(circuit, [TestPass1()])
+
+
+def test_parallel(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    t1 = server_compiler.submit(circuit, [TestPass3()])
+    t2 = server_compiler.submit(circuit, [TestPass4()])
+    c1 = server_compiler.result(t1)
+    c2 = server_compiler.result(t2)
+    assert isinstance(c1, Circuit)
+    assert isinstance(c2, Circuit)
+    assert HGate() in c1.gate_set
+    assert c1.num_operations == 1
+    assert XGate() in c2.gate_set
+    assert c2.num_operations == 1
diff --git a/tests/runtime/test_future.py b/tests/runtime/test_future.py
new file mode 100644
index 000000000..32423948f
--- /dev/null
+++ b/tests/runtime/test_future.py
@@ -0,0 +1,45 @@
+from __future__ import annotations
+
+import time
+from typing import Any
+
+import pytest
+
+from bqskit.compiler import BasePass
+from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.runtime import get_runtime
+
+
+def iden(i: Any) -> Any:
+    return i
+
+
+def sleep1() -> None:
+    time.sleep(1)
+
+
+class TestPassFutureDone(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        future = get_runtime().submit(sleep1)
+        assert not future._done
+        await future
+        assert future._done
+
+
+class TestPassFutureCannotSend(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        future = get_runtime().submit(sleep1)
+        get_runtime().submit(iden, future)
+
+
+def test_future_done(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    server_compiler.compile(circuit, [TestPassFutureDone()])
+
+
+def test_future_cannot_send(server_compiler: Compiler) -> None:
+    circuit = Circuit(2)
+    with pytest.raises(RuntimeError):
+        server_compiler.compile(circuit, [TestPassFutureCannotSend()])
diff --git a/tests/runtime/test_logging.py b/tests/runtime/test_logging.py
new file mode 100644
index 000000000..bee4bfb0a
--- /dev/null
+++ b/tests/runtime/test_logging.py
@@ -0,0 +1,205 @@
+"""This module tests the logging features supported by BQSKit Runtime."""
+from __future__ import annotations
+
+import logging
+from io import StringIO
+
+import pytest
+
+from bqskit import enable_logging
+from bqskit.compiler import BasePass
+from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.runtime import get_runtime
+
+
+async def nested1() -> None:
+    logging.getLogger('bqskit').info('bqskit_level1')
+    logging.getLogger('dummy2').info('dummy2_level1')
+    await get_runtime().submit(nested2)
+
+
+async def nested2() -> None:
+    logging.getLogger('bqskit').info('bqskit_level2')
+    logging.getLogger('dummy2').info('dummy2_level2')
+    await get_runtime().submit(nested3)
+
+
+async def nested3() -> None:
+    logging.getLogger('bqskit').info('bqskit_level3')
+    logging.getLogger('dummy2').info('dummy2_level3')
+
+
+class TestInfoPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        logging.getLogger('bqskit').info('bqskit_info')
+        logging.getLogger('bqskit.dummy').info('bqskit_dummy_info')
+        logging.getLogger('dummy2').info('dummy2_info')
+
+
+class TestDebugPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        logging.getLogger('bqskit').debug('bqskit_debug')
+        logging.getLogger('bqskit.dummy').debug('bqskit_dummy_debug')
+        logging.getLogger('dummy2').debug('dummy2_debug')
+
+
+class TestNestedLogPass(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        logging.getLogger('bqskit').info('bqskit_level0')
+        logging.getLogger('dummy2').info('dummy2_level0')
+        await get_runtime().submit(nested1)
+
+
+def test_using_enable_logging(server_compiler: Compiler) -> None:
+    enable_logging()
+    logger = logging.getLogger('bqskit')
+    stream = StringIO()
+    handler = logging.StreamHandler(stream)
+    handler.setLevel(logging.DEBUG)
+    logger.addHandler(handler)
+    server_compiler.compile(Circuit(1), [TestInfoPass(), TestDebugPass()])
+    log = stream.getvalue()
+    assert 'bqskit_info' in log
+    assert 'bqskit_dummy_info' in log
+    assert 'dummy2_info' not in log
+    assert 'bqskit_debug' not in log
+    assert 'bqskit_dummy_debug' not in log
+    assert 'dummy2_debug' not in log
+    logger.removeHandler(handler)
+    logger.setLevel(logging.WARNING)
+
+
+def test_using_enable_logging_verbose(server_compiler: Compiler) -> None:
+    enable_logging(True)
+    logger = logging.getLogger('bqskit')
+    handler = logging.StreamHandler(StringIO())
+    handler.setLevel(logging.DEBUG)
+    logger.addHandler(handler)
+    server_compiler.compile(Circuit(1), [TestInfoPass(), TestDebugPass()])
+    log = handler.stream.getvalue()
+    assert 'bqskit_info' in log
+    assert 'bqskit_dummy_info' in log
+    assert 'dummy2_info' not in log
+    assert 'bqskit_debug' in log
+    assert 'bqskit_dummy_debug' in log
+    assert 'dummy2_debug' not in log
+    logger.removeHandler(handler)
+    logger.setLevel(logging.WARNING)
+
+
+def test_using_standard_logging(server_compiler: Compiler) -> None:
+    logger = logging.getLogger('bqskit')
+    logger.setLevel(logging.DEBUG)
+    handler = logging.StreamHandler(StringIO())
+    handler.setLevel(logging.DEBUG)
+    logger.addHandler(handler)
+    server_compiler.compile(Circuit(1), [TestInfoPass(), TestDebugPass()])
+    log = handler.stream.getvalue()
+    assert 'bqskit_info' in log
+    assert 'bqskit_dummy_info' in log
+    assert 'dummy2_info' not in log
+    assert 'bqskit_debug' in log
+    assert 'bqskit_dummy_debug' in log
+    assert 'dummy2_debug' not in log
+    logger.removeHandler(handler)
+    logger.setLevel(logging.WARNING)
+
+
+def test_using_standard_logging_specific(server_compiler: Compiler) -> None:
+    logger = logging.getLogger('bqskit.dummy')
+    logger.setLevel(logging.DEBUG)
+    handler = logging.StreamHandler(StringIO())
+    handler.setLevel(logging.DEBUG)
+    logger.addHandler(handler)
+    server_compiler.compile(Circuit(1), [TestInfoPass(), TestDebugPass()])
+    log = handler.stream.getvalue()
+    assert 'bqskit_info' not in log
+    assert 'bqskit_dummy_info' in log
+    assert 'dummy2_info' not in log
+    assert 'bqskit_debug' not in log
+    assert 'bqskit_dummy_debug' in log
+    assert 'dummy2_debug' not in log
+    logger.removeHandler(handler)
+    logger.setLevel(logging.WARNING)
+
+
+def test_using_external_logging(server_compiler: Compiler) -> None:
+    logger = logging.getLogger('dummy2')
+    logger.setLevel(logging.DEBUG)
+    handler = logging.StreamHandler(StringIO())
+    handler.setLevel(logging.DEBUG)
+    logger.addHandler(handler)
+    server_compiler.compile(Circuit(1), [TestInfoPass(), TestDebugPass()])
+    log = handler.stream.getvalue()
+    assert 'bqskit_info' not in log
+    assert 'bqskit_dummy_info' not in log
+    assert 'dummy2_info' in log
+    assert 'bqskit_debug' not in log
+    assert 'bqskit_dummy_debug' not in log
+    assert 'dummy2_debug' in log
+    logger.removeHandler(handler)
+    logger.setLevel(logging.WARNING)
+
+
+@pytest.mark.parametrize('level', [-1, 0, 1, 2, 3, 4])
+def test_limiting_nested_calls_enable_logging(
+    server_compiler: Compiler,
+    level: int,
+) -> None:
+    enable_logging()
+    logger = logging.getLogger('bqskit')
+    stream = StringIO()
+    handler = logging.StreamHandler(stream)
+    handler.setLevel(logging.DEBUG)
+    logger.addHandler(handler)
+
+    server_compiler.compile(
+        Circuit(1),
+        [TestNestedLogPass()],
+        max_logging_depth=level,
+    )
+
+    log = stream.getvalue()
+    if level != -1:
+        expected_prints = list(range(0, min(4, level + 1)))
+    else:
+        expected_prints = [0, 1, 2, 3]
+    unexpected_prints = [x for x in [0, 1, 2, 3] if x not in expected_prints]
+    assert all(f'bqskit_level{i}' in log for i in expected_prints)
+    assert all(f'bqskit_level{i}' not in log for i in unexpected_prints)
+    assert all(f'dummy2_level{i}' not in log for i in [0, 1, 2, 3])
+    logger.removeHandler(handler)
+    logger.setLevel(logging.WARNING)
+
+
+@pytest.mark.parametrize('level', [-1, 0, 1, 2, 3, 4])
+def test_limiting_nested_calls_external_logging(
+    server_compiler: Compiler,
+    level: int,
+) -> None:
+    logger = logging.getLogger('dummy2')
+    logger.setLevel(logging.INFO)
+    stream = StringIO()
+    handler = logging.StreamHandler(stream)
+    handler.setLevel(logging.DEBUG)
+    logger.addHandler(handler)
+
+    server_compiler.compile(
+        Circuit(1),
+        [TestNestedLogPass()],
+        max_logging_depth=level,
+    )
+
+    log = stream.getvalue()
+    if level != -1:
+        expected_prints = list(range(0, min(4, level + 1)))
+    else:
+        expected_prints = [0, 1, 2, 3]
+    unexpected_prints = [x for x in [0, 1, 2, 3] if x not in expected_prints]
+    assert all(f'dummy2_level{i}' in log for i in expected_prints)
+    assert all(f'dummy2_level{i}' not in log for i in unexpected_prints)
+    assert all(f'bqskit_level{i}' not in log for i in [0, 1, 2, 3])
+    logger.removeHandler(handler)
+    logger.setLevel(logging.WARNING)
diff --git a/tests/runtime/test_next.py b/tests/runtime/test_next.py
new file mode 100644
index 000000000..30642a7d7
--- /dev/null
+++ b/tests/runtime/test_next.py
@@ -0,0 +1,83 @@
+"""This module tests the runtime's ability to wake tasks on first result."""
+from __future__ import annotations
+
+import time
+
+import pytest
+
+from bqskit.compiler import BasePass
+from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
+from bqskit.ir.circuit import Circuit
+from bqskit.runtime import get_runtime
+
+
+def sleepi(i: int) -> int:
+    time.sleep(i)
+    return i
+
+
+class TestNoDuplicateResult(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        future = get_runtime().map(sleepi, [0.3, 0.4, 0.1, 0.2])
+        int_ids = await get_runtime().next(future)
+        seen = [0]
+        for int_id in int_ids:
+            assert not int_id[1] in seen
+            seen.append(int_id[1])
+
+
+class TestNoDuplicateResultsInTwoNexts(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        future = get_runtime().map(sleepi, [0.3, 0.4, 0.1, 0.2])
+        seen = [0]
+        int_ids = await get_runtime().next(future)
+
+        for int_id in int_ids:
+            assert not int_id[1] in seen
+            seen.append(int_id[1])
+
+        int_ids = await get_runtime().next(future)
+
+        for int_id in int_ids:
+            assert not int_id[1] in seen
+            seen.append(int_id[1])
+
+
+class TestFutureAwaitGivesAllResultsAfterNext(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        sleep_times = [0.3, 0.4, 0.1, 0.2]
+        future = get_runtime().map(sleepi, sleep_times)
+        _ = await get_runtime().next(future)
+        assert (await future) == sleep_times
+
+
+class TestNextOnCompleteTaskRaisesError(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        future = get_runtime().map(sleepi, [0.3, 0.4, 0.1, 0.2])
+        _ = await future
+        with pytest.raises(RuntimeError):
+            _ = await get_runtime().next(future)
+
+
+class TestNextNeverlosesResults(BasePass):
+    async def run(self, circuit: Circuit, data: PassData) -> None:
+        future = get_runtime().map(sleepi, [0.1])
+        time.sleep(0.2)
+        int_ids = await get_runtime().next(future)
+        assert len(int_ids) == 1  # Only one should have been received
+        assert int_ids[0] == (0, .1)  # the 0th index 1 element
+
+
+@pytest.mark.parametrize(
+    'test_pass', [
+        TestNoDuplicateResult(),
+        TestNoDuplicateResultsInTwoNexts(),
+        TestFutureAwaitGivesAllResultsAfterNext(),
+        TestNextOnCompleteTaskRaisesError(),
+        TestNextNeverlosesResults(),
+    ],
+)
+def test_next_pass(server_compiler: Compiler, test_pass: BasePass) -> None:
+    circuit = Circuit(2)
+    server_compiler.compile(circuit, [test_pass])
diff --git a/tests/runtime/test_task.py b/tests/runtime/test_task.py
new file mode 100644
index 000000000..0bcb1aa48
--- /dev/null
+++ b/tests/runtime/test_task.py
@@ -0,0 +1,78 @@
+"""This module tests RuntimeTask functionality."""
+from __future__ import annotations
+
+from typing import Any
+from typing import Awaitable
+from typing import Generator
+
+import pytest
+
+from bqskit.runtime.address import RuntimeAddress
+from bqskit.runtime.task import RuntimeTask
+
+
+class AwaitableTestObject(Awaitable[Any]):
+    def __await__(self) -> Generator[str, str | None, str]:
+        yield 'test1'
+        assert (yield 'test2') == 'test_send'
+        assert (yield 'test3') is None
+        assert (yield 'test4') is None
+        return 'test_return'
+
+
+async def coro_test() -> str:
+    assert (await AwaitableTestObject()) == 'test_return'
+    return 'test_complete'
+
+
+def test_uninit_task_step_errors() -> None:
+    addr = RuntimeAddress(0, 0, 0)
+    with pytest.raises(RuntimeError):
+        task = RuntimeTask((coro_test, tuple(), {}), addr, 0, tuple())
+        task.step()
+
+
+def test_task_step() -> None:
+    addr = RuntimeAddress(0, 0, 0)
+    task = RuntimeTask((coro_test, tuple(), {}), addr, 0, tuple())
+    task.start()
+    assert task.step() == 'test1'
+    assert task.step() == 'test2'
+    assert task.step('test_send') == 'test3'
+    assert task.step() == 'test4'
+
+    try:
+        task.step()
+    except StopIteration as e:
+        assert e.value == 'test_complete'
+    else:
+        assert False, 'StopIteration next called.'
+
+
+def not_coro_test() -> str:
+    return 'test_complete'
+
+
+def test_task_step_no_coro() -> None:
+    addr = RuntimeAddress(0, 0, 0)
+    task = RuntimeTask((not_coro_test, tuple(), {}), addr, 0, tuple())
+    task.start()
+    try:
+        task.step()
+    except StopIteration as e:
+        assert e.value == 'test_complete'
+    else:
+        assert False, 'StopIteration next called.'
+
+
+def test_task_descendant_of() -> None:
+    task = RuntimeTask(
+        (coro_test, tuple(), {}),
+        RuntimeAddress(0, 0, 0),
+        0,
+        (RuntimeAddress(0, 0, 1), RuntimeAddress(0, 0, 2)),
+    )
+    assert task.is_descendant_of(RuntimeAddress(0, 0, 0))
+    assert task.is_descendant_of(RuntimeAddress(0, 0, 1))
+    assert task.is_descendant_of(RuntimeAddress(0, 0, 2))
+    assert not task.is_descendant_of(RuntimeAddress(1, 0, 0))