Repository: y0-causal-inference/y0

Issue:
Counterfactual Transportability ID Algorithm
This is like ID* to general transportability (#122)

> Counterfactual Transportability: A Formal Approach
J. Correa, S. Lee, E. Bareinboim.
ICML-22. In Proceedings of the 39th International Conference on Machine Learning.
Columbia CausalAI Laboratory, Technical Report (R-82), May, 2022. [[pdf](https://causalai.net/r82.pdf), [bib](https://causalai.net/references/r82.bib)]

Discussion:
cthoyt: @rjc955 there are 17 todos and 5 fixmes in the code right now, could you please take a look at those? There's also a huge amount of commented out code, can you please delete anything that isn't going to get used again?
cthoyt: @djinnome there were some failing tests that seemed to have been commented out, these need to be looked over before merge

Patch:
diff --git a/docs/source/index.rst b/docs/source/index.rst
index f7a27e3..ae7cbe3 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -15,6 +15,7 @@ y0 |release| Documentation
    mutation
    conditional_independence
    identification
+   tian_id
    transport
    lvdags
    struct
diff --git a/docs/source/tian_id.rst b/docs/source/tian_id.rst
new file mode 100644
index 0000000..5fc2e59
--- /dev/null
+++ b/docs/source/tian_id.rst
@@ -0,0 +1,4 @@
+Tian Identification
+===================
+.. automodule:: y0.algorithm.tian_id
+    :members:
diff --git a/docs/source/transport.rst b/docs/source/transport.rst
index 996a6b0..16dfdce 100644
--- a/docs/source/transport.rst
+++ b/docs/source/transport.rst
@@ -2,3 +2,8 @@ Transport
 =========
 .. automodule:: y0.algorithm.transport
     :members:
+
+Counterfactual Transport
+========================
+.. automodule:: y0.algorithm.counterfactual_transport
+    :members:
diff --git a/notebooks/Counterfactual Transportability.ipynb b/notebooks/Counterfactual Transportability.ipynb
new file mode 100644
index 0000000..b3526dc
--- /dev/null
+++ b/notebooks/Counterfactual Transportability.ipynb
@@ -0,0 +1,1646 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "74d52a6f-e942-40a4-a3b3-fda1a72e90aa",
+   "metadata": {},
+   "source": [
+    "# Counterfactual Transportability\n",
+    "\n",
+    "This notebook is a demonstration of algorithms from Correa, Lee, and Bareinboim's 2022 paper, [Counterfactual Transportability: A Formal Approach](https://proceedings.mlr.press/v162/correa22a/correa22a.pdf)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "2006c3a7-bb08-4f5d-9015-fbea48ce15aa",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from y0.algorithm.counterfactual_transport.api import (\n",
+    "    unconditional_cft,\n",
+    "    conditional_cft,\n",
+    "    CFTDomain,\n",
+    ")\n",
+    "from y0.algorithm.transport import transport_variable\n",
+    "from y0.dsl import (\n",
+    "    PP,\n",
+    "    TARGET_DOMAIN,\n",
+    "    X1,\n",
+    "    R,\n",
+    "    P,\n",
+    "    Pi1,\n",
+    "    Pi2,\n",
+    "    W,\n",
+    "    X,\n",
+    "    Y,\n",
+    "    Z,\n",
+    "    Variable,\n",
+    ")\n",
+    "from y0.graph import NxMixedGraph\n",
+    "from y0.algorithm.identify import identify_outcomes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "97081cb0-1015-4eb7-a25b-a35f27d172ce",
+   "metadata": {},
+   "source": [
+    "## Motivating example\n",
+    "\n",
+    "Suppose in a study of vaccine efficacy we're interested in the effect of COVID vaccination on the number of days that a person who gets COVID reports Infected. We might imagine a graph like the following:\n",
+    "\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "f9a895e1-3be5-4047-b473-6fb1d5ae4eb3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "figure_2a_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Z, X),\n",
+    "        (Z, Y),\n",
+    "        (X, Y),\n",
+    "        (X, W),\n",
+    "        (W, Y),\n",
+    "    ],\n",
+    "    undirected=[(Z, X), (W, Y)],\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "b57d5b4c-3d77-44d8-ad30-08f6bd6c529a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from y0.algorithm.counterfactual_transport.ancestor_utils import (\n",
+    "    _compute_ancestral_components_from_ancestral_sets,\n",
+    "    _get_ancestral_set_after_intervening_on_conditioned_variables,\n",
+    "    _get_conditioned_variables_in_ancestral_set,\n",
+    "    _merge_frozen_sets_linked_by_bidirectional_edges,\n",
+    "    _merge_frozen_sets_with_common_vertices,\n",
+    "    _minimize_set,\n",
+    "    get_ancestors_of_counterfactual,\n",
+    "    get_ancestral_components,\n",
+    "    get_base_variables,\n",
+    ")\n",
+    "from y0.algorithm.counterfactual_transport.api import (\n",
+    "    _any_inconsistent_intervention_values,\n",
+    "    _any_variable_values_inconsistent_with_interventions,\n",
+    "    _any_variables_with_inconsistent_values,\n",
+    "    _counterfactual_factor_is_inconsistent,\n",
+    "    _initialize_conditional_transportability_data_structures,\n",
+    "    _no_intervention_variables_in_domain,\n",
+    "    _no_transportability_nodes_in_domain,\n",
+    "    _reduce_reflexive_counterfactual_variables_to_interventions,\n",
+    "    _remove_repeated_variables_and_values,\n",
+    "    _remove_transportability_vertices,\n",
+    "    _split_event_by_reflexivity,\n",
+    "    _transport_conditional_counterfactual_query_line_2,\n",
+    "    _transport_conditional_counterfactual_query_line_4,\n",
+    "    _transport_unconditional_counterfactual_query_line_2,\n",
+    "    _valid_topo_list,\n",
+    "    _validate_transport_conditional_counterfactual_query_input,\n",
+    "    _validate_transport_conditional_counterfactual_query_line_4_output,\n",
+    "    _validate_transport_unconditional_counterfactual_query_input,\n",
+    "    convert_to_counterfactual_factor_form,\n",
+    "    counterfactual_factors_are_transportable,\n",
+    "    do_counterfactual_factor_factorization,\n",
+    "    get_counterfactual_factors,\n",
+    "    get_counterfactual_factors_retaining_variable_values,\n",
+    "    is_counterfactual_factor_form,\n",
+    "    minimize_event,\n",
+    "    same_district,\n",
+    "    simplify,\n",
+    "    transport_conditional_counterfactual_query,\n",
+    "    transport_district_intervening_on_parents,\n",
+    "    transport_unconditional_counterfactual_query,\n",
+    ")\n",
+    "from y0.algorithm.transport import transport_variable\n",
+    "from y0.dsl import (\n",
+    "    PP,\n",
+    "    TARGET_DOMAIN,\n",
+    "    W1,\n",
+    "    W2,\n",
+    "    W3,\n",
+    "    W4,\n",
+    "    W5,\n",
+    "    X1,\n",
+    "    X2,\n",
+    "    CounterfactualVariable,\n",
+    "    Fraction,\n",
+    "    Intervention,\n",
+    "    One,\n",
+    "    P,\n",
+    "    Pi1,\n",
+    "    Pi2,\n",
+    "    Product,\n",
+    "    R,\n",
+    "    Sum,\n",
+    "    Variable,\n",
+    "    W,\n",
+    "    X,\n",
+    "    Y,\n",
+    "    Z,\n",
+    "    Zero,\n",
+    ")\n",
+    "from y0.graph import NxMixedGraph\n",
+    "import logging"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "d0e8dbe1-d24e-4928-b5cc-c966b1ab4389",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "conditions = [(Y @ -X, +Y)]\n",
+    "outcomes = [(Y @ -W, -Y)]\n",
+    "domain_data = [(set(), PP[TARGET_DOMAIN](W, X, Y, Z))]\n",
+    "domain_graphs = [\n",
+    "    (\n",
+    "        figure_2a_graph,\n",
+    "        figure_2a_graph.topological_sort(),\n",
+    "    ),\n",
+    "]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "72b65848-4302-4e31-8bfd-95801d907405",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "logger = logging.getLogger(__name__)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "8caa14ff-3d80-4060-a0c0-794a22f73a77",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "test_1 = transport_conditional_counterfactual_query(\n",
+    "    outcomes=outcomes,\n",
+    "    conditions=conditions,\n",
+    "    target_domain_graph=figure_2a_graph,\n",
+    "    domain_graphs=domain_graphs,\n",
+    "    domain_data=domain_data,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "38cab0f5-3b95-4e5d-a296-e2082f97dd95",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "ConditionalCFTResult(expression=((Sum[W, X, Z](PP[pi*](W | X, Z) * PP[pi*](Y | X, W, Z) * PP[pi*](X | Z) * PP[pi*](Z)) / Sum[W, X, Z](PP[pi*](W | X, Z) * PP[pi*](Y | X, W, Z) * PP[pi*](X | Z) * PP[pi*](Z)))), event=[(Y, -Y), (Y, +Y)])\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(str(test_1))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "52033878-d586-408c-b243-a054be1c5b35",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(Y^{-}, Y^{+})$"
+      ],
+      "text/plain": [
+       "P(-Y, +Y)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\frac{\\sum\\limits_{W, X, Z} P^{\\pi^\\ast}(W | X, Z) P^{\\pi^\\ast}(Y | X, W, Z) P^{\\pi^\\ast}(X | Z) P^{\\pi^\\ast}(Z)}{\\sum\\limits_{W, X, Z} P^{\\pi^\\ast}(W | X, Z) P^{\\pi^\\ast}(Y | X, W, Z) P^{\\pi^\\ast}(X | Z) P^{\\pi^\\ast}(Z)}$"
+      ],
+      "text/plain": [
+       "((Sum[W, X, Z](PP[pi*](W | X, Z) * PP[pi*](Y | X, W, Z) * PP[pi*](X | Z) * PP[pi*](Z)) / Sum[W, X, Z](PP[pi*](W | X, Z) * PP[pi*](Y | X, W, Z) * PP[pi*](X | Z) * PP[pi*](Z))))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "test_1.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "id": "2478e250-5e44-495c-917f-4694ef036f11",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Case 2\n",
+    "conditions_2 = [(Y @ -W, -Y)]\n",
+    "outcomes_2 = [(Y @ -Y, -Y)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "id": "d08712db-40ef-4f1a-bb8a-cdacb2361776",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "test_2 = transport_conditional_counterfactual_query(\n",
+    "    outcomes=outcomes_2,\n",
+    "    conditions=conditions_2,\n",
+    "    target_domain_graph=figure_2a_graph,\n",
+    "    domain_graphs=domain_graphs,\n",
+    "    domain_data=domain_data,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "id": "c9a4e0bc-1d53-4c19-97e7-560ac13a8196",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(Y^{-})$"
+      ],
+      "text/plain": [
+       "P(-Y)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\frac{\\sum\\limits_{W, X, Z} P^{\\pi^\\ast}(W | X, Z) P^{\\pi^\\ast}(Y | X, W, Z) P^{\\pi^\\ast}(X | Z) P^{\\pi^\\ast}(Z)}{\\sum\\limits_{W, X, Z} P^{\\pi^\\ast}(W | X, Z) P^{\\pi^\\ast}(Y | X, W, Z) P^{\\pi^\\ast}(X | Z) P^{\\pi^\\ast}(Z)}$"
+      ],
+      "text/plain": [
+       "((Sum[W, X, Z](PP[pi*](W | X, Z) * PP[pi*](Y | X, W, Z) * PP[pi*](X | Z) * PP[pi*](Z)) / Sum[W, X, Z](PP[pi*](W | X, Z) * PP[pi*](Y | X, W, Z) * PP[pi*](X | Z) * PP[pi*](Z))))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "test_2.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "id": "0d2cad3b-4041-484f-af17-19532649846f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Case 3\n",
+    "conditions_3 = [(Y @ -Y, -Y)]\n",
+    "outcomes_3 = [(Y @ -W, -Y)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "id": "da12aecf-c14c-4ce2-acd4-b9f8f600569b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "test_3 = transport_conditional_counterfactual_query(\n",
+    "    outcomes=outcomes_3,\n",
+    "    conditions=conditions_3,\n",
+    "    target_domain_graph=figure_2a_graph,\n",
+    "    domain_graphs=domain_graphs,\n",
+    "    domain_data=domain_data,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "b4dc5fdc-638e-4f3f-ae9a-281371c4acc2",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(Y^{-})$"
+      ],
+      "text/plain": [
+       "P(-Y)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\frac{\\sum\\limits_{W, X, Z} P^{\\pi^\\ast}(W | X, Z) P^{\\pi^\\ast}(Y | X, W, Z) P^{\\pi^\\ast}(X | Z) P^{\\pi^\\ast}(Z)}{\\sum\\limits_{W, X, Z} P^{\\pi^\\ast}(W | X, Z) P^{\\pi^\\ast}(Y | X, W, Z) P^{\\pi^\\ast}(X | Z) P^{\\pi^\\ast}(Z)}$"
+      ],
+      "text/plain": [
+       "((Sum[W, X, Z](PP[pi*](W | X, Z) * PP[pi*](Y | X, W, Z) * PP[pi*](X | Z) * PP[pi*](Z)) / Sum[W, X, Z](PP[pi*](W | X, Z) * PP[pi*](Y | X, W, Z) * PP[pi*](X | Z) * PP[pi*](Z))))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "test_3.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "id": "b4815b37-fe4d-43ed-a7d1-d2732000f6e1",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABDf0lEQVR4nO3de1xUdf4/8NcAwoCCiCJeEM2xTEPkpmbbtqkloFlZlqmkBqLot68o6rZbqOCldU1Rt29KO4FXXC+1tWlAWlbfti2Rq+IlBZQRFEFhvA8InN8f/DjfmRS5zcyZ4byejwePx8yBc8777Ibz4nNVCIIggIiIiGTLRuoCiIiISFoMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMmcndQFERFKrrKxEVlYWMjMzkZOTg2vXrkGn06G6uhr29vZQKpXo2rUrfH19ERAQAH9/f3Tp0kXqsomMRiEIgiB1EURE5lRbW4vU1FQkJycjPT0dhYWFLb5G//79MXz4cEybNg0hISGwtbU1QaVE5sEwQESyUV5ejsTERCQkJKCoqMho1+3bty8iIyMRHh4Od3d3o12XyFwYBoio3dNoNHjvvfewb98+VFdXG3xPqVSKzf+BgYEICAiAl5cXlEol7O3tUV1dDZ1Oh6KiImRmZopfOTk50Ol0Bteyt7fH5MmTsWrVKnh5eZnzEYnahGGAiNotQRDwySefYNGiRbh586Z4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RUUF7O3tUVxcDHd3d6nLavfYMkBE1I64u7tj8uTJAOq3/Y2Ojpa4opaJjo4WtyuePHkyg4CZsGWAiKid0Wg08Pb2xs2bNwEAKSkpCAkJkbiqpqWkpIgDBZ2dnXHy5En06dNH4qrkgS0DRETtjJeXF9avXy++j4iIwPXr1yWsqGlarRazZ88W38fHxzMImBHDABFROzRr1iyMHTsWAFBSUoKoqChYakOwIAiIiopCSUkJACAoKAjh4eESVyUvDANERO2QQqGAWq2Gs7MzgPp9BVasWCFxVQ8WFxeHHTt2AKjvHlCr1VAoFBJXJS8MA0RE7ZSXlxe2bNkivo+NjTXoPrAE69evR1xcnPg+ISGB3QMSYBggImrHpk2bhg0bNojvFy9ejLi4OMm7DARBQGxsLBYvXiwe27hxI6ZOnSphVfLF2QRERDKwatUqLF26VHw/ffp0bNq0SZINgLRaLaKiosSuAQBYuXIlYmJizF4L1WMYICKSiQ0bNhisO9C7d2+o1WqzTjtMTU1FRESEOFiwoa4FCxaYrQa6H7sJiIhkYuHChdi1a5c4qLCkpATjxo1DeHi4uP2wqWi1WoSFhWHcuHFiEHBxcUFycjKDgAVgywARkcxoNBpERETg0KFD4jE3NzeEhYUhMjLSqJsbFRQUICEhAUlJSeLKgkD99EG1Ws3BghaCYYCISIYEQUBiYiKio6PFlQqB+imJQUFBmDdvHkJCQmBnZ9fia9fU1CA1NRWbN28WtyFu4OLigvj4eISFhXH6oAVhGCAikjGNRoOYmBjs3bsX1dXVBt9TKpUYOnQoAgICEBgYiICAAHh5eUGpVMLBwQFVVVXQ6XTQaDTIzMxERkYGMjMzkZubC51OZ3Ate3t7TJ48GatXr2ZrgAViGCAiIpSXlyMpKQlbtmxBUVGR0a7br18/REZGIiwsjJsOWTCGASIiEtXW1iItLQ27du3CsWPHUFBQ0OJrqFQqDBs2DKGhoQgODoatra0JKiVjYhggIqJGabVaZGVlITMzE9nZ2aioqIBOp0NVVRUcHBygVCrh5uYGPz8/BAQEwN/fX5K1C6htGAaIiIhkjusMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzP0/E/SAoMP9lowAAAAASUVORK5CYII=",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "Vaccinated = Variable(\"V\")\n",
+    "Masked = Variable(\"M\")\n",
+    "Infected = Variable(\"I\")\n",
+    "\n",
+    "simple_covid_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Vaccinated, Masked),\n",
+    "        (Masked, Infected),\n",
+    "        (Vaccinated, Infected),\n",
+    "    ],\n",
+    ")\n",
+    "simple_covid_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4557824f-9386-42f1-bfdc-ed3f1437864c",
+   "metadata": {},
+   "source": [
+    "For purposes of simplicy, $V$ represents whether a vaccine was administered or not, $M$ represents whether a mask was worn during the two weeks prior to a COVID infection, and $I$ represents whether the person tested positive for COVID-19. The vaccine efficacy affects the likelihood of infection, but vaccination can also affect masking behavior by increasing a person's risk tolerance, and masking may affect a person's likelihood of COVID by adjusting the amount of initial viral load to which they were exposed. Thanks to an implementation of Shpitser's ID algorithm in $Y_0$, we can estimate the total effect of the vaccine on symptom duration:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "id": "d5921068-878a-4e90-bd6e-8c24ea78b053",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$\\sum\\limits_{M} P(I | M, V) P(M | V)$"
+      ],
+      "text/plain": [
+       "Sum[M](P(I | M, V) * P(M | V))"
+      ]
+     },
+     "execution_count": 16,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "identify_outcomes(simple_covid_graph, treatments={Vaccinated}, outcomes={Infected})"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e6f958e5-f599-4247-8dd4-d4a3c48bd124",
+   "metadata": {},
+   "source": [
+    "This model is of course too simplistic, because many unmeasured factors affect both the vaccine type and masking preferences such as medical history, social network effects, and so on. Let's represent those unmeasured factors, \"confounders\", by a bi-directed edge between Vaccinated and Masked:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "id": "739daf0d-8bd4-42ba-a30b-3a89f748dee4",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABKgUlEQVR4nO3deVxU9f4/8Bf7KCKC4som44JLgCKmLWp6FTDTbhu5F4ih3TKX6lZqkHqvmaL+bilGLqFkq9W1hDRL0yxUEAzTZBFQAUVhEMFhZDi/P/hy7gygsszMGTiv5+PR484chnPe053gxefzPp+PhSAIAoiIiEi2LKUugIiIiKTFMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHPWUhdARNRWlJSUICUlBcnJyUhNTcX169ehVquh0Whga2sLhUKBzp07w8/PD/7+/hg6dCicnJykLpsIFoIgCFIXQUTUGmm1WiQkJCA+Ph7Hjx9HdnZ2k8/h5eWF4cOHY/r06QgODoaVlZURKiW6O4YBIqImKioqwtatWxETE4Pc3FyDndfDwwMREREICwuDi4uLwc5LdC8MA0REjZSXl4e33noLn3/+OTQajd7XFAqFOPw/bNgw+Pv7w93dHQqFAra2ttBoNFCr1cjNzUVycrL4T2pqKtRqtd65bG1tERISgpUrV8Ld3d2Ub5FkimGAiOgeBEHARx99hMWLF6OsrEw8bmFh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",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "covid_with_confounders_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Vaccinated, Masked),\n",
+    "        (Masked, Infected),\n",
+    "        (Vaccinated, Infected),\n",
+    "    ],\n",
+    "    undirected=[(Masked, Vaccinated)],\n",
+    ")\n",
+    "covid_with_confounders_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "696f41ca-f0be-47d7-b424-394c3665b83f",
+   "metadata": {},
+   "source": [
+    "The effect of vaccination type on symptom duration is not identifiable due to the confounders:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "id": "98c7ed1c-d89a-44bb-83eb-12da28758f18",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 18,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "identify_outcomes(\n",
+    "    covid_with_confounders_graph, treatments={Vaccinated}, outcomes={Infected}\n",
+    ") is None"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d8864ec2-12a4-4886-b1a7-0079f254ae48",
+   "metadata": {},
+   "source": [
+    "The good news is that often we can determine a causal effect when a model is not identifiable by using experimental data from another setting, or by conditioning on additional variables. Let's see two examples in action!"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "87bfbe15-636e-47b1-b022-708658ce23e3",
+   "metadata": {},
+   "source": [
+    "## Conditional transportability example: vaccination and symptom duration"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7bc2659c-563f-4d93-913c-31dce4ac7ab7",
+   "metadata": {},
+   "source": [
+    "Suppose a person was vaccinated $V=v$, and was not infected $I=i$, and we want to know the likelihood that they would be infected had they not been vaccinated  $I_{do(V = v^\\ast)}$. That would be represented in $Y_0$ as follows:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "id": "fd429978-6f58-4e36-9ab1-fc4a5c31b057",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(I_{V^{+}} | I^{+}, V^{-})$"
+      ],
+      "text/plain": [
+       "P(I @ +V | +I, -V)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# TODO check that this can't be written more succinctly with\n",
+    "#  P(+Infected @ +Vaccinated | -Vaccinated)\n",
+    "\n",
+    "effect_of_vaccination_on_the_uninfected_vaccinated = P(\n",
+    "    Infected @ +Vaccinated | +Infected, -Vaccinated\n",
+    ")\n",
+    "display(effect_of_vaccination_on_the_uninfected_vaccinated)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "dae5834c-3dfe-4ec2-9684-1ab35ce61ea8",
+   "metadata": {},
+   "source": [
+    "Next, let's consider that we have two populations: an observational study in the United States, and a vaccine efficacy study in Australia in which people were randomly assigned to receive vaccine or placebo, and the effect on symptom duration was observed. Moreover, suppose the distribution of people who wear masks in Australia is different than in the United States for unknown reasons."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "id": "b30ded65-600d-4f95-bbec-94a6645392e1",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# This graph represents the target domain, the United States.\n",
+    "USA = Variable(\"USA\")\n",
+    "\n",
+    "observational_study_in_usa_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Vaccinated, Masked),\n",
+    "        (Masked, Infected),\n",
+    "        (Vaccinated, Infected),\n",
+    "    ],\n",
+    "    undirected=[(Vaccinated, Masked), (Vaccinated, Infected)],\n",
+    ")\n",
+    "observational_study_in_usa_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "id": "c19f4bf6-b46a-4ea5-a2c6-438233f7d8c2",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "Australia = Variable(\"AUS\")\n",
+    "\n",
+    "vaccine_efficacy_in_australia_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Vaccinated, Masked),\n",
+    "        (Vaccinated, Infected),\n",
+    "        (Masked, Infected),\n",
+    "        (transport_variable(Masked), Masked),\n",
+    "    ],\n",
+    "    undirected=[(Masked, Infected)],\n",
+    ")\n",
+    "vaccine_efficacy_in_australia_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4880f863-fae6-40a8-8521-d935c697c58b",
+   "metadata": {},
+   "source": [
+    "The transportability node in the graph is there to remind us that the difference in the mask wearing populations prohibits us from directly running the Identify algorithm on the graph for the Australian population to estimate the effect of vaccine type on symptom duration for the American sample."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "fa14d5dd-8bcb-4392-b002-18a7a201ee96",
+   "metadata": {},
+   "source": [
+    "We can estimate the effect using the conditional counterfactual transportability algorithm in $Y_0$:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "id": "dae6ae64-f778-4caa-8afa-84c0a2d6191b",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(I^{-}, V^{-})$"
+      ],
+      "text/plain": [
+       "P(-I, -V)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\frac{\\sum\\limits_{M} P^{USA}(I | M, V) P^{USA}(M | V) P^{USA}(V)}{\\sum\\limits_{I, M} P^{USA}(I | M, V) P^{USA}(M | V) P^{USA}(V)}$"
+      ],
+      "text/plain": [
+       "((Sum[M](PP[USA](I | M, V) * PP[USA](M | V) * PP[USA](V)) / Sum[I, M](PP[USA](I | M, V) * PP[USA](M | V) * PP[USA](V))))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# TODO this means literally nothing to a new reader. Need a data structure that implicitly annotates this\n",
+    "vaccine_efficacy_in_usa_query = -Infected @ +Vaccinated\n",
+    "\n",
+    "# TODO this means literally nothing to a new reader. Need a data structure that implicitly annotates this\n",
+    "unvaccinated_cohort = -Vaccinated\n",
+    "\n",
+    "domains = [\n",
+    "    CFTDomain(\n",
+    "        population=Australia,\n",
+    "        graph=vaccine_efficacy_in_australia_graph,\n",
+    "        policy_variables={Vaccinated},\n",
+    "    ),\n",
+    "    CFTDomain(\n",
+    "        population=USA,\n",
+    "        graph=observational_study_in_usa_graph,\n",
+    "    ),\n",
+    "]\n",
+    "\n",
+    "conditional_cft_result = conditional_cft(\n",
+    "    outcomes=vaccine_efficacy_in_usa_query,\n",
+    "    conditions=unvaccinated_cohort,\n",
+    "    target_domain_graph=observational_study_in_usa_graph,\n",
+    "    domains=domains,\n",
+    ")\n",
+    "\n",
+    "conditional_cft_result.display()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ec255bf6-f894-4060-b4b2-0968a28de2fd",
+   "metadata": {},
+   "source": [
+    "For this example, it turns out that we can just compute the probability of symptom duration given vaccine type and masking preference for the United States, and we don't actually need the Australian data. That can happen. Note that Correa, Lee, and Bareinboim have an algorithm for _conditional transportability_ and an algorithm for _unconditional transportability_. Suppose we are interested in the probability that a person would get infected if they were unvacinated and they would not get infected if they were vaccinated"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "id": "28eb7555-b07a-4cba-9885-5220fb42e8c2",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 23,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "event = [(+Infected @ -Vaccinated), (-Infected @ +Vaccinated)]\n",
+    "unconditional_cft(\n",
+    "    event=event, target_domain_graph=observational_study_in_usa_graph, domains=domains\n",
+    ") is None"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d5338ec9-2432-4916-b578-45585fe8fac4",
+   "metadata": {},
+   "source": [
+    "It turns out that there are not enough constraints provided by the assumptions of the model and the domain data to identify an answer the question.  "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4e15dc29-dce8-4f95-bf72-a679b41ff3e6",
+   "metadata": {},
+   "source": [
+    "## Unconditional transportability example: anti-retroviral therapy (ART) and AIDS mortality\n",
+    "\n",
+    "Suppose we're interested in the effect of HIV anti-retroviral therapy ($T$) on mortality from AIDS ($A$) in Nigeria, in a model where the distance to the nearest clinic ($D$) affects mortality and selection into the type of ART regimen, while also being confounded with the ART treatment (e.g., personal income could affect the affordability of ARTs and the mode of transportation a person uses to access the clinic). Additionally, the ART treatment affects a person's CD4 count ($C$), which affects mortality from AIDS and is also confounded with mortality, as comorbidity with other diseases could affect both the CD4 count and AIDS-related mortality. Here's the graph:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "id": "6c9fb9a4-cf60-49e9-9261-c9fd74ed942c",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "Therapy = Variable(\"T\")\n",
+    "Distance = Variable(\"D\")\n",
+    "CD4 = Variable(\"C\")\n",
+    "AIDS = Variable(\"A\")\n",
+    "\n",
+    "nigeria_population = Variable(r\"\\text{Nigeria}\")\n",
+    "\n",
+    "nigeria_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Distance, Therapy),\n",
+    "        (Distance, AIDS),\n",
+    "        (Therapy, AIDS),\n",
+    "        (Therapy, CD4),\n",
+    "        (CD4, AIDS),\n",
+    "    ],\n",
+    "    undirected=[(Distance, Therapy), (CD4, AIDS)],\n",
+    ")\n",
+    "nigeria_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e5a7b1d7-1bb0-41a7-a0ba-a5e83b4fd9cf",
+   "metadata": {},
+   "source": [
+    "Earlier in the AIDS epidemic, rumors existed in some parts of Africa that ARTs and not the HIV virus were responsible for causing AIDS, and that raised concerns at the time that some people may not take ARTs even were they made available. Assuming that a program to provide ARTs to the public would only work if people are willing to take the drugs, suppose we want to know what receiving ARTs ($t_0$) does to AIDS-related mortality in a target population $\\pi^\\ast$, but specifically for people who choose to receive generic ARTs. So, we care about the difference in mortality for a person who chooses to take ARTs, compared to those for a person required to take them. Thus, we wish to know $E[A_{t_0} | t_0] - E[A_t | t_0]$. The first quantity is $E[A | t_0] = \\frac{E[A, t_0]}{E[t_0]}$, and the second is $E[A_t | t_0] = \\frac{E[A_t, t_0]}{E[t_0]}$. Let's estimate $E[A_t, t_0]$ using `transport_unconditional_counterfactual_query()`, introducing our data sets first."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a973ebca-5104-4c78-b213-cc822a604af4",
+   "metadata": {},
+   "source": [
+    "### Data\n",
+    "\n",
+    "Here we imagine we have a study in a different country (Ghana) than the target country (Nigeria) in which ARTs were delivered to participants' villages directly, removing the bi-directed edge between $D$ and $T$. But the study population, $\\pi^{\\text{Ghana}}$, has a different distribution of distance to the nearest clinic than our population of interest $\\pi^{\\text{Nigeria}}$:\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "id": "527393de-74bf-4988-81ea-48d728a80abc",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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gMPhQG5uLnbu3Am32+3znkqlwtixY6HVajF+/HhotVrExMRApVIhMjISLS0tcLlccDgcsNlsOHjwIGw2Gw4fPgyXy+XTllKpRGpqKtasWcPeACIiLwwDFDYaGhpQUlKCTZs2oa6uLmDtxsbGIiMjA3q9npsOERFdAcMAhR2PxwOr1YrS0lKUl5fDbrd3uQ2NRoMJEyZg1qxZSE5ORkRERBAqJSLqGRgGKOw5nU5UVFTAZrPh0KFDaGxshMvlQktLCyIjI6FSqaBWqzFu3DhotVokJCRIsnYBEdGNimGAiIhI5rjOABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERydz/B1Ua17GYk7rzAAAAAElFTkSuQmCC",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "ghana_population = Variable(r\"\\text{Ghana}\")\n",
+    "ghana_with_delivery_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Therapy, AIDS),\n",
+    "        (Therapy, CD4),\n",
+    "        (CD4, AIDS),\n",
+    "        (Distance, AIDS),\n",
+    "        (transport_variable(Distance), Distance),\n",
+    "    ],\n",
+    "    undirected=[\n",
+    "        (CD4, AIDS),\n",
+    "    ],\n",
+    ")\n",
+    "ghana_with_delivery_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "72417bc1-30c6-4b1a-9395-b30fa5855dda",
+   "metadata": {},
+   "source": [
+    "The second set of data is an observational study in a high-risk population of injecting drug users, $\\pi^{\\text{Drug}}$, for which the overall CD4 count distribution is also different than the target population $\\pi^{\\text{Nigeria}}$ but the distribution of distances to the nearest clinic is the same:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "id": "0c8a8b98-d1d5-486a-8938-2694e697dcc7",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABUoklEQVR4nO3de1xUdf4/8NcADqOE4qCmgmiMlSkqAppdt8yA8VLu2q4mXgq8kG1pilttWqBWtqlYW6ahWAZa7dpFjMHLltZ2MQVvaKnAyoQ3UGYUL8PIzOf3B1/PjwmQy9xgzuv5ePD4njmc+Zz3cfsyr/mcz/l8FEIIASIiIpItL3cXQERERO7FMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHMMA0RERDLHMEBERCRzDANEREQyxzBAREQkcwwDREREMscwQEREJHM+7i6Ams9gMCAvLw+5ubnYv38/zp8/D5PJBLPZDKVSCZVKhcDAQISHhyMyMhIRERHo2LGju8smIqIWRiGEEO4ughrHYrFAp9MhMzMTP//8M4qKiprcRmhoKIYMGYK4uDhotVp4e3s7oVIiImpNGAZagbKyMqxduxarVq1CcXGxw9rt2bMnEhMTkZCQgM6dOzusXSIial0YBlowvV6Pl156CZ9++inMZrPN71QqldT9HxUVhcjISISEhEClUkGpVMJsNsNkMqG4uBi5ubnSz/79+2EymWzaUiqVGDduHBYvXoyQkBBXXiIREbUADAMtkBACa9aswdy5c1FRUSHt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ooYZjUZMnz5der18+XIGARdiGCAi8kBTp05FdHQ0AODkyZOYNWuWw+cCcBQhBGbNmoWTJ08CAGJiYpCQkODmquSFYYCIyAMpFAqkpaXB398fAPDhhx9i4cKFbq6qbikpKdLYAH9/f6SlpTl8RkO6MYYBIiIPFRISgvfee096nZycbHP7oCVYtmwZUlJSpNerVq3i7QE3YBggIvJgcXFxSE1NlV4nJSUhJSXF7bcMhBBITk5GUlKStG/FihWYMGGCG6uSLz5NQEQkA4sXL8aCBQuk15MnT8Zbb73llgWAjEYjZs2aZfPY4KJFizB//nyX10LVGAaIiGQiNTUVc+bMkV4HBQUhLS3NpY8d6nQ6TJs2TRoseL2u2bNnu6wGqo23CYiIZOK5555DRkaGNKjw5MmTGDFiBBISEmA0Gp16bqPRiPj4eIwYMUIKAu3bt0dmZiaDQAvAngEiIpnR6/WYNm0atm3bJu1Tq9WIj49HYmIiNBqNw85VWFiIVatWIT09HeXl5dL+mJgYpKWlcbBgC8EwQEQkQ0IIrF27FnPmzJFmKgSqH0mMiYnBzJkzodVq4ePT9PXsqqqqoNPpsHLlSmn1wevat2+P5cuXIz4+no8PtiAMA0REMqbX6zF//nx88sknMJvNNr9TqVQYOHAgIiMjERUVhcjISISEhEClUsHX1xeVlZUwmUzQ6/XIzc3F3r17kZubiwMHDsBkMtm0pVQqMW7cOLz66qvsDWiBGAaIiAhlZWVIT0/He++9h+LiYoe126tXLyQmJiI+Pp6rD7ZgDANERCSxWCzIyclBRkYG9uzZg8LCwia3odFoMHjwYEycOBGxsbHw9vZ2QqXkSAwDRERUL6PRiLy8POTm5mLfvn0oLy+HyWRCZWUlfH19oVKpoFarMWjQIERGRiIiIsItcxeQfRgGiIiIZI7zDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcwxDBAREckcwwAREZHMMQwQERHJHMMAERGRzDEMEBERyRzDABERkcz9P2cGImJhRJh3AAAAAElFTkSuQmCC",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# From [correa22a]_, Figure 3b.\n",
+    "drug_user_population = Variable(r\"\\text{Drug}\")\n",
+    "drug_user_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Distance, Therapy),\n",
+    "        (Distance, AIDS),\n",
+    "        (Therapy, AIDS),\n",
+    "        (Therapy, CD4),\n",
+    "        (CD4, AIDS),\n",
+    "        (transport_variable(CD4), CD4),\n",
+    "    ],\n",
+    "    undirected=[(Distance, Therapy), (CD4, AIDS)],\n",
+    ")\n",
+    "drug_user_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3981da86-318c-427f-a39b-39537973cd78",
+   "metadata": {},
+   "source": [
+    "Let's estimate $E[A_t, t_0]$:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "id": "db5547eb-7a56-40b0-b19f-ff6215fdffd7",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(A^{-}_{T^{-}}, T^{+})$"
+      ],
+      "text/plain": [
+       "P(-A @ -T, +T)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\sum\\limits_{C, D} P^{\\text{Drug}}(D) P^{\\text{Drug}}(T | D) P^{\\text{Ghana}}(A | C, T, D) P^{\\text{Ghana}}(C | T)$"
+      ],
+      "text/plain": [
+       "Sum[C, D](PP[\\text{Drug}](D) * PP[\\text{Drug}](T | D) * PP[\\text{Ghana}](A | C, T, D) * PP[\\text{Ghana}](C | T))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "effect_of_do_art_on_aids_query = [-AIDS @ -Therapy, +Therapy]\n",
+    "\n",
+    "art_for_aids_domains = [\n",
+    "    # CFTDomain(\n",
+    "    #     graph=nigeria_graph,\n",
+    "    #     population=nigeria_population,\n",
+    "    # ),\n",
+    "    CFTDomain(\n",
+    "        population=ghana_population,\n",
+    "        graph=ghana_with_delivery_graph,\n",
+    "        policy_variables={Therapy},\n",
+    "    ),\n",
+    "    CFTDomain(\n",
+    "        population=drug_user_population,\n",
+    "        graph=drug_user_graph,\n",
+    "    ),\n",
+    "]\n",
+    "\n",
+    "do_art_on_aids_result = unconditional_cft(\n",
+    "    event=effect_of_do_art_on_aids_query,\n",
+    "    target_domain_graph=nigeria_graph,\n",
+    "    domains=art_for_aids_domains,\n",
+    ")\n",
+    "do_art_on_aids_result.display()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "93361a08-cbd1-4811-a474-6caaa7719d20",
+   "metadata": {},
+   "source": [
+    "Here we can see the results from the two domain populations getting combined to resolve a causal query for the target population! That result matches Equation 4 in Correa, Lee, and Bareinboim net of some simplification. Next, here's $E[A, t_0]$:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "id": "d841be71-c1fc-40a5-9def-4b17e7a430b2",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(A^{-}, T^{+})$"
+      ],
+      "text/plain": [
+       "P(-A, +T)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\sum\\limits_{C, D} P^{\\text{Drug}}(D) P^{\\text{Drug}}(T | D) P^{\\text{Ghana}}(A | C, T, D) P^{\\text{Ghana}}(C | T)$"
+      ],
+      "text/plain": [
+       "Sum[C, D](PP[\\text{Drug}](D) * PP[\\text{Drug}](T | D) * PP[\\text{Ghana}](A | C, T, D) * PP[\\text{Ghana}](C | T))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "effect_of_see_art_on_aids_query = [-AIDS, +Therapy]\n",
+    "see_art_for_aids_unconditional_result = unconditional_cft(\n",
+    "    event=effect_of_see_art_on_aids_query,\n",
+    "    target_domain_graph=nigeria_graph,\n",
+    "    domains=art_for_aids_domains,\n",
+    ")\n",
+    "see_art_for_aids_unconditional_result.display()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "34b2f51b-98ec-4850-b724-e323f56a80d8",
+   "metadata": {},
+   "source": [
+    "Compare this result to Equation 5 in Correa, Lee, and Bareinboim. Combining these two results, we can estimate our causal effect of interest."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "98697b37-c632-4059-b9f5-da137cb275de",
+   "metadata": {},
+   "source": [
+    "It's worth noting we can also use `transport_conditional_counterfactual_query()` to estimate $P(A_t | t_0)$. The result is messier, but simplifying it and multiplying by $P(t_0)$ gives us the unconditional query result:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "id": "4087fd18-a10c-4d7b-bd09-8f2905d948c7",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(A^{-}, T^{+})$"
+      ],
+      "text/plain": [
+       "P(-A, +T)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\frac{\\sum\\limits_{C, D} P^{\\text{Drug}}(D) P^{\\text{Drug}}(T | D) P^{\\text{Ghana}}(A | C, T, D) P^{\\text{Ghana}}(C | T)}{\\sum\\limits_{A, C, D} P^{\\text{Drug}}(D) P^{\\text{Drug}}(T | D) P^{\\text{Ghana}}(A | C, T, D) P^{\\text{Ghana}}(C | T)}$"
+      ],
+      "text/plain": [
+       "((Sum[C, D](PP[\\text{Drug}](D) * PP[\\text{Drug}](T | D) * PP[\\text{Ghana}](A | C, T, D) * PP[\\text{Ghana}](C | T)) / Sum[A, C, D](PP[\\text{Drug}](D) * PP[\\text{Drug}](T | D) * PP[\\text{Ghana}](A | C, T, D) * PP[\\text{Ghana}](C | T))))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "do_art_see_no_art_result = conditional_cft(\n",
+    "    outcomes=-AIDS @ -Therapy,\n",
+    "    conditions=+Therapy,\n",
+    "    target_domain_graph=nigeria_graph,\n",
+    "    domains=art_for_aids_domains,\n",
+    ")\n",
+    "do_art_see_no_art_result.display()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2ff6a431-626e-495e-ab14-032daafe7bcf",
+   "metadata": {},
+   "source": [
+    "## Climate change example: carbon taxes and cloud seeding"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4c455030-5185-40f0-843b-0c57dc4ff302",
+   "metadata": {},
+   "source": [
+    "Recent years have seen an increase in heat domes in North America and other extremes in hot and cold weather. One theory for their increase in frequency is that as the temperature of the earth rises and the polar ice caps melt the temperature differential between the poles and the equator drops, leading to a slowing-down of the jet stream that causes it to meander like a river does on a shallow grade compared to a steep slope. That in turn can create \"heat domes\" during the summer -- places where the jet stream deviates to the north, along with associated fires and droughts. It can also create copious amounts of snowfall and frigid winters elsewhere for the same reason. Let's consider a scenario in which the United States, concerned about heat domes and their effects on reservoir levels in the future, is considering imposing a carbon tax in order to both directly affect temperatures and also subsidize cities such as New York to conduct cloud seeding to keep reservoirs filled during emergencies. A graph might look like the following:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "id": "a7068607-2108-49d0-8b97-d85313dde0ba",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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Iy4uDkeOHEFWVhYMBgPy8vLg7OwMFxcXuLm5oVu3bvDy8kL37t0V2buAqDJhGCAiIlI57jNARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQqxzBARESkcgwDREREKscwQEREpHIMA0RERCrHMEBERKRyDANEREQq9/8AT4jNAjE/4dwAAAAASUVORK5CYII=",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# Based on [correa20a]_, Figure 1.\n",
+    "USA_population = Variable(r\"\\text{USA}\")\n",
+    "Temperature = Variable(\"T\")\n",
+    "JetStream = Variable(\"J\")\n",
+    "Rainfall = Variable(\"R\")\n",
+    "WaterSupply = Variable(\"W\")\n",
+    "\n",
+    "USA_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Temperature, JetStream),\n",
+    "        (Temperature, Rainfall),\n",
+    "        (JetStream, Rainfall),\n",
+    "        (Temperature, WaterSupply),\n",
+    "        (Rainfall, WaterSupply),\n",
+    "    ],\n",
+    "    undirected=[(Temperature, JetStream), (Rainfall, WaterSupply)],\n",
+    ")\n",
+    "USA_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c75cc09a-be6d-46ab-a579-e13454d74add",
+   "metadata": {},
+   "source": [
+    "In this graph, rising temperature affects the speed of the jet stream. It also indirectly increases precipitation by evaporating water on land and directly decreases reservoir volumes by evaporating reservoir water. Natural phenomena such as El Niño or the 18-year period of nutation of the planet on its axis may affect both temperature and the jet stream velocity, hence the bidirected edge between $J$ and $T$. Rainfall of course directly affects reservoir volumes, and other factors such as land usage affect both precipitation and reservoir volumes. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "66bb265d-34e9-48c2-a319-a04478b4ec77",
+   "metadata": {},
+   "source": [
+    "Suppose that Washington DC is contemplating the effect of a carbon tax, which for the purpose of simplicity we represent as a policy directly affecting temperature. The tax will also pay for a program to seed clouds whenever the jet stream meanders north during the summer and creates heat domes over the water supplies of large urban centers. The policy would therefore look as follows:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "id": "abf97ef7-5f9c-4a90-ae57-3ccb0d074943",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABRAElEQVR4nO3de1hU1f4G8HcEYTRBRMlTIpqYeAwTQVEx06yE0Sytk5fEG4ihZqZinooSb0UdATMvKApHg06anS4YoGXmvVQQ0/IGJBw9GSiMkjoMMOv3Bz/2mfEKw8zsGeb9PA/PM7PZs/Z3izDvrLX22gohhAARERHZrSZyF0BERETyYhggIiKycwwDREREdo5hgIiIyM4xDBAREdk5hgEiIiI7xzBARERk5xgGiIiI7BzDABERkZ1jGCAiIrJzDANERER2jmGAiIjIzjEMEBER2TmGASIiIjvHMEBERGTnGAaIiIjsHMMAERGRnWMYICIisnMMA0RERHaOYYCIiMjOMQwQERHZOYYBIiIiO8cwQEREZOcYBoiIiOwcwwAREZGdYxggIiKycwwDREREdo5hgIiIyM4xDBAREdk5hgEiIiI7xzBARERk5xgGiIiI7BzDABERkZ1jGCAiIrJzDANERER2jmGAiIjIzjEMEBER2TmGASIiIjvHMEBERGTnGAaIiIjsHMMAERGRnWMYICIisnMMA0RERHaOYYCIiMjOMQwQERHZOYYBIiIiO8cwQEREZOcYBoiIiOwcwwAREZGdYxggIiKycwwDREREdo5hgIiIyM4xDBAREdk5hgEiIiI7xzBARERk5xgGiIiI7BzDABERkZ1jGCAiIrJzDANERER2jmGAiIjIzjnKXQARyausrAw5OTnIzs5Gbm4uLl++DI1GA61WCycnJyiVSrRu3Rp+fn4ICAiAv78/WrVqJXfZRGRCCiGEkLsIIrKc6upqZGZmIi0tDYcOHUJBQUG92+jUqRMCAwMxbtw4qFQqODg4mKFSIrIUhgEiO1FSUoINGzYgMTERhYWFJmu3Q4cOiIyMRHh4ODw8PEzWLhFZDsMAUSNXVFSEt956C1u2bIFWqzX4nlKplLr/e/XqhYCAAHh5eUGpVMLJyQlarRYajQaFhYXIzs6WvnJzc6HRaAzacnJywujRo7FkyRJ4eXlZ8hSJqIEYBogaKSEE1q9fj7lz56K8vFzarlAoEBISgunTpyMkJASOjvWfOlRVVYWsrCysXr0aWVlZ0P8z4uLigvj4eISHh0OhUJjkXIjIvBgGiBqhoqIiREREYMeOHdI2d3d3hIeHIzIyEp06dTLZsfLz87F27Vps2LABpaWl0vYhQ4YgKSmJvQRENoBhgKiRSUtLw7Rp0wx6A8LCwhAfH4+WLVua7bhqtRpz585FcnKytM3FxQWJiYl46aWXzHZcImo4hgGiRiQhIQFz5syRnnt6eiIpKQkhISEWqyEzMxMRERG4cOGCQV2vvfaaxWogovrhokNEjcTixYsNgsDEiRNx4sQJiwYBAFCpVDhx4gQmTJggbZs9ezaWLFli0TqIqO4YBogagYSEBLzzzjvS84ULFyIlJcWswwJ34+bmhn/+85+IiYmRtr399ttYvny5LPUQ0d1xmIDIxqWlpSE0NFR6vmzZMsydO1fGigzFxcUhKipKep6WlsY5BERWhmGAyIYVFRXB19dXmiwYExODBQsWyFzVrWJiYrBw4UIANZMKT5w4wasMiKwIwwCRjRJCIDg4GN9++y2AmjkCKSkpVnltvxACkyZNwqZNmwAAwcHByMzMtMpaiewRwwCRjUpKSsLUqVMB1Fw1cOLECdnmCNSFWq2Gr6+vdJVBUlISpkyZInNVRAQwDBDZpJuHBzIyMqBSqWSu6t4yMzMxdOhQABwuILImvJqAyAZFR0dLQSAsLMwmggBQc9nh5MmTAQDl5eWIjo6WuSIiAtgzQGRziouL0b59e2i1Wri7u6OgoKDBwwOJiYmYNm3abb/XvHlztG/fHk888QRee+01+Pj4NOhYarUa3t7eKC0thZOTE86fP8+7HRLJjD0DRDYmOTlZuvtgeHi4SeYJ5Obm3vF7169fx+nTp5GYmIgePXrg888/b9Cx3NzcEBYWBgDQarUGyxcTkTzYM0BkQ6qrq+Ht7Y3CwkIoFAqcPXsW3t7eDW63b9+++Omnn9CyZUvs27dP2q7VapGXl4fly5fj4MGDAGp6CvLz8/GXv/zF6OPl5+ejc+fOAICOHTsiLy8PDg4ODTsJIjIaewaIbEhmZiYKCwsBACEhISYJAjqdDsePHwcAdO/eHb6+vtKXv78/Ro0ahX379qFXr14AanoKPv300wYd09vbW1om+dy5c8jKymrYSRBRgzAMENmQtLQ06fH06dNN0uaZM2dw/fp1AMCjjz56232aNGmCyMhI6fkvv/zS4OPq15+amtrg9ojIeAwDRDbk0KFDAAClUmmyGxDpzxfo3r37Hffr0KGD9LiqqqrBx1WpVFAqlQCAw4cPN7g9IjIewwCRjSgrK0NBQQEAwM/PD46OjiZpVz8M3KlnAABKSkqkx506dWrwcR0dHdGjRw8ANXMI1Gp1g9skIuMwDBDZiJycHOlxQECAydqtDQMKhQK+vr533O+LL76QHj/33HMmObb+eeifHxFZFsMAkY3Izs6WHtdO5jOF2jDQoUMHuLq63nafzz//HFu3bgUAjBs37q49CPWhfx7650dElsUwQGQj9LvzTdUzcPHiRfzxxx8Abp0vUFFRgePHj+O1117DqFGjIITA4MGDsW7dOpMcGzA8j6NHj5qsXSKqH9MMOhKR2V2+fFl6bKr1/PUDRnp6+h3vItinTx9MmTIFkydPNul6APrnUVpaarJ2iah+2DNAZCM0Go30uHYWfkPdbeVBfWq1Gk8++aTJFwbSPw/98yMiy2IYILIRtUsQA4CTk5NJ2tQPA3v37sXx48dx/Phx/PTTT/j444/Rs2dPAMDp06elGwyZkrOzs/S4oqLC5O0TUd1wmIDIRugHAK1Wa/BGaqzaMNC2bVs89thjBt8LDAzECy+8gICAAJw8eRK7d+9Gbm4u/Pz8GnzcWvoBwBTnQ0TGYc8AkY0wdZf69evXcfbsWQC44xt8s2bN8NZbb0nP9VdANAVzDH0QUf0xDBDZiNatW0uPi4qKGtzezz//DJ1OB+DOYQAARo4ciWbNmgEA/v3vfzf4uPr0z8Pd3d2kbRNR3TEMENkI/TdsU1yTrz9f4G5hoHnz5njyyScBAAUFBfj1118bfOxa+udROz+BiCyPYYDIRuhfk3/kyJEGt1fXMAAAw4YNkx6np6c3+Ni19M/DlKsqElH9MAwQ2Qh/f3/psSl7Bpo3b44uXbrcdV/9MLBt27YGH7uW/nnonx8RWRbDAJGNaNWqlXSDoNzc3AbdOVCn0+H48eMAalYebNLk7n8K2rdvLy1BfPDgQYMFkIxVVVWFY8eOAQC8vb3h5ubW4DaJyDgMA0Q2JDAwEEDNLPysrCyj2zlz5gyuX78O4N5DBLVqeweqq6uRkZFh9LFrZWZmSlcT9O7du8HtEZHxFEIIIXcRRFQ327Ztw/DhwwEAKpXKJG/KclGpVFKg2bZtm8FQBBFZFsMAkQ2prq6Gt7c3CgsLoVAocPbsWXh7e8tdVr3l5+ejc+fOAICOHTsiLy/P5EsdE1HdcZiAyIY4ODggMjISACCEwNq1a2WuyDiJiYnS48jISAYBIpmxZ4DIxpSUlMDT0xNarRbu7u4oKChAy5Yt5S6rztRqNby9vVFaWgonJyecP38eHh4ecpdFZNfYM0BkYzw8PDB69GgANbf9nTNnjswV1c+cOXOk2xWPHj2aQYDICrBngMgGFRUVwdfXF+Xl5QCAjIwMqFQqmau6t4yMDGmioIuLC3755Re0b99e5qqIiD0DRDbIy8sLcXFx0vOIiAhcuXJFxoruTa1WY+rUqdLz+Ph4BgEiK8GeASKZCSFw7do1XLp06Zavw4cPo6SkBCtXrrxllUAhBEJCQrBjxw4AwMSJE5GSkgKFQiHHadyVEAKTJk3Cpk2bAADBwcHIzMy0ylqJ7BHDAJGFVFdX48MPP8ShQ4duedOvqKi462t9fHxw6tSpW7bfPFwQExODBQsWmKX+hoiJicHChQsBcHiAyBo5yl0Akb3IyMjA3LlzjXrtQw89dNvtXl5eWLNmDUJDQwHUvOm2aNHC6OOYQ1xcnBQEgJrLChkEiKwL5wwQWUiHDh2Mel3r1q2xefPmO35/3LhxSEhIkJ5HRUVh4cKFkLvTTwiBmJgYREVFSdvi4uLQokULnDt3Tr7CiOgWDANEFvLoo49i0qRJ9X7dxo0b4erqetd9XnvtNSxevFh6HhMTg0mTJkGtVtf7eKagVqsxadIkgx6BxYsXQ6PR4LnnnkPXrl3xxRdfyFIbEd2KYYDIgt5///16LRA0atSoOq/ZHx0djfj4eOn5pk2b4Ovri8zMzHrX2RCZmZnw9fWVJgsCQEJCAqKjo6FUKgEAFRUV+Nvf/mawEiERyUgQkUWtXLlSALjnl5ubm/j999/r3X5qaqpwcXExaCssLEyUlZWZ/mT0lJWVicmTJxsc19XVVaSlpUn7aLVaMWHCBIN93n77baHT6cxaGxHdHcMAkQWdO3dOjBgxok5hICkpyejjFBYWiiFDhhi05+7uLqKiokReXp4Jz0iIvLw8ERUVJdzd3Q2OFxwcLIqKim7ZX6fTifnz5xvsO2XKFFFZWWnSuoio7hgGiCxAo9GIpUuXimbNmtUpCAwcOLDBn5Z1Op1ISkq6pZdAoVCIkJAQ8fXXXxv9BlxZWSm+/vprERISckvtrq6uYv369fesf/ny5UKhUEivGz58uLh27ZpR9RBRw3CdASIz27FjB1555RWcPXtW2ta2bVv4+Phgz549t+zv5OSEn3/+GT4+PiY5flFREaKjo7F582ZotVqD7ymVSvTo0QMBAQHo1asXAgIC4OXlBaVSCWdnZ1RUVECj0aCoqAjZ2dk4cuQIsrOzcezYMWg0mlvqHj16NJYuXVrnSwc3b96M8ePHo7KyEgDQr18/pKeno3Xr1iY5dyKqI7nTCFFjVVRUJF544QWDT81NmjQRs2bNEmq1WhQXFws3N7dbPlkvWrTILPUUFxeL2NhY0aFDhzr1TtT1q2PHjiI2NlYUFxcbVdfOnTsNei+6du0qCgsLTXz2RHQ37BkgMjGtVov4+HgsXrwY169fl7b3798fq1atQo8ePaRta9aswfTp06Xn3bp1w9GjR+Hk5GS2+qqrq5GVlYXU1FQcPnwY+fn59W7D29sbvXv3RmhoKEJCQuDg4NCgmnJzc6FSqXDx4kUAwIMPPoisrCx07969Qe0SUd0wDBCZ0HfffYdXXnkFp0+flrbdf//9+OCDDzB+/Hg0aWJ4NW91dTUCAwORk5MDhUKBffv2ISgoyKI1q9Vq5OTkIDs7G0ePHkVpaSk0Gg0qKirg7OwMpVIJd3d39OzZEwEBAfD394ebm5vJ6/jtt98QHBwsDae0bNkSX331FQYOHGjyYxGRIYYBIhM4f/485s6diy1btkjbmjRpghkzZmDRokV3ffM8f/48li5disGDB+PFF1+0QLXWq6SkBMOGDcPhw4cBAM7OzkhLS8MLL7wgc2VEjRvDAFEDaLVafPjhh1i4cCGuXbsmbe/Xrx9Wr14NPz8/+YqzUdeuXcOLL74oLZakUCiwcuVKg+EUIjItrkBIZKTvv/8efn5+eP3116Ug4OHhgZSUFOzbt49BwEj33XcfvvrqK2npZiEEZsyYgejoaNnvt0DUWDEMENXThQsXMHbsWDz55JM4efIkgP8NCZw+fRqTJk26ZW6ArRBCQKfTyV0GmjZtiuTkZLzxxhvStqVLl2LKlCmoqqqSsTKixsk2/2IRyaCyshJxcXHo2rUrPv30U2l73759cfjwYaxcuRKtWrWSscKGOXToEJycnKBUKvHzzz/LXQ4UCgXeffddrFixAgqFAgCQnJyMkSNHGlylQUQNxzkDRHXwww8/YMaMGfj111+lbW3atMH7779v0z0B+rp3744TJ04AAIYMGYLt27fLXNH/fPbZZwgNDZUWTerbty/S09PRpk0bmSsjahwYBoju4r///S/mzZuHTz75RNqmUCgQGRmJJUuWwN3dXcbqTOfbb7/FkCFDpOfOzs7473//a1Xnt2vXLowYMQJXr14FAPj4+CArKwsdO3aUtzCiRsD2P84QmUFlZSUSEhLQtWtXgyAQGBiIQ4cOYfXq1Vb1RtkQ1dXVmDt3rsG2iooKJCUlyVTR7T3xxBPYs2cPHnjgAQDA6dOnERQUZBVDGkS2jmGA6CZ79uyBv78/5syZg/LycgBA69atsW7dOhw8eBC9evWSuULTSklJwfHjx2/Z/tFHH0n3DLAWPXr0wIEDB6T7Nvz+++8YMGAAfvjhB3kLI7JxDANE/+/ixYsYP348Bg4cKI2dKxQKTJ06FadPn0ZERESjmBugr7y8HNHR0bf93oULF7B161YLV3RvHTt2xL59+9CnTx8AwNWrVxEcHIzPPvtM5sqIbFfj+stGZISqqip8+OGH8PHxQWpqqrS9V69e+PHHH7F27dpGexe9Dz74AH/88ccdv5+QkGCV1/a3adMGO3fuxNChQwHULP40evRorFy5UubKiGwTJxCSXdu3bx+mT59u0E3eqlUrvPvuu4iIiGjwDXis2X/+8x906dLlllsR32zfvn3o37+/haqqn8rKSrz88stISUmRtr355ptYsmSJdDkiEd0bewbILv3xxx+YOHEiBgwYYBAEpkyZgjNnziAyMrJRBwGg5k3zXkEAqOkdsFZNmzbFhg0b8NZbb0nb3n33XYSFhVndfAcia8aeAbIrVVVVWLNmDaKjo6VL1ADA398fq1evlsahG7vDhw8jMDCwTvs2adIEeXl5eOihh8xcVcOsWrUKM2fOlIY1hg4dii1btuC+++6TuTIi68eeAbIb+/fvR69evfDqq69KQcDNzQ2rV6/GoUOH7CYICCFuuZTwbnQ6HT766CMzVmQaM2bMwJYtW+Dk5AQAyMjIwODBg3Hp0iWZKyOyfuwZoEavuLgY8+fPxz//+U+D7WFhYYiNjYWHh4c8hcnk5gWG6sLFxQXnz5+Hq6urmaoynd27d+O5557DlStXAABdunRBVlaW1fdsEMmJPQPUaFVXV2PVqlXw8fExCAJ+fn44cOAANmzYYHdBAABKSkrq/Zry8nLs3r3bDNWY3sCBA7F37148+OCDAIAzZ84gKCgIx44dk7kyIuvFngFqlA4ePIgZM2bg6NGj0raWLVti6dKldjE58G6qqqqwYcMGnDhxAn/++af0lZWVBaBmKeKWLVvizz//lG4I5OnpiR9//BHt2rWTs/R6KSwsREhICE6dOgUAcHV1xZdffoknnnhC5sqIrA/DADUqJSUl+Pvf/47k5GSD7ZMmTcL777+P+++/X6bKrNv169eliXZPPPEEvv/+ewA1vSvXrl1DixYtbHLBpcuXL2P48OE4ePAgAMDJyQkff/wxRo0aJXNlRNbF9n67iW6juroaa9asQZcuXQyCQI8ePbBv3z6kpKQwCNzFn3/+KT1u0aKF9NjBwQGurq42GQSAmmWkv/vuOzzzzDMAahYnGjNmDFasWCFzZUTWxTZ/w4n0/PTTTwgMDMT06dOhVqsB1HQJr1ixAkeOHLHaBXOsyZ3CQGPQvHlzfPHFFwgPDwdQczXFrFmz8MYbb1jl6opEcmAYIJt16dIlREREoG/fvsjJyZG2T5gwAWfOnMHMmTPh6OgoY4W2o/aGTEDNlQONjaOjI5KSkvD2229L22JjYzFp0iQuTkQEhgGyQdXV1Vi7di18fHywfv16aXv37t2xZ88ebNy4EW3btpWxQtvTmHsGaikUCixatAirV6+WliretGkTnn32WYPzJ7JHDANkUw4fPoy+ffsiMjISpaWlAGo+yS5fvhw5OTkYMGCAzBXaJnsIA7WmTZuGrVu3wtnZGQCQlZWFwYMHG3XJJVFjwTBANuHy5ct4+eWX0adPHxw5ckTaHhoaitOnT2PWrFkcEmgA/WGCxh4GAOD555/Ht99+Czc3NwA1IbN///4oKCiQtzAimTAMkFXT6XRISkpCly5dsG7dOmnC1yOPPILdu3fj448/xgMPPCBzlbZPv2egMc4ZuJ0BAwZg79690toJZ8+eRVBQkMHaFET2gmGArFZ2djb69euHqVOnGgwJxMfH4+jRo3j88cdlrrDxsKdhAn2+vr44cOAA/vrXvwKouZvlwIEDsXPnTpkrI7IshgGyOqWlpZg2bRp69+6NQ4cOSdtfeuklnDp1CrNnz0bTpk1lrLDxsdcwAABeXl7Yt28fgoKCANQMmahUKnz66acyV0ZkOQwDZDV0Oh02bNgAHx8fJCYmSkMC3bp1w65du5CWliatN0+m1dgvLbwXd3d3fPfdd3j22WcBAJWVlRg7diyWL18ub2FEFsIwQFYhJycH/fv3x5QpU6RbzrZo0QLLli1Dbm4uBg0aJG+BjZw99wzUatasGT7//HNERERI22bPno3XX38dOp1OxsqIzI9hgGRVVlaGV155Bb1798aPP/4obR8zZgxOnTqFuXPnckjAAhgGajg6OmLt2rVYsGCBtO0f//gHJk6cCK1WK2NlRObFa7FsWFlZGXJycpCdnY3c3FxcvnwZGo0GWq0WTk5OUCqVaN26Nfz8/BAQEAB/f3+0atVK7rIB1AwJbNq0Ca+//rrB9d1du3bFqlWrMHjwYBmrsz/2PkygT6FQICYmBg888ACmT58OnU6H1NRUlJSUYOvWrXYdlqjx4l0LbUh1dTUyMzORlpaGQ4cOGXVNdKdOnRAYGIhx48ZBpVLJcivf3NxczJgxAwcOHJC23XfffViwYAFmzZoFJycni9dk74YOHYrMzEwANWs6uLu7y1yRdfjyyy8xZswYVFRUAAB69eqFb775hje9okaHYcAGlJSUYMOGDUhMTERhYaHJ2u3QoQMiIyMRHh4ODw8Pk7V7J2q1Gu+88w5WrVplMAY7atQoxMXFwdPT0+w10O09/vjj2Lt3LwCgoqKCgUzPvn37MHz4cOkmWN7e3ti+fTu8vb3lLYzIhBgGrFhRURHeeustbNmy5ZbxSqVSKXX/9+rVCwEBAfDy8oJSqYSTkxO0Wi00Gg0KCwuRnZ0tfeXm5kKj0Ri05eTkhNGjR2PJkiXw8vIy+XkIIfDxxx9j3rx5KC4ulrb7+Phg5cqVeOqpp0x+TKqfnj17Ijc3F02bNuXY+G388ssvCAkJwfnz5wEA999/PzIzM+Hv7y9zZUQmIsjq6HQ6sW7dOuHi4iIASF8KhUKoVCqRnp4uKisrjWq7srJSpKenC5VKJRQKhUH7Li4uIikpSeh0OpOdS25urnjssccMjtO8eXMRGxsrKioqTHYcapjOnTsLAMLd3V3uUqxWUVGR6Natm/T/uEWLFmLHjh1yl0VkEgwDVqawsFAMGTLE4M3T3d1dzJs3T+Tn55v0WHl5eWLevHnC3d3d4HhDhgwRhYWFDWpbrVaLV199VTRp0sSg7b/97W8NbptM7y9/+YsAILy8vOQuxapdvnzZINw2bdpUpKWlyV0WUYMxDFiR1NTUW3oDwsLChFqtNutxy8rKRFhY2C29BMb8kdPpdGLTpk2ibdu2Bu09/PDDYvv27WaonkzhvvvuEwBEt27d5C7F6l2/fl2MGDHC4P93XFyc3GURNQjDgJWIj483+OPi6ekpMjMzLVpDRkaGaNeunUEdCQkJdX79zz//LAYMGGDw+mbNmomlS5cKjUZjvsKpQaqrq6WfV58+feQuxyZUVVWJl19+2eD/+ty5c0V1dbXcpREZhWHACixatMjgj8rEiRPN3htwJ2VlZWLChAkG9SxevPiur7ly5YqYPXu2cHBwMHjdyJEjxblz5yxUORmrvLxc+pk9+eSTcpdjM3Q6nVi4cKHB//mXXnqJc2HIJjEMyOzmHoGFCxeadAKfMXQ6nYiJiblnD4FOpxNpaWnSeHPtV+fOnUVGRoblCyej/P7779LP7rnnnpO7HJuzbt06g7kxTz/9tLh69arcZRHVC8OAjFJTUw3eRJctWyZ3SQaWLVtmUJ/+HIKSkhIxcOBAg+8rlUqxePFicePGDRmrpvo6c+aM9DMMDQ2Vuxyb9NVXXwmlUin9O/r7+4uLFy/KXRZRnfHeBDIpKirCtGnTpOcxMTGYO3eujBXdau7cuQZrtEdGRqKoqAgAsHTpUuzevVv63nPPPYeTJ08iOjoaSqXS4rWS8XhfgoZ79tln8d1330nLfefk5CAoKAh5eXkyV0ZUNwwDMhBCYMqUKdJ68BMnTsQ777wjc1W3t2DBAkyYMAFAzfr1U6dOhRAC/fr1g6OjIzp37oxt27bhyy+/RMeOHeUtlozCMGAa/fv3x759+9C+fXsAQEFBAYKCgnDkyBGZKyO6N4YBGaxfvx7ffvstAMDT0xMffvghFAqFzFXdnkKhwIcffoh27doBALZv344NGzZg1KhRuHLlCs6ePYthw4bJXCU1BG9SZDrdunXDgQMH4OvrC6BmKfFBgwZhx44dMldGdHcMAxZWVFRkMBywbt06tGzZUsaK7s3NzQ1JSUnS8zlz5qCoqAjNmzeXsSoyFfYMmJanpyf27NmDAQMGAACuXbuGYcOGITU1VebKiO6MYcDCoqOjpU9iYWFhUKlUZjvWsmXLoFAooFAosG3btga1pVKpMHnyZAA1nySjo6NNUSJZAYYB02vVqhW2b9+OkSNHAgCqqqowfvx4LFu2DIK3gyErxDBgQcXFxdi8eTMAwN3dHfHx8WY93rFjx6THfn5+DW4vPj5eurXt5s2bUVJS0uA2SX76wwQMA6bTrFkzfPbZZwYThefNm4e5c+ca3LWTyBowDFhQcnKydEe48PBwsw8P1IaBNm3amOT2wG5ubggLCwMAaLVaJCcnN7hNkp9+zwDnDJiWg4MDVq1ahcWLF0vbEhISEBoaioqKChkrIzLEMGAh1dXVSExMBFAzKe/ll1826/G0Wi1OnToFAOjRo4fJ2o2MjJQeJyYmorq62mRtkzw4TGBeCoUC0dHRSEpKQpMmNX9y//Wvf2HYsGG4evWqzNUR1WAYsJDMzEwUFhYCAEJCQuDt7W3W4/3666+orKwEYNow4O3tjZCQEADAuXPnkJWVZbK2SR4cJrCMKVOm4Msvv0SzZs0AADt37sTAgQNx8eJFmSsjYhiwmLS0NOnx9OnTzX48U88X0KdfP2dI2z4OE1jO8OHDsXPnTmnuTW5uLoKCgnD27FmZKyN7xzBgIYcOHQIAKJVK6ZO1OZkzDKhUKmmVwcOHD5u0bbI8DhNYVr9+/bBv3z54eXkBAH777TcEBQXxd4lkxTBgAWVlZSgoKABQ88bs6Oho9mPWhgFnZ2d07drVpG07OjpKQw/5+flQq9UmbZ8si2HA8v7617/iwIED6N69OwDg0qVLGDRoEIfdSDYMAxaQk5MjPQ4ICLDIMWvDQLdu3dC0aVOTt69/HvrnR7aHcwbk0a5dO+zZswcDBw4EAFy/fh3Dhw/Hpk2bZK6M7BHDgAVkZ2dLj3v16mX24124cAGXL18GYPohglr656F/fmR7ansGlEqlRXqt6H/c3NyQlZWFF154AUDN4kQTJ07EBx98wMWJyKIYBiwgNzdXemyJngFzzheopX8eR48eNcsxyDJqwwB7BeShVCqxefNmzJgxQ9o2f/58zJ49m4sTkcUwDFhA7ad0ANKkIXPSDwOmvKxQn/55lJaWmuUYZBm1wwQMA/JxcHDARx99hKVLl0rbPvzwQ4wdO/auixOx94BMhWHAAjQajfS4dha+OVmiZ0D/PPTPj2xPbc8ALyuUl0KhwJtvvonk5GQ4ODgAALZs2QKVSoUrV67csv+RI0fw0EMPoVu3biguLrZ0udTIMAxYQO0SxADg5ORk9uPVhoGOHTuabcljZ2dn6TGXVbVd1dXVuHHjBgD2DFiLyZMn46uvvpIWJ9q1axcGDhyI33//Xdrn7NmzGDp0KAoLC3Hy5EmsWLFCrnKpkWAYsAD9AKAfDMzhxo0b0gIm5hoiAAwDgH4wINvCywqt07Bhw/D999+jdevWAGoCflBQEE6fPo2LFy8iODjY4EZhKSkpqKqqkqtcagQYBizAkl3qJ06ckO4XYK4hAsDyQx9kHlx90Hr17dsX+/fvR4cOHQDULP8dFBSEgQMH4rfffjPY97///S8yMjLkKJMaCYYBC6hN9wBQVFRk1mNZYr4AYHgetUurku1hz4B18/HxwYEDB/Doo48CqJmse+bMmdvuu379ekuWRo0Mw4AF6L8pm/uafP3LGM0ZBvTPo2fPnmY7DpkXFxyyfg8++CB++OEH3H///Xfd75tvvsGFCxcsVBU1NgwDFqB/Tf6RI0fMeqzanoGWLVuiY8eOZjuO/nlYalVFMj0OE9iGJUuW3POKAZ1Oh5SUFAtVRI0Nw4AF+Pv7S4/N3TPw888/AzBvrwBgeB7650e2hcME1i8uLg7x8fF12nfDhg1cqIiMwrVHLaBVq1bo1KkTCgoKkJubi6qqKrMs+3r69GlcvXoVQM2d0cylqqpK6oHw9vaGm5ub2Y5F5sUwYN22bt2KqKioOu9/7tw57Ny5E08//bTJaigrK0NOTg6ys7ORm5uLy5cvQ6PRQKvVwsnJCUqlEq1bt4afnx8CAgLg7++PVq1amez4ZBkMAxYSGBiIgoICaDQaZGVl4ZlnnjH5Mb7//nvp8aBBg0zefq3MzEzpaoLevXub7ThkfpwzYN2WL19e79ckJSU1KAxUV1cjMzMTaWlpOHTokHTH1Xv517/+JT3u1KkTAgMDMW7cOKhUKmkRJbJeHCawkHHjxkmPV69ebZZjfPzxxwCANm3a4MknnzTLMQDD+kNDQ812HDI/zhmwbhMmTKj3XUe//PJLgzUI6qqkpASxsbHw9vbG8OHD8emnn9Y5CNysoKAAn376KYYPHw5vb2/ExsYaVRNZDsOAhahUKul64aysLOTn55u0/S1btuDgwYMAgFdeecVsd5/Lz8+X7rnesWNHhISEmOU4ZBkcJrBuU6dOxaVLl/Dll19i+vTp8Pb2vudrKisrsWzZsjofo6ioCOPHj4enpyfeeOMNFBYWSt9TKpXo27cvZsyYgZSUFPz8889Qq9XQaDTQ6XTQaDRQq9U4duwYkpOTMWPGDPTt29dg7ZHCwkK88cYb8PT0xIQJE8x+eTUZSZDFvPfeewKAACDmzZvXoLa0Wq04fvy42L17t3j99ddF06ZNBQDx8MMPi/LychNVfKuoqCjpHGJjY812HLKMefPmST/PH374Qe5yqA7y8vLE6tWrxYgRI4SLi4v089P/atmy5T3b0el0Yt26dbe0oVAohEqlEunp6aKystKoGisrK0V6erpQqVRCoVAYtO/i4iKSkpKETqczqm0yD4YBCyouLhZOTk4CgHB3dxdqtdrotn744Ydb/gB07txZnD592oQVGyorKxPu7u4CgHBychLFxcVmOxZZxrRp06T/P9nZ2XKXQ/Wk1WrF3r17RXR0tOjZs6f0s3zooYfu+rrCwkIxZMgQg78f7u7uYt68eSI/P9+kNebl5Yl58+ZJfztqv4YMGSIKCwtNeiwyHsOAhY0fP176ZQgLCzO6nbi4OKFQKESrVq3EY489JuLj48Wff/5pwkpvNXnyZKn28ePHm/VYZBn6/x/NGSTJMv744w/xySefiGvXrt1xn9TU1Ft6A8LCwhr04aQuysrKRFhY2C29BGlpaWY9LtUNw4CFFRYWGvwiZmRkyF1SnXzzzTcGv8BFRUVyl0QmMGLECOnneuHCBbnLITOLj483eDP29PQUmZmZFq0hIyNDtGvXzqCOhIQEi9ZAt+IEQgvz8vJCXFyc9DwiIuK29yq3Jmq1GlOnTpWex8fHo3379jJWRKbCqwnsx+LFizFnzhzp+cSJE3HixAmLTwJWqVQ4ceIEJkyYIG2bPXs2lixZYtE66CZypxF7pNPpDMbrJk6caLWTaXQ6nZgwYYJUa3BwsNXWSvXXt29f6WdbVVUldzlkJjf3CCxcuFD232OdTidiYmLYQ2AlGAZkcvNwQUxMjNwl3daCBQs4PNCIPfLIIwKAaN68udylkJmkpqYavOEuW7ZM7pIMLFu2zKA+ziGQB8OAjPhLSnLr0KGDACDuv/9+uUshM7DVDx28ysDyGAZklpCQYPCGGxMTYxXdd/q/nADE8uXLZa2JzKN169YCgPD29pa7FDIxnU4nnn76aQ5HUp0wDFiBxYsXG7zxTpgwQZSVlclSS1lZmcEvJQCxePFiWWoh83N2dhYARI8ePeQuhUxs3bp1BlcNmPvSwYYqKyszuMogKSlJ7pLsCsOAlbh5gk+7du0sftlhXS/5ef/994W3t7eIiIgQhw8fFtXV1Ratk0xDq9VKP+f+/fvLXQ6ZkK1ewpyRkcHhApkwDFiROy0GYu5egrKyMoMFhQAIV1fXO84RqF36uPbLw8NDvPTSS2Ljxo3i999/N2utZDqlpaXSzzAkJETucsiEjFncLD09vc7zg7Kysgz+BtzrSpSRI0cKAMLR0fGek5C5uJk8GAaszJ2WCY2KihJ5eXkmPVZeXp6Iioq6ZZnQ4ODgu/7Cenp6Gux/85efn5+YP3+++P7770VFRYVJaybTKSoqkn5mf/vb3+Quh0zkjz/+MGrZ8z179kj/H9asWXPXfYcOHWrwO19aWnrHfQsLC4WDg0Od/59x2XN5cNEhK+Pl5YWsrCwkJSVJi8CUlpZi2bJlePjhh6FSqZCeno6qqiqj2q+qqkJ6ejpUKhU6d+6MZcuWobS0FADg6uqK9evXIzMz866LCr333nt3PUZubi7ef/99DB48GK1bt8azzz6LxMREaLVao2om8ygvL5ce846FjUdycrL0uxYeHo6WLVvW6XX6+129evWO++Xl5SEzM9Ng2932X7NmDaqrqwEAr7766j3rcHNzQ1hYGABAq9UiOTn5nq8hE5A7jdCdFRYWivHjx0spX/9LqVSKPn36iOnTp4vk5GRx7NgxUVZWJm7cuCF0Op24ceOGKCsrE8eOHRPJycli+vTpok+fPkKpVN7SlpOTkxg/fnyd1xC4ceOGaNWq1V17B273NXXqVDP/i1F9/PTTT9LPZubMmXKXQyZQVVUlXS6qUCjq1Zt47tw56f/DW2+9dcf9Zs2aJQAINzc3af9jx47ddl+NRiPatGkj9RjWVV5entR2x44duSCWBTAM2IDi4mIRGxsr/ZKb6qtjx44iNjbWqG64V199td7HmzJlihn+dchYO3fulH42b7zxhtzlkAnoj/urVKp6vbasrOye4bC8vFy4uroKAOLtt9+W9t+7d+9t9//nP/8p7bNhw4Z61RMSEiK9dtu2bfV6LdUfhwlsgIeHB+bPn4/8/Hxs27YNY8aMgbe3t1FteXt7Y8yYMdi2bRvy8vIwf/58eHh41Lud8PDweu3ftWtXxMbG1vs4ZD4cJmh80tLSpMfTp0+v12tdXV2hUCgA3Lnbf+PGjbh69SqcnJwwY8YMNG/e/K77r1y5EgDQpk0bvPTSS/WqR7/+1NTUer2W6s9R7gKo7hwcHDBs2DAMGzYMQM0NhHJycpCdnY2jR4+itLQUGo0GFRUVcHZ2hlKphLu7O3r27ImAgAD4+/vDzc3NJLU8+uijCAwMxKFDh+65b7t27bB9+3a0bt3aJMcm0+BNihqf2t9HpVJZ7xsQNWnSBC4uLrh69ept39yFENKb+5gxY9C2bVu4urri+vXrt73Z2o8//ogjR44AqLkhm1KprFc9KpUKSqUSGo0Ghw8frtdrqf4YBmyYm5sbBg8ejMGDB8ty/PDw8HuGARcXF2RlZcHLy8tCVVFd6YcB9gzYvrKyMhQUFAAA/Pz84OhY/z/vLVu2vGMY+Pbbb3Hq1CkAwKxZswDU9CZcvHjxtvvXBgdHR0dMmzat3rU4OjqiR48e+Omnn5Cfnw+1Wm2yDzN0Kw4TkNHGjBkjdRPeiRACZWVlFqqI6oNhoHHJycmRHgcEBBjVRu0VBfpDSLU++ugjAED//v3h7+8P4H89SjeHgeLiYnz22WcAgBEjRhh9y3P989A/PzI9hgEymqurK0aPHn3Xff78808MGTIEX331lYWqorrinIHGJTs7W3rcq1cvo9qo/eR985t7fn4+MjIyAPyvVwCo+RsA4JZhgnXr1kmXN9blcsI70T8P/fMj02MYoAaZMmXKbbevWLECQ4YMAQBoNBo8//zzSEpKsmRpdA+cM9C45ObmSo8b2jNwcxhYtWoVdDod2rdvj5EjR0rba8OA/v5VVVVYu3YtgJrhigEDBhhVC2B4HkePHjW6Hbo3hgFqkH79+uGvf/2rwbZFixZh5syZSE9Px7hx4wAAOp0OU6dOxeLFiyGEkKNUugmHCRqXy5cvS4+NnaNzuzBw7do1aeGfGTNmGMxFuN0wwZdffonz588DAGbOnGlUHbX0z6N2cTQyD4YBahCFQoHIyEjp+fTp0xEdHQ0AcHJywqZNmzB37lzp+++88w5mzJghrUhG8uEwQeOi0Wikx/WduV+rNgxcu3YNOp0OALBp0yZcuXIFzZo1Q0REhMH+txsmqJ1bYMzlhDfTPw/98yPT49UE1GAzZsxARUUFmjVrhmnTpknXKgM1lystW7YMf/nLXzBv3jwANcuTFhcXIzU11eg/WtRwHCZoXPSX+3ZycjKqjdo5A0IIlJeXo2XLltKbe2hoKNzd3Q32v3mY4Pjx49izZw8A4y4nvJmzs7P0uKKiokFt0d0xDFCDOTg4SG/0dxIVFYW2bdsiLCwMVVVV+Pzzz3Hp0iV89dVXdV47nUyLwwSNi34A0Gq1Bm+kdXXz/QkOHz6MkydPArj9RMCbw0BDLye8mX4AMOZ8qO44TEAWM378eKSnp0uXI+7evRsDBw7E77//LnNl9qk2DCgUinteIkrWzxRd6jeHgRUrVgAAnnzySfj6+t6yf22P0pUrV6BWq6UVEBtyOaE+Uwx9UN0wDJBFhYSEYNeuXWjTpg0A4NixYwgKCsKZM2dkrsz+1M4ZaNGihcHQDtkm/RU+i4qKjGpDPwwcO3YM33zzDYA7Xx6o3zOQkpKCa9eu3XX/+tI/j5uHKMi0GAbI4gIDA7F//3506NABAHDu3Dn079+/Tksbk+nU9gxwiKBx8PPzkx4be02+/gp/sbGx0Ol06NSpE5555pnb7q8/gXD16tVSHQ25nFCf/nn07NnTJG3S7TEMkCy6dOmCAwcO4NFHHwUAXLp0CYMHD8b27dtlrsx+MAw0LvrX5NfeE6C+9HsGjh8/DqDm8sAmTW7/VlE7TKDRaJCXlyftbyr652Hs2glUNwwDJJsHH3wQu3fvxuOPPw6g5nKmZ555hncos4Da2eIAw0BjUbtEMGB8z8DNk3ldXFwQFhZ2x/1rewZqmeJyQn3656F/fmR6DAMkKzc3N2zfvh3PP/88gJrVy8aPH4+4uDiZK2vctFotqqqqAPCywsaiVatW6NSpE4Ca1Qhrf771cXMYmDhx4i1v+Ppu/p4pLiesVVVVhWPHjgGoufU6b1JkXgwDJDulUoktW7YYLF4UFRWFqKgoaeETMi1eVtg4BQYGAqjpts/Kyqr36z09PSGEkL5q1xi4Ex8fH4P93333XaPqvp3MzEzpaoLevXubrF26PYYBsgoODg5YvXo1Fi1aJG2Li4vDxIkTUVlZKWNljRNXH2ycapf/BiBN6LNV+vWHhobKWIl9YBggq6FQKPD2229j7dq10oSl1NRUDB8+3OCTLDUcVx9snFQqlXSVTlZWFvLz82WuyDj5+flSz0bHjh0REhIic0WNH8MAWZ2pU6fi888/l1Yc2759OwYPHoySkhKZK2s8OEzQODk4OEjDbUII6e6BtiYxMVF6HBkZCQcHBxmrsQ8MA2SVRowYgR07dkgTmg4fPoz+/fvj3Llz8hbWSDAMNF7h4eHS0sQbNmwwuImQLVCr1dJdEp2cnO56NQOZDsMAWa3HH38ce/fuxYMPPggAOHv2LPr16yfNMCbj6c8Z4DBB4+Lh4YHRo0cDqLnt75w5c2SuqH7mzJkj3a549OjR8PDwkLki+8AwQFate/fuOHDgAHx8fAAAFy9exOOPP44ffvhB3sJsHHsGGrclS5ZIIS85ORmZmZkyV1Q3GRkZSElJAVATUpcuXSpzRfaDYYCsXocOHbBv3z706dMHQM066MHBwfj8889lrsx2MQzYPiEErl69ivPnz0MIYfA9Ly8vg7U6IiIirH64QK1WY+rUqdLz+Ph4k9zsiOqGtzAmm9CmTRvs3LkTo0aNQkZGBrRaLV588UWsWrXKJLdKtTe8tNA6aTQa5OTk4NKlS7h8+TIuX75s8Fh/W2lpqXTZbdeuXaVbDdeaMmUKtm7dih07duDChQuYNWsWUlJSrPKmVEIIzJo1CxcuXAAABAcHIzw8XOaq7AvDANmM++67D19++SUiIiKwceNGCCEwffp0XLx4ETExMVb5R85a8dJC66PRaPDII4+goKCg3q+93SWECoUCSUlJ8PX1RXl5OTZu3IiHHnoICxYsMEW5JrVw4UJs2rQJQM3/x6SkJP4+WxiHCcimNG3aFCkpKZg/f760bdGiRYiMjDRq+VV7xWEC61NeXm5UEACAl19++bbbvby8sGbNGul5TEyM1S31HRcXh4ULF0rPExMTOTwgA4YBsjkKhQKxsbFISEiQtq1btw4vvvgibty4IWNltoNhwPp4eHgYdZOfgIAALF++/I7fHzdunMHvSlRUFBYuXHjLPANLE0IgJiYGUVFR0rbly5eb9EZHVA+CyIZ98sknomnTpgKAACAee+wxUVpaKndZVm/MmDHSv1lBQYHc5dD/u3DhgmjRooX0s7nXl6Ojo8jNza1T24sXLzZ47YQJE0RZWZl5T+gOysrKxIQJEwzqWbx4sSy1UA32DJBNGzt2LL755hvp0+2+ffvw+OOPSxOR6PbYM2CdHnzwQcTExNR5/3nz5qFHjx512jc6Ohrx8fHS802bNsHX19filx1mZmbC19dXmiMAAAkJCYiOjrZoHXQTudMIkSkcOXJEeHh4SJ8yvLy8xMmTJ+Uuy2oNGjRI+re6fv263OWQnmPHjon77rvvnr0CDz/8sFE/u9TUVOHi4mLQVlhYmNl7CcrKysTkyZMNjuvq6irS0tLMelyqG4YBajTOnj0rOnXqJP2hcXd3FwcPHpS7LKsUEBAgAAgHBweh0+nkLoeEEFevXhVRUVHC0dGxTkMEu3btMvpYhYWFYsiQIQbtubu7i6ioKJGXl2e6kxJC5OXliaioKOHu7m5wvODgYFFUVGTSY5HxGAaoUfn9999Fz549pT84zZo1E9u2bZO7LKvj4+MjAIiWLVvKXYrd0+l04pNPPhEPPvigwZtl8+bN7xgEpkyZYpLjJiUl3dJLoFAoREhIiPj6669FZWWlUW1XVlaKr7/+WoSEhNxSu6urq1i/fj1DqJVRCCHzlFIiE7t69SpGjhyJ77//HkDNndzWr1+PSZMmyVuYFfH09MSFCxfQrl07nD9/Xu5y7NaJEycwc+ZMg+W1nZ2dMX/+fISGhqJnz564du2awWvatm2LkydPolWrViapoaioCNHR0di8eTO0Wq3B95RKJXr06IGAgAD06tULAQEB8PLyglKphLOzMyoqKqDRaFBUVITs7GwcOXIE2dnZOHbsGDQajUFbTk5OGD16NJYuXcpLB62R3GmEyBw0Go0YNWqUwSeS2NhYfhr5f66urgKA8PHxkbsUu3TlyhUxe/Zs4eDgYPB/9JlnnjHopn///fdv+WT92WefmaWm4uJiERsbKzp06FDnqxnq8tWxY0cRGxsriouLzVI3mQbDADVa1dXV4pVXXjH4wzRr1ixRXV0td2my0ul0okmTJgKA6NWrl9zl2BWdTic+/vhj8Ze//MXg/2WnTp1Eenr6LftXVFSIrl27Svs999xzZg+0VVVVYtu2bWLMmDHC29vbqADg7e0txowZI7Zt2yaqqqrMWi+ZBocJqFETQuC9997DW2+9JW0bO3Ys/vnPf0r3fLc3N27cQPPmzQEAgwYNwq5du2SuyD78/PPPeOWVV7B3715pm1KpxBtvvIHXX38dSqXytq87cOAAVCoVPDw8sHv3brRr185SJQOouYFQTk4OsrOzcfToUZSWlkKj0aCiogLOzs5QKpVwd3dHz549ERAQAH9/f7i5uVm0Rmo4hgGyC8nJyZg6dSqqq6sBAE899RT+/e9/2+W6/CUlJbj//vsBAM888wzS09NlrqhxU6vVWLBgAVatWiX9/wOAESNGID4+Hg899NA929BqtVAoFGjatKk5SyU7xkWHyC6EhYXhiy++kD59fffddxg0aBD++OMPmSuzPP07FtpjGLIUnU6HjRs3wsfHBytWrJCCQOfOnZGZmYkvvviiTkEAqJl8xyBA5sQwQHZj+PDh2LlzpzQLOycnB/3797/tHd8aM64+aH65ubkYMGAAJk2ahOLiYgBAs2bNsHTpUpw4cQIhISEyV0hkiGGA7EpQUBD27dsHT09PADW3fg0KCsLRo0dlrsxyGAbMp6ysDK+88goCAgJw4MABafsLL7yAU6dO4c0334Szs7OMFRLdHsMA2Z1u3brhwIED6NatGwCguLgYAwcOlNYlaOw4TGB6Op0OycnJ6NKlC1atWgWdTgcA6NKlC7Zv346tW7fCy8tL5iqJ7oxhgOxS+/btsXfvXgQFBQGoeYMMCQnB5s2bZa7M/NgzYFrZ2dkICgpCeHg4Ll26BAC47777EBsbi+PHj2PIkCEyV0h0bwwDZLfc3d3x7bffYvjw4QCAyspKjB07Fh999JHMlZkXw4BpXL58GZGRkejduzd++uknafuoUaNw6tQpzJ8/324vXyXbwzBAdq158+b497//jfDwcAA16xK8+uqrePPNN9FYr7rVHyZgGKi/6upqrFu3Dj4+Pli7dq30/+Svf/0rvvvuO2zevFmak0JkKxgGyO45OjoiKSnJYGGi9957D1OmTEFVVZWMlZmHfs8A5wzUz6FDh9C3b1+8/PLLuHz5MoCaQPWPf/wDubm5ePLJJ2WukMg4DANEABQKBZYsWYKPPvoICoUCQM1CRSNHjsT169dlrs60OExQf5cuXUJERAT69u2LI0eOSNvHjh2LU6dOISoqikMCZNMYBoj0vPLKK/j000+lP+zbtm3DU089hdLSUpkrMx2Ggbqrrq7GmjVr0KVLF6xfv14aEnjkkUewa9cufPLJJxZfHpjIHBgGiG4yatQoZGVlSV3oBw8exGOPPYb//Oc/MldmGry0sG5+/PFHBAYGYvr06SgrKwNQ8+8VHx+Po0ePYtCgQfIWSGRCDANEt/HEE09g9+7daNu2LQDg5MmT6NevH3755ReZK2s49gzcXXFxMcLCwtCvXz/k5ORI20NDQ3H69GnMnj2bSwNTo8MwQHQHPXv2xIEDB9C5c2cAwIULF/DYY49h//79MlfWMAwDt1dVVYWVK1fCx8cHKSkp0vZHH30Ue/bswccff4wHHnhAxgqJzIdhgOguOnXqhP379yMgIABAzR3onnrqKXz99dcyV2Y8DhPcav/+/ejVqxdmzpwJtVoNAHB1dcWKFSuQnZ2NAQMGyFsgkZkxDBDdw/33349du3bh6aefBgBoNBqMHDkSGzZskLky49T2DDRt2tTuZ8D/8ccfmDhxIh577DEcO3ZM2j5p0iScOXMGM2fOhKOjo4wVElkGwwBRHbi4uGDbtm0YO3YsgJq16KdMmYKlS5fa3OJEtWHAnocIqqqq8OGHH6JLly7YtGmTtN3Pzw/79+9HSkqKNF+EyB4wDBDVkZOTE1JTUzF79mxpW3R0NGbOnCndq94W2HsY2LNnD/z9/fHaa6/h6tWrAAA3NzesWrUKR44cke5XQWRPGAaI6qFJkyaIi4vDBx98IG1btWoVxowZg4qKChkrq7vaOQP2Nl/gv//9L0JDQzFw4EAcP35c2h4eHo4zZ85g+vTpcHBwkLFCIvkwDBDVk0KhwLx587Bx40bpzWPr1q1QqVS4cuWKzNXdnU6nw7Vr1wDYT89AZWUl4uLi4OPjg7S0NGl7QEAAfvzxR6xfvx4eHh4yVkgkP4YBIiNNmDABX3/9NZo3bw4A2LVrFwYNGoSLFy/KXNmd3bhxQ5rjYA9h4IcffoCfnx+ioqKk4RF3d3ckJibip59+Qp8+fWSukMg6MAwQNcDQoUPx/fffo3Xr1gCA3NxcBAUF4ezZszJXdnv2clnhhQsXMHbsWDzxxBP49ddfAdT06EydOhVnzpzByy+/zCEBIj0MA0QN1KdPH+zfvx9eXl4AgN9++w39+/c3uKGNtWjsCw5ptVp88MEH8PHxwaeffiptDwwMxE8//YS1a9dKwY2I/odhgMgEfHx8cODAAfj6+gIASkpKMGjQIOzYsUPmygw15jDw3XffoUePHpg/f740L6J169ZISkrCwYMH0bt3b5krJLJeDANEJtKuXTvs3btXWq3u2rVrGDZsGD755BOZK/ufxjhM8J///Acvvvginn76aZw6dQpAzZDAtGnTcObMGUyZMgVNmvBPHdHd8DeEyITc3Nywfft2jBgxAkDN4jbjxo1DQkKCvIX9v8bUM1BRUYH33nsPXbt2xdatW6Xtffv2xZEjR7B69Wq4u7vLWCGR7WAYIDKxZs2aYevWrZg6daq0bc6cOZg/f77sqxU2ljCwfft2dO/eHW+++SauX78OAPDw8EBycjL2798Pf39/mSsksi0MA0Rm4ODggMTERCxYsEDa9sEHH2DSpEmorKyUrS5PT0/p8SOPPCJbHcYqLCzE888/j5CQEOmKjSZNmmDmzJk4c+YMJk+ezCEBIiMohNwfVYgaucTERMyYMQM6nQ4AoFKp8Nlnn+G+++6zeC1CCPzrX/9CZWUlJkyYAIVCYfEajKHRaLBs2TK8++67uHHjhrT9sccew8qVK9GjRw8ZqyOyfQwDRBbw73//Gy+99JK0ZHFgYCC++eYbtGnTRubKrF9GRgZeffVV5OfnS9vatm2Lf/zjHwgNDbWZQENkzRgGiCxk9+7dePbZZ6Wb4/j4+GD79u3o0KGDWY5XVlaGnJwcZGdnIzc3F5cvX4ZGo4FWq4WTkxOUSiVat24NPz8/BAQEwN/fH61atTJLLcb47bff8Nprr+Hrr7+Wtjk4OGDmzJmIiYlBy5YtZayOqHFhGCCyoJ9//hkhISH4/fffAQAPPvggsrKy0L179wa3XV1djczMTKSlpeHQoUMoKCiodxudOnVCYGAgxo0bB5VKJcsqfTdu3MAHH3yA2NhYaDQaafvjjz+OlStXmuTfiogMMQwQWdi5c+cQHByMM2fOAABatmyJr7/+Go8//rhR7ZWUlGDDhg1ITExEYWGhyers0KEDIiMjER4ebrEb+aSnp2PWrFn47bffpG0PPPAAli1bhrFjx3JIgMhMGAaIZHDp0iUMGzYMhw4dAgA4Ozvjk08+wfPPP1/nNoqKivDWW29hy5Yt0Gq1Bt9TKpVS93+vXr0QEBAALy8vKJVKODk5QavVQqPRoLCwENnZ2dJXbm6uwadxAHBycsLo0aOxZMkSacllU8vPz8esWbPwzTffSNscHR0xa9YsvPPOO3B1dTXLcYno/wkiksWff/4pQkJCBAABQDRp0kSsWbPmnq/T6XRi3bp1wsXFRXotAKFQKIRKpRLp6emisrLSqJoqKytFenq6UKlUQqFQGLTv4uIikpKShE6nM6rt27l27Zp4++23hbOzs8GxnnjiCfHLL7+Y7DhEdHcMA0Qy0mq1Yvz48QZvhDExMXd8wy0sLBRDhgwx2N/d3V3MmzdP5Ofnm7S2vLw8MW/ePOHu7m5wvCFDhojCwsIGta3T6cQXX3whOnToYNB2u3btxKeffmrSwEFE98YwQCSz6upqMW/ePIM3xffee++W/VJTU2/pDQgLCxNqtdqs9ZWVlYmwsLBbegnS0tKMau/MmTMGPSIAhKOjo3j99ddFeXm5iasnorpgGCCyEnFxcdKbY9++fQ2+Fx8fb/Dm6enpKTIzMy1aX0ZGhmjXrp1BHQkJCXV+/Z9//inefPNN4eTkZNDGU089JU6ePGm+wononhgGiKzIV199JUaNGiV++OEHaduiRYsM3jwnTpxo9t6AOykrKxMTJkwwqGfx4sV3fY1OpxNbt24V7du3N3hd+/btxWeffcYhASIrwDBAZMVu7hFYuHCh7G+eOp1OxMTE1KmH4OTJk+Lpp5822Ldp06bijTfeEH/++adlCyeiO+KlhURWKi0tDaGhodLzZcuWYe7cuTJWZCguLg5RUVHS87S0NLz00ksAau6OuHjxYiQkJBjcmCk4OBgrVqxAly5dLF4vEd0ZwwCRFSoqKoKvry/Ky8sBADExMQZ3QLQWMTExWLhwIQDAxcUFJ06cQNu2bREUFIScnBxpvw4dOmD58uV47rnnuHAQkRViGCCyMkIIBAcH49tvvwUATJw4ESkpKVb5JiqEwKRJk7Bp0yYANZ/809LSpBswOTs74/XXX8ff//53NG/eXM5SieguGAaIrExSUhKmTp0KAPD09MSJEyes+qY8arUavr6+uHDhAoCa+lu0aIHjx49j8uTJ6Ny5s8wVEtG9MAwQWZGbhwcyMjKgUqlkrureMjMzMXToUAD/Gy4w19LFRGR6TeQugIj+Jzo6WgoCYWFhNhEEAEClUmHy5MkAgPLyckRHR8tcERHVB3sGiKxEcXEx2rdvD61WC3d3dxQUFNR7eOD999/H3//+9wbVMWbMGPzrX/+q9+vUajW8vb1RWloKJycnnD9/3mJ3OySihmHPAJGVSE5Olu4+GB4ebtQ8gcOHDze4Dn9/f6Ne5+bmhrCwMACAVqtFcnJyg2shIstgzwCRFaiuroa3tzcKCwuhUChw9uxZeHt717udgoICXL9+/bbfi4iIwI8//ogmTZrg8OHDcHJyuu1+7du3N3rCYn5+vjRhsGPHjsjLy4ODg4NRbRGR5TjKXQAR1UzAKywsBACEhIQYFQQAoFOnTnf83rlz5wAADz/8sNGf/u/F29sbISEhyMrKwrlz55CVlYVhw4aZ5VhEZDocJiCyAmlpadLj6dOnm7z94uJiXLx4EQDQo0cPk7evT7/+1NRUsx6LiEyDYYDIChw6dAgAoFQqERISYvL2jx07Jj02dxhQqVRQKpUATDOHgYjMj2GASGZlZWUoKCgAAPj5+cHR0fSjd5YMA46OjtIx8vPzoVarzXo8Imo4hgEimemv4R8QEGCWY1gyDACG56F/fkRknRgGiGSWnZ0tPe7Vq5dZjpGbmwsAaN26NTw9Pc1yDH3656F/fkRknRgGiGRW+0YNmKdnoKKiAqdOnQJgmV4BwPA8jh49apFjEpHxGAaIZHb58mXpsTnW8//1119RVVUFwHJhQP88SktLLXJMIjIewwCRzDQajfS4dha+KVl6vgBgeB7650dE1olhgEhmtUsQA7jjqoANIUcYcHZ2lh5XVFRY5JhEZDyGASKZ6QcA/WBgKrVhoGnTpujWrZvJ278d/QCgHwyIyDoxDBDJzNxd6rVhoGvXrmbpebgdcw99EJFpMQwQyax169bS46KiIpO2/Z///EeawOfn52fStu9G/zzc3d0tdlwiMg7DAJHM9N+kTX1NvhzzBQDD8+jZs6fFjktExmEYIJKZ/jX5R44cMWnbcoUB/fMw16qKRGQ6DANEMtO/nXBj7Bkw1+2Sich0GAaIZNaqVSt06tQJQM1qhLULBJlCbRh44IEH4OHhYbJ276aqqko6rre3N9zc3CxyXCIyHsMAkRUIDAwEUDMLPysryyRtXr9+HXl5eQAs2yuQmZkpXU3Qu3dvix2XiIzHMEBkBcaNGyc9Xr16tUnabN68OaqrqyGEQGZmpknarAv9+kNDQy12XCIynkIIIeQugsjeVVdXw9vbG4WFhVAoFDh79iy8vb3lLqve8vPz0blzZwBAx44dkZeXBwcHB5mrIqJ7Yc8AkRVwcHBAZGQkAEAIgbVr18pckXESExOlx5GRkQwCRDaCPQNEVqKkpASenp7QarVwd3dHQUEBWrZsKXdZdaZWq+Ht7Y3S0lI4OTnh/PnzFpu0SEQNw54BIivh4eGB0aNHA6i57e+cOXNkrqh+5syZI612OHr0aAYBIhvCngEiK1JUVARfX1+Ul5cDADIyMqBSqWSu6t4yMjIwbNgwAICLiwt++eUXtG/fXuaqiKiu2DNAZEW8vLwQFxcnPY+IiMCVK1dkrOje1Go1pk6dKj2Pj49nECCyMQwDRFZmypQpGDJkCADgwoULmDVrFqy1A08IgVmzZuHChQsAgODgYISHh8tcFRHVF8MAkZVRKBRISkqCi4sLAGDjxo1YtGiRzFXd3sKFC7Fp0yYANcMDSUlJUCgUMldFRPXFMEBkhby8vLBmzRrpeUxMjMHwgTWIi4vDwoULpeeJiYkcHiCyUQwDRFZq3LhxSEhIkJ5HRUVh4cKFsg8ZCCEQExODqKgoadvy5cvx0ksvyVgVETUEryYgsnJLlizB22+/LT2fMGECPvzwQ1luAKRWqzFr1ixpaAAAFi9ejOjoaIvXQkSmwzBAZAMSEhIM1h1o164dkpKSLHrZYWZmJiIiIqTJgrV1vfbaaxargYjMg8MERDZg9uzZSE1NlSYVXrhwAUOHDkV4eDjUarVZj61WqxEWFoahQ4dKQcDV1RVpaWkMAkSNBHsGiGxIUVERIiIisGPHDmmbu7s7wsLCEBkZadKbG+Xn5yMxMRHJycnSyoJAzeWDSUlJnCxI1IgwDBDZGCEENmzYgDlz5kgrFQI1lyQGBwdj+vTpUKlUcHR0rHfbVVVVyMzMxOrVq5GVlWXwPVdXV8THxyMsLIyXDxI1MgwDRDaqqKgI0dHR2Lx5M7RarcH3lEolevTogYCAAPTq1QsBAQHw8vKCUqmEs7MzKioqoNFoUFRUhOzsbBw5cgTZ2dk4duwYNBqNQVtOTk4YPXo0li5dyt4AokaKYYDIxpWUlCA5ORlr1qxBYWGhydrt2LEjIiMjERYWxpsOETVyDANEjUR1dTWysrKQmpqKw4cPIz8/v95teHt7o3fv3ggNDUVISAgcHBzMUCkRWRuGAaJGSq1WIycnB9nZ2Th69ChKS0uh0WhQUVEBZ2dnKJVKuLu7o2fPnggICIC/v78saxcQkfwYBoiIiOwc1xkgIiKycwwDREREdo5hgIiIyM4xDBAREdk5hgEiIiI7xzBARERk5xgGiIiI7BzDABERkZ1jGCAiIrJzDANERER2jmGAiIjIzjEMEBER2TmGASIiIjvHMEBERGTnGAaIiIjsHMMAERGRnWMYICIisnMMA0RERHaOYYCIiMjOMQwQERHZOYYBIiIiO8cwQEREZOcYBoiIiOwcwwAREZGdYxggIiKycwwDREREdo5hgIiIyM4xDBAREdk5hgEiIiI7xzBARERk5xgGiIiI7BzDABERkZ1jGCAiIrJzDANERER2jmGAiIjIzjEMEBER2TmGASIiIjvHMEBERGTnGAaIiIjsHMMAERGRnWMYICIisnMMA0RERHaOYYCIiMjOMQwQERHZOYYBIiIiO8cwQEREZOcYBoiIiOwcwwAREZGdYxggIiKycwwDREREdo5hgIiIyM4xDBAREdk5hgEiIiI7xzBARERk5xgGiIiI7BzDABERkZ1jGCAiIrJzDANERER2jmGAiIjIzjEMEBER2bn/A58WQszWm1QrAAAAAElFTkSuQmCC",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "USA_graph_under_policy = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Temperature, JetStream),\n",
+    "        (Temperature, Rainfall),\n",
+    "        (JetStream, Rainfall),\n",
+    "        (Temperature, WaterSupply),\n",
+    "        (Rainfall, WaterSupply),\n",
+    "    ],\n",
+    ")\n",
+    "USA_graph_under_policy.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b53c173e-b63d-4a1c-a781-27abd2216e63",
+   "metadata": {},
+   "source": [
+    "To do its forecasting, the United States government only has observational data from the US. Suppose that China, however, has already conducted experiments with using a carbon tax to pay for cloud seeding -- except that the timing of cloud seeding was not a function of the jet stream velocity but rather the occurrence of holidays and other special events that can be assumed to be independent of fluctuations in the jet stream. Furthermore, cities in China are more densely populated than in the United States, so we represent a different baseline distribution of reservoir volumes with a transportability node:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 32,
+   "id": "6f9057cb-4e1e-42ce-9865-b33639621d04",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABId0lEQVR4nO3de1xUZcIH8N8IDqM1hCjdBCTGSxfyAmj2Vu9b667MYLb3xYTQBnFHbV8VaM3ChNS23RWwm2CjUAq71Va7rxpD2l7a2jZFFBPNkiEZpTVIGCV1GGHO+wc7Z2fk4gBzg/P7fj589szMmed5jrmeH895LjJBEAQQERGRZA3zdQOIiIjItxgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkjmGAiIhI4hgGiIiIJI5hgIiISOIYBoiIiCSOYYCIiEjiGAaIiIgkLtDXDSAaSlpaWnDw4EFUVVWhuroaZ8+ehcVigdVqhVwuh0KhwOjRozF16lTExcUhNjYWo0aN8nWziUjiZIIgCL5uBNFg1dHRAYPBgLKyMuzfvx91dXV9LiM6OhozZsxAcnIyNBoNAgICPNBSIqKeMQwQ9UNTUxO2bduGoqIi1NfXu63ccePGQafTIS0tDWFhYW4rl4ioNwwDRH1gMpnw1FNP4c0334TVanX6TKFQiN3/8fHxiIuLQ2RkJBQKBeRyOaxWKywWC+rr61FVVSX+VFdXw2KxOJUll8uRlJSE9evXIzIy0puXSEQSxDBA5AJBELB161ZkZmaitbVVfF8mk0GtVmPp0qVQq9UIDOz7MJz29nZUVFRg8+bNqKiogOP/JZVKJfLz85GWlgaZTOaWayEiuhLDANFVmEwmpKenY8+ePeJ7oaGhSEtLg06nQ3R0tNvqMhqN2LJlC7Zt24bm5mbx/dmzZ0Ov17OXgIg8gmGAqBdlZWVYsmSJU2+AVqtFfn4+rrvuOo/VazabkZmZieLiYvE9pVKJoqIizJ8/32P1EpE0MQwQ9aCgoAAZGRni6/DwcOj1eqjVaq+1wWAwID09HQ0NDU7tWrFihdfaQERDHxcdIurGunXrnILAggULUFNT49UgAAAajQY1NTVITU0V31u5ciXWr1/v1XYQ0dDGMEB0hYKCAjz99NPi69zcXJSUlHj0sUBvQkJC8OqrryInJ0d8b82aNdi0aZNP2kNEQw8fExA5KCsrQ0pKivh648aNyMzM9GGLnOXl5SErK0t8XVZWxjEERDRgDANE/2YymRATEyMOFszJycHatWt93KqucnJykJubC6BzUGFNTQ1nGRDRgDAMEKFzHYGEhATs3bsXQOcYgZKSEr+c2y8IAhYuXIjt27cDABISEmAwGPyyrUQ0ODAMEAHQ6/VYvHgxgM5ZAzU1NT4bI+AKs9mMmJgYcZaBXq/HokWLfNwqIhqsGAZI8q58PFBeXg6NRuPjVl2dwWBAYmIiAD4uIKKB4WwCkrzs7GwxCGi12kERBIDOaYePPvooAKC1tRXZ2dk+bhERDVbsGSBJa2xsREREBKxWK0JDQ1FXV9fl8cCvf/1rPPHEEwOqZ968efj9738/oDK6YzaboVKp0NzcDLlcjtOnT3O3QyLqM/YMkKQVFxeLuw+mpaV1O06gsrJywPXExsYOuIzuhISEQKvVAgCsVqvT8sVERK5izwBJVkdHB1QqFerr6yGTyXDixAmoVKou59XV1eHixYvdlpGeno5PPvkEw4YNQ2VlJeRyebfnRUREeGxAotFoxPjx4wEAUVFRqK2tRUBAgEfqIqKhqe/7rRINEQaDAfX19QAAtVrdbRAA0OuuhCdPngQATJgwwWO//V+NSqWCWq1GRUUFTp48iYqKCsyZM8cnbSGiwYmPCUiyysrKxOOlS5f2+fuNjY04c+YMAGDKlClua1d/OLa/tLTUhy0hosGIYYAka//+/QAAhULRrw2IDh8+LB77OgxoNBooFAoA7hnjQETSwjBAktTS0oK6ujoAwNSpUxEY2PcnZv4UBgIDA8U2GI1GmM1mn7aHiAYXhgGSpIMHD4rHcXFx/SrDn8IA4HwdjtdHRHQ1DAMkSVVVVeJxfHx8v8qorq4GAIwePRrh4eHuaNaAOF6H4/UREV0NwwBJkv1GDvSvZ6CtrQ3Hjx8H4B+9AoDzdRw6dMiHLSGiwYZhgCTp7Nmz4nF/1vM/duwY2tvbAfhPGHC8jubmZh+2hIgGG4YBkiSLxSIe20fh90VfxgucOXMGMpkMd999d7efv/DCC5DJZBgxYkS3n9u3J/7Zz37Waz2O1+F4fUREV8MwQJJkX4IYQI+rBvamL2EgJCQEAHD+/Pkun3V0dGDTpk0AOm/gju2yy8/PBwBkZWX1Wk9QUJB43NbW1uu5RESOGAZIkhwDQHc34Kuxh4Hhw4fj9ttv7/VchUKBoKCgbsPAO++8gy+//BIREREAgHPnzjl9fuTIEbz//vu47777MGPGjF7rcQwAjsGAiOhqGAZIkgbapW4PA7feeqtLPQshISFdbvQAkJeXh9GjR2PJkiUAuoYBV3sFgIE/+iAi6WIYIEkaPXq0eGwymfr03VOnTokD9KZOnerSd0JCQvDtt9/CZrOJ73300UfYt28fli1bhhtvvBGAcxj4+uuv8fvf/x4TJ07E3Llzr1qH43WEhoa61C4iIoBhgCTK8Sbe1zn5/VlsKCQkBIIgoLW1VXxv48aNUCgUeOyxxxAcHAzAOQy89NJLaGtrQ0ZGBmQy2VXrcLyOadOmudQuIiKAYYAkynFO/oEDB/r03f6GAeA/gwhPnDiBXbt2YcGCBQgLCxO3N7aHgUuXLqGoqAhhYWFYsGCBS3U4Xkd/V1UkImliGCBJctxu2Fs9A8B/bvb2sQCZmZkA0KVnYPv27fjmm2+wdOlSl5//O16Hr7ZTJqLBiWGAJGnUqFGIjo4G0LkaoX0BIVfYw8BNN92EsLAwl77j2DPwzTff4LXXXsP3v/99TJgwAQDEngGz2QxBELBp0yaMGDECy5Ytc6n89vZ2sV0qlUqsj4jIFQwDJFn2qXoWiwUVFRUufefixYuora0F0LeVBx17BjZv3oxLly7h8ccfFz937BkwGAw4fvw4UlNTXQ4bBoNBnE0wffp0l9tFRAQwDJCEJScni8ebN2926TsjR45ER0cHBEGAwWBwuS57GGhsbMTLL7+Me+65x2lFQscwkJ+fD5lMhoyMDJfLd2x/SkqKy98jIgIYBkjCNBoNxo0bBwCoqKiA0Wj0WF32MPDyyy+jsbHRqVcAAK655hoEBgbiww8/xJ///GfMnTsXEydOdKlso9Eo9mxERUVBrVa7te1ENPQxDJBkBQQEQKfTAQAEQcCWLVs8Vpc9DFRWVmLSpEl46KGHupyjVCrFGQGuLDJkV1RUJB7rdDoEBAQMrLFEJDkMAyRpaWlp4gqC27Zt63aVQHdwHNDX07oB9kGEM2bMwH333edSuWazGcXFxQA6l1jWarUDbywRSQ7DAElaWFgYkpKSAHRu+9uX5/R9oVarIQgCBEHA4sWLuz3nyy+/hCAI2Ldvn8vlZmRkiKshJiUluTzgkIjIkUwQBMHXjSDyJZPJhJiYGHF1wPLycmg0Gh+36urKy8sxZ84cAJ2PGI4ePSpueERE1BfsGSDJi4yMRF5envg6PT3dY48L3MVsNjv1MOTn5zMIEFG/MQwQAVi0aBFmz54NAGhoaMDy5cvhr51mgiBg+fLlaGhoAAAkJCQgLS3Nx60iosGMYYAIgEwmg16vh1KpBAC89tpreOaZZ3zcqu7l5uZi+/btADofD+j1epc2MiIi6gnDANG/RUZGorCwUHydk5Pj9PjAH+Tl5SE3N1d8XVRUxMcDRDRgDANEDpKTk1FQUCC+zsrKQm5urs8fGQiCgJycHKf1BzZt2oT58+f7sFVENFRwNgFRN9avX481a9aIr1NTU/H888/7ZAMgs9mM5cuXi48GAGDdunXIzs72eluIaGhiGCDqQUFBgdO6A2PHjoVer/fqtEODwYD09HRxsKC9XStWrPBaG4ho6ONjAqIerFy5EqWlpeKgwoaGBiQmJiItLQ1ms9mjdZvNZmi1WiQmJopBIDg4GGVlZQwCROR27BkgugqTyYT09HTs2bNHfC80NBRarRY6nQ4qlcptdRmNRhQVFaG4uFhcWRDonD6o1+s5WJCIPIJhgMgFgiBg27ZtyMjIEFcqBDqnJCYkJGDp0qXQaDQIDAzsc9nt7e0wGAzYvHmzuPugXXBwMPLz86HVajl9kIg8hmGAqA9MJhOys7PxxhtvwGq1On2mUCgwZcoUxMXFIT4+HnFxcYiMjIRCoUBQUBDa2tpgsVhgMplQVVWFAwcOoKqqCocPH4bFYnEqSy6XIykpCRs2bGBvABF5HMMAUT80NTWhuLgYhYWFqK+vd1u5UVFR0Ol00Gq1qK6uxt/+9jdkZWVh1KhRTufZbDYIgsDtionILRgGiAago6MDFRUVKC0tRWVlJYxGY5/LUKlUmD59OlJSUqBWqxEQEACbzQalUomLFy/iu9/9Lvbu3QsA+Prrr/Hss89Cr9dj4sSJ2L9/v7gFMxFRfzEMELmR2WzGwYMHUVVVhUOHDqG5uRkWiwVtbW0ICgqCQqFAaGgopk2bhri4OMTGxva4dsGkSZPwxRdfAAD+9Kc/4ZNPPsELL7yAixcviud8/PHHuPvuu71xaUQ0hDEMEPmpHTt2IDU1FQAQEBCAjo6OLuf89a9/xf333+/llhHRUMN1Boj80KVLl3DmzBlxTEB3QQDonIlARDRQfZ8HRUQeY7VaUVxcjHXr1uGrr7666vkMA0TkDgwDRH6isbERDzzwAI4dO+bydxgGiMgd+JiAyE989NFHfQoCAHD58mUPtYaIpIRhgMhPaDQa3HPPPX36DnsGiMgdGAaI/MSIESPw17/+FU899ZTLSw8zDBCROzAMEPmR4cOHY/369fjzn/+Mm2+++arnMwwQkTswDBD5oQceeACHDx/GQw891Ot5DANE5A4MA0R+asyYMfjTn/6El156CUFBQd2ewzBARO7AMEDkx2QyGZYtW4b9+/fj9ttv7/K543bKRET9xTBANAhMnjwZlZWV+PnPf+70vn3vAiKigWAYIBokRo4ciaKiIhQWFiIgIAAymQwPP/ywr5tFREMANyoiGoSsVitsNhsUCoWvm0JEQwDDANEQ19LSIm6rXF1djbNnz8JiscBqtUIul0OhUGD06NGYOnWquK3yqFGjfN1sIvIihgGiIaajowMGgwFlZWXYv38/6urq+lxGdHQ0ZsyYgeTkZGg0GnH3RCIamhgGiIaIpqYmbNu2DUVFRaivr3dbuePGjYNOp0NaWhrCwsLcVi4R+Q+GAaJBzmQy4amnnsKbb74Jq9Xq9JlCoRC7/+Pj4xEXF4fIyEgoFArI5XJYrVZYLBbU19ejqqpK/KmurobFYnEqSy6XIykpCevXr0dkZKQ3L5GIPIxhgGiQEgQBW7duRWZmptN6AzKZDGq1GkuXLoVarUZgYN93Km9vb0dFRQU2b96MiooKOP4zoVQqkZ+fj7S0NJf3UCAi/8YwQDQImUwmpKenY8+ePeJ7oaGhSEtLg06nQ3R0tNvqMhqN2LJlC7Zt24bm5mbx/dmzZ0Ov17OXgGgIYBggGmTKysqwZMkSp94ArVaL/Px8XHfddR6r12w2IzMzE8XFxeJ7SqUSRUVFmD9/vsfqJSLPYxggGkQKCgqQkZEhvg4PD4der4darfZaGwwGA9LT09HQ0ODUrhUrVnitDUTkXlyBkGiQWLdunVMQWLBgAWpqarwaBABAo9GgpqYGqamp4nsrV67E+vXrvdoOInIfhgGiQaCgoABPP/20+Do3NxclJSUefSzQm5CQELz66qvIyckR31uzZg02bdrkk/YQ0cDwMQGRnysrK0NKSor4euPGjcjMzPRhi5zl5eUhKytLfF1WVsYxBESDDMMAkR8zmUyIiYkRBwvm5ORg7dq1Pm5VVzk5OcjNzQXQOaiwpqaGswyIBhGGASI/JQgCEhISsHfvXgCdYwRKSkr8cm6/IAhYuHAhtm/fDgBISEiAwWDwy7YSUVcMA0R+Sq/XY/HixQA6Zw3U1NT4bIyAK8xmM2JiYsRZBnq9HosWLfJxq4jIFQwDRH7oyscD5eXl0Gg0Pm7V1RkMBiQmJgLg4wKiwYSzCYj8UHZ2thgEtFrtoAgCQOe0w0cffRQA0NraiuzsbB+3iIhcwTBA5GcaGxvxxhtvAOhcYjg/P9+l7+3evRsymQwymQy/+93vej33vffeE8+VyWTo6Ojo9fwf/ehHkMlkGD58OE6dOtXrufn5+QgNDQUAvPHGG2hqanKp/UTkOwwDRH6muLhY3H0wLS3N5XECjuedP3++13NfeOEFp9e9nW8ymbBz504AwA9+8ANERET0WnZISAi0Wi0AwGq1Oi1fTET+iWGAyI90dHSgqKgIQOfugz//+c9d/q6rYaC2thYGg8Hpvd7OLywsFHsO/vd//9eltuh0OvG4qKjoqj0PRORbDANEfsRgMKC+vh4AoFaroVKpXP6uq2HgpZdegiAICAkJEd87d+5ct+e2tbVh69atAICpU6fivvvuc6ktKpVKXCb55MmTqKiocOl7ROQbDANEfqSsrEw8Xrp0aZ++60oY+Pbbb1FSUgIA+MUvfnHV819//XV88803Xc53hWP7S0tL+/RdIvIuhgEiP7J//34AgEKh6PMGRMHBweIiPz3d3F977TWcP38ecrkcy5Ytw8iRI3s9/6WXXgIAjBkzps9LDGs0GigUCgBAZWVln75LRN7FMEDkJ1paWlBXVwegs0s+MDCwT98fNmwYlEolgO5v7oIgiDf3efPm4YYbbkBwcDCA7h8TfPLJJzhw4AAAID09XbyxuyowMBBTpkwBABiNRpjN5j59n4i8h2GAyE8cPHhQPI6Li+tXGfZHBd2Fgb179+L48eMAgOXLlwOAGAa6O98eHAIDA7FkyZJ+tcfxOhyvj4j8C8MAkZ+oqqoSj+Pj4/tVhj0M2BcscvTiiy8CAO655x7ExsYCQI89CY2NjfjDH/4AwLXphD1xvA7H6yMi/8IwQOQnqqurxeP+9gzYZwhceXM3Go0oLy8H8J9eAQA9PiZ45ZVXxLUOXJ1O2B3H6zh06FC/yyEiz2IYIPITZ8+eFY/7u55/T48JXn75ZdhsNkREROCHP/yh+H53jwna29uxZcsWAH2bTtgdx+tobm7udzlE5FkMA0R+wmKxiMd9Haxn110YuHDhgrgK4LJly5wGJnb3mOBPf/oTTp8+DaDv0wmv5HgdjtdHRP6FYYDIT9i75QFALpf3qwx7GLhw4QJsNhsAYPv27Th37hxGjBiB9PR0p/O7e0xgH1vQn+mEVwoKChKP29raBlQWEXkOwwCRn3AMAI7BoC/sYwYEQRAHEdpv7ikpKeIGQnZXPiY4cuQI/v73vwPo33TCKzkGAMdgQET+pW8TmYnIY67sUu/PzfPKVQgrKyvx2WefAeh+IOCVYcAd0wkduePRBxF5HnsGiPzE6NGjxWOTydSvMq4MA/bdCWfNmoWYmJgu59vHDJw7dw5ms1lcDnkg0wkdOV7Hlb0SROQ/GAaI/MTUqVPF4/7OyXcMA4cPH8a7774LoOfpgY49AyUlJbhw4UKv5/eV43VMmzbNLWUSkfsxDBD5Ccc5+fZlgPvKcSfC5557DjabDdHR0XjwwQe7Pd9xAOHmzZsBDHw6oSPH6+jv2glE5HkcM0DkJ+yrAgLu6Rk4cuQIgM7pgcOGdZ/77Y8JLBYLamtrxfPdxfE6HK/PFZcvX8aJEydw5MgRHDlyBEajEd/97neRlpbmtvYRUSeZIAiCrxtBRJ1UKhXq6uqgUCjQ2tra582Kjh496jQ2QKlU4vTp02IPwJUqKysxY8YM8fWYMWNw6tQptwz2a29vh1KphMVigUqlEsPGlQRBwKlTp1BTUyPe+I8cOYLjx493O6uiubkZo0aNGnD7iOg/2DNA5EdmzJiBuro6WCwWVFRU9Ni93xPHngEAWLBgQY9BAECXz9wxndDOYDCIswmmT5/u9NnHH3+MrKwsnD17FmfOnOlxC+UrXXvttbjmmmvc0j4i+g+GASI/kpycjNdffx0AsHnz5j6HgfDwcPSls2/SpEl9Or8v7GMQgM41Dhw99NBDTssvuyohIaHfCzIRUc84gJDIj2g0GowbNw4AUFFRAaPR6OMW9Y/RaERFRQUAICoqCmq12ulz+zX21dy5cwfcNiLqimGAyI8EBARAp9MB6HyWbt8waLApKioSj3U6HQICApw+/8c//oF77723T2XKZDJoNBq3tI+InHEAIZGfaWpqQnh4OKxWK0JDQ1FXV9dlLIA/M5vNUKlUaG5uhlwux+nTpxEWFtblvPb2dixatAivvfaay2Xfd999ePDBBzF37lzceuutkMlk7mw6kWSxZ4DIz4SFhSEpKQlA58j5jIwMH7eobzIyMsTtipOSkroNAkDnksfFxcV9msr44YcfYtWqVbj99tsxfvx4rFixAu+//36/93Igok7sGSDyQyaTCTExMeJmQ+Xl5YOii7y8vBxz5swB0Dmt8ejRo1dd1lgQBKxduxbr1q3r9byoqCicPHmy28+USiUSEhIwd+5caDSaHgMIEXWPYYDIT+n1eixevBgAMHbsWBw9etSvHxeYzWbExMSgoaEBQGf7Fy1a5PL38/LykJWV1e1nkZGROHnyJGpra7F7927s3r0bf//739He3t7lXJlMhrvvvlt8nHDHHXfwccIAtLS04ODBg6iqqkJ1dTXOnj0Li8UCq9UKuVwOhUKB0aNHY+rUqYiLi0NsbCzXgRiMBCLySzabTZg9e7YAQAAgLFiwQLDZbL5uVrdsNpuQmpoqtjUhIaFfbX3llVcEmUwmlmP/WbZsWZdzW1pahNdff11ISUkRQkNDu3zH/hMVFSU89thjQkVFhWCxWNxxuUNae3u7sGvXLmHevHlCdHR0j3+uvf1ER0cL8+bNE3bt2iW0t7f7+pLIBQwDRH6svr5eUCqV4j+yOTk5vm5St9auXSu2UalUCiaTqd9lvf7660JgYKDTzcVgMPT6ncuXLwsffvih8Mtf/lK4/fbbe7xJXXPNNcIPf/hDobi4WDhz5ky/2zgUNTY2Cr/61a+EcePG9SsA9PQzbtw44Ve/+pXQ2Njo60ukXjAMEPm50tJSp39cN27c6OsmOdm4caNT+8rKygZc5rvvvisoFAoBgHDdddcJly5d6tP3jUaj8Pzzzwvf/e53heHDh3d7k5LJZMJdd90lrFu3TqiurvbbXhdPq6+vF1JSUgS5XN7lz0ihUAgzZ84Uli1bJpSUlAiffvqpYDabBYvFIthsNsFisQhms1k4fPiwUFxcLCxbtkyYOXOm+N/O8UculwuPPPKIUF9f7+tLpm4wDBANAgUFBU7/sObk5Pj85mWz2Zx6BAAImzZtclv5+/btExYuXCi89957Ayrn3Llzwh/+8AdhwYIFwpgxY3r8DTYiIkJYsmSJ8O677/Y5fAxGNptNeOWVV5x6nuwhSaPRCLt27RIuX77cr7IvX74s7Nq1S9BoNF0e+yiVSkGv1/v87y85YxggGiTWrVvn9I9qamqq0NLS4pO2tLS0OI0RACCsW7fOJ23pi/b2duHjjz8WVq9eLdx55509BoORI0cKDz30kKDX64WvvvrK1812u/r6eqfxKACE0NBQ4fHHHxeMRqNb66qtrRUef/zxLuM6Zs+ezV4CP8IwQDSI5OfnO/2DOnbsWKG8vNyrbSgvLxfGjh3r1I6CggKvtsFdvvzyS+Gll14SEhISuu0mt//Ex8cLOTk5QlVV1aD/jba0tLRLb4BWqxXMZrNH621paRG0Wm2XXgJ3PFaigWMYIBpkevrH3NO9BC0tLcKjjz7qVG9wcPCQ+ce8tbVVeOeddwStVivccMMNPQaDm2++WVi8eLGwc+dO4cKFC75udp9cGSbDw8OvOjjT3YZSmBxKGAaIBqGeunmzsrKE2tpat9ZVW1srZGVldenmTUhIGNCsAX/W0dEh7Nu3T8jOzhamTp3aYzBQKBTCnDlzhKKiIuHUqVO+bnavnnnmGae2L1iwwOO9AT0ZrI+ZhjKGAaJBymazCXq9vtsBYGq1Wti5c+eABoDt3LlTUKvVXW6AwcHBwtatWwd9d3lfmEwmobCwUEhMTBSCgoJ6DAfTpk0T1qxZI+zfv1/o6OjwdbNFV/YI5Obm+vy/n81mE3JycthD4Ce4AiHRIGcymZCdnY033nijyxr9CoUCU6ZMQVxcHOLj4xEXF4fIyEgoFAoEBQWhra0NFosFJpMJVVVVOHDgAKqqqnD48GFYLBansuRyOZKSkrBhw4arLjE8lF24cAF//vOfxZUQ//Wvf3V73o033og5c+bgwQcfxPe+9z1cc801Xm5pp7KyMqSkpIivN27ciMzMTJ+0pTtXrjxZVlaG+fPn+7BF0sQwQDRENDU1obi4GIWFhaivr3dbuVFRUdDpdNBqtVzz/wo2mw2HDh3Crl27sHv3blRVVXV7XlBQEB544AHMnTsXDz74ICIjI73Sviv3uMjJycHatWu9Undf5OTkIDc3F0DnPhM1NTVe+zOiTgwDRENMR0cHKioqUFpaisrKShiNxj6XoVKpMH36dKSkpECtViMgIMADLR16GhoaUF5ejl27duH999/HpUuXuj1v8uTJ4t4J06dP98ifryAISEhIwN69ewEACxYsQElJiV/u0yAIAhYuXIjt27cDABISEmAwGPyyrUMVwwDREGc2m8WNZg4dOoTm5mZYLBa0tbUhKCgICoUCoaGhmDZtmrjRTEhIiK+bPehdunQJf/nLX7B7927s2rVL3MDpSmFhYeLjhNmzZ0OpVLqlfseNrsLDw1FTUzOkN7qigWEYIBqkdu7ciQ8//BDr169HUFCQr5tDvRAEAYcPHxYfJ+zfv7/b84YPH477779ffJxwyy239Ku+wboFtsFgQGJiIgA+LvA2hgGiQUYQBDz55JN47rnnAABz587Fzp07fdwq6oszZ86IjxP27NmDixcvdnveHXfcIT5OmDlzpsuPE1JTU7Fjxw4AgFarxbZt29zWdk/TarUoKSkBADzyyCPiowPyLIYBokHk/PnzePTRR/HOO++I78XGxvY4cI38n8Viwd/+9jfxcYLJZOr2vNGjRyMxMREPPvggEhISeuzyb2xsREREBKxWK0JDQ1FXV+eWxwNFRUVYsmRJt5+NHDkSEREReOCBB7BixQpMmjSp3/WYzWaoVCo0NzdDLpfj9OnTHLjqBcN83QAics3x48dx1113OQUBAJKe5jcUKBQKqNVqvPTSSzh58iQOHz6MDRs2YObMmU4D6M6ePYsdO3YgKSkJY8aMwaxZs7Bp0yacPHnSqbzi4mJximlaWprbxglUV1f3+NnFixfx+eefo6ioCFOmTMHbb7/d73pCQkKg1WoBAFarFcXFxf0ui1zHngGiQeCdd97BggUL8O2333b57Ac/+AH++Mc/+qBV5GmNjY0oLy/H7t278d5773X731+hUOC9997Df//3f6OjowMqlQr1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",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "China_population = Variable(r\"\\text{China}\")\n",
+    "China_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Temperature, JetStream),\n",
+    "        (Temperature, Rainfall),\n",
+    "        (Temperature, WaterSupply),\n",
+    "        (Rainfall, WaterSupply),\n",
+    "        (transport_variable(WaterSupply), WaterSupply),\n",
+    "    ],\n",
+    ")\n",
+    "China_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2ce6f76d-fdfa-4c1a-969f-82945fa5e2ac",
+   "metadata": {},
+   "source": [
+    "Finally, we have data from a cloud seeding experiment in South Africa in which clouds were seeded whenever cities experienced heat domes, but which did not involve the imposition of a carbon tax. South Africa is in the Southern Hemisphere, so the jet stream will move differently than in the case of the United States and China and we attach a transportability node to the `JetStream` variable. Additionally, the absence of a carbon tax implies no intervention in the temperatures, so the bidirectional edge between `Temperature` and `JetStream` remains. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 33,
+   "id": "13f071a1-9ef1-4046-878e-d575d98ffa0b",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "South_Africa_population = Variable(r\"\\text{SA}\")\n",
+    "South_Africa_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (Temperature, JetStream),\n",
+    "        (JetStream, Rainfall),\n",
+    "        (Temperature, Rainfall),\n",
+    "        (Temperature, WaterSupply),\n",
+    "        (Rainfall, WaterSupply),\n",
+    "        (transport_variable(JetStream), JetStream),\n",
+    "    ],\n",
+    "    undirected=[(Temperature, JetStream)],\n",
+    ")\n",
+    "South_Africa_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "5a1fe0a5-81be-49e2-abb2-11916118ca9f",
+   "metadata": {},
+   "source": [
+    "Suppose the United States would like to know the amount of water that New York City would have had in its reservoirs this year were it to have implemented a carbon tax in 2019 and begun cloud seeding to compensate for the effects of heat domes. We frame an unconditional counterfactual query:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 34,
+   "id": "47a890ec-c1c7-4e0e-b0f3-5e61e3269c65",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(A^{-}_{T^{-}}, T^{+})$"
+      ],
+      "text/plain": [
+       "P(-A @ -T, +T)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\sum\\limits_{C, D} P^{\\text{Drug}}(D) P^{\\text{Drug}}(T | D) P^{\\text{Ghana}}(A | C, T, D) P^{\\text{Ghana}}(C | T)$"
+      ],
+      "text/plain": [
+       "Sum[C, D](PP[\\text{Drug}](D) * PP[\\text{Drug}](T | D) * PP[\\text{Ghana}](A | C, T, D) * PP[\\text{Ghana}](C | T))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "effect_of_do_art_on_aids_query = [-AIDS @ -Therapy, +Therapy]\n",
+    "\n",
+    "art_for_aids_domains = [\n",
+    "    # CFTDomain(\n",
+    "    #     graph=nigeria_graph,\n",
+    "    #     population=nigeria_population,\n",
+    "    # ),\n",
+    "    CFTDomain(\n",
+    "        population=ghana_population,\n",
+    "        graph=ghana_with_delivery_graph,\n",
+    "        policy_variables={Therapy},\n",
+    "    ),\n",
+    "    CFTDomain(\n",
+    "        population=drug_user_population,\n",
+    "        graph=drug_user_graph,\n",
+    "    ),\n",
+    "]\n",
+    "\n",
+    "do_art_on_aids_result = unconditional_cft(\n",
+    "    event=effect_of_do_art_on_aids_query,\n",
+    "    target_domain_graph=nigeria_graph,\n",
+    "    domains=art_for_aids_domains,\n",
+    ")\n",
+    "do_art_on_aids_result.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 35,
+   "id": "faa48491-79fb-4537-99bb-ceead4c4768a",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P_{T^{-}}(W^{-})$"
+      ],
+      "text/plain": [
+       "P[T](-W)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\sum\\limits_{J, R} P^{\\text{China}}(J | T) P^{\\text{SA}}(W | J, T, R) \\sum\\limits_{W} P^{\\text{USA}}(R | J, T) P^{\\text{USA}}(W | J, T, R)$"
+      ],
+      "text/plain": [
+       "Sum[J, R](PP[\\text{China}](J | T) * PP[\\text{SA}](W | J, T, R) * Sum[W](PP[\\text{USA}](R | J, T) * PP[\\text{USA}](W | J, T, R)))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "effect_of_do_tax_query = -WaterSupply @ -Temperature\n",
+    "\n",
+    "do_tax_domains = [\n",
+    "    CFTDomain(\n",
+    "        population=USA_population,\n",
+    "        graph=USA_graph,\n",
+    "    ),\n",
+    "    CFTDomain(\n",
+    "        population=China_population,\n",
+    "        graph=China_graph,\n",
+    "        policy_variables={Temperature, Rainfall},\n",
+    "    ),\n",
+    "    CFTDomain(\n",
+    "        population=South_Africa_population,\n",
+    "        graph=South_Africa_graph,\n",
+    "        policy_variables={Rainfall},\n",
+    "    ),\n",
+    "]\n",
+    "\n",
+    "do_tax_result = unconditional_cft(\n",
+    "    event=effect_of_do_tax_query,\n",
+    "    target_domain_graph=USA_graph_under_policy,\n",
+    "    domains=do_tax_domains,\n",
+    ")\n",
+    "do_tax_result.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 36,
+   "id": "fec156b3-8f8d-48c0-9102-4147aae799e5",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Sum[J, R](PP[\\text{China}](J | T) * PP[\\text{SA}](W | J, T, R) * Sum[W](PP[\\text{USA}](R | J, T) * PP[\\text{USA}](W | J, T, R)))\n"
+     ]
+    }
+   ],
+   "source": [
+    "# I need to check this calculation by hand in any case, but I'm not sure I like the fact that we're summing over W here.\n",
+    "# (Answer: yes, this is correct! We sum over W for the US case because our query is on R, R is in a district containing R and W,\n",
+    "# and none of the ancestors of R in G[R,W] include W. Therefore the Tian-Pearl Identify algorithm sums over W before returning.\n",
+    "print(str(do_tax_result.expression))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a5203bfc-0c1b-43d8-a229-049784552010",
+   "metadata": {},
+   "source": [
+    "We can see that the probability of the water supply having value `-W` in 2024, given the carbon tax adjusted the temperature to `-T` in 2019, is identifiable and is a composite of experimental results from China and South Africa and observational data from the United States. Note that without the data from China and South Africa, the query is not identifiable:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 37,
+   "id": "37c44937-aa4b-44af-a67f-18c4b8bef902",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 37,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "effect_of_do_tax_query = -WaterSupply @ -Temperature\n",
+    "\n",
+    "do_tax_domains_US_only = [\n",
+    "    CFTDomain(\n",
+    "        population=USA_population,\n",
+    "        graph=USA_graph,\n",
+    "    ),\n",
+    "]\n",
+    "\n",
+    "do_tax_result_US_only = unconditional_cft(\n",
+    "    event=effect_of_do_tax_query,\n",
+    "    target_domain_graph=USA_graph_under_policy,\n",
+    "    domains=do_tax_domains_US_only,\n",
+    ")\n",
+    "do_tax_result_US_only is None"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "df7f86c9-a178-4494-8012-e798b5f0d385",
+   "metadata": {},
+   "source": [
+    "Let us also specify a more targeted conditional query: we would like to know the water supply in a counterfactual world where the tax had been implemented, for areas that receive a certain amount of rainfall (also affected by the tax). "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 38,
+   "id": "147b53fb-e26b-45e3-8cd3-3a34bd52938e",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/latex": [
+       "$P(R^{-}, W^{-})$"
+      ],
+      "text/plain": [
+       "P(-R, -W)"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/latex": [
+       "$\\frac{P^{\\text{SA}}(W | J, T, R)}{\\sum\\limits_{W} P^{\\text{SA}}(W | J, T, R)}$"
+      ],
+      "text/plain": [
+       "((PP[\\text{SA}](W | J, T, R) / Sum[W](PP[\\text{SA}](W | J, T, R))))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "conditional_do_tax_result = conditional_cft(\n",
+    "    outcomes=-WaterSupply @ -Temperature,\n",
+    "    conditions=-Rainfall @ -Temperature,\n",
+    "    target_domain_graph=USA_graph_under_policy,\n",
+    "    domains=do_tax_domains,\n",
+    ")\n",
+    "conditional_do_tax_result.display()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 39,
+   "id": "2526b567-d748-4fc1-9f8a-91c591c3c634",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "((PP[\\text{SA}](W | J, T, R) / Sum[W](PP[\\text{SA}](W | J, T, R))))\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(str(conditional_do_tax_result.expression))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d1037cf0-dd8d-432f-9e1e-c30a3694f36a",
+   "metadata": {},
+   "source": [
+    "## User interface demonstration"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "25aae618-4841-4cd3-bb8d-c97ab048ec77",
+   "metadata": {},
+   "source": [
+    "The following exercise is from a tutorial at Pacific Northwest National Laboratory that demonstrates that detailed validation of user inputs makes using the software easy. We've deliberately included some typos in the problem solution and show the software catching them. Feel free to experiment with the input parameters and generate other errors!\n",
+    "\n",
+    "1. Construct a graph with the following edges:\n",
+    "   ```\n",
+    "   X1 -> Z,\n",
+    "   R -> Z,\n",
+    "   Z -> W,\n",
+    "   W -> Y,\n",
+    "   W <-> Z\t\n",
+    "   ```\n",
+    "2. Consider you have available observational data from the target domain, and an experiment from Domain 1 in which $W$ is fixed to be only a function of $Z$ (breaking the bidirected edge between $W$ and $Z$), and $R$ is drawn from a different population than in the target domain.\n",
+    "3. Estimate $P(Y_{X1},W_{R}|X1)$ with the values ${-Y,-W,-X}$ respectively."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 40,
+   "id": "2f344daa-1446-4844-8dc8-a682486ec2ac",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABAeklEQVR4nO3de1xUdd4H8M9wGSZbFFG3WhVZxq3WKBXw0m57adcVRh/3UirkNUGMMBdEbXeVEtIe3VJB1xRFMA221C6mxpDd1rZ9MhWF1G4COfPoqwKBMbwMw8B5/uDFeWa4w5yZMzPn8369fDWXM+d8D2jzOb/bUQmCIICIiIgUy0fuAoiIiEheDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArHMEBERKRwDANEREQKxzBARESkcAwDRERECscwQEREpHAMA0RERArnJ3cBRETkPurq6nD69GmUlJSgtLQUNTU1MJvNsFgsUKvV0Gg0GDRoEMaMGYPIyEhERERg4MCBcpdNDlIJgiDIXQQREcmjqakJer0ehYWFOHHiBCorK3u9j7CwMIwfPx6zZ8+GTqeDr6+vEyolZ2IYICJSoOrqauTl5SEnJwcGg0Gy/Y4YMQJJSUlISEjAkCFDJNsvORfDABGRghiNRqxatQr79++HxWKxe0+j0YjN/1FRUYiMjERISAg0Gg3UajUsFgvMZjMMBgNKSkrEP6WlpTCbzXb7UqvViI2Nxdq1axESEuLKU6Q+YBggIlIAQRCwa9cuLFu2DPX19eLr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",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "example_3_target_domain_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (X1, Z),\n",
+    "        (R, Z),\n",
+    "        (Z, W),\n",
+    "        (W, Y),\n",
+    "    ],\n",
+    "    undirected=[(Z, W)],\n",
+    ")\n",
+    "example_3_target_domain_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 41,
+   "id": "be995896-111d-4731-900d-91ac2fcba9e8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "example_3_break_w_graph = NxMixedGraph.from_edges(\n",
+    "    directed=[\n",
+    "        (X1, Z),\n",
+    "        (R, Z),\n",
+    "        (Z, W),\n",
+    "        (W, Y),\n",
+    "        (transport_variable(R), R),\n",
+    "    ],\n",
+    ")\n",
+    "# example_3_break_w_graph.draw()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 42,
+   "id": "2286a2b3-58ab-4da7-a377-58eabf5e6830",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "error In _validate_transport_conditional_counterfactual_query_input: one of the input conditioned variables is not in the target domain graph. Check your inputs. \n"
+     ]
+    }
+   ],
+   "source": [
+    "example_3_outcomes = [-Y @ -X1, -W @ -R]\n",
+    "example_3_conditions = [-X]  ## NOTE: Should be X1\n",
+    "\n",
+    "example_3_target_domain_graph_topo = list(example_3_target_domain_graph.topological_sort())\n",
+    "example_3_break_w_graph_topo = list(example_3_break_w_graph.topological_sort())\n",
+    "example_3_domains = [\n",
+    "    CFTDomain(\n",
+    "        population=Pi1,\n",
+    "        graph=example_3_break_w_graph,\n",
+    "        ordering=example_3_break_w_graph_topo,\n",
+    "        policy_variables={W},\n",
+    "    ),\n",
+    "    CFTDomain(\n",
+    "        population=TARGET_DOMAIN,\n",
+    "        graph=example_3_target_domain_graph,\n",
+    "        # This is an intentionally incorrect topological ordering. To fix, do so manually or comment out the line entirely.\n",
+    "        ordering=[W, X1, R, Z, Y],\n",
+    "    ),\n",
+    "]\n",
+    "try:\n",
+    "    result = conditional_cft(\n",
+    "        outcomes=example_3_outcomes,\n",
+    "        conditions=example_3_conditions,\n",
+    "        target_domain_graph=example_3_target_domain_graph,\n",
+    "        domains=example_3_domains,\n",
+    "    )\n",
+    "    result.display()\n",
+    "except ValueError as e:\n",
+    "    print(\"error\", e)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.12.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/y0/algorithm/counterfactual_transport/__init__.py b/src/y0/algorithm/counterfactual_transport/__init__.py
new file mode 100644
index 0000000..15b8dd6
--- /dev/null
+++ b/src/y0/algorithm/counterfactual_transport/__init__.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+"""Implementation of counterfactual transportability.
+
+.. [correa22a] https://proceedings.mlr.press/v162/correa22a/correa22a.pdf.
+"""
+
+from .api import (
+    CFTDomain,
+    ConditionalCFTResult,
+    Event,
+    UnconditionalCFTResult,
+    conditional_cft,
+    transport_conditional_counterfactual_query,
+    transport_unconditional_counterfactual_query,
+    unconditional_cft,
+)
+
+__all__ = [
+    "unconditional_cft",
+    "conditional_cft",
+    "transport_unconditional_counterfactual_query",
+    "transport_conditional_counterfactual_query",
+    #
+    "Event",
+    "CFTDomain",
+    "ConditionalCFTResult",
+    "UnconditionalCFTResult",
+]
diff --git a/src/y0/algorithm/counterfactual_transport/ancestor_utils.py b/src/y0/algorithm/counterfactual_transport/ancestor_utils.py
new file mode 100644
index 0000000..5359619
--- /dev/null
+++ b/src/y0/algorithm/counterfactual_transport/ancestor_utils.py
@@ -0,0 +1,599 @@
+"""Utilities for getting ancestral components."""
+
+import logging
+from collections import defaultdict
+from itertools import combinations_with_replacement
+from typing import Iterable
+
+from y0.dsl import CounterfactualVariable, Intervention, Variable
+from y0.graph import NxMixedGraph
+
+__all__ = [
+    "get_ancestors_of_counterfactual",
+    "minimize_counterfactual",
+    "get_ancestral_components",
+]
+
+logger = logging.getLogger(__file__)
+
+
+def get_ancestors_of_counterfactual(event: Variable, graph: NxMixedGraph) -> set[Variable]:
+    """Get the ancestors of a counterfactual variable.
+
+    This follows [correa22a]_, Definition 2.1 and Example 2.1.
+    If the input variable has no interventions, the problem reduces to getting
+    ancestors in a graph.
+
+    :param event: A single counterfactual variable.
+    :param graph: The graph containing it.
+    :returns:
+        A set of counterfactual variables. [correa22a]_ consider
+        a "counterfactual variable" to also include variables with no interventions.
+
+        .. note::
+
+            In our case, we allow our returned set to include the "Variable" class for
+            $Y_0$ syntax, and should test examples including ordinary variables as
+            ancestors.
+    :raises TypeError:
+        get_ancestors_of_counterfactual only accepts a single Variable or CounterfactualVariable
+    """
+    # There's a TypeError check here because it is easy for a user to pass a set of variables in, instead of
+    # a single variable.
+    if not isinstance(event, Variable):
+        raise TypeError(
+            "This function requires a variable, usually a counterfactual variable, as input."
+        )
+
+    # logger.debug("In get_ancestors_of_counterfactual: input = " + str(event))
+    if not isinstance(event, CounterfactualVariable):
+        return graph.ancestors_inclusive(event)
+
+    # This is the set of variables X in [correa22a]_, Definition 2.1.
+    intervention_variables = {intervention.get_base() for intervention in event.interventions}
+    intervention_values = {intervention for intervention in event.interventions}
+
+    graph_minus_in = graph.remove_in_edges(intervention_variables)
+    ancestors = graph.remove_out_edges(intervention_variables).ancestors_inclusive(event.get_base())
+
+    ancestors_of_counterfactual_variable: set[Variable] = set()
+    for ancestor in ancestors:
+        candidate_interventions_z = {
+            value
+            for value in intervention_values
+            if value.get_base() in graph_minus_in.ancestors_inclusive(ancestor)
+        }
+        # TODO: graph_minus_in.ancestors_inclusive(candidate_ancestor) returns variables.
+        # intervention_variables are Interventions, which are a type of Variable.
+        # Will these sets intersect without throwing errors?
+        if candidate_interventions_z:
+            ancestors_of_counterfactual_variable.add(ancestor.intervene(candidate_interventions_z))
+        else:
+            ancestors_of_counterfactual_variable.add(ancestor)
+    # logger.debug(
+    #    "In get_ancestors_of_counterfactual: output = " + str(ancestors_of_counterfactual_variable)
+    # )
+    return ancestors_of_counterfactual_variable
+
+
+def _minimize_set(*, graph: NxMixedGraph, variables: Iterable[Variable]) -> set[Variable]:
+    r"""Minimize a set of counterfactual variables.
+
+    Source: last paragraph in Section 4 of [correa22a]_, before Section 4.1.
+    $||\mathbf Y_*|| = {||Y_{\mathbf x}|| | Y_{\mathbf x}} \in {\mathbf Y_*}$.
+
+    :param variables: A set of counterfactual variables to minimize (some may have no interventions).
+    :param graph: The graph containing them.
+    :returns:
+        A set of minimized counterfactual variables such that each minimized variable
+        is an element of the original set.
+    """
+    return {minimize_counterfactual(variable, graph) for variable in variables}
+
+
+def minimize_counterfactual(variable: Variable, graph: NxMixedGraph) -> Variable:
+    r"""Minimize a single variable which is usually counterfactual and may have multiple interventions.
+
+    Source: last paragraph in Section 4 of [correa22a]_, before Section 4.1.
+
+    $||Y_{\mathbf x}|| = Y_{\mathbf t}, where \mathbf T = \mathbf X \intersect An(Y)_{G_{\overline(\mathbf X)}}$
+    and $\mathbf t = \mathbf x \intersect \mathbf T$.
+
+    :param variable: A counterfactual variable to minimize (which may have no interventions).
+    :param graph: The graph containing them.
+    :returns: a minimized counterfactual variable which may omit some interventions from the original one.
+    """
+    if not isinstance(variable, CounterfactualVariable):
+        return variable
+
+    # :math: $\mathbf X$
+    intervention_variables: set[Variable] = {
+        intervention.get_base() for intervention in variable.interventions
+    }
+    # :math: $\mathbf T$
+    treatment_variables = (
+        graph.remove_in_edges(intervention_variables)
+        .ancestors_inclusive(variable.get_base())
+        .intersection(intervention_variables)
+    )
+    # :math: $\mathbf t$
+    treatment_interventions: frozenset[Intervention] = frozenset(
+        intervention
+        for intervention in variable.interventions
+        if intervention.get_base() in treatment_variables
+    )
+    # RJC: [correa22a]_ isn't clear about whether the value of a minimized variable shoudl get preserved.
+    #      But they write: "Given a counterfactual variable Y_x some values in $\mathbf x$ may be causally
+    #      irrelevant to Y once the rest of $\mathbf x$ is fixed." There's nothing in there to suggest that
+    #      minimization of $Y_{\mathbf x}$ wouldn't preserve the counterfactual variable's value.  So
+    #      we keep the star value.
+    # RJC: Sorting the interventions makes the output more predictable and testing is therefore more robust.
+    return CounterfactualVariable(
+        name=variable.name,
+        star=variable.star,
+        interventions=treatment_interventions,
+    )
+
+
+def _get_conditioned_variables_in_ancestral_set(
+    *,
+    conditioned_variables: set[Variable],
+    ancestral_set_root_variable: Variable,
+    graph: NxMixedGraph,
+) -> frozenset[Variable]:
+    r"""Retrieve the intersection of the ancestral set of a root variable and a set of conditioned variables.
+
+    This function computes $\mathbf{V}(\|\mathbf{X_{\ast}}\| \cap An(W_{\mathbf{t}}))$,
+    the conditioned variables that are ancestors of the input root variable $W_{\mathbf{t}}$.
+
+    Note that [correa22a]_ contains an apparent contradiction: Example 4.5 indicates that
+    this operator may return a set of counterfactual variables such as $\{Z_{x}\}$ in the text,
+    but in that case this function should compute
+    $\mathbf{V}(\|\mathbf{X_{\ast}}\| \cap An(W_{\mathbf{t}}))_{\mathbf{t}}$ and not
+    $\mathbf{V}(\|\mathbf{X_{\ast}}\| \cap An(W_{\mathbf{t}}))$. The ambiguity can best be
+    resolved with a correction to the article. Meanwhile, because in practice this function
+    is used to consider graphs removing edges out of the set of vertices that the function
+    returns, we implement it according to its definition in the text of [correa22a]_:
+    $\mathbf{V}(\|\mathbf{X_{\ast}}\| \cap An(W_{\mathbf{t}}))$.
+
+    :param conditioned_variables: Following [correa22a]_ this is $\mathbf{X_{\ast}}$, a set of variables that
+           are conditioned on in a query. They may be Variable or CounterfactualVariable objects.
+    :param ancestral_set_root_variable: following [correa22a]_ this is $W_{\mathbf{t}}$, a variable
+           that the function uses to generate its ancestral set.
+    :param graph: the relevant graph (the target domain graph in [correa22a]_).
+    :returns: an expression for $\mathbf{V}(\|\mathbf{X_{\ast}}\| \cap An(W_{\mathbf{t}}))$,
+           that is, the conditioned variables that are ancestors of the input root variable $W_{\mathbf{t}}$.
+    """
+    minimized_conditioned_variables = _minimize_set(variables=conditioned_variables, graph=graph)
+    ancestral_set = get_ancestors_of_counterfactual(ancestral_set_root_variable, graph)
+    conditioned_variables_in_ancestral_set = frozenset(
+        variable.get_base()
+        for variable in minimized_conditioned_variables.intersection(ancestral_set)
+    )
+    return conditioned_variables_in_ancestral_set
+
+
+def _get_ancestral_set_after_intervening_on_conditioned_variables(
+    *,
+    conditioned_variables: set[Variable],
+    ancestral_set_root_variable: Variable,
+    graph: NxMixedGraph,
+) -> frozenset[Variable]:
+    r"""Get a variable's ancestral set after first intervening on any conditioned variables that are its ancestors.
+
+    This function computes $An(W_{\mathbf{t}})_{\mathcal{G}_{\underline{\mathbf{X_{\ast}(W_{\mathbf{t}})}}}$,
+    per [correa22a]_.
+
+    :param conditioned_variables: Following [correa22a]_ this is $\mathbf{X_{\ast}}$, a set of variables that
+           are conditioned on in a query. They may be Variable or CounterfactualVariable objects. This function
+           will not intervene on every one of these conditioned variables, just those that are ancestors of
+           the ancestral set root variable.
+    :param ancestral_set_root_variable: following [correa22a]_ this is $W_{\mathbf{t}}$, a variable
+           that the function uses to generate its ancestral set.
+    :param graph: the relevant graph (the target domain graph in [correa22a]_).
+    :returns: a set of variables corresponding to
+           $An(W_{\mathbf{t}})_{\mathcal{G}_{\underline{\mathbf{X_{\ast}(W_{\mathbf{t}})}}}$,
+           that is, the ancestors of $W_{\mathbf{t}}$ in a graph intervening on those
+           conditioned variables that would otherwise be in $W_{\mathbf{t}}$'s ancestral set were the
+           interventions not applied.
+    """
+    conditioned_variables_in_ancestral_set = _get_conditioned_variables_in_ancestral_set(
+        conditioned_variables=conditioned_variables,
+        ancestral_set_root_variable=ancestral_set_root_variable,
+        graph=graph,
+    )
+    graph_with_interventions = graph.remove_out_edges(conditioned_variables_in_ancestral_set)
+    new_ancestral_set = get_ancestors_of_counterfactual(
+        event=ancestral_set_root_variable, graph=graph_with_interventions
+    )
+    return frozenset(new_ancestral_set)
+
+
+def _compute_ancestral_components_from_ancestral_sets(
+    *,
+    ancestral_sets: set[frozenset[Variable]],
+    graph: NxMixedGraph,
+) -> frozenset[frozenset[Variable]]:
+    r"""Construct a set of ancestral components from ancestral sets following Definition 4.2 of [correa22a]_.
+
+    Note: [correa22a]_ is silent regarding an algorithm for efficiently combining the input
+        ancestral sets for this function. This implementation runs in time $O(V^{3})$, where $V$
+        is the number of vertices in the graph. The implementation matches Correa and
+        Bareinboim's efficiency analysis in Appendix B of [correa22a]_.
+
+    :param ancestral_sets: These are the sets
+           $An(W_{\mathbf{t}})_{\mathcal{G}_{\underline{\mathbf{X_{\ast}(W_{\mathbf{t}})}}}$ in
+           Definition 4.2 of [correa22a]_. They are induced by $\mathbf{W_{\ast}}$, given $\mathbf{X_{\ast}}$.
+    :param graph: the relevant graph $\mathcal{G}$ (without intervening on any conditioned variables).
+    :returns: the sets $\mathbf{A}_{1},\mathbf{A}_{2},\ldots$ that form a partition over $An(\mathbf{W_{\ast}})$,
+           made of unions of the input ancestral sets. Two sets are combined via a union operation if they are
+           not disjoint or there exists a bidirected arrow in $\mathcal{G}$ connecting variables
+           in those sets. (Definition 4.2 of [correa22a]_.)
+    """
+    # Initialization
+
+    # O(V)
+    ancestral_components: set[frozenset[Variable]] = {s for s in ancestral_sets}
+
+    # O(V^3)
+    merged_ancestral_components_using_vertices: set[frozenset[Variable]] = (
+        _merge_frozen_sets_with_common_vertices(ancestral_components)
+    )
+
+    # O(V^3)
+    merged_ancestral_components: set[frozenset[Variable]] = (
+        _merge_frozen_sets_linked_by_bidirectional_edges(
+            input_sets=merged_ancestral_components_using_vertices, graph=graph
+        )
+    )
+
+    # Final check
+    # O(V)
+    # vertex_to_ancestral_component_mappings: defaultdict[
+    #    Variable, set[frozenset[Variable]]
+    # ] = defaultdict(set)
+    # for s in merged_ancestral_components:
+    #    # original_to_merged_ancestral_set_mappings[s] = s
+    #    for v in s:
+    #        # Two counterfactual variables with the same base variable and different interventions
+    #        # may be considered not disjoint, because they correspond to the same
+    #        # graph vertex. So we do the indexing by primitive variable, not counterfactual
+    #        # variable. See the proof of Lemma A.5 of [correa22a]_.
+    #        vertex_to_ancestral_component_mappings[v.get_base()].add(s)
+
+    # 1. Prove this next check never gets triggered. Assume: there's a vertex in more than one component
+    # of merged_ancestral_outcomes.
+    #
+    #    Then, either _merge_frozen_sets_with_common_vertices() returned two frozen sets with a vertex
+    # in common or it didn't, and _merge_frozen_sets_linked_by_bidirectional_edges() therefore
+    # split a frozen set.  _merge_frozen_sets_linked_by_bidirectional_edges() only ever applies
+    # union operations to input sets, so the latter condition could not have happened. That leaves
+    # _merge_frozen_sets_with_common_vertices() returning two frozen sets with a vertex in common.
+    # Again, it only applies union operations to input sets, so there must have existed two input sets
+    # with a vertex in common that didn't get merged.
+    # So _merge_frozen_sets_with_common_vertices() returned two frozen sets with a vertex in common.
+    #
+    #    Consider two such sets, A and B, that both contain vertex V (or equivalently, which contain two
+    # counterfactual variables with base variable V). (_convert_counterfactual_variables_to_base_variables()
+    # works: a variable is either its own base in which we're done, or it's not in which case this
+    # one-line function returns its base.) A and B definitely get compared during the for loop that
+    # calls combinations_with_replacement() on input_sets, at which point B goes into the adjaceny
+    # list for A and A goes into the adjacency list for B. Next, observe that every vertex V gets
+    # visited at least once by dft(), either by a call to dft(V,V) or recursively, by a call to
+    # dft(V, W) where W is some other node with V as its neighbor. So, one of three things can happen:
+    # 1. A gets visited for the first time with a call dft(A,A), in which case dft(A,A) adds
+    #    A to components[A]. The final line will then union sets A and B together as B is a
+    #    neighbor of A.
+    # 2. B gets visited for the first time with a call dft(B,B), in which case dft(B,B) adds
+    #    B to components[B]. The final line will then union sets A and B together as A is a
+    #    neighbor of B.
+    # 3. A gets visited for the first time with a call dft(A,C), at which point A goes into
+    #    components[C]. During the last line of this function, A and its neighbors will
+    #    then get unioned together, so A and B will go into one set.
+    #    a. If B hasn't been visited, then dft(B, C) gets called, at which point B goes into
+    #       components[C]. Then A, B, and C will get merged during the last line of this function
+    #       because A and B will be neighbors of C.
+    #    b. B cannot have already been visited. Assume it was visited already. It could have been per
+    #       a call dft(B,B) which would lead to a call dft(A,B) since A hasn't been visited.
+    #       But we visited A for the first time with a call dft(A,C), a contradiction. It could have
+    #       been per a call dft(B,C) in which case B is also a neighbor of C, and A, B, and C will get
+    #       merged during the last line of this function. Or it could have been per a call dft(B,D)
+    #       where D is some other vertex. But since dft is a depth-first traversal, a call
+    #       dft(B,D) would have led to a call dft(A,D) and A was visited for the first time
+    #       in the call dft(A,C). So all three possibilities lead to contradictions and B cannot
+    #       have already been visited.
+    #
+    #    We have seen that in all possible cases, _merge_frozen_sets_with_common_vertices() leads
+    # to A and B merged into a frozen set. So we must reject our assumption that there's a vertex in
+    # more than one component of merged_ancestral_outcomes, and this validity check is unnecessary
+    # and the line of code will never be run.
+    #
+    # if any(
+    #    len(vertex_to_ancestral_component_mappings[v]) > 1
+    #    for v in vertex_to_ancestral_component_mappings.keys()
+    # ):
+    #    logger.debug(
+    #        "In _compute_ancestral_components_from_ancestral_sets: a vertex is still associated "
+    #        + "with more than one ancestral component during final checks."
+    #    )
+    #    for v in vertex_to_ancestral_component_mappings.keys():
+    #        logger.debug("Vertex: " + str(v))
+    #        logger.debug(
+    #            "   Ancestral components associated with this vertex: "
+    #            + str(vertex_to_ancestral_component_mappings[v])
+    #        )
+    #    raise ValueError(
+    #        "In _compute_ancestral_components_from_ancestral_sets: a vertex "
+    #        + "is still associated with more than one ancestral component during final checks."
+    #    )
+
+    # 2. Prove this next check never gets triggered. Assume: there exist two sets A and B such that
+    # A is in one component of merged_ancestral_outcomes and B is in a second component, and
+    # there exist vertices v1 in A and v2 in B such that a bidirected edge v1 <-> v2 exists in the
+    # graph.
+    #
+    #    Either _merge_frozen_sets_with_common_vertices() placed A and B into the same component or
+    # it didn't. It could not have done so because _merge_frozen_sets_linked_by_bidirectional_edges()
+    # would then have had to split a component, and the function only calls union operations on
+    # input sets. So A and B were in separate input sets passed into
+    # _merge_frozen_sets_linked_by_bidirectional_edges(). Therefore we try to show that
+    # _merge_frozen_sets_linked_by_bidirectional_edges() returned
+    # A in one component and B in a second component of its output, such that
+    # there exist vertices v1 in A and v2 in B with a bidirected edge v1 <-> v2 existing in the
+    # graph. Clearly A and B can't be in the same input set. Because the edge is in the graph, it
+    # gets visited by the for loop visiting graph.undirected.edges, and thus A is a neighbor of
+    # B in the adjacency list and B is a neighbor of A. The rest of the code and the rest of the proof
+    # is exactly the same as the proof that _merge_frozen_sets_with_common_vertices() always links
+    # sets that share a common vertex / counterfactual variables with the same base variable.
+    # We have to reject our assumption and thus the next check never gets triggered.
+    # if any(
+    #    v1 in vertex_to_ancestral_component_mappings
+    #    and v2 in vertex_to_ancestral_component_mappings
+    #    and vertex_to_ancestral_component_mappings[v1] != vertex_to_ancestral_component_mappings[v2]
+    #    for v1, v2 in graph.undirected.edges
+    # ):
+    #    logger.debug(
+    #        "In _compute_ancestral_components_from_ancestral_sets: a bidirected edge still connects "
+    #        + "two ancestral components during final checks."
+    #    )
+    #    raise ValueError(
+    #        "In _compute_ancestral_components_from_ancestral_sets: a bidirected edge "
+    #        + "still connects two ancestral components during final checks."
+    #    )
+    #
+    #   These two proofs together verify that this algorithm meets the requirements of the
+    # last sentence of Definition 4.2 in [correa22a]_.
+    return frozenset(merged_ancestral_components)
+
+
+def get_ancestral_components(
+    *, conditioned_variables: set[Variable], root_variables: set[Variable], graph: NxMixedGraph
+) -> frozenset[frozenset[Variable]]:
+    r"""Compute a set of ancestral components corresponding to Definition 4.2 of [correa22a].
+
+    :param conditioned_variables: The set of variables $\mathbf{X_{\ast}}$ on which
+           a counterfactual query has been conditioned.
+    :param root_variables: The set of variables $\mathbf{W_{\ast}}$, such that
+          $\mathbf{X_{\ast}} \subseteq \mathbf{W_{\ast}}$, that we use to construct ancestral sets
+          for each variable in $\mathbf{W_{\ast}}$ and ancestral components from those sets.
+    :param graph: the relevant graph $\mathcal{G}$ (without intervening on any conditioned variables).
+    :returns: the sets $\mathbf{A}_{1},\mathbf{A}_{2},\ldots$ that form a partition over $An(\mathbf{W_{\ast}})$,
+           made of unions of the input ancestral sets. Two sets are combined via a union operation if they are
+           not disjoint or there exists a bidirected arrow in $\mathcal{G}$ connecting variables
+           in those sets. (Definition 4.2 of [correa22a]_.)
+    """
+    ancestral_sets: set[frozenset[Variable]] = {
+        _get_ancestral_set_after_intervening_on_conditioned_variables(
+            conditioned_variables=conditioned_variables, ancestral_set_root_variable=v, graph=graph
+        )
+        for v in root_variables
+    }
+    logger.debug("In _get_ancestral_components: ancestral_sets = " + str(ancestral_sets))
+    ancestral_components: frozenset[frozenset[Variable]] = (
+        _compute_ancestral_components_from_ancestral_sets(
+            ancestral_sets=ancestral_sets, graph=graph
+        )
+    )
+    logger.debug(
+        "In _get_ancestral_components: computed these ancestral components: "
+        + str(ancestral_components)
+    )
+    return ancestral_components
+
+
+def _merge_frozen_sets_with_common_vertices(
+    input_sets: set[frozenset[Variable]],
+) -> set[frozenset[Variable]]:
+    r"""Merge a set of frozen sets of counterfactual variables using common vertices.
+
+    Two sets get merged if they share a common graph vertex. That is, there exists a counterfactual
+    variable in the first set and a counterfactual variable in the first set such that both
+    counterfactual variables have the same base variable name. This algorithm treats input sets as
+    nodes in a graph, defines an edge in the graph in all cases for which two input sets share
+    a common vertex, computes the union of the vertices in each connected component in
+    the resulting graph as a frozen set, and returns the set of those frozen sets.
+
+    Total running time: O(V^3) where V is the number of vertices in the graph.
+
+    :param input_sets: the counterfactual variables in question.
+    :returns: A set containing the merged frozen sets.
+    """
+    # From this StackOverflow post: https://stackoverflow.com/questions/42211947/merging-sets-with-common-elements
+    result = set()
+    visited: set[frozenset[Variable]] = set()
+    components: defaultdict[frozenset[Variable], list[frozenset[Variable]]] = defaultdict(list)
+    adj_list: defaultdict[frozenset[Variable], list[frozenset[Variable]]] = defaultdict(list)
+
+    # DFS runs in O(V+E) time where V is the number of vertices and E is the number of edges in a graph
+    # (Cormen, Leisserson, Rivest and Stein 2002:541).
+    #
+    # Here V is the number of input sets and E is the number of edges are the number of sets with common vertices.
+    # The number of sets with common vertices is O(V), e.g., there can be V-1 sets each containing two vertices
+    # that form a giant cycle, so that's O(V) calls to dft. The number of edges are also O(V) in this case so
+    # dft() runs in O(V+V) = O(V) time.
+    def _dft(node: frozenset[Variable], key: frozenset[Variable]) -> None:
+        visited.add(node)
+        components[key].append(node)
+        for neighbor in adj_list[node]:
+            if neighbor not in visited:
+                _dft(neighbor, key)
+
+    # Create a hash table to speed the process of checking if pairs of sets contain common vertices
+    # after their members are converted from counterfactual variables to their base variables.
+    # O(V) running time
+    converted_sets: defaultdict[frozenset[Variable], frozenset[Variable]] = defaultdict(frozenset)
+    for s in input_sets:
+        converted_sets[s] = get_base_variables(s)
+
+    # Prove this never gets triggered. Assume: there's a vertex in more than one component
+    # of merged_ancestral_outcomes.
+    # Then, either _merge_frozen_sets_with_common_vertices() returned two frozen sets with a vertex
+    # in common or it didn't, and _merge_frozen_sets_linked_by_bidirectional_edges() therefore
+    # split a frozen set.  _merge_frozen_sets_linked_by_bidirectional_edges() only ever applies
+    # union operations to input sets, so the latter condition could not have happened. That leaves
+    # _merge_frozen_sets_with_common_vertices() returning two frozen sets with a vertex in common.
+    # Again, it only applies union operations to input sets, so there must have existed two input sets
+    # with a vertex in common that didn't get merged.
+    # So _merge_frozen_sets_with_common_vertices() returned two frozen sets with a vertex in common.
+
+    # Consider two such sets, A and B, that both contain vertex V (or equivalently, which contain two
+    # counterfactual variables with base variable V). (_convert_counterfactual_variables_to_base_variables()
+    # works: a variable is either its own base in which we're done, or it's not in which case this
+    # one-line function returns its base.) A and B definitely get compared during the for loop that
+    # calls combinations_with_replacement() on input_sets, at which point B goes into the adjaceny
+    # list for A and A goes into the adjacency list for B. Next, observe that every vertex V gets
+    # visited at least once by dft(), either by a call to dft(V,V) or recursively, by a call to
+    # dft(V, W) where W is some other node with V as its neighbor. So, one of three things can happen:
+    # 1. A gets visited for the first time with a call dft(A,A), in which case dft(A,A) adds
+    #    A to components[A]. The final line will then union sets A and B together as B is a
+    #    neighbor of A.
+    # 2. B gets visited for the first time with a call dft(B,B), in which case dft(B,B) adds
+    #    B to components[B]. The final line will then union sets A and B together as A is a
+    #    neighbor of B.
+    # 3. A gets visited for the first time with a call dft(A,C), at which point A goes into
+    #    components[C]. During the last line of this function, A and its neighbors will
+    #    then get unioned together, so A and B will go into one set.
+    #    a. If B hasn't been visited, then dft(B, C) gets called, at which point B goes into
+    #       components[C]. Then A, B, and C will get merged during the last line of this function
+    #       because A and B will be neighbors of C.
+    #    b. B cannot have already been visited. Assume it was visited already. It could have been per
+    #       a call dft(B,B) which would lead to a call dft(A,B) since A hasn't been visited.
+    #       But we visited A for the first time with a call dft(A,C), a contradiction. It could have
+    #       been per a call dft(B,C) in which case B is also a neighbor of C, and A, B, and C will get
+    #       merged during the last line of this function. Or it could have been per a call dft(B,D)
+    #       where D is some other vertex. But since dft is a depth-first traversal, a call
+    #       dft(B,D) would have led to a call dft(A,D) and A was visited for the first time
+    #       in the call dft(A,C). So all three possibilities lead to contradictions and B cannot
+    #       have already been visited.
+    # We have seen that in all possible cases, _merge_frozen_sets_with_common_vertices() leads
+    # to A and B merged into a frozen set. So we must reject our assumption that there's a vertex in
+    # more than one component of merged_ancestral_outcomes, and this validity check is unnecessary
+    # and the line of code will never be run.
+
+    # The adjacency lists for both A and B get visited during
+    # the loop through the adj_list dictionary. If A and B get placed into the same connected component
+    # during this step, then they get merged during the union operation during the last line, at which point
+    # _merge_frozen_sets_with_common_vertices() does not
+
+    # We wish to show that A and B will get placed
+    # into a connected component.
+    #
+    # One of three things can happen:
+
+    # 3. Note that if A and B are to be ultimately merged, they must be part of a connected
+    #
+    # B gets visited for the first time with a call dft(B,B) and then calls dft(A,B) within
+    # When that happens, either A has been visited before during a
+    # call to dft() or it has not. If it has not, then dft(A,A) gets called,
+    # Runs in O(V^2) time
+    for r1, r2 in combinations_with_replacement(input_sets, 2):
+        if converted_sets[r1] & converted_sets[r2]:
+            adj_list[r1].append(r2)
+            adj_list[r2].append(r1)
+
+    # O(V^2): outer loop is O(V), inner loop is O(V)
+    for node in adj_list:
+        if node not in visited:
+            _dft(node, node)
+
+    # O(V) components, and for each component O(V) neighbors receive a union operation that runs
+    # in O(V) time. So, total running time of this loop (and thus the algorithm) is O(V^3).
+    for node, neighbors in components.items():
+        result.add(node.union(*neighbors))
+    return result
+
+
+def _merge_frozen_sets_linked_by_bidirectional_edges(
+    input_sets: set[frozenset[Variable]],
+    graph: NxMixedGraph,
+) -> set[frozenset[Variable]]:
+    r"""Merge a set of frozen sets of counterfactual variables using common bidirectional edges.
+
+    Two sets get merged if a bidirectional edge connects them.
+    Running time: O(V^3)
+
+    :param input_sets: the counterfactual variables in question.
+    :param graph: the graph in question.
+    :returns: A set containing the merged frozen sets.
+    """
+    # Modified from https://stackoverflow.com/questions/42211947/merging-sets-with-common-elements
+    result = set()
+    visited: set[frozenset[Variable]] = set()
+    components: defaultdict[frozenset[Variable], list[frozenset[Variable]]] = defaultdict(list)
+    adj_list: defaultdict[frozenset[Variable], list[frozenset[Variable]]] = defaultdict(list)
+
+    # O(V)
+    # FIXME this DFT code is duplicate from above
+    def _dft(node: frozenset[Variable], key: frozenset[Variable]) -> None:
+        visited.add(node)
+        components[key].append(node)
+        for neighbor in adj_list[node]:
+            if neighbor not in visited:
+                _dft(neighbor, key)
+
+    # O(V^2)
+    converted_sets: defaultdict[frozenset[Variable], frozenset[Variable]] = defaultdict(frozenset)
+    for s in input_sets:
+        converted_sets[s] = get_base_variables(s)
+
+    # Loop through the sets and create a vertex to set mapping. O(V^2)
+    vertices_to_input_sets: defaultdict[Variable, frozenset[Variable]] = defaultdict(frozenset)
+    for s in input_sets:
+        for v in converted_sets[s]:
+            vertices_to_input_sets[v] = s
+
+    # Connect sets to themselves. O(V)
+    for s in input_sets:
+        adj_list[s].append(s)
+
+    # Connect sets joined by bidirectional edges. O(E)
+    for e in graph.undirected.edges:
+        v1 = e[0]
+        v2 = e[1]
+        r1 = vertices_to_input_sets[v1]
+        r2 = vertices_to_input_sets[v2]
+        if r1 != r2:
+            adj_list[r1].append(r2)
+            adj_list[r2].append(r1)
+
+    # O(V^2)
+    for node in adj_list:
+        if node not in visited:
+            _dft(node, node)
+
+    # O(V^3).
+    for node, neighbors in components.items():
+        result.add(node.union(*neighbors))
+
+    # So, total running time is O(V^3+E). But since E can't be greater than V^2,
+    # the running time is just O(V^3).
+    return result
+
+
+def get_base_variables(variables: frozenset[Variable]) -> frozenset[Variable]:
+    r"""Replace a set of counterfactual variables with a set of corresponding base variables.
+
+    :param variables: the counterfactual variables in question.
+    :returns: The base variables.
+    """
+    return frozenset(variable.get_base() for variable in variables)
diff --git a/src/y0/algorithm/counterfactual_transport/api.py b/src/y0/algorithm/counterfactual_transport/api.py
new file mode 100644
index 0000000..aa445a8
--- /dev/null
+++ b/src/y0/algorithm/counterfactual_transport/api.py
@@ -0,0 +1,2816 @@
+# -*- coding: utf-8 -*-
+
+"""Implementation of counterfactual transportability.
+
+.. [correa22a] https://proceedings.mlr.press/v162/correa22a/correa22a.pdf.
+.. [correa20a] https://proceedings.neurips.cc/paper/2020/file/7b497aa1b2a83ec63d1777a88676b0c2-Paper.pdf.
+"""
+
+import itertools as itt
+import logging
+from collections import defaultdict
+from dataclasses import dataclass, field
+from typing import Collection, NamedTuple
+
+from networkx import is_directed_acyclic_graph
+
+from y0.algorithm.tian_id import compute_c_factor, identify_district_variables
+from y0.algorithm.transport import is_transport_node, transport_variable
+from y0.dsl import (
+    PP,
+    TARGET_DOMAIN,
+    CounterfactualVariable,
+    Expression,
+    Fraction,
+    Intervention,
+    One,
+    P,
+    Population,
+    PopulationProbability,
+    Probability,
+    Product,
+    Sum,
+    Variable,
+    Zero,
+)
+from y0.graph import NxMixedGraph
+
+from .ancestor_utils import (
+    get_ancestors_of_counterfactual,
+    get_ancestral_components,
+    minimize_counterfactual,
+)
+
+__all__ = [
+    # TODO do a proper audit of which of these a user should ever have to import
+    "unconditional_cft",
+    "conditional_cft",
+    "transport_unconditional_counterfactual_query",
+    "transport_conditional_counterfactual_query",
+    #
+    "Event",
+    "CFTDomain",
+    "ConditionalCFTResult",
+    "UnconditionalCFTResult",
+    # Utilities
+    "simplify",
+    "minimize_event",
+    "same_district",
+    "is_counterfactual_factor_form",
+    "get_counterfactual_factors",
+    "convert_to_counterfactual_factor_form",
+    "do_counterfactual_factor_factorization",
+    "counterfactual_factors_are_transportable",
+    "transport_district_intervening_on_parents",
+    # TODO add functions/classes/variables you want to appear in the docs and be exposed to the user in this list
+    #  Run tox -e docs then `open docs/build/html/index.html` to see docs
+]
+
+logger = logging.getLogger(__name__)
+
+# FIXME potentially rename, since there are more strict events that are tuple[Variable, Intervention]
+EventItem = tuple[Variable, Intervention | None]
+Event = list[EventItem]
+
+
+def event_to_probability(event: Event) -> Probability:
+    """Turn an event list into a probability object."""
+    values: list[Variable] = []
+    for variable, value in event:
+        if variable.star is not None:
+            raise ValueError("events can not have star values for the variable")
+        star = None if value is None else value.star
+        if isinstance(variable, CounterfactualVariable):
+            values.append(
+                CounterfactualVariable(
+                    name=variable.name,
+                    star=star,
+                    interventions=variable.interventions,
+                )
+            )
+        else:
+            values.append(Variable(name=variable.name, star=star))
+    return Probability.safe(values)
+
+
+def _any_variables_with_inconsistent_values(
+    *,
+    nonreflexive_variable_to_value_mappings: dict[Variable, set[Intervention | None]],
+    reflexive_variable_to_value_mappings: dict[Variable, set[Intervention | None]],
+) -> bool:
+    r"""Check for variables with inconsistent values following Line 2 of Algorithm 1 in [correa_22a]_."""
+    # Part 1 of Line 2:
+    # :math: **if** there exists $Y_{\mathbf{x}}\in \mathbf{Y}_\ast$ with
+    # two or more different values in  $\mathbf{y_\ast}$ **then return** 0.
+    # Note this definition has to do with counterfactual values, and is different than
+    # the "inconsistent counterfactual factor" definition in Definition 4.1 of [correa22a]_.
+    if any(
+        len(value_set) > 1 and None in value_set
+        for value_set in nonreflexive_variable_to_value_mappings.values()
+    ) or any(
+        None in reflexive_variable_to_value_mappings[key]
+        and not isinstance(key, CounterfactualVariable)
+        and len(reflexive_variable_to_value_mappings[key]) > 1
+        # FIXME use .items() when iterating over dicts to directly give a name to the value!
+        for key in reflexive_variable_to_value_mappings
+    ):
+        # most of the type errors in this module should actually be value errors
+        raise TypeError(
+            "In _any_variables_with_inconsistent values: a variable lacking interventions on itself "
+            + "has an assigned value and also a value of None. That should not occur. Check your inputs."
+        )
+
+    if any(len(value_set) > 1 for value_set in nonreflexive_variable_to_value_mappings.values()):
+        return True
+
+    # Part 2 of Line 2:
+    # :math: **if** there exists $Y_y\in \mathbf{Y}_\ast$ with $\mathbf{y_*} \cap Y_y \neq y$ **then return** 0.
+    if any(
+        None in reflexive_variable_to_value_mappings[key]
+        and (isinstance(key, CounterfactualVariable))
+        for key in reflexive_variable_to_value_mappings
+    ):
+        raise TypeError(
+            "In _any_variables_with_inconsistent values: a variable containing interventions on itself "
+            + "has an assigned value of None. That should not occur. Check your inputs."
+        )
+    return any(
+        (
+            not isinstance(variable, CounterfactualVariable)
+            and len(reflexive_variable_to_value_mappings[variable]) > 1
+        )
+        or (
+            isinstance(variable, CounterfactualVariable)
+            and any(
+                {intervention} != reflexive_variable_to_value_mappings[variable]
+                for intervention in variable.interventions
+            )
+        )
+        for variable in reflexive_variable_to_value_mappings.keys()
+    )
+    # Longer version of the above. (Old)
+    # for variable in reflexive_variable_to_value_mappings.keys():  # Y_y, Y
+    #    if not isinstance(variable, CounterfactualVariable):  # Y
+    #        # TODO: Check with JZ that it's intended that $Y_y$ and $Y$ are the same.
+    #        #       I infer that is so because of Equation 4 in [correa22a]_.
+    #        # If Y takes on at least two values as part of the same query, then there exists
+    #        # $Y_{y}\in \mathbf{Y}_\ast$ with two or more different values in  $\mathbf{y_\ast}$.
+    #        # That implies that there exists $Y_y\in \mathbf{Y}_\ast$ with $\mathbf{y_*} \cap Y_y \neq y$,
+    #        # so we return 0.
+    #        if len(reflexive_variable_to_value_mappings[variable]) > 1:
+    #            logger.debug("Part 2 of Line 2 fails for (non-counterfactual) variables: ")
+    #            logger.debug(
+    #                "    Variable = "
+    #                + str(variable)
+    #                + ", values are "
+    #                + str(reflexive_variable_to_value_mappings[variable])
+    #            )
+    #            return True
+    #    else:  # Y_y
+    #        for intervention in variable.interventions:
+    #            if intervention.get_base() != variable.get_base():
+    #                raise TypeError(
+    #                    f"In _any_variables_with_inconsistent_values: reflexive variable {str(variable)} \
+    #                      has an intervention that is not itself: {str(intervention)}."
+    #                )
+    #            # reflexive_variable_to_value_mappings[variable] = $\mathbf{y_*} \cap Y_y
+    #            # {intervention} = y
+    #            if {intervention} != reflexive_variable_to_value_mappings[variable]:
+    #                logger.debug(
+    #                    "Part 2 of Line 2 fails: {{intervention}} = "
+    #                    + str({intervention})
+    #                    + " and reflexive_variable_to_value_mappings[variable] = "
+    #                    + str(reflexive_variable_to_value_mappings[variable])
+    #                )
+    #                return True
+    # return False
+
+
+# Deprecated.
+# def _simplify_self_interventions_with_consistent_values(
+#    outcome_variable_to_value_mappings: defaultdict[Variable, set[Intervention]],
+#    outcome_variables: set[Variable],
+# ) -> tuple[defaultdict[Variable, set[Intervention]], set[Variable]]:
+#    r"""Address Part 2 of Line 3 of SIMPLIFY from [correa22a]_.
+#
+#    :math: **if** there exists $Y_y\in \mathbf{Y}_\ast$ with $\mathbf{y_*} \cap Y_y = y$ **then**
+#    remove repeated variables from $\mathbf{Y_\ast}$ and values $\mathbf{y_\ast}$.
+#
+#    Note that Y_y and Y are repeated variables. So, when the counterfactual variable Y_y and the
+#    intervention Y are both in the set of outcome variables, we want to remove one of them and
+#    the obvious one to remove is the more complex Y_y. What [correa22a]_ does not specify is,
+#    in the case where Y_y is in the set of events but Y is not, should Y_y get reduced to Y?
+#    The question is analogous to asking, in the case where Y is in the set of events but Y_y is
+#    not, should Y be replaced by Y_y? The latter answer is "no" because the notation becomes
+#    more complex. So, our answer to the former question is "yes" because the notation
+#    becomes simpler without changing the results of Algorithms 2 or 3 in [correa22a]_.
+#
+#    :param outcome_variable_to_value_mappings:
+#        A dictionary mapping Variable objects to their values, represented as Intervention objects.
+#    :param outcome_variables:
+#        A set of outcome variables (really just the keys for outcome_variable_to_value_mappings, the
+#        code could be further optimized).
+#    :returns:
+#        These same two inputs with any redundant outcome variables removed.
+#    """
+#    for variable in list(outcome_variables):  # if isinstance(variable, CounterfactualVariable):
+#        if not isinstance(variable, CounterfactualVariable):
+#            continue
+#        for intervention in variable.interventions:
+#            if intervention.get_base() != variable.get_base():
+#                continue
+#            # Y_Y
+#            if variable.get_base() in outcome_variables:
+#                outcome_variable_to_value_mappings[variable.get_base()].update({intervention})
+#                del outcome_variable_to_value_mappings[variable]
+#                outcome_variables.remove(variable)
+#            else:
+#                outcome_variable_to_value_mappings[variable.get_base()].update(
+#                    outcome_variable_to_value_mappings[variable]
+#                )
+#                outcome_variables.add(variable.get_base())
+#                del outcome_variable_to_value_mappings[variable]
+#                outcome_variables.remove(variable)
+#    return outcome_variable_to_value_mappings, outcome_variables
+
+
+def _remove_repeated_variables_and_values(event: Event) -> dict[Variable, set[Intervention | None]]:
+    r"""Implement the first half of Line 3 of the SIMPLIFY algorithm from [correa22a]_.
+
+    The implementation is as simple as creating a dictionary. Adding variables to
+    the dictionary removes repeated variables in the input event, and adding values to
+    the dictionary using the variables as keys removes repeated values.
+
+    :math: **if** there exists $Y_{\mathbf{x}}\in \mathbf{Y}_\ast$ with
+    two consistent values in  $\mathbf{y_\ast} \cap Y_x$ **then**
+    remove repeated variables from $\mathbf{Y_\ast}$ and values $\mathbf{y_\ast}$.
+
+    In this function we also handle a case that [correa22a]_ does not address. Some variables
+    passed in to Algorithm 2 of [correa22a]_ (transport_unconditional_counterfactual_query())
+    from Algorithm 3 of [correa22a]_ (transport_conditional_counterfactual_query())
+    may have values of None, because they were ancestors of a counterfactual when processed
+    in Algorithm 3 and not variables passed in to Algorithm 3 along with actual values.
+    Such variables may be redundant with variables passed in to Algorithm 2 for which values
+    are observed. Because a value of None in this context implies that the variable could
+    take any value, it is consistent with a specific value. Its intervention set is also
+    guaranteed to match the intervention set of its counterpart that has a specific value, because
+    all variables passed from Algorithm 3 to Algorithm 2 are in counterfactual factor form.
+    Therefore, in this function, we want to treat the None value as consistent with a specific value.
+
+    :param event:
+        A tuple associating $\mathbf{Y_\ast}$, a set of counterfactual variables (or regular variables)
+        in $\mathbf{V}$ with $\mathbf{y_\ast}$, a set of values for $\mathbf{Y_\ast}$. We encode the
+        counterfactual variables as Variable objects, and the values as Intervention objects.
+    :returns:
+        A dictionary mapping the event variables to all values associated with each variable in the event.
+    """
+    variable_to_value_mappings: defaultdict[Variable, set[Intervention | None]] = defaultdict(set)
+    for variable, value in event:
+        variable_to_value_mappings[variable].add(value)
+    for variable in variable_to_value_mappings.keys():
+        if (
+            len(variable_to_value_mappings[variable]) > 1
+            and None in variable_to_value_mappings[variable]
+        ):
+            variable_to_value_mappings[variable].remove(None)
+    return dict(variable_to_value_mappings)
+
+
+def _split_event_by_reflexivity(event: Event) -> tuple[Event, Event]:
+    r"""Categorize variables in an event by reflexivity (i.e., whether they intervene on themselves).
+
+    :param event:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :returns:
+        Two events, one containing variables in :math: $Y_{\mathbf{x}} \in \mathbf{Y}_\ast$}
+        and one containing variables in :math: $Y_{y} \in \mathbf{Y}_\ast$.
+        Note that only if minimization has already taken place (which is the case here),
+        variables that are not counterfactual variables are considered the equivalent
+        of :math: $Y_{\mathbf{y} \in \mathbf{Y}_\ast$ and fall into the latter category.
+    """
+    # Y_y
+    reflexive_interventions_event: Event = [
+        (variable, value)
+        for variable, value in event
+        if (
+            isinstance(variable, CounterfactualVariable)
+            and any(
+                intervention.get_base() == variable.get_base()
+                for intervention in variable.interventions
+            )
+        )
+        or not (isinstance(variable, CounterfactualVariable))
+    ]
+    # Y_x
+    nonreflexive_interventions_event: Event = [
+        (variable, value)
+        for variable, value in event
+        if isinstance(variable, CounterfactualVariable)
+        and not any(
+            intervention.get_base() == variable.get_base()
+            for intervention in variable.interventions
+        )
+    ]
+    # for variable, value in event:
+    #    if isinstance(variable, CounterfactualVariable):
+    #        if any(
+    #            [
+    #                intervention.get_base() == variable.get_base()
+    #                for intervention in variable.interventions
+    #            ]
+    #        ):
+    #            reflexive_interventions_event.append((variable, value))
+    #        else:
+    #            nonreflexive_interventions_event.append((variable, value))
+    #    else:
+    #        # A variable with no intervention, Y, is the same thing as Y_y in the event that
+    #        # minimization has already taken place (which is the case here)
+    #        reflexive_interventions_event.append((variable, value))
+    return reflexive_interventions_event, nonreflexive_interventions_event
+
+
+def _reduce_reflexive_counterfactual_variables_to_interventions(
+    variables: dict[Variable, set[Intervention | None]]
+) -> dict[Variable, set[Intervention | None]]:
+    r"""Simplify counterfactual variables intervening on themselves to Intervention objects with the same base.
+
+    :param variables: A mapping :math: $\mathbf{Y_\ast}$, a set of counterfactual variables in
+        $\mathbf{V}$, to $\mathbf{y_\ast}$, a set of values for $\mathbf{Y_\ast}$. Each variable in
+        $\mathbf{Y_\ast}$ is assumed to be either $Y_{y}$ \in $\mathbf{Y_\ast}$ or just $Y$ in $\mathbf{Y_\ast}$,
+        where $Y$ is considered a special case of $Y_{y}$ because minimization has already taken place.
+        The $\mathbf{Y_\ast}$ variables are CounterfactualVariable objects, and the values are Intervention objects.
+    :raises ValueError:
+        a variable in the input dictionary has more than one intervention or its intervention is not itself.
+    :returns:
+        A mapping from simple variables :math: $\mathbf{Y}$ to $\mathbf{y}$ to a set of corresponding values.
+    """
+    result_dict: defaultdict[Variable, set[Intervention | None]] = defaultdict(set)
+    for variable, interventions in variables.items():
+        if not isinstance(variable, CounterfactualVariable):
+            result_dict[variable].update(interventions)
+        else:
+            if len(variable.interventions) != 1:
+                raise ValueError(f"Variable had more than one intervention: {variable}")
+            if _check_nonreflexive(variable):
+                raise ValueError(f"Variable had non-reflexive intervention: {variable}")
+            result_dict[variable.get_base()].update(interventions)
+    return dict(result_dict)
+
+
+def _check_nonreflexive(variable: CounterfactualVariable) -> bool:
+    base = variable.get_base()
+    return any(intervention.get_base() != base for intervention in variable.interventions)
+
+
+def simplify(*, event: Event, graph: NxMixedGraph) -> Event | None:
+    r"""Run algorithm 1, the SIMPLIFY algorithm from [correa22a]_.
+
+    Correa, Lee, and Bareinboim [correa22a]_ state that this algorithm should return "an interventionally
+    minimal event :math:$\mathbf Y_* = \mathbf y_*$ without redundant subscripts or 0 if the counterfactual
+    event is guaranteed to have probability 0." In Y0, Zero() is an expression object. Here, we return
+    None instead of 0 to denote an event that is impossible, and rely on the calling function to translate either
+    the returned event or a None object into an appropriate probabilistic expression.
+
+    This implementation switches the order of Lines 2 and 3 in Algorithm 1 of [correa22a]_. The reason
+    is that Line 3 reduces (Y @ -Y, -Y) to (Y, -Y). The substitution could introduce the inconsistency
+    [(Y, -Y), (Y, +Y)] in the case where the input to SIMPLIFY is [(Y @ -Y, -Y), (Y, +Y)].
+
+    :param event:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param graph:
+        The associated graph.
+    :raises TypeError: Improperly formatted inputs for simplify()
+    :returns:
+        "Y_* = y_*". We use a dict with counterfactual variables for keys and
+        interventions for values.
+    """
+    # TODO: Ask Jeremy:
+    # 1) Is it better to have Union[CounterfactualVariable, Variable] instead of just CounterfactualVariable?
+    #  answer: no, variable is the superclass so just annotate with Variable
+    # 2) Is there a better way to capture the values than with Intervention objects?
+    #
+    #  answer(JZ): "Hmm...things are a little broken, because what we would like to express is
+    #  `P(Y @ -y == -y)`, but instead, all we have is `P(Y @ -y)`, or `P(-y)`."
+    #  If we want to know the value of a counterfactual variable, then we should use Intervention.
+    #  Variable(name="Y", star="False") shouldn't exist. Variables should set star to None, so that
+    #  it is only interpreted as a Variable.  If you want to talk about the value of a variable, then you
+    #  can make a tuple: (Variable(name="Y", star=None), Intervention(name="Y", star=False))
+    #
+    #  RJC: That's why below we test that a Variable that is not a CounterfactualVariable has
+    #       set star to None. Putting the check here means that we don't need separate checks
+    #       in functions such as get_counterfactual_ancestors(), because the ctfTRu and ctfTR
+    #       algorithms all call SIMPLIFY early in their processing.
+    if any([len(tup) != 2 for tup in event]):
+        raise TypeError(
+            "Improperly formatted inputs for simplify(): an event element is a tuple with length not equal to 2."
+        )
+    for variable, intervention in event:
+        if not isinstance(variable, Variable) or not (
+            isinstance(intervention, Intervention) or intervention is None
+        ):
+            # FIXME please replace all instances of concatenating str() with usage of f strings
+            raise TypeError(
+                "Improperly formatted inputs for simplify(): check input event element ("
+                + str(variable)
+                + ", "
+                + str(intervention)
+                + ")"
+            )
+        if (
+            isinstance(variable, Variable)
+            and not isinstance(variable, CounterfactualVariable)
+            and variable.star is not None
+        ):
+            raise TypeError(
+                f"Improperly formatted inputs for simplify(): {variable} should have "
+                "a star value of None because it is a Variable"
+            )
+
+    # It's not enough to minimize the variables, we need to keep track of what values are associated with
+    # the minimized variables. So we minimize the event.
+    # minimized_variables: set[Variable] = minimize(variables={variable for variable, _ in event}, graph=graph)
+    minimized_event: Event = minimize_event(event=event, graph=graph)
+    # logger.debug("In simplify: minimized_event = " + str(minimized_event))
+
+    # Split the query into Y_x variables and Y_y ("reflexive") variables
+    (
+        reflexive_interventions_event,
+        nonreflexive_interventions_event,
+    ) = _split_event_by_reflexivity(minimized_event)
+
+    # Creating this dict addresses part 1 of Line 3:
+    # :math: If there exists $Y_{\mathbf{X}} \in \mathbf{Y_\ast}$ with two consistent values in
+    # $\mathbf{y_\ast} \cap Y_{\mathbf{X}}$ then remove repeated variables from
+    # $\mathbf{Y_\ast}$ and values $\mathbf{y_\ast}$.
+    minimized_nonreflexive_variable_to_value_mappings: dict[Variable, set[Intervention | None]] = (
+        _remove_repeated_variables_and_values(nonreflexive_interventions_event)
+    )
+    # Creating this dict partly addresses part 2 of Line 3:
+    # :math: If there exists $Y_{y} \in \mathbf{Y_\ast}$ with
+    # $\mathbf{y_\ast} \cap Y_{y} = y$ then remove repeated variables from
+    # $\mathbf{Y_\ast}$ and values $\mathbf{y_\ast}$.
+    #
+    # There is an exception: we don't yet handle the edge case that the CounterfactualVariable Y_y
+    # and the Intervention Y, when observed as part of the same event, are considered repeated
+    # variables after minimization has taken place.
+    minimized_reflexive_variable_to_value_mappings: dict[Variable, set[Intervention | None]] = (
+        _remove_repeated_variables_and_values(reflexive_interventions_event)
+    )
+
+    # logger.debug(
+    #    "In simplify after part 1 of line 3: minimized_nonreflexive_variable_to_value_mappings = "
+    #    + str(minimized_nonreflexive_variable_to_value_mappings)
+    # )
+    # logger.debug(
+    #    "                                    minimized_reflexive_variable_to_value_mappings = "
+    #    + str(minimized_reflexive_variable_to_value_mappings)
+    # )
+
+    # Line 2 of SIMPLIFY.
+    if _any_variables_with_inconsistent_values(
+        nonreflexive_variable_to_value_mappings=minimized_nonreflexive_variable_to_value_mappings,
+        reflexive_variable_to_value_mappings=minimized_reflexive_variable_to_value_mappings,
+    ):
+        return None
+
+    # logger.debug(
+    #    "In simplify after line 2: minimized_nonreflexive_variable_to_value_mappings = "
+    #    + str(minimized_nonreflexive_variable_to_value_mappings)
+    # )
+    # logger.debug(
+    #    "                                    minimized_reflexive_variable_to_value_mappings = "
+    #    + str(minimized_reflexive_variable_to_value_mappings)
+    # )
+
+    # Now we're able to reduce the reflexive counterfactual variables to interventions.
+    # This simultaneously addresses Part 2 of Line 3:
+    # :math: **if** there exists $Y_y\in \mathbf{Y}_\ast$ with $\mathbf{y_*} \cap Y_y = y$ **then**
+    # remove repeated variables from $\mathbf{Y_\ast}$ and values $\mathbf{y_\ast}$.
+    minimized_reflexive_variable_to_value_mappings = (
+        _reduce_reflexive_counterfactual_variables_to_interventions(
+            minimized_reflexive_variable_to_value_mappings
+        )
+    )
+
+    # logger.debug(
+    #    "In simplify after part 2 of line 3: minimized_reflexive_variable_to_value_mappings = "
+    #    + str(minimized_reflexive_variable_to_value_mappings)
+    # )
+
+    # (Original part 2 of Line 3):
+    # :math: **if** there exists $Y_y\in \mathbf{Y}_\ast$ with $\mathbf{y_*} \cap Y_y = y$ **then**
+    # remove repeated variables from $\mathbf{Y_\ast}$ and values $\mathbf{y_\ast}$.
+    # (
+    #    minimized_variable_to_value_mappings,
+    #    minimized_variables,
+    # ) = _simplify_self_interventions_with_consistent_values(
+    #    minimized_variable_to_value_mappings, minimized_variables
+    # )
+
+    # Call line 2 of SIMPLIFY again to handle counterfactual variables such as (Y @ -Y, -Y) that Line 3 reduces to
+    # interventions inconsistent with existing variables such as (Y, +Y). This handles an
+    # edge case for part 2 of Line 3:
+    # :math: **if** there exists $Y_y\in \mathbf{Y}_\ast$ with $\mathbf{y_*} \cap Y_y = y$ **then**
+    # remove repeated variables from $\mathbf{Y_\ast}$ and values $\mathbf{y_\ast}$.
+    if _any_variables_with_inconsistent_values(
+        nonreflexive_variable_to_value_mappings=minimized_nonreflexive_variable_to_value_mappings,
+        reflexive_variable_to_value_mappings=minimized_reflexive_variable_to_value_mappings,
+    ):
+        return None
+
+    # logger.debug(
+    #    "In simplify after line 2: minimized_nonreflexive_variable_to_value_mappings = "
+    #    + str(minimized_nonreflexive_variable_to_value_mappings)
+    # )
+    # logger.debug(
+    #    "                                    minimized_reflexive_variable_to_value_mappings = "
+    #    + str(minimized_reflexive_variable_to_value_mappings)
+    # )
+
+    simplified_event = [
+        (key, minimized_nonreflexive_variable_to_value_mappings[key].pop())
+        for key in minimized_nonreflexive_variable_to_value_mappings
+    ] + [
+        (key, minimized_reflexive_variable_to_value_mappings[key].pop())
+        for key in minimized_reflexive_variable_to_value_mappings
+    ]
+    # FIXME please replace all instances of concatenating str() with usage of f strings
+    logger.debug("In simplify before return: return value = " + str(simplified_event))
+    return simplified_event
+
+
+def minimize_event(*, event: Event, graph: NxMixedGraph) -> Event:
+    r"""Minimize a set of counterfactual variables wrapped into an event.
+
+    Source: last paragraph in Section 4 of [correa22a]_, before Section 4.1.
+    $||\mathbf Y_*|| = {||Y_{\mathbf x}|| | Y_{\mathbf x}} \in {\mathbf Y_*}$.
+
+    :param event: A set of counterfactual variables to minimize (some may have no interventions),
+                  along with their associated values.
+    :param graph: The graph containing them.
+    :returns:
+        An event comprised of a set of minimized counterfactual variables such that each minimized variable
+        is an element of the original set, and the values associated with those variables.
+    """
+    return [(minimize_counterfactual(variable, graph), value) for variable, value in event]
+
+
+def same_district(event: set[Variable], graph: NxMixedGraph) -> bool:
+    """Check if a set of counterfactual variables are in the same district (c-component) of a graph.
+
+    Edge cases: return True if the event contains one or no events.
+
+    :param event: A set of counterfactual variables.
+    :param graph: The graph containing them.
+    :returns: A boolean.
+    """
+    if len(event) < 1:
+        return True
+    visited_districts: set[frozenset] = {
+        graph.get_district(variable.get_base()) for variable in event
+    }
+    return len(visited_districts) == 1
+
+
+def is_counterfactual_factor_form(*, event: set[Variable], graph: NxMixedGraph) -> bool:
+    """Check if a set of counterfactual variables is a counterfactual factor in a graph.
+
+    See [correa22a]_, Definition 3.4. A "ctf-factor" is a counterfactual factor.
+
+    For a counterfactual variable to be in counterfactual factor form, all of its parents
+    must be in the intervention set and the variable itself cannot be (because Y_y = y,
+    so we want to apply the counterfactual factor form of y by itself). If the variable
+    is not a counterfactual variable, then it must have no parents to be in counterfactual
+    factor form.
+
+    :param event: A set of counterfactual variables, some of which may have no interventions.
+    :param graph: The corresponding graph.
+    :returns: A single boolean value (True if the input event is a ctf-factor, False otherwise).
+    """
+    for variable in event:
+        parents = list(graph.directed.predecessors(variable.get_base()))
+        if isinstance(variable, CounterfactualVariable):
+            if any(
+                variable.get_base().name == intervention.name
+                for intervention in variable.interventions
+            ):
+                return False
+            for parent in parents:
+                if not any(
+                    parent.name == intervention.name for intervention in variable.interventions
+                ):
+                    return False
+        else:
+            if len(parents) > 0:
+                return False
+    return True
+
+
+def get_counterfactual_factors(*, event: set[Variable], graph: NxMixedGraph) -> list[set[Variable]]:
+    """Decompose a joint probability distribution of counterfactual variables.
+
+    Rather than work with probability distributions directly, the function
+    takes in a set of counterfactual variables and returns a set of
+    sets that correspond to factors associated with individual districts
+    (c-components) in the graph. The function returns "None" if any of the
+    counterfactual variables are not in counterfactual factor form, or
+    if an event variable is not in any district (i.e., not in the graph).
+
+    See [correa22a]_, Definition 3.4. A "ctf-factor" is a counterfactual factor.
+
+    :param event:
+        A set of counterfactual variables, some of which may
+        have no interventions. All counterfactual variables must be in counterfactual
+        factor form.
+    :param graph: The corresponding graph.
+    :raises ValueError: An event is not in counterfactual factor form.
+    :returns:
+        A set of sorted lists of counterfactual variables in counterfactual factor
+        form, with each list associated with a district of the graph. The lists
+        need not contain every variable in the district, but they can't contain
+        variables missing from the graph.
+    """
+    if not is_counterfactual_factor_form(event=event, graph=graph):
+        logger.debug("Supposed to trigger ValueError in get_counterfactual_factors().")
+        # FIXME please replace all instances of concatenating str() with usage of f strings
+        raise ValueError(
+            "In get_counterfactual_factors(): the event %s is not in counterfactual factor form.",
+            str(event),
+        )
+
+    district_mappings: defaultdict[frozenset[Variable], set[Variable]] = defaultdict(set)
+    for variable in event:
+        district_mappings[graph.get_district(variable.get_base())].add(variable)
+
+    # TODO if there aren't duplicates, this can be a set of frozensets
+    return_value = [set(value) for value in district_mappings.values()]
+    return return_value
+
+
+def get_counterfactual_factors_retaining_variable_values(
+    *, event: set[tuple[Variable, Intervention | None]], graph: NxMixedGraph
+) -> list[set[tuple[Variable, Intervention | None]]]:
+    """Decompose a joint probability distribution of counterfactual variables.
+
+    Rather than work with probability distributions directly, the function
+    takes in a set of counterfactual variables and returns a set of
+    sets that correspond to factors associated with individual districts
+    (c-components) in the graph. The function returns "None" if any of the
+    counterfactual variables are not in counterfactual factor form, or
+    if an event variable is not in any district (i.e., not in the graph).
+    This variation of the function keeps the values of the counterfactual
+    variables bound to the variables in the form of tuples (necessary for
+    Algorithm 2 of [correa22a]_).
+
+    See [correa22a]_, Definition 3.4. A "ctf-factor" is a counterfactual factor.
+
+    :param event:
+        A set of counterfactual variables, some of which may
+        have no interventions. All counterfactual variables must be in counterfactual
+        factor form.
+    :param graph: The corresponding graph.
+    :raises ValueError: An event is not in counterfactual factor form.
+    :returns:
+        A set of sorted lists of counterfactual variables in counterfactual factor
+        form along with the values for those variables, with each list associated
+        with a district of the graph. The lists need not contain every variable in
+        the district, but they can't contain variables missing from the graph.
+    """
+    event_without_values = {variable for variable, _ in event}
+
+    if not is_counterfactual_factor_form(event=event_without_values, graph=graph):
+        logger.debug(
+            "Supposed to trigger ValueError in get_counterfactual_factors_retaining_variable_values()."
+        )
+        # FIXME please replace all instances of concatenating str() with usage of f strings
+        logger.debug(f"    Event = {str(event)}")
+        raise ValueError(
+            "In get_counterfactual_factors_retaining_variable_values(): the event %s is not"
+            + " in counterfactual factor form.",
+            str(event),
+        )
+
+    district_mappings: defaultdict[
+        frozenset[Variable], set[tuple[Variable, Intervention | None]]
+    ] = defaultdict(set)
+    for variable, value in event:
+        district_mappings[graph.get_district(variable.get_base())].add((variable, value))
+
+    logger.debug(
+        "In get_counterfactual_factors_retaining_variable_values(): district_mappings = "
+        + str(district_mappings)
+    )
+
+    # TODO if there aren't duplicates, this can be a set of frozensets
+    return_value = [set(value) for value in district_mappings.values()]
+    logger.debug(
+        "In get_counterfactual_factors_retaining_variable_values(): returning " + str(return_value)
+    )
+    return return_value
+
+
+def convert_to_counterfactual_factor_form(*, event: Event, graph: NxMixedGraph) -> Event:
+    r"""Convert a list of variables and their values to counterfactual factor ("ctf-factor") form.
+
+    That requires intervening on all the parents of each counterfactual variable.
+
+    :param event: A list of variables (which may have interventions) and their values (:math:`\mathbf W_\ast`).
+    :param graph: The corresponding graph.
+    :returns:
+        The output above, represented as a set of counterfactual variables (those without interventions
+        are just variables).
+
+        :math:`w_{1[\mathbf{pa_{1}}]},w_{2[\mathbf{pa_{2}}]},\cdots,w_{l[\mathbf{pa_{l}}]}`
+        for each :math:`W_i \in \mathbf V`.
+    """
+    result: Event = []
+    for variable, value in event:
+        candidate_parents = set(graph.directed.predecessors(variable.get_base()))
+        if isinstance(variable, CounterfactualVariable):
+            parents = {
+                intervention
+                for intervention in variable.interventions
+                if intervention.get_base() in candidate_parents
+            }
+        else:
+            parents = set()
+        parent_names = {parent.get_base() for parent in parents}
+        parents.update(
+            {
+                candidate_parent
+                for candidate_parent in candidate_parents
+                if candidate_parent.get_base() not in parent_names
+            }
+        )
+        if len(parents) > 0:
+            # FIXME why not just append the list? much better for readability
+            result += [(variable.get_base().intervene(parents), value)]
+        else:
+            result += [(variable.get_base(), value)]
+    return result
+
+
+def do_counterfactual_factor_factorization(
+    *, variables: list[tuple[Variable, Intervention]], graph: NxMixedGraph
+) -> tuple[Expression, list[tuple[Variable, Intervention]]]:
+    r"""Get the counterfactual factor form for a query, factorized according to the graph c-components.
+
+    :param variables:
+        A list of counterfactual variables (the left side of Equation 11 in [correa22a]_).
+
+        :math:P*( \mathbf y_*) (i.e., a joint probability distribution corresponding to a query).
+    :param graph: The corresponding graph.
+    :raises TypeError: do_counterfactual_factorization() requires at least one variable in the query variable set.
+    :returns:
+        An expression following the right side of Equation 15 in [correa22a]_ (example: Equation 16).
+        Additionally, the function returns a tuple matching variables to interventions, in order to specify
+        the values of those variables in the returned expression that are not indexes of the summation term
+        (i.e. the expression doesn't marginalize over those variables).
+
+        :math:Sum_{ \mathbf d_* \backslash \mathbf y_*} P*( \mathbf d_* ), where :math:\mathbf D_* = An( \mathbf Y_* ),
+        and where P*( \mathbf d_* ) has been further decomposed as per
+        :math: P*( \mathbf d_* ) = prod_{j}(P*( \mathbf c_{j*}) (Equation 15).
+    """
+    # We can't directly compute the ancestral set via a set comprehension because get_ancestors_of_counterfactual()
+    # returns mutable sets, so we'd get an 'unhashable type: set' error
+    # note from @cthoyt - use frozenset if you want immutable/hashable sets
+    #                     (Thanks!)
+    #
+    # TODO: Do we need to check for consistency among the elements of the ancestral set before
+    #       applying the union operation?
+    #
+    # TODO: Check that we get variables only, and not values as well, from calling
+    # get_ancestors_of_counterfactual() with these inputs. Because we want the ancestral set.
+    if not variables:
+        raise TypeError(
+            # FIXME the stack trace always says which function was called when an exception
+            #  is raised, so it's not necessary to write the name of the function inside.
+            #  in fact, it's better not to since this might get out of sync with the function name
+            "do_counterfactual_factorization() requires at least one variable in the query."
+        )
+
+    result_event: list[tuple[Variable, Intervention]] = [
+        (convert_to_counterfactual_factor_form(event=[(variable, value)], graph=graph)[0][0], value)
+        for variable, value in variables
+    ]
+
+    ancestral_set: set[Variable] = set()
+    for counterfactual_variable, _ in variables:
+        ancestral_set.update(get_ancestors_of_counterfactual(counterfactual_variable, graph))
+
+    #  e.g., Equation 14 in [correa22a]_, without the summation component.
+    ancestral_set_in_counterfactual_factor_form: set[Variable] = {
+        convert_to_counterfactual_factor_form(event=[(variable, None)], graph=graph)[0][0]
+        for variable in ancestral_set
+    }
+
+    # P*(d_*). It's a counterfactual variable hint, so a distribution can be constructed from it.
+    ancestral_set_variable_names: set[Variable] = {
+        variable.get_base() for variable in ancestral_set_in_counterfactual_factor_form
+    }
+
+    outcome_variable_bases: set[Variable] = {variable.get_base() for (variable, _) in variables}
+
+    # Decompose the query by c-component (e.g., Equation 16 in [correa22a]_)
+    ancestral_set_subgraph = graph.subgraph(ancestral_set_variable_names)
+    factorized_ancestral_set: list[set[Variable]] = get_counterfactual_factors(
+        event=ancestral_set_in_counterfactual_factor_form, graph=ancestral_set_subgraph
+    )
+
+    result_expression = Product.safe(P(factor) for factor in factorized_ancestral_set)
+
+    # The summation portion of Equation 11 in [correa22a]_
+    sum_range = ancestral_set_variable_names - outcome_variable_bases
+
+    result_expression = Sum.safe(result_expression, sum_range)
+    return result_expression, result_event
+
+
+def counterfactual_factors_are_transportable(
+    *, factors: set[Variable], domain_graph: NxMixedGraph
+) -> bool:
+    """Determine if a set of counterfactual factors can be transported from a domain to the target.
+
+    :param domain_graph: Corresponds to the domain from which we're doing the transporting.
+    :param factors: The counterfactual factors in question.
+    :returns: Whether the query is transportable.
+    """
+    nodes = set(domain_graph.nodes())
+    return not any(transport_variable(factor.get_base()) in nodes for factor in factors)
+
+
+def _remove_transportability_vertices(*, vertices: Collection[Variable]) -> set[Variable]:
+    """Remove the transportability nodes from a set of vertices.
+
+    :param vertices: The input vertices.
+    :returns: The input vertices, without the transportability nodes.
+    """
+    return {v for v in vertices if not is_transport_node(v)}
+
+
+def validate_inputs_for_transport_district_intervening_on_parents(
+    *,
+    district: Collection[Variable],
+    domain_graphs: list[tuple[NxMixedGraph, list[Variable]]],
+    domain_data: list[tuple[Collection[Variable], PopulationProbability]],
+) -> None:
+    r"""Conduct pre-processing checks for the sigma-TR algorithm from [correa22a]_ (Algorithm 4 in Appendix B).
+
+    :param district: the C-component $\mathbf{C}\_{i}$ under analysis.
+    :param domain_graphs: A set of $K$ tuples, one for each of the $K$ domains. Each tuple
+           contains a selection diagram for that domain. In particular the graph contains
+           transportability nodes for every vertex that is distributed differently in the
+           domain in question than in the target domain (e.g., Vertex Z in Figure 3(a)
+           in [correa22a]_), and it is a causal diagram such that its edges represent
+           the state of the graph after a regime corresponding to domain $k$ has been
+           applied (e.g., policy $\sigma_{X}$ in Figure 4 of [correa22a]_). The second
+           element of the tuple is a topologically sorted list of all the vertices in
+           the corresponding graph that are not transportability nodes. (Nodes that
+           have no parents come first in such lists.)
+    :param domain_data: Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+           $K$ tuples, one for each of the $K$ domains. Each tuple contains a set of
+           variables corresponding to $\sigma_{\mathbf{Z}_{k}}$ and an expression
+           denoting the probability distribution
+           $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$.
+    :raises NotImplementedError: a domain_data probability expression of type One() or Zero() was
+           passed in.
+    :raises TypeError: the input arguments are in an improper format or not internally consistent.
+    :raises KeyError: a variable in an input argument is missing from another input argument
+           and should be there.
+    """
+    # Preliminary checks, starting with type checking
+    if not (isinstance(district, Collection) and all(isinstance(v, Variable) for v in district)):
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: "
+            + "the input district must be a Collection of Variable objects."
+        )
+    if not (isinstance(domain_graphs, list) and all(isinstance(t, tuple) for t in domain_graphs)):
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: the "
+            + "input domain graphs must be a list of tuples."
+        )
+    if not all(
+        isinstance(g, NxMixedGraph)
+        and isinstance(l, list)
+        and all(isinstance(v, Variable) for v in l)
+        for g, l in domain_graphs
+    ):
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: the input domain "
+            + "graph tuples must all contain NxMixedGraph objects and lists of variables."
+        )
+    if not (isinstance(domain_data, list) and all(isinstance(t, tuple) for t in domain_data)):
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: the "
+            + "input domain data must be a list of tuples."
+        )
+    if not all(
+        isinstance(sigma_z, Collection)
+        and all(isinstance(v, Variable) for v in sigma_z)
+        and isinstance(e, Expression)
+        for sigma_z, e in domain_data
+    ):
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: the input "
+            + "domain data tuples must all contain Collections of Variable objects "
+            + "(first element) and Expressions (second element)."
+        )
+    if any(e == Zero() or e == One() for _, e in domain_data):
+        raise NotImplementedError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: this algorithm "
+            + "does not currently handle domain_data probability expressions that are of type "
+            + "One() or Zero()."
+        )
+    # Check we have no empty lists
+    if len(domain_graphs) == 0 or len(domain_data) == 0:
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: empty list for "
+            + "either domain_graphs or domain_data. Check your inputs."
+        )
+    if len(district) == 0:
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: the "
+            + "input district cannot be an empty set."
+        )
+    if any(len(g.nodes()) == 0 for g, _ in domain_graphs):
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: at least one input "
+            + "domain graph contained no nodes. Check your inputs."
+        )
+    if any(len(topo) == 0 for _, topo in domain_graphs):
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: an input set of "
+            + "topologically sorted vertices was empty. Check your inputs."
+        )
+    if len(domain_graphs) != len(domain_data):
+        raise TypeError(
+            "In validate_inputs_for_transport_district_intervening_on_parents: the length of the "
+            + "domain_graphs and domain_data must be the same."
+        )
+    # Technically the topologically sorted vertices could be for the graph $G$ containing $G_{\mathbf{C}_{i}}$,
+    # but we currently have a stricter requirement that they are for $G_{\mathbf{C}_{i}}$. That requirement
+    # could be relaxed if it becomes a computational burden in the ctf_TRu algorithm.
+    for k in range(len(domain_graphs)):
+        logger.debug("k = " + str(k))
+        topo_vertices = frozenset(domain_graphs[k][1])
+        expression_vertices = frozenset(domain_data[k][1].get_variables())
+        graph_vertices = frozenset(domain_graphs[k][0].nodes())
+        graph_vertices_without_transportability_nodes = frozenset(
+            _remove_transportability_vertices(vertices=graph_vertices)
+        )
+        policy_vertices = frozenset(domain_data[k][0])
+        if topo_vertices != graph_vertices:
+            raise KeyError(
+                "In validate_inputs_for_transport_district_intervening_on_parents: the vertices "
+                + "in each domain graph must match those in the "
+                + "corresponding topologically sorted list of vertices. Check your inputs and "
+                + "note that the topologically sorted vertex lists "
+                + "must contain any transportability nodes. An easy way to "
+                + "generate a usable list is to call "
+                + "[graph_name].topological_sort() on the graph. "
+                + "Graph vertices: "
+                + str(graph_vertices)
+                + ". List: "
+                + str(topo_vertices)
+                + "."
+            )
+        # It's possible for the probability distribution to contain vertices not in the graph
+        # due to conditioning on vertices outside the c-component associated with this graph.
+        # The other way around is not possible, though.
+        if not all(v in expression_vertices for v in graph_vertices_without_transportability_nodes):
+            raise KeyError(
+                "In validate_inputs_for_transport_district_intervening_on_parents: some of the "
+                + "vertices in a domain graph do not appear in the expression"
+                + " for the probability of the graph. Check your inputs. Graph vertices: "
+                + str(graph_vertices_without_transportability_nodes)
+                + ". Expression vertices: "
+                + str(expression_vertices)
+                + "."
+            )
+        if not all(v in graph_vertices_without_transportability_nodes for v in policy_vertices):
+            raise KeyError(
+                "In validate_inputs_for_transport_district_intervening_on_parents: the set of "
+                + "vertices for which a policy has been applied for one "
+                + "of the domains contains at least one vertex not in the domain graph. Check your inputs. "
+                + "Policy vertices: "
+                + str(policy_vertices)
+                + ". Graph vertices: "
+                + str(graph_vertices_without_transportability_nodes)
+            )
+        for v in district:
+            if v not in graph_vertices_without_transportability_nodes:
+                raise KeyError(
+                    "In validate_inputs_for_transport_district_intervening_on_parents: one of "
+                    + "the variables in the input district "
+                    + "is not in a domain graph. District: "
+                    + str(district)
+                    + ". Node missing from the graph: "
+                    + str(v)
+                    + ". Nodes in the graph: "
+                    + str(graph_vertices_without_transportability_nodes)
+                    + "."
+                )
+    return
+
+
+def _no_intervention_variables_in_domain(
+    *, district: Collection[Variable], interventions: Collection[Variable]
+):
+    r"""Check that a district in a graph contains no intervention veriables.
+
+    Helper function for the transport_district_intervening_on_parents algorithm
+    from [correa22a]_ (Algorithm 4 in Appendix B).
+    :param district: the C-component $\mathbf{C}\_{i}$ under analysis.
+    :param interventions: Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+           variables corresponding to $\sigma_{\mathbf{Z}_{k}}$.
+    :returns: true or false.
+    """
+    return len(set(district).intersection(interventions)) == 0
+
+
+def _no_transportability_nodes_in_domain(
+    *, district: Collection[Variable], domain_graph: NxMixedGraph
+):
+    r"""Check that a district in a graph contains no transportability nodes.
+
+    Helper function for the transport_district_intervening_on_parents algorithm from
+    [correa22a]_ (Algorithm 4 in Appendix B).
+    :param district: the C-component $\mathbf{C}\_{i}$ under analysis.
+    :param domain_graph: a selection diagram for the domain in question.
+           The graph contains a transportability node for every vertex distributed differently
+           in the domain in question than in the target domain (e.g., Vertex Z in Figure 3(a)
+           in [correa22a]_), and it is a causal diagram such that its edges represent
+           the state of the graph after a regime corresponding to domain $k$ has been
+           applied (e.g., policy $\sigma_{X}$ in Figure 4 of [correa22a]_).
+    :returns: true or false.
+    """
+    return not any(transport_variable(v) in domain_graph.nodes() for v in district)
+
+
+def transport_district_intervening_on_parents(
+    *,
+    district: Collection[Variable],
+    domain_graphs: list[tuple[NxMixedGraph, list[Variable]]],
+    domain_data: list[tuple[Collection[Variable], PopulationProbability]],
+) -> Expression | None:
+    r"""Implement the sigma-TR algorithm from [correa22a]_ (Algorithm 4 in Appendix B).
+
+    See also: [correa20a]_, which contains the original sigma-TR algorithm. Algorithm 4 in
+    [correa22a]_ is the algorithm in [correa20a]_ modified to run on a single district in a
+    graph.
+
+    :param district: the C-component $\mathbf{C}\_{i}$ under analysis.
+    :param domain_graphs: A set of $K$ tuples, one for each of the $K$ domains. Each tuple
+           contains a selection diagram for that domain. In particular the graph contains
+           transportability nodes for every vertex that is distributed differently in the
+           domain in question than in the target domain (e.g., Vertex Z in Figure 3(a)
+           in [correa22a]_), and it is a causal diagram such that its edges represent
+           the state of the graph after a regime corresponding to domain $k$ has been
+           applied (e.g., policy $\sigma_{X}$ in Figure 4 of [correa22a]_). The second
+           element of the tuple is a topologically sorted list of all the vertices in
+           the corresponding graph that are not transportability nodes. (Nodes that
+           have no parents come first in such lists.)
+    :param domain_data:
+            .. todo::
+
+                Start with a human readable version of explaining what the data structure is.
+                Then, after it is possible to understand without thinking about math at all,
+                you can then say "this corresponds to the impenetrable math notation used in some paper"
+
+            Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+            $K$ tuples, one for each of the $K$ domains. Each tuple contains a set of
+            variables corresponding to $\sigma_{\mathbf{Z}_{k}}$ and an expression
+            denoting the probability distribution
+            $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$.
+    :raises ValueError: the vertices in the input district are part of more than one district
+           in a domain graph.
+    :returns: A probabilistic expression for $P^{\ast}_{Pa(\mathbf{C})_{i}}(\mathbf{C}\_i)$ if
+           it is transportable, or None if it is not transportable.
+    """
+    # We don't require the user to input $\mathcal{G}^{\ast}$, the target graph, and
+    # therefore can't verify that the input district is in fact a district of the
+    # target graph as part of validating the user input.
+    validate_inputs_for_transport_district_intervening_on_parents(
+        district=district, domain_graphs=domain_graphs, domain_data=domain_data
+    )
+    logger.debug("In transport_district_intervening_on_parents: input validated successfully.")
+    # Line 1
+    # FIXME use list comprehension + enumerate
+    for k in range(len(domain_graphs)):
+        # Also Line 1 (the published pseudocode could break the for loop and this test into two lines)
+        logger.debug(" k = " + str(k))
+        logger.debug(
+            " _no_intervention_variables_in_domain: "
+            + str(
+                _no_intervention_variables_in_domain(
+                    district=district, interventions=domain_data[k][0]
+                )
+            )
+        )
+        logger.debug(
+            " _no_transportability_nodes_in_domain: "
+            + str(
+                _no_transportability_nodes_in_domain(
+                    district=district, domain_graph=domain_graphs[k][0]
+                )
+            )
+        )
+        if _no_intervention_variables_in_domain(
+            district=district, interventions=domain_data[k][0]
+        ) and _no_transportability_nodes_in_domain(
+            district=district, domain_graph=domain_graphs[k][0]
+        ):
+            logger.debug("In transport_district_intervening_on_parents: domain = " + str(k))
+            domain_graph = domain_graphs[k][0]
+            domain_graph_variables = _remove_transportability_vertices(
+                vertices=domain_graph.nodes()
+            )
+            domain_topo = domain_graphs[k][1]
+
+            # Line 2
+            domain_graph_district = frozenset().union(
+                *[domain_graph.get_district(v) for v in district]
+            )  # $B_{i}$
+            logger.debug(
+                "In transport_district_intervening_on_parents: domain_graph_district = "
+                + str(domain_graph_district)
+            )
+            # Sanity check: confirm that $C_{i} \subseteq B_{i}$
+            if any(
+                domain_graph_district != frozenset(domain_graph.get_district(v)) for v in district
+            ):
+                # FIXME use f-string and triple quote. This is unreadable.
+                raise ValueError(
+                    "Error in transport_district_intervening_on_parents: the vertices in an input district "
+                    + "are part of more than one district in a domain graph. Input district: "
+                    + str(district)
+                    + " and domain_graph_district derived from it: "
+                    + str(domain_graph_district)
+                    + ". "
+                    + "Also, here is each domain graph district we are comparing it to:"
+                    + str({frozenset(domain_graph.get_district(v)) for v in district})
+                    + "Domain index: "
+                    + str(k)
+                    + "."
+                )
+
+            # Line 3
+            logger.debug("Subgraph_probability: " + domain_data[k][1].to_latex())
+            domain_graph_district_q_probability = compute_c_factor(
+                district=domain_graph_district,  # district=district,
+                subgraph_variables=domain_graph_variables,
+                subgraph_probability=domain_data[k][1],
+                graph_topo=domain_topo,
+            )
+            logger.debug(
+                "domain_graph_district_q_probability: "
+                + domain_graph_district_q_probability.to_latex()
+            )
+            # Line 4
+            district_q_probability = identify_district_variables(
+                input_variables=frozenset(district),
+                input_district=domain_graph_district,
+                district_probability=domain_graph_district_q_probability,
+                graph=domain_graph,
+                topo=domain_topo,
+            )
+            # Incorporate the domain index in the representation for Q
+
+            # Lines 5-7
+            if district_q_probability is not None:
+                return district_q_probability
+    # Line 9
+    return None
+
+
+def _transport_unconditional_counterfactual_query_line_2(
+    event: Event, graph: NxMixedGraph
+) -> tuple[
+    set[tuple[Variable, Intervention | None]], list[set[tuple[Variable, Intervention | None]]]
+]:
+    r"""Implement the ctfTRu algorithm from [correa22a]_ (Algorithm 2).
+
+    :param event:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param graph: The graph corresponding to the target domain for the query.
+    :returns: a tuple containing:
+        1. An expression for $W_{\ast} = An(\mathbf{Y_{\ast}})$, which is an event
+              because some variables in $W_{\ast}$ may have values and others may not.
+              We need to keep the known values coupled to their associated variables.
+        2. A list of ctf-factors corresponding to $W_{\ast}$, and values for those
+              variables as well when available.
+    """
+    ancestral_set: set[Variable] = set()
+    # $W_{\ast}$
+    for variable, _ in event:
+        ancestral_set.update(get_ancestors_of_counterfactual(variable, graph))
+    outcome_value_dict = {variable: value for variable, value in event}
+    ancestral_set_with_values: set[tuple[Variable, Intervention | None]] = {
+        (
+            (variable, outcome_value_dict[variable])
+            if variable in outcome_value_dict
+            else (variable, None)
+        )
+        for variable in ancestral_set
+    }
+    logger.debug(
+        "In _transport_unconditional_counterfactual_query_line_2: ancestral_set_with_values = "
+        + str(ancestral_set_with_values)
+    )
+
+    #  e.g., Equation 13 in [correa22a]_, without the summation component.
+    ancestral_set_in_counterfactual_factor_form_with_values: set[
+        tuple[Variable, Intervention | None]
+    ] = {
+        (convert_to_counterfactual_factor_form(event=[(variable, value)], graph=graph)[0][0], value)
+        for variable, value in ancestral_set_with_values
+    }
+    ancestor_bases = {v.get_base() for v in ancestral_set}
+    outcome_ancestor_graph = graph.subgraph(ancestor_bases)
+    factorized_ancestral_set_with_values: list[set[tuple[Variable, Intervention | None]]] = (
+        get_counterfactual_factors_retaining_variable_values(
+            event=ancestral_set_in_counterfactual_factor_form_with_values,
+            graph=outcome_ancestor_graph,
+        )
+    )
+    logger.debug(
+        "In _transport_unconditional_counterfactual_query_line_2: factorized_ancestral_set_with_values = "
+        + str(factorized_ancestral_set_with_values)
+    )
+    return ancestral_set_with_values, factorized_ancestral_set_with_values
+
+
+def _any_variable_values_inconsistent_with_interventions(
+    event: Collection[tuple[Variable, Intervention | None]]
+) -> bool:
+    r"""Determine whether a counterfactual factor has a variable value inconsistent with any intervention value.
+
+    Source: Definition 4.1 in [correa22a]_ (Algorithm 2), part (i).
+    :param event:
+        $\mathbf{W_{\ast}}$, a set of counterfactual variables in V and $\mathbf{w_{\ast}}$, a set of
+        values for $\mathbf{W_{\ast}}$. We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :returns: True if the counterfactual factor has a variable value inconsistent with any intervention value, and
+        false otherwise.
+    """
+    intervention_variable_dictionary: dict[Variable, set[Intervention]] = defaultdict(set)
+    # $W_{\ast}$
+    counterfactual_factor_variable_names = {variable.get_base() for variable, _ in event}
+    counterfactual_factor_variables_with_interventions = {
+        variable for variable, _ in event if isinstance(variable, CounterfactualVariable)
+    }
+    # $\mathbf{T} \cup \mathbf{V(W_{\ast})} = \mathbf{Z}$ per Definition 4.1 (i)
+    intervention_variable_names = {
+        intervention.get_base()
+        for variable in counterfactual_factor_variables_with_interventions
+        for intervention in variable.interventions
+    }.intersection(counterfactual_factor_variable_names)
+    for counterfactual_factor_variable, counterfactual_factor_variable_value in event:
+        if (
+            counterfactual_factor_variable.get_base() in intervention_variable_names
+            and counterfactual_factor_variable_value is not None
+        ):
+            intervention_variable_dictionary[counterfactual_factor_variable.get_base()].update(
+                {counterfactual_factor_variable_value}
+            )
+        if isinstance(counterfactual_factor_variable, CounterfactualVariable):
+            for intervention in counterfactual_factor_variable.interventions:
+                if intervention.get_base() in intervention_variable_names:
+                    intervention_variable_dictionary[intervention.get_base()].update({intervention})
+    logger.debug(
+        "In _any_variable_values_inconsistent_with_interventions: dictionary = "
+        + str(intervention_variable_dictionary)
+    )
+    return any(len(values) > 1 for values in intervention_variable_dictionary.values())
+
+
+def _any_inconsistent_intervention_values(
+    event: Collection[tuple[Variable, Intervention | None]]
+) -> bool:
+    r"""Determine whether a counterfactual factor has two inconsistent intervention values.
+
+    :param event:
+        $\mathbf{W_{\ast}}$, a set of counterfactual variables in V and $\mathbf{w_{\ast}}$, a set of
+        values for $\mathbf{W_{\ast}}$. We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :returns: True if the counterfactual factor has at least two inconsistent intervention values, and false otherwise.
+
+    .. seealso:: Definition 4.1 in [correa22a]_ (Algorithm 2), part (ii).
+    """
+    base_to_interventions = defaultdict(set)
+    # $\mathbf{T}$ per Definition 4.1 (ii)
+    for counterfactual_factor_variable, _ in event:
+        if isinstance(counterfactual_factor_variable, CounterfactualVariable):
+            for intervention in counterfactual_factor_variable.interventions:
+                base_to_interventions[intervention.get_base()].add(intervention)
+    logger.debug(
+        "In _any_inconsistent_intervention_values: dictionary = " + str(base_to_interventions)
+    )
+    return any(len(values) > 1 for values in base_to_interventions.values())
+
+
+def _counterfactual_factor_is_inconsistent(
+    event: Collection[tuple[Variable, Intervention | None]]
+) -> bool:
+    r"""Determine whether a counterfactual factor is inconsistent.
+
+    :param event:
+        $\mathbf{W_{\ast}}$, a set of counterfactual variables in V and $\mathbf{w_{\ast}}$, a set of
+        values for $\mathbf{W_{\ast}}$. We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :returns: If the counterfactual factor is consistent
+
+    .. seealso:: Source: Definition 4.1 in [correa22a]_ (Algorithm 2).
+    """
+    # Are counterfactual factor and intervention values inconsistent?
+    return _any_variable_values_inconsistent_with_interventions(
+        event
+    ) or _any_inconsistent_intervention_values(event)
+    # Are different counterfactual factor intervention values inconsistent?
+
+
+def _validate_transport_unconditional_counterfactual_query_input(  # noqa:C901
+    event: Event,
+    target_domain_graph: NxMixedGraph,
+    domain_graphs: list[tuple[NxMixedGraph, list[Variable]]],
+    domain_data: list[tuple[Collection[Variable], PopulationProbability]],
+) -> None:
+    r"""Conduct pre-processing checks to transport unconditional counterfacutal queries (Algorithm 2 from [correa22a]_).
+
+    :param event:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param target_domain_graph: a graph for the target domain.
+    :param domain_graphs: A set of $K$ tuples, one for each of the $K$ domains. Each tuple
+           contains a selection diagram for that domain and a topologically sorted list
+           of all the vertices in the corresponding graph that are not transportability
+           nodes. (Nodes that have no parents come first in such lists.) The selection
+           diagram contains a transportability node for every vertex that is distributed
+           differently in the domain in question than in the target domain (e.g., Vertex
+           Z in Figure 3(a) in [correa22a]_), and it is a causal diagram such that its edges
+           represent the state of the graph after a regime corresponding to domain $k$ has
+           been applied (e.g., policy $\sigma_{X}$ in Figure 4 of [correa22a]_).
+    :param domain_data: Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+           $K$ tuples, one for each of the $K$ domains except for the target domain.
+           Each tuple contains a set of variables corresponding to
+           $\sigma_{\mathbf{Z}_{k}}$ and an expression denoting the probability distribution
+           $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$.
+    :raises TypeError: a validity check associated with either the input variables
+           or the output event failed. See the error message for specifics.
+    :raises ValueError: an input variable is of valid type but has an invalid value. See
+           the error message for specifics.
+    :raises NotImplementedError: this algorithm does not currently handle input graph probability
+           Expression objects that are One() or Zero(), or cases where the conditioned and
+           outcome variable sets share one or more variables in common.
+    """
+    # Here are all the checks (numbering is just based on convenience during implementation, and
+    #    the numbered order is not necessarily the order of implementation):
+    # 1. Type checking for the event
+    # 2. Type checking for target_domain_graph
+    # 3. Type checking for domain_graphs
+    # 4. Type checking for domain_data
+    #    4.5. Make sure probabilistic expressions in domain_data aren't Zero() or One()
+    # 5. Make sure the event isn't empty
+    # 6. (Skipped for the conditional transportability algorithm, included for unconditional
+    #    transportability) Make sure at least one event element has a non-None value
+    # 7. Check domain_graphs and domain_data aren't empty lists
+    # 8. Check all graphs in domain_graphs have nodes
+    # 9. Check all topologically sorted lists have entries
+    # 9.2. Check that the target domain graph contains no transportability nodes and is a directed acyclic graph
+    # 9.5. Check that the domain_graphs and domain_data list lengths are equal
+    # 9.7. Check that every domain graph is a directed acyclic graph
+    # 10. Check that every topological order list in domain_graphs is a valid topological order,
+    #     given the corresponding graph
+    # 11. Check the domain graph vertices are all the same as the target domain graph vertices,
+    #     net of transportability nodes
+    # 12. Check the event vertices are in the target domain graph (given check #11, that
+    #     means they're in every graph)
+    # 13. Check the event variables have the same base variable as the base variable of their
+    #     corresponding values, or are none
+    # 14. Domain graphs: make sure the vertex set of the topologically sorted vertex order matches
+    #     the set of vertices in each corresponding domain graph
+    # 15. It's possible for a graph probability expression to contain vertices not in the graph
+    #     due to conditioning on vertices outside the graph. But the graph vertices must all be
+    #     represented in that graph probability expression.
+    # 15.5. All the policy vertices must be in the graph, for each domain.
+    # 16. If the target domain graph is also in the domain_graphs list (i.e., data were collected for
+    #     the target domain), then the target domain graph in the domain_graphs list must be
+    #     identical to the target_domain_graph parameter.
+    if not (isinstance(event, list) and all(isinstance(t, tuple) and len(t) == 2 for t in event)):
+        raise TypeError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the input event "
+            + "must be a list of tuples of length 2. Check your inputs."
+        )
+    if not all(
+        isinstance(variable, Variable) and (value is None or isinstance(value, Intervention))
+        for variable, value in event
+    ):
+        raise TypeError(
+            "In _validate_transport_unconditional_counterfactual_query_input: each tuple in the input event "
+            + "must contain a Variable object and its corresponding value (an Intervention or None). "
+            + "Check your inputs."
+        )
+
+    # Type checking for inputs consistent with both Algorithms 2 and 3
+    # 2.
+    if not isinstance(target_domain_graph, NxMixedGraph):
+        raise TypeError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the target_domain_graph "
+            + "must be an NxMixedGraph object."
+        )
+
+    # Check we have no empty inputs
+    # 5.
+    if len(event) == 0:
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: empty list for "
+            + "the event. Check your inputs."
+        )
+    # 8. (Target domain graph)
+    if len(target_domain_graph.nodes()) == 0:
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the target "
+            + "domain graph contained no nodes. Check your inputs."
+        )
+
+    # Type checking for inputs consistent with Algorithms 2,3, and 4
+    # 3.
+    if not (isinstance(domain_graphs, list) and all(isinstance(t, tuple) for t in domain_graphs)):
+        raise TypeError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the "
+            + "domain_graphs input parameter must be a list of tuples."
+        )
+    if not all(
+        isinstance(g, NxMixedGraph)
+        and isinstance(l, list)
+        and all(isinstance(v, Variable) for v in l)
+        for g, l in domain_graphs
+    ):
+        raise TypeError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the input domain "
+            + "graph tuples must all contain NxMixedGraph objects and lists of variables."
+        )
+    # 4 and 4.5.
+    if not (isinstance(domain_data, list) and all(isinstance(t, tuple) for t in domain_data)):
+        raise TypeError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the "
+            + "input domain data must be a list of tuples."
+        )
+    if any(e == Zero() or e == One() for _, e in domain_data):
+        raise NotImplementedError(
+            "In _validate_transport_unconditional_counterfactual_query_input: this algorithm "
+            + "does not currently handle domain_data probability expressions that are of type "
+            + "One() or Zero()."
+        )
+    if not all(
+        isinstance(sigma_z, Collection)
+        and all(isinstance(v, Variable) for v in sigma_z)
+        and isinstance(pp, PopulationProbability)
+        for sigma_z, pp in domain_data
+    ):
+        raise TypeError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the input "
+            + "domain data tuples must all contain Collections of Variable objects "
+            + "(first element) and PopulationProbability expressions (second element)."
+        )
+
+    # 6. (Skipped for the conditional transportability algorithm, included for unconditional
+    #    transportability) Make sure at least one event element has a non-None value
+    if all(value is None for _, value in event):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the event list "
+            + "must contain at least one variable with a value that is not None. Check your inputs."
+        )
+
+    # Check we have no empty inputs (Algorithms 2, 3, and 4)
+    # 7.
+    if len(domain_graphs) == 0 or len(domain_data) == 0:
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: empty list for "
+            + "either domain_graphs or domain_data. Check your inputs."
+        )
+    # 8.
+    if any(len(g.nodes()) == 0 for g, _ in domain_graphs):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: at least one input "
+            + "domain graph contained no nodes. Check your inputs."
+        )
+    # 9.
+    if any(len(topo) == 0 for _, topo in domain_graphs):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: an input set of "
+            + "topologically sorted vertices was empty. Check your inputs."
+        )
+    # 9.5.
+    if len(domain_graphs) != len(domain_data):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the length of the "
+            + "domain_graphs and domain_data must be the same."
+        )
+
+    # Check the target domain graph contains no transportability nodes and is a directed acyclic graph
+    # 9.2.
+    if frozenset(target_domain_graph.nodes()) != frozenset(
+        _remove_transportability_vertices(vertices=target_domain_graph.nodes())
+    ):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the target domain graph "
+            + "cannot contain a transportability node. Check your inputs."
+        )
+    if not is_directed_acyclic_graph(target_domain_graph.directed):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: the directed edges in "
+            + "the target domain graph cannot form a cycle. Check your inputs."
+        )
+    # Check the domain graph vertices are all the same as the target domain graph vertices,
+    #    net of transportability nodes
+    # 11.
+    if any(
+        frozenset(target_domain_graph.nodes())
+        != frozenset(_remove_transportability_vertices(vertices=domain_graph.nodes()))
+        for domain_graph, _ in domain_graphs
+    ):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: a domain graph contained"
+            + " different vertices than the target domain graph after excluding transportability "
+            + "nodes. Check your inputs."
+        )
+    # Check the event vertices are in the target domain graph (given the above check, that means they're in every graph)
+    # 12.
+    if any(variable.get_base() not in target_domain_graph.nodes() for variable, _ in event):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: one of the input "
+            + "event variables is not in the target domain graph. Check your inputs. "
+        )
+    # 13.
+    if any(
+        value is not None and variable.get_base() != value.get_base() for variable, value in event
+    ):
+        raise ValueError(
+            "In _validate_transport_unconditional_counterfactual_query_input: all input "
+            + "event variables must either have values of None or the same base variable "
+            + "as their corresponding values (e.g., your variable is (W @ -X) and its value "
+            + "must be +W or -W, but it's -X). Check your inputs."
+        )
+
+    # Technically the topologically sorted vertices could be for a superset of the vertices
+    # in the input graphs, but we currently require them to be for the vertices in the input graphs.
+    for k in range(len(domain_graphs)):
+        topo_vertices = frozenset(domain_graphs[k][1])
+        expression_vertices = frozenset(domain_data[k][1].get_variables())
+        graph_vertices = frozenset(domain_graphs[k][0].nodes())
+        graph_vertices_without_transportability_nodes = frozenset(
+            _remove_transportability_vertices(vertices=graph_vertices)
+        )
+        policy_vertices = frozenset(domain_data[k][0])
+        # 14.
+        if topo_vertices != graph_vertices:
+            raise ValueError(
+                "In _validate_transport_unconditional_counterfactual_query_input: the vertices "
+                + "in each domain graph must match those in the "
+                + "corresponding topologically sorted list of vertices. Check your inputs and "
+                + "note that the topologically sorted vertex lists "
+                + "must contain any transportability nodes. An easy way to "
+                + "generate a usable list is to call "
+                + "[graph_name].topological_sort() on the graph. "
+                + "Graph vertices: "
+                + str(graph_vertices)
+                + ". Topologically sorted list of vertices: "
+                + str(topo_vertices)
+                + ". Domain index (zero-indexed): "
+                + str(k)
+            )
+        # It's possible for a graph probability expression to contain vertices not in the graph
+        # due to conditioning on vertices outside the graph. But the graph vertices must all be
+        # represented in that graph probability expression.
+        # 15.
+        if not all(v in expression_vertices for v in graph_vertices_without_transportability_nodes):
+            raise ValueError(
+                "In _validate_transport_unconditional_counterfactual_query_input: some of the "
+                + "vertices in a domain graph do not appear in the expression"
+                + " for the probability of the graph. Check your inputs. Graph vertices: "
+                + str(graph_vertices_without_transportability_nodes)
+                + ". Expression vertices: "
+                + str(expression_vertices)
+                + ". Domain index (zero-indexed): "
+                + str(k)
+            )
+        # 15.5.
+        if not all(v in graph_vertices_without_transportability_nodes for v in policy_vertices):
+            raise ValueError(
+                "In _validate_transport_unconditional_counterfactual_query_input: the set of "
+                + "vertices for which a policy has been applied for one "
+                + "of the domains contains at least one vertex not in the domain graph. Check your inputs. "
+                + "Policy vertices: "
+                + str(policy_vertices)
+                + ". Graph vertices: "
+                + str(graph_vertices_without_transportability_nodes)
+                + ". Domain index (zero-indexed): "
+                + str(k)
+            )
+        # 9.7. (The directed acyclic graph check must come before the topological order check)
+        if not is_directed_acyclic_graph(domain_graphs[k][0].directed):
+            raise ValueError(
+                "In _validate_transport_unconditional_counterfactual_query_input: the directed edges in "
+                + "domain graph entry "
+                + str(k)
+                + " (zero-indexed) form a cycle and the graph must be a "
+                + "directed acyclic graph. Check your inputs."
+            )
+        # 10.
+        if not _valid_topo_list(topo=domain_graphs[k][1], graph=domain_graphs[k][0]):
+            raise ValueError(
+                "In _validate_transport_unconditional_counterfactual_query_input: the provided topologically "
+                + "sorted order of the vertices ("
+                + str(domain_graphs[k][1])
+                + ") for domain graph entry "
+                + str(k)
+                + " (zero-indexed) "
+                + "is not valid, given the input domain_graph. Check your inputs."
+            )
+        # 16. If the target domain graph is also in the domain_graphs list (i.e., data were collected for
+        #     the target domain), then the target domain graph in the domain_graphs list must be
+        #     identical to the target_domain_graph parameter.
+        # TODO: relax the code base to allow Expressions in the domain_data instead of
+        #     PopulationProbability types as follows:
+        # if isinstance(domain_data[k][1], PopulationProbability) and
+        #    str(domain_data[k][1].population)==str(TARGET_DOMAIN):
+        #    That covers a corner case where a user wishes to run this algorithm with a single domain and
+        #    without specifying a population for the graph probability. In that case the problem reduces
+        #    to running ID* anyway, so it's not likely to get much usage.
+        if str(domain_data[k][1].population) == str(TARGET_DOMAIN):
+            if domain_graphs[k][0] != target_domain_graph:
+                raise ValueError(
+                    "In _validate_transport_unconditional_counterfactual_query_input: the domain_data input contains "
+                    + 'a graph probability expression from the target domain (i.e., "pi*"), but the corresponding '
+                    + "domain_graph is not the same graph as the target_domain_graph. Check your inputs. Domain index "
+                    + "(zero-indexed): "
+                    + str(k)
+                )
+    return
+
+
+@dataclass
+class CFTDomain:
+    r"""Represents a counterfactual transport domain.
+
+    Each CFTDomain class contains a selection diagram for that domain,
+    an expression denoting the probability distribution
+    $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$,
+    a set of policy variables corresponding to $\sigma_{\mathbf{Z}_{k}}$,
+    and a topologically sorted list of all the vertices in the corresponding graph
+    that are not transportability nodes. (Nodes that have no parents come first in
+    such lists.) The selection diagram contains a transportability node for every
+    vertex that is distributed differently in the domain in question than in the
+    target domain (e.g., Vertex Z in Figure 3(a) in [correa22a]_), and it is a causal
+    diagram such that its edges represent the state of the graph after a regime
+    corresponding to domain $k$ has been applied (e.g., policy $\sigma_{X}$ in Figure
+    4 of [correa22a]_).
+    """
+
+    #: The domain graph (a selection diagram, containing transportability nodes)
+    graph: NxMixedGraph
+    #: An expression for the joint probability of the vertices in the domain graph,
+    #: which may be a conditional probability with vertices not in the graph to the
+    #: right of the conditioning bar (i.e., we may be getting a subgraph of something larger).
+    #: Can also directly give a Variable, in which case, the population probability is inferred
+    #: to be the joint population probability over all non-transport nodes in the graph
+    population: PopulationProbability
+    #: The variables in the domain receiving an intervention, which may be stochastic
+    #: (i.e., intervening on the variable does not necessarily break all incoming
+    #: edges and may even add some edges)
+    policy_variables: Collection[Variable] = field(default_factory=set)
+    #: Topological ordering of the vertices in the domain graph (optional)
+    ordering: list[Variable] | None = None
+
+    def __post_init__(self) -> None:
+        if isinstance(self.population, Population):
+            nodes = [node for node in self.graph.nodes() if not is_transport_node(node)]
+            self.population = PP[self.population](nodes)
+
+
+class UnconditionalCFTResult(NamedTuple):
+    """Represents the result from an unconditional counterfactual transportability query."""
+
+    expression: Expression
+    event: Event | None
+
+    def display(self):
+        """Display this result."""
+        from IPython.display import display
+
+        display(event_to_probability(self.event))
+        display(self.expression)
+
+
+def _event_from_counterfactuals(
+    variables: Variable | list[Variable],
+) -> list[tuple[Variable, Intervention | None]]:
+    if isinstance(variables, Variable):
+        variables = [variables]
+    rv = []
+    for variable in variables:
+        base = _event_base(variable)
+        if variable.star is not None:
+            value = Intervention(name=variable.name, star=variable.star)
+        else:
+            value = None
+        rv.append((base, value))
+    return rv
+
+
+def _event_from_counterfactuals_strict(
+    variables: Variable | list[Variable],
+) -> list[tuple[Variable, Intervention]]:
+    if isinstance(variables, Variable):
+        variables = [variables]
+    rv = []
+    for variable in variables:
+        base = _event_base(variable)
+        if variable.star is not None:
+            value = Intervention(name=variable.name, star=variable.star)
+        else:
+            raise TypeError
+        rv.append((base, value))
+    return rv
+
+
+def _event_base(variable):
+    if isinstance(variable, CounterfactualVariable):
+        return CounterfactualVariable(
+            name=variable.name,
+            star=None,
+            interventions=variable.interventions,
+        )
+    else:
+        return variable.get_base()
+
+
+def unconditional_cft(
+    *,
+    event: Variable | list[Variable],
+    target_domain_graph: NxMixedGraph,
+    domains: list[CFTDomain],
+) -> UnconditionalCFTResult | None:
+    r"""Run an unconditional counterfactual transportability query (Algorithm 2 from [correa22a]_).
+
+    :param event:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param target_domain_graph: a graph for the target domain.
+    :param domains: A set of $K$ CFTDomain classes, one for each of the $K$ domains. See the
+        documentation for CFTDomain for more details.
+
+    :returns: The result of the query as an UnconditionalCFTResult object.
+    """
+    domain_graphs = [
+        (domain.graph, domain.ordering or domain.graph.topological_sort()) for domain in domains
+    ]
+    domain_data = [(domain.policy_variables, domain.population) for domain in domains]
+    return transport_unconditional_counterfactual_query(
+        event=_event_from_counterfactuals(event),
+        target_domain_graph=target_domain_graph,
+        domain_graphs=domain_graphs,
+        domain_data=domain_data,
+    )
+
+
+def transport_unconditional_counterfactual_query(
+    *,
+    event: Event,
+    target_domain_graph: NxMixedGraph,
+    domain_graphs: list[tuple[NxMixedGraph, list[Variable]]],
+    domain_data: list[tuple[Collection[Variable], PopulationProbability]],
+) -> UnconditionalCFTResult | None:
+    r"""Implement the ctfTRu algorithm from [correa22a]_ (Algorithm 2).
+
+    :param event:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param target_domain_graph: a graph for the target domain.
+    :param domain_graphs: A set of $K$ tuples, one for each of the $K$ domains. Each tuple
+           contains a selection diagram for that domain and a topologically sorted list
+           of all the vertices in the corresponding graph that are not transportability
+           nodes. (Nodes that have no parents come first in such lists.) The selection
+           diagram contains a transportability node for every vertex that is distributed
+           differently in the domain in question than in the target domain (e.g., Vertex
+           Z in Figure 3(a) in [correa22a]_), and it is a causal diagram such that its edges
+           represent the state of the graph after a regime corresponding to domain $k$ has
+           been applied (e.g., policy $\sigma_{X}$ in Figure 4 of [correa22a]_).
+    :param domain_data: Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+           $K$ tuples, one for each of the $K$ domains except for the target domain.
+           Each tuple contains a set of variables corresponding to
+           $\sigma_{\mathbf{Z}_{k}}$ and an expression denoting the probability distribution
+           $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$.
+    :returns: an expression for $P^{\ast}(\mathbf{Y_{\ast}}=\mathbf{y_{\ast}})$
+    """
+    _validate_transport_unconditional_counterfactual_query_input(
+        event=event,
+        target_domain_graph=target_domain_graph,  #: NxMixedGraph,
+        domain_graphs=domain_graphs,  #: list[tuple[NxMixedGraph, list[Variable]]],
+        domain_data=domain_data,  #: list[tuple[Collection[Variable], PopulationProbability]],
+    )
+    # Line 1
+    simplified_event: Event | None = simplify(event=event, graph=target_domain_graph)
+    if simplified_event is None:
+        # as specified by the output for Algorithm 1 in [correa22a]_
+        return UnconditionalCFTResult(expression=Zero(), event=simplified_event)
+
+    # Line 2
+    # FIXME if you want to make type annotations, then create a named tuple
+    # tuple[set[tuple[Variable, Intervention | None]], list[set[tuple[Variable, Intervention | None]]]
+    (
+        outcome_ancestors_with_values,
+        counterfactual_factors_with_values,
+    ) = _transport_unconditional_counterfactual_query_line_2(simplified_event, target_domain_graph)
+
+    # Line 3
+    if any(
+        _counterfactual_factor_is_inconsistent(factor)
+        for factor in counterfactual_factors_with_values
+    ):
+        logger.debug(
+            "In transport_unconditional_counterfactual_query: inconsistent counterfactual "
+            + "factor. Returning FAIL (None)"
+        )
+        return None  # This means FAIL
+
+    # Line 4
+    district_probabilities_intervening_on_parents = []
+    for factor in counterfactual_factors_with_values:
+        # Sigma-TR just takes in the district and then intervenes on the parents of the district. So
+        # we need to strip the district variables of their interventions before running Sigma-TR.
+        district_without_interventions = {variable.get_base() for variable, _ in factor}
+        # Line 5
+        logger.debug(
+            "In transport_unconditional_counterfactual_query: attempting to transport district "
+            + str(district_without_interventions)
+        )
+        district_probability_intervening_on_parents = transport_district_intervening_on_parents(
+            district=district_without_interventions,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )  # The q_value
+        if district_probability_intervening_on_parents is None:
+            # logger.debug(
+            #    "In transport_unconditional_counterfactual_query: unable to transport "
+            #    + "counterfactual factor: "
+            #    + str(factor)
+            # )
+            return None  # Return FAIL (Line 13)
+        else:
+            # Note about lines 7-9 of the algorithm:
+            # Line 7 does not require a specific implementation. It is merely a comment to the
+            #    reader regarding how the district probability intervening on the district's
+            #    parents (i.e., its Q expression) is transported from one of the available domains.
+            # Line 8 is a statement that the values over which the Q expression is evaluated come
+            #    from the union of the parents of each of the parents of the district variables
+            #    and the values of the district variables themselves. Many of these values are
+            #    not associated with actual input variables $\mathbf{y_{\ast}}$. Instead, we
+            #    will end up marginalizing over them in Line 14. Correa and Bareinboim point this
+            #    out ([correa22a]_) in their appendix after Equation 27, stating that
+            #    "$\mathbf{d_{\ast}}$ is the union of $\mathbf{z_{\ast}} and the indexing values
+            #    of the sum," where the "sum" refers (in Algorithm 2) to
+            #    $\sum_{\mathbf{w_{\ast}\backslash y_{\ast}}}\prod_{i}{P^{\ast}(\mathbf{C}_{i\ast}
+            #       = \mathbf{c}_{i\ast})}$.
+            #    So, especially because no actual values of the counterfactual factor variables
+            #    or their parents will need to be used until after Line 14, we defer binding of
+            #    these variable values to a Q expression until Line 14. Line 8 is therefore
+            #    unnecessary for us as written. We do, however, check that the variables
+            #    in the Q expression are contained in the set of variables referenced in
+            #    Line 8.
+            # Line 9 involves formally evaluating Q over the set of values $\mathbf{c}$. We defer
+            #    this action until Line 14, when we do so by simply returning the simplified event
+            #    with the expression for $P^{\ast}(\mathbf{Y_{\ast} = y_{\ast}})$.
+            district_variables_and_their_parents: set[Variable] = (
+                set()
+            )  # district_without_interventions
+            for variable in district_without_interventions:
+                district_variables_and_their_parents.update(
+                    {v for v in target_domain_graph.directed.predecessors(variable.get_base())}
+                )
+            district_variables_and_their_parents.update(district_without_interventions)
+            logger.debug(
+                "In transport_unconditional_counterfactual_query: got a Q value of "
+                + district_probability_intervening_on_parents.to_latex()
+                + " for district "
+                + str(district_without_interventions)
+                + " corresponding to counterfactual factor "
+                + str(factor)
+                + "."
+            )
+            district_probabilities_intervening_on_parents.append(
+                district_probability_intervening_on_parents
+            )
+    ancestors_excluding_outcomes = {
+        variable.get_base()
+        for variable, value in outcome_ancestors_with_values
+        if (variable, value) not in simplified_event
+    }
+    transported_unconditional_query = Sum.safe(
+        Product.safe(district_probabilities_intervening_on_parents),
+        ancestors_excluding_outcomes,
+    )
+    return UnconditionalCFTResult(
+        expression=transported_unconditional_query, event=simplified_event
+    )
+
+
+# Internal subroutine for transport_conditional_counterfactual_query()
+def _initialize_conditional_transportability_data_structures(
+    *,
+    outcomes: list[tuple[Variable, Intervention]],
+    conditions: list[tuple[Variable, Intervention]],
+) -> tuple[
+    set[Variable],
+    set[Variable],
+    set[Variable],
+    dict[Variable, set[Intervention]],
+    dict[Variable, set[Intervention]],
+    set[Variable],
+    set[Variable],
+]:
+    r"""Set up data structures to process a conditional counterfactual query per Algorithm 3 of [correa22a]_.
+
+    :param outcomes:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and their values as Intervention objects.
+    :param conditions:
+        "X_*, a set of counterfactual variables in V and x_* a set of
+        values for X_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and their values as Intervention objects.
+    :returns: a tuple of hash tables and dictionaries that speed processing of a conditional counterfactual query.
+    """
+    conditioned_variables: set[Variable] = {variable for variable, _ in conditions}
+    outcome_variables: set[Variable] = {outcome for outcome, _ in outcomes}
+    outcome_and_conditioned_variables: set[Variable] = conditioned_variables.union(
+        outcome_variables
+    )
+    outcome_variable_to_value_mappings: defaultdict[Variable, set[Intervention]] = defaultdict(set)
+    # outcome_and_conditioned_variable_to_value_mappings: defaultdict[
+    #    Variable, set[Intervention]
+    # ] = defaultdict(set)
+    outcome_and_conditioned_variable_names_to_values: defaultdict[Variable, set[Intervention]] = (
+        defaultdict(set)
+    )
+    for key, value in outcomes:
+        outcome_variable_to_value_mappings[key].update({value})
+        # outcome_and_conditioned_variable_to_value_mappings[key].update({value})
+        outcome_and_conditioned_variable_names_to_values[key.get_base()].add(value)
+    for key, value in conditions:
+        # outcome_and_conditioned_variable_to_value_mappings[key].update({value})
+        outcome_and_conditioned_variable_names_to_values[key.get_base()].add(value)
+
+    outcome_and_conditioned_variable_names: set[Variable] = {
+        v.get_base() for v in outcome_and_conditioned_variables
+    }
+    conditioned_variable_names: set[Variable] = {v.get_base() for v in conditioned_variables}
+
+    # TODO use named tuple here
+    return (
+        conditioned_variables,
+        outcome_variables,
+        outcome_and_conditioned_variables,
+        dict(outcome_variable_to_value_mappings),
+        dict(outcome_and_conditioned_variable_names_to_values),
+        outcome_and_conditioned_variable_names,
+        conditioned_variable_names,
+    )
+
+
+def _transport_conditional_counterfactual_query_line_2(
+    *,
+    ancestral_components: frozenset[frozenset[Variable]],
+    outcome_variables: set[Variable],
+    outcome_variable_to_value_mappings: dict[Variable, set[Intervention]],
+    target_domain_graph: NxMixedGraph,
+) -> tuple[Event, set[Variable]]:
+    r"""Set up data structures to process a conditional counterfactual query per Algorithm 3 of [correa22a]_.
+
+    This function is an internal subroutine for transport_conditional_counterfactual_query().
+
+    :param ancestral_components:
+        The ancestral components associated with a conditional counterfactual query, encoded as a set of sets of
+        counterfactual variables.
+
+        :math: Let $\mathbf{W_{\ast}}$ be a set of counterfactual variables,
+           $\mathbf{X_{\ast}} \subseteq \mathbf{W_{\ast}}$,
+           and $\mathcal{G}$ be a causal diagram. Then the ancestral components induced by $\mathbf{W_{\ast}}$,
+           given $\mathbf{X_{\ast}}$, are sets $\mathbf{A}_{1\ast},\mathbf{A}_{2\ast},\ldots$ that form a partition
+           over $An(\mathbf{W_{\ast}})$, made of unions of the ancestral sets
+           $An(W_\mathbf{t})_{\mathcal{G}_{\underline{\mathbf{X_{\ast}}(W_\mathbf{t})}}}$,
+           $W_{\mathbf{t}} \in \mathbf{W_{\ast}}$. Sets
+           $An(W_{1\left[\mathbf{t}_{1}\right]})_{\mathcal{G}_{\underline{\mathbf{X_{\ast}}(W_{1\left[\mathbf{t}_{1}\right]})}}}$
+           and
+           $An(W_{2\left[\mathbf{t}_{2}\right]})_{\mathcal{G}_{\underline{\mathbf{X_{\ast}}(W_{2\left[\mathbf{t}_{2}\right]})}}}$
+           are put together if they are not disjoint or there exists a bidirected arrow in $\mathcal{G}$
+           connecting variables in those sets.
+    :param outcome_variables:
+        "Y_*, a set of counterfactual variables in V.", encoded as a set of Variable objects.
+    :param outcome_variable_to_value_mappings:
+        A dictionary mapping Variable objects to sets of Intervention objects representing the values
+        those variables attain in the query.
+    :param target_domain_graph: The associated graph, for the target domain.
+    :returns: The union of the ancestral components containing at least one outcome variable:
+
+        :math: "Let $\mathbf{D_{\ast}}$ be the union of the ancestral components containing a variable in
+           $\mathbf{Y_{\ast}}$ and $\mathbf{d_{\ast}}$ the corresponding set of values" ([correa22a]_, Algorithm 3).
+
+        This function converts the variables to counterfactual factor form in preparation for calling Line 3 of
+        Algorithm 3 of [correa22a]_. It also returns a set of variables representing the target domain graph vertices
+        associated with variables in $\mathbf{D_{\ast}}$.
+    """
+    outcome_ancestral_component_variables_and_values: Event = []
+    outcome_variable_ancestral_component_variables: set[Variable] = set()
+    for component in ancestral_components:
+        if any(variable in outcome_variables for variable in component):
+            outcome_variable_ancestral_component_variables.update(set(component))
+    for variable in outcome_variable_ancestral_component_variables:
+        if variable not in outcome_variable_to_value_mappings:
+            outcome_ancestral_component_variables_and_values.append((variable, None))
+        else:
+            # There could be redundant values for a variable, and Simplify() will catch them
+            for value in outcome_variable_to_value_mappings[variable]:
+                outcome_ancestral_component_variables_and_values.append((variable, value))
+    outcome_ancestral_component_query_in_counterfactual_factor_form: Event = (
+        convert_to_counterfactual_factor_form(
+            event=outcome_ancestral_component_variables_and_values, graph=target_domain_graph
+        )
+    )
+    outcome_variable_ancestral_component_variable_names = {
+        variable.get_base() for variable in outcome_variable_ancestral_component_variables
+    }
+    return (
+        outcome_ancestral_component_query_in_counterfactual_factor_form,
+        outcome_variable_ancestral_component_variable_names,
+    )
+
+
+def _validate_transport_conditional_counterfactual_query_line_4_output(
+    *,
+    simplified_event: Event,
+    outcome_and_conditioned_variable_names: set[Variable],
+    outcome_and_conditioned_variable_names_to_values: dict[Variable, set[Intervention]],
+    outcome_ancestral_component_variables_with_no_values: set[Variable],
+    result_expression: Expression,
+    result_event: list[tuple[Variable, Intervention]],
+    domain_data: list[tuple[Collection[Variable], PopulationProbability]],
+) -> None:
+    r"""Perform validity checks on output for Algorithm 3 of [correa22a]_.
+
+    This function is an internal subroutine for transport_conditional_counterfactual_query(). It performs
+    some validity checks and does not return any value if successful or raises a KeyError or TypeError if not.
+
+    :param simplified_event:
+        This is the set of variables $D_{\ast}$ (in counterfactual factor form) and their values $d_{\ast}$
+        after getting processed by the simplify() algorithm (Algorithm 1 in [correa22a]_, called from Line 1 in
+        Algorithm 2 of [correa22a]_. ) We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param outcome_and_conditioned_variable_names: The graph vertices associated with outcome and conditioned variables
+        in the query (stripped of any interventions), represented as a set of Variable objects.
+    :param outcome_and_conditioned_variable_names_to_values: a dictionary mapping those variables to their values
+        from the query passed in to transport_conditional_counterfactual_query() as a parameter.
+    :param outcome_ancestral_component_variables_with_no_values: the graph vertices associated with elements of
+        the ancestral components of $\mathbf{Y_{\ast}} \cap \mathbf{X_{\ast}}$ given $\mathbf{X_{\ast}}$
+        that are neither outcome variables nor conditioned variables in the query, and therefore do not have
+        values assigned to those variables. Represented as a set of Variable objects.
+    :param result_expression: the probabilistic expression to be returned by this query (if this function gets called,
+        the query is not expected to fail or return a probability of zero due to inconsistent query variable values).
+    :param result_event: a list of tuples of variables and their values used to evaluate the result_expression.
+    :param domain_data: Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+           $K$ tuples, one for each of the $K$ domains except for the target domain.
+           Each tuple contains a set of variables corresponding to
+           $\sigma_{\mathbf{Z}_{k}}$ and an expression denoting the probability distribution
+           $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$.
+           Passed in to transport_conditional_counterfactual_query() as an input parameter.
+    :raises KeyError: a variable in one of the input parameters should appear in another parameter and does not,
+           or should not and does. See each error description for specifics.
+    :raises TypeError: a return value used to evaluate the result_expression is None and that should not happen.
+    """
+    simplified_event_variable_names_to_values: dict[Variable, Intervention | None] = {
+        variable.get_base(): value for variable, value in simplified_event
+    }
+    # 1. Make sure in the simplified event we got back, all of the variables
+    #    that have values are either variables in the outcomes or variables
+    #    in the conditions.
+    if any(
+        name not in outcome_and_conditioned_variable_names
+        for name in simplified_event_variable_names_to_values.keys()
+        if simplified_event_variable_names_to_values[name] is not None
+    ):
+        raise KeyError(
+            "In final checks for transport_conditional_counterfactual_query: a variable "
+            + "that transport_unconditional_counterfactual_query() returned to evaluate "
+            + "the return expression is not one of the input variables (outcomes and "
+            + "conditioned variables."
+        )
+    # 2. Make sure the values for those variables in the simplified event match
+    #    the values for their corresponding outcome or condition variables
+    #    in the input for this function.
+    # logger.debug("In _validate_transport_conditionalcounterfactual_query_line_4_output: ")
+    # logger.debug("    simplified_event: " + str(simplified_event))
+    # logger.debug(
+    #    "    outcome_and_conditioned_variable_names_to_values: "
+    #    + str(outcome_and_conditioned_variable_names_to_values)
+    # )
+    if any(
+        value not in outcome_and_conditioned_variable_names_to_values[variable.get_base()]
+        for variable, value in simplified_event
+        if simplified_event_variable_names_to_values[variable.get_base()] is not None
+    ):
+        raise KeyError(
+            "In final checks for transport_conditional_counterfactual_query: a value "
+            + "of a variable that transport_unconditional_counterfactual_query() returned "
+            + "for the purpose of evaluating the return expression is not a value associated"
+            + " with one of the input variables (outcomes and conditioned variables) "
+            + "that has the same name after ignoring any intervention set."
+        )
+    # 3. Make sure all the variables in the expression this function will return are either
+    #    in the outcomes, the conditions, one of the expressions passed in with the
+    #    domain data, or the outcome ancestral component variables excluding outcomes and conditions.
+    if not all(
+        variable in outcome_ancestral_component_variables_with_no_values
+        or variable in outcome_and_conditioned_variable_names
+        or any(variable in expression.get_variables() for _, expression in domain_data)
+        for variable in result_expression.get_variables()
+    ):
+        raise KeyError(
+            "In final checks for transport_conditional_counterfactual_query: a variable in "
+            + "the expression that transport_unconditional_counterfactual_query() "
+            + "returned is neither one of the input variables (outcomes and conditioned "
+            + "variables) nor a variable in one of the ancestral components containing "
+            + "an outcome variable. This validity check ignored interventions in all variables."
+        )
+    # 4. Make sure we're not going to return any variables with None values. TODO: This check
+    #    won't be necessary after we verify that the input outcomes and conditions have no
+    #    None values.
+    if any(value is None for _, value in result_event):
+        raise TypeError(
+            "In transport_conditional_counterfactual_query: all returned values "
+            + "used to evaluate the query result expression should be actual outcome "
+            + "variable values, but at least one is None. The result_event is "
+            + str(result_event)
+            + ". Also check your inputs."
+        )
+    # 5. Make sure all the variables in the result_event are in the result_expression.
+    if not all(variable in result_expression.get_variables() for variable, _ in result_event):
+        raise KeyError(
+            "In final checks for transport_conditional_counterfactual_query: at least one variable in "
+            + "the event that transport_unconditional_counterfactual_query() "
+            + "will return is not a variable in the expression for the probability of the query "
+            + "that is to be returned. result_event: "
+            + str(result_event)
+            + " and return expression: "
+            + str(result_expression)
+        )
+
+
+def _transport_conditional_counterfactual_query_line_4(
+    *,
+    outcome_variable_ancestral_component_variable_names: set[Variable],
+    outcome_and_conditioned_variable_names: set[Variable],
+    conditioned_variable_names: set[Variable],
+    transported_unconditional_query_expression: Expression,
+    simplified_event: Event,
+    outcome_and_conditioned_variable_names_to_values: dict[Variable, set[Intervention]],
+    outcomes: list[tuple[Variable, Intervention]],
+    conditions: list[tuple[Variable, Intervention]],
+    domain_data: list[tuple[Collection[Variable], PopulationProbability]],
+) -> "ConditionalCFTResult":
+    r"""Execute Line 4 of Algorithm 3 of [correa22a]_.
+
+    This function is an internal subroutine for transport_conditional_counterfactual_query().
+
+    :param outcome_variable_ancestral_component_variable_names: The graph vertices associated with ancestral
+        components containing at least one outcome variable in the query, represented as a set of Variable objects.
+    :param outcome_and_conditioned_variable_names: The graph vertices associated with outcome and conditioned variables
+        in the query (stripped of any interventions), represented as a set of Variable objects.
+    :param conditioned_variable_names: The graph vertices associated with conditioned variables
+        in the query (stripped of any interventions), represented as a set of Variable objects.
+    :param transported_unconditional_query_expression: the probabilistic expression returned by the call to Algorithm
+        2 of [correa22a]_ and represented by $Q$ in the pseudocode for Algorithm 3 of [correa22a]_.
+    :param simplified_event:
+        This is the set of variables $D_{\ast}$ (in counterfactual factor form) and their values $d_{\ast}$
+        after getting processed by the simplify() algorithm (Algorithm 1 in [correa22a]_, called from Line 1 in
+        Algorithm 2 of [correa22a]_. ) We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param outcome_and_conditioned_variable_names_to_values: a dictionary mapping those variables to their values
+        from the query passed in to transport_conditional_counterfactual_query() as a parameter.
+    :param outcomes:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param conditions:
+        "X_*, a set of counterfactual variables in V and x_* a set of
+        values for X_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param domain_data: Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+           $K$ tuples, one for each of the $K$ domains except for the target domain.
+           Each tuple contains a set of variables corresponding to
+           $\sigma_{\mathbf{Z}_{k}}$ and an expression denoting the probability distribution
+           $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$.
+           Passed in to transport_conditional_counterfactual_query() as an input parameter.
+    :returns: a tuple containing an expression representing the query result, and a list of
+           variables and their values used to evaluate the expression. Per Line 4 of Algorithm 4 of
+           [correa22a]_, the return expression has the following form:
+        :math: $\frac{\Sigma_{\mathbf{d_{\ast}}\backslash(\mathbf{y_{\ast}} \cup \mathbf{x_{\ast}})}{Q}}
+                     {\Sigma_{\mathbf{d_{\ast}}\backslash\mathbf{x_{\ast}}}{Q}}$
+    """
+    # Line 4: compute the expression to return
+    # $\mathbf{d_{\ast}} \backslash (\mathbf{y_{\ast}}\cup\mathbf{x_{\ast}})}$
+    outcome_ancestral_component_variables_with_no_values: set[Variable] = (
+        outcome_variable_ancestral_component_variable_names - outcome_and_conditioned_variable_names
+    )
+    # $\mathbf{d_{\ast}} \backslash \mathbf{x_{\ast}}$
+    outcome_ancestral_component_variable_names_excluding_outcomes = (
+        outcome_variable_ancestral_component_variable_names - conditioned_variable_names
+    )
+    result_expression: Expression = Fraction(
+        Sum.safe(
+            transported_unconditional_query_expression,
+            outcome_ancestral_component_variables_with_no_values,
+        ),
+        Sum.safe(
+            transported_unconditional_query_expression,
+            outcome_ancestral_component_variable_names_excluding_outcomes,
+        ),
+    )
+    logger.debug(
+        "In transport_conditional_counterfactual_query: result_expression = "
+        + result_expression.to_latex()
+    )
+    # The input outcome and condition variables and their values, to be used
+    # to evaluate the return expression.
+    result_event: list[tuple[Variable, Intervention]] = [
+        (variable.get_base(), value) for variable, value in itt.chain(outcomes, conditions)
+    ]
+    _validate_transport_conditional_counterfactual_query_line_4_output(
+        simplified_event=simplified_event,
+        outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+        outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+        outcome_ancestral_component_variables_with_no_values=outcome_ancestral_component_variables_with_no_values,
+        result_expression=result_expression,
+        result_event=result_event,
+        domain_data=domain_data,
+    )
+    return ConditionalCFTResult(expression=result_expression, event=result_event)
+
+
+class ConditionalCFTResult(NamedTuple):
+    """Represents the result of a conditional counterfactual transportability query."""
+
+    expression: Expression
+    event: list[tuple[Variable, Intervention]] | None
+
+    def display(self):
+        """Display this result."""
+        from IPython.display import display
+
+        display(event_to_probability(self.event))
+        display(self.expression)
+
+
+def conditional_cft(
+    *,
+    outcomes: Variable | list[Variable],
+    conditions: Variable | list[Variable],
+    target_domain_graph: NxMixedGraph,
+    domains: list[CFTDomain],
+) -> ConditionalCFTResult | None:
+    r"""Run a conditional counterfactual transportability query (Algorithm 3 from [correa22a]_).
+
+    :param outcomes:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param conditions:
+        "X_*, a set of counterfactual variables in V and x_* a set of
+        values for X_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param target_domain_graph: a graph for the target domain.
+    :param domains: A set of $K$ CFTDomain classes, one for each of the $K$ domains. See the
+        documentation for CFTDomain for more details.
+
+    :returns: The result of the query as a ConditionalCFTResult object.
+    """
+    domain_graphs = [
+        (domain.graph, domain.ordering or domain.graph.topological_sort()) for domain in domains
+    ]
+    domain_data = [(domain.policy_variables, domain.population) for domain in domains]
+    return transport_conditional_counterfactual_query(
+        outcomes=_event_from_counterfactuals_strict(outcomes),
+        conditions=_event_from_counterfactuals_strict(conditions),
+        target_domain_graph=target_domain_graph,
+        domain_graphs=domain_graphs,
+        domain_data=domain_data,
+    )
+
+
+def transport_conditional_counterfactual_query(
+    *,
+    outcomes: list[tuple[Variable, Intervention]],
+    conditions: list[tuple[Variable, Intervention]],
+    target_domain_graph: NxMixedGraph,
+    domain_graphs: list[tuple[NxMixedGraph, list[Variable]]],
+    domain_data: list[tuple[Collection[Variable], PopulationProbability]],
+) -> ConditionalCFTResult | None:
+    r"""Implement the ctfTR algorithm from [correa22a]_ (Algorithm 3).
+
+    :param outcomes:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param conditions:
+        "X_*, a set of counterfactual variables in V and x_* a set of
+        values for X_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param domain_graphs: A set of $K$ tuples, one for each of the $K$ domains. Each tuple
+           contains a selection diagram for that domain and a topologically sorted list
+           of all the vertices in the corresponding graph that are not transportability
+           nodes. (Nodes that have no parents come first in such lists.) The selection
+           diagram contains a transportability node for every vertex that is distributed
+           differently in the domain in question than in the target domain (e.g., Vertex
+           Z in Figure 3(a) in [correa22a]_), and it is a causal diagram such that its edges
+           represent the state of the graph after a regime corresponding to domain $k$ has
+           been applied (e.g., policy $\sigma_{X}$ in Figure 4 of [correa22a]_).
+    :param target_domain_graph: a graph for the target domain.
+    :param domain_data: Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+           $K$ tuples, one for each of the $K$ domains except for the target domain.
+           Each tuple contains a set of variables corresponding to
+           $\sigma_{\mathbf{Z}_{k}}$ and an expression denoting the probability distribution
+           $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$.
+    :returns: FAIL (None) if the algorithm fails, or a probabilistic expression for
+           $P^{\ast}(\mathbf{Y_{\ast}}=\mathbf{y_{\ast}} | \mathbf{X_{\ast}}=\mathbf{x_{\ast}}$
+           along a mapping of variables to values used to evaluate that expression.
+           If that expression evaluated to 0 because input values of some counterfactual
+           variables were not consistent, then the algorithm returns Zero (a DSL Expression type)
+           and no mapping.
+    """
+    _validate_transport_conditional_counterfactual_query_input(
+        outcomes=outcomes,
+        conditions=conditions,
+        target_domain_graph=target_domain_graph,
+        domain_graphs=domain_graphs,
+        domain_data=domain_data,
+    )
+
+    # Initialize data structures
+    (
+        conditioned_variables,
+        outcome_variables,
+        outcome_and_conditioned_variables,
+        outcome_variable_to_value_mappings,
+        # outcome_and_conditioned_variable_to_value_mappings,
+        outcome_and_conditioned_variable_names_to_values,
+        outcome_and_conditioned_variable_names,
+        conditioned_variable_names,
+    ) = _initialize_conditional_transportability_data_structures(
+        outcomes=outcomes, conditions=conditions
+    )
+
+    # Line 1: compute $\mathbf{A_{\ast}}$
+    ancestral_components: frozenset[frozenset[Variable]] = get_ancestral_components(
+        conditioned_variables=conditioned_variables,
+        root_variables=outcome_and_conditioned_variables,
+        graph=target_domain_graph,
+    )
+
+    # Line 2: compute $\mathbf{D_{\ast}}$ and $\mathbf{d_{\ast}}$
+    (
+        outcome_ancestral_component_query_in_counterfactual_factor_form,
+        outcome_variable_ancestral_component_variable_names,
+    ) = _transport_conditional_counterfactual_query_line_2(
+        ancestral_components=ancestral_components,
+        outcome_variables=outcome_variables,
+        outcome_variable_to_value_mappings=outcome_variable_to_value_mappings,
+        target_domain_graph=target_domain_graph,
+    )
+
+    # Line 3
+    unconditional_query_result = transport_unconditional_counterfactual_query(
+        event=outcome_ancestral_component_query_in_counterfactual_factor_form,
+        target_domain_graph=target_domain_graph,
+        domain_graphs=domain_graphs,
+        domain_data=domain_data,
+    )
+    if unconditional_query_result is None:
+        # Technically the logic of possibly returning FAIL if Algorithm 2 of [correa22a]_ returns FAIL is
+        # not in the published version of Algorithm 3, but it's implied by Algorithm 2's return values
+        return None
+
+    transported_unconditional_query_expression = unconditional_query_result.expression  # This is Q
+    simplified_event: Event | None = unconditional_query_result.event
+
+    # Event has probability of zero due to inconsistent values in the query
+    if simplified_event is None:
+        return ConditionalCFTResult(
+            expression=transported_unconditional_query_expression, event=simplified_event
+        )
+    else:
+        # Line 4: compute the expression to return
+        return _transport_conditional_counterfactual_query_line_4(
+            outcome_variable_ancestral_component_variable_names=outcome_variable_ancestral_component_variable_names,
+            outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+            conditioned_variable_names=conditioned_variable_names,
+            transported_unconditional_query_expression=transported_unconditional_query_expression,
+            simplified_event=simplified_event,
+            outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+            outcomes=outcomes,
+            conditions=conditions,
+            domain_data=domain_data,
+        )
+
+
+def _validate_transport_conditional_counterfactual_query_input(  # noqa:C901
+    outcomes: list[tuple[Variable, Intervention]],
+    conditions: list[tuple[Variable, Intervention]],
+    target_domain_graph: NxMixedGraph,
+    domain_graphs: list[tuple[NxMixedGraph, list[Variable]]],
+    domain_data: list[tuple[Collection[Variable], PopulationProbability]],
+) -> None:
+    r"""Conduct pre-processing checks to transport conditional counterfacutal queries (Algorithm 3 from [correa22a]_).
+
+    :param outcomes:
+        "Y_*, a set of counterfactual variables in V and y_* a set of
+        values for Y_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param conditions:
+        "X_*, a set of counterfactual variables in V and x_* a set of
+        values for X_*." We encode the counterfactual variables as
+        CounterfactualVariable objects, and the values as Intervention objects.
+    :param domain_graphs: A set of $K$ tuples, one for each of the $K$ domains. Each tuple
+           contains a selection diagram for that domain and a topologically sorted list
+           of all the vertices in the corresponding graph that are not transportability
+           nodes. (Nodes that have no parents come first in such lists.) The selection
+           diagram contains a transportability node for every vertex that is distributed
+           differently in the domain in question than in the target domain (e.g., Vertex
+           Z in Figure 3(a) in [correa22a]_), and it is a causal diagram such that its edges
+           represent the state of the graph after a regime corresponding to domain $k$ has
+           been applied (e.g., policy $\sigma_{X}$ in Figure 4 of [correa22a]_).
+    :param target_domain_graph: a graph for the target domain.
+    :param domain_data: Corresponding to $\mathcal{Z}$ in [correa22a]_, this is a set of
+           $K$ tuples, one for each of the $K$ domains except for the target domain.
+           Each tuple contains a set of variables corresponding to
+           $\sigma_{\mathbf{Z}_{k}}$ and an expression denoting the probability distribution
+           $P^{k}(\mathbf{V};\sigma_{\mathbf{Z}\_{j}})|{\mathbf{Z}_{j}} \in \mathcal{Z}^{i}$.
+    :raises TypeError: a validity check associated with either the input variables
+           or the output event failed. See the error message for specifics.
+    :raises ValueError: an input variable is of valid type but has an invalid value. See
+           the error message for specifics.
+    :raises NotImplementedError: this algorithm does not currently handle input graph probability
+           Expression objects that are One() or Zero(), or cases where the conditioned and
+           outcome variable sets share one or more variables in common.
+    """
+    # Here are all the checks (numbering is just based on convenience during implementation, and
+    #    the numbered order is not necessarily the order of implementation):
+    # 1. Type checking for outcomes and conditions
+    # 2. Type checking for target_domain_graph
+    # 3. Type checking for domain_graphs
+    # 4. Type checking for domain_data
+    #    4.5. Make sure probabilistic expressions in domain_data aren't Zero() or One()
+    # 5. Make sure conditions and outcomes aren't empty
+    # 6. (Skipped for the conditional transportability algorithm, included for unconditional
+    #    transportability) Make sure at least one event element has a non-None value
+    # 7. Check domain_graphs and domain_data aren't empty lists
+    # 8. Check all graphs in domain_graphs have nodes
+    # 9. Check all topologically sorted lists have entries
+    # 9.2. Check that the target domain graph contains no transportability nodes and is a directed acyclic graph
+    # 9.5. Check that the domain_graphs and domain_data list lengths are equal
+    # 9.7. Check that every domain graph is a directed acyclic graph
+    # 10. Check that every topological order list in domain_graphs is a valid topological order,
+    #     given the corresponding graph
+    # 11. Check the domain graph vertices are all the same as the target domain graph vertices,
+    #     net of transportability nodes
+    # 12. Check the event vertices are in the target domain graph (given check #11, that
+    #     means they're in every graph)
+    # 13. Check the conditioned and outcome variables have the same base variable
+    #     as the base variable of their corresponding values
+    # 14. Domain graphs: make sure the vertex set of the topologically sorted vertex order matches
+    #     the set of vertices in each corresponding domain graph
+    # 15. It's possible for a graph probability expression to contain vertices not in the graph
+    #     due to conditioning on vertices outside the graph. But the graph vertices must all be
+    #     represented in that graph probability expression.
+    # 15.5. All the policy vertices must be in the graph, for each domain.
+    # 16. If the target domain graph is also in the domain_graphs list (i.e., data were collected for
+    #     the target domain), then the target domain graph in the domain_graphs list must be
+    #     identical to the target_domain_graph parameter.
+    # 17. Make sure the conditioned variable and outcome variable sets don't share graph vertices
+
+    # Preliminary checks, starting with type checking for inputs unique to Algorithm 3
+    # 1.
+    if not (
+        isinstance(outcomes, list) and all(isinstance(t, tuple) and len(t) == 2 for t in outcomes)
+    ):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: the input outcomes "
+            + "must be a list of tuples of length 2. Check your inputs."
+        )
+    if not all(
+        isinstance(variable, Variable) and isinstance(value, Intervention)
+        for variable, value in outcomes
+    ):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: each tuple in the input outcomes "
+            + "must contain a Variable object and its corresponding value (an Intervention). Check your inputs."
+        )
+    if not (
+        isinstance(conditions, list)
+        and all(isinstance(t, tuple) and len(t) == 2 for t in conditions)
+    ):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: the input conditions "
+            + "must be a list of tuples of length 2. Check your inputs."
+        )
+    if not all(
+        isinstance(variable, Variable) and isinstance(value, Intervention)
+        for variable, value in conditions
+    ):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: each tuple in the input conditions "
+            + "must contain a Variable object and its corresponding value (an Intervention). Check your inputs."
+        )
+    # Type checking for inputs consistent with both Algorithms 2 and 3
+    # 2.
+    if not isinstance(target_domain_graph, NxMixedGraph):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: the target_domain_graph "
+            + "must be an NxMixedGraph object."
+        )
+
+    # Check we have no empty inputs, for inputs unique to Algorithm 3
+    # 5.
+    if len(conditions) == 0:
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: empty list for "
+            + "the conditions. Check your inputs or consider directly calling "
+            + "transport_unconditional_counterfactual_query()."
+        )
+    if len(outcomes) == 0:
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: empty list for "
+            + "the outcomes. Check your inputs."
+        )
+    if len(target_domain_graph.nodes()) == 0:
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: the target "
+            + "domain graph contained no nodes. Check your inputs."
+        )
+
+    # Type checking for inputs consistent with Algorithms 2,3, and 4
+    # 3.
+    if not (isinstance(domain_graphs, list) and all(isinstance(t, tuple) for t in domain_graphs)):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: the "
+            + "domain_graphs input parameter must be a list of tuples."
+        )
+    if not all(
+        isinstance(g, NxMixedGraph)
+        and isinstance(l, list)
+        and all(isinstance(v, Variable) for v in l)
+        for g, l in domain_graphs
+    ):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: the input domain "
+            + "graph tuples must all contain NxMixedGraph objects and lists of variables."
+        )
+    # 4 and 4.5.
+    if not (isinstance(domain_data, list) and all(isinstance(t, tuple) for t in domain_data)):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: the "
+            + "input domain data must be a list of tuples."
+        )
+    if any(e == Zero() or e == One() for _, e in domain_data):
+        raise NotImplementedError(
+            "In _validate_transport_conditional_counterfactual_query_input: this algorithm "
+            + "does not currently handle domain_data probability expressions that are of type "
+            + "One() or Zero()."
+        )
+    if not all(
+        isinstance(sigma_z, Collection)
+        and all(isinstance(v, Variable) for v in sigma_z)
+        and isinstance(pp, PopulationProbability)
+        for sigma_z, pp in domain_data
+    ):
+        raise TypeError(
+            "In _validate_transport_conditional_counterfactual_query_input: the input "
+            + "domain data tuples must all contain Collections of Variable objects "
+            + "(first element) and PopulationProbability expressions (second element)."
+        )
+
+    # 17.
+    # conditioned_variables = {variable.get_base() for variable, _ in conditions}
+    # outcome_variables = {variable.get_base() for variable, _ in outcomes}
+    # if conditioned_variables.intersection(outcome_variables) != set():
+    #    raise NotImplementedError(
+    #        "In _validate_transport_conditional_counterfactual_query_input: currently this "
+    #        + "algorithm does not allow for the conditioned and outcome variables to share common"
+    #        + " vertices. Overlapping graph vertices (i.e., without their interventions): "
+    #        + str(conditioned_variables.intersection(outcome_variables))
+    #    )
+
+    # Check we have no empty inputs (Algorithms 2, 3, and 4)
+    # 7.
+    if len(domain_graphs) == 0 or len(domain_data) == 0:
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: empty list for "
+            + "either domain_graphs or domain_data. Check your inputs."
+        )
+    # 8.
+    if any(len(g.nodes()) == 0 for g, _ in domain_graphs):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: at least one input "
+            + "domain graph contained no nodes. Check your inputs."
+        )
+    # 9.
+    if any(len(topo) == 0 for _, topo in domain_graphs):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: an input set of "
+            + "topologically sorted vertices was empty. Check your inputs."
+        )
+    # 9.5.
+    if len(domain_graphs) != len(domain_data):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: the length of the "
+            + "domain_graphs and domain_data must be the same."
+        )
+
+    # Check the target domain graph contains no transportability nodes and is a directed acyclic graph
+    # 9.2.
+    if frozenset(target_domain_graph.nodes()) != frozenset(
+        _remove_transportability_vertices(vertices=target_domain_graph.nodes())
+    ):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: the target domain graph "
+            + "cannot contain a transportability node. Check your inputs."
+        )
+    if not is_directed_acyclic_graph(target_domain_graph.directed):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: the directed edges in "
+            + "the target domain graph cannot form a cycle. Check your inputs."
+        )
+    # Check the domain graph vertices are all the same as the target domain graph vertices,
+    #    net of transportability nodes
+    # 11.
+    if any(
+        frozenset(target_domain_graph.nodes())
+        != frozenset(_remove_transportability_vertices(vertices=domain_graph.nodes()))
+        for domain_graph, _ in domain_graphs
+    ):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: a domain graph contained"
+            + " different vertices than the target domain graph after excluding transportability "
+            + "nodes. Check your inputs."
+        )
+    # Check the event vertices are in the target domain graph (given the above check, that means they're in every graph)
+    # 12.
+    if any(variable.get_base() not in target_domain_graph.nodes() for variable, _ in conditions):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: one of the input "
+            + "conditioned variables is not in the target domain graph. Check your inputs. "
+        )
+    if any(variable.get_base() not in target_domain_graph.nodes() for variable, _ in outcomes):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: one of the input "
+            + "outcome variables is not in the target domain graph. Check your inputs. "
+        )
+    # 13.
+    if any(variable.get_base() != value.get_base() for variable, value in conditions):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: one of the input "
+            + "conditioned variables does not have the same base variable as its corresponding"
+            + "value (e.g., your variable is (W @ -X) and its value must be +W or -W, but it's"
+            + "-X). Check your inputs."
+        )
+    if any(variable.get_base() != value.get_base() for variable, value in outcomes):
+        raise ValueError(
+            "In _validate_transport_conditional_counterfactual_query_input: one of the input "
+            + "outcome variables does not have the same base variable as its corresponding"
+            + "value (e.g., your variable is (W @ -X) and its value must be +W or -W, but the "
+            + "value is -X). Check your inputs."
+        )
+
+    # Technically the topologically sorted vertices could be for a superset of the vertices
+    # in the input graphs, but we currently require them to be for the vertices in the input graphs.
+    for k in range(len(domain_graphs)):
+        # logger.debug("k = " + str(k))
+        topo_vertices = frozenset(domain_graphs[k][1])
+        expression_vertices = frozenset(domain_data[k][1].get_variables())
+        graph_vertices = frozenset(domain_graphs[k][0].nodes())
+        graph_vertices_without_transportability_nodes = frozenset(
+            _remove_transportability_vertices(vertices=graph_vertices)
+        )
+        policy_vertices = frozenset(domain_data[k][0])
+        # 14.
+        if topo_vertices != graph_vertices:
+            raise ValueError(
+                "In _validate_transport_conditional_counterfactual_query_input: the vertices "
+                + "in each domain graph must match those in the "
+                + "corresponding topologically sorted list of vertices. Check your inputs and "
+                + "note that the topologically sorted vertex lists "
+                + "must contain any transportability nodes. An easy way to "
+                + "generate a usable list is to call "
+                + "[graph_name].topological_sort() on the graph. "
+                + "Graph vertices: "
+                + str(graph_vertices)
+                + ". Topologically sorted list of vertices: "
+                + str(topo_vertices)
+                + ". Domain index (zero-indexed): "
+                + str(k)
+            )
+        # It's possible for a graph probability expression to contain vertices not in the graph
+        # due to conditioning on vertices outside the graph. But the graph vertices must all be
+        # represented in that graph probability expression.
+        # 15.
+        if not all(v in expression_vertices for v in graph_vertices_without_transportability_nodes):
+            raise ValueError(
+                "In _validate_transport_conditional_counterfactual_query_input: some of the "
+                + "vertices in a domain graph do not appear in the expression"
+                + " for the probability of the graph. Check your inputs. Graph vertices: "
+                + str(graph_vertices_without_transportability_nodes)
+                + ". Expression vertices: "
+                + str(expression_vertices)
+                + ". Domain index (zero-indexed): "
+                + str(k)
+            )
+        # 15.5.
+        if not all(v in graph_vertices_without_transportability_nodes for v in policy_vertices):
+            raise ValueError(
+                "In _validate_transport_conditional_counterfactual_query_input: the set of "
+                + "vertices for which a policy has been applied for one "
+                + "of the domains contains at least one vertex not in the domain graph. Check your inputs. "
+                + "Policy vertices: "
+                + str(policy_vertices)
+                + ". Graph vertices: "
+                + str(graph_vertices_without_transportability_nodes)
+                + ". Domain index (zero-indexed): "
+                + str(k)
+            )
+        # 9.7. (The directed acyclic graph check must come before the topological order check)
+        if not is_directed_acyclic_graph(domain_graphs[k][0].directed):
+            raise ValueError(
+                "In _validate_transport_conditional_counterfactual_query_input: the directed edges in "
+                + "domain graph entry "
+                + str(k)
+                + " (zero-indexed) form a cycle and the graph must be a "
+                + "directed acyclic graph. Check your inputs."
+            )
+        # 10.
+        if not _valid_topo_list(topo=domain_graphs[k][1], graph=domain_graphs[k][0]):
+            raise ValueError(
+                "In _validate_transport_conditional_counterfactual_query_input: the provided topologically "
+                + "sorted order of the vertices ("
+                + str(domain_graphs[k][1])
+                + ") for domain graph entry "
+                + str(k)
+                + " (zero-indexed) "
+                + "is not valid, given the input domain_graph. Check your inputs."
+            )
+        # 16. If the target domain graph is also in the domain_graphs list (i.e., data were collected for
+        #     the target domain), then the target domain graph in the domain_graphs list must be
+        #     identical to the target_domain_graph parameter.
+        # TODO: relax the code base to allow Expressions in the domain_data instead of
+        #     PopulationProbability types as follows:
+        # if isinstance(domain_data[k][1], PopulationProbability) and
+        #    str(domain_data[k][1].population)==str(TARGET_DOMAIN):
+        #    That covers a corner case where a user wishes to run this algorithm with a single domain and
+        #    without specifying a population for the graph probability. In that case the problem reduces
+        #    to running ID* anyway, so it's not likely to get much usage.
+        if str(domain_data[k][1].population) == str(TARGET_DOMAIN):
+            if domain_graphs[k][0] != target_domain_graph:
+                raise ValueError(
+                    "In _validate_transport_conditional_counterfactual_query_input: the domain_data contain "
+                    + 'a graph probability expression from the target domain (i.e., "pi*"), but the '
+                    + "corresponding domain_graph is not the same graph as the target_domain_graph. "
+                    + "Check your inputs. Domain index (zero-indexed): "
+                    + str(k)
+                )
+    return
+
+
+def _valid_topo_list(topo: list[Variable], graph: NxMixedGraph) -> bool:
+    r"""Verify that a list of vertices is in topologically sorted order for a given graph.
+
+    :param topo: A candidate list of graph vertices. This function assumes every vertex in topo is somewhere
+        in the graph and every graph vertex is in topo (that is, this information has already been verified).
+    :param graph: The graph in question.
+    :returns: True if the list is in a valid topologically sorted order, False otherwise.
+
+    From Cormen, Leiserson, Rivest, and Stein, "Introduction to Algorithms", second edition, p. 549:
+    a graph is sorted topologically if for every directed edge in the graph from $u$ to $v$,
+    the index of $u$ in the topologically sorted array of vertices is less than the index of $v$.
+    We just have to check this definition holds for each edge.
+    $O(E*V)$ if we use :func:`list.index` to do this. But :func:`list.index` is $O(n)$,
+    so we improve the running time to $O(E+V)$ by creating a hash table first.
+    """
+    node_to_index = {node: index for index, node in enumerate(topo)}
+    return not any(node_to_index[u] > node_to_index[v] for u, v in graph.directed.edges)
diff --git a/src/y0/algorithm/identify/__init__.py b/src/y0/algorithm/identify/__init__.py
index c7ceea9..ac1bb99 100644
--- a/src/y0/algorithm/identify/__init__.py
+++ b/src/y0/algorithm/identify/__init__.py
@@ -8,6 +8,7 @@ Identification
 Algorithm               Implementation
 ======================  ====================================================================
 ID [shpitser2006]_      :mod:`y0.algorithm.identifiy.id_std`
+Tian ID [tian03a]_      :mod:`y0.algorithm.tian_id`
 IDC [shpitser2008]_     :mod:`y0.algorithm.identifiy.idc`
 ID* [shpitser2012]_     :mod:`y0.algorithm.identifiy.id_star`
 IDC* [shpitser2012]_    :mod:`y0.algorithm.identifiy.idc_star`
@@ -22,13 +23,13 @@ identification methods.
 
 Transport
 ---------
-=============================================  ======================================
+=============================================  ============================================
 Algorithm                                      Implementation
-=============================================  ======================================
+=============================================  ============================================
 Surrogate Outcomes [tikka2018]_                :mod:`y0.algorithm.transport`
 Transportability [correa2020]_                 :mod:`y0.algorithm.transport`
-Counterfactual Transportability [correa2022]_  `PR #197 <https://github.com/y0-causal-inference/y0/pull/197>`_
-=============================================  ======================================
+Counterfactual Transportability [correa2022]_  :mod:`y0.algorithm.counterfactual_transport`
+=============================================  ============================================
 
 .. [shpitser2006] `Identification of joint interventional distributions in recursive semi-Markovian
        causal models <https://dl.acm.org/doi/10.5555/1597348.1597382>`_
diff --git a/src/y0/algorithm/tian_id.py b/src/y0/algorithm/tian_id.py
new file mode 100644
index 0000000..8ce8b21
--- /dev/null
+++ b/src/y0/algorithm/tian_id.py
@@ -0,0 +1,495 @@
+"""An implementation of Tian and Pearl's identification algorithm from [tian03a]_.
+
+.. [tikka20a] https://github.com/santikka/causaleffect/blob/master/R/compute.c.factor.R.
+.. [tikka20b] https://github.com/santikka/causaleffect/blob/master/R/identify.R.
+.. [tian03a] https://ftp.cs.ucla.edu/pub/stat_ser/R290-L.pdf
+"""
+
+import logging
+from typing import Collection
+
+from y0.dsl import (
+    Distribution,
+    Expression,
+    Fraction,
+    One,
+    P,
+    PopulationProbability,
+    Probability,
+    Product,
+    Sum,
+    Variable,
+)
+from y0.graph import NxMixedGraph
+
+__all__ = [
+    "identify_district_variables",
+    "compute_c_factor_conditioning_on_topological_predecessors",
+    "compute_q_value_of_variables_with_low_topological_ordering_indices",
+    "compute_c_factor_marginalizing_over_topological_successors",
+    "compute_c_factor",
+    "compute_ancestral_set_q_value",
+]
+
+logger = logging.getLogger(__name__)
+
+
+def identify_district_variables(
+    *,
+    input_variables: frozenset[Variable],
+    input_district: frozenset[Variable],
+    district_probability: Expression,
+    graph: NxMixedGraph,
+    topo: list[Variable],
+) -> Expression | None:
+    """Implement the IDENTIFY algorithm as presented in [tian03a]_ with pseudocode in [correa22a]_ (Algorithm 5).
+
+    Tikka and colleagues implemented this algorithm in the R package Causal Effect ([tikka20b]_). We draw from that
+    implementation. Their version also keeps track of the structure of calls, while this one does not.
+
+    :param input_variables: The set of variables, C, for which we're checking if causal identification is possible.
+    :param input_district: The C-component, T, containing C.
+    :param district_probability:
+        The expression $Q[T]$ as per [tian03a]_, Equation 34. Because T is a single district,
+        $Q[T]$ is the "post-intervention distribution of the variables in T, under an intervention that sets all
+        other variables to constants" (see Equation 36 of [tian03a]_).
+    :param graph: The relevant graph.
+    :param topo: A list of variables in topological order that includes all variables in the graph and may contain more.
+    :returns: An expression for $Q[C]$ in terms of $Q$, or Fail.
+
+    :raises KeyError:
+        at least one input variable is not in the input district or at least one input district variable
+        is not in the topologically sorted list of graph variables.
+    :raises TypeError:
+        the subgraph of the input graph G comprised of the vertices in the input vertex set T should
+        have only district and has more.
+    :raises NotImplementedError:
+        If we get to the end of the conditional, which still needs an "else"
+
+    """
+    if not input_variables.intersection(input_district) == input_variables:
+        # if not all(v in input_district for v in input_variables):
+        raise KeyError(
+            "In identify_district_variables: at least one of the input variables C is not in the input district T."
+        )
+    if not input_district.intersection(set(topo)) == input_district:
+        raise KeyError(
+            "In identify_district_variables: at least one input district variable is not in the "
+            "topologically sorted variable list."
+        )
+    district_subgraph = graph.subgraph(vertices=input_district)  # $G_{T}$
+    if len(district_subgraph.districts()) > 1:
+        raise TypeError(
+            "In identify_district_variables: the subgraph of the input graph G comprised of the"
+            " vertices in the input vertex set T should have only district and has more."
+        )
+    if (not isinstance(district_probability, Expression)) | isinstance(
+        district_probability, Expression
+    ) and not (isinstance(district_probability, (Sum, Product, Fraction, Probability))):
+        raise TypeError(
+            "In identify_district_variables: the district probability must be an expression that is a "
+            "Sum, Product, Fraction, or Probability."
+        )
+
+    # A = Ancestors of C in $G_{T}$
+    ancestral_set = frozenset(district_subgraph.ancestors_inclusive(input_variables))
+
+    # Next, Tikka has an additional line intersecting the ancestral set with the set T in case any C was not in T,
+    # but we raise an error in that case as a pre-processing step, so we omit that line.
+
+    ordered_ancestral_set = [a for a in topo if a in ancestral_set]
+    if ancestral_set == input_variables:
+        logger.debug("In identify_district_variables: A = C. Applying Lemma 3.")
+        logger.debug("   Subgraph_probability = " + district_probability.to_latex())
+        rv = compute_ancestral_set_q_value(
+            ancestral_set=ancestral_set,
+            subgraph_variables=input_district,
+            subgraph_probability=district_probability,
+            graph_topo=topo,
+        )
+        logger.debug("   Returning Q value: " + rv.to_latex())
+    elif ancestral_set == input_district:
+        logger.debug("In identify_district_variables: A = T. Returning None (i.e., FAIL).")
+        logger.debug("   A = " + str(input_district))
+        logger.debug("   T = " + str(ancestral_set))
+        rv = None
+    elif input_variables.issubset(ancestral_set) and ancestral_set.issubset(input_district):
+        ancestral_set_subgraph = graph.subgraph(vertices=ordered_ancestral_set)
+        ancestral_set_subgraph_districts = list(ancestral_set_subgraph.districts())
+        targeted_ancestral_set_subgraph_district = ancestral_set_subgraph_districts[
+            [
+                input_variables.issubset(district) for district in ancestral_set_subgraph_districts
+            ].index(True)
+        ]
+        # t_prime = [district for district in ancestral_set_subgraph_districts if
+        #             input_variables.intersect(district)==input_variables][0]
+        # ordered_t_prime_vertices = [v for v in topo if v in t_prime]
+        # t_one = t_prime.intersection(ancestral_set) # RC: This line is in Tikka
+        if isinstance(district_probability, (Fraction, Product, Sum)):  # Compute Q[A] from Lemma 3
+            ancestral_set_probability = compute_ancestral_set_q_value(
+                ancestral_set=ancestral_set,
+                subgraph_variables=input_district,
+                subgraph_probability=district_probability,  # Q[T]
+                graph_topo=topo,
+            )
+        elif isinstance(district_probability, Probability):
+            logger.debug(
+                "About to get ancestral_set_probability. district_probability = "
+                + district_probability.to_latex()
+            )
+            logger.debug(
+                "   Is the district_probability a PopulationProbability? "
+                + str(isinstance(district_probability, PopulationProbability))
+            )
+            if isinstance(district_probability, PopulationProbability):
+                ancestral_set_probability = PopulationProbability(
+                    population=district_probability.population,
+                    distribution=ordered_ancestral_set[0].joint(ordered_ancestral_set[1:])
+                    | district_probability.parents,
+                )
+            else:
+                ancestral_set_probability = P(
+                    ordered_ancestral_set[0].joint(ordered_ancestral_set[1:])
+                    | district_probability.parents
+                )
+            logger.debug(
+                "Got ancestral_set_probability. Result = " + ancestral_set_probability.to_latex()
+            )
+        else:
+            raise TypeError(
+                "In identify_district_variables: the district probability is an expression of an unknown type."
+            )
+        # Get Q[T'] by Lemma 4 or Lemma 1
+        logger.debug(
+            "In identify_district_variables: about to call _compute_c_factor. Subgraph_probability = "
+            + ancestral_set_probability.to_latex()
+        )
+        targeted_ancestral_set_subgraph_district_probability = compute_c_factor(
+            district=targeted_ancestral_set_subgraph_district,
+            subgraph_variables=ancestral_set,
+            subgraph_probability=ancestral_set_probability,
+            graph_topo=topo,
+        )
+        logger.debug(
+            "In identify_district_variables: about to recursively call identify_district_variables."
+        )
+        logger.debug("    C = " + str(input_variables))
+        logger.debug("    T' = " + str(targeted_ancestral_set_subgraph_district))
+        logger.debug("    Q[T'] =" + str(targeted_ancestral_set_subgraph_district_probability))
+        logger.debug("    graph nodes = " + str(list(graph.nodes())))
+        logger.debug("    topo = " + str(topo))
+        rv = identify_district_variables(
+            input_variables=input_variables,
+            input_district=targeted_ancestral_set_subgraph_district,
+            district_probability=targeted_ancestral_set_subgraph_district_probability,
+            graph=graph,
+            topo=topo,
+        )
+        logger.debug(
+            "In identify_district_variables: returned from recursive call to identify_district_variables."
+        )
+        logger.debug("    Return value = " + str(rv))
+    else:
+        raise NotImplementedError
+    return rv
+
+
+def _do_identify_district_variables_line_1(
+    input_variables: set[Variable],
+    input_district: set[Variable],
+    graph: NxMixedGraph,
+) -> set[Variable] | None:
+    """Implement line 1 of the IDENTIFY algorithm in [tian03a]_ and [correa22a]_ (Algorithm 5)."""
+    raise NotImplementedError("Unimplemented function: _do_identify_district_variables_line_1")
+
+
+def compute_c_factor_conditioning_on_topological_predecessors(
+    *,
+    district: Collection[Variable],
+    graph_probability: Probability,
+    topo: list[Variable],
+) -> Expression:
+    r"""Compute the Q value associated with the C-component (district) in a graph as per [tian03a]_, Equation 37.
+
+    This algorithm uses part (i) of Lemma 1 of [tian03a]_.
+
+    :math: Let a topological order over $V$ be $V_{1} < \ldots < V_{n}$, and let
+        $V^{(i)}=\{V_{1},\ldots,V_{i}\}$, $i = 1,\ldots,n$, and $V^{(0)} = \emptyset$.
+        For any set $C$, let $G_{C}$ denote the subgraph of $G$ composed only of variables in $C$.
+        Then each c-factor $Q_{j}$, $j=1,\ldots,k$, is identifiable and is given by
+
+        \begin{equation}
+        $Q_{j} = \prod_\limit{\{i|V_{i}\in S_{j}\}}{P(v_i}|v^{(i-1}))}$.
+        \end{equation}
+
+    :param district: A list of variables comprising the district for which we're computing a C factor.
+    :param graph_probability: the Q value for the full graph.
+    :param topo: a topological sort of the vertices in the graph.
+    :raises TypeError: the district or variable set from which it is drawn contained no variables.
+    :raises KeyError: a variable in the district is not in the topological sort of the graph vertices.
+    :returns: An expression for Q[district].
+    """
+    # (Topological sort is O(V+E): https://stackoverflow.com/questions/31010922/)
+    variables = set(topo)
+    logger.debug(
+        "In _compute_c_factor_conditioning_on_topological_predecessors: topo = " + str(topo)
+    )
+    logger.debug(
+        "In _compute_c_factor_conditioning_on_topological_predecessors: graph_probability = "
+        + graph_probability.to_latex()
+    )
+
+    if len(district) == 0 or len(variables) == 0:
+        raise TypeError(
+            "Error in _compute_c_factor_conditioning_on_topological_predecessors: the district or variable "
+            + "set from which it is drawn contained no variables."
+        )
+    if any(v not in variables for v in district):
+        raise KeyError(
+            "Error in _compute_c_factor_conditioning_on_topological_predecessors: a variable in the district"
+            + " is not in the topological sort of the graph vertices."
+        )
+    if isinstance(graph_probability, PopulationProbability):
+        population_probabilities = []
+        # A little subtle so it deserves a comment: the Q value passed into Tian's Identify function may
+        # already be conditioned on some variables that are in G but not in the subgraph H. In applying Lemma 1
+        # (but not Lemma 4), we have to make sure we're also conditioning on those variables.
+        graph_probability_parents = set(graph_probability.parents)
+        for variable in district:
+            preceding_variables = topo[: topo.index(variable)]
+            conditioned_variables = graph_probability_parents.union(preceding_variables)  # V^(i-1)
+            pp = PopulationProbability(
+                population=graph_probability.population,
+                distribution=Distribution(
+                    children=(variable,), parents=tuple(conditioned_variables)
+                ),
+            )
+            population_probabilities.append(pp)
+            logger.debug(
+                "In _compute_c_factor_conditioning_on_topological_predecessors: returning "
+                + str(Product.safe(population_probabilities))
+            )
+            logger.debug(
+                "Return value in Latex form is " + Product.safe(population_probabilities).to_latex()
+            )
+        return Product.safe(population_probabilities)
+    else:
+        probabilities = []
+        # A little subtle so it deserves a comment: the Q value passed into Tian's Identify function may
+        # already be conditioned on some variables that are in G but not in the subgraph H. In applying Lemma 1
+        # (but not Lemma 4), we have to make sure we're also conditioning on those variables.
+        graph_probability_parents = set(graph_probability.parents)
+        for variable in district:
+            preceding_variables = topo[: topo.index(variable)]
+            conditioned_variables = graph_probability_parents.union(preceding_variables)  # V^(i-1)
+            probability = P(variable | conditioned_variables)  # v_i
+            probabilities.append(probability)
+        logger.debug(
+            "In _compute_c_factor_conditioning_on_topological_predecessors: returning "
+            + str(Product.safe(probabilities))
+        )
+        logger.debug("Return value in Latex form is " + Product.safe(probabilities).to_latex())
+        return Product.safe(probabilities)
+
+
+def compute_q_value_of_variables_with_low_topological_ordering_indices(
+    *,
+    vertex: Variable | None,
+    graph_probability: Expression,  # Q[H]
+    topo: list[Variable],
+) -> Expression:
+    r"""Compute the Q value of a set of variables according to [tian03a]_, Equation 72.
+
+    :math: Let $H \subseteq V$, and assume that $H$ is partitioned into c-components $H_{1}, \dots, V_{h_{l}}$
+        in the subgraph $G_{H}$. Then we have...
+
+        (ii) Let $k$ be the number of variables in $H$, and let a topological order of
+        the variables in $H$ be $V_{h_{1}} < \cdots < V_{h_{k}}$ be the set of
+        variables in $G_{H}$. Let $H^{(i)} = {V_{h_{1}},\dots,V_{h_{i}}$ be the set of variables in $H$ ordered
+        before $V_{h_{i}}$ (including $V_{h_{i}}$), $i=1,\dots,k$, and $H^{(0)} = \emptyset$. Then each
+        $Q[H_{j}]$, $j = 1,\dots,l$, is computable from $Q[H]$ and is given by
+
+        \begin{equation}
+        $Q[H_{j} = \prod_{\{i|V_{h_{i}}\in H_{j}\}}{\frac{Q[H^{(i)}]}{Q[H^{(i-1)}]},$
+        \end{equation}
+
+        where each $Q[H^{(i)}], i = 0, 1, \dots\, k$, is given by
+
+        \begin{equation}
+        $Q[H^{(i)}] = \sum_{h \backslash h^{(i)}}{Q[H]}$.
+        \end{equation}
+
+    (The second equation above is Equation 72.)
+
+    :param vertex: The $i^{th}$ variable in topological order
+    :param graph_probability: The probability of $H$ corresponding to $Q[H]$ in Equation 72.
+    :param topo: a topological sorting of the subgraph under analysis, $G_{H}$.
+    :raises KeyError: the input vertex is not in the variable set or not in the topological ordering of graph vertices.
+    :returns: An expression for $Q[H^{(i)}]$.
+    """
+    # $Q[H^{(0)}] = Q[\emptyset] = 1$
+    if vertex is None:
+        return One()
+    variables = set(topo)
+    logger.debug(
+        "In _compute_q_value_of_variables_with_low_topological_ordering_indices: input vertex is "
+        + str(vertex)
+    )
+    logger.debug("   and variables are " + str(variables))
+    logger.debug("   and topo is " + str(topo))
+    if vertex not in variables:
+        raise KeyError(
+            "In _compute_q_value_of_variables_with_low_topological_ordering_indices: input vertex "
+            + "%s is not in the input graph.",
+            vertex,
+        )
+
+    ranges = topo[topo.index(vertex) + 1 :]
+    return Sum.safe(graph_probability, ranges)
+
+
+def compute_c_factor_marginalizing_over_topological_successors(
+    *, district: Collection[Variable], graph_probability: Expression, topo: list[Variable]
+) -> Expression:
+    r"""Compute the Q value associated with the C-component (district) in a graph as per [tian03a]_, eqns. 71 and 72.
+
+    This algorithm uses part (ii) of Lemma 4 of [tian03a]_. The context for Equations 71 and 72 follow:
+
+    :math: Let $H \subseteq V$, and assume that $H$ is partitioned into c-components $H_{1}, \dots, V_{h_{l}}$
+        in the subgraph $G_{H}$. Then we have...
+
+        (ii) Let $k$ be the number of variables in $H$, and let a topological order of
+        the variables in $H$ be $V_{h_{1}} < \cdots < V_{h_{k}}$ be the set of
+        variables in $G_{H}$. Let $H^{(i)} = {V_{h_{1}},\dots,V_{h_{i}}$ be the set of variables in $H$ ordered
+        before $V_{h_{i}}$ (including $V_{h_{i}}$), $i=1,\dots,k$, and $H^{(0)} = \emptyset$. Then each
+        $Q[H_{j}]$, $j = 1,\dots,l$, is computable from $Q[H]$ and is given by
+
+        \begin{equation}
+        $Q[H_{j} = \prod_{\{i|V_{h_{i}}\in H_{j}\}}{\frac{Q[H^{(i)}]}{Q[H^{(i-1)}]},$
+        \end{equation}
+
+        where each $Q[H^{(i)}], i = 0, 1, \dots\, k$, is given by
+
+        \begin{equation}
+        $Q[H^{(i)}] = \sum_{h \backslash h^{(i)}}{Q[H]}$.
+        \end{equation}
+
+    :param district: A list of variables comprising the district for which we're computing a C factor.
+    :param graph_probability:
+        The expression $Q$ corresponding to the set of variables in $v$. It is
+        $Q[A]$ on the line calling Lemma 4 in [tian03a]_, Figure 7.
+    :param topo: a topological ordering of the vertices in the subgraph $G_{H}$ in question.
+    :returns: An expression for Q[district].
+    """
+
+    def _get_expression_from_index(index: int) -> Expression:  # Compute $Q[H^{i}]$ given i
+        current_index_expr = compute_q_value_of_variables_with_low_topological_ordering_indices(
+            vertex=topo[index], graph_probability=graph_probability, topo=topo
+        )
+        if index == 0:
+            return current_index_expr
+        previous_index_expr = compute_q_value_of_variables_with_low_topological_ordering_indices(
+            vertex=topo[index - 1], graph_probability=graph_probability, topo=topo
+        )
+        rv = Fraction(current_index_expr, previous_index_expr)
+        return rv
+
+    expressions = []
+    for _, vertex in enumerate(district):
+        logger.debug("In Lemma 4(ii): vertex = " + str(vertex))
+        index = topo.index(vertex)
+        expression = _get_expression_from_index(index)
+        expressions.append(expression)
+        logger.debug("\nIndex = %d, Q[H^(i)] = %s", index, expression)
+
+    rv = Product.safe(expressions)
+    # TODO: We can simplify this product by cancelling terms in the numerator and denominator.
+    logger.debug("Returning product: %s", rv)
+    return rv
+
+
+def compute_c_factor(
+    *,
+    district: Collection[Variable],
+    subgraph_variables: Collection[Variable],
+    subgraph_probability: Expression,
+    graph_topo: list[Variable],
+) -> Expression:
+    """Compute the Q value associated with the C-component (district) in a graph as per [tian03a]_ and [tikka20a]_.
+
+    This algorithm uses both Lemma 1, part (i) of Tian03a (Equation 37) and Lemma 4 of Tian 03a (Equations 71 and 72).
+
+    :param district: A list of variables comprising the district C for which we're computing a C factor.
+    :param subgraph_variables: The variables in the subgraph T under analysis.
+    :param subgraph_probability:
+        The expression Q corresponding to the set of variables in T. As an example, this
+        quantity would be Q[A] on the line calling Lemma 4 in [tian03a]_, Figure 7.
+    :param graph_topo:
+        A list of variables in topological order that includes all variables in G, where T is contained
+        in G.
+    :raises TypeError: In _compute_c_factor: expected the subgraph_probability parameter to be a simple probability.
+    :returns: An expression for Q[district].
+    """
+    # The graph_topo is the ordering of vertices in G, but the lemmas use the topological sorting in a subgraph H of G.
+    # We take in the topological ordering of G to make testing easier, as there could be multiple ways to
+    # sort the vertices in H topologically. It is also faster as topological sort is O(V+E) and getting
+    # subgraph_topo below is O(V).
+    subgraph_topo = [v for v in graph_topo if v in subgraph_variables]
+    logger.debug("In _compute_c_factor: graph_topo = " + str(graph_topo))
+    logger.debug("In _compute_c_factor: subgraph_topo = " + str(subgraph_topo))
+    if isinstance(subgraph_probability, (Fraction, Product, Sum)):
+        logger.debug(
+            "In _compute_c_factor: calling _compute_c_factor_marginalizing_over_topological_successors"
+        )
+        # Lemma 4
+        rv = compute_c_factor_marginalizing_over_topological_successors(
+            district=district, graph_probability=subgraph_probability, topo=subgraph_topo
+        )
+        logger.debug("Returning from _compute_c_factor: " + str(rv))
+        return rv
+    if not isinstance(subgraph_probability, Probability):
+        raise TypeError(
+            "In _compute_c_factor: expected the subgraph_probability "
+            + str(subgraph_probability)
+            + " to be a simple probability."
+        )
+    # Lemma 1
+    return compute_c_factor_conditioning_on_topological_predecessors(
+        district=district, graph_probability=subgraph_probability, topo=subgraph_topo
+    )
+
+
+def compute_ancestral_set_q_value(
+    *,
+    ancestral_set: frozenset[Variable],  # A
+    subgraph_variables: frozenset[Variable],  # T
+    subgraph_probability: Expression,
+    graph_topo: list[Variable],  # topological ordering of variables in a graph containing T
+) -> Expression:
+    r"""Compute the Q value associated with a subgraph as per Equation 69 of [tian03a]_.
+
+    This algorithm uses Lemma 3 of [tian03a]_ (Equation 69).
+
+    :math: Let $W \subseteq C \subseteq V$, and $W' = C \backslash W$. If $W$ is an ancestral set in the subgraph
+        $G_{C}$ $(An(W)_{G_{C}} = W)$, or equivalently, if none of the parents of $W$ is in
+        $W'$ $(Pa(W) \cap W' = \emptyset)$, then
+
+        \begin{equation}
+        \sum\limits_{W'}{Q[C]=Q[W]}
+        \end{equation}
+
+    :param ancestral_set:
+        A set of variables (W in Equation 69, A in Figure 7 of [tian03a]_) that comprise the
+        ancestral set of some other variables (unspecified in Equation 69, and C in Figure 7 of [tian03a]_).
+    :param subgraph_variables: The variables in the subgraph under analysis (C in Equation 69, and T in Figure 7).
+    :param subgraph_probability: The expression Q corresponding to Q[C] in Equation 69 and Q[T] in Figure 7.
+    :param graph_topo:
+        A list of variables in topological order that includes all variables in the graph
+        (i.e., V in Equation 69 and G in Figure 7).
+    :returns: An expression for Q[ancestral_set].
+    """
+    # T\A is W', so Sum_{T\A}{Q[T]} = Q[A], corresponding to Sum_{W'}{Q[C]} = Q[W] in Equation 69
+    # The next two lines are included so the summation shows the marginalization variables in topological order
+    marginalization_set = subgraph_variables - ancestral_set
+    marginalization_variables = [v for v in graph_topo if v in marginalization_set]
+    return Sum.safe(subgraph_probability, marginalization_variables)
diff --git a/src/y0/graph.py b/src/y0/graph.py
index 9ffc29e..3f249ac 100644
--- a/src/y0/graph.py
+++ b/src/y0/graph.py
@@ -28,7 +28,15 @@ import networkx as nx
 from networkx.classes.reportviews import NodeView
 from networkx.utils import open_file
 
-from .dsl import CounterfactualVariable, Intervention, Variable, vmap_adj, vmap_pairs
+from .dsl import (
+    CounterfactualVariable,
+    Intervention,
+    P,
+    Probability,
+    Variable,
+    vmap_adj,
+    vmap_pairs,
+)
 
 if TYPE_CHECKING:
     import ananke.graphs
@@ -144,6 +152,10 @@ class NxMixedGraph:
         """Get the nodes in the graph."""
         return self.directed.nodes()
 
+    def joint_probability(self) -> Probability:
+        """Get the joint probability over all nodes."""
+        return P(self.nodes())
+
     def to_admg(self) -> "ananke.graphs.ADMG":
         """Get an ananke ADMG."""
         self.raise_on_counterfactual()
diff --git a/tox.ini b/tox.ini
index 169762f..b74e7bd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -109,7 +109,7 @@ deps =
     types-click
     types-tabulate
 skip_install = true
-commands = mypy --ignore-missing-imports src/y0/ tests/test_dsl.py
+commands = mypy --ignore-missing-imports src/y0/ tests/test_dsl.py tests/test_algorithm/test_counterfactual_transportability.py
 description = Run the mypy tool to check static typing on the project.
 
 [testenv:doc8]


Test patch:
diff --git a/tests/test_algorithm/cases.py b/tests/test_algorithm/cases.py
index 04202ea..8c4bddc 100644
--- a/tests/test_algorithm/cases.py
+++ b/tests/test_algorithm/cases.py
@@ -3,8 +3,10 @@
 """Test cases."""
 
 import unittest
+from collections import Counter
+from typing import Collection
 
-from y0.dsl import Expression, get_outcomes_and_treatments
+from y0.dsl import Expression, Variable, get_outcomes_and_treatments
 from y0.graph import NxMixedGraph
 from y0.mutate import canonicalize
 
@@ -48,3 +50,11 @@ class GraphTestCase(unittest.TestCase):
             actual_canonical,
             msg=f"\nExpected: {str(expected_canonical)}\nActual:   {str(actual_canonical)}",
         )
+
+    def assert_collection_of_set_equal(
+        self, left: Collection[set[Variable]], right: Collection[set[Variable]]
+    ) -> None:
+        """Check that two collections contain sets with the same elements."""
+        c1 = Counter(frozenset(element) for element in left)
+        c2 = Counter(frozenset(el) for el in right)
+        self.assertEqual(c1, c2)
diff --git a/tests/test_algorithm/test_counterfactual_transportability.py b/tests/test_algorithm/test_counterfactual_transportability.py
new file mode 100644
index 0000000..0b0b619
--- /dev/null
+++ b/tests/test_algorithm/test_counterfactual_transportability.py
@@ -0,0 +1,6055 @@
+# -*- coding: utf-8 -*-
+
+"""Tests for counterfactual transportability.
+
+.. [huang08a] https://link.springer.com/article/10.1007/s10472-008-9101-x.
+.. [correa20a] https://proceedings.neurips.cc/paper/2020/file/7b497aa1b2a83ec63d1777a88676b0c2-Paper.pdf.
+.. [correa22a] https://proceedings.mlr.press/v162/correa22a/correa22a.pdf.
+.. [tikka20a] https://github.com/santikka/causaleffect/blob/master/R/compute.c.factor.R.
+.. [tikka20b] https://github.com/santikka/causaleffect/blob/master/R/identify.R.
+.. [tian03a] https://ftp.cs.ucla.edu/pub/stat_ser/R290-L.pdf.
+"""
+
+import logging
+import unittest
+from collections import defaultdict
+
+from networkx import NetworkXError
+
+from tests.test_algorithm import cases
+from y0.algorithm.counterfactual_transport.ancestor_utils import (
+    _compute_ancestral_components_from_ancestral_sets,
+    _get_ancestral_set_after_intervening_on_conditioned_variables,
+    _get_conditioned_variables_in_ancestral_set,
+    _merge_frozen_sets_linked_by_bidirectional_edges,
+    _merge_frozen_sets_with_common_vertices,
+    _minimize_set,
+    get_ancestors_of_counterfactual,
+    get_ancestral_components,
+    get_base_variables,
+)
+from y0.algorithm.counterfactual_transport.api import (
+    _any_inconsistent_intervention_values,
+    _any_variable_values_inconsistent_with_interventions,
+    _any_variables_with_inconsistent_values,
+    _counterfactual_factor_is_inconsistent,
+    _initialize_conditional_transportability_data_structures,
+    _no_intervention_variables_in_domain,
+    _no_transportability_nodes_in_domain,
+    _reduce_reflexive_counterfactual_variables_to_interventions,
+    _remove_repeated_variables_and_values,
+    _remove_transportability_vertices,
+    _split_event_by_reflexivity,
+    _transport_conditional_counterfactual_query_line_2,
+    _transport_conditional_counterfactual_query_line_4,
+    _transport_unconditional_counterfactual_query_line_2,
+    _valid_topo_list,
+    _validate_transport_conditional_counterfactual_query_input,
+    _validate_transport_conditional_counterfactual_query_line_4_output,
+    _validate_transport_unconditional_counterfactual_query_input,
+    convert_to_counterfactual_factor_form,
+    counterfactual_factors_are_transportable,
+    do_counterfactual_factor_factorization,
+    get_counterfactual_factors,
+    get_counterfactual_factors_retaining_variable_values,
+    is_counterfactual_factor_form,
+    minimize_event,
+    same_district,
+    simplify,
+    transport_conditional_counterfactual_query,
+    transport_district_intervening_on_parents,
+    transport_unconditional_counterfactual_query,
+)
+from y0.algorithm.transport import transport_variable
+from y0.dsl import (
+    PP,
+    TARGET_DOMAIN,
+    W1,
+    W2,
+    W3,
+    W4,
+    W5,
+    X1,
+    X2,
+    CounterfactualVariable,
+    Fraction,
+    Intervention,
+    One,
+    P,
+    Pi1,
+    Pi2,
+    Product,
+    R,
+    Sum,
+    Variable,
+    W,
+    X,
+    Y,
+    Z,
+    Zero,
+)
+from y0.graph import NxMixedGraph
+
+logger = logging.getLogger(__name__)
+
+# [correa22a]_, Figure 1, without the transportability node.
+# (This graph represents the target domain, so there is no
+# transportability node. Figure 1 may include a transportability
+# node because at that point in the paper, the notion of target
+# and source domains had not been introduced.)
+figure_1_graph_no_transportability_nodes = NxMixedGraph.from_edges(
+    directed=[
+        (X, Z),
+        (Z, Y),
+        (X, Y),
+    ],
+    undirected=[(Z, X)],
+)
+figure_1_graph_no_transportability_nodes_topo = list(
+    figure_1_graph_no_transportability_nodes.topological_sort()
+)
+
+# The graph for Domain 1 as described by the text of Example 1.1 and
+# Figure 1 of [correa22a]_. The graph isn't in any figures in the
+# paper, but a reader can infer it.
+figure_1_graph_domain_1_with_interventions = NxMixedGraph.from_edges(
+    directed=[(X, Z), (X, Y), (Z, Y), (transport_variable(Y), Y)],
+    undirected=[],
+)
+figure_1_graph_domain_1_with_interventions_topo = list(
+    figure_1_graph_domain_1_with_interventions.topological_sort()
+)
+
+# From [correa22a]_, Figure 2a.
+figure_2a_graph = NxMixedGraph.from_edges(
+    directed=[
+        (Z, X),
+        (Z, Y),
+        (X, Y),
+        (X, W),
+        (W, Y),
+    ],
+    undirected=[(Z, X), (W, Y)],
+)
+
+# From [correa22a]_, Figures 3a and 4.
+figure_2_graph_domain_1_with_interventions = NxMixedGraph.from_edges(
+    directed=[(X, Y), (X, W), (W, Y), (Z, Y), (transport_variable(Z), Z)],
+    undirected=[(W, Y)],
+)
+figure_2_graph_domain_1_with_interventions_topo = list(
+    figure_2_graph_domain_1_with_interventions.topological_sort()
+)
+
+# From [correa22a]_, Figure 3a.
+figure_2_graph_domain_1 = NxMixedGraph.from_edges(
+    directed=[
+        (Z, X),
+        (Z, Y),
+        (X, Y),
+        (X, W),
+        (W, Y),
+        (transport_variable(Z), Z),
+    ],
+    undirected=[(Z, X), (W, Y)],
+)
+
+# From [correa22a]_, Figure 3b.
+figure_2_graph_domain_2 = NxMixedGraph.from_edges(
+    directed=[
+        (Z, X),
+        (Z, Y),
+        (X, Y),
+        (X, W),
+        (W, Y),
+        (transport_variable(W), W),
+    ],
+    undirected=[(Z, X), (W, Y)],
+)
+figure_2_graph_domain_2_topo = list(figure_2_graph_domain_2.topological_sort())
+
+# From [correa20a]_, Figure 1a.
+soft_interventions_figure_1a_graph = NxMixedGraph.from_edges(
+    directed=[(X1, Z), (X1, X2), (Z, X2), (X1, Y), (X2, Y)],
+    undirected=[(X1, Z), (X2, Y)],
+)
+
+# From [correa20a]_, Figure 1b.
+soft_interventions_figure_1b_graph = NxMixedGraph.from_edges(
+    directed=[
+        (X1, Z),
+        (X1, X2),
+        (X1, Y),
+        (X2, Y),
+        (transport_variable(Y), Y),
+    ],
+)
+
+# From [correa20a]_, Figure 1c.
+soft_interventions_figure_1c_graph = NxMixedGraph.from_edges(
+    directed=[
+        (X1, Z),
+        (X1, X2),
+        (X1, Y),
+        (Z, X2),
+        (X2, Y),
+        (transport_variable(Z), Z),
+    ],
+    undirected=[(X1, Z)],
+)
+
+# From [correa20a]_, Figure 1d.
+soft_interventions_figure_1d_graph = NxMixedGraph.from_edges(
+    directed=[
+        (X1, Z),
+        (X1, X2),
+        (X1, Y),
+        (Z, X2),
+        (X2, Y),
+    ],
+)
+
+# From [correa20a]_, Figure 2a.
+soft_interventions_figure_2a_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, Z),
+        (W, X),
+        (X, Z),
+        (Z, Y),
+    ],
+    undirected=[
+        (R, Y),
+        (W, R),
+        (W, X),
+        (W, Z),
+        (X, Y),
+    ],
+)
+
+# From [correa20a]_, Figure 2b.
+soft_interventions_figure_2b_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, Z),
+        (R, X),
+        (X, Z),
+        (Z, Y),
+    ],
+    undirected=[
+        (R, Y),
+        (W, R),
+        (W, Z),
+    ],
+)
+
+# From [correa20a]_, Figure 2c.
+soft_interventions_figure_2c_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, Z),
+        (X, Z),
+        (W, X),
+        (Z, Y),
+        (transport_variable(R), R),
+        (transport_variable(W), W),
+    ],
+    undirected=[
+        (R, Y),
+        (W, R),
+        (W, X),
+        (W, Z),
+        (X, Y),
+    ],
+)
+
+# From [correa20a]_, Figure 2d.
+soft_interventions_figure_2d_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, Z),
+        (X, Z),
+        (W, X),
+        (Z, Y),
+        (transport_variable(W), W),
+    ],
+    undirected=[
+        (R, Y),
+        (W, R),
+        (W, X),
+        (X, Y),
+    ],
+)
+
+# From [correa20a]_, Figure 2e.
+soft_interventions_figure_2e_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, Z),
+        (X, Z),
+        (W, X),
+        (Z, Y),
+        (transport_variable(R), R),
+    ],
+    undirected=[
+        (R, Y),
+        (X, Y),
+    ],
+)
+
+# From [correa20a]_, Figure 3, and corresponding to pi* with the intervention sigma* not applied.
+soft_interventions_figure_3_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, W),
+        (W, X),
+        (X, Z),
+        (Z, Y),
+        (X, Y),
+    ],
+    undirected=[
+        (R, Z),
+        (W, Y),
+        (W, X),
+        (R, X),
+    ],
+)
+
+tian_pearl_figure_9a_graph = NxMixedGraph.from_edges(
+    directed=[
+        (W1, W2),
+        (W2, X),
+        (W3, W4),
+        (W4, X),
+        (X, Y),
+    ],
+    undirected=[
+        (W1, W3),
+        (W3, W5),
+        (W4, W5),
+        (W2, W3),
+        (W1, X),
+        (W1, Y),
+    ],
+)
+
+
+class TestGetAncestorsOfCounterfactual(unittest.TestCase):
+    """Test getting the ancestors of a counterfactual.
+
+    This test follows [correa22a]_, Definition 2.1 and Example 2.1.
+    """
+
+    # TODO: It seems important to be able to represent Y_{x_0}, not just Y_{X}.
+    # (That is, the value of Y were we to intervene on X and set its value to x0).
+    # That's not yet implemented with Y0's architecture.
+
+    def test_example_2_1_1(self):
+        """Test the first result of Example 2.1.
+
+        Note we test whether we can get Z back, Z is just a Variable so
+        it's a test of the Union in the return value for the tested function.
+        """
+        test1_in = CounterfactualVariable(
+            name="Y", star=None, interventions=frozenset({Intervention(name="X", star=False)})
+        )  # Y @ -X
+        # test1_in = (Y @ -X)
+        test1_out = {
+            CounterfactualVariable(
+                name="Y", star=None, interventions=frozenset({Intervention(name="X", star=False)})
+            ),
+            CounterfactualVariable(
+                name="W", star=None, interventions=frozenset({Intervention(name="X", star=False)})
+            ),
+            Variable(name="Z"),
+        }
+        result = get_ancestors_of_counterfactual(event=test1_in, graph=figure_2a_graph)
+        self.assertTrue(variable in test1_out for variable in result)
+        self.assertTrue(test1_in in result)  # Every outcome is its own ancestor
+
+    def test_example_2_1_2(self):
+        """Test the second result of Example 2.1."""
+        test2_in = CounterfactualVariable(
+            name="W",
+            star=None,
+            interventions=frozenset(
+                {Intervention(name="Y", star=False), Intervention(name="Z", star=False)}
+            ),
+        )  # W @ -Y @ -Z
+        test2_out = {
+            CounterfactualVariable(
+                name="W", star=None, interventions=frozenset({Intervention(name="Z", star=False)})
+            ),
+            CounterfactualVariable(
+                name="X", star=None, interventions=frozenset({Intervention(name="Z", star=False)})
+            ),
+        }
+        result = get_ancestors_of_counterfactual(event=test2_in, graph=figure_2a_graph)
+        self.assertTrue(variable in test2_out for variable in result)
+        self.assertTrue((W @ -Z) in result)  # The outcome, (W @ -Y @ -Z), simplifies to this
+        self.assertFalse(test2_in in result)
+
+    def test_example_2_1_3(self):
+        """Test the third result of Example 2.1."""
+        test3_in = CounterfactualVariable(
+            name="Y", star=None, interventions=frozenset({Intervention(name="W", star=False)})
+        )  # Y @ -W
+        test3_out = {
+            CounterfactualVariable(
+                name="Y", star=None, interventions=frozenset({Intervention(name="W", star=False)})
+            ),
+            Variable(name="X"),
+            Variable(name="Z"),
+        }
+        result = get_ancestors_of_counterfactual(event=test3_in, graph=figure_2a_graph)
+        self.assertTrue(variable in test3_out for variable in result)
+        self.assertTrue(test3_in in result)  # Every outcome is its own ancestor
+
+    def test_4(self):
+        """Test passing in a variable with no interventions.
+
+        The problem reduces to just getting ancestors. Source: out of RJC's head.
+        """
+        test4_in = Variable(name="Z", star=None)
+        test4_out = {Variable(name="Z")}
+        result = get_ancestors_of_counterfactual(event=test4_in, graph=figure_2a_graph)
+        self.assertTrue(variable in test4_out for variable in result)
+        self.assertTrue(test4_in in result)  # Every outcome is its own ancestor
+
+    def test_5(self):
+        """Test passing in a variable intervening on itself.
+
+        The problem reduces to just getting ancestors. Source: out of RJC's head.
+        """
+        test5_in = CounterfactualVariable(
+            name="Y", star=None, interventions=frozenset({Intervention(name="Y", star=False)})
+        )
+        test5_out = {
+            CounterfactualVariable(
+                name="Y", star=None, interventions=frozenset({Intervention(name="Y", star=False)})
+            )
+        }
+        result = get_ancestors_of_counterfactual(event=test5_in, graph=figure_2a_graph)
+        self.assertTrue(variable in test5_out for variable in result)
+        self.assertTrue(test5_in in result)  # Every outcome is its own ancestor
+
+    def test_6(self):
+        """Test the pre-processing check for get_ancestors_of_counterfactual().
+
+        Send in anything that's not a variable.
+        Source: out of RJC's head.
+        """
+        test6_in = [
+            CounterfactualVariable(
+                name="Y", star=None, interventions=frozenset({Intervention(name="Y", star=False)})
+            ),
+            CounterfactualVariable(
+                name="X", star=None, interventions=frozenset({Intervention(name="Y", star=False)})
+            ),
+        ]
+        self.assertRaises(
+            TypeError,
+            get_ancestors_of_counterfactual,
+            event=test6_in,
+            graph=figure_2a_graph,
+        )
+        self.assertRaises(
+            TypeError,
+            get_ancestors_of_counterfactual,
+            event=[],
+            graph=figure_2a_graph,
+        )
+
+    def test_7(self):
+        """Make sure the star values of interventions are properly handled for get_ancestors_of_counterfactual().
+
+        Source: out of RJC's head.
+        """
+        test7_in = W @ +X
+        test7_out = {W @ +X}
+        result = get_ancestors_of_counterfactual(event=test7_in, graph=figure_2a_graph)
+        logger.warning("In test_7: result = " + str(result))
+        self.assertTrue(variable in test7_out for variable in result)
+
+
+class TestSimplify(cases.GraphTestCase):
+    """Test the simplify algorithm from counterfactual transportability.
+
+    We also test the subroutines that only simplify calls:
+    1. _bifurcate_event_by_reflexivity
+    2. _reduce_reflexive_counterfactual_variables_to_interventions
+    3. _remove_repeated_values
+    """
+
+    # TODO: 1. Incorporate a test involving counterfactual unnesting.
+    #       2. Take the line 2 and line 3 tests and run them through the
+    #           simplify() function in addition to the separate functions
+    #           for lines 2 and 3.
+    #       x 3. Test this algorithm and the functions it calls for events
+    #           with values of None assigned to variables.
+    #       x 4. A. Trigger this line in _any_variables_with_inconsistent_values():
+    #             if any(len(value_set) > 1 and None in value_set for value_set in \
+    #                nonreflexive_variable_to_value_mappings.values()):
+    #                raise TypeError("In _any_variables_with_inconsistent values: "+\
+    #                       " a variable lacking interventions on itself "+\
+    #                       "has an assigned value and also a value of None. "+\
+    #                       "That should not occur. Check your inputs.")
+    #           B. Also trigger the equivalent line checking the dictionary containing
+    #            variable-to-value mappings for variables with reflexive interventions.
+    #           C. Also make sure that _any_variables_with_inconsistent_values()
+    #            handles None values for reflexive and nonreflexive interventions
+    #            in the case where there are no conflicts with other intervention values.
+    #       x 5. Test _reduce_reflexive_counterfactual_variables_to_interventions()
+    #            for a case in which a variable, counterfactual or otherwise, has a
+    #            value of None.
+    #       x 6. Test _split_event_by_reflexivity() for cases in which variables have
+    #            values of None.
+    #       x 7. Test that _remove_repeated_variables_and_values() removes None as a
+    #            duplicate value for a variable that also has an actual value.
+    def test_inconsistent_1(self):
+        """Test simplifying an inconsistent event.
+
+        Correa et al. specify the output should be 0 if the counterfactual event
+        is guaranteed to have probability 0. Source: RJC's mind.
+        """
+        event = [(Y @ -X, -Y), (Y @ -X, +Y)]
+        result = simplify(event=event, graph=figure_2a_graph)
+        logger.warning("Result for test_inconsistent_1 is " + str(result))
+        self.assertIsNone(simplify(event=event, graph=figure_2a_graph))
+
+    def test_inconsistent_2(self):
+        """Second test for simplifying an inconsistent event. Source: RJC's mind."""
+        event = [(Y @ -Y, +Y)]
+        self.assertIsNone(simplify(event=event, graph=figure_2a_graph))
+
+    def test_split_event_by_reflexivity(self):
+        """Test splitting event variables that intervene on themselves from those that do not.
+
+        Source: RJC's mind.
+        """
+        # TODO: test queries like (Y @ -Y @ -X, -Y), a low priority because we
+        # call this function after calling minimize_event().
+        reflexive_event_1, nonreflexive_event_1 = _split_event_by_reflexivity(
+            event=[(Y @ -X, -Y), (Y @ -X, +Y), (Y @ -Y, -Y)]
+        )
+        self.assertCountEqual(reflexive_event_1, [(Y @ -Y, -Y)])
+        self.assertCountEqual(nonreflexive_event_1, [(Y @ -X, -Y), (Y @ -X, +Y)])
+        reflexive_event_2, nonreflexive_event_2 = _split_event_by_reflexivity(
+            event=[(Y @ -X, -Y), (Y @ -X, +Y)]
+        )
+        self.assertCountEqual(reflexive_event_2, [])
+        self.assertCountEqual(nonreflexive_event_2, [(Y @ -X, -Y), (Y @ -X, +Y)])
+        reflexive_event_3, nonreflexive_event_3 = _split_event_by_reflexivity(event=[(Y @ -Y, -Y)])
+        self.assertCountEqual(reflexive_event_3, [(Y @ -Y, -Y)])
+        self.assertCountEqual(nonreflexive_event_3, [])
+        reflexive_event_4, nonreflexive_event_4 = _split_event_by_reflexivity(
+            event=[(Y @ -X, -Y), (Y @ -X, None), (Y @ -Y, None)]
+        )
+        self.assertCountEqual(reflexive_event_4, [(Y @ -Y, None)])
+        self.assertCountEqual(nonreflexive_event_4, [(Y @ -X, -Y), (Y @ -X, None)])
+
+    def test_reduce_reflexive_counterfactual_variables_to_interventions_part_1(self):
+        """Test reducing counterfactual variables that intervene on themselves to simpler Intervention objects.
+
+        We test both TypeError tests and normal functioning of the function.
+        Source: RJC's mind.
+        """
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(+Y)
+        reflexive_variable_to_value_mappings[Y].add(-Y)
+        result_dict = _reduce_reflexive_counterfactual_variables_to_interventions(
+            reflexive_variable_to_value_mappings
+        )
+        self.assertIn(Y, result_dict)
+        self.assertSetEqual(result_dict[Y], {+Y, -Y})
+        # The next test sends in a counterfactual variable intervening on itself and something else.
+        # The minimize() algorithm should always get rid of the intervention that is not the
+        # self-intervention, but here we're testing a pre-processing check in this algorithm
+        # that looks for input variables with multiple interventions.
+        reflexive_variable_to_value_mappings_2 = defaultdict(set)
+        reflexive_variable_to_value_mappings_2[Y @ [-Y, -X]].add(+Y)
+        reflexive_variable_to_value_mappings_2[Y].add(-Y)
+        self.assertRaises(
+            ValueError,
+            _reduce_reflexive_counterfactual_variables_to_interventions,
+            variables=reflexive_variable_to_value_mappings_2,
+        )
+        # Here we're testing a check that all input interventions are self-interventions.
+        reflexive_variable_to_value_mappings_3 = defaultdict(set)
+        reflexive_variable_to_value_mappings_3[Y @ -X].add(+Y)
+        reflexive_variable_to_value_mappings_3[Y].add(-Y)
+        self.assertRaises(
+            ValueError,
+            _reduce_reflexive_counterfactual_variables_to_interventions,
+            variables=reflexive_variable_to_value_mappings_3,
+        )
+        # Here we test the case where a variable has a value of None.
+        reflexive_variable_to_value_mappings_4 = defaultdict(set)
+        reflexive_variable_to_value_mappings_4[Y @ -Y].add(+Y)
+        reflexive_variable_to_value_mappings_4[Y].add(None)
+        result_dict_4 = _reduce_reflexive_counterfactual_variables_to_interventions(
+            reflexive_variable_to_value_mappings_4
+        )
+        self.assertIn(Y, result_dict_4)
+        self.assertSetEqual(result_dict_4[Y], {+Y, None})
+        # Here we test the case where a counterfactual variable has a value of None.
+        reflexive_variable_to_value_mappings_5 = defaultdict(set)
+        reflexive_variable_to_value_mappings_5[Y @ -Y].add(None)
+        reflexive_variable_to_value_mappings_5[Y].add(-Y)
+        result_dict_5 = _reduce_reflexive_counterfactual_variables_to_interventions(
+            reflexive_variable_to_value_mappings_5
+        )
+        self.assertIn(Y, result_dict_5)
+        self.assertSetEqual(result_dict_5[Y], {-Y, None})
+
+    def test_remove_repeated_variables_and_values(self):
+        """Test removing repeated occurrences of variables and associated values in an event.
+
+        Source: RJC's mind.
+        """
+        event_1 = [(Y @ -Y, -Y), (Y @ -Y, -Y)]
+        event_2 = [(X, -X), (X, -X)]
+        event_3 = [(X, -X), (X, None)]
+        event_4 = [(Y @ -Y, None), (Y @ -Y, -Y)]
+        result_1 = _remove_repeated_variables_and_values(event=event_1)
+        result_2 = _remove_repeated_variables_and_values(event=event_2)
+        result_3 = _remove_repeated_variables_and_values(event=event_3)
+        result_4 = _remove_repeated_variables_and_values(event=event_4)
+        # @cthoyt: Just curious: is there a one-liner to assert that two dictionaries are equal?
+        self.assertCountEqual([Y @ -Y], result_1.keys())
+        self.assertEqual(len(result_1[Y @ -Y]), 1)
+        self.assertSetEqual(result_1[Y @ -Y], {-Y})
+        self.assertCountEqual([X], result_2.keys())
+        self.assertEqual(len(result_2[X]), 1)
+        self.assertSetEqual(result_2[X], {-X})
+        self.assertCountEqual([X], result_3.keys())
+        self.assertEqual(len(result_3[X]), 1)
+        self.assertSetEqual(result_3[X], {-X})
+        self.assertCountEqual([Y @ -Y], result_4.keys())
+        self.assertEqual(len(result_4[Y @ -Y]), 1)
+        self.assertSetEqual(result_4[Y @ -Y], {-Y})
+
+    def test_line_2_1(self):
+        """Directly test the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls."""
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(-Y)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(-Y)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(+Y)
+
+        logger.warning(
+            "In test_line_2_1: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_1: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertTrue(
+            _any_variables_with_inconsistent_values(
+                nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+                reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+            )
+        )
+
+    def test_line_2_2(self):
+        """Second test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls."""
+        reflexive_variable_to_value_mappings = defaultdict(set)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(-Y)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(-Y)
+        logger.warning(
+            "In test_line_2_2: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_2: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertFalse(
+            _any_variables_with_inconsistent_values(
+                nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+                reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+            )
+        )
+
+    def test_line_2_10(self):
+        """Tenth test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls."""
+        reflexive_variable_to_value_mappings = defaultdict(set)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(None)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(None)
+        logger.warning(
+            "In test_line_2_10: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_10: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertFalse(
+            _any_variables_with_inconsistent_values(
+                nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+                reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+            )
+        )
+
+    def test_line_2_3(self):
+        """Third test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls."""
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(+Y)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        logger.warning(
+            "In test_line_2_3: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_3: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertTrue(
+            _any_variables_with_inconsistent_values(
+                nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+                reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+            )
+        )
+
+    def test_line_2_11(self):
+        """Eleventh test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls.
+
+        This one's a little subtle: Y@-Y with a value of None evaluates to Y with a value of -Y and also None,
+        which should raise an error instead of merely signalling that values are inconsistent and therefore
+        the expression has a probability of Zero.
+        """
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(None)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        logger.warning(
+            "In test_line_2_11: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_11: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertRaises(
+            TypeError,
+            _any_variables_with_inconsistent_values,
+            nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+            reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+        )
+
+    def test_line_2_4(self):
+        """Fourth test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls."""
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(-Y)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+
+        logger.warning(
+            "In test_line_2_4: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_4: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertFalse(
+            _any_variables_with_inconsistent_values(
+                nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+                reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+            )
+        )
+
+    def test_line_2_5(self):
+        """Fifth test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls."""
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ +Y].add(-Y)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        logger.warning(
+            "In test_line_2_5: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_5: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertTrue(
+            _any_variables_with_inconsistent_values(
+                nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+                reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+            )
+        )
+
+    def test_line_2_6(self):
+        """Sixth test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls."""
+        reflexive_variable_to_value_mappings = defaultdict(set)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(-Y)
+        nonreflexive_variable_to_value_mappings[Y @ -Z].add(+Y)
+        logger.warning(
+            "In test_line_2_6: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_6: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertFalse(
+            _any_variables_with_inconsistent_values(
+                nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+                reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+            )
+        )
+
+    def test_line_2_7(self):
+        """Seventh test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls."""
+        event = [(Y @ -Y, -Y), (Y, +Y)]
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(-Y)
+        reflexive_variable_to_value_mappings[Y].add(+Y)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+
+        logger.warning(
+            "In test_line_2_7: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_7: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        # Should be false because by calling _any_variables_with_inconsistent_values
+        # directly, we never reduce Y@-Y to an intervention.
+        self.assertFalse(
+            _any_variables_with_inconsistent_values(
+                nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+                reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+            )
+        )
+        self.assertIsNone(simplify(event=event, graph=figure_2a_graph))
+
+    def test_line_2_8(self):
+        """Eighth test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls.
+
+        We're checking that an error gets thrown when a nonreflexive intervention has a value that is None
+        and also a value that is something else.
+        """
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(-Y)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(-Y)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(None)
+
+        logger.warning(
+            "In test_line_2_8: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_8: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertRaises(
+            TypeError,
+            _any_variables_with_inconsistent_values,
+            nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+            reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+        )
+
+    def test_line_2_9(self):
+        """Ninth test for the internal function _any_variables_with_inconsistent_values() that SIMPLIFY calls.
+
+        We're checking that an error gets thrown when a reflexive intervention has a value that is None
+        and also a value that is something else.
+        """
+        reflexive_variable_to_value_mappings = defaultdict(set)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(-Y)
+        reflexive_variable_to_value_mappings[Y @ -Y].add(None)
+
+        nonreflexive_variable_to_value_mappings = defaultdict(set)
+        nonreflexive_variable_to_value_mappings[Y @ -X].add(-Y)
+
+        logger.warning(
+            "In test_line_2_9: nonreflexive_variable_to_value_mappings = "
+            + str(nonreflexive_variable_to_value_mappings)
+        )
+        logger.warning(
+            "In test_line_2_9: reflexive_variable_to_value_mappings = "
+            + str(reflexive_variable_to_value_mappings)
+        )
+        self.assertRaises(
+            TypeError,
+            _any_variables_with_inconsistent_values,
+            nonreflexive_variable_to_value_mappings=nonreflexive_variable_to_value_mappings,
+            reflexive_variable_to_value_mappings=reflexive_variable_to_value_mappings,
+        )
+
+    def test_redundant_1(self):
+        """First test for simplifying an event with redundant subscripts. Source: RJC's mind."""
+        event = [(Y @ -X, -Y), (Y @ -X, -Y)]
+        result = simplify(event=event, graph=figure_2a_graph)
+        self.assertEqual(result, [(Y @ -X, -Y)])
+
+    def test_redundant_2(self):
+        """Second test for simplifying an event with redundant subscripts. Source: RJC's mind."""
+        event = [(Y @ -X, -Y), (Y @ -X, -Y), (X @ -Z, -X)]
+        # Kind of misleading assertion name, but checks that two lists are the same regardless of order
+        test_result = simplify(event=event, graph=figure_2a_graph)
+        self.assertCountEqual(test_result, [(Y @ -X, -Y), (X @ -Z, -X)])
+        self.assertCountEqual(test_result, [(X @ -Z, -X), (Y @ -X, -Y)])
+
+    def test_redundant_3(self):
+        """Third test for simplifying an event with redundant subscripts.
+
+        (Y @ (-Y,-X), -Y) reduces to (Y @ -Y, -Y) via line 1 of the SIMPLIFY algorithm.
+        And then we want to further simplify (Y @ -Y, -Y) to
+        Source: JZ's mind.
+        """
+        event = [
+            (Y @ (-Y, -X), -Y),
+            (Y @ -Y, -Y),
+            (X @ -Z, -X),
+        ]
+        self.assertCountEqual(simplify(event=event, graph=figure_2a_graph), [(Y, -Y), (X @ -Z, -X)])
+
+    def test_redundant_4(self):
+        """Test that Y@-Y and -Y are treated as redundant and properly minimized to -Y. Source: out of RJC's mind."""
+        event1 = [(Y @ -Y, -Y), (Y, -Y)]
+        event2 = [(Y, -Y), (Y @ -Y, -Y), (Y, -Y)]
+        self.assertEqual(simplify(event=event1, graph=figure_2a_graph), [(Y, -Y)])
+        self.assertEqual(simplify(event=event2, graph=figure_2a_graph), [(Y, -Y)])
+
+    def test_misspecified_input(self):
+        """Make sure users don't pass improper input into SIMPLIFY.
+
+        That includes variables with star values that are not counterfactual variables,
+        as well as events with tuples that are the wrong length or elements that
+        are not pairs of variables and interventions.
+
+        Source: JZ and RJC
+        """
+        event_1 = [
+            (Y @ -Y, -Y),
+            (
+                Variable(
+                    name="Y",
+                    star=False,
+                ),
+                -Y,
+            ),
+        ]
+        event_2 = [
+            (Y @ -Y, -Y),
+            (
+                Variable(
+                    name="Y",
+                    star=True,
+                ),
+                -Y,
+            ),
+        ]
+        event_3 = [
+            (Y @ -Y, -Y, -Y),
+            (
+                Variable(
+                    name="Y",
+                    star=None,
+                ),
+                -Y,
+            ),
+        ]
+        self.assertRaises(TypeError, simplify, event=event_1, graph=figure_2a_graph)
+        self.assertRaises(TypeError, simplify, event=event_2, graph=figure_2a_graph)
+        self.assertRaises(TypeError, simplify, event=event_3, graph=figure_2a_graph)
+        # First variable in the tuple must be a variable, second must be an intervention.
+        # Pass in a list of variables for the first and a list of interventions for the
+        # second test below
+        event_4 = [([Y @ -Y, Y @ -X], -Y)]
+        event_5 = [(Y @ -Y, [-Y, -X])]
+        self.assertRaises(TypeError, simplify, event=event_4, graph=figure_2a_graph)
+        self.assertRaises(TypeError, simplify, event=event_5, graph=figure_2a_graph)
+
+    def test_simplify_1(self):
+        """Test of the full simplify algorithm. Source: RJC's mind."""
+        event_1 = [
+            (Y @ -X, -Y),
+            (Y @ -Z, -Y),
+        ]
+        self.assertCountEqual(
+            simplify(event=event_1, graph=figure_2a_graph), [(Y @ -Z, -Y), (Y @ -X, -Y)]
+        )
+        # Check that simplify() correctly processes None as a value.
+        event_2 = [
+            (Y @ -X, None),
+            (Y @ -Z, -Y),
+        ]
+        self.assertCountEqual(
+            simplify(event=event_2, graph=figure_2a_graph), [(Y @ -Z, -Y), (Y @ -X, None)]
+        )
+        # Check that simplify() gets rid of None as a value when it's redundant with another counterfactual variable.
+        event_3 = [
+            (Y @ -X, -Y),
+            (Y @ -X, None),
+            (Y @ -Z, -Y),
+        ]
+        self.assertCountEqual(
+            simplify(event=event_3, graph=figure_2a_graph), [(Y @ -Z, -Y), (Y @ -X, -Y)]
+        )
+
+    def test_simplify_y(self):
+        """Comprehensive test involving combinations of outcome variables with redundant and conflicting subscripts.
+
+        Source: RJC's mind.
+        """
+        event_1 = [(Y @ -Y, -Y)]
+        event_2 = [(Y @ -Y, +Y)]
+        event_3 = [(Y @ +Y, -Y)]
+        event_4 = [(Y @ +Y, +Y)]
+        event_5 = [(Y @ -Y, -Y), (Y, -Y)]
+        event_6 = [(Y @ -Y, -Y), (Y, +Y)]
+        event_7 = [(Y @ -Y, +Y), (Y, -Y)]
+        event_8 = [(Y @ -Y, +Y), (Y, +Y)]
+        event_9 = [(Y @ +Y, -Y), (Y, -Y)]
+        event_10 = [(Y @ +Y, -Y), (Y, +Y)]
+        event_11 = [(Y @ +Y, +Y), (Y, -Y)]
+        event_12 = [(Y @ +Y, +Y), (Y, +Y)]
+        event_13 = [(Y, -Y)]
+        event_14 = [(Y, +Y)]
+        event_15 = [(Y @ [-W, -Y], -Y), (Y, -Y)]
+        # event_15 = [(Y, -Y),(Y, +Y)]
+        # The reason event_1 can safely simplify to an intervention is that An(Y_y) == An(Y) (Algorithm 2, Line 2).
+        self.assertCountEqual(simplify(event=event_1, graph=figure_2a_graph), [(Y, -Y)])
+        self.assertIsNone(simplify(event=event_2, graph=figure_2a_graph))
+        self.assertIsNone(simplify(event=event_3, graph=figure_2a_graph))
+        self.assertCountEqual(simplify(event=event_4, graph=figure_2a_graph), [(Y, +Y)])
+        self.assertCountEqual(simplify(event=event_5, graph=figure_2a_graph), [(Y, -Y)])
+        self.assertIsNone(simplify(event=event_6, graph=figure_2a_graph))
+        self.assertIsNone(simplify(event=event_7, graph=figure_2a_graph))
+        self.assertIsNone(simplify(event=event_8, graph=figure_2a_graph))
+        self.assertIsNone(simplify(event=event_9, graph=figure_2a_graph))
+        self.assertIsNone(simplify(event=event_10, graph=figure_2a_graph))
+        self.assertIsNone(simplify(event=event_11, graph=figure_2a_graph))
+        self.assertCountEqual(simplify(event=event_12, graph=figure_2a_graph), [(Y, +Y)])
+        self.assertCountEqual(simplify(event=event_13, graph=figure_2a_graph), [(Y, -Y)])
+        self.assertCountEqual(simplify(event=event_14, graph=figure_2a_graph), [(Y, +Y)])
+        self.assertCountEqual(simplify(event=event_15, graph=figure_2a_graph), [(Y, -Y)])
+
+
+class TestIsCounterfactualFactorForm(unittest.TestCase):
+    """Test whether a set of counterfactual variables are all in counterfactual factor form."""
+
+    # TODO: Incorporate a test involving counterfactual unnesting.
+
+    def test_is_counterfactual_factor_form(self):
+        """From Example 3.3 of [correa22a]_."""
+        event1 = {(Y @ (-Z, -W, -X)), (W @ -X)}  # (Y @ -Z @ -W @ -X)
+        self.assertTrue(is_counterfactual_factor_form(event=event1, graph=figure_2a_graph))
+
+        event2 = {(W @ -X), (-Z)}
+        self.assertTrue(is_counterfactual_factor_form(event=event2, graph=figure_2a_graph))
+
+        event3 = {(Y @ (-Z, -W)), (W @ -X)}
+        self.assertFalse(is_counterfactual_factor_form(event=event3, graph=figure_2a_graph))
+
+        # Y has parents, so they should be intervened on but are not
+        event4 = {(Y)}
+        self.assertFalse(is_counterfactual_factor_form(event=event4, graph=figure_2a_graph))
+
+        # Z has no parents, so this variable is also a ctf-factor
+        event5 = {(Z)}
+        self.assertTrue(is_counterfactual_factor_form(event=event5, graph=figure_2a_graph))
+
+        # Z is not a parent of W, so the second counterfactual variable is not a ctf-factor,
+        # because it is not a valid counterfactual variable
+        event6 = {(Y @ (-Z, -W)), (W @ (-X, Z))}
+        self.assertFalse(is_counterfactual_factor_form(event=event6, graph=figure_2a_graph))
+
+        # Check that P(Y_y) is not in counterfactual factor form
+        event7 = {(Y @ (-Z, -W, -X, -Y)), (W @ -X)}  # (Y @ -Z @ -W @ -X)
+        self.assertFalse(is_counterfactual_factor_form(event=event7, graph=figure_2a_graph))
+
+
+class TestMinimizeEvent(cases.GraphTestCase):
+    r"""Test minimizing a set of counterfactual variables.
+
+    Source: last paragraph in Section 4 of [correa22a]_, before Section 4.1.
+    Mathematical expression: ||\mathbf Y_*|| = {||Y_{\mathbf x}|| | Y_{\mathbf x}} \elementof \mathbf Y_*}, and
+    ||Y_{\mathbf x}|| = Y_{\mathbf t}, where \mathbf T = \mathbf X \intersect An(Y)_{G_{\overline{\mathbf X}}}}.
+    (The math syntax is not necessarily cannonical LaTeX.)
+    """
+
+    minimize_graph_1 = NxMixedGraph.from_edges(
+        directed=[
+            (X, W),
+            (W, Y),
+        ],
+        undirected=[(X, Y)],
+    )
+
+    minimize_graph_2 = NxMixedGraph.from_edges(
+        directed=[
+            (Z, X),
+            (X, W),
+            (W, Y),
+        ],
+        undirected=[(X, Y)],
+    )
+
+    def test_minimize_event_1(self):
+        """Test the minimize_event function sending in a single counterfactual variable.
+
+        Source: out of RJC's head.
+        """
+        minimize_event_test1_in = [(Y @ -W @ -X, -Y)]
+        minimize_event_test1_out = [(Y @ -W, -Y)]
+        self.assertCountEqual(
+            minimize_event_test1_out,
+            minimize_event(event=minimize_event_test1_in, graph=self.minimize_graph_1),
+        )
+
+    def test_minimize_event_2(self):
+        """Test the minimize_event function for multiple counterfactual variables.
+
+        Source: out of RJC's head.
+        """
+        minimize_event_test2_in = [(Y @ (-W, -X, -Z), -Y), (W @ (-X, -Z), -W)]
+        minimize_event_test2_out = [(Y @ -W, -Y), (W @ -X, -W)]
+        self.assertCountEqual(
+            minimize_event_test2_out,
+            frozenset(minimize_event(event=minimize_event_test2_in, graph=self.minimize_graph_2)),
+        )
+
+    def test_minimize_event_3(self):
+        """Test the minimize_event function for multiple counterfactual variables and a different graph.
+
+        Source: out of RJC's head.
+        """
+        minimize_event_test3_in = [(Y @ (-W, -X), -Y)]
+        minimize_event_test3_out = [(Y @ (-X, -W), -Y)]
+        # self.assertSetEqual(minimize_test3_out, minimize(variables = minimize_test3_in, graph = figure_2a_graph))
+        # The intentional reverse order of the interventions means we have to use P() to sort it out (no pun intended).
+        self.assertCountEqual(
+            minimize_event_test3_out,
+            minimize_event(event=minimize_event_test3_in, graph=figure_2a_graph),
+        )
+
+    def test_minimize_event_4(self):
+        """Test the minimize_event function sending in a single variable with no interventions.
+
+        Source: out of RJC's head.
+        """
+        minimize_event_test4_in = [(Y, -Y)]
+        minimize_event_test4_out = [(Y, -Y)]
+        self.assertCountEqual(
+            minimize_event_test4_out,
+            minimize_event(event=minimize_event_test4_in, graph=self.minimize_graph_1),
+        )
+
+    def test_minimize_event_5(self):
+        """Test the minimize_event function sending in a single variable with no interventions.
+
+        Source: out of RJC's head.
+        """
+        minimize_event_test5_in = [(Y @ -X, -Y)]
+        minimize_event_test5_out = [(Y @ -X, -Y)]
+        self.assertCountEqual(
+            minimize_event_test5_out,
+            minimize_event(event=minimize_event_test5_in, graph=self.minimize_graph_1),
+        )
+
+    def test_minimize_event_6(self):
+        """Test the minimize_event function sending in a single variable with no interventions.
+
+        Source: out of RJC's head.
+        """
+        minimize_event_test6_in = [(Y @ -X @ -Y, -Y)]
+        minimize_event_test6_out = [(Y @ -Y, -Y)]
+        self.assertCountEqual(
+            minimize_event_test6_out,
+            minimize_event(event=minimize_event_test6_in, graph=self.minimize_graph_1),
+        )
+
+    def test_minimize_event_7(self):
+        """Test the application of the minimize_event function from [correa22a], Example 4.5.
+
+        Source: out of RJC's head.
+        """
+        minimize_event_test7_in = [(Y @ -X, -Y)]
+        minimize_event_test7_out = [(Y @ -X, -Y)]
+        self.assertCountEqual(
+            minimize_event_test7_out,
+            minimize_event(event=minimize_event_test7_in, graph=self.minimize_graph_1),
+        )
+
+    def test_minimize_event_8(self):
+        """Test the minimize_event function for variables with a value of None.
+
+        Source: out of RJC's head.
+        """
+        minimize_event_test8_in = [(Y @ (-W, -X, -Z), None), (W @ (-X, -Z), -W)]
+        minimize_event_test8_out = [(Y @ -W, None), (W @ -X, -W)]
+        self.assertCountEqual(
+            minimize_event_test8_out,
+            frozenset(minimize_event(event=minimize_event_test8_in, graph=self.minimize_graph_2)),
+        )
+
+
+class TestMinimize(cases.GraphTestCase):
+    r"""Test minimizing a set of counterfactual variables.
+
+    Source: last paragraph in Section 4 of [correa22a]_, before Section 4.1.
+    Mathematical expression: ||\mathbf Y_*|| = {||Y_{\mathbf x}|| | Y_{\mathbf x}} \elementof \mathbf Y_*}, and
+    ||Y_{\mathbf x}|| = Y_{\mathbf t}, where \mathbf T = \mathbf X \intersect An(Y)_{G_{\overline{\mathbf X}}}}.
+    (The math syntax is not necessarily cannonical LaTeX.)
+    """
+
+    minimize_graph_1 = NxMixedGraph.from_edges(
+        directed=[
+            (X, W),
+            (W, Y),
+        ],
+        undirected=[(X, Y)],
+    )
+
+    minimize_graph_2 = NxMixedGraph.from_edges(
+        directed=[
+            (Z, X),
+            (X, W),
+            (W, Y),
+        ],
+        undirected=[(X, Y)],
+    )
+
+    def test_minimize_1(self):
+        """Test the minimize function sending in a single counterfactual variable.
+
+        Source: out of RJC's head.
+        """
+        minimize_test1_in = {(Y @ -W @ -X)}
+        minimize_test1_out = {(Y @ -W)}
+        self.assertSetEqual(
+            frozenset(minimize_test1_out),
+            frozenset(_minimize_set(variables=minimize_test1_in, graph=self.minimize_graph_1)),
+        )
+
+    def test_minimize_2(self):
+        """Test the minimize function for multiple counterfactual variables.
+
+        Source: out of RJC's head.
+        """
+        minimize_test2_in = {(Y @ (-W, -X, -Z)), (W @ (-X, -Z))}
+        minimize_test2_out = {(Y @ -W), (W @ -X)}
+        self.assertSetEqual(
+            frozenset(minimize_test2_out),
+            frozenset(_minimize_set(variables=minimize_test2_in, graph=self.minimize_graph_2)),
+        )
+
+    def test_minimize_3(self):
+        """Test the minimize function for multiple counterfactual variables and a different graph.
+
+        Source: out of RJC's head.
+        """
+        minimize_test3_in = {(Y @ (-W, -X))}
+        minimize_test3_out = {(Y @ (-X, -W))}
+        # self.assertSetEqual(minimize_test3_out, minimize(variables = minimize_test3_in, graph = figure_2a_graph))
+        # The intentional reverse order of the interventions means we have to use P() to sort it out (no pun intended).
+        self.assertSetEqual(
+            frozenset(minimize_test3_out),
+            frozenset(_minimize_set(variables=minimize_test3_in, graph=figure_2a_graph)),
+        )
+
+    def test_minimize_4(self):
+        """Test the minimize function sending in a single variable with no interventions.
+
+        Source: out of RJC's head.
+        """
+        minimize_test4_in = {(Y)}
+        minimize_test4_out = {(Y)}
+        self.assertSetEqual(
+            frozenset(minimize_test4_out),
+            frozenset(_minimize_set(variables=minimize_test4_in, graph=self.minimize_graph_1)),
+        )
+
+    def test_minimize_5(self):
+        """Test the minimize function sending in a single variable with no interventions.
+
+        Source: out of RJC's head.
+        """
+        minimize_test5_in = {(Y @ -X)}
+        minimize_test5_out = {(Y @ -X)}
+        self.assertSetEqual(
+            frozenset(minimize_test5_out),
+            frozenset(_minimize_set(variables=minimize_test5_in, graph=self.minimize_graph_1)),
+        )
+
+    def test_minimize_6(self):
+        """Test the minimize function sending in a single variable with no interventions.
+
+        Source: out of RJC's head.
+        """
+        minimize_test6_in = {(Y @ -X @ -Y)}
+        minimize_test6_out = {(Y @ -Y)}
+        self.assertSetEqual(
+            frozenset(minimize_test6_out),
+            frozenset(_minimize_set(variables=minimize_test6_in, graph=self.minimize_graph_1)),
+        )
+
+    def test_minimize_7(self):
+        """Test the application of the minimize function from [correa22a], Example 4.5.
+
+        Source: out of RJC's head.
+        """
+        minimize_test7_in = {(Y @ -X)}
+        minimize_test7_out = {(Y @ -X)}
+        self.assertSetEqual(
+            frozenset(minimize_test7_out),
+            frozenset(_minimize_set(variables=minimize_test7_in, graph=self.minimize_graph_1)),
+        )
+
+
+class TestSameDistrict(unittest.TestCase):
+    """Test whether a set of counterfactual variables are in the same district (c-component)."""
+
+    def test_same_district_1(self):
+        """Test #1 for whether a set of counterfactual variables are in the same district (c-component).
+
+        Source: out of RJC's head.
+        """
+        same_district_test1_in = {(Y @ (-X, -W, -Z)), (W @ (-X))}
+        self.assertTrue(same_district(same_district_test1_in, figure_2a_graph))
+
+    def test_same_district_2(self):
+        """Test #2 for whether a set of counterfactual variables are in the same district (c-component).
+
+        Source: out of RJC's head.
+        """
+        same_district_test2_in = {(Z), (X @ -Z)}
+        self.assertTrue(same_district(same_district_test2_in, figure_2a_graph))
+
+    def test_same_district_3(self):
+        """Test #3 for whether a set of counterfactual variables are in the same district (c-component).
+
+        Source: out of RJC's head.
+        """
+        same_district_test3_in = {(Y @ -Y), (Z), (X @ -Z)}
+        self.assertFalse(same_district(same_district_test3_in, figure_2a_graph))
+
+    def test_same_district_4(self):
+        """Test #4 for whether a set of counterfactual variables are in the same district (c-component).
+
+        Source: out of RJC's head.
+        """
+        same_district_test4_in = {(Y @ -Y)}
+        self.assertTrue(same_district(same_district_test4_in, figure_2a_graph))
+
+    def test_same_district_5(self):
+        """Test #5 for whether a set of counterfactual variables are in the same district (c-component).
+
+        Source: out of RJC's head. This test is meant to raise a key error.
+        """
+        same_district_test5_in = {(R @ -Y)}
+        self.assertRaises(KeyError, same_district, same_district_test5_in, figure_2a_graph)
+
+    def test_same_district_6(self):
+        """Test #6 for whether a set of counterfactual variables are in the same district (c-component).
+
+        Source: out of RJC's head. Edge case where we pass in no variables.
+        """
+        same_district_test6_in = set()
+        self.assertTrue(KeyError, same_district(same_district_test6_in, figure_2a_graph))
+
+
+class TestGetCounterfactualFactors(cases.GraphTestCase):
+    """Test the GetCounterfactualFactors function in counterfactual_transportability.py.
+
+    This is one step in the ctf-factor factorization process. Here we want
+    to check that we can separate a joint probability distribution of ctf-factors
+    into a set of joint probability distributions that we'll later multiply
+    together as per Equation 15 in [correa22a]_.
+    """
+
+    # def assert_collection_of_set_equal(
+    #    self, left: Collection[set[Variable]], right: Collection[set[Variable]]
+    # ) -> None:
+    #    """Check that two collections contain sets with the same elements."""
+    #    c1 = Counter(frozenset(element) for element in left)
+    #    c2 = Counter(frozenset(el) for el in right)
+    #    self.assertEqual(c1, c2)
+
+    def test_get_counterfactual_factors_1(self):
+        """Test factoring a set of counterfactual variables by district (c-component).
+
+        Source: Example 4.2 of [correa22a]_. Note that
+                we're not testing the full example, just computing factors
+                for the query.
+        """
+        get_counterfactual_factors_test_1_in = {
+            (Y @ (-X, -W, -Z)),
+            (W @ -X),
+            (X @ -Z),
+            (-Z),
+        }
+        get_counterfactual_factors_test_1_expected = [
+            {(Y @ (-X, -W, -Z)), (W @ -X)},
+            {(X @ -Z), (-Z)},
+        ]
+        self.assert_collection_of_set_equal(
+            get_counterfactual_factors_test_1_expected,
+            get_counterfactual_factors(
+                event=get_counterfactual_factors_test_1_in, graph=figure_2a_graph
+            ),
+        )
+
+    def test_get_counterfactual_factors_2(self):
+        """Test factoring a set of counterfactual variables by district (c-component).
+
+        Source: RJC. We send in a variable not in the graph. Throws a NetworkXError
+           when we try to get that node's parents.
+        """
+        get_counterfactual_factors_test_2_in = {
+            (Y @ (-X, -W, -Z)),
+            (W @ -X),
+            (X @ -Z),
+            (-Z),
+            (R @ -Y),
+        }
+        self.assertRaises(
+            NetworkXError,
+            get_counterfactual_factors,
+            event=get_counterfactual_factors_test_2_in,
+            graph=figure_2a_graph,
+        )
+
+    def test_get_counterfactual_factors_3(self):
+        """Test factoring a set of counterfactual variables by district (c-component).
+
+        Source: RJC. We send in a variable not in counterfactual factor form.
+        """
+        get_counterfactual_factors_test_3_in = {
+            (Y @ (-X, -W)),
+            (W @ -X),
+            (X @ -Z),
+            (Z @ -Z),
+            (-Z),
+        }
+        self.assertRaises(
+            ValueError,
+            get_counterfactual_factors,
+            event=get_counterfactual_factors_test_3_in,
+            graph=figure_2a_graph,
+        )
+
+
+class TestDoCounterfactualFactorFactorization(cases.GraphTestCase):
+    """Test factorizing the counterfactual factors corresponding to a query, as per Example 4.2 of [correa22a]_.
+
+    This puts together getting the ancestral set of a query, getting the ctf-factors for each element of the set,
+    and factorizing the resulting joint distribution according to the C-components of the graph.
+    """
+
+    # TODO: Add more tests, looking at edge cases: empty set, One(),
+    # maybe check that a graph with distinct ancestral components just treats
+    # everything as having one ancestral set here, and check that every variable
+    # that's in the returned event is also somewhere in the returned expression.
+    def test_do_counterfactual_factor_factorization_1(self):
+        """Test counterfactual factor factorization as per Equation 16 in [correa22a]_.
+
+        Source: Equations 11, 14, and 16 of [correa22a]_.
+        """
+        # First test is already in counterfactual factor form
+        equation_16_test_1_in = [(Y @ (-X, -W, -Z), -Y), (W @ -X, -W), (X @ -Z, -X), (Z, -Z)]
+        equation_16_test_1_expected = (
+            P([(Y @ (-X, -W, -Z)), (W @ -X)]) * P([(X @ -Z), (Z)]),
+            [(Y @ (-X, -W, -Z), -Y), (X @ -Z, -X), (W @ -X, -W), (Z, -Z)],
+        )
+        equation_16_test_1_out = do_counterfactual_factor_factorization(
+            variables=equation_16_test_1_in, graph=figure_2a_graph
+        )
+        self.assert_expr_equal(
+            equation_16_test_1_out[0],
+            equation_16_test_1_expected[0],
+        )
+        self.assertCountEqual(equation_16_test_1_out[1], equation_16_test_1_expected[1])
+
+    def test_do_counterfactual_factor_factorization_2(self):
+        """Test counterfactual factor factorization as per Equation 16 in [correa22a]_.
+
+        Source: Equations 11, 14, and 16 of [correa22a]_.
+        """
+        # Second test is not in counterfactual factor form
+        equation_16_test_2_in = [(Y @ -X, -Y), (W @ -X, -W), (X @ -Z, -X), (Z, -Z)]
+        equation_16_test_2_expected = (
+            P((Y @ (-X, -W, -Z)), (W @ -X)) * P((X @ -Z), (Z)),
+            [(Y @ (-X, -W, -Z), -Y), (X @ -Z, -X), (W @ -X, -W), (Z, -Z)],
+        )
+        equation_16_test_2_out = do_counterfactual_factor_factorization(
+            variables=equation_16_test_2_in, graph=figure_2a_graph
+        )
+        self.assert_expr_equal(
+            equation_16_test_2_out[0],
+            equation_16_test_2_expected[0],
+        )
+        self.assertCountEqual(equation_16_test_2_out[1], equation_16_test_2_expected[1])
+
+    def test_do_counterfactual_factor_factorization_3(self):
+        """Test counterfactual factor factorization as per Equation 16 in [correa22a]_.
+
+        Source: Equations 11, 14, and 16 of [correa22a]_.
+        """
+        # This is the actual equation 16 content in [correa22a]_
+        equation_16_test_3_in = [(Y @ -X, -Y), (X, -X)]
+        equation_16_test_3_expected = (
+            Sum.safe(P((Y @ (-X, -W, -Z)), (W @ -X)) * P((X @ -Z), (Z)), [Z, W]),
+            [(Y @ (-X, -W, -Z), -Y), (X @ -Z, -X)],
+        )
+        equation_16_test_3_out = do_counterfactual_factor_factorization(
+            variables=equation_16_test_3_in, graph=figure_2a_graph
+        )
+        self.assert_expr_equal(
+            equation_16_test_3_out[0],
+            equation_16_test_3_expected[0],
+        )
+        self.assertCountEqual(equation_16_test_3_out[1], equation_16_test_3_expected[1])
+
+    def test_do_counterfactual_factor_factorization_4(self):
+        """Test counterfactual factor factorization with an empty query.
+
+        Source: RJC's head (inspired by a code coverage report).
+        """
+        self.assertRaises(
+            TypeError,
+            do_counterfactual_factor_factorization,
+            variables=[],
+            graph=figure_2a_graph,
+        )
+
+
+class TestConvertToCounterfactualFactorForm(unittest.TestCase):
+    """Test converting a set of variables to counterfactual factor form.
+
+    Source: Example 4.2 of [correa22a]_.
+    """
+
+    def test_convert_to_counterfactual_factor_form_1(self):
+        """Test conversion of a set of variables to counterfactual factor form.
+
+        Source: Equation 12 of [correa22a]_.
+        """
+        test_1_in = [(Y @ -X, -Y), (W, -W)]
+        test_1_expected = [(Y @ (-X, -W, -Z), -Y), (W @ -X, -W)]
+        self.assertCountEqual(
+            convert_to_counterfactual_factor_form(event=test_1_in, graph=figure_2a_graph),
+            test_1_expected,
+        )
+
+    def test_convert_to_counterfactual_factor_form_2(self):
+        """Test conversion of a set of variables to counterfactual factor form.
+
+        Source: Equation 12 of [correa22a]_.
+
+        Here we pass in a simple variable with no parents, and should get it back.
+        """
+        test_2_in = [(Z, -Z)]
+        test_2_expected = [(Z, -Z)]
+        self.assertCountEqual(
+            convert_to_counterfactual_factor_form(event=test_2_in, graph=figure_2a_graph),
+            test_2_expected,
+        )
+
+    def test_convert_to_counterfactual_factor_form_3(self):
+        """Test conversion of an outcome intervened on itself to counterfactual factor form.
+
+        Source: out of RJC's head.
+        """
+        test_3_1_in = [(Y, -Y)]
+        test_3_2_in = [(Y @ -Y, -Y)]
+        test_3_3_in = [(Y @ -Y, -Y), (Y, -Y)]
+        test_3_expected = [(Y @ (-X, -W, -Z), -Y)]
+        self.assertCountEqual(
+            convert_to_counterfactual_factor_form(event=test_3_1_in, graph=figure_2a_graph),
+            test_3_expected,
+        )
+        self.assertCountEqual(
+            convert_to_counterfactual_factor_form(
+                event=simplify(event=test_3_2_in, graph=figure_2a_graph), graph=figure_2a_graph
+            ),
+            test_3_expected,
+        )
+        self.assertCountEqual(
+            convert_to_counterfactual_factor_form(
+                event=simplify(event=test_3_3_in, graph=figure_2a_graph), graph=figure_2a_graph
+            ),
+            test_3_expected,
+        )
+
+    def test_convert_to_counterfactual_factor_form_4(self):
+        """Convert a variable with no value to counterfactual factor form.
+
+        Source: Equation 12 of [correa22a]_.
+
+        Here we pass in a simple variable with no parents, and should get it back.
+        """
+        test_4_in = [(Z, None)]
+        test_4_expected = [(Z, None)]
+        self.assertCountEqual(
+            convert_to_counterfactual_factor_form(event=test_4_in, graph=figure_2a_graph),
+            test_4_expected,
+        )
+
+    def test_convert_to_counterfactual_factor_form_5(self):
+        """Convert a variable intervening on an ancestor that is not a parent to counterfactual factor form.
+
+        Source: RJC's mind.
+
+        Here we should expect the ancestor X2 to not be an intervention for Z in the output.
+        """
+        graph = NxMixedGraph.from_edges(
+            directed=[
+                (X1, W),
+                (X1, Y),
+                (X2, W),
+                (W, Y),
+                (W, Z),
+            ],
+        )
+
+        test_5_in = [(Y @ -X1, -Y), (Z @ -X2, -Z)]
+        test_5_expected = [(Y @ [-X1, -W], -Y), (Z @ -W, -Z)]
+        self.assertCountEqual(
+            convert_to_counterfactual_factor_form(event=test_5_in, graph=graph),
+            test_5_expected,
+        )
+
+
+class TestCounterfactualFactorTransportability(unittest.TestCase):
+    """Test whether a counterfactual factor can be transported from a domain to the target domain.
+
+    Source: Lemma 3.1 and Example 3.4 of [correa22a]_.
+    """
+
+    def test_counterfactual_factor_transportability_1(self):
+        """Test equation 9 of [correa22a]_."""
+        test_1_in = {(-Y @ -X @ -Z @ -W), (-W @ -X)}
+        self.assertTrue(
+            counterfactual_factors_are_transportable(
+                factors=test_1_in, domain_graph=figure_2_graph_domain_1
+            )
+        )
+        self.assertFalse(
+            counterfactual_factors_are_transportable(
+                factors=test_1_in, domain_graph=figure_2_graph_domain_2
+            )
+        )
+
+    def test_counterfactual_factor_transportability_2(self):
+        """Test equation 10 of [correa22a]_."""
+        test_2_in = {(-Y @ -X @ -Z @ -W)}
+        self.assertTrue(
+            counterfactual_factors_are_transportable(
+                factors=test_2_in, domain_graph=figure_2_graph_domain_1
+            )
+        )
+        self.assertTrue(
+            counterfactual_factors_are_transportable(
+                factors=test_2_in, domain_graph=figure_2_graph_domain_2
+            )
+        )
+
+    def test_counterfactual_factor_transportability_3(self):
+        """Test Example 3.4 of [correa22a]_."""
+        test_3_in = {(-W @ -X), (-Z)}
+        self.assertFalse(
+            counterfactual_factors_are_transportable(
+                factors=test_3_in, domain_graph=figure_2_graph_domain_1
+            )
+        )
+        self.assertFalse(
+            counterfactual_factors_are_transportable(
+                factors=test_3_in, domain_graph=figure_2_graph_domain_2
+            )
+        )
+
+    def test_counterfactual_factor_transportability_4(self):
+        """Another test related to Example 3.4 of [correa22a]_."""
+        test_4_in = {(-X @ -Z), (-Z)}
+        self.assertFalse(
+            counterfactual_factors_are_transportable(
+                factors=test_4_in, domain_graph=figure_2_graph_domain_1
+            )
+        )
+        self.assertTrue(
+            counterfactual_factors_are_transportable(
+                factors=test_4_in, domain_graph=figure_2_graph_domain_2
+            )
+        )
+
+    def test_counterfactual_factor_transportability_5(self):
+        """Another test related to Example 3.4 of [correa22a]_.
+
+        Checking that a child of a transported node in the same c-component is still tranportable.
+        """
+        test_5_in = {(-X @ -Z)}
+        self.assertTrue(
+            counterfactual_factors_are_transportable(
+                factors=test_5_in, domain_graph=figure_2_graph_domain_1
+            )
+        )
+        self.assertTrue(
+            counterfactual_factors_are_transportable(
+                factors=test_5_in, domain_graph=figure_2_graph_domain_2
+            )
+        )
+
+
+class TestRemoveTransportabilityVertices(cases.GraphTestCase):
+    """Test the _remove_transportability_vertices utility function."""
+
+    def test_remove_transportability_vertices(self):
+        """Simple test that we're properly removing transportability nodes from a set.
+
+        Source: RJC.
+        """
+        test_graph_1 = NxMixedGraph.from_edges(
+            directed=[(X, Y), (X, W), (W, Y), (Z, Y), (transport_variable(Z), Z)],
+            undirected=[
+                (W, Y),
+            ],
+        )
+        input_vertices = set(test_graph_1.topological_sort())
+        expected_result = frozenset({W, X, Y, Z})
+        self.assertSetEqual(
+            expected_result, _remove_transportability_vertices(vertices=input_vertices)
+        )
+        test_graph_2 = NxMixedGraph.from_edges(
+            directed=[(X, Y), (X, W), (W, Y), (Z, Y)],
+            undirected=[
+                (W, Y),
+            ],
+        )
+        input_vertices_2 = set(test_graph_2.topological_sort())
+        expected_result_2 = frozenset({W, X, Y, Z})
+        self.assertSetEqual(
+            expected_result_2, _remove_transportability_vertices(vertices=input_vertices_2)
+        )
+
+
+class TestTransportDistrictInterveningOnParents(cases.GraphTestCase):
+    """Test Correa and Bareinboim's algorithm to transport a district (Algorithm 4 from [correa22a]_)."""
+
+    def test_transport_district_intervening_on_parents_1(self):
+        """First test case involving a transportable counterfactual factor.
+
+        Source: Equation 17 of [correa22a]_.
+        """
+        district = {Y, W}
+        domain_graphs = [
+            (
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_1_with_interventions_topo,
+            ),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        expected_result = PP[Pi1](Y | W, X, Z) * PP[Pi1](W | X)
+        result = transport_district_intervening_on_parents(
+            district=district, domain_graphs=domain_graphs, domain_data=domain_data
+        )
+        self.assert_expr_equal(expected_result, result)
+
+    def test_transport_district_intervening_on_parents_2(self):
+        """Second test case involving a transportable counterfactual factor.
+
+        Source: Equation 19 of [correa22a]_.
+        """
+        district = {X, Z}
+        domain_graphs = [
+            (
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_1_with_interventions_topo,
+            ),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        expected_result = PP[Pi2](X | Z) * PP[Pi2](Z)
+        logger.warning(
+            "In test_transport_district_intervening_on_parents_2: expected_result is "
+            + expected_result.to_latex()
+        )
+        result = transport_district_intervening_on_parents(
+            district=district, domain_graphs=domain_graphs, domain_data=domain_data
+        )
+        self.assert_expr_equal(expected_result, expected_result)
+        self.assert_expr_equal(expected_result, PP[Pi2](X | Z) * PP[Pi2](Z))
+        self.assert_expr_equal(expected_result, result)
+
+    def test_transport_district_intervening_on_parents_3(self):
+        """Test case in which a transportability node interferes with transportability for every domain.
+
+        Source: RJC's mind.
+        """
+        # This is figure_2_graph_domain_1, changing the transportability node from T(Z) to T(Y)
+        graph_1 = NxMixedGraph.from_edges(
+            directed=[(X, Y), (X, W), (W, Y), (Z, Y), (transport_variable(Y), Y)],
+            undirected=[
+                (W, Y),
+            ],
+        )
+        graph_1_topo = list(graph_1.topological_sort())
+        district = {W, Y}
+        domain_graphs = [
+            (graph_1, graph_1_topo),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        self.assertIsNone(
+            transport_district_intervening_on_parents(
+                district=district, domain_graphs=domain_graphs, domain_data=domain_data
+            )
+        )
+
+    def test_transport_district_intervening_on_parents_4(self):
+        """Test case in which nothing's transportable because there's an interfering intervention for every domain.
+
+        Source: RJC's mind.
+        """
+        district = {W, Y}
+        graph_1 = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (X, Y),
+                (Z, Y),
+                (W, Y),
+            ],
+            undirected=[
+                (Z, X),
+            ],
+        )
+        graph_1_topo = list(graph_1.topological_sort())
+        graph_2 = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (X, W),
+                (Z, Y),
+                (W, Y),
+            ],
+            undirected=[
+                (W, Y),
+            ],
+        )
+        graph_2_topo = list(graph_2.topological_sort())
+        domain_data = [({W}, PP[Pi1](W, X, Y, Z)), ({Y}, PP[Pi2](W, X, Y, Z))]
+        self.assertIsNone(
+            transport_district_intervening_on_parents(
+                district=district,
+                domain_graphs=[(graph_1, graph_1_topo), (graph_2, graph_2_topo)],
+                domain_data=domain_data,
+            )
+        )
+
+    def test_transport_district_intervening_on_parents_5(self):
+        """Test case in which a transportability node blocks one domain and an intervention blocks the other.
+
+        Source: RJC's mind.
+        """
+        district = {W, Y}
+        graph_1 = NxMixedGraph.from_edges(
+            directed=[(Z, X), (X, Y), (Z, Y), (W, Y), (transport_variable(Y), Y)],
+            undirected=[
+                (Z, X),
+            ],
+        )
+        graph_1_topo = list(graph_1.topological_sort())
+        graph_2 = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (X, W),
+                (Z, Y),
+                (W, Y),
+            ],
+            undirected=[
+                (W, Y),
+            ],
+        )
+        graph_2_topo = list(graph_2.topological_sort())
+        domain_data = [(set(), PP[Pi1](W, X, Y, Z)), ({Y}, PP[Pi2](W, X, Y, Z))]
+        self.assertIsNone(
+            transport_district_intervening_on_parents(
+                district=district,
+                domain_graphs=[(graph_1, graph_1_topo), (graph_2, graph_2_topo)],
+                domain_data=domain_data,
+            )
+        )
+
+    def test_transport_district_intervening_on_parents_6(self):
+        """Sixth test case involving a transportable counterfactual factor.
+
+        Here we make the input district contain variables in more than one district.
+
+        Source: Equation 17 of [correa22a]_.
+        """
+        district = {Y, W, X}  # X is from another district
+        domain_graphs = [
+            (
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_1_with_interventions_topo,
+            ),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [(set(), PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        self.assertRaises(
+            ValueError,
+            transport_district_intervening_on_parents,
+            district=district,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+
+    def test_transport_district_intervening_on_parents_7(self):
+        """Seventh test case involving a transportable counterfactual factor.
+
+        We need a test case in which a call to tian_pearl_identify() returns None.
+
+        Source: RC's mind.
+        """
+        district = {Y, Z}
+        # For reference: an example (unused) target graph such that Y and Z are one of the C-components
+        # target_graph = NxMixedGraph.from_edges(
+        #    directed=[(X,Y)],
+        #    undirected=[
+        #        (Y, Z),
+        #    ],
+        # )
+        domain_graphs = [
+            (
+                NxMixedGraph.from_edges(
+                    directed=[(X, Y)],
+                    undirected=[
+                        (Y, Z),
+                        (X, Y),
+                    ],
+                ),
+                [X, Y, Z],
+            ),
+            (
+                NxMixedGraph.from_edges(
+                    directed=[(X, Y)],
+                    undirected=[
+                        (Y, Z),
+                        (X, Y),
+                    ],
+                ),
+                [X, Y, Z],
+            ),
+        ]
+        domain_data = [(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({Y}, PP[Pi1](X, Y, Z))]
+        self.assertIsNone(
+            transport_district_intervening_on_parents(
+                district=district, domain_graphs=domain_graphs, domain_data=domain_data
+            )
+        )
+
+    def test_transport_district_intervening_on_parents_preprocessing(self):
+        """Tests of the various data integrity checks at the start of sigma-tr.
+
+        Source: RJC.
+        """
+        domain_graphs = [
+            (
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_1_with_interventions_topo,
+            ),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        # Wrong data type for district (not a collection of variable objects)
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district=X,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district=[1, 2, 3],
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        # Wrong data type for domain_graphs
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=X,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=None,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_2,
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    None,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_2,
+                    {X, Y},
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_2,
+                    [1, 2, 3],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_2,
+                    [X, Y, 1],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # Wrong data type for domain_data. TODO: consider cases where a probability is One() or Zero()
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, R},
+            domain_graphs=domain_graphs,
+            domain_data=None,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, R},
+            domain_graphs=domain_graphs,
+            domain_data=({X}, set()),
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, R},
+            domain_graphs=domain_graphs,
+            domain_data=[(X, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, R},
+            domain_graphs=domain_graphs,
+            domain_data=[({1}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, R},
+            domain_graphs=domain_graphs,
+            domain_data=[({X}, None), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        # Empty district
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={},
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        # Empty domain graphs
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[],
+            domain_data=domain_data,
+        )
+        # Graph with empty nodes
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    NxMixedGraph(),
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # Empty domain data
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=domain_graphs,
+            domain_data=[],
+        )
+        # Empty topo list
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    [],
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # Length of the domain_graphs is not the same as the length of the domain_data
+        self.assertRaises(
+            TypeError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # topo vertices != graph vertices
+        self.assertRaises(
+            KeyError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[(X, Y), (X, W), (W, Y), (Z, Y), (transport_variable(Z), Z)],
+                        undirected=[
+                            (W, Y),
+                        ],
+                    ),
+                    [X, Y, Z, R],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # expression vertices not contained in graph vertices
+        self.assertRaises(
+            KeyError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=[(set(), PP[Pi1](W, X, Y)), ({Y}, PP[Pi2](W, X, Y, Z))],
+        )
+        # Policy variable not in the graph vertices
+        self.assertRaises(
+            KeyError,
+            transport_district_intervening_on_parents,
+            district={W, Y},
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=[({R}, PP[Pi1](W, X, Y, Z)), ({Y}, PP[Pi2](W, X, Y, Z))],
+        )
+        # District variable not in a graph
+        self.assertRaises(
+            KeyError,
+            transport_district_intervening_on_parents,
+            district={X, R},
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+
+    def test_transport_district_intervening_on_parents_line_1(self):
+        """Tests of the checks in Line 1 of sigma-TR (Algorithm 4 of [correa22a]_).
+
+        Source: RJC.
+        """
+        self.assertTrue(_no_intervention_variables_in_domain(district={Y, W}, interventions={X}))
+        self.assertFalse(_no_intervention_variables_in_domain(district={X, Z}, interventions={X}))
+        self.assertTrue(
+            _no_transportability_nodes_in_domain(
+                district={Y, W}, domain_graph=figure_2_graph_domain_1_with_interventions
+            )
+        )
+        self.assertFalse(
+            _no_transportability_nodes_in_domain(
+                district={X, Z}, domain_graph=figure_2_graph_domain_1_with_interventions
+            )
+        )
+
+
+class TestInconsistentCounterfactualFactorVariableAndInterventionValues(cases.GraphTestCase):
+    """Test a check of whether a counterfactual factor has any variable value inconsistent with an intervention.
+
+    See Definition 4.1, part (i) from [correa22a]_.
+    """
+
+    def test_any_variable_values_inconsistent_with_interventions_1(self):
+        """Test #1 for whether a counterfactual factor variable has a value inconsistent with any intervention.
+
+        Source: RJC
+        """
+        event = {(Y @ [-X, -W, -Z], -Y), (W @ -X, -W), (X @ -Z, -X), (Z, -Z)}
+        self.assertFalse(_any_variable_values_inconsistent_with_interventions(event=event))
+
+    def test_any_variable_values_inconsistent_with_interventions_2(self):
+        """Test #2 for whether a counterfactual factor variable has a value inconsistent with any intervention.
+
+        Source: RJC
+        """
+        event = {(Y @ [+X, -W, -Z], -Y), (W @ -X, -W), (X @ -Z, -X), (Z, -Z)}
+        self.assertTrue(_any_variable_values_inconsistent_with_interventions(event=event))
+
+    def test_any_variable_values_inconsistent_with_interventions_3(self):
+        """Test #3 for whether a counterfactual factor variable has a value inconsistent with any intervention.
+
+        Source: RJC
+        """
+        event = {(Y @ [+X, -W, -Z], -Y), (W @ +X, -W), (X @ -Z, +X), (Z, -Z)}
+        self.assertFalse(_any_variable_values_inconsistent_with_interventions(event=event))
+
+    def test_any_variable_values_inconsistent_with_interventions_4(self):
+        """Test #4 for whether a counterfactual factor variable has a value inconsistent with any intervention.
+
+        Source: RJC
+        """
+        event = {(Y @ [-X, -W, -Z], -Y), (W @ -X, -W), (X @ -Z, +X), (Z, -Z)}
+        self.assertTrue(_any_variable_values_inconsistent_with_interventions(event=event))
+
+    def test_any_variable_values_inconsistent_with_interventions_5(self):
+        """Test #5 for whether a counterfactual factor variable has a value inconsistent with any intervention.
+
+        Source: RJC
+        """
+        event = {(Y @ [+X, -W, -Z], -Y), (W @ +X, -W), (X @ -Z, -X), (Z, -Z)}
+        self.assertTrue(_any_variable_values_inconsistent_with_interventions(event=event))
+
+    def test_any_variable_values_inconsistent_with_interventions_6(self):
+        """Test #6 for whether a counterfactual factor variable has a value inconsistent with any intervention.
+
+        This is a case that is inconsistent according to Definition 4.1(ii)
+        but not Definition 4.1(i).
+        Source: RJC
+        """
+        # graph = NxMixedGraph.from_edges(
+        #    directed=[
+        #        (X, Y),
+        #        (X, Z),
+        #    ],
+        #    undirected=[(Y, Z)],
+        # )
+        event = {(Y @ -X, -Y), (Z @ +X, -Z)}
+        self.assertFalse(_any_variable_values_inconsistent_with_interventions(event=event))
+
+
+class TestInconsistentCounterfactualFactorVariableInterventionValues(cases.GraphTestCase):
+    """Test function checking whether a counterfactual factor has any inconsistent intervention values.
+
+    See Definition 4.1, part (ii) from [correa22a]_.
+    """
+
+    def test_any_inconsistent_intervention_values_1(self):
+        """Test #1 for whether a counterfactual factor has any inconsistent intervention values.
+
+        Source: RJC
+        """
+        event = {(Y @ [-X, -W, -Z], -Y), (W @ -X, -W), (X @ -Z, +X), (Z, -Z)}
+        self.assertFalse(_any_inconsistent_intervention_values(event=event))
+
+    def test_any_inconsistent_intervention_values_2(self):
+        """Test #2 for whether a counterfactual factor has any inconsistent intervention values.
+
+        Source: RJC
+        """
+        event = {(Y @ [+X, -W, -Z], -Y), (W @ -X, -W), (X @ -Z, -X), (Z, -Z)}
+        self.assertTrue(_any_inconsistent_intervention_values(event=event))
+
+    def test_any_inconsistent_intervention_values_3(self):
+        """Test #3 for whether a counterfactual factor has any inconsistent intervention values.
+
+        Source: RJC
+        """
+        event = [(Y @ [+X, -W, -Z], -Y), (W @ +X, -W), (X @ -Z, +X), (Z, -Z)]
+        self.assertFalse(_any_inconsistent_intervention_values(event=event))
+
+    def test_any_inconsistent_intervention_values_4(self):
+        """Test #4 for whether a counterfactual factor has any inconsistent intervention values.
+
+        Source: RJC
+        """
+        event = [(Y @ [+X, +W, +Z], -Y), (W @ +X, -W), (X @ +Z, -X), (Z, -Z)]
+        self.assertFalse(_any_inconsistent_intervention_values(event=event))
+
+    def test_any_inconsistent_intervention_values_5(self):
+        """Test #5 for whether a counterfactual factor has any inconsistent intervention values.
+
+        This is a case that is inconsistent according to Definition 4.1(ii)
+        but not Definition 4.1(i).
+        Source: RJC
+        """
+        # graph = NxMixedGraph.from_edges(
+        #    directed=[
+        #        (X, Y),
+        #        (X, Z),
+        #    ],
+        #    undirected=[(Y, Z)],
+        # )
+        event = [(Y @ -X, -Y), (Z @ +X, -Z)]
+        self.assertTrue(_counterfactual_factor_is_inconsistent(event=event))
+
+
+class TestCounterfactualFactorIsInconsistent(cases.GraphTestCase):
+    """Test a check of whether a counterfactual factor is inconsistent (Definition 4.1 of [correa22a]_)."""
+
+    def test_counterfactual_factor_is_inconsistent_1(self):
+        """Test #1 for whether a counterfactual factor is inconsistent.
+
+        Source: RJC
+        """
+        event = [(Y @ [-X, -W, -Z], -Y), (W @ -X, -W), (X @ -Z, +X), (Z, -Z)]
+        self.assertTrue(_counterfactual_factor_is_inconsistent(event=event))
+
+    def test_counterfactual_factor_is_inconsistent_2(self):
+        """Test #2 for whether a counterfactual factor is inconsistent.
+
+        Source: RJC
+        """
+        event = [(Y @ [+X, -W, -Z], -Y), (W @ -X, -W), (X @ -Z, -X), (Z, -Z)]
+        self.assertTrue(_counterfactual_factor_is_inconsistent(event=event))
+
+    def test_counterfactual_factor_is_inconsistent_3(self):
+        """Test #3 for whether a counterfactual factor is inconsistent.
+
+        Source: RJC
+        """
+        event = [(Y @ [+X, -W, -Z], -Y), (W @ +X, -W), (X @ -Z, +X), (Z, -Z)]
+        self.assertFalse(_counterfactual_factor_is_inconsistent(event=event))
+
+    def test_counterfactual_factor_is_inconsistent_4(self):
+        """Test #4 for whether a counterfactual factor is inconsistent.
+
+        Source: RJC
+        """
+        event = [(Y @ [+X, +W, +Z], -Y), (W @ +X, -W), (X @ +Z, -X), (Z, -Z)]
+        self.assertTrue(_counterfactual_factor_is_inconsistent(event=event))
+
+    def test_counterfactual_factor_is_inconsistent_5(self):
+        """Test #5 for whether a counterfactual factor is inconsistent.
+
+        This is a case that is inconsistent according to Definition 4.1(ii)
+        but not Definition 4.1(i).
+
+        Source: RJC
+        """
+        # graph = NxMixedGraph.from_edges(
+        #    directed=[
+        #        (X, Y),
+        #        (X, Z),
+        #    ],
+        #    undirected=[(Y, Z)],
+        # )
+        event = [(Y @ -X, -Y), (Z @ +X, -Z)]
+        self.assertTrue(_counterfactual_factor_is_inconsistent(event=event))
+
+
+# TODO: x 1. We need a test case that returns a probability of Zero() if the Simplify() algorithm
+#          returns a probability 0 during transport_unconditional_counterfactual_query().
+#       2. We need to test _transport_unconditional_counterfactual_query_line_2() for an input
+#          event containing a variable that already has a value of None. This represents input
+#          values for $\mathbf{y_{\ast}}$ corresponding to variables in $\mathbf{d_{\ast}}$ but
+#          not $\mathbf{y_{\ast}}$ or $\mathbf{x_{\ast}}$ processed in Algorithm 3,
+#          transport_conditional_counterfactual_query.
+#       3. Test a query containing a variable with a value of None and the same variable with an
+#          actual value (see test cases for TransportConditionalCounterfactualQuery for the reason).
+class TestTransportUnconditionalCounterfactualQuery(cases.GraphTestCase):
+    """Test [correa22a]_'s unconditional counterfactual transportability algorithm (Algorithm 2)."""
+
+    def test_transport_unconditional_counterfactual_query_1(self):
+        """Test of Algorithm 2 of [correa22a]_.
+
+        Source: Example 4.2 from [correa22]_ (Equations 15, 17, and 19).
+        """
+        event = [(Y @ -X, -Y), (X, -X)]
+        domain_graphs = [
+            (
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_1_with_interventions_topo,
+            ),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        expected_result_part_1 = Product.safe(
+            [PP[Pi1](Y | W, X, Z), PP[Pi1](W | X), PP[Pi2](X | Z), PP[Pi2](Z)]
+        )
+        expected_result = Sum.safe(expected_result_part_1, [Z, W])
+
+        result_expr, result_event = transport_unconditional_counterfactual_query(
+            event=event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        logger.warning("Result_expr = " + result_expr.to_latex())
+        logger.warning("Result_event = " + str(result_event))
+        self.assert_expr_equal(expected_result, result_expr)
+
+    def test_transport_unconditional_counterfactual_query_2(self):
+        """Test of Algorithm 2 of [correa22a]_.
+
+        Checking Line 3: an inconsistent counterfactual factor.
+        Source: RJC's mind.
+        """
+        event = [(Y @ -X, -Y), (W @ +X, -W), (X, -X)]
+        domain_graphs = [
+            (
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_1_with_interventions_topo,
+            ),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        # expected_result_part_1 = Product.safe(
+        #    [PP[Pi1](Y | W, X, Z), PP[Pi1](W | X), PP[Pi2](X | Z), PP[Pi2](Z)]
+        # )
+        # expected_result = Sum.safe(expected_result_part_1, [Z, W])
+
+        result = transport_unconditional_counterfactual_query(
+            event=event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertIsNone(result)
+        # self.assert_expr_equal(expected_result, result)
+
+    def test_transport_unconditional_counterfactual_query_3(self):
+        """Test of Algorithm 2 of [correa22a]_.
+
+        Checking that the algorithm properly processes variables with values of None.
+
+        Source: Example 4.2 from [correa22]_ (Equations 15, 17, and 19).
+        """
+        event = [(Y @ -X, -Y), (X, -X)]
+        domain_graphs = [
+            (
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_1_with_interventions_topo,
+            ),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        expected_result_part_1 = Product.safe(
+            [PP[Pi1](Y | W, X, Z), PP[Pi1](W | X), PP[Pi2](X | Z), PP[Pi2](Z)]
+        )
+        expected_result = Sum.safe(expected_result_part_1, [Z, W])
+        result_expr, result_event = transport_unconditional_counterfactual_query(
+            event=event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        logger.warning("Result_expr = " + result_expr.to_latex())
+        logger.warning("Result_event = " + str(result_event))
+        self.assert_expr_equal(expected_result, result_expr)
+        self.assertCountEqual(event, result_event)
+        # Test sending variables with a value of None into this algorithm
+        event_2 = [(Y @ -X, None), (X, -X)]
+        event_3 = [(Y @ -X, -Y), (X, None)]
+        event_4 = [(Y @ -X, -Y), (Y @ -X, None), (X, -X)]
+        result_expr_2, result_event_2 = transport_unconditional_counterfactual_query(
+            event=event_2,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        result_expr_3, result_event_3 = transport_unconditional_counterfactual_query(
+            event=event_3,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        result_expr_4, result_event_4 = transport_unconditional_counterfactual_query(
+            event=event_4,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assert_expr_equal(expected_result, result_expr_2)
+        self.assertCountEqual(event_2, result_event_2)
+        self.assert_expr_equal(expected_result, result_expr_3)
+        self.assertCountEqual(event_3, result_event_3)
+        self.assert_expr_equal(expected_result, result_expr_4)
+        # Simplify() should drop the redundant query variable that has a None value
+        self.assertCountEqual(event, result_event_4)
+
+    def test_transport_unconditional_counterfactual_query_line_2_1(self):
+        """Test of Line 2 of Algorithm 2 of [correa22a]_.
+
+        Source: Example 4.2 from [correa22]_ (Equations 15, 17, and 19).
+        """
+        event = [(Y @ -X, -Y), (X, -X)]
+        target_domain_graph = figure_2a_graph
+        expected_ancestors_with_values = {(Y @ -X, -Y), (W @ -X, None), (X, -X), (Z, None)}
+        expected_counterfactual_factors_with_values = [
+            {(Y @ [-X, -W, -Z], -Y), (W @ -X, None)},
+            {(X @ -Z, -X), (Z, None)},
+        ]
+        (
+            outcome_ancestors_with_values,
+            counterfactual_factors_with_values,
+        ) = _transport_unconditional_counterfactual_query_line_2(event, target_domain_graph)
+        self.assertSetEqual(expected_ancestors_with_values, outcome_ancestors_with_values)
+        self.assertCountEqual(
+            expected_counterfactual_factors_with_values, counterfactual_factors_with_values
+        )
+
+    def test_transport_unconditional_counterfactual_query_line_2_2(self):
+        """Second test of Line 2 of Algorithm 2 of [correa22a]_.
+
+        This tests whether we properly get W @-X1 and W @ -X2 as two separate ancestors, and then
+        merge them into one counterfactual factor W @ [-X1, -X2].
+
+        Source: Example 4.2 from [correa22]_ (Equations 15, 17, and 19).
+        """
+        graph = NxMixedGraph.from_edges(
+            directed=[
+                (X1, W),
+                (X1, Y),
+                (X2, W),
+                (W, Y),
+                (W, Z),
+            ],
+        )
+
+        event = [(Y @ -X1, -Y), (Z @ -X2, -Z)]
+        target_domain_graph = graph
+        expected_ancestors_with_values = {
+            (Y @ -X1, -Y),
+            (W @ -X1, None),
+            (W @ -X2, None),
+            (Z @ -X2, -Z),
+            (X1, None),
+            (X2, None),
+        }
+        expected_counterfactual_factors_with_values = [
+            {(Y @ [-X1, -W], -Y)},
+            {(W @ [-X1, -X2], None)},
+            {(Z @ -W, -Z)},
+            {(X1, None)},
+            {(X2, None)},
+        ]
+        (
+            outcome_ancestors_with_values,
+            counterfactual_factors_with_values,
+        ) = _transport_unconditional_counterfactual_query_line_2(event, target_domain_graph)
+        self.assertSetEqual(expected_ancestors_with_values, outcome_ancestors_with_values)
+        self.assertCountEqual(
+            expected_counterfactual_factors_with_values, counterfactual_factors_with_values
+        )
+
+    def test_transport_unconditional_counterfactual_query_simplify_returns_none(self):
+        """Test of Line 1 of Algorithm 2 of [correa22a]_.
+
+        This tests whether we properly return (Zero(), None) if a query, when simplified,
+        returns a probability of zero.
+
+        Source: Example 4.2 from [correa22]_, modified to make the simplification fail.
+        """
+        # 1. We need a test case that returns a probability of Zero() if the Simplify() algorithm
+        #   returns a probability 0 during transport_unconditional_counterfactual_query().
+        event = [(Y @ -Y, -Y), (Y, +Y)]
+        # event = [(Y @ -X, -Y), (X, -X)]
+        domain_graphs = [
+            (
+                figure_2_graph_domain_1_with_interventions,
+                figure_2_graph_domain_1_with_interventions_topo,
+            ),
+            (
+                figure_2_graph_domain_2,
+                figure_2_graph_domain_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        result_expr, result_event = transport_unconditional_counterfactual_query(
+            event=event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assert_expr_equal(result_expr, Zero())
+        self.assertIsNone(result_event)
+
+    def test_transport_unconditional_counterfactual_query_line_2_3(self):
+        """Third test of Line 2 of Algorithm 2 of [correa22a]_.
+
+        This tests whether we properly get W @ -X1 and W @ -X2 from two different variables but
+        represent it as one ancestor, which we then merge into one counterfactual factor W @ -X2.
+
+        Source: Example 4.2 from [correa22]_ (Equations 15, 17, and 19).
+        """
+        graph = NxMixedGraph.from_edges(
+            directed=[
+                (X1, W),
+                (X1, Y),
+                (X2, W),
+                (W, Y),
+                (W, Z),
+            ],
+        )
+
+        event = [(Y @ -X1, -Y), (Z @ -X2, -Z)]
+        target_domain_graph = graph
+        expected_ancestors_with_values = {
+            (Y @ -X1, -Y),
+            (W @ -X1, None),
+            (W @ -X2, None),
+            (Z @ -X2, -Z),
+            (X1, None),
+            (X2, None),
+        }
+        expected_counterfactual_factors_with_values = [
+            {(Y @ [-X1, -W], -Y)},
+            {(W @ [-X1, -X2], None)},
+            {(Z @ -W, -Z)},
+            {(X1, None)},
+            {(X2, None)},
+        ]
+        (
+            outcome_ancestors_with_values,
+            counterfactual_factors_with_values,
+        ) = _transport_unconditional_counterfactual_query_line_2(event, target_domain_graph)
+        self.assertSetEqual(expected_ancestors_with_values, outcome_ancestors_with_values)
+        self.assertCountEqual(
+            expected_counterfactual_factors_with_values, counterfactual_factors_with_values
+        )
+
+    def test_transport_unconditional_counterfactual_query_line_2_4(self):
+        """Fourth test of Line 2 of Algorithm 2 of [correa22a]_.
+
+        This tests if the function properly parses input counterfactual variables with
+        values of None.
+
+        Source: Example 4.2 from [correa22]_ (Equations 15, 17, and 19).
+        """
+        event = [(Y @ -X, None), (X, -X)]
+        target_domain_graph = figure_2a_graph
+        expected_ancestors_with_values = {(Y @ -X, None), (W @ -X, None), (X, -X), (Z, None)}
+        expected_counterfactual_factors_with_values = [
+            {(Y @ [-X, -W, -Z], None), (W @ -X, None)},
+            {(X @ -Z, -X), (Z, None)},
+        ]
+        (
+            outcome_ancestors_with_values,
+            counterfactual_factors_with_values,
+        ) = _transport_unconditional_counterfactual_query_line_2(event, target_domain_graph)
+        self.assertSetEqual(expected_ancestors_with_values, outcome_ancestors_with_values)
+        self.assertCountEqual(
+            expected_counterfactual_factors_with_values, counterfactual_factors_with_values
+        )
+
+    def test_transport_unconditional_counterfactual_query_line_2_5(self):
+        """Fifth test of Line 2 of Algorithm 2 of [correa22a]_.
+
+        This tests if the function properly parses input variables with
+        values of None.
+
+        Source: Example 4.2 from [correa22]_ (Equations 15, 17, and 19).
+        """
+        event = [(Y @ -X, -Y), (X, None)]
+        target_domain_graph = figure_2a_graph
+        expected_ancestors_with_values = {(Y @ -X, -Y), (W @ -X, None), (X, None), (Z, None)}
+        expected_counterfactual_factors_with_values = [
+            {(Y @ [-X, -W, -Z], -Y), (W @ -X, None)},
+            {(X @ -Z, None), (Z, None)},
+        ]
+        (
+            outcome_ancestors_with_values,
+            counterfactual_factors_with_values,
+        ) = _transport_unconditional_counterfactual_query_line_2(event, target_domain_graph)
+        self.assertSetEqual(expected_ancestors_with_values, outcome_ancestors_with_values)
+        self.assertCountEqual(
+            expected_counterfactual_factors_with_values, counterfactual_factors_with_values
+        )
+
+    def test_transport_unconditional_counterfactual_query_line_5(self):
+        """Test of Line 5 of Algorithm 2 of [correa22a]_.
+
+        This test checks that if a counterfactual factor can't be transported, the algorithm
+        returns None.
+
+        Source: RJC's mind.
+        """
+        graph_1 = NxMixedGraph.from_edges(
+            directed=[(Z, X), (X, Y), (Z, Y), (W, Y), (transport_variable(Y), Y)],
+            undirected=[
+                (Z, X),
+            ],
+        )
+        graph_1_topo = list(graph_1.topological_sort())
+        # Let's say graph 2 is a stochastic intervention where Y is a function of W and Z,
+        # but not X.
+        graph_2 = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (X, W),
+                (Z, Y),
+                (W, Y),
+            ],
+            undirected=[],
+        )
+        graph_2_topo = list(graph_2.topological_sort())
+        domain_data = [(set(), PP[Pi1](W, X, Y, Z)), ({Y}, PP[Pi2](W, X, Y, Z))]
+        query_result = transport_unconditional_counterfactual_query(
+            event=[(Y @ -X, -Y), (X, -X)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[(graph_1, graph_1_topo), (graph_2, graph_2_topo)],
+            domain_data=domain_data,
+        )
+        self.assertIsNone(query_result)
+
+
+class TestGetConditionedVariablesInAncestralSet(cases.GraphTestCase):
+    r"""Identify conditioned variables that are ancestors of an input variable ($\mathbf{X_{\ast}}(W_{\mathbf{\ast}})$).
+
+    Note: see the documentation for get_conditioned_variables_in_ancestral_set for a note about
+    some ambiguity in the mathematical definition of that function in [correa22a]_.
+    """
+
+    def test_get_conditioned_variable_in_ancestral_set_1(self):
+        """First test of the function to identify conditioned variables that are ancestors of an input variable.
+
+        Source: Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_1 = {Z}
+        result_1 = _get_conditioned_variables_in_ancestral_set(
+            conditioned_variables={Z @ -X, X},
+            ancestral_set_root_variable=Y @ -X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_1, result_1)
+
+    def test_get_conditioned_variable_in_ancestral_set_2(self):
+        """Second test of the function to identify conditioned variables that are ancestors of an input variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_2 = {Z}
+        result_2 = _get_conditioned_variables_in_ancestral_set(
+            conditioned_variables={Z @ -X, X},
+            ancestral_set_root_variable=Z @ -X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_2, result_2)
+
+    def test_get_conditioned_variable_in_ancestral_set_3(self):
+        """Third test of the function to identify conditioned variables that are ancestors of an input variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_3 = {X}
+        result_3 = _get_conditioned_variables_in_ancestral_set(
+            conditioned_variables={Z @ -X, X},
+            ancestral_set_root_variable=X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_3, result_3)
+
+    def test_get_conditioned_variable_in_ancestral_set_4(self):
+        """Fourth test of the function to identify conditioned variables that are ancestors of an input variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_4 = set()
+        result_4 = _get_conditioned_variables_in_ancestral_set(
+            conditioned_variables={Z @ -X},
+            ancestral_set_root_variable=X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_4, result_4)
+
+    def test_get_conditioned_variable_in_ancestral_set_5(self):
+        """Fourth test of the function to identify conditioned variables that are ancestors of an input variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_5 = set()
+        result_5 = _get_conditioned_variables_in_ancestral_set(
+            conditioned_variables={X},
+            ancestral_set_root_variable=Z @ -X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_5, result_5)
+
+    def test_get_conditioned_variable_in_ancestral_set_6(self):
+        """Fourth test of the function to identify conditioned variables that are ancestors of an input variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_6 = {X}
+        result_6 = _get_conditioned_variables_in_ancestral_set(
+            conditioned_variables={X},
+            ancestral_set_root_variable=X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_6, result_6)
+
+    def test_get_conditioned_variable_in_ancestral_set_7(self):
+        """Fourth test of the function to identify conditioned variables that are ancestors of an input variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_7 = set()
+        result_7 = _get_conditioned_variables_in_ancestral_set(
+            conditioned_variables={X},
+            ancestral_set_root_variable=Y @ -X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_7, result_7)
+
+
+class TestGetAncestralSetAfterInterveningOnConditionedVariables(cases.GraphTestCase):
+    """Intervene on conditioned variables in a variable's ancestral set and recompute the ancestral set."""
+
+    def test_get_ancestral_set_after_intervening_on_conditioned_variables_1(self):
+        """First test of the function to get an ancestral set after intervening on some conditioned variables.
+
+        Note that we only intervene on the conditioned variables that are ancestors of the input root variable.
+
+        Source: Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_1 = frozenset({Y @ -X})
+        result_1 = _get_ancestral_set_after_intervening_on_conditioned_variables(
+            conditioned_variables={Z @ -X, X},
+            ancestral_set_root_variable=Y @ -X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_1, result_1)
+
+    def test_get_ancestral_set_after_intervening_on_conditioned_variables_2(self):
+        """Second test of the function to get an ancestral set after intervening on some conditioned variables.
+
+        Note that we only intervene on the conditioned variables that are ancestors of the input root variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_2 = frozenset({Z @ -X})
+        result_2 = _get_ancestral_set_after_intervening_on_conditioned_variables(
+            conditioned_variables={Z @ -X, X},
+            ancestral_set_root_variable=Z @ -X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_2, result_2)
+
+    def test_get_ancestral_set_after_intervening_on_conditioned_variables_3(self):
+        """Third test of the function to get an ancestral set after intervening on some conditioned variables.
+
+        Note that we only intervene on the conditioned variables that are ancestors of the input root variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_3 = frozenset({X})
+        result_3 = _get_ancestral_set_after_intervening_on_conditioned_variables(
+            conditioned_variables={Z @ -X, X},
+            ancestral_set_root_variable=X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_3, result_3)
+
+    def test_get_ancestral_set_after_intervening_on_conditioned_variables_4(self):
+        """Fourth test of the function to get an ancestral set after intervening on some conditioned variables.
+
+        Note that we only intervene on the conditioned variables that are ancestors of the input root variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_4 = frozenset({Z @ -X})
+        result_4 = _get_ancestral_set_after_intervening_on_conditioned_variables(
+            conditioned_variables={Z @ -X},
+            ancestral_set_root_variable=Z @ -X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_4, result_4)
+
+    def test_get_ancestral_set_after_intervening_on_conditioned_variables_5(self):
+        """Fifth test of the function to get an ancestral set after intervening on some conditioned variables.
+
+        Note that we only intervene on the conditioned variables that are ancestors of the input root variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_5 = frozenset({Y @ -X, Z @ -X})
+        result_5 = _get_ancestral_set_after_intervening_on_conditioned_variables(
+            conditioned_variables={X},
+            ancestral_set_root_variable=Y @ -X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_5, result_5)
+
+    def test_get_ancestral_set_after_intervening_on_conditioned_variables_6(self):
+        """Sixth test of the function to get an ancestral set after intervening on some conditioned variables.
+
+        Note that we only intervene on the conditioned variables that are ancestors of the input root variable.
+
+        Source: Slight modification of Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_6 = frozenset({X})
+        result_6 = _get_ancestral_set_after_intervening_on_conditioned_variables(
+            conditioned_variables={Z @ -X},
+            ancestral_set_root_variable=X,
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_6, result_6)
+
+
+# TODO: Add additional tests after checking code coverage.
+class TestComputeAncestralComponentsFromAncestralSets(cases.GraphTestCase):
+    """Test a function to combine ancestral sets if they share vertices or are joined by bidirected edges."""
+
+    def test_compute_ancestral_components_from_ancestral_sets_1(self):
+        """First test of a function to combine ancestral sets if they share vertices or are joined by bidirected edges.
+
+        Source: Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_1 = frozenset({frozenset({Y @ -X}), frozenset({Z @ -X, X})})
+        result_1 = _compute_ancestral_components_from_ancestral_sets(
+            ancestral_sets=frozenset({frozenset({X}), frozenset({Z @ -X}), frozenset({Y @ -X})}),
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_1, result_1)
+
+    def test_compute_ancestral_components_from_ancestral_sets_2(self):
+        """Second test of a function to combine ancestral sets if they share vertices or are joined by bidirected edges.
+
+        Source: RJC, using the graph for Figure 2a of [correa22a]_.
+        """
+        expected_result_2 = frozenset({frozenset({X, Z}), frozenset({W, Y})})
+        result_2 = _compute_ancestral_components_from_ancestral_sets(
+            ancestral_sets=frozenset(
+                {frozenset({W}), frozenset({X}), frozenset({Y}), frozenset({Z})}
+            ),
+            graph=figure_2a_graph,
+        )
+        self.assertSetEqual(expected_result_2, result_2)
+
+    def test_compute_ancestral_components_from_ancestral_sets_3(self):
+        """Third test of a function to combine ancestral sets if they share vertices or are joined by bidirected edges.
+
+        Source: RJC, using the graph for Figure 2a of [correa22a]_.
+        """
+        expected_result_3 = frozenset({frozenset({W, X, Y, Z})})
+        result_3 = _compute_ancestral_components_from_ancestral_sets(
+            ancestral_sets=frozenset({frozenset({W, X}), frozenset({Y}), frozenset({Z})}),
+            graph=figure_2a_graph,
+        )
+        self.assertSetEqual(expected_result_3, result_3)
+
+    def test_compute_ancestral_components_from_ancestral_sets_4(self):
+        """Fourth test of a function to combine ancestral sets if they share vertices or are joined by bidirected edges.
+
+        Source: RJC, based the graph for Figure 2a of [correa22a]_ with no bidirectional edges.
+        """
+        graph = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (Z, Y),
+                (X, Y),
+                (X, W),
+                (W, Y),
+            ],
+        )
+        expected_result_4 = frozenset({frozenset({W, X, Y}), frozenset({Z})})
+        result_4 = _compute_ancestral_components_from_ancestral_sets(
+            ancestral_sets=frozenset({frozenset({W, X}), frozenset({X, Y}), frozenset({Z})}),
+            graph=graph,
+        )
+        self.assertSetEqual(expected_result_4, result_4)
+
+    def test_compute_ancestral_components_from_ancestral_sets_5(self):
+        """Fifth test of a function to combine ancestral sets if they share vertices or are joined by bidirected edges.
+
+        Source: Example 1.1 of [correa22a]_, modified to include ancestral sets that are not disjoint and
+           bidirected edges joining two ancestral sets.
+        """
+        expected_result_5 = frozenset({frozenset({Y @ -X, Y, Z @ -X, X})})
+        result_5 = _compute_ancestral_components_from_ancestral_sets(
+            ancestral_sets=frozenset({frozenset({X, Y}), frozenset({Z @ -X}), frozenset({Y @ -X})}),
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_5, result_5)
+
+    def test_compute_ancestral_components_from_ancestral_sets_6(self):
+        """Sixth test of a function to combine ancestral sets if they share vertices or are joined by bidirected edges.
+
+        Source: Example 1.1 of [correa22a]_, modified to include ancestral sets that are not disjoint and
+           bidirected edges joining two ancestral sets.
+        """
+        expected_result_6 = frozenset({frozenset({Y @ -X, Y @ -Z, Z @ -X, X})})
+        result_6 = _compute_ancestral_components_from_ancestral_sets(
+            ancestral_sets=frozenset(
+                {frozenset({X, Y @ -Z}), frozenset({Z @ -X}), frozenset({Y @ -X})}
+            ),
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_6, result_6)
+
+
+# TODO: Add additional tests covering more complex scenarios.
+class TestGetAncestralComponents(cases.GraphTestCase):
+    """Test a function to compute ancestral components given a set of vertices and conditioned variables."""
+
+    def test_get_ancestral_components_1(self):
+        """First test of a function to compute ancestral components for a graph.
+
+        Source: Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        expected_result_1 = frozenset({frozenset({Y @ -X}), frozenset({Z @ -X, X})})
+        result_1 = get_ancestral_components(
+            conditioned_variables=frozenset({X, Z @ -X}),
+            root_variables=frozenset({Y @ -X, Z @ -X, X}),
+            graph=figure_1_graph_no_transportability_nodes,
+        )
+        self.assertSetEqual(expected_result_1, result_1)
+
+
+# TODO: 1. We need a test case that returns a probability of Zero() if the Simplify() algorithm
+#          returns a probability 0 during transport_unconditional_counterfactual_query().
+#       2. Test a case that sends a query to TransportUnconditionalCounterfactualQuery containing
+#          a variable with a value of None and the same variable with an actual value, due to its
+#          status as both an outcome variable and an ancestor of an outcome variable.
+class TestTransportConditionalCounterfactualQuery(cases.GraphTestCase):
+    """Test a function to transport a conditional counterfactual query (Algorithm 3 of [correa22a]_)."""
+
+    example_1_outcomes = [(Y @ -X, -Y)]
+    example_1_conditions = [(Z @ -X, -Z), (X, +X)]
+    example_1_target_domain_graph = figure_1_graph_no_transportability_nodes
+    example_1_domain_graphs = [
+        (
+            figure_1_graph_no_transportability_nodes,
+            figure_1_graph_no_transportability_nodes_topo,
+        ),
+        (
+            figure_1_graph_domain_1_with_interventions,
+            figure_1_graph_domain_1_with_interventions_topo,
+        ),
+    ]
+    example_2_outcomes = [(Y @ -X1, -Y), (W @ -X2, -W)]
+    example_2_conditions = [(X1, -X1)]
+    example_2_target_domain_graph = NxMixedGraph.from_edges(
+        directed=[
+            (X1, Z),
+            (X2, Z),
+            (Z, W),
+            (W, Y),
+        ],
+        undirected=[(Z, W)],
+    )
+    example_2_target_domain_graph_topo = list(example_2_target_domain_graph.topological_sort())
+    example_2_domain_1_graph = NxMixedGraph.from_edges(
+        directed=[(X1, Z), (X2, Z), (Z, W), (W, Y), (transport_variable(X2), X2)],
+        undirected=[],
+    )
+    example_2_domain_1_graph_topo = list(example_2_domain_1_graph.topological_sort())
+    example_2_domain_graphs = [
+        (
+            example_2_target_domain_graph,
+            example_2_target_domain_graph_topo,
+        ),
+        (
+            example_2_domain_1_graph,
+            example_2_domain_1_graph_topo,
+        ),
+    ]
+
+    def test_transport_conditional_counterfactual_query_1(self):
+        """First test of Algorithm 3 of [correa22a], transporting a conditional counterfactual query.
+
+        Source: Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        # DSL isn't smart enough to replace the denominator with 1
+        domain_data = [(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({X}, PP[Pi1](X, Y, Z))]
+        expected_result_expr, expected_result_event = (
+            Fraction(PP[TARGET_DOMAIN](Y | X, Z), Sum.safe(PP[TARGET_DOMAIN](Y | X, Z), {Y})),
+            [(Y, -Y), (X, +X), (Z, -Z)],
+        )
+        result_expr, result_event = transport_conditional_counterfactual_query(
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=domain_data,
+        )
+        logger.warning("expected_result_expr = " + expected_result_expr.to_latex())
+        logger.warning("expected_result_event = " + str(expected_result_event))
+        logger.warning("Result_expr = " + result_expr.to_latex())
+        logger.warning("Result_event = " + str(result_event))
+        self.assert_expr_equal(expected_result_expr, result_expr)
+        self.assertCountEqual(expected_result_event, result_event)
+
+    def test_transport_conditional_counterfactual_query_2(self):
+        """Second test of Algorithm 3 of [correa22a], transporting a conditional counterfactual query.
+
+        Source: RC's mind. Designed to test merging outcome ancestral sets into ancestral components.
+        """
+        # DSL isn't smart enough to replace the denominator with 1
+        # @cthoyt: The code base would be simpler were we to instantiate these as static class-level objects,
+        # but then we get the following MyPy error:
+        # error: Value of type "type[PopulationProbabilityBuilderType]" is not indexable
+        expected_result_expr, expected_result_event = (
+            Fraction(
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2},
+                ),
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2, Y, W},
+                ),
+            ),
+            [(Y, -Y), (W, -W), (X1, -X1)],
+        )
+        domain_data = [
+            (set(), PP[TARGET_DOMAIN](X1, X2, W, Y, Z)),
+            ({W}, PP[Pi1](X1, X2, W, Y, Z)),
+        ]
+        result_expr, result_event = transport_conditional_counterfactual_query(
+            outcomes=self.example_2_outcomes,
+            conditions=self.example_2_conditions,
+            target_domain_graph=self.example_2_target_domain_graph,
+            domain_graphs=self.example_2_domain_graphs,
+            domain_data=domain_data,
+        )
+        logger.warning("expected_result_expr = " + expected_result_expr.to_latex())
+        logger.warning("expected_result_event = " + str(expected_result_event))
+        logger.warning("Result_expr = " + result_expr.to_latex())
+        logger.warning("Result_event = " + str(result_event))
+        self.assert_expr_equal(expected_result_expr, result_expr)
+        self.assertCountEqual(expected_result_event, result_event)
+
+    def test_transport_conditional_counterfactual_query_subroutines_1(self):
+        """Test _initialize_conditional_transportability_data_structures().
+
+        Source: RC's mind.
+        """
+        outcomes = [(Y @ -X1, -Y), (W @ -X2, -W)]
+        conditions = [(X1, -X1), (X2, -X2)]
+        (
+            conditioned_variables,
+            outcome_variables,
+            outcome_and_conditioned_variables,
+            outcome_variable_to_value_mappings,
+            outcome_and_conditioned_variable_names_to_values,
+            outcome_and_conditioned_variable_names,
+            conditioned_variable_names,
+        ) = _initialize_conditional_transportability_data_structures(
+            outcomes=outcomes, conditions=conditions
+        )
+        self.assertSetEqual(conditioned_variables, {X1, X2})
+        self.assertSetEqual(outcome_variables, {Y @ -X1, W @ -X2})
+        self.assertSetEqual(outcome_and_conditioned_variables, {X1, X2, Y @ -X1, W @ -X2})
+        self.assertDictEqual(outcome_variable_to_value_mappings, {Y @ -X1: {-Y}, W @ -X2: {-W}})
+        self.assertDictEqual(
+            outcome_and_conditioned_variable_names_to_values,
+            {X1: {-X1}, X2: {-X2}, Y: {-Y}, W: {-W}},
+        )
+        self.assertSetEqual(outcome_and_conditioned_variable_names, {X1, X2, W, Y})
+        self.assertSetEqual(conditioned_variable_names, {X1, X2})
+
+    def test_transport_conditional_counterfactual_query_subroutines_2(self):
+        """Test _transport_conditional_counterfactual_query_line_2().
+
+        Source: RC's mind.
+        """
+        ancestral_components = frozenset(
+            {frozenset({Y @ -X1, W @ -X1, Z @ -X1, X2, W @ -X2, Z @ -X2}), frozenset({X1})}
+        )
+        outcome_variables = {Y @ -X1, W @ -X2}
+        outcome_variable_to_value_mappings = {Y @ -X1: {-Y}, W @ -X2: {-W}}
+        expected_outcome_ancestral_component_query_in_counterfactual_factor_form = [
+            (Y @ -W, -Y),
+            (W @ -Z, -W),
+            (W @ -Z, None),
+            (Z @ -X1 @ -X2, None),
+            (Z @ -X1 @ -X2, None),
+            (X2, None),
+        ]
+        expected_outcome_variable_ancestral_component_variable_names = {Y, W, Z, X2}
+        (
+            outcome_ancestral_component_query_in_counterfactual_factor_form,
+            outcome_variable_ancestral_component_variable_names,
+        ) = _transport_conditional_counterfactual_query_line_2(
+            ancestral_components=ancestral_components,
+            outcome_variables=outcome_variables,
+            outcome_variable_to_value_mappings=outcome_variable_to_value_mappings,
+            target_domain_graph=self.example_2_target_domain_graph,
+        )
+        self.assertCountEqual(
+            expected_outcome_ancestral_component_query_in_counterfactual_factor_form,
+            outcome_ancestral_component_query_in_counterfactual_factor_form,
+        )
+        self.assertSetEqual(
+            outcome_variable_ancestral_component_variable_names,
+            expected_outcome_variable_ancestral_component_variable_names,
+        )
+
+    def test_transport_conditional_counterfactual_query_subroutines_3(self):
+        """Test _transport_conditional_counterfactual_query_line_4().
+
+        Source: RC's mind.
+        """
+        outcomes = [(Y @ -X1, -Y), (W @ -X2, -W)]
+        conditions = [(X1, -X1)]
+        outcome_variable_ancestral_component_variable_names = {Y, W, Z, X2}
+        outcome_and_conditioned_variable_names = {Y, W, X1}
+        conditioned_variable_names = {X1}
+        transported_unconditional_query_expression = Product.safe(
+            [
+                PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                PP[TARGET_DOMAIN](W | Z, X2, X1),
+                PP[TARGET_DOMAIN](Z | X2, X1),
+                PP[TARGET_DOMAIN](X2 | X1),
+            ],
+        )
+        simplified_event = [
+            (Y @ -W, -Y),
+            (W @ -Z, -W),
+            (Z @ -X1 @ -X2, None),
+            (X2, None),
+        ]
+        # @cthoyt: The code base would be simpler were we to instantiate these as static class-level objects,
+        # but then we get the following MyPy error:
+        # error: Value of type "type[PopulationProbabilityBuilderType]" is not indexable
+        expected_result_expr, expected_result_event = (
+            Fraction(
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2},
+                ),
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2, Y, W},
+                ),
+            ),
+            [(Y, -Y), (W, -W), (X1, -X1)],
+        )
+        domain_data = [
+            (set(), PP[TARGET_DOMAIN](X1, X2, W, Y, Z)),
+            ({W}, PP[Pi1](X1, X2, W, Y, Z)),
+        ]
+        outcome_and_conditioned_variable_names_to_values = {X1: {-X1}, Y: {-Y}, W: {-W}}
+        result_expression, result_event = _transport_conditional_counterfactual_query_line_4(
+            outcome_variable_ancestral_component_variable_names=outcome_variable_ancestral_component_variable_names,
+            outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+            conditioned_variable_names=conditioned_variable_names,
+            transported_unconditional_query_expression=transported_unconditional_query_expression,
+            simplified_event=simplified_event,
+            outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+            outcomes=outcomes,
+            conditions=conditions,
+            domain_data=domain_data,
+        )
+        self.assert_expr_equal(result_expression, expected_result_expr)
+        self.assertCountEqual(result_event, expected_result_event)
+
+    def test_transport_conditional_counterfactual_query_subroutines_4(self):
+        """Test _validate_transport_conditional_counterfactual_query_line_4_output().
+
+        Source: RC's mind.
+        """
+        simplified_event = [
+            (Y @ -W, -Y),
+            (W @ -Z, -W),
+            (Z @ -X1 @ -X2, None),
+            (X2, None),
+        ]
+        outcome_and_conditioned_variable_names = {Y, W, X1}
+        outcome_and_conditioned_variable_names_to_values = {X1: {-X1}, Y: {-Y}, W: {-W}}
+        outcome_ancestral_component_variables_with_no_values = {Z, X2}  # {Y, W, Z, X2} - {Y, W, X1}
+        result_expression, result_event = (
+            Fraction(
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2},
+                ),
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2, Y, W},
+                ),
+            ),
+            [(Y, -Y), (W, -W), (X1, -X1)],
+        )
+        domain_data = [
+            (set(), PP[TARGET_DOMAIN](X1, X2, W, Y, Z)),
+            ({W}, PP[Pi1](X1, X2, W, Y, Z)),
+        ]
+        # 1. Make sure in the simplified event we got back, all of the variables
+        #    that have values are either variables in the outcomes or variables
+        #    in the conditions.
+        self.assertRaises(
+            KeyError,
+            _validate_transport_conditional_counterfactual_query_line_4_output,
+            simplified_event=[
+                (Y @ -W, -Y),
+                (W @ -Z, -W),
+                (Z @ -X1 @ -X2, -Z),  # Testing the first check
+                (X2, None),
+            ],
+            outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+            outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+            outcome_ancestral_component_variables_with_no_values=outcome_ancestral_component_variables_with_no_values,
+            result_expression=result_expression,
+            result_event=result_event,
+            domain_data=domain_data,
+        )
+        # 2. Make sure the values for those variables in the simplified event match
+        #    the values for their corresponding outcome or condition variables
+        #    in the input for this function.
+        self.assertRaises(
+            KeyError,
+            _validate_transport_conditional_counterfactual_query_line_4_output,
+            simplified_event=[
+                (Y @ -W, +Y),  # Testing the second check
+                (W @ -Z, -W),
+                (Z @ -X1 @ -X2, None),
+                (X2, None),
+            ],
+            outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+            outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+            outcome_ancestral_component_variables_with_no_values=outcome_ancestral_component_variables_with_no_values,
+            result_expression=result_expression,
+            result_event=result_event,
+            domain_data=domain_data,
+        )
+        # 3. Make sure all the variables in the expression this function will return are either
+        #    in the outcomes, the conditions, one of the expressions passed in with the
+        #    domain data, or the outcome ancestral component variables excluding outcomes and conditions.
+        #    See also test_transport_conditional_counterfactual_query_5 below.
+        self.assertRaises(
+            KeyError,
+            _validate_transport_conditional_counterfactual_query_line_4_output,
+            simplified_event=simplified_event,
+            outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+            outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+            outcome_ancestral_component_variables_with_no_values=outcome_ancestral_component_variables_with_no_values,
+            result_expression=Fraction(
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2},
+                ),
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1, R),  # Testing the third check
+                        ],
+                    ),
+                    {Z, X2, Y, W},
+                ),
+            ),
+            result_event=result_event,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            KeyError,
+            _validate_transport_conditional_counterfactual_query_line_4_output,
+            simplified_event=simplified_event,
+            outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+            outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+            outcome_ancestral_component_variables_with_no_values=outcome_ancestral_component_variables_with_no_values,
+            result_expression=Fraction(
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y, R | W, Z, X2, X1),  # Testing the third check
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2},
+                ),
+                Sum.safe(
+                    Product.safe(
+                        [
+                            PP[TARGET_DOMAIN](Y | W, Z, X2, X1),
+                            PP[TARGET_DOMAIN](W | Z, X2, X1),
+                            PP[TARGET_DOMAIN](Z | X2, X1),
+                            PP[TARGET_DOMAIN](X2 | X1),
+                        ],
+                    ),
+                    {Z, X2, Y, W},
+                ),
+            ),
+            result_event=result_event,
+            domain_data=domain_data,
+        )
+        # 4. Make sure we're not going to return any variables with None values. TODO: This check
+        #    won't be necessary after we verify that the input outcomes and conditions have no
+        #    None values.
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_line_4_output,
+            simplified_event=simplified_event,
+            outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+            outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+            outcome_ancestral_component_variables_with_no_values=outcome_ancestral_component_variables_with_no_values,
+            result_expression=result_expression,
+            result_event=[(Y, -Y), (W, -W), (X1, None)],
+            domain_data=domain_data,
+        )
+        # 5. Make sure the result expression and result event have the same variables.
+        self.assertRaises(
+            KeyError,
+            _validate_transport_conditional_counterfactual_query_line_4_output,
+            simplified_event=simplified_event,
+            outcome_and_conditioned_variable_names=outcome_and_conditioned_variable_names,
+            outcome_and_conditioned_variable_names_to_values=outcome_and_conditioned_variable_names_to_values,
+            outcome_ancestral_component_variables_with_no_values=outcome_ancestral_component_variables_with_no_values,
+            result_expression=result_expression,
+            result_event=[(Y, -Y), (W, -W), (X1, -X1), (R, -R)],
+            domain_data=domain_data,
+        )
+
+    def test_transport_conditional_counterfactual_query_5(self):
+        """Fifth test of Algorithm 3 of [correa22a], transporting a conditional counterfactual query.
+
+        Here we test the notion that the joint probability of the vertices sent in as input conditions
+        on other variables we don't see in the graph. A user may wish to transport a subgraph,
+        for example.
+
+        Source: Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        domain_data = [(set(), PP[TARGET_DOMAIN](X, Y, Z | R)), ({X}, PP[Pi1](X, Y, Z | R))]
+        expected_result_expr, expected_result_event = (
+            Fraction(PP[TARGET_DOMAIN](Y | X, Z, R), Sum.safe(PP[TARGET_DOMAIN](Y | X, Z, R), {Y})),
+            [(Y, -Y), (X, +X), (Z, -Z)],
+        )
+        result_expr, result_event = transport_conditional_counterfactual_query(
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=domain_data,
+        )
+        logger.warning("expected_result_expr = " + expected_result_expr.to_latex())
+        logger.warning("expected_result_event = " + str(expected_result_event))
+        logger.warning("Result_expr = " + result_expr.to_latex())
+        logger.warning("Result_event = " + str(result_event))
+        self.assert_expr_equal(expected_result_expr, result_expr)
+        self.assertCountEqual(expected_result_event, result_event)
+
+    def test_transport_conditional_counterfactual_query_6(self):
+        """Sixth test of Algorithm 3 of [correa22a], transporting a conditional counterfactual query.
+
+        Here we test the notion that the joint probability of the vertices sent in as input conditions
+        on other variables we don't see in the graph. A user may wish to transport a subgraph,
+        for example.
+
+        Source: Example 4.5 and Figure 6 of [correa22a]_.
+        """
+        domain_data = [(set(), PP[TARGET_DOMAIN](X, Y, Z, R)), ({X}, PP[Pi1](X, Y, Z, R))]
+        # What happens here is the extra variable R in the domain_data graph probability expression
+        # doesn't get used during processing because it's outside the graph and we don't condition on it.
+        # Leaving out R doesn't happen explicitly: it happens implicitly during compute_c_factor() called
+        # from tian_pearl_identify(). compute_c_factor() leaves out R because it's not part of the graph
+        # and it's not a conditioned variable, and that's expected behavior.
+        expected_result_expr, expected_result_event = (
+            Fraction(PP[TARGET_DOMAIN](Y | X, Z), Sum.safe(PP[TARGET_DOMAIN](Y | X, Z), {Y})),
+            [(Y, -Y), (X, +X), (Z, -Z)],
+        )
+        result_expr, result_event = transport_conditional_counterfactual_query(
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=domain_data,
+        )
+        logger.warning("expected_result_expr = " + expected_result_expr.to_latex())
+        logger.warning("expected_result_event = " + str(expected_result_event))
+        logger.warning("Result_expr = " + result_expr.to_latex())
+        logger.warning("Result_event = " + str(result_event))
+        self.assert_expr_equal(expected_result_expr, result_expr)
+        self.assertCountEqual(expected_result_event, result_event)
+
+    def test_transport_conditional_counterfactual_query_7(self):
+        """Seventh test of Algorithm 3 of [correa22a], transporting a conditional counterfactual query.
+
+        This test checks that if a counterfactual factor can't be transported, the algorithm
+        returns None.
+
+        Source: RJC's mind.
+        """
+        target_domain_graph = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (Z, Y),
+                (X, Y),
+                (X, W),
+                (W, Y),
+                (Y, R),
+            ],
+            undirected=[(Z, X), (W, Y)],
+        )
+        graph_1 = NxMixedGraph.from_edges(
+            directed=[(Z, X), (X, Y), (Z, Y), (W, Y), (transport_variable(Y), Y), (Y, R)],
+            undirected=[
+                (Z, X),
+            ],
+        )
+        graph_1_topo = list(graph_1.topological_sort())
+        # Let's say graph 2 is a stochastic intervention where Y is a function of W and Z,
+        # but not X.
+        graph_2 = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (X, W),
+                (Z, Y),
+                (W, Y),
+                (Y, R),
+            ],
+            undirected=[],
+        )
+        graph_2_topo = list(graph_2.topological_sort())
+        domain_data = [(set(), PP[Pi1](W, X, Y, Z, R)), ({Y}, PP[Pi2](W, X, Y, Z, R))]
+        query_result = transport_conditional_counterfactual_query(
+            outcomes=[(Y @ -X, -Y), (X, -X)],
+            conditions=[(R, -R)],
+            target_domain_graph=target_domain_graph,
+            domain_graphs=[(graph_1, graph_1_topo), (graph_2, graph_2_topo)],
+            domain_data=domain_data,
+        )
+        self.assertIsNone(query_result)
+
+    def test_transport_conditional_counterfactual_query_8(self):
+        """Eigth test of Algorithm 3 of [correa22a], transporting a conditional counterfactual query.
+
+        This tests whether we properly return (Zero(), None) if a query, when simplified,
+        returns a probability of zero.
+
+        Source: Example 4.2 from [correa22]_, modified to make the simplification fail.
+        """
+        # 1. We need a test case that returns a probability of Zero() if the Simplify() algorithm
+        #   returns a probability 0 during transport_unconditional_counterfactual_query().
+        outcomes = [(Y @ -Y, -Y), (Y, +Y)]
+        conditions = [(R, -R)]
+        # event = [(Y @ -X, -Y), (X, -X)]
+        target_domain_graph = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (Z, Y),
+                (X, Y),
+                (X, W),
+                (W, Y),
+                (Y, R),
+            ],
+            undirected=[(Z, X), (W, Y)],
+        )
+        # From [correa22a]_, Figures 3a and 4, with an extra vertex R that is a descendent of Y.
+        graph_1 = NxMixedGraph.from_edges(
+            directed=[(X, Y), (X, W), (W, Y), (Z, Y), (transport_variable(Z), Z), (Y, R)],
+            undirected=[
+                (W, Y),
+            ],
+        )
+        graph_1_topo = list(graph_1.topological_sort())
+
+        # From [correa22a]_, Figure 3b, with an extra vertex R that is a descendent of Y.
+        graph_2 = NxMixedGraph.from_edges(
+            directed=[
+                (Z, X),
+                (Z, Y),
+                (X, Y),
+                (X, W),
+                (W, Y),
+                (transport_variable(W), W),
+                (Y, R),
+            ],
+            undirected=[(Z, X), (W, Y)],
+        )
+        graph_2_topo = list(graph_2.topological_sort())
+        domain_graphs = [
+            (
+                graph_1,
+                graph_1_topo,
+            ),
+            (
+                graph_2,
+                graph_2_topo,
+            ),
+        ]
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z, R)), (set(), PP[Pi2](W, X, Y, Z, R))]
+        result_expr, result_event = transport_conditional_counterfactual_query(
+            outcomes=outcomes,
+            conditions=conditions,
+            target_domain_graph=target_domain_graph,
+            domain_graphs=domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assert_expr_equal(result_expr, Zero())
+        self.assertIsNone(result_event)
+
+    def test_transport_conditional_counterfactual_query_preprocessing(self):
+        """Tests of input validation for transport_conditional_counterfactual_query() [correa22a].
+
+        Here are all the checks (numbering is just based on convenience during implementation, and
+        the numbered order is not necessarily the order of implementation):
+        1. Type checking for outcomes and conditions
+        2. Type checking for target_domain_graph
+        3. Type checking for domain_graphs
+        4. Type checking for domain_data
+        4.5. Make sure probabilistic expressions in domain_data aren't Zero() or One()
+        5. Make sure conditions and outcomes aren't empty
+        6. (Skipped for the conditional transportability algorithm, included for unconditional
+        transportability) Make sure at least one event element has a non-None value
+        7. Check domain_graphs and domain_data aren't empty lists
+        8. Check all graphs in domain_graphs have nodes
+        9. Check all topologically sorted lists have entries
+        9.2. Check that the target domain graph contains no transportability nodes and is a directed acyclic graph
+        9.5. Check that the domain_graphs and domain_data list lengths are equal
+        9.7. Check that every domain graph is a directed acyclic graph
+        10. Check that every topological order list in domain_graphs is a valid topological order,
+            given the corresponding graph
+        11. Check the domain graph vertices are all the same as the target domain graph vertices
+        12. Check the event vertices are in the target domain graph (given check #11, that
+            means they're in every graph)
+        13. Check the conditioned and outcome variables have the same base variable
+            as the base variable of their corresponding values
+        14. Domain graphs: make sure the vertex set of the topologically sorted vertex order matches
+            the set of vertices in each corresponding domain graph
+        15. It's possible for a graph probability expression to contain vertices not in the graph
+            due to conditioning on vertices outside the graph. But the graph vertices must all be
+            represented in that graph probability expression.
+        16. If the target domain graph is also in the domain_graphs list (i.e., data were collected for
+            the target domain), then the target domain graph in the domain_graphs list must be
+            identical to the target_domain_graph parameter.
+        17. Make sure the conditioned variable and outcome variable sets don't share graph vertices
+
+        Source: RJC.
+        """
+        # 1. Type check the outcomes
+        example_1_domain_data = [(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({X}, PP[Pi1](X, Y, Z))]
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=1,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=[1],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=1,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=[1],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=[(Y @ -X, -Y, None)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=[(Y @ -X, -Y, None)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=[(1, -Y)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=[(Y @ -X, None)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=[(1, -Y)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=[(Y @ -X, None)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        # 1. Type check the conditions
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=1,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=[1],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=1,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=[1],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (5, +X), None],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (5, +X), None],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (5, +X)],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (5, +X)],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (X, None)],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=[(Z @ -X, -Z), (X, None)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        # Check we have no empty inputs, for inputs unique to Algorithm 3
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=[],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=[],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=[],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=[],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=NxMixedGraph(),
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=NxMixedGraph(),
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        # Type checking for inputs consistent with both Algorithms 2 and 3
+        # 2.
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=1,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=1,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=1,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=1,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[None],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[1],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+                (
+                    1,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+                (
+                    1,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    1,
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    1,
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    [1],
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    [1],
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        # 4.
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=1,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=1,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[1],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[1],
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(None, PP[TARGET_DOMAIN](X, Y, Z)), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[({1}, PP[TARGET_DOMAIN](X, Y, Z)), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), 1), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), 1), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        # 4.5.
+        self.assertRaises(
+            NotImplementedError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), Zero()), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            NotImplementedError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), Zero()), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            NotImplementedError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), One()), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            NotImplementedError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), One()), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        # 17.
+        # self.assertRaises(
+        #    NotImplementedError,
+        #    _validate_transport_conditional_counterfactual_query_input,
+        #    outcomes=[(Y @ -X, -Y)],
+        #    conditions=[(Y @ -Z, -Y), (X, +X)],
+        #    target_domain_graph=self.example_1_target_domain_graph,
+        #    domain_graphs=self.example_1_domain_graphs,
+        #    domain_data=[(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({X}, PP[Pi1](X, Y, Z))],
+        # )
+        # self.assertRaises(
+        #    NotImplementedError,
+        #    transport_conditional_counterfactual_query,
+        #    outcomes=[(Y @ -X, -Y)],
+        #    conditions=[(Y @ -Z, -Y), (X, +X)],
+        #    target_domain_graph=self.example_1_target_domain_graph,
+        #    domain_graphs=self.example_1_domain_graphs,
+        #    domain_data=[(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({X}, PP[Pi1](X, Y, Z))],
+        # )
+        # 7.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[],
+            domain_data=[(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[],
+            domain_data=[(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[],
+        )
+        # 8.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+                (
+                    NxMixedGraph(),
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+                (
+                    NxMixedGraph(),
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        # 9.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    [],
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    [],
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        # 9.5.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        # 9.2.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=figure_1_graph_domain_1_with_interventions,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=figure_1_graph_domain_1_with_interventions,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=figure_1_graph_domain_1_with_interventions,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=figure_1_graph_domain_1_with_interventions,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (X, Z),
+                    (Z, Y),
+                    (Y, X),
+                ],
+            ),
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (X, Z),
+                    (Z, Y),
+                    (Y, X),
+                ],
+            ),
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        # 11.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (X, Z),
+                    (Z, Y),
+                    (X, R),
+                ],
+            ),
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (X, Z),
+                    (Z, Y),
+                    (X, R),
+                ],
+            ),
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[(X, Z), (X, Y), (R, Y)],
+                        undirected=[],
+                    ),
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    figure_1_graph_no_transportability_nodes,
+                    figure_1_graph_no_transportability_nodes_topo,
+                ),
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[(X, Z), (X, Y), (R, Y)],
+                        undirected=[],
+                    ),
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        # 12.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (R, -R)],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (R, -R)],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=[(R @ -X, -R)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=[(R @ -X, -R)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        # 13.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (X, -Z)],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=[(Z @ -X, -Z), (X, -Z)],
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=[(Y @ -X, -R)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=[(Y @ -X, -Z)],
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        # 14.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (X, Z),
+                            (Z, Y),
+                            (X, Y),
+                        ],
+                        undirected=[(Z, X)],
+                    ),
+                    [X, Z, Y, R],
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (X, Z),
+                            (Z, Y),
+                            (X, Y),
+                        ],
+                        undirected=[(Z, X)],
+                    ),
+                    [X, Z, Y, R],
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        # 15.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), PP[TARGET_DOMAIN](X, Y)), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), PP[TARGET_DOMAIN](X, Y)), ({X}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({X, R}, PP[Pi1](X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=[(set(), PP[TARGET_DOMAIN](X, Y, Z)), ({X, R}, PP[Pi1](X, Y, Z))],
+        )
+        # 10.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (X, Z),
+                            (Z, Y),
+                            (X, Y),
+                        ],
+                        undirected=[(Z, X)],
+                    ),
+                    [Y, Z, X],
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (X, Z),
+                            (Z, Y),
+                            (X, Y),
+                        ],
+                        undirected=[(Z, X)],
+                    ),
+                    [Y, Z, X],
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        # 16.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (X, Z),
+                    (Z, Y),
+                    (X, Y),
+                ],
+                undirected=[],
+            ),
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (X, Z),
+                    (Z, Y),
+                    (X, Y),
+                ],
+                undirected=[],
+            ),
+            domain_graphs=self.example_1_domain_graphs,
+            domain_data=example_1_domain_data,
+        )
+        # 9.7.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_conditional_counterfactual_query_input,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (X, Z),
+                            (Z, Y),
+                            (Y, X),
+                        ],  # not a directed acyclic graph
+                        undirected=[(Z, X)],
+                    ),
+                    [X, Z, Y],
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_conditional_counterfactual_query,
+            outcomes=self.example_1_outcomes,
+            conditions=self.example_1_conditions,
+            target_domain_graph=self.example_1_target_domain_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (X, Z),
+                            (Z, Y),
+                            (Y, X),
+                        ],  # not a directed acyclic graph
+                        undirected=[(Z, X)],
+                    ),
+                    [X, Z, Y],
+                ),
+                (
+                    figure_1_graph_domain_1_with_interventions,
+                    figure_1_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=example_1_domain_data,
+        )
+
+
+class TestTransportConditionalCounterfactualQueryUtils(cases.GraphTestCase):
+    """Test utility functions to transport a conditional counterfactual query."""
+
+    def test_valid_topo_list(self):
+        r"""Test the valid_topo_list() utility function.
+
+        Source: RC's mind.
+        """
+        graph = NxMixedGraph.from_edges(
+            directed=[
+                (X1, Z),
+                (X2, Z),
+                (X2, W),
+                (W, Y),
+                (Z, Y),
+            ],
+        )
+        topo = list(graph.topological_sort())
+        self.assertTrue(_valid_topo_list([X1, X2, Z, W, Y], graph))
+        self.assertTrue(_valid_topo_list(topo, graph))
+        self.assertFalse(_valid_topo_list([X1, Z, X2, W, Y], graph))
+        self.assertTrue(_valid_topo_list([X2, W, X1, Z, Y], graph))
+
+
+class TestTransportUnconditionalCounterfactualQueryPreprocessing(cases.GraphTestCase):
+    """Test input validation for [correa22a]_'s unconditional counterfactual transportability algorithm."""
+
+    event = [(Y @ -X, -Y), (X, -X)]
+    domain_graphs = [
+        (
+            figure_2_graph_domain_1_with_interventions,
+            figure_2_graph_domain_1_with_interventions_topo,
+        ),
+        (
+            figure_2_graph_domain_2,
+            figure_2_graph_domain_2_topo,
+        ),
+    ]
+
+    def test_unconditional_counterfactual_query_preprocessing(self):
+        """Tests of input validation for transport_conditional_counterfactual_query() [correa22a].
+
+        Here are all the checks (numbering is just based on convenience during implementation, and
+        the numbered order is not necessarily the order of implementation):
+        1. Type checking for the input event
+        2. Type checking for target_domain_graph
+        3. Type checking for domain_graphs
+        4. Type checking for domain_data
+        4.5. Make sure probabilistic expressions in domain_data aren't Zero() or One()
+        5. Make sure the input event isn't empty
+        6. Make sure at least one event element has a non-None value
+        7. Check domain_graphs and domain_data aren't empty lists
+        8. Check all graphs in domain_graphs have nodes
+        9. Check all topologically sorted lists have entries
+        9.2. Check that the target domain graph contains no transportability nodes and is a directed acyclic graph
+        9.5. Check that the domain_graphs and domain_data list lengths are equal
+        9.7. Check that every domain graph is a directed acyclic graph
+        10. Check that every topological order list in domain_graphs is a valid topological order,
+            given the corresponding graph
+        11. Check the domain graph vertices are all the same as the target domain graph vertices
+        12. Check the event vertices are in the target domain graph (given check #11, that
+            means they're in every graph)
+        13. Check the event variables have the same base variable as the base variable of their
+            corresponding values, if those values aren't None
+        14. Domain graphs: make sure the vertex set of the topologically sorted vertex order matches
+            the set of vertices in each corresponding domain graph
+        15. It's possible for a graph probability expression to contain vertices not in the graph
+            due to conditioning on vertices outside the graph. But the graph vertices must all be
+            represented in that graph probability expression
+        15.5. Make sure policy vertices are in the target domain graph
+        16. If the target domain graph is also in the domain_graphs list (i.e., data were collected for
+            the target domain), then the target domain graph in the domain_graphs list must be
+            identical to the target_domain_graph parameter
+
+        Source: RJC.
+        """
+        # 1. Type check the outcomes
+        domain_data = [({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))]
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=1,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=1,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[1],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=[1],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[(Y @ -X, -Y), (X, -X), None],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=[(Y @ -X, -Y), (X, -X), None],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[(Y @ -X, -Y, None), (X, -X)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=[(Y @ -X, -Y, None), (X, -X)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[(1, -Y), (X, -X)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=[(1, -Y), (X, -X)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[(Y @ -X, 1), (X, -X)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=[(Y @ -X, 1), (X, -X)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        # None values for event interventions are allowed for Algorithm 2 but not Algorithm 3.
+        # So the next two (commented out) tests fail and are supposed to fail.
+        # self.assertRaises(
+        #    TypeError,
+        #    _validate_transport_unconditional_counterfactual_query_input,
+        #    event=[(Y @ -X, None), (X, -X)],
+        #    target_domain_graph=figure_2a_graph,
+        #    domain_graphs=self.domain_graphs,
+        #    domain_data=domain_data,
+        # )
+        # self.assertRaises(
+        #    TypeError,
+        #    transport_unconditional_counterfactual_query,
+        #    event=[(Y @ -X, None), (X, -X)],
+        #    target_domain_graph=figure_2a_graph,
+        #    domain_graphs=self.domain_graphs,
+        #    domain_data=domain_data,
+        # )
+        # 5.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=[],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        # 2.
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=1,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=None,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        # 3.
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=1,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=1,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[1],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[None],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    1,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    1,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    1,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    1,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    [1],
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    [1],
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=1,
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=1,
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[1],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[1],
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[(None, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[(1, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({1}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({1}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, 1), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            TypeError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, 1), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            NotImplementedError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, Zero()), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            NotImplementedError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, Zero()), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            NotImplementedError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, One()), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            NotImplementedError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, One()), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        # 6. At least one event element has a non-None value
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[(Y @ -X, None), (X, None)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=[(Y @ -X, None), (X, None)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        # 7.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[],
+        )
+        # 8.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=NxMixedGraph(),
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=NxMixedGraph(),
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    NxMixedGraph(),
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    NxMixedGraph(),
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # 9.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    [],
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    [],
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # 9.5.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    figure_2_graph_domain_1_with_interventions,
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # 9.2
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2_graph_domain_2,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2_graph_domain_2,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (Z, X),
+                    (Y, Z),
+                    (X, Y),
+                    (X, W),
+                    (W, Y),
+                ],
+                undirected=[(Z, X), (W, Y)],
+            ),
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (Z, X),
+                    (Y, Z),
+                    (X, Y),
+                    (X, W),
+                    (W, Y),
+                ],
+                undirected=[(Z, X), (W, Y)],
+            ),
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        # 11.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (Z, X),
+                    (Z, R),
+                    (X, Y),
+                    (X, W),
+                    (W, Y),
+                ],
+                undirected=[(Z, X), (W, Y)],
+            ),
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=NxMixedGraph.from_edges(
+                directed=[
+                    (Z, X),
+                    (Z, R),
+                    (X, Y),
+                    (X, W),
+                    (W, Y),
+                ],
+                undirected=[(Z, X), (W, Y)],
+            ),
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[(X, Y), (X, W), (W, R), (Z, Y), (transport_variable(Z), Z)],
+                        undirected=[
+                            (W, Y),
+                        ],
+                    ),
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[(X, Y), (X, W), (W, R), (Z, Y), (transport_variable(Z), Z)],
+                        undirected=[
+                            (W, Y),
+                        ],
+                    ),
+                    figure_2_graph_domain_1_with_interventions_topo,
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # 12.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[(Y @ -X, -Y), (R, -R)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=[(Y @ -X, -Y), (R, -R)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        # 13.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=[(Y @ -X, -Y), (X, -R)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=[(Y @ -X, -Y), (X, -R)],
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=domain_data,
+        )
+        # 14.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[(X, Y), (X, W), (W, Y), (Z, Y), (transport_variable(Z), Z)],
+                        undirected=[
+                            (W, Y),
+                        ],
+                    ),
+                    [Z, X, W, R],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[(X, Y), (X, W), (W, Y), (Z, Y), (transport_variable(Z), Z)],
+                        undirected=[
+                            (W, Y),
+                        ],
+                    ),
+                    [Z, X, W, R],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # 15.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, PP[Pi1](W, X, Y)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, PP[Pi1](W, X, Y)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        # 15.5.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, PP[Pi1](W, X, Y, Z)), ({R}, PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=self.domain_graphs,
+            domain_data=[({X}, PP[Pi1](W, X, Y, Z)), ({R}, PP[Pi2](W, X, Y, Z))],
+        )
+        # 10.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (X, Y),
+                            (X, W),
+                            (W, Y),
+                            (Z, Y),
+                            (Z, X),
+                            (transport_variable(Z), Z),
+                        ],
+                        undirected=[
+                            (W, Y),
+                        ],
+                    ),
+                    [transport_variable(Z), X, Z, W, Y],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (X, Y),
+                            (X, W),
+                            (W, Y),
+                            (Z, Y),
+                            (Z, X),
+                            (transport_variable(Z), Z),
+                        ],
+                        undirected=[
+                            (W, Y),
+                        ],
+                    ),
+                    [transport_variable(Z), X, Z, W, Y],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=domain_data,
+        )
+        # 16.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (Z, X),
+                            (Z, Y),
+                            (X, Y),
+                            (X, W),
+                            (W, Y),
+                        ],
+                        undirected=[(Z, X)],  # Missing an undirected edge
+                    ),
+                    [Z, X, W, Y],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=[({X}, PP[TARGET_DOMAIN](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (Z, X),
+                            (Z, Y),
+                            (X, Y),
+                            (X, W),
+                            (W, Y),
+                        ],
+                        undirected=[(Z, X)],  # Missing an undirected edge
+                    ),
+                    [Z, X, W, Y],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=[({X}, PP[TARGET_DOMAIN](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        # 9.7.
+        self.assertRaises(
+            ValueError,
+            _validate_transport_unconditional_counterfactual_query_input,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (Z, X),
+                            (Y, Z),
+                            (X, Y),
+                            (X, W),
+                            (W, Y),
+                        ],  # Not a DAG
+                        undirected=[(Z, X), (W, Y)],
+                    ),
+                    [Z, X, W, Y],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=[({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+        self.assertRaises(
+            ValueError,
+            transport_unconditional_counterfactual_query,
+            event=self.event,
+            target_domain_graph=figure_2a_graph,
+            domain_graphs=[
+                (
+                    NxMixedGraph.from_edges(
+                        directed=[
+                            (Z, X),
+                            (Y, Z),
+                            (X, Y),
+                            (X, W),
+                            (W, Y),
+                        ],  # Not a DAG
+                        undirected=[(Z, X)],
+                    ),
+                    [Z, X, W, Y],
+                ),
+                (
+                    figure_2_graph_domain_2,
+                    figure_2_graph_domain_2_topo,
+                ),
+            ],
+            domain_data=[({X}, PP[Pi1](W, X, Y, Z)), (set(), PP[Pi2](W, X, Y, Z))],
+        )
+
+
+class TestGetCounterfactualFactorsRetainingVariableValues(cases.GraphTestCase):
+    """Test function to retrieve counterfactual factors from a graph."""
+
+    def test_get_counterfactual_factors_retaining_variable_values(self):
+        r"""Test a function to retrieve counterfactual factors, retaining variable values.
+
+        Source: RC's mind.
+        """
+        event = [(Y @ -X, -Y), (X @ -Z, -X)]
+        self.assertRaises(
+            ValueError,
+            get_counterfactual_factors_retaining_variable_values,
+            event=event,
+            graph=figure_2a_graph,
+        )
+        # The next test is expected to fail and it does
+        event_2 = [(Y @ -X @ -W @ -Z, -Y), (X @ -Z, -X)]
+        expected_result = [{(Y @ -X @ -W @ -Z, -Y)}, {(X @ -Z, -X)}]
+        # expected_result[frozenset({W, Y})] = {(Y @ -X @ -W @ -Z, -Y)}
+        # expected_result[frozenset({X, Z})] = {(X @ -Z, -X)}
+        self.assertCountEqual(
+            expected_result,
+            get_counterfactual_factors_retaining_variable_values(
+                event=event_2, graph=figure_2a_graph
+            ),
+        )
+
+    # district_mappings: DefaultDict[
+    #    frozenset[Variable], set[tuple[Variable, Intervention | None]]
+    # ] = defaultdict(set)
+    # for variable, value in event:
+    #    district_mappings[graph.get_district(variable.get_base())].add((variable, value))
+    # self.assertRaises(
+    #   ValueError,
+    #   get_counterfactual_factors_retaining_variable_values,
+    #   event=event_2,
+    #   graph=figure_2a_graph
+    # )
+
+
+class TestMergeFrozenSetsWithCommonElements(cases.GraphTestCase):
+    """Utility function to merge frozen sets that have common elements."""
+
+    def test_merge_frozen_sets_with_common_vertices(self):
+        r"""Test a helper function to merge ancestral components based on common vertices.
+
+        Source: RC's mind.
+        """
+        test_1_inputs = {
+            frozenset([W, X]),
+            frozenset([X, Y]),
+            frozenset([W, Z]),
+            frozenset([X1, X2]),
+            frozenset([X1, R, W1]),
+        }
+        result = _merge_frozen_sets_with_common_vertices(test_1_inputs)
+        expected_result = {frozenset([W, X, Y, Z]), frozenset([R, W1, X1, X2])}
+        self.assertSetEqual(result, expected_result)
+        test_2_inputs = {
+            frozenset([W]),
+            frozenset(),
+            frozenset([X]),
+            frozenset([W, Y]),
+            frozenset([Z, R]),
+            frozenset([W1]),
+        }
+        result_2 = _merge_frozen_sets_with_common_vertices(test_2_inputs)
+        expected_result_2 = {frozenset([W, Y]), frozenset([X]), frozenset([W1]), frozenset([R, Z])}
+        self.assertSetEqual(result_2, expected_result_2)
+
+    def test_merge_frozen_sets_linked_by_bidirectional_edges(self):
+        r"""Test a helper function to merge ancestral components linked by bidirectional edges.
+
+        Source: RC's mind.
+        """
+        graph_1 = NxMixedGraph.from_edges(
+            directed=[],  # Not a DAG
+            undirected=[(W, Y), (W, X), (W1, R)],
+        )
+        test_1_inputs = {frozenset([W, Y]), frozenset([X]), frozenset([W1]), frozenset([R, Z])}
+        result = _merge_frozen_sets_linked_by_bidirectional_edges(
+            input_sets=test_1_inputs, graph=graph_1
+        )
+        expected_result = {frozenset([W, X, Y]), frozenset([R, W1, Z])}
+        self.assertSetEqual(result, expected_result)
+        graph_2 = NxMixedGraph.from_edges(
+            directed=[],
+            undirected=[],
+        )
+        test_2_inputs = {frozenset([X]), frozenset([W, Y]), frozenset([Z, R]), frozenset([W1])}
+        result_2 = _merge_frozen_sets_linked_by_bidirectional_edges(
+            input_sets=test_2_inputs, graph=graph_2
+        )
+        expected_result_2 = {frozenset([W, Y]), frozenset([X]), frozenset([W1]), frozenset([R, Z])}
+        logger.warning(str(expected_result_2))
+        logger.warning(str(result_2))
+        self.assertSetEqual(result_2, expected_result_2)
+        graph_3 = NxMixedGraph.from_edges(directed=[], undirected=[(W, X), (X, Y), (R, Z), (W1, Y)])
+        result_3 = _merge_frozen_sets_linked_by_bidirectional_edges(
+            input_sets=test_2_inputs, graph=graph_3
+        )
+        expected_result_3 = {frozenset([W, Y, X, W1]), frozenset([R, Z])}
+        self.assertSetEqual(result_3, expected_result_3)
+
+    def test_convert_counterfactual_variables_to_base_variables(self):
+        r"""Test a helper function to merge ancestral components linked by bidirectional edges.
+
+        Source: RC's mind.
+        """
+        test_1_input = frozenset([X])
+        result_1 = get_base_variables(test_1_input)
+        self.assertSetEqual(result_1, frozenset([X]))
+        test_2_input = frozenset([X @ -Y])
+        result_2 = get_base_variables(test_2_input)
+        self.assertSetEqual(result_2, frozenset([X]))
+        test_3_input = frozenset([])
+        self.assertSetEqual(get_base_variables(test_3_input), frozenset([]))
diff --git a/tests/test_algorithm/test_tian_pearl_identify.py b/tests/test_algorithm/test_tian_pearl_identify.py
new file mode 100644
index 0000000..9b20988
--- /dev/null
+++ b/tests/test_algorithm/test_tian_pearl_identify.py
@@ -0,0 +1,841 @@
+# -*- coding: utf-8 -*-
+
+"""Tests for Tian and Pearl's Identify algorithm.
+
+.. [huang08a] https://link.springer.com/article/10.1007/s10472-008-9101-x.
+.. [correa20a] https://proceedings.neurips.cc/paper/2020/file/7b497aa1b2a83ec63d1777a88676b0c2-Paper.pdf.
+.. [correa22a] https://proceedings.mlr.press/v162/correa22a/correa22a.pdf.
+.. [tikka20a] https://github.com/santikka/causaleffect/blob/master/R/compute.c.factor.R.
+.. [tikka20b] https://github.com/santikka/causaleffect/blob/master/R/identify.R.
+.. [tian03a] https://ftp.cs.ucla.edu/pub/stat_ser/R290-L.pdf.
+"""
+
+import logging
+
+from tests.test_algorithm import cases
+from y0.algorithm.tian_id import (
+    compute_ancestral_set_q_value,
+    compute_c_factor,
+    compute_c_factor_conditioning_on_topological_predecessors,
+    compute_c_factor_marginalizing_over_topological_successors,
+    compute_q_value_of_variables_with_low_topological_ordering_indices,
+    identify_district_variables,
+)
+from y0.algorithm.transport import transport_variable
+from y0.dsl import (
+    PP,
+    TARGET_DOMAIN,
+    W1,
+    W2,
+    W3,
+    W4,
+    W5,
+    X1,
+    X2,
+    Fraction,
+    One,
+    P,
+    Pi1,
+    Pi2,
+    Product,
+    R,
+    Sum,
+    W,
+    X,
+    Y,
+    Z,
+    Zero,
+)
+from y0.graph import NxMixedGraph
+
+# From [correa22a]_, Figure 2a.
+figure_2a_graph = NxMixedGraph.from_edges(
+    directed=[
+        (Z, X),
+        (Z, Y),
+        (X, Y),
+        (X, W),
+        (W, Y),
+    ],
+    undirected=[(Z, X), (W, Y)],
+)
+
+# From [correa20a]_, Figure 1b.
+soft_interventions_figure_1b_graph = NxMixedGraph.from_edges(
+    directed=[
+        (X1, Z),
+        (X1, X2),
+        (X1, Y),
+        (X2, Y),
+        (transport_variable(Y), Y),
+    ],
+    undirected=[],
+)
+
+# From [correa20a]_, Figure 2a.
+soft_interventions_figure_2a_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, Z),
+        (W, X),
+        (X, Z),
+        (Z, Y),
+    ],
+    undirected=[
+        (R, Y),
+        (W, R),
+        (W, X),
+        (W, Z),
+        (X, Y),
+    ],
+)
+
+# From [correa20a]_, Figure 2d.
+soft_interventions_figure_2d_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, Z),
+        (X, Z),
+        (W, X),
+        (Z, Y),
+        (transport_variable(W), W),
+    ],
+    undirected=[
+        (R, Y),
+        (W, R),
+        (W, X),
+        (X, Y),
+    ],
+)
+
+# From [correa20a]_, Figure 3, and corresponding to pi* with the intervention sigma* not applied.
+soft_interventions_figure_3_graph = NxMixedGraph.from_edges(
+    directed=[
+        (R, W),
+        (W, X),
+        (X, Z),
+        (Z, Y),
+        (X, Y),
+    ],
+    undirected=[
+        (R, Z),
+        (W, Y),
+        (W, X),
+        (R, X),
+    ],
+)
+
+tian_pearl_figure_9a_graph = NxMixedGraph.from_edges(
+    directed=[
+        (W1, W2),
+        (W2, X),
+        (W3, W4),
+        (W4, X),
+        (X, Y),
+    ],
+    undirected=[
+        (W1, W3),
+        (W3, W5),
+        (W4, W5),
+        (W2, W3),
+        (W1, X),
+        (W1, Y),
+    ],
+)
+
+
+logger = logging.getLogger(__name__)
+
+
+class TestIdentify(cases.GraphTestCase):
+    """Test the IDENTIFY algorithm (Algorithm 5 of [correa22a]_).
+
+    Source: The example on page 7 of [correa20a]_ (using Figure 1d).
+    Note that [correa20a]_ and [correa22a]_ use the same specification of the IDENTIFY
+    algorithm. Both papers have a typo on Line 4. See [huang08a]_ for the correct
+    version of Line 4.
+    """
+
+    def test_identify_preprocessing(self):
+        """Test the preprocessing checks in this implementation of IDENTIFY."""
+        # Raises a TypeError because the graph has only one C-component: {R,X,W,Z,Y} and our input district T
+        # is a subset and therefore not a C-component.
+        self.assertRaises(
+            KeyError,
+            identify_district_variables,
+            input_variables=frozenset({Y, R}),
+            input_district=frozenset({R, X, W, Z}),
+            district_probability=PP[TARGET_DOMAIN](R, W, X, Y, Z),
+            graph=soft_interventions_figure_3_graph,
+            topo=list(soft_interventions_figure_3_graph.topological_sort()),
+        )
+        # Raises a KeyError because a variable in T is not in the topo.
+        self.assertRaises(
+            KeyError,
+            identify_district_variables,
+            input_variables=frozenset({X, Z}),
+            input_district=frozenset({R, X, W, Z}),
+            district_probability=PP[TARGET_DOMAIN](R, W, X, Y, Z),
+            graph=soft_interventions_figure_3_graph,
+            topo=[W, X, Z, Y],
+        )
+        # Raises a TypeError because G_{X,Y,Z} has two districts and there should be at most one.
+        # {X,Y} happen to be in two different districts of G_{X,Y,Z}.
+        self.assertRaises(
+            TypeError,
+            identify_district_variables,
+            input_variables=frozenset({X, Y}),
+            input_district=frozenset({X, Y, Z}),
+            district_probability=PP[TARGET_DOMAIN](R, W, X, Y, Z),
+            graph=soft_interventions_figure_3_graph,
+            topo=list(soft_interventions_figure_3_graph.topological_sort()),
+        )
+        # Raises a TypeError because the input district probability has an unrecognized format.
+        test_4_identify_input_variables = {Z}  # A
+        test_4_identify_input_district = {Z}  # B
+        # test_4_district_probability = PP[Population("pi1")](Z | X1)  # Q
+        self.assertRaises(
+            TypeError,
+            identify_district_variables,
+            input_variables=frozenset(test_4_identify_input_variables),
+            input_district=frozenset(test_4_identify_input_district),
+            district_probability=[],
+            graph=soft_interventions_figure_1b_graph,
+            topo=list(soft_interventions_figure_1b_graph.topological_sort()),
+        )
+        self.assertRaises(
+            TypeError,
+            identify_district_variables,
+            input_variables=frozenset(test_4_identify_input_variables),
+            input_district=frozenset(test_4_identify_input_district),
+            district_probability=One(),
+            graph=soft_interventions_figure_1b_graph,
+            topo=list(soft_interventions_figure_1b_graph.topological_sort()),
+        )
+        self.assertRaises(
+            TypeError,
+            identify_district_variables,
+            input_variables=frozenset(test_4_identify_input_variables),
+            input_district=frozenset(test_4_identify_input_district),
+            district_probability=Zero(),
+            graph=soft_interventions_figure_1b_graph,
+            topo=list(soft_interventions_figure_1b_graph.topological_sort()),
+        )
+
+    def test_identify_1(self):
+        """Test Line 2 of Algorithm 5 of [correa22a]_.
+
+        This tests the case where A == C.
+        """
+        # π_star = Pi_star = Variable(f"π*")
+        test_1_identify_input_variables = {Z}  # A
+        test_1_identify_input_district = {Z}  # B
+
+        # @cthoyt @JZ The next two commented-out lines produce a mypy error:
+        test_1_district_probability = PP[Pi1](Z | X1)
+        # error: Type application targets a non-generic function or class  [misc]
+        # test_transport.py uses a similar syntax and does not trigger the error,
+        #   so I'm probably missing something simple.
+        # test_1_district_probability = PP[pi1](Z | X1)  # Q
+        result = identify_district_variables(
+            input_variables=frozenset(test_1_identify_input_variables),
+            input_district=frozenset(test_1_identify_input_district),
+            district_probability=test_1_district_probability,
+            graph=soft_interventions_figure_1b_graph,
+            topo=list(soft_interventions_figure_1b_graph.topological_sort()),
+        )
+        logger.warning("Result of identify() call for test_identify_1 is " + result.to_latex())
+        self.assert_expr_equal(result, PP[Pi1](Z | X1))
+
+    def test_identify_2(self):
+        """Test Line 3 of Algorithm 5 of [correa22a]_.
+
+        This tests the case where A == T.
+        Sources: a modification of the example following Theorem 2 in [correa20a]_
+        and the paragraph at the end of section 4 in [correa20a]_.
+        """
+        result1 = identify_district_variables(
+            input_variables=frozenset({R, Y}),
+            input_district=frozenset({W, R, X, Z, Y}),
+            district_probability=PP[TARGET_DOMAIN](
+                W, R, X, Z, Y
+            ),  # This is a c-factor if the input variables comprise a c-component
+            graph=soft_interventions_figure_2a_graph,
+            topo=list(soft_interventions_figure_2a_graph.topological_sort()),
+        )
+        logger.warning("Result of identify() call for test_identify_2 part 1 is " + str(result1))
+        self.assertIsNone(result1)
+        result2 = identify_district_variables(
+            input_variables=frozenset({Z, R}),
+            input_district=frozenset({R, X, W, Z}),
+            district_probability=PP[TARGET_DOMAIN](R, W, X, Z),
+            graph=soft_interventions_figure_3_graph.subgraph(vertices={R, Z, X, W}),
+            topo=list(soft_interventions_figure_3_graph.topological_sort()),
+        )
+        self.assertIsNone(result2)
+
+    def test_identify_3(self):
+        """Test Lines 4-7 of Algorithm 5 of [correa22a]_.
+
+        Source: the example in section 4 of [correa20a]_, which returns FAIL (i.e., None).
+        """
+        test_3_identify_input_variables = {R, X}
+        test_3_identify_input_district = {R, X, W, Y}
+        test_3_district_probability = PP[Pi1]((Y, W)).conditional([R, X, Z]) * PP[Pi1](R, X)
+        result1 = identify_district_variables(
+            input_variables=frozenset(test_3_identify_input_variables),
+            input_district=frozenset(test_3_identify_input_district),
+            district_probability=test_3_district_probability,
+            graph=soft_interventions_figure_2d_graph,
+            topo=list(soft_interventions_figure_2d_graph.topological_sort()),
+        )
+        logger.warning("Result of identify() call for test_identify_3 is " + str(result1))
+        self.assertIsNone(result1)
+        result2 = identify_district_variables(
+            input_variables=frozenset({Z, R}),
+            input_district=frozenset({R, X, W, Y, Z}),
+            district_probability=PP[TARGET_DOMAIN](R, W, X, Y, Z),
+            graph=soft_interventions_figure_3_graph,
+            topo=list(soft_interventions_figure_3_graph.topological_sort()),
+        )
+        logger.warning("Result of identify() call for test_identify_3 is " + str(result2))
+        self.assertIsNone(result2)
+
+    def test_identify_4(self):
+        """Further test Lines 4-7 of Algorithm 5 of [correa22a]_.
+
+        Source: the example from page 29 of [tian03a]_.
+
+        Note: Tian and Pearl provide a simpler result that is due to using probabilistic
+        axioms to simplify the formula. This result is what we get when running Santikka's
+        implementation of identify in their R package, Causal Effect ([tikka20b]_), and is more
+        complex in its structure but easier to code for an initial Python implementation.
+        """
+        result_piece_1 = Product.safe(
+            [
+                P(W1),
+                P(W3 | W1),
+                P(W2 | (W3, W1)),
+                P(X | (W1, W3, W2, W4)),
+                P(Y | (W1, W3, W2, W4, X)),
+            ]
+        )
+        result_piece_2_part_1 = Fraction(
+            Sum.safe(Sum.safe(result_piece_1, [W3]), [W2, X, Y]), One()
+        )  # Q[W1]/Q[\emptyset]
+        result_piece_2_part_2 = Fraction(
+            Sum.safe(Sum.safe(result_piece_1, [W3]), [Y]),
+            Sum.safe(Sum.safe(result_piece_1, [W3]), [X, Y]),
+        )  # Q[X]/Q[W2]
+        result_piece_2_part_3 = Fraction(
+            Sum.safe(result_piece_1, [W3]), Sum.safe(Sum.safe(result_piece_1, [W3]), [Y])
+        )  # Q[Y]/Q[X]
+        result_piece_2 = Product.safe(
+            [result_piece_2_part_1, result_piece_2_part_2, result_piece_2_part_3]
+        )
+        expected_result = Fraction(
+            Sum.safe(result_piece_2, [W1]),
+            Sum.safe(Sum.safe(result_piece_2, [W1]), [Y]),
+        )  # Q[X,Y]/Q[X]
+        result_4 = identify_district_variables(
+            input_variables=frozenset({Y}),
+            input_district=frozenset({X, Y, W1, W2, W3, W4, W5}),
+            district_probability=P(W1, W2, W3, W4, W5, X, Y),
+            graph=tian_pearl_figure_9a_graph,
+            topo=list(tian_pearl_figure_9a_graph.topological_sort()),
+        )
+        self.assert_expr_equal(result_4, expected_result)
+
+    def test_identify_4_with_population_probabilities(self):
+        """Further test Lines 4-7 of Algorithm 5 of [correa22a]_.
+
+        Source: the example from page 29 of [tian03a]_, requiring all probabilities
+        be specified as population probabilities.
+        """
+        result_piece_1 = Product.safe(
+            [
+                PP[Pi1](W1),
+                PP[Pi1](W3 | W1),
+                PP[Pi1](W2 | (W3, W1)),
+                PP[Pi1](X | (W1, W3, W2, W4)),
+                PP[Pi1](Y | (W1, W3, W2, W4, X)),
+            ]
+        )
+        result_piece_2_part_1 = Fraction(
+            Sum.safe(Sum.safe(result_piece_1, [W3]), [W2, X, Y]), One()
+        )  # Q[W1]/Q[\emptyset]
+        result_piece_2_part_2 = Fraction(
+            Sum.safe(Sum.safe(result_piece_1, [W3]), [Y]),
+            Sum.safe(Sum.safe(result_piece_1, [W3]), [X, Y]),
+        )  # Q[X]/Q[W2]
+        result_piece_2_part_3 = Fraction(
+            Sum.safe(result_piece_1, [W3]), Sum.safe(Sum.safe(result_piece_1, [W3]), [Y])
+        )  # Q[Y]/Q[X]
+        result_piece_2 = Product.safe(
+            [result_piece_2_part_1, result_piece_2_part_2, result_piece_2_part_3]
+        )
+        expected_result = Fraction(
+            Sum.safe(result_piece_2, [W1]),
+            Sum.safe(Sum.safe(result_piece_2, [W1]), [Y]),
+        )  # Q[X,Y]/Q[X]
+        result_4 = identify_district_variables(
+            input_variables=frozenset({Y}),
+            input_district=frozenset({X, Y, W1, W2, W3, W4, W5}),
+            district_probability=PP[Pi1](W1, W2, W3, W4, W5, X, Y),
+            graph=tian_pearl_figure_9a_graph,
+            topo=list(tian_pearl_figure_9a_graph.topological_sort()),
+        )
+        logger.warning("Result from identify_district_variables: " + result_4.to_latex())
+        logger.warning("  Expected result: " + expected_result.to_latex())
+        self.assert_expr_equal(result_4, expected_result)
+
+
+class TestComputeCFactor(cases.GraphTestCase):
+    """Test the "compute_c_factor" subroutine of Tian and Pearl's identify algorithm as implemented by [tikka20a].
+
+    This subroutine applies Lemma 1 and Lemma 4 of [tian03a]_.
+    """
+
+    expected_result_1 = Product.safe(
+        [P(W1), P(W3 | W1), P(W2 | (W3, W1)), P(X | (W1, W3, W2, W4)), P(Y | (W1, W3, W2, W4, X))]
+    )
+    expected_result_2_part_1 = Fraction(
+        Sum.safe(Sum.safe(expected_result_1, [W3]), [W2, X, Y]), One()
+    )  # Q[W1]/Q[\emptyset]
+    expected_result_2_part_2 = Fraction(
+        Sum.safe(Sum.safe(expected_result_1, [W3]), [Y]),
+        Sum.safe(Sum.safe(expected_result_1, [W3]), [X, Y]),
+    )  # Q[X]/Q[W2]
+    expected_result_2_part_3 = Fraction(
+        Sum.safe(expected_result_1, [W3]), Sum.safe(Sum.safe(expected_result_1, [W3]), [Y])
+    )  # Q[Y]/Q[X]
+    expected_result_2 = Product.safe(
+        [expected_result_2_part_1, expected_result_2_part_2, expected_result_2_part_3]
+    )
+
+    def test_compute_c_factor_1(self):
+        """First test of the compute C factor subroutine, based on the example on page 29 of [tian03a]."""
+        result_1 = compute_c_factor(
+            district=[Y, W1, W3, W2, X],
+            subgraph_variables=[X, W4, W2, W3, W1, Y],
+            subgraph_probability=P(W1, W2, W3, W4, X, Y),
+            graph_topo=list(tian_pearl_figure_9a_graph.topological_sort()),
+        )
+        self.assert_expr_equal(result_1, self.expected_result_1)
+
+    def test_compute_c_factor_2(self):
+        """Second test of the compute C factor subroutine, based on the example on page 29 of [tian03a]."""
+        result_2 = compute_c_factor(
+            district=[W1, X, Y],
+            subgraph_variables=[W1, W2, X, Y],
+            subgraph_probability=Sum.safe(self.expected_result_1, [W3]),
+            graph_topo=list(tian_pearl_figure_9a_graph.topological_sort()),
+        )
+        self.assert_expr_equal(result_2, self.expected_result_2)
+
+    def test_compute_c_factor_3(self):
+        """Third test of the compute C factor subroutine, based on the example on page 29 of [tian03a]."""
+        result_3 = compute_c_factor(
+            district=[Y],
+            subgraph_variables=[X, Y],
+            subgraph_probability=Sum.safe(self.expected_result_2, [W1]),
+            graph_topo=list(tian_pearl_figure_9a_graph.topological_sort()),
+        )
+        expected_result_3 = Fraction(
+            Sum.safe(self.expected_result_2, [W1]),
+            Sum.safe(Sum.safe(self.expected_result_2, [W1]), [Y]),
+        )  # Q[X,Y]/Q[X]
+        self.assert_expr_equal(result_3, expected_result_3)
+
+    def test_compute_c_factor_4(self):
+        """Fourth test of the Compute C Factor function.
+
+        Source: [tian03a], the example in section 4.6.
+        """
+        topo = list(tian_pearl_figure_9a_graph.topological_sort())
+        district = [W1, W3, W2, X, Y]
+        subgraph_variables = [W1, W3, W2, W4, X, Y]
+        subgraph_probability = P(W1, W3, W2, W4, X, Y)
+        expected_result_4 = (
+            P(Y | [W1, W2, W3, W4, X])
+            * P(X | [W1, W2, W3, W4])
+            * P(W2 | [W1, W3])
+            * P(W3 | W1)
+            * P(W1)
+        )
+        result_4 = compute_c_factor(
+            district=district,
+            subgraph_variables=subgraph_variables,
+            subgraph_probability=subgraph_probability,
+            graph_topo=topo,
+        )
+        self.assert_expr_equal(result_4, expected_result_4)
+        # TODO: As a test, have Q condition on variables in the C factor and see what happens
+        #       when you apply Lemma 4(ii) but especially Lemma 1.
+        # TODO: Currently when the input Q is an instance of Sum, we immediately
+        #       apply Lemma 4(ii). And that's because in theory we should never
+        #       have a sum applied to a simple probability. Make sure we can't
+        #       have Q set to something like $Sum_{W3}{W4 | W3}$.
+
+    def test_compute_c_factor_5(self):
+        """Fifth test of the Compute C Factor function.
+
+        Testing Lemma 1 as called from _compute_c_factor,
+        conditioning on a variable as part of the input Q value for the graph.
+        Source: [tian03a], the example in section 4.6.
+        """
+        topo = list(tian_pearl_figure_9a_graph.topological_sort())
+        district = [W1, W3, W2, X, Y]
+        subgraph_variables = [W1, W3, W2, W4, X, Y]
+        subgraph_probability = P(W1, W3, W2, W4, X, Y | W5)
+        expected_result_5 = (
+            P(Y | [W1, W2, W3, W4, X, W5])
+            * P(X | [W1, W2, W3, W4, W5])
+            * P(W2 | [W1, W3, W5])
+            * P(W3 | [W1, W5])
+            * P(W1 | W5)
+        )
+        result_5 = compute_c_factor(
+            district=district,
+            subgraph_variables=subgraph_variables,
+            subgraph_probability=subgraph_probability,
+            graph_topo=topo,
+        )
+        self.assert_expr_equal(result_5, expected_result_5)
+
+    def test_compute_c_factor_5_with_population_probabilities(self):
+        """Fifth test of the Compute C Factor function, using population probabilities.
+
+        Testing Lemma 1 as called from _compute_c_factor,
+        conditioning on a variable as part of the input Q value for the graph.
+        Source: [tian03a], the example in section 4.6.
+        """
+        topo = list(tian_pearl_figure_9a_graph.topological_sort())
+        district = [W1, W3, W2, X, Y]
+        subgraph_variables = [W1, W3, W2, W4, X, Y]
+        subgraph_probability = PP[Pi2](W1, W3, W2, W4, X, Y | W5)
+        expected_result_5 = (
+            PP[Pi2](Y | [W1, W2, W3, W4, X, W5])
+            * PP[Pi2](X | [W1, W2, W3, W4, W5])
+            * PP[Pi2](W2 | [W1, W3, W5])
+            * PP[Pi2](W3 | [W1, W5])
+            * PP[Pi2](W1 | W5)
+        )
+        result_5 = compute_c_factor(
+            district=district,
+            subgraph_variables=subgraph_variables,
+            subgraph_probability=subgraph_probability,
+            graph_topo=topo,
+        )
+        logger.warning(
+            "In test_compute_c_factor_5_with_population_probabilities: expected_result = "
+            + expected_result_5.to_latex()
+        )
+        logger.warning(
+            "In test_compute_c_factor_5_with_population_probabilities: result = "
+            + result_5.to_latex()
+        )
+        self.assert_expr_equal(result_5, expected_result_5)
+
+    def test_compute_c_factor_6(self):
+        """Sixth test of the Compute C Factor function.
+
+        Here we test a case in which the input probability is neither a product, sum, fraction,
+        nor a simple probability.
+        Source: derivative from [tian03a], the example in section 4.6.
+        """
+        # TODO: Discuss whether we want identify_district_variables() and _compute_c_factor()
+        # to handle expressions of type One, Zero, or QFactor.
+        topo = list(tian_pearl_figure_9a_graph.topological_sort())
+        district = [W1, W3, W2, X, Y]
+        subgraph_variables = [W1, W3, W2, W4, X, Y]
+        subgraph_probability = One()
+        self.assertRaises(
+            TypeError,
+            compute_c_factor,
+            district=district,
+            subgraph_variables=subgraph_variables,
+            subgraph_probability=subgraph_probability,
+            graph_topo=topo,
+        )
+
+
+class TestComputeCFactorConditioningOnTopologicalPredecessors(cases.GraphTestCase):
+    """Test the use of Lemma 1, part (i), of [tian03a]_ to compute a C factor."""
+
+    def test_compute_c_factor_conditioning_on_topological_predecessors_part_1(self):
+        """First test of Lemma 1, part (i) (Equation 37 in [tian03a]_.
+
+        Source: The example on p. 30 of [Tian03a]_, run initially through [tikka20a]_.
+        """
+        topo = [W1, W3, W2, W4, X, Y]
+        part_1_graph = tian_pearl_figure_9a_graph.subgraph([Y, X, W1, W2, W3, W4])
+        result_1 = compute_c_factor_conditioning_on_topological_predecessors(
+            district=[Y, W1, W3, W2, X],
+            topo=topo,
+            graph_probability=part_1_graph.joint_probability(),
+        )
+        self.assert_expr_equal(
+            result_1,
+            Product.safe(
+                [
+                    P(W1),
+                    P(W3 | W1),
+                    P(W2 | (W3, W1)),
+                    P(X | (W1, W3, W2, W4)),
+                    P(Y | (W1, W3, W2, W4, X)),
+                ]
+            ),
+        )
+        # District contains no variables
+        self.assertRaises(
+            TypeError,
+            compute_c_factor_conditioning_on_topological_predecessors,
+            district=[],
+            topo=topo,
+            graph_probability=part_1_graph.joint_probability(),
+        )
+        # District variable not in topo set
+        self.assertRaises(
+            KeyError,
+            compute_c_factor_conditioning_on_topological_predecessors,
+            district=[Y, W1, W3, W2, X, Z],
+            topo=topo,
+            graph_probability=part_1_graph.joint_probability(),
+        )
+
+    def test_compute_c_factor_conditioning_on_topological_predecessors_part_2(self):
+        """Second test of Lemma 1, part (i) (Equation 37 in [tian03a]_.
+
+        This one handles a graph_probability conditioning on variables.
+        Source: The example on p. 30 of [Tian03a]_, run initially through [tikka20a]_.
+        """
+        # working with tian_pearl_figure_9a_graph.subgraph([Y, X, W1, W2, W3, W4])
+        topo = [W1, W3, W2, W4, X, Y]
+        result_1 = compute_c_factor_conditioning_on_topological_predecessors(
+            district=[Y, W1, W3, W2, X],
+            topo=topo,
+            graph_probability=P(Y, X, W1, W2, W3, W4 | W5),
+        )
+        self.assert_expr_equal(
+            result_1,
+            Product.safe(
+                [
+                    P(W1 | W5),
+                    P(W3 | (W1, W5)),
+                    P(W2 | (W3, W1, W5)),
+                    P(X | (W1, W3, W2, W4, W5)),
+                    P(Y | (W1, W3, W2, W4, X, W5)),
+                ]
+            ),
+        )
+
+
+class TestComputeCFactorMarginalizingOverTopologicalSuccessors(cases.GraphTestCase):
+    """Test the use of Lemma 4, part (ii), of [tian03a]_ to compute a C factor."""
+
+    result_piece = Product.safe(
+        [
+            P(W1),
+            P(W3 | W1),
+            P(W2 | (W3, W1)),
+            P(X | (W1, W3, W2, W4)),
+            P(Y | (W1, W3, W2, W4, X)),
+        ]
+    )
+    expected_result_1_part_1 = Fraction(
+        Sum.safe(Sum.safe(result_piece, [W3]), [W2, X, Y]), One()
+    )  # Q[W1]/Q[\emptyset]
+    expected_result_1_part_2 = Fraction(
+        Sum.safe(Sum.safe(result_piece, [W3]), [Y]), Sum.safe(Sum.safe(result_piece, [W3]), [X, Y])
+    )  # Q[X]/Q[W2]
+    expected_result_1_part_3 = Fraction(
+        Sum.safe(result_piece, [W3]), Sum.safe(Sum.safe(result_piece, [W3]), [Y])
+    )  # Q[Y]/Q[X]
+    #
+    # A future version of Y0 could improve simplification of mathematical expressions so that
+    # a test using the version of "expected_result_1" commented out below would also pass.
+    # expected_result_1_num = Product.safe(
+    #    [
+    #        Sum.safe(Sum.safe(result_piece, [W3]), [W2, X, Y]),
+    #        Sum.safe(result_piece, [W3]),
+    #        Sum.safe(Sum.safe(result_piece, [W3]), [Y]),
+    #    ]
+    # )
+    # expected_result_1_den = Product.safe(
+    #    [
+    #        # Sum.safe(Sum.safe(result_piece, [W3]),[W1, W2, X, Y]),
+    #        One(),
+    #        Sum.safe(Sum.safe(result_piece, [W3]), [X, Y]),
+    #        Sum.safe(Sum.safe(result_piece, [W3]), [Y]),
+    #    ]
+    # )
+    # expected_result_1 = Fraction(expected_result_1_num, expected_result_1_den)
+    expected_result_1 = Product.safe(
+        [expected_result_1_part_1, expected_result_1_part_2, expected_result_1_part_3]
+    )
+    expected_result_2_num = Sum.safe(expected_result_1, [W1])
+    expected_result_2_den = Sum.safe(Sum.safe(expected_result_1, [W1]), [Y])
+    expected_result_2 = Fraction(expected_result_2_num, expected_result_2_den)
+
+    # Same thing, but with population probabilities
+    result_piece_pp = Product.safe(
+        [
+            PP[Pi1](W1),
+            PP[Pi1](W3 | W1),
+            PP[Pi1](W2 | (W3, W1)),
+            PP[Pi1](X | (W1, W3, W2, W4)),
+            PP[Pi1](Y | (W1, W3, W2, W4, X)),
+        ]
+    )
+    expected_result_1_part_1_pp = Fraction(
+        Sum.safe(Sum.safe(result_piece_pp, [W3]), [W2, X, Y]), One()
+    )  # Q[W1]/Q[\emptyset]
+    expected_result_1_part_2_pp = Fraction(
+        Sum.safe(Sum.safe(result_piece_pp, [W3]), [Y]),
+        Sum.safe(Sum.safe(result_piece_pp, [W3]), [X, Y]),
+    )  # Q[X]/Q[W2]
+    expected_result_1_part_3_pp = Fraction(
+        Sum.safe(result_piece_pp, [W3]), Sum.safe(Sum.safe(result_piece_pp, [W3]), [Y])
+    )  # Q[Y]/Q[X]
+    expected_result_1_pp = Product.safe(
+        [expected_result_1_part_1_pp, expected_result_1_part_2_pp, expected_result_1_part_3_pp]
+    )
+    expected_result_2_num_pp = Sum.safe(expected_result_1_pp, [W1])
+    expected_result_2_den_pp = Sum.safe(Sum.safe(expected_result_1_pp, [W1]), [Y])
+    expected_result_2_pp = Fraction(expected_result_2_num_pp, expected_result_2_den_pp)
+
+    def test_compute_c_factor_marginalizing_over_topological_successors_part_1(self):
+        """First test of Lemma 4, part (ii) (Equations 71 and 72 in [tian03a]_.
+
+        Source: The example on p. 30 of [Tian03a]_, run initially through [tikka20a]_.
+        """
+        result = compute_c_factor_marginalizing_over_topological_successors(
+            district={W1, X, Y},
+            graph_probability=Sum.safe(self.result_piece, [W3]),
+            topo=list(tian_pearl_figure_9a_graph.subgraph({W1, W2, X, Y}).topological_sort()),
+        )
+        logger.warning(
+            "In first test of Lemma 4(ii): expecting this result: " + str(self.expected_result_1)
+        )
+        self.assert_expr_equal(result, self.expected_result_1)
+
+    def test_compute_c_factor_marginalizing_over_topological_successors_part_2(self):
+        """Second test of Lemma 4, part (ii) (Equations 71 and 72 in [tian03a]_.
+
+        Source: The example on p. 30 of [Tian03a]_, run initially through [tikka20a]_.
+        """
+        logger.warning(
+            "In second test of Lemma 4(ii): expecting this result: " + str(self.expected_result_2)
+        )
+        logger.warning("Expected_result_1 = " + str(self.expected_result_1))
+        result = compute_c_factor_marginalizing_over_topological_successors(
+            district={Y},
+            graph_probability=Sum.safe(self.expected_result_1, [W1]),
+            topo=list(tian_pearl_figure_9a_graph.subgraph({X, Y}).topological_sort()),
+        )
+        self.assert_expr_equal(result, self.expected_result_2)
+
+    def test_compute_c_factor_marginalizing_over_topological_successors_part_3(self):
+        """First test of Equations 71 and 72 in [tian03a]_ using population probabilities.
+
+        Source: The example on p. 30 of [Tian03a]_, run initially through [tikka20a]_.
+        """
+        result = compute_c_factor_marginalizing_over_topological_successors(
+            district={W1, X, Y},
+            graph_probability=Sum.safe(self.result_piece_pp, [W3]),
+            topo=list(tian_pearl_figure_9a_graph.subgraph({W1, W2, X, Y}).topological_sort()),
+        )
+        logger.warning(
+            "In first test of Lemma 4(ii): expecting this result: "
+            + self.expected_result_1_pp.to_latex()
+        )
+        self.assert_expr_equal(result, self.expected_result_1_pp)
+
+    def test_compute_c_factor_marginalizing_over_topological_successors_part_4(self):
+        """Second test of Equations 71 and 72 in [tian03a]_ using population probabilities.
+
+        Source: The example on p. 30 of [Tian03a]_, run initially through [tikka20a]_.
+        """
+        logger.warning(
+            "In second test of Lemma 4(ii): expecting this result: "
+            + self.expected_result_2.to_latex()
+        )
+        logger.warning("Expected_result_1 = " + self.expected_result_1_pp.to_latex())
+        result = compute_c_factor_marginalizing_over_topological_successors(
+            district={Y},
+            graph_probability=Sum.safe(self.expected_result_1_pp, [W1]),
+            topo=list(tian_pearl_figure_9a_graph.subgraph({X, Y}).topological_sort()),
+        )
+        logger.warning("Expected result = " + self.expected_result_2_pp.to_latex())
+        self.assert_expr_equal(result, self.expected_result_2_pp)
+
+
+class TestComputeQValueOfVariablesWithLowTopologicalOrderingIndices(cases.GraphTestCase):
+    """Test the use of Equation 72 in Lemma 1, part (ii), of [tian03a]_."""
+
+    def test_compute_q_value_of_variables_with_low_topological_ordering_indices_part_1(self):
+        """First test of Equation 72 in [tian03a]_.
+
+        Source: RJC's mind.
+        """
+        topo = [variable for variable in figure_2a_graph.subgraph({Z, X, Y, W}).topological_sort()]
+        result = compute_q_value_of_variables_with_low_topological_ordering_indices(
+            vertex=W,
+            graph_probability=P(Y | W, X, Z) * P(W | X, Z) * P(X | Z) * P(Z),
+            topo=topo,
+        )
+        self.assert_expr_equal(
+            result, Sum.safe(P(Y | W, X, Z) * P(W | X, Z) * P(X | Z) * P(Z), [Y])
+        )
+        # Variable not in the graph
+        self.assertRaises(
+            KeyError,
+            compute_q_value_of_variables_with_low_topological_ordering_indices,
+            vertex={R},
+            graph_probability=P(Y | W, X, Z) * P(W | X, Z) * P(X | Z) * P(Z),
+            topo=topo,
+        )
+
+    def test_compute_q_value_of_variables_with_low_topological_ordering_indices_part_2(self):
+        r"""Second test of Equation 72 in [tian03a]_, checking $Q[H^{(0)}]=Q[\emptyset]$.
+
+        Source: RJC's mind.
+        """
+        topo = [variable for variable in figure_2a_graph.subgraph({Z, X, Y, W}).topological_sort()]
+        result = compute_q_value_of_variables_with_low_topological_ordering_indices(
+            vertex=None,
+            graph_probability=P(Y | W, X, Z) * P(W | X, Z) * P(X | Z) * P(Z),
+            topo=topo,
+        )
+        self.assert_expr_equal(result, One())
+
+
+class TestComputeAncestralSetQValue(cases.GraphTestCase):
+    """Test the use of Lemma 3 (i.e., Equation 69) of [tian03a]_ to compute a C factor."""
+
+    def test_compute_ancestral_set_q_value_part_1(self):
+        """First test of Lemma 3 in [tian03a]_ (Equation 69).
+
+        Source: The example on p. 30 of [Tian03a]_, run initially through [tikka20a]_.
+        """
+        topo = [W1, W3, W5, W2, W4, X, Y]
+        # Q_T = Q[{W1, W2, W3, X, Y}]
+        subgraph_probability = Product.safe(
+            [
+                P(W1),
+                P(W3 | W1),
+                P(W2 | (W3, W1)),
+                P(X | (W1, W3, W2, W4)),
+                P(Y | (W1, W3, W2, W4, X)),
+            ]
+        )
+        # The ancestors of {Y} in Figure 9(c) of [tian03a]_
+        ancestral_set = {W1, W2, X, Y}
+        subgraph_variables = {W1, W2, W3, X, Y}  # T in Figure 9(c) of [tian03a]_
+        result_1 = compute_ancestral_set_q_value(
+            ancestral_set=ancestral_set,
+            subgraph_variables=subgraph_variables,
+            subgraph_probability=subgraph_probability,
+            graph_topo=topo,
+        )
+        expected_result_1 = Sum.safe(subgraph_probability, [W3])
+        self.assert_expr_equal(expected_result_1, result_1)