Repository: BQSKit/bqskit (language: python)

Issue:
Better State Support
Implemented basic support for compiling `StateVector` and `StateSystem` objects in the `bqskit.compile` function.
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)
```


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/bqskit/compiler/basepass.py b/bqskit/compiler/basepass.py
index 08d534f64..6138bcd86 100644
--- a/bqskit/compiler/basepass.py
+++ b/bqskit/compiler/basepass.py
@@ -2,22 +2,19 @@
 from __future__ import annotations
 
 import abc
-import logging
-from typing import Any
+import warnings
 from typing import TYPE_CHECKING
-from typing import TypeVar
-
-from bqskit.compiler.machine import MachineModel
-from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
-from bqskit.utils.typing import is_sequence
 
 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
-
-_logger = logging.getLogger(__name__)
-
-T = TypeVar('T')
+    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):
@@ -30,7 +27,7 @@ class BasePass(abc.ABC):
 
     Examples:
         >>> class PrintCNOTCountPass(BasePass):
-        ...     def run(self, circ: Circuit, data: dict[str, Any] = {}) -> None:
+        ...     def run(self, circ: Circuit, }one:
         ...         print(f"Number of CNOTs: {circ.count(CNOTGate())}")
     """
 
@@ -40,14 +37,14 @@ def name(self) -> str:
         return self.__class__.__name__
 
     @abc.abstractmethod
-    async 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
@@ -59,136 +56,121 @@ async 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.
+        (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.connectivity
 
-            data (dict[str, Any]): The data dictionary.
+    @staticmethod
+    def get_target(
+        _: Any,
+        data: PassData,
+    ) -> UnitaryMatrix | StateVector | StateSystem:
+        """
+        Retrieve the target from 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.target
 
     @staticmethod
-    def get_target(circuit: Circuit, data: dict[str, Any]) -> UnitaryMatrix:
+    def in_parallel(data: dict[str, Any]) -> bool:
         """
-        Retrieve the target unitary from the data dictionary.
-
-        Args:
-            circuit (Circuit): The pass circuit.
+        Return true if pass is being executed in a parallel.
 
-            data (dict[str, Any]): The data dictionary.
+        (Deprecated)
+        """
+        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
 
-        Returns:
-            UnitaryMatrix: The target unitary.
+    @staticmethod
+    def execute(*args: Any, **kwargs: Any) -> Any:
         """
-        if len(data) == 0:
-            return circuit.get_unitary()
+        Map a function over iterable arguments in parallel.
 
-        if 'target_unitary' not in data:
-            data['target_unitary'] = 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.',
+        )
 
-        if not isinstance(data['target_unitary'], UnitaryMatrix):
-            _logger.warning('Expected target_unitary to be a unitary.')
-            return circuit.get_unitary()
 
-        return data['target_unitary']
+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()
 
-    @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
+    await workflow.run(circuit, data)
 
-        if not isinstance(data['parallel'], bool):
-            _logger.warning('Expected parallel to be a bool.')
-            return False
+    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)
 
-        return data['parallel']
+    return circuit, data
diff --git a/bqskit/compiler/compile.py b/bqskit/compiler/compile.py
index b8794142c..ee9fcbe34 100644
--- a/bqskit/compiler/compile.py
+++ b/bqskit/compiler/compile.py
@@ -13,8 +13,10 @@
 
 from bqskit.compiler.compiler import Compiler
 from bqskit.compiler.machine import MachineModel
+from bqskit.compiler.passdata import PassData
 from bqskit.compiler.task import CompilationTask
 from bqskit.ir.circuit import Circuit
+from bqskit.ir.gate import Gate
 from bqskit.ir.gates import CNOTGate
 from bqskit.ir.gates import RZGate
 from bqskit.ir.gates import SqrtXGate
@@ -22,27 +24,36 @@
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.gates.measure import MeasurementPlaceholder
 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.ir.opt.minimizers.lbfgs import LBFGSMinimizer
 from bqskit.passes import *
 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,
@@ -120,10 +131,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(
@@ -132,17 +146,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)}.')
@@ -150,11 +166,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]
 
@@ -306,6 +324,25 @@ def compile(
             error_sim_size,
         )
 
+    elif isinstance(input, StateSystem):
+        statemap = input
+
+        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.',
+            )
+
+        task = _statemap_workflow(
+            statemap,
+            model,
+            optimization_level,
+            synthesis_epsilon,
+            max_synthesis_size,
+            error_threshold,
+            error_sim_size,
+        )
+
     # Connect to or construct a Compiler
     managed_compiler = compiler is None
 
@@ -320,15 +357,17 @@ def compile(
 
     # Perform the compilation
     if error_threshold is None:
-        out = compiler.compile(task)
+        t_id = compiler.submit(task)
+        out = compiler.result(t_id)
     else:
         task.request_data = True
-        out, data = compiler.compile(task)  # type: ignore
+        t_id = compiler.submit(task)
+        out, data = compiler.result(t_id)  # type: ignore
 
     # Log error if necessary
     if error_threshold is not None:
-        data = cast(dict[str, Any], data)
-        error = data['error']
+        data = cast(PassData, data)
+        error = data.error
         nonsq_error = 1 - np.sqrt(max(1 - (error * error), 0))
         if nonsq_error > error_threshold:
             warnings.warn(
@@ -411,19 +450,26 @@ def _opt1_workflow(
             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)
         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
@@ -541,19 +587,26 @@ def _opt2_workflow(
             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)
         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
@@ -687,8 +740,8 @@ def _opt3_workflow(
             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]
 
@@ -696,11 +749,18 @@ def _opt3_workflow(
         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
@@ -839,12 +899,176 @@ def _stateprep_workflow(
     error_sim_size: int = 8,
 ) -> CompilationTask:
     """Build a workflow for state preparation."""
-    # TODO
-    raise NotImplementedError('State preparation is not yet implemented.')
+    circuit = Circuit(1)
+    layer_gen = _get_layer_gen(model)
+
+    if optimization_level == 1:
+        inst_ops = {
+            'multistarts': 1,
+            'method': 'minimization',
+            'minimizer': LBFGSMinimizer(),
+        }
+        synthesis = 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,
+        )
+
+    elif optimization_level == 3:
+        inst_ops = {
+            'multistarts': 8,
+            'method': 'minimization',
+            'ftol': 5e-16,
+            'gtol': 1e-15,
+        }
+        if circuit.num_qudits > 3:
+            synthesis = LEAPSynthesisPass(
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+                min_prefix_size=7,
+            )
+        else:
+            synthesis = QSearchSynthesisPass(  # type: ignore
+                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,
+        }
+        raise NotImplementedError('Not yet.')
+
+    scan = ScanningGateRemovalPass(
+        success_threshold=synthesis_epsilon,
+        instantiate_options=inst_ops,
+        cost=HilbertSchmidtCostGenerator(),
+    )
+
+    workflow = [
+        SetModelPass(model),
+        SetTargetPass(state),
+        synthesis,
+        scan,
+    ]
+
+    return CompilationTask(circuit, workflow)
+
+
+def _statemap_workflow(
+    state: StateSystem,
+    model: MachineModel,
+    optimization_level: int = 1,
+    synthesis_epsilon: float = 1e-8,
+    max_synthesis_size: int = 4,
+    error_threshold: float | None = None,
+    error_sim_size: int = 8,
+) -> CompilationTask:
+    """Build a workflow for state preparation."""
+    circuit = Circuit(1)
+    layer_gen = _get_layer_gen(model)
+
+    if optimization_level == 1:
+        inst_ops = {
+            'multistarts': 1,
+            'method': 'minimization',
+        }
+        synthesis = 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 circuit.num_qudits > 3:
+            synthesis = LEAPSynthesisPass(
+                success_threshold=synthesis_epsilon,
+                layer_generator=layer_gen,
+                instantiate_options=inst_ops,
+                min_prefix_size=7,
+            )
+        else:
+            synthesis = QSearchSynthesisPass(  # type: ignore
+                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,
+        }
+        raise NotImplementedError('Not yet.')
+
+    scan = ScanningGateRemovalPass(
+        success_threshold=synthesis_epsilon,
+        instantiate_options=inst_ops,
+    )
+
+    workflow = [
+        SetModelPass(model),
+        SetTargetPass(state),
+        synthesis,
+        scan,
+    ]
+
+    return CompilationTask(circuit, workflow)
 
 
 def _get_layer_gen(model: MachineModel) -> LayerGenerator:
     """Build a `model`-compliant layer generator."""
+    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]])
+
     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]
 
@@ -852,9 +1076,9 @@ def _get_layer_gen(model: MachineModel) -> LayerGenerator:
         if CNOTGate() in tq_gates:
             return FourParamGenerator()
         else:
-            return SimpleLayerGenerator(tq_gates[0])
+            return SimpleLayerGenerator(tq_gates[0], sq_gate)
 
-    return WideLayerGenerator(tq_gates + mq_gates)
+    return WideLayerGenerator(tq_gates + mq_gates, sq_gate)
 
 
 def _get_single_qudit_gate_rebase_pass(model: MachineModel) -> BasePass:
diff --git a/bqskit/compiler/compiler.py b/bqskit/compiler/compiler.py
index 3ae609780..974c1a193 100644
--- a/bqskit/compiler/compiler.py
+++ b/bqskit/compiler/compiler.py
@@ -7,26 +7,27 @@
 import multiprocessing as mp
 import os
 import signal
+import sys
 import time
 import uuid
 import warnings
 from multiprocessing.connection import Client
 from multiprocessing.connection import Connection
 from types import FrameType
-from typing import Iterable
 from typing import overload
 from typing import TYPE_CHECKING
 
+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
 from bqskit.runtime import default_server_port
-from bqskit.runtime.attached import start_attached_server
+from bqskit.runtime import default_worker_port
 from bqskit.runtime.message import RuntimeMessage
-from bqskit.utils.typing import is_iterable
 
 if TYPE_CHECKING:
     from typing import Any
-    from bqskit.compiler.basepass import BasePass
     from bqskit.ir.circuit import Circuit
 
 _logger = logging.getLogger(__name__)
@@ -65,33 +66,44 @@ class Compiler:
     def __init__(
         self,
         ip: None | str = None,
-        port: None | int = 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."""
         self.p: mp.Process | None = None
         self.conn: Connection | None = None
-        if port is None:
-            port = default_server_port
 
         atexit.register(self.close)
         if ip is None:
             ip = 'localhost'
-            self._start_server(num_workers)
+            self._start_server(num_workers, runtime_log_level, worker_port)
 
         self._connect_to_server(ip, port)
 
-    def _start_server(self, num_workers: int) -> None:
-        self.p = mp.Process(target=start_attached_server, args=(num_workers,))
+    def _start_server(
+        self,
+        num_workers: int,
+        runtime_log_level: int,
+        worker_port: int,
+    ) -> None:
+        from bqskit.runtime.attached import start_attached_server
+        self.p = mp.Process(
+            target=start_attached_server,
+            args=(num_workers, runtime_log_level),
+            kwargs={'worker_port': worker_port},
+        )
         _logger.debug('Starting runtime server process.')
         self.p.start()
 
     def _connect_to_server(self, ip: str, port: int) -> None:
-        max_retries = 5
+        max_retries = 7
         wait_time = .25
         for _ in range(max_retries):
             try:
-                conn = Client((ip, port))
+                family = 'AF_INET' if sys.platform == 'win32' else None
+                conn = Client((ip, port), family)
             except ConnectionRefusedError:
                 time.sleep(wait_time)
                 wait_time *= 2
@@ -144,7 +156,10 @@ def close(self) -> None:
 
                 self.p.join(1)
                 if self.p.exitcode is None:
-                    os.kill(self.p.pid, signal.SIGKILL)
+                    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:
@@ -170,7 +185,7 @@ def __del__(self) -> None:
     def submit(
         self,
         task_or_circuit: CompilationTask | Circuit,
-        workflow: Iterable[BasePass] | None = None,
+        workflow: WorkflowLike | None = None,
         request_data: bool = False,
         logging_level: int | None = None,
         max_logging_depth: int = -1,
@@ -184,7 +199,7 @@ def submit(
                 argument should be specified. If a task is not specified,
                 the circuit must be paired with a workflow argument.
 
-            workflow (Iterable[BasePass]): The compilation job submitted
+            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
@@ -218,14 +233,10 @@ def submit(
 
         else:
             if workflow is None:
-                raise TypeError(
-                    'Must specify workflow when providing a circuit to submit.',
-                )
-
-            if not is_iterable(workflow):
-                raise TypeError('Expected sequence of bqskit passes.')
+                m = 'Must specify workflow when providing a circuit to submit.'
+                raise TypeError(m)
 
-            task = CompilationTask(task_or_circuit, list(workflow))
+            task = CompilationTask(task_or_circuit, Workflow(workflow))
 
         # Set task configuration
         task.request_data = request_data
@@ -256,7 +267,7 @@ def status(self, task_id: CompilationTask | uuid.UUID) -> CompilationStatus:
     def result(
         self,
         task_id: CompilationTask | uuid.UUID,
-    ) -> Circuit | tuple[Circuit, dict[str, Any]]:
+    ) -> Circuit | tuple[Circuit, PassData]:
         """Block until the task is finished, return its result."""
         if isinstance(task_id, CompilationTask):
             warnings.warn(
@@ -294,7 +305,7 @@ def cancel(self, task_id: CompilationTask | uuid.UUID) -> bool:
     def compile(
         self,
         task_or_circuit: CompilationTask,
-    ) -> Circuit | tuple[Circuit, dict[str, Any]]:
+    ) -> Circuit | tuple[Circuit, PassData]:
         """Submit a task, wait for its results; see :func:`submit` for more."""
         ...
 
@@ -302,7 +313,7 @@ def compile(
     def compile(
         self,
         task_or_circuit: Circuit,
-        workflow: Iterable[BasePass],
+        workflow: WorkflowLike,
         request_data: None = None,
         logging_level: int | None = None,
         max_logging_depth: int = -1,
@@ -314,22 +325,22 @@ def compile(
     def compile(
         self,
         task_or_circuit: Circuit,
-        workflow: Iterable[BasePass],
+        workflow: WorkflowLike,
         request_data: bool,
         logging_level: int | None = None,
         max_logging_depth: int = -1,
-    ) -> tuple[Circuit, dict[str, Any]]:
+    ) -> tuple[Circuit, PassData]:
         """Submit a task, wait for its results; see :func:`submit` for more."""
         ...
 
     def compile(
         self,
         task_or_circuit: CompilationTask | Circuit,
-        workflow: Iterable[BasePass] | None = None,
+        workflow: WorkflowLike | None = None,
         request_data: bool | None = None,
         logging_level: int | None = None,
         max_logging_depth: int = -1,
-    ) -> Circuit | tuple[Circuit, dict[str, Any]]:
+    ) -> Circuit | tuple[Circuit, PassData]:
         """Submit a task, wait for its results; see :func:`submit` for more."""
         if isinstance(task_or_circuit, CompilationTask):
             warnings.warn(
diff --git a/bqskit/compiler/machine.py b/bqskit/compiler/machine.py
index 10bc9e19e..c97c4529a 100644
--- a/bqskit/compiler/machine.py
+++ b/bqskit/compiler/machine.py
@@ -5,8 +5,11 @@
 from typing import TYPE_CHECKING
 
 from bqskit.ir.gate import Gate
+from bqskit.ir.gates.constant.csum import CSUMGate
 from bqskit.ir.gates.constant.cx import CNOTGate
 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.location import CircuitLocation
 from bqskit.qis.graph import CouplingGraph
 from bqskit.qis.graph import CouplingGraphLike
@@ -17,10 +20,14 @@
     from bqskit.ir.circuit import Circuit
 
 
-default_gate_set: set[Gate] = {
+default_qubit_gate_set: set[Gate] = {
     CNOTGate(),
     U3Gate(),
 }
+default_qutrit_gate_set: set[Gate] = {
+    CSUMGate(),
+    U8Gate(),
+}
 
 
 class MachineModel:
@@ -30,7 +37,7 @@ def __init__(
         self,
         num_qudits: int,
         coupling_graph: CouplingGraphLike | None = None,
-        gate_set: set[Gate] = default_gate_set,
+        gate_set: set[Gate] | None = None,
         radixes: Sequence[int] = [],
     ) -> None:
         """
@@ -46,7 +53,8 @@ def __init__(
                 (Default: None)
 
             gate_set (set[Gate]): The native gate set available on the
-                machine.
+                machine. Defaults to CNOTs+U3s for qubits, CSUM+U8s for
+                qutrits, and variable unitary gates for arbitrary qudits.
 
             radixes (Sequence[int]): A sequence with its length equal
                 to `num_qudits`. Each element specifies the base of a
@@ -87,6 +95,15 @@ def __init__(
         ):
             raise TypeError('Invalid coupling graph, expected list of tuples')
 
+        if gate_set is None:
+            r = self.radixes[0]
+            if r == 2:
+                gate_set = default_qubit_gate_set
+            elif r == 3:
+                gate_set = default_qutrit_gate_set
+            else:
+                gate_set = {VariableUnitaryGate(2, [r, r])}
+
         if not isinstance(gate_set, set):
             raise TypeError(f'Expected set of gates, got {type(gate_set)}.')
 
@@ -101,7 +118,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..85b027e66
--- /dev/null
+++ b/bqskit/compiler/passdata.py
@@ -0,0 +1,201 @@
+"""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.machine import MachineModel
+from bqskit.ir.circuit import Circuit
+from bqskit.qis import StateVector
+from bqskit.qis import UnitaryMatrix
+from bqskit.qis.graph import CouplingGraph
+from bqskit.qis.state.system import StateSystem
+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 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/task.py b/bqskit/compiler/task.py
index be383fc6f..08d398ec9 100644
--- a/bqskit/compiler/task.py
+++ b/bqskit/compiler/task.py
@@ -3,12 +3,13 @@
 
 import logging
 import uuid
-from typing import Any
-from typing import Iterable
 from typing import TYPE_CHECKING
 
+from bqskit.compiler.passdata import PassData
+from bqskit.compiler.workflow import Workflow
+from bqskit.compiler.workflow import WorkflowLike
+
 if TYPE_CHECKING:
-    from bqskit.compiler.basepass import BasePass
     from bqskit.ir.circuit import Circuit
 
 _logger = logging.getLogger(__name__)
@@ -22,21 +23,21 @@ class CompilationTask():
     can be submitted to a BQSKit compiler to be efficiently executed.
     """
 
-    def __init__(self, input: Circuit, workflow: Iterable[BasePass]) -> None:
+    def __init__(self, input: Circuit, workflow: WorkflowLike) -> None:
         """
         Construct a CompilationTask.
 
         Args:
             input (Circuit): The input circuit to be compiled.
 
-            workflow (Iterable[BasePass]): The configured workflow to be
+            workflow (WorkflowLike): The configured workflow to be
                 performed on the circuit.
         """
         self.task_id = uuid.uuid4()
         self.circuit = input
-        self.workflow = workflow
+        self.workflow = Workflow(workflow)
 
-        self.data: dict[str, Any] = {}
+        self.data = PassData(input)
         """The task's data for use in BQSKit passes."""
 
         self.done = False
@@ -51,12 +52,9 @@ def __init__(self, input: Circuit, workflow: Iterable[BasePass]) -> None:
         self.max_logging_depth = -1
         """No logging for tasks with more than `max_logging_depth` parents."""
 
-    async def run(self) -> Circuit | tuple[Circuit, dict[str, Any]]:
+    async def run(self) -> Circuit | tuple[Circuit, PassData]:
         """Execute the task."""
-        for pass_obj in self.workflow:
-            await pass_obj.run(self.circuit, self.data)
-
-        self.done = True
+        await self.workflow.run(self.circuit, self.data)
 
         if not self.request_data:
             return self.circuit
diff --git a/bqskit/compiler/workflow.py b/bqskit/compiler/workflow.py
new file mode 100644
index 000000000..603ae2474
--- /dev/null
+++ b/bqskit/compiler/workflow.py
@@ -0,0 +1,115 @@
+"""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 Union
+
+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_iterable
+
+
+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: Workflow) -> Workflow:
+        return Workflow(self._passes + other._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/ir/circuit.py b/bqskit/ir/circuit.py
index 064b681c8..3a5903160 100644
--- a/bqskit/ir/circuit.py
+++ b/bqskit/ir/circuit.py
@@ -20,8 +20,6 @@
 import numpy as np
 import numpy.typing as npt
 
-from bqskit.compiler.compiler import Compiler
-from bqskit.compiler.task import CompilationTask
 from bqskit.ir.gate import Gate
 from bqskit.ir.gates.circuitgate import CircuitGate
 from bqskit.ir.gates.composed.daggergate import DaggerGate
@@ -47,6 +45,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,9 +56,7 @@
 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
@@ -2617,23 +2614,28 @@ def perform(
             Rather, you should use the
             :class:`~bqskit.compiler.compiler.Compiler` directly.
         """
-        if data is None:
-            data = {}
+        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 = data
-            self.become(compiler.compile(task))  # type: ignore
+            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,
         **kwargs: Any,
     ) -> Circuit:
         """
@@ -2644,12 +2646,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
@@ -2750,15 +2753,27 @@ 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:
+        try:
+            if UnitaryMatrix.is_unitary(target):
+                target = UnitaryMatrix(target)
+
+            elif StateVector.is_pure_state(target):
+                target = StateVector(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(
-                'Expected either StateVector or UnitaryMatrix'
-                ' for target, got %s.' % type(target),
-            )
+                'Unable to determine type of input.'
+                ' Ensure that you are trying to compile a valid'
+                ' circuit, unitary, or state.',
+            ) from e
 
         if target.dim != self.dim:
             raise ValueError('Target dimension mismatch with circuit.')
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..8cf15b084 100644
--- a/bqskit/ir/opt/instantiater.py
+++ b/bqskit/ir/opt/instantiater.py
@@ -8,11 +8,13 @@
 import numpy.typing as npt
 
 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 +30,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 +39,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.
 
@@ -73,21 +75,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/minimization.py b/bqskit/ir/opt/instantiaters/minimization.py
index bab584f34..e245cbc3f 100644
--- a/bqskit/ir/opt/instantiaters/minimization.py
+++ b/bqskit/ir/opt/instantiaters/minimization.py
@@ -13,6 +13,7 @@
 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 +26,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 +37,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,20 +46,16 @@ 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."""
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/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 f810501a5..a6eac3620 100644
--- a/bqskit/passes/__init__.py
+++ b/bqskit/passes/__init__.py
@@ -235,6 +235,7 @@
 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
@@ -326,4 +327,5 @@
     'CNOTToCHPass',
     'CNOTToCYPass',
     'CYToCNOTPass',
+    'SetTargetPass',
 ]
diff --git a/bqskit/passes/alias.py b/bqskit/passes/alias.py
index 30d232246..6f59e597e 100644
--- a/bqskit/passes/alias.py
+++ b/bqskit/passes/alias.py
@@ -2,9 +2,9 @@
 from __future__ import annotations
 
 import abc
-from typing import Any
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir.circuit import Circuit
 
 
@@ -15,7 +15,7 @@ class PassAlias(BasePass):
     def get_passes(self) -> list[BasePass]:
         """Return the passes that should be run, when this alias is called."""
 
-    async 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():
             await bqskit_pass.run(circuit, data)
diff --git a/bqskit/passes/control/dothendecide.py b/bqskit/passes/control/dothendecide.py
index c1a4a4ae6..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)
 
-    async 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:
-            await 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 312616f1b..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)
 
-    async 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:
-            await 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:
-                await 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 c30b998bc..31f677cd7 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
@@ -16,8 +18,6 @@
 from bqskit.ir.operation import Operation
 from bqskit.ir.point import CircuitPoint
 from bqskit.runtime import get_runtime
-from bqskit.utils.typing import is_integer
-from bqskit.utils.typing import is_sequence
 
 _logger = logging.getLogger(__name__)
 
@@ -26,8 +26,8 @@ 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'
@@ -35,18 +35,17 @@ class ForEachBlockPass(BasePass):
 
     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
@@ -69,61 +68,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 < 0:
-            raise ValueError(
-                'Expected nonnegative integer for batch_size'
-                f', got {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,
             )
 
-        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)}.',
             )
 
-    async 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:
@@ -135,15 +107,19 @@ async 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):
@@ -152,7 +128,7 @@ async 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(
@@ -162,43 +138,26 @@ async 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
 
             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 = 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 +182,7 @@ async 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 +194,8 @@ async 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 +211,3 @@ def default_collection_filter(op: Operation) -> bool:
 
 def default_replace_filter(circuit: Circuit, op: Operation) -> bool:
     return True
-
-
-async 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:
-            await 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 8c786dee4..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
 
-    async 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:
-                await 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:
-                await 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 5a60f784c..42b9bbeee 100644
--- a/bqskit/passes/control/paralleldo.py
+++ b/bqskit/passes/control/paralleldo.py
@@ -2,14 +2,21 @@
 from __future__ import annotations
 
 import logging
-from typing import Any
 from typing import Callable
-from typing import Sequence
+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.compiler.workflow import Workflow
 from bqskit.runtime import get_runtime
-from bqskit.utils.typing import is_sequence
+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__)
 
@@ -18,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:
@@ -31,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.
@@ -40,42 +49,26 @@ 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
 
-    async 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.')
 
@@ -83,7 +76,7 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
         runtime = get_runtime()
         future = runtime.map(
             _sub_do_work,
-            self.pass_seqs,
+            self.workflows,
             circuit=circuit,
             data=data,
         )
@@ -92,6 +85,7 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
         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
 
@@ -104,16 +98,4 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
 
         # Become best result
         circuit.become(best_circ)
-        data.update(best_data)
-
-
-async def _sub_do_work(
-    loop_body: Sequence[BasePass],
-    circuit: Circuit,
-    data: dict[str, Any],
-) -> tuple[Circuit, dict[str, Any]]:
-    """Execute a sequence of passes on circuit."""
-    # TODO: Move to BasePass
-    for loop_pass in loop_body:
-        await loop_pass.run(circuit, data)
-    return circuit, data
+        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 0a6610b72..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)
 
-    async 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:
-                await 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 9b6678659..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
 
-    async 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
 
-    async 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 8e13b759e..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
 
-    async 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 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                 block.params,
             )
             subcircuit.unfold((0, 0))
-            await 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`',
             )
 
