Coverage for gpkit/nomials/array.py: 99%

128 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-03 16:47 -0500

1"""Module for creating NomialArray instances. 

2 

3 Example 

4 ------- 

5 >>> x = gpkit.Monomial('x') 

6 >>> px = gpkit.NomialArray([1, x, x**2]) 

7 

8""" 

9from operator import eq, le, ge, xor 

10from functools import reduce # pylint: disable=redefined-builtin 

11import numpy as np 

12from .map import NomialMap 

13from ..small_classes import HashVector, EMPTY_HV 

14from ..small_scripts import try_str_without 

15from ..constraints import ArrayConstraint 

16from ..repr_conventions import ReprMixin 

17 

18@np.vectorize 

19def vec_recurse(element, function, *args, **kwargs): 

20 "Vectorizes function with particular args and kwargs" 

21 return function(element, *args, **kwargs) 

22 

23 

24def array_constraint(symbol, func): 

25 "Return function which creates constraints of the given operator." 

26 vecfunc = np.vectorize(func) 

27 

28 def wrapped_func(self, other): 

29 "Creates array constraint from vectorized operator." 

30 if not isinstance(other, NomialArray): 

31 other = NomialArray(other) 

32 result = vecfunc(self, other) 

33 return ArrayConstraint(result, getattr(self, "key", self), 

34 symbol, getattr(other, "key", other)) 

35 return wrapped_func 

36 

37 

38class NomialArray(ReprMixin, np.ndarray): 

39 """A Numpy array with elementwise inequalities and substitutions. 

40 

41 Arguments 

42 --------- 

43 input_array : array-like 

44 

45 Example 

46 ------- 

47 >>> px = gpkit.NomialArray([1, x, x**2]) 

48 """ 

49 

50 def __mul__(self, other, *, reverse_order=False): 

51 astorder = (self, other) if not reverse_order else (other, self) 

52 out = NomialArray(np.ndarray.__mul__(self, other)) 

53 out.ast = ("mul", astorder) 

54 return out 

55 

56 def __truediv__(self, other): 

57 out = NomialArray(np.ndarray.__truediv__(self, other)) 

58 out.ast = ("div", (self, other)) 

59 return out 

60 

61 def __rtruediv__(self, other): 

62 out = (np.ndarray.__mul__(self**-1, other)) 

63 out.ast = ("div", (other, self)) 

64 return out 

65 

66 def __add__(self, other, *, reverse_order=False): 

67 astorder = (self, other) if not reverse_order else (other, self) 

68 out = (np.ndarray.__add__(self, other)) 

69 out.ast = ("add", astorder) 

70 return out 

71 

72 # pylint: disable=multiple-statements 

73 def __rmul__(self, other): return self.__mul__(other, reverse_order=True) 

74 def __radd__(self, other): return self.__add__(other, reverse_order=True) 

75 

76 def __pow__(self, expo): # pylint: disable=arguments-differ 

77 out = (np.ndarray.__pow__(self, expo)) # pylint: disable=too-many-function-args 

78 out.ast = ("pow", (self, expo)) 

79 return out 

80 

81 def __neg__(self): 

82 out = (np.ndarray.__neg__(self)) 

83 out.ast = ("neg", self) 

84 return out 

85 

86 def __getitem__(self, idxs): 

87 out = np.ndarray.__getitem__(self, idxs) 

88 if not getattr(out, "shape", None): 

89 return out 

90 out.ast = ("index", (self, idxs)) 

91 return out 

92 

93 def str_without(self, excluded=()): 

94 "Returns string without certain fields (such as 'lineage')." 

95 if "ast" not in excluded and self.ast: 

96 return self.parse_ast(excluded) 

97 if "key" not in excluded and hasattr(self, "key"): 

98 return self.key.str_without(excluded) # pylint: disable=no-member 

99 if not self.shape: 

100 return try_str_without(self.flatten()[0], excluded) 

101 

102 return "[%s]" % ", ".join( 

103 [try_str_without(np.ndarray.__getitem__(self, i), excluded) 

104 for i in range(self.shape[0])]) # pylint: disable=unsubscriptable-object 

105 

