# Coverage for gpkit/nomials/map.py: 100%

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

## 121 statements

1"Implements the NomialMap class"

2from collections import defaultdict

3import numpy as np

4from .. import units

5from ..exceptions import DimensionalityError

6from ..small_classes import HashVector, Strings, qty, EMPTY_HV

7from .substitution import parse_subs

9DIMLESS_QUANTITY = qty("dimensionless")

12class NomialMap(HashVector):

13 """Class for efficent algebraic represention of a nomial

15 A NomialMap is a mapping between hashvectors representing exponents

16 and their coefficients in a posynomial.

18 For example, {{x : 1}: 2.0, {y: 1}: 3.0} represents 2*x + 3*y, where

19 x and y are VarKey objects.

20 """

21 units = None

22 expmap = None # used for monomial-mapping postsubstitution; see .mmap()

23 csmap = None # used for monomial-mapping postsubstitution; see .mmap()

25 def copy(self):

26 "Return a copy of this"

27 return self.__class__(self)

29 def units_of_product(self, thing, thing2=None):

30 "Sets units to those of `thing*thing2`. Ugly optimized code."

31 if thing is None and thing2 is None:

32 self.units = None

33 elif hasattr(thing, "units"):

34 if hasattr(thing2, "units"):

35 self.units, dimless_convert = units.of_product(thing, thing2)

36 if dimless_convert:

37 for key in self:

38 self[key] *= dimless_convert

39 else:

40 self.units = qty(thing.units)

41 elif hasattr(thing2, "units"):

42 self.units = qty(thing2.units)

43 elif thing2 is None and isinstance(thing, Strings):

44 self.units = qty(thing)

45 else:

46 self.units = None

48 def to(self, to_units):

49 "Returns a new NomialMap of the given units"

50 sunits = self.units or DIMLESS_QUANTITY

51 nm = self * sunits.to(to_units).magnitude # note that * creates a copy

52 nm.units_of_product(to_units) # pylint: disable=no-member

53 return nm

57 if self.units != other.units:

58 try:

59 other *= float(other.units/self.units)

60 except (TypeError, AttributeError): # if one of those is None

61 raise DimensionalityError(self.units, other.units)

63 hmap.units = self.units

64 return hmap

66 def diff(self, varkey):

67 "Differentiates a NomialMap with respect to a varkey"

68 out = NomialMap()

69 out.units_of_product(self.units,

70 1/varkey.units if varkey.units else None)

71 for exp in self:

72 if varkey in exp:

73 x = exp[varkey]

74 c = self[exp] * x

75 exp = exp.copy()

76 if x == 1:

77 exp.hashvalue ^= hash((varkey, 1))

78 del exp[varkey]

79 else:

80 exp.hashvalue ^= hash((varkey, x)) ^ hash((varkey, x-1))

81 exp[varkey] = x-1

82 out[exp] = c

83 return out

85 def sub(self, substitutions, varkeys, parsedsubs=False):

86 """Applies substitutions to a NomialMap

88 Parameters

89 ----------

90 substitutions : (dict-like)

91 list of substitutions to perform

93 varkeys : (set-like)

94 varkeys that are present in self

95 (required argument so as to require efficient code)

97 parsedsubs : bool

98 flag if the substitutions have already been parsed

99 to contain only keys in varkeys

101 """

102 # pylint: disable=too-many-locals, too-many-branches

103 if parsedsubs or not substitutions:

104 fixed = substitutions

105 else:

106 fixed, _, _ = parse_subs(varkeys, substitutions)

108 if not fixed:

109 if not self.expmap:

110 self.expmap, self.csmap = {exp: exp for exp in self}, {}

111 return self

113 cp = NomialMap()

114 cp.units = self.units

115 # csmap is modified during substitution, but keeps the same exps

116 cp.expmap, cp.csmap = {}, self.copy()

117 varlocs = defaultdict(set)

118 for exp, c in self.items():

119 new_exp = exp.copy()

120 cp.expmap[exp] = new_exp # cp modifies exps, so it needs new ones

121 cp[new_exp] = c

122 for vk in new_exp:

123 if vk in fixed:

126 squished = set()

127 for vk in varlocs:

128 exps, cval = varlocs[vk], fixed[vk]

129 if hasattr(cval, "hmap"):

130 if cval.hmap is None or any(cval.hmap.keys()):

131 raise ValueError("Monomial substitutions are not"

132 " supported.")

133 cval, = cval.hmap.to(vk.units or DIMLESS_QUANTITY).values()

134 elif hasattr(cval, "to"):

135 cval = cval.to(vk.units or DIMLESS_QUANTITY).magnitude

136 for o_exp, exp in exps:

137 subinplace(cp, exp, o_exp, vk, cval, squished)

138 return cp

140 def mmap(self, orig):

141 """Maps substituted monomials back to the original nomial

143 self.expmap is the map from pre- to post-substitution exponents, and

144 takes the form {original_exp: new_exp}

146 self.csmap is the map from pre-substitution exponents to coefficients.

148 m_from_ms is of the form {new_exp: [old_exps, ]}

150 pmap is of the form [{orig_idx1: fraction1, orig_idx2: fraction2, }, ]

151 where at the index corresponding to each new_exp is a dictionary

152 mapping the indices corresponding to the old exps to their

153 fraction of the post-substitution coefficient

154 """

155 pmap = [{} for _ in self]

156 origexps = list(orig.keys())

157 selfexps = list(self.keys())

158 for orig_exp, self_exp in self.expmap.items():

159 if self_exp not in self: # can occur in tautological constraints

160 continue # after substitution

161 fraction = self.csmap.get(orig_exp, orig[orig_exp])/self[self_exp]

162 orig_idx = origexps.index(orig_exp)

163 pmap[selfexps.index(self_exp)][orig_idx] = fraction

164 return pmap

167# pylint: disable=invalid-name

168def subinplace(cp, exp, o_exp, vk, cval, squished):

169 "Modifies cp by substituing cval/expval for vk in exp"

170 x = exp[vk]

171 powval = float(cval)**x if cval != 0 or x >= 0 else np.sign(cval)*np.inf

172 cp.csmap[o_exp] *= powval

173 if exp in cp:

174 c = cp.pop(exp)

175 exp.hashvalue ^= hash((vk, x)) # remove (key, value) from hashvalue

176 del exp[vk]

177 value = powval * c

178 if exp in cp:

180 currentvalue = cp[exp]

181 if value != -currentvalue:

182 cp[exp] += value

183 else:

184 del cp[exp] # remove zeros created during substitution

185 elif value:

186 cp[exp] = value

187 if not cp: # make sure it's never an empty hmap

188 cp[EMPTY_HV] = 0.0

189 elif exp in squished:

190 exp.hashvalue ^= hash((vk, x)) # remove (key, value) from hashvalue

191 del exp[vk]