-    async 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/apply.py b/bqskit/passes/mapping/apply.py
index 1b2b2ba46..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."""
 
-    async 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/layout/sabre.py b/bqskit/passes/mapping/layout/sabre.py
index 69201e80e..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,
         )
 
-    async 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/placement/greedy.py b/bqskit/passes/mapping/placement/greedy.py
index 246d91dfe..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."""
 
-    async 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 e2c06b314..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."""
 
-    async 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/sabre.py b/bqskit/passes/mapping/routing/sabre.py
index 4dadaa164..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.
     """
 
-    async 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/setmodel.py b/bqskit/passes/mapping/setmodel.py
index 1c9f3b945..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
 
-    async 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/measure.py b/bqskit/passes/measure.py
index 2015bc896..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__'
 
-    async 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 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
 class RestoreMeasurements(BasePass):
     """A pass that restores the measurements in a circuit."""
 
-    async 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 d103dbdc5..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."""
 
-    async 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 faec20869..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
 
-    async 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 f94e7a21c..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
 
-    async 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 a44d16ef8..8fa505562 100644
--- a/bqskit/passes/partitioning/quick.py
+++ b/bqskit/passes/partitioning/quick.py
@@ -2,11 +2,11 @@
 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.location import CircuitLocation
@@ -48,7 +48,7 @@ def __init__(self, block_size: int = 3) -> None:
 
         self.block_size = block_size
 
-    async 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.
 
diff --git a/bqskit/passes/partitioning/scan.py b/bqskit/passes/partitioning/scan.py
index ebcd68c83..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
 
-    async 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 c9dcf09cf..d73f7abe7 100644
--- a/bqskit/passes/partitioning/single.py
+++ b/bqskit/passes/partitioning/single.py
@@ -1,9 +1,8 @@
 """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.region import CircuitRegion
 
