コード例 #1
0
    def _bound_dual(self, cur_lp: CyClpSimplex) -> CyClpSimplex:
        """ Place a bound on each index of the dual variable associated with the
        constraints of this node's LP relaxation and resolve. We do this by adding
        to each constraint i the slack variable 's_i' in the node's LP relaxation,
        and we give each new variable a large, positive coefficient in the objective.
        By duality, we get the desired dual LP constraints. Therefore, for nodes
        with infeasible primal LP relaxations and unbounded dual LP relaxations,
        resolving gives us a finite (albeit very large) dual solution, which can
        be used to parametrically lower bound the objective value of this node as
        we change its right hand side.

        :param cur_lp: the CyClpSimplex instance for which we want to bound its
        dual solution and resolve
        :return: A CyClpSimplex instance representing the same model as input,
        with the additions prescribed in the method description
        """

        # we add slack at end so user does not have to worry about adding to each node they create
        # and so we don't have to go back and update needlessly
        assert isinstance(cur_lp, CyClpSimplex), 'must give CyClpSimplex instance'
        for i, constr in enumerate(cur_lp.constraints):
            assert f's_{i}' not in [v.name for v in cur_lp.variables], \
                f"variable 's_{i}' is a reserved name. please name your variable something else"

        # cylp lacks (a documented) way to add a column, so rebuild the LP :[
        new_lp = CyClpSimplex()
        new_lp.logLevel = 0  # quiet output when resolving

        # recreate variables
        var_map = {v: new_lp.addVariable(v.name, v.dim) for v in cur_lp.variables}

        # bound them
        for orig_v, new_v in var_map.items():
            new_lp += CyLPArray(orig_v.lower) <= new_v <= CyLPArray(orig_v.upper)

        # re-add constraints, with slacks this time
        s = {}
        for i, constr in enumerate(cur_lp.constraints):
            s[i] = new_lp.addVariable(f's_{i}', constr.nRows)
            new_lp += s[i] >= CyLPArray(np.zeros(constr.nRows))
            new_lp += CyLPArray(constr.lower) <= \
                sum(constr.varCoefs[v] * var_map[v] for v in constr.variables) \
                + np.matrix(np.identity(constr.nRows))*s[i] <= CyLPArray(constr.upper)

        # set objective
        new_lp.objective = sum(
            CyLPArray(cur_lp.objectiveCoefficients[orig_v.indices]) * new_v for
            orig_v, new_v in var_map.items()
        ) + sum(self._M * v.sum() for v in s.values())

        # warm start
        orig_var_status, orig_slack_status = cur_lp.getBasisStatus()
        # each s_i at lower bound of 0 when added - status 3
        np.concatenate((orig_var_status, np.ones(sum(v.dim for v in s.values()))*3))
        new_lp.setBasisStatus(orig_var_status, orig_slack_status)

        # rerun and reassign
        new_lp.dual(startFinishOptions='x')
        return new_lp
コード例 #2
0
ファイル: phase_proc.py プロジェクト: deeplycloudy/pyart
def solve_cylp(model, B_vectors, weights, ray, chunksize):
    """
    Worker process for LP_solver_cylp_mp.

    Parameters
    ----------
    model : CyClpModel
        Model of the LP Problem, see :py:func:`LP_solver_cylp_mp`
    B_vectors : matrix
        Matrix containing B vectors, see :py:func:`construct_B_vectors`
    weights : array
        Weights.
    ray : int
        Starting ray.
    chunksize : int
        Number of rays to process.

    Returns
    -------
    soln : array
        Solution to LP problem.

    See Also
    --------
    LP_solver_cylp_mp : Parent function.
    LP_solver_cylp : Single Process Solver.

    """
    from cylp.cy.CyClpSimplex import CyClpSimplex
    from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray

    n_gates = weights.shape[1] // 2
    n_rays = B_vectors.shape[0]
    soln = np.zeros([chunksize, n_gates])

    # import LP model in solver
    s = CyClpSimplex(model)

    # disable logging in multiprocessing anyway
    s.logLevel = 0

    i = 0
    for raynum in range(ray, ray + chunksize):
        # set new B_vector values for actual ray
        s.setRowLowerArray(np.squeeze(np.asarray(B_vectors[raynum])))
        # set new weights (objectives) for actual ray
        s.setObjectiveArray(np.squeeze(np.asarray(weights[raynum])))
        # solve with dual method, it is faster
        s.dual()
        # extract primal solution
        soln[i, :] = s.primalVariableSolution['x'][n_gates: 2 * n_gates]
        i = i + 1

    return soln
