-
Notifications
You must be signed in to change notification settings - Fork 40
/
array.py
205 lines (171 loc) · 7.43 KB
/
array.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
"""Module for creating NomialArray instances.
Example
-------
>>> x = gpkit.Monomial('x')
>>> px = gpkit.NomialArray([1, x, x**2])
"""
from operator import eq, le, ge, xor
from functools import reduce # pylint: disable=redefined-builtin
import numpy as np
from .map import NomialMap
from ..small_classes import Numbers, HashVector, EMPTY_HV
from ..small_scripts import try_str_without, mag
from ..constraints import ArrayConstraint
from ..repr_conventions import ReprMixin
from ..exceptions import DimensionalityError
@np.vectorize
def vec_recurse(element, function, *args, **kwargs):
"Vectorizes function with particular args and kwargs"
return function(element, *args, **kwargs)
def array_constraint(symbol, func):
"Return function which creates constraints of the given operator."
vecfunc = np.vectorize(func)
def wrapped_func(self, other):
"Creates array constraint from vectorized operator."
if not isinstance(other, NomialArray):
other = NomialArray(other)
result = vecfunc(self, other)
return ArrayConstraint(result, getattr(self, "key", self),
symbol, getattr(other, "key", other))
return wrapped_func
class NomialArray(ReprMixin, np.ndarray):
"""A Numpy array with elementwise inequalities and substitutions.
Arguments
---------
input_array : array-like
Example
-------
>>> px = gpkit.NomialArray([1, x, x**2])
"""
def __mul__(self, other, *, reverse_order=False):
astorder = (self, other) if not reverse_order else (other, self)
out = NomialArray(np.ndarray.__mul__(self, other))
out.ast = ("mul", astorder)
return out
def __truediv__(self, other):
out = NomialArray(np.ndarray.__truediv__(self, other))
out.ast = ("div", (self, other))
return out
def __rtruediv__(self, other):
out = (np.ndarray.__mul__(self**-1, other))
out.ast = ("div", (other, self))
return out
def __add__(self, other, *, reverse_order=False):
astorder = (self, other) if not reverse_order else (other, self)
out = (np.ndarray.__add__(self, other))
out.ast = ("add", astorder)
return out
# pylint: disable=multiple-statements
def __rmul__(self, other): return self.__mul__(other, reverse_order=True)
def __radd__(self, other): return self.__add__(other, reverse_order=True)
def __pow__(self, expo): # pylint: disable=arguments-differ
out = (np.ndarray.__pow__(self, expo)) # pylint: disable=too-many-function-args
out.ast = ("pow", (self, expo))
return out
def __neg__(self):
out = (np.ndarray.__neg__(self))
out.ast = ("neg", self)
return out
def __getitem__(self, idxs):
out = np.ndarray.__getitem__(self, idxs)
if not getattr(out, "shape", None):
return out
out.ast = ("index", (self, idxs))
return out
def str_without(self, excluded=()):
"Returns string without certain fields (such as 'lineage')."
if self.ast:
return self.parse_ast(excluded)
if hasattr(self, "key"):
return self.key.str_without(excluded) # pylint: disable=no-member
if not self.shape:
return try_str_without(self.flatten()[0], excluded)
return "[%s]" % ", ".join(
[try_str_without(np.ndarray.__getitem__(self, i), excluded)
for i in range(self.shape[0])]) # pylint: disable=unsubscriptable-object
def latex(self, excluded=()):
"Returns latex representation without certain fields."
units = self.latex_unitstr() if "units" not in excluded else ""
if hasattr(self, "key"):
return self.key.latex(excluded) + units # pylint: disable=no-member
return np.ndarray.__str__(self)
def __hash__(self):
return reduce(xor, map(hash, self.flat), 0)
def __new__(cls, input_array):
"Constructor. Required for objects inheriting from np.ndarray."
# Input is an already formed ndarray instance cast to our class type
return np.asarray(input_array).view(cls)
def __array_finalize__(self, obj):
"Finalizer. Required for objects inheriting from np.ndarray."
def __array_wrap__(self, out_arr, context=None): # pylint: disable=arguments-differ
"""Called by numpy ufuncs.
Special case to avoid creation of 0-dimensional arrays
See http://docs.scipy.org/doc/numpy/user/basics.subclassing.html"""
if out_arr.ndim:
return np.ndarray.__array_wrap__(self, out_arr, context) # pylint: disable=too-many-function-args
val = out_arr.item()
return np.float(val) if isinstance(val, np.generic) else val
__eq__ = array_constraint("=", eq)
__le__ = array_constraint("<=", le)
__ge__ = array_constraint(">=", ge)
def outer(self, other):
"Returns the array and argument's outer product."
return NomialArray(np.outer(self, other))
def vectorize(self, function, *args, **kwargs):
"Apply a function to each terminal constraint, returning the array"
return vec_recurse(self, function, *args, **kwargs)
def sub(self, subs, require_positive=True):
"Substitutes into the array"
return self.vectorize(lambda nom: nom.sub(subs, require_positive))
@property
def units(self):
"""units must have same dimensions across the entire nomial array"""
units = None
for el in self.flat: # pylint: disable=not-an-iterable
el_units = getattr(el, "units", None)
if units is None:
units = el_units
elif ((el_units and units != el_units) or
(isinstance(el, Numbers) and not (el == 0 or np.isnan(el)))):
raise DimensionalityError(el_units, units)
return units
def sum(self, *args, **kwargs): # pylint: disable=arguments-differ
"Returns a sum. O(N) if no arguments are given."
if args or kwargs or not self.shape:
return np.ndarray.sum(self, *args, **kwargs)
hmap = NomialMap()
hmap.units = self.units
it = np.nditer(self, flags=["multi_index", "refs_ok"])
while not it.finished:
m = self[it.multi_index]
it.iternext()
if isinstance(mag(m), Numbers):
if mag(m):
hmap[EMPTY_HV] = mag(m) + hmap.get(EMPTY_HV, 0)
else:
hmap += m.hmap
out = Signomial(hmap)
out.ast = ("sum", (self, None))
return out
def prod(self, *args, **kwargs): # pylint: disable=arguments-differ
"Returns a product. O(N) if no arguments and only contains monomials."
if args or kwargs:
return np.ndarray.prod(self, *args, **kwargs)
c, unitpower = 1.0, 0
exp = HashVector()
it = np.nditer(self, flags=["multi_index", "refs_ok"])
while not it.finished:
m = self[it.multi_index]
it.iternext()
if not hasattr(m, "hmap") and len(m.hmap) == 1:
return np.ndarray.prod(self, *args, **kwargs)
c *= mag(m.c)
unitpower += 1
exp += m.exp
hmap = NomialMap({exp: c})
units = self.units
hmap.units = units**unitpower if units else None
out = Signomial(hmap)
out.ast = ("prod", (self, None))
return out
from .math import Signomial # pylint: disable=wrong-import-position