@@ -15,7 +14,7 @@ class GroupSingleQuditGatePass(BasePass):
     This pass groups together consecutive single-qudit gates.
     """
 
-    async 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
diff --git a/bqskit/passes/processing/exhaustive.py b/bqskit/passes/processing/exhaustive.py
index 2dda439c3..64656cf67 100644
--- a/bqskit/passes/processing/exhaustive.py
+++ b/bqskit/passes/processing/exhaustive.py
@@ -8,6 +8,7 @@
 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
@@ -105,8 +106,12 @@ def __init__(
         }
         self.instantiate_options.update(instantiate_options)
 
-    async 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)
@@ -142,7 +147,7 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                 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 24ee3e02c..b2768c473 100644
--- a/bqskit/passes/processing/rebase.py
+++ b/bqskit/passes/processing/rebase.py
@@ -6,6 +6,7 @@
 from typing import Sequence
 
 from bqskit.compiler.basepass import BasePass
+from bqskit.compiler.passdata import PassData
 from bqskit.ir import Gate
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import U3Gate
@@ -152,8 +153,11 @@ def __init__(
         self.sq = single_qudit_gate
         self.generate_new_gate_templates()
 
-    async 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)
@@ -191,7 +195,7 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                     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 +281,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 +289,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 60c04cde3..3ae7d589d 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
@@ -91,11 +92,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)
 
-    async 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}.')
 
@@ -125,7 +131,7 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                 Circuit.instantiate,
                 working_copy,
                 target=target,
-                **self.instantiate_options,
+                **instantiate_options,
             )
 
             if self.cost(working_copy, target) < self.success_threshold:
diff --git a/bqskit/passes/processing/substitute.py b/bqskit/passes/processing/substitute.py
index 511e54aab..37dc5f057 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
@@ -92,8 +93,11 @@ def __init__(
         }
         self.instantiate_options.update(instantiate_options)
 
-    async 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 = [
@@ -130,7 +134,7 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
                     Circuit.instantiate,
                     circuit_copy,
                     target=target,
-                    **self.instantiate_options,
+                    **instantiate_options,
                 )
 
                 if self.cost(circuit_copy, target) < self.success_threshold:
diff --git a/bqskit/passes/rules/ch2cnot.py b/bqskit/passes/rules/ch2cnot.py
index 68e7c880a..ddc46c030 100644
--- a/bqskit/passes/rules/ch2cnot.py
+++ b/bqskit/passes/rules/ch2cnot.py
@@ -1,11 +1,10 @@
 """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