コード例 #3
0
def solve_cylp(model, B_vectors, weights, ray, chunksize):
    """
    Worker process for LP_solver_cylp_mp.

    Parameters
    ----------
    model : CyClpModel
        Model of the LP Problem, see :py:func:`LP_solver_cylp_mp`
    B_vectors : matrix
        Matrix containing B vectors, see :py:func:`construct_B_vectors`
    weights : array
        Weights.
    ray : int
        Starting ray.
    chunksize : int
        Number of rays to process.

    Returns
    -------
    soln : array
        Solution to LP problem.

    See Also
    --------
    LP_solver_cylp_mp : Parent function.
    LP_solver_cylp : Single Process Solver.

    """
    from cylp.cy.CyClpSimplex import CyClpSimplex
    from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray

    n_gates = weights.shape[1] // 2
    n_rays = B_vectors.shape[0]
    soln = np.zeros([chunksize, n_gates])

    # import LP model in solver
    s = CyClpSimplex(model)

    # disable logging in multiprocessing anyway
    s.logLevel = 0

    i = 0
    for raynum in range(ray, ray + chunksize):
        # set new B_vector values for actual ray
        s.setRowLowerArray(np.squeeze(np.asarray(B_vectors[raynum])))
        # set new weights (objectives) for actual ray
        s.setObjectiveArray(np.squeeze(np.asarray(weights[raynum])))
        # solve with dual method, it is faster
        s.dual()
        # extract primal solution
        soln[i, :] = s.primalVariableSolution['x'][n_gates:2 * n_gates]
        i = i + 1

    return soln
コード例 #4
0
    def __init__(self: T,
                 lp: CyClpSimplex,
                 integer_indices: List[int],
                 idx: int = None,
                 lower_bound: Union[float, int] = -float('inf'),
                 b_idx: int = None,
                 b_dir: str = None,
                 b_val: float = None,
                 depth: int = 0,
                 ancestors: tuple = None):
        """
        :param lp: model object simplex is run against. Assumed Ax >= b
        :param idx: index of this node (e.g. in the branch and bound tree)
        :param integer_indices: indices of variables we aim to find integer solutions
        :param lower_bound: starting lower bound on optimal objective value
        for the minimization problem in this node
        :param b_idx: index of the branching variable
        :param b_dir: direction of branching
        :param b_val: initial value of the branching variable
        :param depth: how deep in the tree this node is
        :param ancestors: tuple of nodes that preceded this node (e.g. were branched
        on to create this node)
        """
        assert isinstance(lp, CyClpSimplex), 'lp must be CyClpSimplex instance'
        assert all(0 <= idx < lp.nVariables and isinstance(idx, int)
                   for idx in integer_indices), 'indices must match variables'
        assert idx is None or isinstance(
            idx, int), 'node idx must be integer if provided'
        assert len(set(integer_indices)) == len(integer_indices), \
            'indices must be distinct'
        assert isinstance(lower_bound, float) or isinstance(lower_bound, int), \
            'lower bound must be a float or an int'
        assert (b_dir is None) == (b_idx is None) == (b_val is None), \
            'none are none or all are none'
        assert b_idx in integer_indices or b_idx is None, \
            'branch index corresponds to integer variable if it exists'
        assert b_dir in ['right', 'left'] or b_dir is None, \
            'we can only branch right or left'
        if b_val is not None:
            good_left = 0 < b_val - lp.variablesUpper[b_idx] < 1
            good_right = 0 < lp.variablesLower[b_idx] - b_val < 1
            assert (b_dir == 'left' and good_left) or \
                   (b_dir == 'right' and good_right), 'branch val should be within 1 of both bounds'
        assert isinstance(depth,
                          int) and depth >= 0, 'depth is a positive integer'
        if ancestors is not None:
            assert isinstance(ancestors,
                              tuple), 'ancestors must be a tuple if provided'
            assert idx not in ancestors, 'idx cannot be an ancestor of itself'

        lp.logLevel = 0
        self.lp = lp
        self._integer_indices = integer_indices
        self.idx = idx
        self._var_indices = list(range(lp.nVariables))
        self._row_indices = list(range(lp.nConstraints))
        self.lower_bound = lower_bound
        self.objective_value = None
        self.solution = None
        self.lp_feasible = None
        self.unbounded = None
        self.mip_feasible = None
        self._epsilon = .0001
        self._b_dir = b_dir
        self._b_idx = b_idx
        self._b_val = b_val
        self.depth = depth
        self.search_method = 'best first'
        self.branch_method = 'most fractional'
        self.is_leaf = True
        ancestors = ancestors or tuple()
        idx_tuple = (idx, ) if idx is not None else tuple()
        self.lineage = ancestors + idx_tuple or None  # test all 4 ways this can pan out
