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

## 128 statements

, created at 2022-07-28 12:35 -0400

1"""Module for creating NomialArray instances.

3 Example

4 -------

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

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

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

18@np.vectorize

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

20 "Vectorizes function with particular args and kwargs"

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

24def array_constraint(symbol, func):

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

26 vecfunc = np.vectorize(func)

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

38class NomialArray(ReprMixin, np.ndarray):

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

41 Arguments

42 ---------

43 input_array : array-like

45 Example

46 -------

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

48 """

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

56 def __truediv__(self, other):

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

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

59 return out

61 def __rtruediv__(self, other):

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

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

64 return out

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

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

70 return out

72 # pylint: disable=multiple-statements

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

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

81 def __neg__(self):

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

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

84 return out

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

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)

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

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)

113 def __hash__(self):

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

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)

121 def __array_finalize__(self, obj):

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

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

133 __eq__ = array_constraint("=", eq)

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

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

137 def inner(self, other):

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

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

141 def outer(self, other):

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

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

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)

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

150 "Substitutes into the array"

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

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

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

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