@@ -29,7 +28,7 @@ def __init__(self) -> None:
         circuit.append_gate(RYGate(), 1, [-np.pi / 4])
         self.cg = CircuitGate(circuit)
 
-    async 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 9a774d7ed..1939fd1e9 100644
--- a/bqskit/passes/rules/cnot2ch.py
+++ b/bqskit/passes/rules/cnot2ch.py
@@ -1,11 +1,10 @@
 """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
@@ -29,7 +28,7 @@ def __init__(self) -> None:
         circuit.append_gate(RYGate(), 1, [np.pi / 4])
         self.cg = CircuitGate(circuit)
 
-    async 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 ac4b62907..f7c66d696 100644
--- a/bqskit/passes/rules/cnot2cy.py
+++ b/bqskit/passes/rules/cnot2cy.py
@@ -1,9 +1,8 @@
 """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
@@ -28,7 +27,7 @@ def __init__(self) -> None:
         circuit.append_gate(SdgGate(), 1)
         self.cg = CircuitGate(circuit)
 
-    async 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 b3e7fb28c..859d6b840 100644
--- a/bqskit/passes/rules/cnot2cz.py
+++ b/bqskit/passes/rules/cnot2cz.py
@@ -1,9 +1,8 @@
 """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
@@ -27,7 +26,7 @@ def __init__(self) -> None:
         circuit.append_gate(HGate(), 1)
         self.cg = CircuitGate(circuit)
 
-    async 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 103e0d4a8..2c177d787 100644
--- a/bqskit/passes/rules/cy2cnot.py
+++ b/bqskit/passes/rules/cy2cnot.py
@@ -1,9 +1,8 @@
 """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
@@ -28,7 +27,7 @@ def __init__(self) -> None:
         circuit.append_gate(SGate(), 1)
         self.cg = CircuitGate(circuit)
 
-    async 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 1d3a754fb..f7cc6ac44 100644
--- a/bqskit/passes/rules/cz2cnot.py
+++ b/bqskit/passes/rules/cz2cnot.py
@@ -1,9 +1,8 @@
 """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
@@ -27,7 +26,7 @@ def __init__(self) -> None:
         circuit.append_gate(HGate(), 1)
         self.cg = CircuitGate(circuit)
 
-    async 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 aaee50197..9ac3a0dd1 100644
--- a/bqskit/passes/rules/swap2cnot.py
+++ b/bqskit/passes/rules/swap2cnot.py
@@ -1,9 +1,8 @@
 """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
@@ -26,7 +25,7 @@ def __init__(self) -> None:
         circuit.append_gate(CNOTGate(), (1, 0))
         self.cg = CircuitGate(circuit)
 
-    async 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 1802ff4d3..c63c1f482 100644
--- a/bqskit/passes/rules/u3.py
+++ b/bqskit/passes/rules/u3.py
@@ -1,9 +1,8 @@
 """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
 
@@ -15,7 +14,7 @@ class U3Decomposition(BasePass):
     Convert a single-qubit circuit to U3 gate.
     """
 
-    async 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/rules/zxzxz.py b/bqskit/passes/rules/zxzxz.py
index 35a512b88..7099bd8ea 100644
--- a/bqskit/passes/rules/zxzxz.py
+++ b/bqskit/passes/rules/zxzxz.py
@@ -2,11 +2,11 @@
 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
@@ -19,7 +19,7 @@ class ZXZXZDecomposition(BasePass):
     Convert a single-qubit circuit to ZXZXZ sequence.
     """
 
-    async 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..e03e88381 100644
--- a/bqskit/passes/search/generators/fourparam.py
+++ b/bqskit/passes/search/generators/fourparam.py
@@ -2,9 +2,8 @@
 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
@@ -13,6 +12,7 @@
 from bqskit.ir.gates 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,7 +71,7 @@ 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 = []
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..218919a2f 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.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..ceaa92da0 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.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.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__)
 
@@ -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..e397671b6 100644
--- a/bqskit/passes/synthesis/__init__.py
+++ b/bqskit/passes/synthesis/__init__.py
@@ -6,6 +6,7 @@
 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 +14,5 @@
     'QPredictDecompositionPass',
     'QSearchSynthesisPass',
     'SynthesisPass',
+    'SetTargetPass',
 ]
diff --git a/bqskit/passes/synthesis/leap.py b/bqskit/passes/synthesis/leap.py
index 4e65eed66..c2d4a4d6f 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,6 +17,8 @@
 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
@@ -156,12 +159,14 @@ def __init__(
 
     async def synthesize(
         self,
-        utry: UnitaryMatrix,
-        data: dict[str, Any],
+        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)
@@ -169,7 +174,7 @@ async def synthesize(
             Circuit.instantiate,
             initial_layer,
             target=utry,
-            **self.instantiate_options,
+            **instantiate_options,
         )
         frontier.add(initial_layer, 0)
 
@@ -203,7 +208,7 @@ async def synthesize(
                 Circuit.instantiate,
                 successors,
                 target=utry,
-                **self.instantiate_options,
+                **instantiate_options,
             )
 
             # Evaluate successors
@@ -235,7 +240,6 @@ async def synthesize(
                         _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/qfast.py b/bqskit/passes/synthesis/qfast.py
index 58b3e6034..fc6ebf890 100644
--- a/bqskit/passes/synthesis/qfast.py
+++ b/bqskit/passes/synthesis/qfast.py
@@ -6,6 +6,7 @@
 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
@@ -15,6 +16,8 @@
 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
@@ -125,15 +128,17 @@ def __init__(
 
     async def synthesize(
         self,
-        utry: UnitaryMatrix,
-        data: dict[str, Any],
+        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)
@@ -153,12 +158,12 @@ async def synthesize(
         while True:
 
             # Instantiate circuit
-            circuit.instantiate(utry, **self.instantiate_options)
+            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
 
@@ -195,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
@@ -208,7 +213,7 @@ def finalize(
         # Reinstantiate
         dist = self.cost.calc_cost(circuit, utry)
         while dist > self.success_threshold:
-            circuit.instantiate(utry, **self.instantiate_options)
+            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 d64b28ac4..4eaf035d7 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.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
@@ -182,10 +185,16 @@ def __init__(
 
     async def synthesize(
         self,
-        utry: UnitaryMatrix,
-        data: dict[str, Any],
+        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:
@@ -228,7 +237,7 @@ async def synthesize(
                 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 b5693283c..5eac51c69 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,6 +14,8 @@
 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
@@ -135,10 +138,14 @@ def __init__(
 
     async def synthesize(
         self,
-        utry: UnitaryMatrix,
-        data: dict[str, Any],
+        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
@@ -147,7 +154,7 @@ async def synthesize(
             Circuit.instantiate,
             initial_layer,
             target=utry,
-            **self.instantiate_options,
+            **instantiate_options,
         )
         frontier.add(initial_layer, 0)
 
@@ -178,7 +185,7 @@ async def synthesize(
                 Circuit.instantiate,
                 successors,
                 target=utry,
-                **self.instantiate_options,
+                **instantiate_options,
             )
 
             # Evaluate successors
diff --git a/bqskit/passes/synthesis/synthesis.py b/bqskit/passes/synthesis/synthesis.py
index d63ddeb60..8f52a0734 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
 
@@ -27,8 +29,8 @@ class SynthesisPass(BasePass):
     @abstractmethod
     async def synthesize(
         self,
-        utry: UnitaryMatrix,
-        data: dict[str, Any],
+        target: UnitaryMatrix | StateVector | StateSystem,
+        data: PassData,
     ) -> Circuit:
         """
         Synthesis abstract method to synthesize a UnitaryMatrix into a Circuit.
