Coverage for gpkit\nomials\map.py: 0%

Shortcuts on this page

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 

8 

9DIMLESS_QUANTITY = qty("dimensionless") 

10 

11 

12class NomialMap(HashVector): 

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

14 

15 A NomialMap is a mapping between hashvectors representing exponents 

16 and their coefficients in a posynomial. 

17 

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() 

24 

25 def copy(self): 

26 "Return a copy of this" 

27 return self.__class__(self) 

28 

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 

47 

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 

54 

55 def __add__(self, other): 

56 "Adds NomialMaps together" 

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) 

62 hmap = HashVector.__add__(self, other) 

63 hmap.units = self.units 

64 return hmap 

65 

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 

84 

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

86 """Applies substitutions to a NomialMap 

87 

88 Parameters 

89 ---------- 

90 substitutions : (dict-like) 

91 list of substitutions to perform 

92 

93 varkeys : (set-like) 

94 varkeys that are present in self 

95 (required argument so as to require efficient code) 

96 

97 parsedsubs : bool 

98 flag if the substitutions have already been parsed 

99 to contain only keys in varkeys 

100 

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) 

107 

108 if not fixed: 

109 if not self.expmap: 

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

111 return self 

112 

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: 

124 varlocs[vk].add((exp, new_exp)) 

125 

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 

139 

140 def mmap(self, orig): 

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

142 

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

144 takes the form {original_exp: new_exp} 

145 

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

147 

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

149 

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 

165 

166 

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: 

179 squished.add(exp.copy()) 

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]