106 def latex(self, excluded=()): 

107 "Returns latex representation without certain fields." 

108 units = self.latex_unitstr() if "units" not in excluded else "" 

109 if hasattr(self, "key"): 

110 return self.key.latex(excluded) + units # pylint: disable=no-member 

111 return np.ndarray.__str__(self) 

112 

113 def __hash__(self): 

114 return reduce(xor, map(hash, self.flat), 0) 

115 

116 def __new__(cls, input_array): 

117 "Constructor. Required for objects inheriting from np.ndarray." 

118 # Input is an already formed ndarray instance cast to our class type 

119 return np.asarray(input_array).view(cls) 

120 

121 def __array_finalize__(self, obj): 

122 "Finalizer. Required for objects inheriting from np.ndarray." 

123 

124 def __array_wrap__(self, out_arr, context=None): # pylint: disable=arguments-differ 

125 """Called by numpy ufuncs. 

126 Special case to avoid creation of 0-dimensional arrays 

127 See http://docs.scipy.org/doc/numpy/user/basics.subclassing.html""" 

128 if out_arr.ndim: 

129 return np.ndarray.__array_wrap__(self, out_arr, context) # pylint: disable=too-many-function-args 

130 val = out_arr.item() 

131 return np.float(val) if isinstance(val, np.generic) else val 

132 

133 __eq__ = array_constraint("=", eq) 

134 __le__ = array_constraint("<=", le) 

135 __ge__ = array_constraint(">=", ge) 

136 

137 def inner(self, other): 

138 "Returns the array and argument's inner product." 

139 return NomialArray(np.inner(self, other)) 

140 

141 def outer(self, other): 

142 "Returns the array and argument's outer product." 

143 return NomialArray(np.outer(self, other)) 

144 

145 def vectorize(self, function, *args, **kwargs): 

146 "Apply a function to each terminal constraint, returning the array" 

147 return vec_recurse(self, function, *args, **kwargs) 

148 

149 def sub(self, subs, require_positive=True): 

150 "Substitutes into the array" 

151 return self.vectorize(lambda nom: nom.sub(subs, require_positive)) 

152 

153 def sum(self, *args, **kwargs): # pylint: disable=arguments-differ 

154 "Returns a sum. O(N) if no arguments are given." 

155 if not self.size: 

156 raise ValueError("cannot sum NomialArray of size 0") 

157 if args or kwargs or not self.shape: 

158 return np.ndarray.sum(self, *args, **kwargs) 

159 hmap = NomialMap() 

160 for p in self.flat: # pylint:disable=not-an-iterable 

161 if not hmap and hasattr(p, "units"): 

162 hmap.units = p.units 

163 if hasattr(p, "hmap"): 

164 hmap += p.hmap 

165 else: 

166 if hasattr(p, "units"): 

167 p = p.to(hmap.units).magnitude 

168 elif hmap.units and p and not np.isnan(p): 

169 p /= float(hmap.units) 

170 hmap[EMPTY_HV] = p + hmap.get(EMPTY_HV, 0) 

171 out = Signomial(hmap) 

172 out.ast = ("sum", (self, None)) 

173 return out 

174 

175 def prod(self, *args, **kwargs): # pylint: disable=arguments-differ 

176 "Returns a product. O(N) if no arguments and only contains monomials." 

177 if not self.size: 

178 raise ValueError("cannot prod NomialArray of size 0") 

179 if args or kwargs: 

180 return np.ndarray.prod(self, *args, **kwargs) 

181 c, exp = 1.0, HashVector() 

182 hmap = NomialMap() 

183 for m in self.flat: # pylint:disable=not-an-iterable 

184 try: 

185 (mexp, mc), = m.hmap.items() 

186 except (AttributeError, ValueError): 

187 return np.ndarray.prod(self, *args, **kwargs) 

188 c *= mc 

189 exp += mexp 

190 if m.units: 

191 hmap.units = (hmap.units or 1) * m.units 

192 hmap[exp] = c 

193 out = Signomial(hmap) 

194 out.ast = ("prod", (self, None)) 

195 return out 

196 

197 

198from .math import Signomial # pylint: disable=wrong-import-position