docs: def get_critical_voltage_at_nodes(grid):
      r"""
      Estimate voltage drop/increase induced by loads/generators connected to the
      grid.
  
      Based on voltage level at each node of the grid critical nodes in terms
      of exceed tolerable voltage drop/increase are determined.
      The tolerable voltage drop/increase is defined by [#VDE]_ a adds up to
      3 % of nominal voltage.
      The longitudinal voltage drop at each line segment is estimated by a
      simplified approach (neglecting the transverse voltage drop) described in
      [#VDE]_.
  
      Two equations are available for assessing voltage drop/ voltage increase.
  
      The first is used to assess a voltage drop in the load case
  
      .. math::
          \\Delta u = \\frac{S_{Amax} \cdot ( R_{kV} \cdot cos(\phi) + X_{kV} \cdot sin(\phi) )}{U_{nom}}
  
      The second equation can be used to assess the voltage increase in case of
      feedin. The only difference is the negative sign before X. This is related
      to consider a voltage drop due to inductive operation of generators.
  
      .. math::
          \\Delta u = \\frac{S_{Amax} \cdot ( R_{kV} \cdot cos(\phi) - X_{kV} \cdot sin(\phi) )}{U_{nom}}
  
      =================  =============================
      Symbol             Description
      =================  =============================
      :math:`\Delta u`   Voltage drop/increase at node
      :math:`S_{Amax}`   Apparent power
      :math:`R_{kV}`     Short-circuit resistance
      :math:`X_{kV}`     Short-circuit reactance
      :math:`cos(\phi)`  Power factor
      :math:`U_{nom}`    Nominal voltage
      =================  =============================
  
      Parameters
      ----------
      grid : LVGridDing0
          Ding0 LV grid object
  
      Notes
      -----
      The implementation highly depends on topology of LV grid. This must not
      change its topology from radial grid with stubs branching from radial
      branches. In general, the approach of [#VDE]_ is only applicable to grids of
      radial topology.
  
      We consider the transverse voltage drop/increase by applying the same
      methodology successively on results of main branch. The voltage
      drop/increase at each house connection branch (aka. stub branch or grid
      connection point) is estimated by superposition based on voltage level
      in the main branch cable distributor.
      
      References
      ----------
      .. [#VDE] VDE Anwenderrichtlinie: Erzeugungsanlagen am Niederspannungsnetz –
          Technische Mindestanforderungen für Anschluss und Parallelbetrieb von
          Erzeugungsanlagen am Niederspannungsnetz, 2011
      """
  
      v_delta_tolerable_fc = cfg_ding0.get('assumptions',
                                        'lv_max_v_level_fc_diff_normal')
      v_delta_tolerable_lc = cfg_ding0.get('assumptions',
                                        'lv_max_v_level_lc_diff_normal')
  
      omega = 2 * math.pi * 50
  
      crit_nodes = []
  
      # get list of nodes of main branch in right order
      tree = nx.dfs_tree(grid._graph, grid._station)
  
      # list for nodes of main branch
      main_branch = []
  
      # list of stub cable distributors branching from main branch
      grid_conn_points = []
  
      # fill two above lists
      for node in list(nx.descendants(tree, grid._station)):
          successors = list(tree.successors(node))
          if successors and all(isinstance(successor, LVCableDistributorDing0)
                 for successor in successors):
              main_branch.append(node)
          elif (isinstance(node, LVCableDistributorDing0) and
              all(isinstance(successor, (GeneratorDing0, LVLoadDing0))
                 for successor in successors)):
              grid_conn_points.append(node)
  
      # voltage at substation bus bar
      r_mv_grid, x_mv_grid = get_mv_impedance(grid)
  
      r_trafo = sum([tr.r for tr in grid._station._transformers])
      x_trafo = sum([tr.x for tr in grid._station._transformers])
  
      v_delta_load_case_bus_bar, \
      v_delta_gen_case_bus_bar = get_voltage_at_bus_bar(grid, tree)
  
      if (abs(v_delta_gen_case_bus_bar) > v_delta_tolerable_fc
          or abs(v_delta_load_case_bus_bar) > v_delta_tolerable_lc):
          crit_nodes.append({'node': grid._station,
                             'v_diff': [v_delta_load_case_bus_bar,
                                        v_delta_gen_case_bus_bar]})
  
  
  
      # voltage at main route nodes
      for first_node in [b for b in tree.successors(grid._station)
                     if b in main_branch]:
  
          # cumulative resistance/reactance at bus bar
          r = r_mv_grid + r_trafo
          x = x_mv_grid + x_trafo
  
          # roughly estimate transverse voltage drop
          stub_node = [_ for _ in list(tree.successors(first_node)) if
                       _ not in main_branch][0]
          v_delta_load_stub, v_delta_gen_stub = voltage_delta_stub(
              grid,
              tree,
              first_node,
              stub_node,
              r,
              x)
  
          # cumulative voltage drop/increase at substation bus bar
          v_delta_load_cum = v_delta_load_case_bus_bar
          v_delta_gen_cum = v_delta_gen_case_bus_bar
  
          # calculate voltage at first node of branch
          voltage_delta_load, voltage_delta_gen, r, x = \
              get_voltage_delta_branch(grid, tree, first_node, r, x)
  
          v_delta_load_cum += voltage_delta_load
          v_delta_gen_cum += voltage_delta_gen
  
          if (abs(v_delta_gen_cum) > (v_delta_tolerable_fc - v_delta_gen_stub)
              or abs(v_delta_load_cum) > (v_delta_tolerable_lc - v_delta_load_stub)):
              crit_nodes.append({'node': first_node,
                                 'v_diff': [v_delta_load_cum,
                                            v_delta_gen_cum]})
              crit_nodes.append({'node': stub_node,
                                 'v_diff': [
                                     v_delta_load_cum + v_delta_load_stub,
                                     v_delta_gen_cum + v_delta_gen_stub]})
  
          # get next neighboring nodes down the tree
          successor = [x for x in tree.successors(first_node)
                        if x in main_branch]
          if successor:
              successor = successor[0] # simply unpack
  
          # successively determine voltage levels for succeeding nodes
          while successor:
              voltage_delta_load, voltage_delta_gen, r, x = \
                  get_voltage_delta_branch(grid, tree, successor, r, x)
  
              v_delta_load_cum += voltage_delta_load
              v_delta_gen_cum += voltage_delta_gen
  
              # roughly estimate transverse voltage drop
              stub_node = [_ for _ in tree.successors(successor) if
                           _ not in main_branch][0]
              v_delta_load_stub, v_delta_gen_stub = voltage_delta_stub(
                  grid,
                  tree,
                  successor,
                  stub_node,
                  r,
                  x)
  
              if (abs(v_delta_gen_cum) > (v_delta_tolerable_fc - v_delta_gen_stub)
                  or abs(v_delta_load_cum) > (
                              v_delta_tolerable_lc - v_delta_load_stub)):
                  crit_nodes.append({'node': successor,
                                     'v_diff': [v_delta_load_cum,
                                                v_delta_gen_cum]})
                  crit_nodes.append({'node': stub_node,
                                     'v_diff': [
                                         v_delta_load_cum + v_delta_load_stub,
                                         v_delta_gen_cum + v_delta_gen_stub]})
  
              successor = [_ for _ in tree.successors(successor)
                           if _ in main_branch]
              if successor:
                  successor = successor[0]
  
      return crit_nodes