docs: def _optimize_voltage_based_curtailment(feedin, voltage_pu, total_curtailment,
                                          voltage_threshold, timeindex, solver):
      """
      Formulates and solves linear problem to find linear relation between
      curtailment and node voltage.
  
      Parameters
      ------------
      feedin : :pandas:`pandas.DataFrame<dataframe>`
          See `feedin` parameter in
          :func:`edisgo.flex_opt.curtailment.voltage_based` for more information.
      voltage_pu : :pandas:`pandas.DataFrame<dataframe>
          Dataframe containing voltages in p.u. at the generator nodes. Index
          of the dataframe is a :pandas:`pandas.DatetimeIndex<datetimeindex>`,
          columns are the generator representatives.
      total_curtailment : :pandas:`pandas.Series<series>`
          Series containing the specific curtailment in kW to be allocated to the
          generators. The index is a
          :pandas:`pandas.DatetimeIndex<datetimeindex>`.
      voltage_threshold : :pandas:`pandas.Series<series>`
          Series containing the voltage thresholds in p.u. below which no
          generator curtailment will occur. The index is a
          :pandas:`pandas.DatetimeIndex<datetimeindex>`.
      solver : :obj:`str`
          The solver used to optimize the linear problem. Default: 'cbc'.
  
      Returns
      -------
      :pandas:`pandas:DataFrame<dataframe>`
          Dataframe containing the curtailment in kW per generator and time step
          feed-in was provided for in `feedin` parameter. Index is a
          :pandas:`pandas.DatetimeIndex<datetimeindex>`, columns are the
          generator representatives.
  
      """
  
      logging.debug("Start curtailment optimization.")
    
      v_max = voltage_pu.max(axis=1)
      generators = feedin.columns
  
      # additional curtailment factors
      cf_add = pd.DataFrame(index=timeindex)
      for gen in generators:
          cf_add[gen] = abs(
              (voltage_pu.loc[timeindex, gen] - v_max[timeindex]) / (
                      voltage_threshold[timeindex] - v_max[timeindex]))
  
      # curtailment factors
      cf = pd.DataFrame(index=timeindex)
      for gen in generators:
          cf[gen] = abs(
              (voltage_pu.loc[timeindex, gen] - voltage_threshold[timeindex]) / (
                      v_max[timeindex] - voltage_threshold[timeindex]))
  
      # initialize model
      model = ConcreteModel()
  
      # add sets
      model.T = Set(initialize=timeindex)
      model.G = Set(initialize=generators)
  
      # add parameters
      def feedin_init(model, t, g):
          return feedin.loc[t, g]
  
      model.feedin = Param(model.T, model.G, initialize=feedin_init)
  
      def voltage_pu_init(model, t, g):
          return voltage_pu.loc[t, g]
  
      model.voltage_pu = Param(model.T, model.G, initialize=voltage_pu_init)
  
      def cf_add_init(model, t, g):
          return cf_add.loc[t, g]
  
      model.cf_add = Param(model.T, model.G, initialize=cf_add_init)
  
      def cf_init(model, t, g):
          return cf.loc[t, g]
  
      model.cf = Param(model.T, model.G, initialize=cf_init)
  
      def total_curtailment_init(model, t):
          return total_curtailment.loc[t]
  
      model.total_curtailment = Param(model.T, initialize=total_curtailment_init)
  
      # add variables
      model.offset = Var(model.T, bounds=(0, 1))
      model.cf_max = Var(model.T, bounds=(0, 1))
  
      def curtailment_init(model, t, g):
          return (0, feedin.loc[t, g])
  
      model.c = Var(model.T, model.G, bounds=curtailment_init)
  
      # add objective
      def obj_rule(model):
          expr = (sum(model.offset[t] * 100
                      for t in model.T))
          return expr
  
      model.obj = Objective(rule=obj_rule, sense=minimize)
  
      # add constraints
      # curtailment per generator constraints
      def curtail(model, t, g):
          return (
          model.cf[t, g] * model.cf_max[t] * model.feedin[t, g] + model.cf_add[
              t, g] * model.offset[t] * model.feedin[t, g] - model.c[t, g] == 0)
  
      model.curtailment = Constraint(model.T, model.G, rule=curtail)
  
      # total curtailment constraint
      def total_curtailment(model, t):
          return (
          sum(model.c[t, g] for g in model.G) == model.total_curtailment[t])
  
      model.sum_curtailment = Constraint(model.T, rule=total_curtailment)
  
      # solve
      solver = SolverFactory(solver)
      results = solver.solve(model, tee=False)
  
      # load results back into model
      model.solutions.load_from(results)
  
      return pd.DataFrame({g: [model.c[t, g].value for t in model.T]
                           for g in model.G}, index=model.T.value)