@@ -45,10 +47,6 @@ async def synthesize(
             This function should be self-contained and have no side effects.
         """
 
-    async 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(await 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/compress.py b/bqskit/passes/util/compress.py
index cab2613ed..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.
     """
 
-    async 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 18130ef24..07cbe758a 100644
--- a/bqskit/passes/util/conversion.py
+++ b/bqskit/passes/util/conversion.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.ir.gates import CircuitGate
 from bqskit.ir.gates import ConstantUnitaryGate
@@ -68,7 +68,7 @@ def __init__(
         else:
             raise ValueError('Unexpected input for conversion target.')
 
-    async 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 35193f744..f86b38d22 100644
--- a/bqskit/passes/util/converttou3.py
+++ b/bqskit/passes/util/converttou3.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.ir.gates.parameterized.pauli import PauliGate
 from bqskit.ir.gates.parameterized.u3 import U3Gate
@@ -17,7 +17,7 @@
 class ToU3Pass(BasePass):
     """Converts single-qubit general unitary gates to U3 Gates."""
 
-    async 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('Converting single-qubit general gates to U3Gates.')
         for cycle, op in circuit.operations_with_cycles():
diff --git a/bqskit/passes/util/extend.py b/bqskit/passes/util/extend.py
index db05a5dc2..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
 
-    async 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 466507857..2761f961d 100644
--- a/bqskit/passes/util/fill.py
+++ b/bqskit/passes/util/fill.py
@@ -2,11 +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.parameterized.u3 import U3Gate
+from bqskit.ir.gates.parameterized.u8 import U8Gate
+from bqskit.ir.gates.parameterized.unitary import VariableUnitaryGate
 
 
 _logger = logging.getLogger(__name__)
@@ -25,21 +27,27 @@ def __init__(self, success_threshold: float = 1e-10):
         """
         self.success_threshold = success_threshold
 
-    async 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)
+        target = data.target
 
         complete_circuit = Circuit(circuit.num_qudits, circuit.radixes)
 
-        if target.num_qudits == 1:
-            params = U3Gate.calc_params(target)
+        if target.num_qudits == 1 and circuit.radixes[0] == 2:
+            params = U3Gate.calc_params(circuit.get_unitary())
             complete_circuit.append_gate(U3Gate(), 0, params)
             circuit.become(complete_circuit)
             return
 
         for q in range(circuit.num_qudits):
-            complete_circuit.append_gate(U3Gate(), q)
+            radix = circuit.radixes[q]
+            if radix == 2:
+                complete_circuit.append_gate(U3Gate(), q)
+            elif radix == 3:
+                complete_circuit.append_gate(U8Gate(), q)
+            else:
+                complete_circuit.append_gate(VariableUnitaryGate(1), q)
 
         for op in circuit:
             if op.num_qudits == 1:
@@ -47,12 +55,21 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
 
             complete_circuit.append(op)
             for q in op.location:
-                complete_circuit.append_gate(U3Gate(), q)
+                radix = circuit.radixes[q]
+                if radix == 2:
+                    complete_circuit.append_gate(U3Gate(), q)
+                elif radix == 3:
+                    complete_circuit.append_gate(U8Gate(), q)
+                else:
+                    complete_circuit.append_gate(VariableUnitaryGate(1), q)
 
         dist = 1.0
         for i in range(10):
             complete_circuit.instantiate(target)
-            dist = complete_circuit.get_unitary().get_distance_from(target, 1)
+            dist = complete_circuit.get_unitary().get_distance_from(target, 1)  # type: ignore  # noqa
+            # TODO: State update
 
         if dist <= self.success_threshold:
             circuit.become(complete_circuit)
+        else:
+            _logger.warning('Unable to instantiate completed circuit.')
diff --git a/bqskit/passes/util/log.py b/bqskit/passes/util/log.py
index 271974a95..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
 
-    async 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
 
-    async 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 5bcc1db3c..33747492d 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
 
-    async 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)
+        data.seed = self.seed
diff --git a/bqskit/passes/util/record.py b/bqskit/passes/util/record.py
index 745cd97cc..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'
 
-    async 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 97aa7d62e..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.
     """
 
-    async 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 36c7e5cec..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
 
-    async 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/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..82226f592 100644
--- a/bqskit/qis/state/state.py
+++ b/bqskit/qis/state/state.py
@@ -188,6 +188,15 @@ def is_pure_state(V: Any, tol: float = 1e-8) -> TypeGuard[StateLike]:
 
         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 +239,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 +253,24 @@ def __eq__(self, other: object) -> bool:
 
         return NotImplemented
 
+    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/runtime/__init__.py b/bqskit/runtime/__init__.py
index f3a034e38..df16959a3 100644
--- a/bqskit/runtime/__init__.py
+++ b/bqskit/runtime/__init__.py
@@ -185,4 +185,5 @@ def get_runtime() -> RuntimeHandle:
 
 default_server_port = 7472
 default_manager_port = 7473
+default_worker_port = 7474
 __all__ = ['get_runtime']
diff --git a/bqskit/runtime/attached.py b/bqskit/runtime/attached.py
index c9490e00a..4b64d77f1 100644
--- a/bqskit/runtime/attached.py
+++ b/bqskit/runtime/attached.py
@@ -1,12 +1,14 @@
 """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
@@ -30,6 +32,7 @@ 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.
@@ -42,6 +45,10 @@ def __init__(
             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)
 
@@ -63,16 +70,26 @@ def __init__(
             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(*args: Any, **kwargs: Any) -> None:
+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(*args, **kwargs)
+    server = AttachedServer(num_workers, **kwargs)
 
     # Run the server
     server.run()
diff --git a/bqskit/runtime/base.py b/bqskit/runtime/base.py
index 355da0cf3..d154ca841 100644
--- a/bqskit/runtime/base.py
+++ b/bqskit/runtime/base.py
@@ -8,10 +8,10 @@
 import random
 import selectors
 import signal
+import socket
 import sys
 import time
 import traceback
-from multiprocessing import Pipe
 from multiprocessing import Process
 from multiprocessing.connection import Client
 from multiprocessing.connection import Connection
@@ -24,6 +24,7 @@
 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
@@ -117,7 +118,7 @@ def __init__(self) -> None:
         self.sel = selectors.DefaultSelector()
         """Used to efficiently idle and wake when communication is ready."""
 
-        self.terminate_hotline, p = Pipe()
+        p, self.terminate_hotline = socket.socketpair()
         self.sel.register(p, selectors.EVENT_READ, MessageDirection.SIGNAL)
         """Terminate hotline is used to unblock select while running."""
 
@@ -211,7 +212,11 @@ def connect_to_manager(
 
         raise RuntimeError(f'Manager connection refused at {ip}:{port}')
 
-    def spawn_workers(self, num_workers: int = -1) -> None:
+    def spawn_workers(
+        self,
+        num_workers: int = -1,
+        port: int = default_worker_port,
+    ) -> None:
         """
         Spawn worker processes.
 
@@ -219,6 +224,10 @@ def spawn_workers(self, num_workers: int = -1) -> None:
             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()
@@ -227,16 +236,35 @@ def spawn_workers(self, num_workers: int = -1) -> None:
         if self.lower_id_bound + num_workers >= self.upper_id_bound:
             raise RuntimeError('Insufficient id range for workers.')
 
+        # Create and start all worker processed
+        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):
-            p, q = Pipe()
-            args = (self.lower_id_bound + i, q)
-            proc = Process(target=start_worker, args=args)
-            self.employees.append(RuntimeEmployee(p, 1, proc))
-            self.conn_to_employee_dict[p] = self.employees[-1]
-            proc.start()
+            self.employees.append(temp_reorder[i])
 
+        # Register employee communication
         for i, employee in enumerate(self.employees):
-            assert employee.conn.recv() == ((RuntimeMessage.STARTED, None))
             self.sel.register(
                 employee.conn,
                 selectors.EVENT_READ,
@@ -251,7 +279,8 @@ def spawn_workers(self, num_workers: int = -1) -> None:
 
     def listen_once(self, port: int) -> Connection:
         """Listen on `port` for a connection and return on first one."""
-        listener = Listener(('0.0.0.0', port))
+        family = 'AF_INET' if sys.platform == 'win32' else None
+        listener = Listener(('0.0.0.0', port), family)
         conn = listener.accept()
         listener.close()
         return conn
@@ -404,7 +433,11 @@ def assign_tasks(
 
         # Sort the employees by how many tasks they have
         ntasks = sorted([
-            (e.num_tasks, random.random(), i)  # Random value for tie breaker
+            (
+                e.num_tasks + len(assignments[i]),  # Consider idle assignments
+                random.random(),  # Random value for tie breaker
+                i,
+            )
             for i, e in enumerate(self.employees)
         ])
 
@@ -511,3 +544,18 @@ def parse_ipports(ipports_str: Sequence[str]) -> list[tuple[str, int]]:
 
             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
index 9b5ffc384..80c44e7d8 100644
--- a/bqskit/runtime/detached.py
+++ b/bqskit/runtime/detached.py
@@ -21,6 +21,7 @@
 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
@@ -403,6 +404,11 @@ def start_server() -> None:
         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)
 
@@ -411,6 +417,10 @@ def start_server() -> None:
     _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)
 
diff --git a/bqskit/runtime/manager.py b/bqskit/runtime/manager.py
index 9473e31d2..6155a5961 100644
--- a/bqskit/runtime/manager.py
+++ b/bqskit/runtime/manager.py
@@ -12,7 +12,9 @@
 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
@@ -38,6 +40,7 @@ def __init__(
         port: int = default_manager_port,
         num_workers: int = -1,
         ipports: list[tuple[str, int]] | None = None,
+        worker_port: int = default_worker_port,
     ) -> None:
         """
         Create a manager instance in one of two ways:
@@ -67,6 +70,10 @@ def __init__(
 
             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.
         """
         super().__init__()
 
@@ -86,7 +93,7 @@ def __init__(
 
         # Case 1: spawn and manage workers
         if ipports is None:
-            self.spawn_workers(num_workers)
+            self.spawn_workers(num_workers, worker_port)
 
         # Case 2: Connect to managers at ipports
         else:
@@ -263,12 +270,23 @@ def start_manager() -> None:
         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(
         '--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()
 
     # If ips and ports were provided parse them
@@ -279,8 +297,12 @@ def start_manager() -> None:
     _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 manager
-    manager = Manager(args.port, args.num_workers, ipports)
+    manager = Manager(args.port, args.num_workers, ipports, args.worker_port)
 
     # Start the manager
     manager.run()
diff --git a/bqskit/runtime/worker.py b/bqskit/runtime/worker.py
index 556d64eb9..af90635a7 100644
--- a/bqskit/runtime/worker.py
+++ b/bqskit/runtime/worker.py
@@ -4,11 +4,13 @@
 import logging
 import signal
 import sys
+import time
 import traceback
+from collections import OrderedDict
 from dataclasses import dataclass
+from multiprocessing.connection import Client
 from multiprocessing.connection import Connection
 from multiprocessing.connection import wait
-from queue import Queue
 from typing import Any
 from typing import Callable
 from typing import cast
@@ -21,6 +23,33 @@
 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:
     """
@@ -154,7 +183,7 @@ def __init__(self, id: int, conn: Connection) -> None:
         self._delayed_tasks: list[RuntimeTask] = []
         """Store all delayed tasks in LIFO order."""
 
-        self._ready_task_ids: Queue[RuntimeAddress] = Queue()
+        self._ready_task_ids: WorkerQueue = WorkerQueue()
         """Tasks queued up for execution."""
 
         self._cancelled_task_ids: set[RuntimeAddress] = set()
@@ -188,7 +217,7 @@ def record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
         logging.setLogRecordFactory(record_factory)
 
         # Communicate that this worker is ready
-        self._conn.send((RuntimeMessage.STARTED, None))
+        self._conn.send((RuntimeMessage.STARTED, self._id))
 
     def _loop(self) -> None:
         """Main worker event loop."""
@@ -255,6 +284,7 @@ def _add_task(self, task: RuntimeTask) -> None:
     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
@@ -553,7 +583,7 @@ async def next(self, future: RuntimeFuture) -> list[tuple[int, Any]]:
 _worker = None
 
 
-def start_worker(*args: Any, **kwargs: Any) -> None:
+def start_worker(id: int, port: int) -> None:
     """Start this process's worker."""
     # Ignore interrupt signals on workers, boss will handle it for us
     signal.signal(signal.SIGINT, signal.SIG_IGN)
@@ -565,8 +595,24 @@ def start_worker(*args: Any, **kwargs: Any) -> None:
         logger.handlers.clear()
     logging.Logger.manager.loggerDict = {}
 
+    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.')
+
     global _worker
-    _worker = Worker(*args, **kwargs)
+    _worker = Worker(id, conn)
     _worker._loop()
 
 
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/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/setup.cfg b/setup.cfg
index 97091ebee..3a97bce05 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = bqskit
-version = 1.0.4
+version = 1.1.0a1
 description = Berkeley Quantum Synthesis Toolkit
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -38,7 +38,7 @@ project_urls =
 [options]
 packages = find:
 install_requires =
-    bqskitrs>=0.3.0
+    bqskitrs>=0.4.0rc3
     lark-parser
     numpy>=1.22.0
     scipy>=1.8.0
@@ -47,7 +47,7 @@ python_requires = >=3.8, <4
 
 [options.packages.find]
 exclude =
-    tests
+    tests*
     examples
 
 [options.entry_points]
@@ -57,7 +57,7 @@ console_scripts =
 
 [options.extras_require]
 dev =
-    hypothesis
+    hypothesis[zoneinfo]
     mypy
     pre-commit
     psutil


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6f5c0f9d6..3e2c1ab59 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]
@@ -21,7 +22,7 @@ jobs:
     - 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
+      run: python -m pip install psutil pytest 'hypothesis[zoneinfo]' qiskit pytket qutip cirq
     - name: Install BQSKit
       env:
         SYSTEM_VERSION_COMPAT: 0
diff --git a/tests/bqskitrs/__init__.py b/tests/bqskitrs/__init__.py
new file mode 100644
index 000000000..e69de29bb
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/test_prep.py b/tests/compiler/compile/test_prep.py
new file mode 100644
index 000000000..b685efbc1
--- /dev/null
+++ b/tests/compiler/compile/test_prep.py
@@ -0,0 +1,103 @@
+from __future__ import annotations
+
+from bqskit import compile
+from bqskit.ir 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() -> None:
+    state = StateVector.random(3)
+    out_circuit = compile(state)
+    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
+
+
+def test_state_map() -> 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)
+    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_synthesis.py b/tests/compiler/compile/test_synthesis.py
index 49897a163..dec43ec03 100644
--- a/tests/compiler/compile/test_synthesis.py
+++ b/tests/compiler/compile/test_synthesis.py
@@ -4,7 +4,7 @@
 
 from bqskit import compile
 from bqskit import MachineModel
-from bqskit.compiler.machine import default_gate_set
+from bqskit.compiler.machine import default_qubit_gate_set
 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
@@ -25,7 +25,7 @@
         {U3Gate()},
         {PhasedXZGate()},
         {U1qGate(), XGate()},
-        default_gate_set,
+        default_qubit_gate_set,
         rigetti_gate_set,
         honeywell_gate_set,
         google_gate_set,
@@ -47,13 +47,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,
+        default_qubit_gate_set,
         rigetti_gate_set,
         honeywell_gate_set,
         google_gate_set,
@@ -72,12 +72,12 @@ 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
+    assert out_circuit.get_unitary().get_distance_from(tq_utry, 1) < 1e-8
 
 
 @pytest.mark.parametrize(
     'gate_set', [
-        default_gate_set,
+        default_qubit_gate_set,
         rigetti_gate_set,
         honeywell_gate_set,
         {IToffoliGate(), U3Gate()},
@@ -97,7 +97,7 @@ 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
+    assert out_circuit.get_unitary().get_distance_from(utry, 1) < 1e-8
 
 
 def test_fail_on_larger_max_synthesis_size() -> None:
@@ -117,6 +117,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 86fdf2e1a..1087a1905 100644
--- a/tests/compiler/test_compiler.py
+++ b/tests/compiler/test_compiler.py
@@ -4,22 +4,17 @@
 import logging
 import traceback
 from io import StringIO
-from typing import Any
 
 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
 
 
 class ErrorPass(BasePass):
-    async def run(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any] = {},
-    ) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         raise RuntimeError('TestError')
 
 
@@ -27,30 +22,35 @@ class LogPass(BasePass):
     async def run(
         self,
         circuit: Circuit,
-        data: dict[str, Any] = {},
+        data: PassData,
     ) -> None:
         logging.getLogger('bqskit.dummy').debug('Test.')
 
 
 def test_errors_raised_locally() -> None:
-    task = CompilationTask(Circuit(1), [ErrorPass()])
     with pytest.raises(RuntimeError) as exc_info:
         with Compiler() as compiler:
-            compiler.compile(task)
+            compiler.compile(Circuit(1), [ErrorPass()])
 
     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:
-    task = CompilationTask(Circuit(1), [LogPass()])
     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(task)
+        compiler.compile(Circuit(1), [LogPass()])
     assert 'Test.' in handler.stream.getvalue()
     logger.removeHandler(handler)
     logger.setLevel(logging.WARNING)
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 6556ae6ad..1d6c97706 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -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)
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 e2ede2844..f68bb557b 100644
--- a/tests/ext/test_cirq.py
+++ b/tests/ext/test_cirq.py
@@ -1,5 +1,9 @@
 # flake8: noqa
 from __future__ import annotations
+
+import pytest
+pytest.importorskip('cirq')
+
 from bqskit.qis import UnitaryMatrix
 from bqskit.ir.gates import HGate
 from bqskit.ir.gates import CNOTGate
@@ -11,9 +15,6 @@
 from cirq import Circuit as QuantumCircuit
 import cirq
 
-import pytest
-pytest.importorskip('cirq')
-
 
 class TestTranslate:
     @pytest.fixture
diff --git a/tests/ext/test_pytket.py b/tests/ext/test_pytket.py
index f3d730c98..1be35559d 100644
--- a/tests/ext/test_pytket.py
+++ b/tests/ext/test_pytket.py
@@ -1,5 +1,10 @@
 # flake8: noqa
 from __future__ import annotations
+
+import pytest
+pytest.importorskip('pytket')
+pytest.importorskip('pytket.extensions.qiskit')
+
 from bqskit.qis import UnitaryMatrix
 from bqskit.ir.gates import U3Gate
 from bqskit.ir.gates import CNOTGate
@@ -13,9 +18,6 @@
 from pytket import OpType
 import numpy as np
 
-import pytest
-pytest.importorskip('pytket')
-
 
 class TestTranslate:
     @pytest.fixture
@@ -65,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()
diff --git a/tests/ext/test_qiskit.py b/tests/ext/test_qiskit.py
index 38770dee8..b96949e09 100644
--- a/tests/ext/test_qiskit.py
+++ b/tests/ext/test_qiskit.py
@@ -1,20 +1,20 @@
 # flake8: noqa
 from __future__ import annotations
+
+import pytest
+pytest.importorskip('qiskit')
+
 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.compiler import Compiler
 from bqskit.compiler.compile import compile
 from qiskit import transpile
 from qiskit import QuantumCircuit
 import qiskit.quantum_info as qi
 
-import pytest
-pytest.importorskip('qiskit')
-
 
 class TestTranslate:
     @pytest.fixture
@@ -50,7 +50,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)
diff --git a/tests/ext/test_qutip.py b/tests/ext/test_qutip.py
index 872931c5b..5851a28e5 100644
--- a/tests/ext/test_qutip.py
+++ b/tests/ext/test_qutip.py
@@ -1,5 +1,9 @@
 # flake8: noqa
 from __future__ import annotations
