Coverage for gpkit/nomials/array.py: 99%
128 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-28 11:39 -0400
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-28 11:39 -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)
68 out = (np.ndarray.__add__(self, other))
69 out.ast = ("add", astorder)
70 return out
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)
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