Ejemplo n.º 1
0
def b2_sddp(settings, n1, n2, k, m, p, q, x_obj, y_obj, A_rows, G_rows,
            T_rows, D_rows, W_rows, b, d, w, scenarios, prob, z_lower, 
            delta, output_stream = sys.stdout):
    """Apply the B^2 algorithm for dynamic programming.

    Solve an infinite horizon stochastic dynamic program using the B^2
    algorithm, given the initial data. It is assumed that this is a
    minimization problem and the constraints are in >= form. The
    problem is written as:

    .. math ::

    \min \{\sum_{t=0}^\infty c^T x_t + h^T y_t : \forall t A x_t + G
    y_t \ge b + Ty_{t-1}, D x_t \ge d, W y_t \ge w\}


    Parameters
    ----------
    settings : b2_sddp_settings.B2Settings
        Algorithmic settings.

    n1 : int
        Number of x variables of the problem.

    n2 : int
        Number of y variables of the problem.

    k : int
        Number of scenarios.

    m : int
        Number of rows in the first set of constraints
        A x + G y >= b + T y_{t-1}.

    p : int
        Number of rows in the second set of constraints D x >= d.

    q : int
        Number of rows in the third set of constraints W x >= w.

    x_obj : List[float]
        Cost coefficient for the x variables.

    y_obj : List[float]
        Cost coefficient for the y (state) variables.

    A_rows : List[List[int], List[float]]
        The coefficients of the rows of the A matrix in A x + G y >=
        b + T y_{t-1}. These are in sparse form: indices and elements.

    G_rows : List[List[int], List[float]]
        The coefficients of the rows of the G matrix in A x + G y >=
        b + T y_{t-1}. These are in sparse form: indices and elements.

    T_rows : List[List[int], List[float]]
        The coefficients of the rows of the T matrix in A x + G y >=
        b + T y_{t-1}. These are in sparse form: indices and elements.

    D_rows : List[List[int], List[float]]
        The coefficients of the rows of the D matrix in D x >= d.
        These are in sparse form: indices and elements.

    W_rows : List[List[int], List[float]]
        The coefficients of the rows of the W matrix in W y >= w.
        These are in sparse form: indices and elements.

    b : List[float]
        The rhs of the first set of constraints in the master.

    d : List[float]
        The rhs of the second set of constraints in the master.

    w : List[float]
        The rhs of the third set of constraints in the master.

    scenarios : List[List[float]]
        The rhs vector b, d, w for each scenario.

    prob : List[float]
        The probability of each scenario.

    z_lower : List[float]
        Lower bounds on the objective value of each scenario.

    delta : float
        Discount factor, 0 <= delta < 1.

    output_stream : file
        Output stream. Must have a 'write' and 'flush' method.

    Returns
    -------
    (cplex.Cplex, float, float, float, int, float)
        The master problem after convergence, the best upper bound,
        the best lower bound, the final gap (as a fraction, i.e. not
        in percentage point), the final length of the time horizon
        tau, the total CPU time in seconds.

    """
    assert(isinstance(settings, B2Settings))
    assert(len(x_obj) == n1)
    assert(len(y_obj) == n2)
    assert(len(A_rows) == m)
    assert(len(G_rows) == m)
    assert(len(T_rows) == m)
    assert(len(D_rows) == p)
    assert(len(W_rows) == q)
    assert(len(b) == m)
    assert(len(d) == p)
    assert(len(w) == q)
    assert(len(scenarios) == k)
    for i in range(k):
        assert(len(scenarios[i]) == m + p + q)
    assert(len(prob) == k)
    assert(len(z_lower) == k)
    assert(0 <= delta < 1)
    # Start counting time
    start_time = time.clock()

    # Initialize inverse CDF of the probability distribution of the
    # scenarios
    inverse_cdf = util.InverseCDF(prob)
    # Compute column representation of T
    T_cols = util.row_matrix_to_col_matrix(m, n2, T_rows)

    # Create the master and slave
    master = create_master_problem(settings, n1, n2, k, m, p, q, x_obj, y_obj,
                                   A_rows, G_rows, D_rows, W_rows,
                                   b, d, w, scenarios, prob, z_lower, delta)
    slave = create_slave_problem(settings, n1, n2, k, m, p, q, x_obj, y_obj, 
                                 A_rows, G_rows, D_rows, W_rows, 
                                 d, w, prob, z_lower, delta)
    if (settings.primal_bound_method == 'no_y'):
        # Create master problem without the y variables
        pb_prob = pb.create_p_no_y_problem(settings, n1, k, m, p, q, 
                                           x_obj, A_rows, D_rows, b, d)
    elif (settings.primal_bound_method == 'fixed_y'):
        # Create master problem with fixed y variables
        pb_prob = pb.create_p_fixed_y_problem(settings, n1, n2, k, m, p, q, 
                                              x_obj, y_obj, A_rows, G_rows, 
                                              T_rows, D_rows, W_rows, d, w, 
                                              scenarios)
    elif (settings.primal_bound_method == 'p_heur'):
        # Create the p_heur problem
        pb_prob = pb.create_p_heur_problem(settings, n1, n2, k, m, p, q, 
                                           x_obj, y_obj, A_rows, G_rows, 
                                           T_rows, D_rows, W_rows, d, w, 
                                           scenarios)
    elif (settings.primal_bound_method == 'p_rebalance'):
        # Create an empty p_heur problem
        pb_prob = pb.create_p_rebalance()
    else:
        raise ValueError('Primal bound method ' +
                         '{:s}'.format(settings.primal_bound_method) +
                         ' not implemented')
	
    if (not settings.print_cplex_output):
        master.set_results_stream(None)
        slave.set_results_stream(None)
        pb_prob.set_results_stream(None)
    else:
        master.set_results_stream(output_stream)
        slave.set_results_stream(output_stream)
        pb_prob.set_results_stream(output_stream)

    # Store solutions for all forward passes
    fwpass_sol = list()
    # Store cost of primal solutions, i.e. sample paths
    cost_sol = list()
    # The best primal bound available so far
    best_ub = cplex.infinity
    # Is the stopping criterion satisfied?
    stop = False
    # Length of the time horizon
    tau = 0
    # Cut activity levels
    cut_activity = list()

    
    # Comput single-stage cost for the infinite tail, if necessary
    if (settings.primal_bound_method == 'no_y'):
        ub_tail = pb.cost_tail_no_y(settings, n1, n2, k, m, p, q,
                                    T_cols, [0]*n2, 0, delta,
                                    scenarios, prob, 0, pb_prob)
    elif (settings.primal_bound_method == 'fixed_y'):
        ub_tail = pb.cost_tail_fixed_y(settings, n1, n2, k, m, p, q,
                                       T_cols, [0]*n2, 0, delta,
                                       scenarios, prob, 0, pb_prob)
    elif (settings.primal_bound_method == 'p_rebalance'):
        ub_tail = pb.cost_tail_rebalance(settings, n1, n2, k, m,
                                         p, q, x_obj, T_cols, [0]*n2,
                                         0, delta, scenarios, 
                                         prob,0)
      
    # Start the B^2 algorithm!
    while (not stop):
        # Increment length of the time horizon if necessary
        tau = update_time_horizon(settings, tau)
        
	print('*** Major iteration with tau {:d}'.format(tau),
              file = output_stream)
        output_stream.flush()
        for num_pass in range(settings.num_passes):
            x, y, z = forward_pass(settings, n1, n2, k, m, p, q, tau, T_cols,
                                   b, d, w, scenarios, prob, inverse_cdf, 
                                   master, num_pass, cut_activity)
            # Store all LP solutions
            fwpass_sol.append((x, y, z))
            # Clean up LPs
            cut_activity = util.purge_cuts(settings, m, p, q, cut_activity,
                                           master, slave)
            # Computation of upper bounds
            cost_sol.append(pb.cost_primal_sol(x, y, x_obj, y_obj, 
                                               tau, delta))
            # Backward pass: collect Benders cuts
            cuts = backward_pass(settings, n1, n2, k, m, p, q, tau, T_cols, 
                                 scenarios, prob, x, y, z, slave, num_pass)
            util.add_cuts(master, cuts)
        # -- end time horizon tau

        # Extend existing sample paths to the desired length
        for (i, (sol_x, sol_y, sol_z)) in enumerate(fwpass_sol):
            while (len(sol_x) < tau):
                # Move forward in time starting from the last solution
                x, y, z = single_forward_step(settings, n1, n2, k, m, p, q, 
                                              T_cols, sol_y[-1], scenarios, 
                                              prob, inverse_cdf, master,
                                              cut_activity)
                sol_x.append(x)
                sol_y.append(y)
                sol_z.append(z)
                
                # Update upper bound
                cost_sol[i] += pb.cost_primal_sol([x], [y], x_obj, y_obj, 
                                                  tau, delta, tau - 1)
                # Backward pass: collect Benders cuts
                cuts = backward_pass(settings, n1, n2, k, m, p, q, 1,
                                     T_cols, scenarios, prob, [x], [y], [z],
                                     slave, 0)
                util.add_cuts(master, cuts)

        # Primal bound: compute cost for the entire path until infinity
        cost_path = list()
        
        for (i, (sol_x, sol_y, sol_z)) in enumerate(fwpass_sol):
            if (settings.primal_bound_method == 'no_y'):
                cost_path.append(pb.cost_tail_no_y(settings, n1, n2, k, m, p, 
                                                   q, T_cols, sol_y[-1], tau,
                                                   delta, scenarios, prob, 
                                                   ub_tail, pb_prob) +
                                 cost_sol[i])
            elif (settings.primal_bound_method == 'fixed_y'):
                cost_path.append(pb.cost_tail_fixed_y(settings, n1, n2, k, m,
                                                      p, q, T_cols, sol_y[-1],
                                                      tau, delta, scenarios, 
                                                      prob, ub_tail, pb_prob) +
                                 cost_sol[i])
            elif (settings.primal_bound_method == 'p_heur'):
                cost_path.append(pb.cost_tail_p_heur(settings, n1, n2, k, m,
                                                     p, q, T_cols, sol_y[-1],
                                                     tau, delta, scenarios, 
                                                     prob, pb_prob) +
                                 cost_sol[i])
            elif (settings.primal_bound_method == 'p_rebalance'):
                cost_path.append(pb.cost_tail_rebalance(settings, n1, n2, k, m,
                                                        p, q, x_obj, T_cols, 
                                                        sol_y[-1], tau, delta,
                                                        scenarios, prob,
                                                        ub_tail) +
                                 cost_sol[i])
            
	ub = pb.primal_bound(settings, cost_path)
	
        # Get current dual bound and compute optimality gap
        master.linear_constraints.set_rhs([(i, b[i]) for i in range(m)])
        master.linear_constraints.set_rhs([(m + i, d[i]) for i in range(p)])
        master.linear_constraints.set_rhs([(m + p + i, w[i]) 
                                           for i in range(q)])
        master.solve()
        
        lb = master.solution.get_objective_value()

        if (ub > 0):
            gap =  (ub - lb) / (ub + 1.0e-10)
        else:
            gap =  (ub - lb) / (-ub - 1.0e-10)
        # Report progress status
        #print('Primal bound: {:f}  '.format(ub) +
             #'Dual bound: {:f}  '.format(lb) +
             #'gap {:.2f} %'.format(100*gap))
        
        print('Primal bound: {:f}  '.format(ub) +
             'Dual bound: {:f}  '.format(lb) +
             'gap {:.2f} %'.format(100*gap),
             file = output_stream)
        output_stream.flush()
        cpu_time = time.clock() - start_time
        
        #Stop when the optimality gap is small enough
        if (tau > 1):
	  if (tau >= settings.max_tau or 
	      cpu_time >= settings.max_cpu_time or
	      gap <= settings.gap_relative or
	      (ub - lb) <= settings.gap_absolute):
	      stop = True

    # -- end main loop

    # Print last solution and exit
    util.print_LP_solution(settings, n1, n2, k, master,
                           'Final solution of the master:', 
                           output_stream = output_stream)

    total_time = time.clock() - start_time
    print(file = output_stream)
    print('Summary:', end = ' ', file = output_stream)
    print('ub {:.3f} lb {:.3f} '.format(ub, lb) + 
          'gap {:.2f} % tau {:d} '.format(100 * gap, tau) + 
          'time {:.4f}'.format(total_time),
          file = output_stream)
    output_stream.flush()

    return (master, ub, lb, gap, tau, total_time)