+
+import pytest
+pytest.importorskip('qutip')
+
 from bqskit.qis import UnitaryMatrix
 from bqskit.ir.gates import U3Gate
 from bqskit.ir.gates import CNOTGate
@@ -8,11 +12,8 @@
 from bqskit.ext import bqskit_to_qutip
 from bqskit.compiler.compiler import Compiler
 from bqskit.compiler.compile import compile
-from qutip import QubitCircuit
-from qutip import CircuitSimulator
-
-import pytest
-pytest.importorskip('qutip')
+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/lang/__init__.py b/tests/ir/lang/__init__.py
new file mode 100644
index 000000000..e69de29bb
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 53f3cf3b1..19a71cd87 100644
--- a/tests/passes/control/test_dothendecide.py
+++ b/tests/passes/control/test_dothendecide.py
@@ -1,8 +1,8 @@
 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
@@ -17,32 +17,28 @@ def reject_always(c1: Circuit, c2: Circuit) -> bool:
 
 
 class AddCNOTPass(BasePass):
-    async def run(
-        self,
-        circuit: Circuit,
-        data: dict[str, Any] = {},
-    ) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         circuit.append_gate(CNOTGate(), (0, 1))
 
 
-def test_dothendecide_accept() -> None:
+def test_dothendecide_accept(compiler: Compiler) -> None:
     circuit = Circuit(2)
     dtd_pass = DoThenDecide(accept_always, AddCNOTPass())
