code: def create_causal_model_from_equations(
      node_equations: str,
  ) -> StructuralCausalModel:
      """
      Create a causal model from a set of equations defining causal relationships between nodes.
      The equation format supports the following cases in which expression can be defined:
      1. Specifying root node equation:
          >>> "<node_name> = <noise_model_name>(<optional_arguments>)"
      The noise model name can be one of the following:
          - empirical()
          - bayesiangaussianmixture()
          - parametric()
          - <scipy.stats.*>
      Empirical and bayessian models are already defined and one can find the description
      of those in the dowhy library.
      Use parametric when you want to find the best continuous distribution for the data.
      You can specify any noise function defined in scipy\.stats library.
      2. Specifying non-root node equation:
          >>> "<node_name> = <function-expression> + <noise_model_name>(<optional_arguments>)"
      The function-expression can be any expression containing airthmetic operations of the nodes
      and calling functions defined under numpy. The format/definition of noise for the non-root node
      remains same as in point one.
      3. Specifying unknown causal model equation:
          >>> "Node -> <node_name1>, <node_name2>, ..."
      In case we don't know the causal relationship model between nodes then we can
      use the above format to just define the edges between the nodes.
      Example:
          >>> scm = \"""
          X = empirical()
          Z = norm(loc=0, scale=1)
          Y = 12 * X + log(Z) + norm(loc=0, scale=1)
          \"""
      :param node_equations: A string containing equations defining the relationships between nodes.
                              Each equation should be separated by a newline.
      :return: StructuralCausalModel: A StructuralCausalModel object representing the created causal model.
      """
      banned_characters = [":", ";", "[", "__", "import", "lambda"]
      causal_nodes_info = {}
      causal_graph = nx.DiGraph()
      for equation in node_equations.split("\n"):
          equation = equation.strip()
          _sanitize_input_expression(equation, banned_characters)
          if equation:
              parsed_args = {}
              node_name, expression = _extract_equation_components(
                  equation
              )
              _check_node_redundancy(causal_nodes_info, node_name)
              causal_nodes_info[node_name] = {}
              root_node_match = re.match(
                  _NOISE_MODEL_PATTERN, expression
              )
              unknown_model_match = _check_if_model_is_unknown(
                  equation, expression
              )
              causal_graph.add_node(node_name)
              if root_node_match:
                  causal_mechanism_name = root_node_match.group(1)
                  args = root_node_match.group(2)
                  parsed_args = _parse_args(args)
                  causal_nodes_info[node_name][
                      "causal_mechanism"
                  ] = _identify_noise_model(
                      causal_mechanism_name, parsed_args
                  )
              elif unknown_model_match:
                  parent_node_candidates = expression.split(",")
                  parent_nodes = _get_sorted_parent_nodes(
                      parent_node_candidates
                  )
                  _add_parent_nodes_to_graph(
                      causal_graph, parent_nodes, node_name
                  )
                  causal_nodes_info[node_name]["unknown"] = True
              else:
                  custom_func, noise_eq = expression.rsplit("+", 1)
                  # Find all node names in the expression string.
                  parent_node_candidates = re.findall(
                      _NODE_NAME_PATTERN, custom_func
                  )
                  parent_nodes = _get_sorted_parent_nodes(
                      parent_node_candidates
                  )
                  _add_parent_nodes_to_graph(
                      causal_graph, parent_nodes, node_name
                  )
                  (
                      noise_model_name,
                      parsed_args,
                  ) = _extract_noise_model_components(noise_eq)
                  noise_model = _identify_noise_model(
                      noise_model_name, parsed_args
                  )
                  causal_nodes_info[node_name][
                      "causal_mechanism"
                  ] = AdditiveNoiseModel(
                      CustomEquationModel(custom_func, parent_nodes),
                      noise_model,
                  )
              causal_nodes_info[node_name]["fully_defined"] = (
                  True if parsed_args else False
              )
      _add_undefined_nodes_info(
          causal_nodes_info, list(causal_graph.nodes)
      )
      causal_model = StructuralCausalModel(causal_graph)
      for node in causal_graph.nodes:
          if not ("unknown" in causal_nodes_info[node]):
              causal_model.set_causal_mechanism(
                  node, causal_nodes_info[node]["causal_mechanism"]
              )
          if causal_nodes_info[node]["fully_defined"]:
              causal_model.graph.nodes[node][
                  PARENTS_DURING_FIT
              ] = get_ordered_predecessors(causal_model.graph, node)
      return causal_model
  
imports: import ast
  import logging
  import re
  from typing import Tuple
  import networkx
  import numpy
  import scipy.stats
  from dowhy.gcm import AdditiveNoiseModel, EmpiricalDistribution, ScipyDistribution, StructuralCausalModel
  from dowhy.gcm.causal_mechanisms import StochasticModel
  from dowhy.gcm.causal_models import PARENTS_DURING_FIT
  from dowhy.gcm.ml.prediction_model import PredictionModel
  from dowhy.gcm.util.general import shape_into_2d
  from dowhy.graph import get_ordered_predecessors
label_desc: Use image node_id 1 for calling a global function with example usage: create_causal_model_from_equations(node_equations) and returns: causal_model