def admits_ri(cid: CID, decision: str, node: str) -> bool: r"""Check if a CID admits a response incentive on a node. - A CID G admits a response incentive on X ∈ V \ {D} if and only if the reduced graph G* min has a directed path X --> D. ("Agent Incentives: a Causal Perspective" by Everitt, Carey, Langlois, Ortega, and Legg, 2020) """ if len(cid.agents) > 1: raise ValueError( f"This CID has {len(cid.agents)} agents. This incentive is currently only valid for CIDs with one agent." ) if node not in cid.nodes: raise KeyError(f"{node} is not present in the cid") if decision not in cid.nodes: raise KeyError(f"{decision} is not present in the cid") if not cid.sufficient_recall(): raise ValueError( "Response inventives are only implemented for graphs with sufficient recall" ) if node == decision: return False req_graph = requisite_graph(cid) try: next(find_all_dir_paths(req_graph, node, decision)) except StopIteration: return False else: return True
def admits_voc(cid: CID, node: str) -> bool: """Check if a CID admits positive value of control for a node. A CID G admits positive value of control for a node X ∈ V if and only if X is not a decision node and there is a directed path X --> U in the reduced graph G∗. """ if len(cid.agents) > 1: raise ValueError( f"This CID has {len(cid.agents)} agents. This incentive is currently only valid for CIDs with one agent." ) if node not in cid.nodes: raise KeyError(f"{node} is not present in the cid") if not cid.sufficient_recall(): raise ValueError("VoC only implemented graphs with sufficient recall") if node in cid.decisions: return False req_graph = requisite_graph(cid) agent_utilities = cid.utilities for util in agent_utilities: if node == util or util in nx.descendants(req_graph, node): return True return False
def admits_dir_voc(cid: CID, node: str) -> bool: r"""Check if a CID admits direct positive value of control for a node. - A CID G admits positive value of control for a node X ∈ V \ {D} if and only if there is a directed path X --> U in the requisite graph G∗. - The path X --> U may or may not pass through decisions. - The agent has a direct value of control incentive on D if the path does not pass through a decision. - The agent has an indirect value of control incentive on D if the path does pass through any decision and there is also a backdoor path X--U that begins backwards from X (...<- X) and is active when conditioning on Fa_D \ {X} """ if len(cid.agents) > 1: raise ValueError( f"This CID has {len(cid.agents)} agents. This incentive is currently only valid for CIDs with one agent." ) if node not in cid.nodes: raise KeyError(f"{node} is not present in the cid") agent_utilities = cid.utilities req_graph = requisite_graph(cid) if not admits_voc(cid, node): return False req_graph_node_descendants = set(nx.descendants(req_graph, node)) for util in agent_utilities: if util != node and util not in req_graph_node_descendants: continue for path in find_all_dir_paths(req_graph, node, util): if not set(path).intersection(cid.decisions): return True return False
def admits_voi(cid: CID, decision: str, node: str) -> bool: r"""Return True if cid admits value of information for node. - A CID admits value of information for a node X if: i) X is not a descendant of the decision node, D. ii) X is d-connected to U given Fa_D \ {X}, where U ∈ U ∩ Desc(D) ("Agent Incentives: a Causal Perspective" by Everitt, Carey, Langlois, Ortega, and Legg, 2020) """ if len(cid.agents) > 1: raise ValueError( f"This CID has {len(cid.agents)} agents. This incentive is currently only valid for CIDs with one agent." ) if node not in cid.nodes: raise KeyError(f"{node} is not present in the cid") if decision not in cid.nodes: raise KeyError(f"{decision} is not present in the cid") if not cid.sufficient_recall(): raise ValueError("Voi only implemented graphs with sufficient recall") if node in nx.descendants(cid, decision) or node == decision: return False cid2 = cid.copy_without_cpds() cid2.add_edge(node, decision) req_graph = requisite_graph(cid2) return node in req_graph.get_parents(decision)
def test_trim_example(cid_trim_example: CID) -> None: assert len(cid_trim_example.edges) == 12 assert set(cid_trim_example.get_parents("D2")) == { "Y1", "Y2", "D1", "Z1", "Z2" } req_graph = requisite_graph(cid_trim_example) assert len(req_graph.edges) == 7 assert set(req_graph.get_parents("D2")) == {"Y2"}
def admits_indir_voc(cid: CID, decision: str, node: str) -> bool: r"""Check if a single-decision CID admits indirect positive value of control for a node. - A single-decision CID G admits positive value of control for a node X ∈ V \ {D} if and only if there is a directed path X --> U in the reduced graph G∗. - The path X --> U may or may not pass through D. - The agent has a direct value of control incentive on D if the path does not pass through D. - The agent has an indirect value of control incentive on D if the path does pass through D and there is also a backdoor path X--U that begins backwards from X (...<- X) and is active when conditioning on Fa_D \ {X} """ if len(cid.agents) > 1: raise ValueError( f"This CID has {len(cid.agents)} agents. This incentive is currently only valid for CIDs with one agent." ) if node not in cid.nodes: raise KeyError(f"{node} is not present in the cid") if decision not in cid.nodes: raise KeyError(f"{decision} is not present in the cid") if not cid.sufficient_recall(): raise ValueError("VoC only implemented graphs with sufficient recall") agent_utilities = cid.utilities req_graph = requisite_graph(cid) d_family = [decision] + cid.get_parents(decision) con_nodes = {i for i in d_family if i != node} if not admits_voc(cid, node): return False req_graph_node_descendants = set(nx.descendants(req_graph, node)) for util in agent_utilities: if util != node and util not in req_graph_node_descendants: continue if not is_active_backdoor_trail(req_graph, node, util, con_nodes): continue if any(decision in path for path in find_all_dir_paths(req_graph, node, util)): return True return False