def pp_sddp(settings,
            n1,
            n2,
            k,
            m,
            p,
            q,
            x_obj,
            y_obj,
            A_rows,
            G_rows,
            T_rows,
            D_rows,
            W_rows,
            b,
            d,
            w,
            scenarios,
            prob,
            z_lower,
            delta,
            output_stream=sys.stdout):
    """Apply the Pereira-Pinto SDDP algorithm for dynamic programming.

    Solve an infinite horizon stochastic dynamic program using the
    Pereira-Pinto SDDP algorithm, given the initial data. It is
    assumed that this is a minimization problem and the constraints
    are in >= form. The problem is written as:

    .. math ::

    \min \{\sum_{t=0}^\infty c^T x_t + h^T y_t : \forall t A x_t + G
    y_t \ge b + Ty_{t-1}, D x_t \ge d, W y_t \ge w\}


    Parameters
    ----------
    settings : b2_sddp_settings.B2Settings
        Algorithmic settings.

    n1 : int
        Number of x variables of the problem.

    n2 : int
        Number of y variables of the problem.

    k : int
        Number of scenarios.

    m : int
        Number of rows in the first set of constraints
        A x + G y >= b + T y_{t-1}.

    p : int
        Number of rows in the second set of constraints D x >= d.

    q : int
        Number of rows in the third set of constraints W x >= w.

    x_obj : List[float]
        Cost coefficient for the x variables.

    y_obj : List[float]
        Cost coefficient for the y (state) variables.

    A_rows : List[List[int], List[float]]
        The coefficients of the rows of the A matrix in A x + G y >=
        b + T y_{t-1}. These are in sparse form: indices and elements.

    G_rows : List[List[int], List[float]]
        The coefficients of the rows of the G matrix in A x + G y >=
        b + T y_{t-1}. These are in sparse form: indices and elements.

    T_rows : List[List[int], List[float]]
        The coefficients of the rows of the T matrix in A x + G y >=
        b + T y_{t-1}. These are in sparse form: indices and elements.

    D_rows : List[List[int], List[float]]
        The coefficients of the rows of the D matrix in D x >= d.
        These are in sparse form: indices and elements.

    W_rows : List[List[int], List[float]]
        The coefficients of the rows of the W matrix in W y >= w.
        These are in sparse form: indices and elements.

    b : List[float]
        The rhs of the first set of constraints in the master.

    d : List[float]
        The rhs of the second set of constraints in the master.

    w : List[float]
        The rhs of the third set of constraints in the master.

    scenarios : List[List[float]]
        The rhs vector (b, d, w) for each scenario.

    prob : List[float]
        The probability of each scenario.

    z_lower : List[float]
        Lower bounds on the objective value of each scenario.

    delta : float
        Discount factor, 0 <= delta < 1.

    output_stream : file
        Output stream. Must have a 'write' and 'flush' method.

    Returns
    -------
    (cplex.Cplex, float, float, float, int, float)
        The master problem after convergence, the best upper bound,
        the best lower bound, the final gap (as a fraction, i.e. not
        in percentage point), the final length of the time horizon
        tau, the total CPU time in seconds.
    """
    assert (isinstance(settings, B2Settings))
    assert (len(x_obj) == n1)
    assert (len(y_obj) == n2)
    assert (len(A_rows) == m)
    assert (len(G_rows) == m)
    assert (len(T_rows) == m)
    assert (len(D_rows) == p)
    assert (len(W_rows) == q)
    assert (len(b) == m)
    assert (len(d) == p)
    assert (len(w) == q)
    assert (len(scenarios) == k)
    for i in range(k):
        assert (len(scenarios[i]) == m + p + q)
    assert (len(prob) == k)
    assert (len(z_lower) == k)
    assert (0 <= delta < 1)

    # Start counting time
    start_time = time.clock()

    # Initialize inverse CDF of the probability distribution of the
    # scenarios
    inverse_cdf = util.InverseCDF(prob)
    # Compute column representation of T
    T_cols = util.row_matrix_to_col_matrix(m, n2, T_rows)

    # Create the master and slave
    master_orig = create_master_problem(settings, n1, n2, k, m, p, q, x_obj,
                                        y_obj, A_rows, G_rows, D_rows, W_rows,
                                        b, d, w, scenarios, prob, z_lower,
                                        delta)
    slave_orig = create_slave_problem(settings, n1, n2, k, m, p, q, x_obj,
                                      y_obj, A_rows, G_rows, D_rows, W_rows, d,
                                      w, prob, z_lower, delta)

    # Store solutions for all forward passes
    fwpass_sol = list()
    # Store cost of primal solutions, i.e. sample paths
    cost_sol = list()
    # The best primal bound available so far
    best_ub = cplex.infinity
    # Is the stopping criterion satisfied?
    stop = False

    # Create master problem with fixed y variables
    pb_prob = pb.create_p_fixed_y_problem(settings, n1, n2, k, m, p, q, x_obj,
                                          y_obj, A_rows, G_rows, T_rows,
                                          D_rows, W_rows, d, w, scenarios)

    if (not settings.print_cplex_output):
        pb_prob.set_results_stream(None)
    else:
        pb_prob.set_results_stream(output_stream)

    # Compute cost of a single stage, using a fixed y policy
    ub_tail = pb.cost_tail_fixed_y(settings, n1, n2, k, m, p, q, T_cols,
                                   [0] * n2, 0, delta, scenarios, prob, 0,
                                   pb_prob)
    # Fraction of the absolute gap used to upper bound the error in
    # the infinite tail
    gap_frac = 0.9
    # Length of the time horizon
    tau = get_time_horizon_length(delta, ub_tail,
                                  settings.gap_absolute * gap_frac)
    print('Pereira-Pinto time horizon of length {:d}'.format(tau),
          file=output_stream)
    output_stream.flush()

    # Make copies of master and slave
    master = [cplex.Cplex(master_orig) for t in range(tau)]
    slave = [cplex.Cplex(slave_orig) for t in range(tau)]
    # Cut activity levels
    cut_activity = [list() for t in range(tau)]

    for t in range(tau):
        if (not settings.print_cplex_output):
            master[t].set_results_stream(None)
            slave[t].set_results_stream(None)
        else:
            master[t].set_results_stream(output_stream)
            slave[t].set_results_stream(output_stream)

    # Start the PP SDDP algorithm!
    while (not stop):
        print('*** Major iteration after {:d} passes'.format(len(fwpass_sol)),
              file=output_stream)
        output_stream.flush()
        for num_pass in range(settings.num_passes):
            # Collect forward solution
            x, y, z = single_forward_step(settings, n1, n2, k, m, p, q, T_cols,
                                          [0] * n2, scenarios, prob,
                                          inverse_cdf, master[0],
                                          cut_activity[0])
            sol_x, sol_y, sol_z = [x], [y], [z]
            for t in range(1, tau):
                # One step at a time
                x, y, z = single_forward_step(settings, n1, n2, k, m, p, q,
                                              T_cols, sol_y[-1], scenarios,
                                              prob, inverse_cdf, master[t],
                                              cut_activity[t])
                sol_x.append(x)
                sol_y.append(y)
                sol_z.append(z)
            # Store all LP solutions
            fwpass_sol.append((sol_x, sol_y, sol_z))
            # Computation of upper bounds
            cost_sol.append(
                pb.cost_primal_sol(sol_x, sol_y, x_obj, y_obj, tau, delta))
            # Backward pass: collect Benders cuts
            for t in reversed(range(tau)):
                cuts = backward_pass(settings, n1, n2, k, m, p, q, 1, T_cols,
                                     scenarios, prob, [sol_x[t]], [sol_y[t]],
                                     [sol_z[t]], slave[t], num_pass)
                util.add_cuts(master[t], cuts)
        # -- end passes

        # Clean up LPs
        for t in range(1, tau):
            cut_activity[t] = util.purge_cuts(settings, m, p, q,
                                              cut_activity[t], master[t],
                                              slave[t])

        # Primal bound: compute cost based on samples
        ub = pb.primal_bound(settings, cost_sol)

        # Get current dual bound and compute optimality gap
        master[0].linear_constraints.set_rhs([(i, b[i]) for i in range(m)])
        master[0].solve()
        lb = master[0].solution.get_objective_value()
        gap = ((ub + settings.gap_absolute * gap_frac - lb) / (ub + 1.0e-10))

        # Report progress status
        print('Primal bound: {:f}  '.format(ub) +
              'Dual bound: {:f}  '.format(lb) +
              'gap {:.2f} %'.format(100 * gap),
              file=output_stream)
        output_stream.flush()
        cpu_time = time.clock() - start_time

        # Stop when the optimality gap is small enough
        if (tau >= settings.max_tau or cpu_time >= settings.max_cpu_time
                or gap <= settings.gap_relative
                or (ub - lb) <= settings.gap_absolute * (1 - gap_frac)):
            stop = True

    # -- end main loop

    # Print last solution and exit
    util.print_LP_solution(settings,
                           n1,
                           n2,
                           k,
                           master[0],
                           'Final solution of the master:',
                           output_stream=output_stream)

    total_time = time.clock() - start_time
    print(file=output_stream)
    print('Summary:', end=' ', file=output_stream)
    print('ub {:.3f} lb {:.3f} '.format(ub + settings.gap_absolute * gap_frac,
                                        lb) +
          'gap {:.2f} % tau {:d} '.format(100 * gap, tau) +
          'time {:.4f}'.format(total_time),
          file=output_stream)
    output_stream.flush()

    return (master[0], ub + settings.gap_absolute * gap_frac, lb, gap, tau,
            total_time)
