Source code for cvxpy.reductions.chain

from cvxpy import settings as s
from cvxpy.reductions.reduction import Reduction


def _compose_id_map(step_maps):
    """Compose a sequence of ``{old_id: [new_id, ...]}`` mappings.

    Each step map comes from a reduction's ``var_id_map`` or
    ``param_id_map``.  The result is a single mapping from the
    outermost original IDs to lists of the innermost final IDs.
    """
    result = {}
    for step_map in step_maps:
        # Forward existing mappings through this step (1-to-many capable).
        result = {
            orig_id: [new_id for cur_id in cur_ids
                             for new_id in step_map.get(cur_id, [cur_id])]
            for orig_id, cur_ids in result.items()
        }
        # Add new mappings introduced by this reduction step.
        for orig_id in step_map.keys() - result.keys():
            result[orig_id] = list(step_map[orig_id])
    return result


[docs] class Chain(Reduction): """A logical grouping of multiple reductions into a single reduction. Attributes ---------- reductions : list[Reduction] A list of reductions. """ def __init__(self, problem=None, reductions=None) -> None: super(Chain, self).__init__(problem=problem) self.reductions = [] if reductions is None else reductions def __str__(self): return str(self.reductions) def __repr__(self) -> str: return "Chain(reductions=%s)" % repr(self.reductions) def get(self, reduction_type): for reduction in self.reductions: if isinstance(reduction, reduction_type): return reduction raise KeyError
[docs] def accepts(self, problem) -> bool: """A problem is accepted if the sequence of reductions is valid. In particular, the i-th reduction must accept the output of the i-1th reduction, with the first reduction (self.reductions[0]) in the sequence taking as input the supplied problem. Parameters ---------- problem : Problem The problem to check. Returns ------- bool True if the chain can be applied, False otherwise. """ for r in self.reductions: if not r.accepts(problem): return False problem, _ = r.apply(problem) return True
[docs] def apply(self, problem, verbose: bool = False): """Applies the chain to a problem and returns an equivalent problem. Parameters ---------- problem : Problem The problem to which the chain will be applied. verbose : bool, optional Whehter to print verbose output. Returns ------- Problem or dict The problem yielded by applying the reductions in sequence, starting at self.reductions[0]. list The inverse data yielded by each of the reductions. """ inverse_data = [] for r in self.reductions: if verbose: s.LOGGER.info('Applying reduction %s', type(r).__name__) problem, inv = r.apply(problem) inverse_data.append(inv) return problem, inverse_data
[docs] def compose_var_id_map(self): """Compose variable ID mappings across all reductions. Returns a single ``{orig_var_id: [final_var_id, ...]}`` dict that accounts for every variable replacement in the chain. If reduction *i* maps ``A → [A']`` and reduction *j* (j > i) maps ``A' → [A'']``, the result contains ``A → [A'']``. If a step maps ``A' → [A'', A''']``, the result expands to ``A → [A'', A''']``. Returns ------- dict Maps original variable IDs to lists of their final (innermost) IDs. """ return _compose_id_map(r.var_id_map for r in self.reductions)
[docs] def compose_param_id_map(self): """Compose parameter ID mappings across all reductions. Returns a single ``{orig_param_id: [final_param_id, ...]}`` dict that accounts for every parameter replacement in the chain. Returns ------- dict Maps original parameter IDs to lists of their final (innermost) IDs. """ return _compose_id_map(r.param_id_map for r in self.reductions)
[docs] def compose_constr_id_map(self): """Compose constraint ID mappings across all reductions. Returns a single ``{orig_constr_id: final_constr_id}`` dict tracing each original constraint ID through every reduction step. Each reduction exposes its mapping via the ``cons_id_map`` property, which is populated during ``apply()``. Returns ------- dict Maps original constraint IDs to their final (innermost) IDs. """ result = {} for r in self.reductions: step_map = r.cons_id_map if step_map: result = {orig: step_map.get(cur, cur) for orig, cur in result.items()} for orig_id, new_id in step_map.items(): if orig_id not in result: result[orig_id] = new_id return result
[docs] def invert(self, solution, inverse_data): """Returns a solution to the original problem given the inverse_data. """ for r, inv in reversed(list(zip(self.reductions, inverse_data))): solution = r.invert(solution, inv) return solution