-
Notifications
You must be signed in to change notification settings - Fork 40
/
array.py
198 lines (164 loc) · 7.24 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
"""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 HashVector, EMPTY_HV
from ..small_scripts import try_str_without
from ..constraints import ArrayConstraint
from ..repr_conventions import ReprMixin
@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 "ast" not in excluded and self.ast:
return self.parse_ast(excluded)
if "key" not in excluded and 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 inner(self, other):
"Returns the array and argument's inner product."
return NomialArray(np.inner(self, other))
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))
def sum(self, *args, **kwargs): # pylint: disable=arguments-differ
"Returns a sum. O(N) if no arguments are given."
if not self.size:
raise ValueError("cannot sum NomialArray of size 0")
if args or kwargs or not self.shape:
return np.ndarray.sum(self, *args, **kwargs)
hmap = NomialMap()
for p in self.flat: # pylint:disable=not-an-iterable
if not hmap and hasattr(p, "units"):
hmap.units = p.units
if hasattr(p, "hmap"):
hmap += p.hmap
else:
if hasattr(p, "units"):
p = p.to(hmap.units).magnitude
elif hmap.units and p and not np.isnan(p):
p /= float(hmap.units)
hmap[EMPTY_HV] = p + hmap.get(EMPTY_HV, 0)
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 not self.size:
raise ValueError("cannot prod NomialArray of size 0")
if args or kwargs:
return np.ndarray.prod(self, *args, **kwargs)
c, exp = 1.0, HashVector()
hmap = NomialMap()
for m in self.flat: # pylint:disable=not-an-iterable
try:
(mexp, mc), = m.hmap.items()
except (AttributeError, ValueError):
return np.ndarray.prod(self, *args, **kwargs)
c *= mc
exp += mexp
if m.units:
hmap.units = (hmap.units or 1) * m.units
hmap[exp] = c
out = Signomial(hmap)
out.ast = ("prod", (self, None))
return out
from .math import Signomial # pylint: disable=wrong-import-position