Ejemplo n.º 3
0
def forward_pass(settings, n1, n2, k, m, p, q, tau, T_cols, b, d, w, scenarios,
                 prob, inverse_cdf, master, current_pass, cut_activity):
    """Perform the forward pass of the B^2 SDDP algorithm.

    Generate sample path and compute the corresponding LP solutions
    moving forward in time. LP solutions could be aggregated over
    several samples, but at the moment we do not do it.

    Parameters
    ----------
    settings : b2_sddp_settings.B2Settings
        Algorithmic settings.

    n1 : int
        Number of x variables of the problem.

    n2 : int
        Number of y variables of the problem.

    k : int
        Number of scenarios.

    m : int
        Number of rows in the first set of constraints
        A x + G y >= b + T y_{t-1}.

    p : int
        Number of rows in the second set of constraints D x >= d.

    q : int
        Number of rows in the third set of constraints W x >= w.

    tau : int
        The current length of the time horizon.

    T_cols : List[List[int], List[float]]
        The coefficients of the columns of the T matrix in A x + G y >=
        b + T y_{t-1}. These are in sparse form: indices and elements.

    b : List[float]
        The rhs of the master.

    d : List[float]
        The rhs of the second set of constraints in the master.

    w : List[float]
        The rhs of the third set of constraints in the master.

    scenarios : List[List[float]]
        The rhs vector (b, d, w) for each scenario.

    prob : List[float]
        The probability of each scenario.

    inverse_cdf : b2_sddp_utility.InverseCDF
        Inverse CDF of the probability distribution of the scenarios.

    master : cplex.Cplex
        Master problem.

    current_pass : int
        Identifier of the current pass.

    cut_activity : List[int]
        Number of consecutive rounds that a cut has been
        inactive. This will be updated at the end of the forward pass.

    Returns
    -------
    (List[List[float]], List[List[float]], List[List[float]])
        A triple containing the x, y, z components of the LP
        solutions. Each list will have length equal to tau, and
        contain the corresponding values of the solutions.
    """
    assert (isinstance(settings, B2Settings))
    assert (len(T_cols) == n2)
    assert (len(b) == m)
    assert (len(d) == p)
    assert (len(w) == q)
    assert (len(scenarios) == k)
    for i in range(k):
        assert (len(scenarios[i]) == m + p + q)
    assert (len(prob) == k)
    assert (isinstance(inverse_cdf, util.InverseCDF))
    assert (current_pass < settings.num_passes)

    # Numbering system for LPs written to file, for debugging
    lp_id = 0

    # Adjust length of cut activity vector
    num_rows = master.linear_constraints.get_num()
    if (len(cut_activity) < num_rows - (m + p + q)):
        cut_activity.extend([0] * (num_rows - (m + p + q) - len(cut_activity)))

    # Consider sample paths up to this stage
    sample_path = generate_sample_path(settings, tau, inverse_cdf)
    # Store solutions in these lists
    x = list()
    y = list()
    z = list()

    for t in range(tau):
        # Find current scenario index
        j = sample_path[t]
        if (t == 0):
            # The rhs value is the initial one, b
            master.linear_constraints.set_rhs([(i, b[i]) for i in range(m)])
            master.linear_constraints.set_rhs([(m + i, d[i])
                                               for i in range(p)])
            master.linear_constraints.set_rhs([(m + p + i, w[i])
                                               for i in range(q)])
        else:
            # Get rhs from the corresponding scenario, adding carried
            # over resources
            carry_over = util.col_matrix_vector_product(m, n2, T_cols, y[-1])
            rhs = ([scenarios[j][i] + carry_over[i]
                    for i in range(m)] + scenarios[j][m:])
            master.linear_constraints.set_rhs([(i, rhs[i])
                                               for i in range(m + p + q)])
        # Save problem for debugging
        if (settings.debug_save_lp):
            print('Writing problem master_' + str(tau) + '_' +
                  str(current_pass) + '_' + str(lp_id) + '.lp')
            master.write(
                'master_' + str(tau) + '_' + str(current_pass) + '_' +
                str(lp_id) + '.lp', 'lp')
            lp_id += 1

        master.solve()
        if (util.is_problem_optimal(settings, master)):
            # Save all solutions
            x.append(master.solution.get_values(0, n1 - 1))
            y.append(master.solution.get_values(n1, n1 + n2 - 1))
            z.append(master.solution.get_values(n1 + n2, n1 + n2 + k - 1))
            # Check cut activity
            dual = master.solution.get_dual_values(m + p + q, num_rows - 1)
            for (i, val) in enumerate(dual):
                if (abs(val) <= settings.eps_activity):
                    cut_activity[i] += 1
                else:
                    cut_activity[i] = 0

            if (settings.print_lp_solution_forward):
                util.print_LP_solution(
                    settings, n1, n2, k, master, 'FORWARD At stage ' +
                    '{:d}, '.format(t) + 'scenario {:d}'.format(j))
        else:
            print('Cannot  solve master problem; abort.')
            sys.exit()

    return (x, y, z)
