def __init__(self, x, p=None, max_denom=1024): """ Implementation details of geo_mean. Attributes ---------- w_dyad : tuple of ``Fractions`` whose denominators are all a power of two The dyadic completion of ``w``, which is used internally to form the inequalities representing the geometric mean. tree : ``dict`` keyed by dyadic tuples, whose values are Sequences of children. The children are also dyadic tuples. This represents the graph that needs to be formed to represent the weighted geometric mean. cone_lb : int A known lower bound (which is not always tight) on the number of cones needed to represent this geometric mean. cone_num_over : int The number of cones beyond the lower bound that this geometric mean used. If 0, we know that it used the minimum possible number of cones. Since cone_lb is not always tight, it may be using the minimum number of cones even if cone_num_over is not 0. cone_num : int The number of second order cones used to form this geometric mean """ super(geo_mean, self).__init__(x) x = self.args[0] if x.is_vector(): n = 1 if x.ndim == 0 else max(x.shape) else: raise ValueError('x must be a row or column vector.') if p is None: p = [1]*n if len(p) != n: raise ValueError('x and p must have the same number of elements.') if any(v < 0 for v in p) or sum(p) <= 0: raise ValueError('powers must be nonnegative and not all zero.') self.w, self.w_dyad = fracify(p, max_denom) self.approx_error = approx_error(p, self.w) self.tree = decompose(self.w_dyad) # known lower bound on number of cones needed to represent w_dyad self.cone_lb = lower_bound(self.w_dyad) # number of cones used past known lower bound self.cone_num_over = over_bound(self.w_dyad, self.tree) # number of cones used self.cone_num = self.cone_lb + self.cone_num_over
def gm_constrs(t_expr, x_exprs, p): assert power_tools.is_weight(p) w = power_tools.dyad_completion(p) tree = power_tools.decompose(w) # Sigh Python variable scoping.. gm_vars = [0] def create_gm_var(): var = epi_var(t_expr, "gm_var_%d" % gm_vars[0]) gm_vars[0] += 1 return var d = defaultdict(create_gm_var) d[w] = t_expr if len(x_exprs) < len(w): x_exprs += [t_expr] assert len(x_exprs) == len(w) for i, (p, v) in enumerate(zip(w, x_exprs)): if p > 0: tmp = [0]*len(w) tmp[i] = 1 d[tuple(tmp)] = v constraints = [] for elem, children in tree.items(): if 1 not in elem: constraints += [gm(d[elem], d[children[0]], d[children[1]])] return constraints
def gm_constrs(t_expr, x_exprs, p): assert power_tools.is_weight(p) w = power_tools.dyad_completion(p) tree = power_tools.decompose(w) # Sigh Python variable scoping.. gm_vars = [0] def create_gm_var(): var = epi_var(t_expr, "gm_var_%d" % gm_vars[0]) gm_vars[0] += 1 return var d = defaultdict(create_gm_var) d[w] = t_expr if len(x_exprs) < len(w): x_exprs += [t_expr] assert len(x_exprs) == len(w) for i, (p, v) in enumerate(zip(w, x_exprs)): if p > 0: tmp = [0] * len(w) tmp[i] = 1 d[tuple(tmp)] = v constraints = [] for elem, children in list(tree.items()): if 1 not in elem: constraints += [gm(d[elem], d[children[0]], d[children[1]])] return constraints
def transform_geo_mean(expr): w = [Fraction(x.a, x.b) for x in expr.geo_mean_params.w] w_dyad = [Fraction(x.a, x.b) for x in expr.geo_mean_params.w_dyad] tree = power_tools.decompose(w_dyad) t = epi_var(expr, "geo_mean") x = only_arg(expr) x_list = [expression.index(x, i, i+1) for i in range(len(w))] return t, gm_constrs(t, x_list, w)
def transform_geo_mean(expr): w = [Fraction(x.a, x.b) for x in expr.geo_mean_params.w] w_dyad = [Fraction(x.a, x.b) for x in expr.geo_mean_params.w_dyad] tree = power_tools.decompose(w_dyad) t = epi_var(expr, "geo_mean") x = only_arg(expr) x_list = [expression.index(x, i, i + 1) for i in range(len(w))] return t, gm_constrs(t, x_list, w)