-    circuit.perform(dtd_pass)
-    assert circuit.num_operations == 1
-    assert circuit.gate_set == {CNOTGate()}
+    out_circuit = compiler.compile(circuit, dtd_pass)
+    assert out_circuit.num_operations == 1
+    assert out_circuit.gate_set == {CNOTGate()}
 
 
-def test_dothendecide_reject() -> None:
+def test_dothendecide_reject(compiler: Compiler) -> None:
     circuit = Circuit(2)
     dtd_pass = DoThenDecide(reject_always, AddCNOTPass())
-    circuit.perform(dtd_pass)
-    assert circuit.num_operations == 0
+    out_circuit = compiler.compile(circuit, dtd_pass)
+    assert out_circuit.num_operations == 0
 
 
-def test_dothendecide_list() -> None:
+def test_dothendecide_list(compiler: Compiler) -> None:
     circuit = Circuit(2)
     dtd_pass = DoThenDecide(accept_always, [AddCNOTPass(), AddCNOTPass()])
-    circuit.perform(dtd_pass)
-    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
index e5b6a6d0b..2d69b5e70 100644
--- a/tests/passes/control/test_paralleldo.py
+++ b/tests/passes/control/test_paralleldo.py
@@ -1,6 +1,72 @@
-# No sequence, one sequence, many sequence
-# Less than works properly
-# Less than accepts lambdas
-# Pick first works properly
-# Circuit/data updated correctly
 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/partitioning/__init__.py b/tests/passes/partitioning/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/partitioning/test_greedy.py b/tests/passes/partitioning/test_greedy.py
deleted file mode 100644
index f06274fcb..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()
-        r6_qudit_circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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()
-        circuit.perform(GreedyPartitioner(3))
-
-        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 9547d0e96..f9c6a8e46 100644
--- a/tests/passes/partitioning/test_parts.py
+++ b/tests/passes/partitioning/test_parts.py
@@ -11,8 +11,14 @@
 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:
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/rules/__init__.py b/tests/passes/rules/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/rules/test_zxzxz.py b/tests/passes/rules/test_zxzxz.py
index 7dce7f315..6de3721aa 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.compiler.compiler import Compiler
 from bqskit.ir 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)
-        test_circuit.perform(ZXZXZDecomposition())
-        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/synthesis/__init__.py b/tests/passes/synthesis/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/passes/synthesis/test_qfast.py b/tests/passes/synthesis/test_qfast.py
index adc6e1713..7fb06ee86 100644
--- a/tests/passes/synthesis/test_qfast.py
+++ b/tests/passes/synthesis/test_qfast.py
@@ -1,7 +1,5 @@
 from __future__ import annotations
 
-import pytest
-
 from bqskit.compiler import Compiler
 from bqskit.ir.circuit import Circuit
 from bqskit.ir.gates import CircuitGate
@@ -46,7 +44,6 @@ def test_3_qubit_with_compiler(self) -> None:
             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))
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_random.py b/tests/passes/util/test_random.py
index caaccdd03..f01c35659 100644
--- a/tests/passes/util/test_random.py
+++ b/tests/passes/util/test_random.py
@@ -1,9 +1,6 @@
 from __future__ import annotations
 
-import pytest
-
 from bqskit.compiler import Compiler
-from bqskit.compiler.task import CompilationTask
 from bqskit.ir import Circuit
 from bqskit.ir.gates import CCXGate
 from bqskit.passes import QSearchSynthesisPass
@@ -11,43 +8,26 @@
 from bqskit.qis import UnitaryMatrix
 
 
-@pytest.mark.xfail(reason='Bug #107')
 def test_two_qubit_syn_with_seed(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)
+    workflow = [SetRandomSeedPass(0), QSearchSynthesisPass()]
+    circ1 = compiler.compile(circ1, workflow)
+    circ2 = compiler.compile(circ2, workflow)
 
     for op1, op2 in zip(circ1, circ2):
         assert op1 == op2
 
 