Ejemplo n.º 4
0
def backward_pass(settings, n1, n2, k, m, p, q, tau, T_cols, scenarios, prob,
                  x, y, z, slave, current_pass):
    """Perform the backward pass of the B^2 SDDP algorithm.

    Generate Benders cuts moving backward in time using the given
    sequence of LP solutions. Cuts can be aggregated depending on the
    settings. They are automatically added to the slave problem.

    Parameters
    ----------
    settings : b2_sddp_settings.B2Settings
        Algorithmic settings.

    n1 : int
        Number of x variables of the problem.

    n2 : int
        Number of y variables of the problem.

    k : int
        Number of scenarios.

    m : int
        Number of rows in the first set of constraints 
        A x + G y >= b + T y_{t-1}.

    p : int
        Number of rows in the second set of constraints D x >= d.
    
    q : int
        Number of rows in the third set of constraints W x >= w.

    tau : int
        The current length of the time horizon.

    scenarios : List[List[float]]
        The rhs vector (b, d, w) for each scenario.

    prob : List[float]
        The probability of each scenario.

    x : List[List[float]]
        The values of the x component of the solution in the forward pass.

    y : List[List[float]]
        The values of the y component of the solution in the forward pass.

    z : List[List[float]]
        The values of the z component of the solution in the forward pass.

    slave : cplex.Cplex
        Cut generating problem (i.e. slave).

    current_pass : int
        Identifier of the current pass.

    Returns
    -------
    (List[b2_sddp_utility.CutData])
        A list of generated cuts, that should be added to the master.

    """
    assert (isinstance(settings, B2Settings))
    assert (len(T_cols) == n2)
    assert (len(scenarios) == k)
    for i in range(k):
        assert (len(scenarios[i]) == m + p + q)
    assert (len(prob) == k)
    assert (len(x) == tau)
    assert (len(y) == tau)
    assert (len(z) == tau)
    assert (current_pass < settings.num_passes)

    # Numbering system for LPs written to file, for debugging
    lp_id = 0

    # Store cuts for the master here
    pool = list()
    # Backward pass: collect Benders cuts
    for t in reversed(range(1, tau + 1)):
        # Pool of cuts from the current pass
        local_pool = list()
        # Scenario a cut was generated from
        local_pool_scenario = list()
        for j in range(k):
            # Construct rhs of the slave, remembering that we have the
            # optimality cut together with the resource constraints
            carry_over = util.col_matrix_vector_product(
                m, n2, T_cols, y[t - 1])
            rhs = ([scenarios[j][i] + carry_over[i]
                    for i in range(m)] + scenarios[j][m:])
            slave.linear_constraints.set_rhs([(i, rhs[i])
                                              for i in range(m + p + q)])
            slave.linear_constraints.set_rhs(m + p + q, -z[t - 1][j])
            m_with_cuts = slave.linear_constraints.get_num()
            rhs_with_cuts = slave.linear_constraints.get_rhs()
            # Save problem for debugging
            if (settings.debug_save_lp):
                print('Writing problem slave_' + str(tau) + '_' +
                      str(current_pass) + '_' + str(lp_id) + '.lp')
                slave.write(
                    'slave_' + str(tau) + '_' + str(current_pass) + '_' +
                    str(lp_id) + '.lp', 'lp')
                lp_id += 1
            slave.solve()
            if (util.is_problem_optimal(settings, slave) and
                    slave.solution.get_objective_value() >= settings.eps_opt):
                # The problem is infeasible; collect a cut in >= form.
                dual = slave.solution.get_dual_values()
                # The cut rhs depends on the scenario's rhs, and on
                # the part that comes from the Benders cuts.
                cut_rhs = sum(dual[i] * scenarios[j][i]
                              for i in range(m + p + q))
                cut_rhs += sum(dual[i] * rhs_with_cuts[i]
                               for i in range(m + p + q + 1, m_with_cuts))
                cut_coeff = util.vector_col_matrix_product(
                    m, n2, dual[:m], T_cols)
                # We usually normalize by the dual variable that
                # multiplies the z variable for the current scenario,
                # but if such variable is zero, we simply do not
                # normalize the cut.
                if (abs(dual[m + p + q]) > settings.eps_zero):
                    cut_rhs /= dual[m + p + q]
                    # Now generate the cut coefficients
                    cut_ind = [i for i in range(n1, n1 + n2)] + [n1 + n2 + j]
                    cut_elem = (
                        [-cut_coeff[i] / dual[m + p + q]
                         for i in range(n2)] + [1])
                else:
                    cut_ind = [i for i in range(n1, n1 + n2)]
                    cut_elem = [-cut_coeff[i] for i in range(n2)]
                cut = util.CutData(cut_ind, cut_elem, 'G', cut_rhs,
                                   'c_' + str(t) + '_' + str(j))

                local_pool.append(cut)
                local_pool_scenario.append(j)

                # Check violation
                if (settings.debug_check_violation):
                    primal_sol = x[t - 1] + y[t - 1] + z[t - 1]
                    lhs = sum(primal_sol[cut_ind[i]] * cut_elem[i]
                              for i in range(len(cut_ind)))
                    if (lhs - cut_rhs >= 0):
                        print('Cut not violated')
                        print('Cut {:f} >= {:f}'.format(lhs, cut_rhs))
                        print(local_pool[-1])
                        sys.exit()
                if (settings.print_lp_solution_backward
                        and util.is_problem_optimal(settings, slave)):
                    util.print_LP_solution(
                        settings, n1, n2, k, slave, 'BACKWARD At stage ' +
                        '{:d}, scenario {:d}'.format(t, j), True)

                # Add cut to the slave
                if (settings.add_cuts_immediately
                        and not settings.aggregate_cuts):
                    add_master_cut_to_slave(settings, n1, n2, k, cut, slave)

        if (settings.aggregate_cuts):
            # Define the aggregate cut as the surrogate of all the cuts
            if (local_pool):
                aggr_cut = generate_aggregate_cut(settings, n1, n2, k, prob,
                                                  local_pool,
                                                  local_pool_scenario,
                                                  'c_aggr_' + str(t))
                pool.append(aggr_cut)
                add_master_cut_to_slave(settings, n1, n2, k, aggr_cut, slave)
        else:
            # If the cut was not already added, we should add it now
            if (not settings.add_cuts_immediately):
                for cut in local_pool:
                    add_master_cut_to_slave(settings, n1, n2, k, cut, slave)
            pool.extend(local_pool)
    return pool
Ejemplo n.º 5
0
def single_forward_step(settings, n1, n2, k, m, p, q, T_cols, carry, scenarios,
                        prob, inverse_cdf, master, cut_activity):
    """Perform a single forward step of the B^2 SDDP algorithm.

    Sample a scenario and compute the corresponding LP solution.

    Parameters
    ----------
    settings : b2_sddp_settings.B2Settings
        Algorithmic settings.

    n1 : int
        Number of x variables of the problem.

    n2 : int
        Number of y variables of the problem.

    k : int
        Number of scenarios.

    m : int
        Number of rows in the first set of constraints
        A x + G y >= b + T y_{t-1}.

    p : int
        Number of rows in the second set of constraints D x >= d.

    q : int
        Number of rows in the third set of constraints W x >= w.

    T_cols : List[List[int], List[float]]
        The coefficients of the columns of the T matrix in A x + G y >=
        b + T y_{t-1}. These are in sparse form: indices and elements.

    carry : List[float]
        Carried over inventory from previous time period, i.e. y_{t-1}.

    scenarios : List[List[float]]
        The rhs vector (b, d, w) for each scenario.

    prob : List[float]
        The probability of each scenario.

    inverse_cdf : b2_sddp_utility.InverseCDF
        Inverse CDF of the probability distribution of the scenarios.

    master : cplex.Cplex
        Master problem.

    cut_activity : List[int]
        Number of consecutive rounds that a cut has been
        inactive. This will be updated at the end of the forward pass.

    Returns
    -------
    (List[float], List[float], List[float])
        A triple containing the x, y, z components of the LP
        solutions. 
    """
    assert (isinstance(settings, B2Settings))
    assert (len(T_cols) == n2)
    assert (len(carry) == n2)
    assert (len(scenarios) == k)
    for i in range(k):
        assert (len(scenarios[i]) == m + p + q)
    assert (len(prob) == k)
    assert (isinstance(inverse_cdf, util.InverseCDF))

    # Adjust length of cut activity vector
    num_rows = master.linear_constraints.get_num()
    if (len(cut_activity) < num_rows - (m + p + q)):
        cut_activity.extend([0] * (num_rows - (m + p + q) - len(cut_activity)))

    # Sample current scenario index
    j = inverse_cdf.at(random.uniform(0, 1))
    # Get rhs from the corresponding scenario, adding
    # carried over resources
    carry_over = util.col_matrix_vector_product(m, n2, T_cols, carry)
    rhs = ([scenarios[j][i] + carry_over[i]
            for i in range(m)] + scenarios[j][m:])

    master.linear_constraints.set_rhs([(i, rhs[i]) for i in range(m + p + q)])

    if (settings.debug_save_lp):
        print('Writing problem master_single_fp.lp')
        master.write('master_single_fp.lp', 'lp')

    master.solve()
    if (util.is_problem_optimal(settings, master)):
        # Save all solutions
        x = master.solution.get_values(0, n1 - 1)
        y = master.solution.get_values(n1, n1 + n2 - 1)
        z = master.solution.get_values(n1 + n2, n1 + n2 + k - 1)
        # Check cut activity
        dual = master.solution.get_dual_values(m + p + q, num_rows - 1)
        for (i, val) in enumerate(dual):
            if (abs(val) <= settings.eps_activity):
                cut_activity[i] += 1
            else:
                cut_activity[i] = 0

        # Report progress status
        if (settings.print_lp_solution_forward):
            util.print_LP_solution(
                settings, n1, n2, k, master, 'FORWARD At stage ' +
                '{:d}, '.format(t) + 'scenario {:d}'.format(j))
    else:
        print('Cannot  solve master problem; abort.')
        sys.exit()

    return (x, y, z)