コード例 #5
0
    def find_strong_disjunctive_cut(self, root_id: int) -> Tuple[CyLPArray, float]:
        """ Generate a strong cut valid for the disjunction encoded in the subtree
        rooted at node <root_id>. This cut is optimized to maximize the violation
        of the LP relaxation solution at node <root_id>

        see ISE 418 Lecture 13 slide 3, Lecture 14 slide 9, and Lecture 15 slides
        6-7 for derivation

        :param root_id: id of the node off which we will base the disjunction
        :return: a valid inequality (pi, pi0), i.e. pi^T x >= pi0 for all x in
        the convex hull of the disjunctive terms' LP relaxations
        """
        # sanity checks
        assert root_id in self.tree, 'parent must already exist in tree'
        root = self.tree.get_node_instances(root_id)
        # get each disjunctive term
        terminal_nodes = self.tree.get_leaves(root_id)
        # terminal nodes pruned for infeasibility do not expand disjunction, so remove them
        disjunctive_nodes = {n.idx: n for n in terminal_nodes if n.lp_feasible is not False}
        var_dicts = [{v.name: v.dim for v in n.lp.variables} for n in disjunctive_nodes.values()]
        assert all(var_dicts[0] == d for d in var_dicts), \
            'Each disjunctive term should have the same variables. The feature allowing' \
            ' otherwise remains to be developed.'

        # useful constants
        num_vars = sum(var_dim for var_dim in var_dicts[0].values())
        inf = root.lp.getCoinInfinity()

        # set infinite lower/upper bounds to 0 so they don't create numerical issues in constraints
        lb = {idx: CyLPArray([val if val > -inf else 0 for val in n.lp.variablesLower])
              for idx, n in disjunctive_nodes.items()}  # adjusted lower bound
        ub = {idx: CyLPArray([val if val < inf else 0 for val in n.lp.variablesUpper])
              for idx, n in disjunctive_nodes.items()}  # adjusted upper bound

        # set corresponding variables in cglp to 0 to reflect there is no bound
        # i.e. this variable should not exist in cglp
        wb = {idx: CyLPArray([inf if val > -inf else 0 for val in n.lp.variablesLower])
              for idx, n in disjunctive_nodes.items()}  # w bounds - variable on lb constraints
        vb = {idx: CyLPArray([inf if val < inf else 0 for val in n.lp.variablesUpper])
              for idx, n in disjunctive_nodes.items()}  # v bounds - variable on ub constraints

        # instantiate LP
        cglp = CyClpSimplex()
        cglp.logLevel = 0  # quiet output when resolving

        # declare variables (what to do with case when we have degenerate constraint)
        pi = cglp.addVariable('pi', num_vars)
        pi0 = cglp.addVariable('pi0', 1)
        u = {idx: cglp.addVariable(f'u_{idx}', n.lp.nConstraints) for idx, n in
             disjunctive_nodes.items()}
        w = {idx: cglp.addVariable(f'w_{idx}', n.lp.nVariables) for idx, n in
             disjunctive_nodes.items()}
        v = {idx: cglp.addVariable(f'v_{idx}', n.lp.nVariables) for idx, n in
             disjunctive_nodes.items()}

        # bound them
        for idx in disjunctive_nodes:
            cglp += u[idx] >= 0
            cglp += 0 <= w[idx] <= wb[idx]
            cglp += 0 <= v[idx] <= vb[idx]

        # add constraints
        for i, n in disjunctive_nodes.items():
            # (pi, pi0) must be valid for each disjunctive term's LP relaxation
            cglp += 0 >= -pi + n.lp.coefMatrix.T * u[i] + \
                np.matrix(np.eye(num_vars)) * w[i] - np.matrix(np.eye(num_vars)) * v[i]
            cglp += 0 <= -pi0 + CyLPArray(n.lp.constraintsLower) * u[i] + \
                lb[i] * w[i] - ub[i] * v[i]
        # normalize variables so they don't grow arbitrarily
        cglp += sum(var.sum() for var_dict in [u, w, v] for var in var_dict.values()) == 1

        # set objective: find the deepest cut
        # since pi * x >= pi0 for all x in disjunction, we want min pi * x_star - pi0
        cglp.objective = CyLPArray(root.solution) * pi - pi0

        # solve
        cglp.primal(startFinishOptions='x')
        assert cglp.getStatusCode() == 0, 'we should get optimal solution'
        assert cglp.objectiveValue <= 0, 'pi * x >= pi0 -> pi * x - pi0 >= 0 -> ' \
            'negative objective at x^* since it gets cut off'

        # get solution
        return cglp.primalVariableSolution['pi'], cglp.primalVariableSolution['pi0']