-@pytest.mark.xfail(reason='Bug #107')
 def test_three_qubit_syn_with_seed(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/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
index 59c417290..f3a5a77f2 100644
--- a/tests/runtime/conftest.py
+++ b/tests/runtime/conftest.py
@@ -2,6 +2,7 @@
 
 import signal
 import subprocess
+import sys
 from typing import Any
 from typing import Iterator
 
@@ -9,38 +10,21 @@
 
 from bqskit.compiler import Compiler
 
-
-@pytest.fixture
-def detached_compiler() -> Iterator[Compiler]:
-    manager = subprocess.Popen(['bqskit-manager', '-n1'])
-    server = subprocess.Popen(['bqskit-server', 'localhost'])
-    compiler = Compiler('localhost')
-    yield compiler
-    compiler.close()
-    server.send_signal(signal.SIGINT)
-    server.wait()
-    manager.wait()
-
-
-@pytest.fixture
-def attached_compiler() -> Iterator[Compiler]:
-    compiler = Compiler()
-    yield compiler
-    compiler.close()
+params = ['attached', 'detached'] if sys.platform != 'win32' else ['attached']
 
 
-@pytest.fixture(params=['attached', 'detached'])
+@pytest.fixture(params=params)
 def server_compiler(request: Any) -> Iterator[Compiler]:
-    if request.param[0] == 'detached':
-        manager = subprocess.Popen(['bqskit-manager', '-n1'])
-        server = subprocess.Popen(['bqskit-server', 'localhost'])
+    if request.param == 'detached':
+        manager = subprocess.Popen(['bqskit-manager', '-n2', '-i'])
+        server = subprocess.Popen(['bqskit-server', 'localhost', '-i'])
         compiler = Compiler('localhost')
     else:
-        compiler = Compiler()
+        compiler = Compiler(num_workers=2, runtime_log_level=1)
 
     yield compiler
 
-    if request.param[0] == 'detached':
+    if request.param == 'detached':
         compiler.close()
         server.send_signal(signal.SIGINT)
         server.wait()
diff --git a/tests/runtime/test_attached.py b/tests/runtime/test_attached.py
index 81834725f..12f0df4df 100644
--- a/tests/runtime/test_attached.py
+++ b/tests/runtime/test_attached.py
@@ -4,13 +4,15 @@
 import os
 import signal
 import subprocess
-from typing import Any
+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 import Circuit
 from bqskit.runtime import get_runtime
 
@@ -21,6 +23,8 @@ def test_startup_shutdown_transparently(num_workers: int) -> None:
     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
 
@@ -30,6 +34,8 @@ 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
 
@@ -39,6 +45,8 @@ 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
 
@@ -52,6 +60,10 @@ def test_create_workers(num_workers: int) -> None:
 
 
 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()) == 1
@@ -69,6 +81,11 @@ def test_double_close() -> 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',
@@ -79,16 +96,23 @@ def test_interrupt_handling() -> None:
         time.sleep(10)
         """,
     ])
-    p.send_signal(signal.SIGINT)
-    out_num_childs = len(psutil.Process(os.getpid()).children(recursive=True))
-    assert in_num_childs + 1 == out_num_childs
+    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: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         raise RuntimeError('Boo!')
 
 
@@ -97,12 +121,12 @@ def raise_error() -> None:
 
 
 class TestNestedErrorPass(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    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: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         exit()
 
 
@@ -112,6 +136,8 @@ def test_errors_shutdown_system() -> None:
         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:
@@ -124,6 +150,8 @@ def test_errors_shutdown_system_nested() -> None:
         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:
@@ -136,6 +164,8 @@ def test_worker_fail_shutdown_system() -> None:
         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:
diff --git a/tests/runtime/test_cancel.py b/tests/runtime/test_cancel.py
index cc9891c43..6257b47f4 100644
--- a/tests/runtime/test_cancel.py
+++ b/tests/runtime/test_cancel.py
@@ -7,6 +7,7 @@
 
 from bqskit.compiler import BasePass
 from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
 from bqskit.ir import Circuit
 from bqskit.runtime import get_runtime
 
@@ -20,13 +21,13 @@ def sleep1() -> None:
 
 
 class TestCancelTask(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    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: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         future = get_runtime().submit(sleep1)
         get_runtime().cancel(future)
         await future
diff --git a/tests/runtime/test_execution.py b/tests/runtime/test_execution.py
index 0d6d78c03..0d44ba906 100644
--- a/tests/runtime/test_execution.py
+++ b/tests/runtime/test_execution.py
@@ -1,10 +1,9 @@
 """Checks the runtime's ability and correctness when executing tasks."""
 from __future__ import annotations
 
-from typing import Any
-
 from bqskit.compiler import BasePass
 from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
 from bqskit.ir import Circuit
 from bqskit.ir.gates import HGate
 from bqskit.ir.gates import XGate
@@ -30,26 +29,26 @@ async def add_xgate_to_circuit(c: Circuit) -> Circuit:
 
 
 class TestPass1(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    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: dict[str, Any] = {}) -> None:
+    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: dict[str, Any] = {}) -> None:
+    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: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         out = await get_runtime().submit(add_xgate_to_circuit, circuit)
         circuit.become(out)
 
diff --git a/tests/runtime/test_future.py b/tests/runtime/test_future.py
index ca7a0500d..2d31bf6e1 100644
--- a/tests/runtime/test_future.py
+++ b/tests/runtime/test_future.py
@@ -7,6 +7,7 @@
 
 from bqskit.compiler import BasePass
 from bqskit.compiler import Compiler
+from bqskit.compiler.passdata import PassData
 from bqskit.ir import Circuit
 from bqskit.runtime import get_runtime
 
@@ -20,7 +21,7 @@ def sleep1() -> None:
 
 
 class TestPassFutureDone(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         future = get_runtime().submit(sleep1)
         assert not future._done
         await future
@@ -28,7 +29,7 @@ async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
 
 
 class TestPassFutureCannotSend(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    async def run(self, circuit: Circuit, data: PassData) -> None:
         future = get_runtime().submit(sleep1)
         get_runtime().submit(iden, future)
 
diff --git a/tests/runtime/test_logging.py b/tests/runtime/test_logging.py
index 7241ef0c8..a7f31584d 100644
--- a/tests/runtime/test_logging.py
+++ b/tests/runtime/test_logging.py
@@ -3,13 +3,13 @@
 
 import logging
 from io import StringIO
-from typing import Any
 
 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 import Circuit
 from bqskit.runtime import get_runtime
 
@@ -32,21 +32,21 @@ async def nested3() -> None:
 
 
 class TestInfoPass(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
+    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: dict[str, Any] = {}) -> None:
+    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: dict[str, Any] = {}) -> None:
+    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)
diff --git a/tests/runtime/test_next.py b/tests/runtime/test_next.py
index e8ebafd70..254f65064 100644
--- a/tests/runtime/test_next.py
+++ b/tests/runtime/test_next.py
@@ -2,12 +2,12 @@
 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 import Circuit
 from bqskit.runtime import get_runtime
 
@@ -17,73 +17,65 @@ def sleepi(i: int) -> int:
     return i
 
 
-class TestNextTask(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-        future = get_runtime().map(sleepi, [3, 4, 1, 2])
+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 int_id[1] == max(seen) + 1
+            assert not int_id[1] in seen
             seen.append(int_id[1])
 
 
-class TestDoubleNextTask(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-        future = get_runtime().map(sleepi, [3, 4, 1, 2])
+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 int_id[1] == max(seen) + 1
+            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 int_id[1] == max(seen) + 1
+            assert not int_id[1] in seen
             seen.append(int_id[1])
 
 
-class TestAwaitAfterNextTask(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-        future = get_runtime().map(sleepi, [3, 4, 1, 2])
+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) == [3, 4, 1, 2]
+        assert (await future) == sleep_times
 
 
-class TestAwaitAfterDoubleNextTask(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-        future = get_runtime().map(sleepi, [3, 4, 1, 2])
-        _ = await get_runtime().next(future)
-        _ = await get_runtime().next(future)
-        assert (await future) == [3, 4, 1, 2]
-
-
-class TestNextOnCompleteTask(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-        future = get_runtime().map(sleepi, [3, 4, 1, 2])
+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 TestNextAfterSleepTask(BasePass):
-    async def run(self, circuit: Circuit, data: dict[str, Any] = {}) -> None:
-        future = get_runtime().map(sleepi, [1, 2])
-        time.sleep(1.5)
+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
+        assert int_ids[0] == (0, .1)  # the 0th index 1 element
 
 
 @pytest.mark.parametrize(
     'test_pass', [
-        TestNextTask(),
-        TestDoubleNextTask(),
-        TestAwaitAfterNextTask(),
-        TestAwaitAfterDoubleNextTask(),
-        TestNextOnCompleteTask(),
-        TestNextAfterSleepTask(),
+        TestNoDuplicateResult(),
+        TestNoDuplicateResultsInTwoNexts(),
+        TestFutureAwaitGivesAllResultsAfterNext(),
+        TestNextOnCompleteTaskRaisesError(),
+        TestNextNeverlosesResults(),
     ],
 )
 def test_next_pass(server_compiler: Compiler, test_pass: BasePass) -> None: