code: def build_graph(
      action_nodes: List[str],
      outcome_nodes: List[str],
      common_cause_nodes: List[str] = None,
      instrument_nodes=None,
      effect_modifier_nodes=None,
      mediator_nodes=None,
  ):
      """Creates nodes and edges based on variable names and their semantics.
  
      Currently only considers the graphical representation of "direct" effect modifiers. Thus, all effect modifiers are assumed to be "direct" unless otherwise expressed using a graph. Based on the taxonomy of effect modifiers by VanderWheele and Robins: "Four types of effect modification: A classification based on directed acyclic graphs. Epidemiology. 2007."
      """
      graph = nx.DiGraph()
  
      action_nodes = parse_state(action_nodes)
      outcome_nodes = parse_state(outcome_nodes)
      common_cause_nodes = parse_state(common_cause_nodes)
      instrument_nodes = parse_state(instrument_nodes)
      effect_modifier_nodes = parse_state(effect_modifier_nodes)
  
      for treatment in action_nodes:
          graph.add_node(treatment)
      for outcome in outcome_nodes:
          graph.add_node(outcome)
      for treatment, outcome in itertools.product(
          action_nodes, outcome_nodes
      ):
          graph.add_edge(treatment, outcome)
  
      # Adding common causes
      if common_cause_nodes:
          for node_name in common_cause_nodes:
              for treatment, outcome in itertools.product(
                  action_nodes, outcome_nodes
              ):
                  graph.add_node(node_name)
                  graph.add_edge(node_name, treatment)
                  graph.add_edge(node_name, outcome)
  
      # Adding instruments
      if instrument_nodes:
          if type(instrument_nodes[0]) != tuple:
              if len(action_nodes) > 1:
                  _logger.info(
                      "Assuming Instrument points to all treatments! Use tuples for more granularity."
                  )
              for instrument, treatment in itertools.product(
                  instrument_nodes, action_nodes
              ):
                  graph.add_node(instrument)
                  graph.add_edge(instrument, treatment)
          else:
              for instrument, treatment in itertools.product(
                  instrument_nodes
              ):
                  graph.add_node(instrument)
                  graph.add_edge(instrument, treatment)
  
      # Adding effect modifiers
      if effect_modifier_nodes:
          for node_name in effect_modifier_nodes:
              if node_name not in common_cause_nodes:
                  for outcome in outcome_nodes:
                      graph.add_node(node_name)
                      # Assuming the simple form of effect modifier
                      # that directly causes the outcome.
                      graph.add_edge(node_name, outcome)
                      # self._graph.add_edge(node_name, outcome, style = "dotted", headport="s", tailport="n")
                      # self._graph.add_edge(outcome, node_name, style = "dotted", headport="n", tailport="s") # TODO make the ports more general so that they apply not just to top-bottom node configurations
      if mediator_nodes:
          for node_name in mediator_nodes:
              for treatment, outcome in itertools.product(
                  action_nodes, outcome_nodes
              ):
                  graph.add_node(node_name)
                  graph.add_edge(treatment, node_name)
                  graph.add_edge(node_name, outcome)
  
      return graph
  
imports: import itertools
  import logging
  import re
  from abc import abstractmethod
  from typing import Any, List, Protocol
  import networkx
  from networkx.algorithms.dag import has_cycle
  from dowhy.utils.api import parse_state
  from dowhy.utils.graph_operations import daggity_to_dot
label_desc: Use image node_id 19 for calling a global function with example usage: build_graph(action_nodes, outcome_nodes, common_cause_nodes, instrument_nodes, effect_modifier_nodes, mediator_nodes) and returns: graph