コード例 #6
0
ファイル: phase_proc.py プロジェクト: deeplycloudy/pyart
def LP_solver_cylp(A_Matrix, B_vectors, weights, really_verbose=False):
    """
    Solve the Linear Programming problem given in Giangrande et al, 2012 using
    the CyLP module.

    Parameters
    ----------
    A_Matrix : matrix
        Row augmented A matrix, see :py:func:`construct_A_matrix`
    B_vectors : matrix
        Matrix containing B vectors, see :py:func:`construct_B_vectors`
    weights : array
        Weights.
    really_verbose : bool
        True to print CLP messaging. False to suppress.

    Returns
    -------
    soln : array
        Solution to LP problem.

    See Also
    --------
    LP_solver_cvxopt : Solve LP problem using the CVXOPT module.
    LP_solver_pyglpk : Solve LP problem using the PyGLPK module.

    """
    from cylp.cy.CyClpSimplex import CyClpSimplex
    from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray

    n_gates = weights.shape[1] // 2
    n_rays = B_vectors.shape[0]
    soln = np.zeros([n_rays, n_gates])

    # Create CyLPModel and initialize it
    model = CyLPModel()
    G = np.matrix(A_Matrix)
    h = CyLPArray(np.empty(B_vectors.shape[1]))
    x = model.addVariable('x', G.shape[1])
    model.addConstraint(G * x >= h)
    #c = CyLPArray(np.empty(weights.shape[1]))
    c = CyLPArray(np.squeeze(weights[0]))
    model.objective = c * x

    # import model in solver
    s = CyClpSimplex(model)
    # disable logging
    if not really_verbose:
            s.logLevel = 0

    for raynum in range(n_rays):

        # set new B_vector values for actual ray
        s.setRowLowerArray(np.squeeze(np.asarray(B_vectors[raynum])))
        # set new weights (objectives) for actual ray
        #s.setObjectiveArray(np.squeeze(np.asarray(weights[raynum])))
        # solve with dual method, it is faster
        s.dual()
        # extract primal solution
        soln[raynum, :] = s.primalVariableSolution['x'][n_gates: 2 * n_gates]

    # apply smoothing filter on a per scan basis
    soln = smooth_and_trim_scan(soln, window_len=5, window='sg_smooth')
    return soln
コード例 #7
0
def LP_solver_cylp(A_Matrix, B_vectors, weights, really_verbose=False):
    """
    Solve the Linear Programming problem given in Giangrande et al, 2012 using
    the CyLP module.

    Parameters
    ----------
    A_Matrix : matrix
        Row augmented A matrix, see :py:func:`construct_A_matrix`
    B_vectors : matrix
        Matrix containing B vectors, see :py:func:`construct_B_vectors`
    weights : array
        Weights.
    really_verbose : bool
        True to print CLP messaging. False to suppress.

    Returns
    -------
    soln : array
        Solution to LP problem.

    See Also
    --------
    LP_solver_cvxopt : Solve LP problem using the CVXOPT module.
    LP_solver_pyglpk : Solve LP problem using the PyGLPK module.

    """
    from cylp.cy.CyClpSimplex import CyClpSimplex
    from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray

    n_gates = weights.shape[1] // 2
    n_rays = B_vectors.shape[0]
    soln = np.zeros([n_rays, n_gates])

    # Create CyLPModel and initialize it
    model = CyLPModel()
    G = np.matrix(A_Matrix)
    h = CyLPArray(np.empty(B_vectors.shape[1]))
    x = model.addVariable('x', G.shape[1])
    model.addConstraint(G * x >= h)
    #c = CyLPArray(np.empty(weights.shape[1]))
    c = CyLPArray(np.squeeze(weights[0]))
    model.objective = c * x

    # import model in solver
    s = CyClpSimplex(model)
    # disable logging
    if not really_verbose:
        s.logLevel = 0

    for raynum in range(n_rays):

        # set new B_vector values for actual ray
        s.setRowLowerArray(np.squeeze(np.asarray(B_vectors[raynum])))
        # set new weights (objectives) for actual ray
        #s.setObjectiveArray(np.squeeze(np.asarray(weights[raynum])))
        # solve with dual method, it is faster
        s.dual()
        # extract primal solution
        soln[raynum, :] = s.primalVariableSolution['x'][n_gates:2 * n_gates]

    # apply smoothing filter on a per scan basis
    soln = smooth_and_trim_scan(soln, window_len=5, window='sg_smooth')
    return soln