Coverage for gpkit/small_classes.py: 99%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Miscellaneous small classes"""
2from operator import xor
3from functools import reduce
4import numpy as np
5from .units import Quantity, qty # pylint: disable=unused-import
7Strings = (str,)
8Numbers = (int, float, np.number, Quantity)
11class FixedScalarMeta(type):
12 "Metaclass to implement instance checking for fixed scalars"
13 def __instancecheck__(cls, obj):
14 return getattr(obj, "hmap", None) and len(obj.hmap) == 1 and not obj.vks
17class FixedScalar(metaclass=FixedScalarMeta): # pylint: disable=no-init
18 "Instances of this class are scalar Nomials with no variables"
21class Count:
22 "Like python 2's itertools.count, for Python 3 compatibility."
23 def __init__(self):
24 self.count = -1
26 def next(self):
27 "Increment self.count and return it"
28 self.count += 1
29 return self.count
32def matrix_converter(name):
33 "Generates conversion function."
34 def to_(self): # used in tocoo, tocsc, etc below
35 "Converts to another type of matrix."
36 # pylint: disable=unused-variable
37 return getattr(self.tocsr(), "to"+name)()
38 return to_
41class CootMatrix:
42 "A very simple sparse matrix representation."
43 def __init__(self, row, col, data):
44 self.row, self.col, self.data = row, col, data
45 self.shape = [(max(self.row) + 1) if self.row else 0,
46 (max(self.col) + 1) if self.col else 0]
48 def __eq__(self, other):
49 return (self.row == other.row and self.col == other.col
50 and self.data == other.data and self.shape == other.shape)
52 tocoo = matrix_converter("coo")
53 tocsc = matrix_converter("csc")
54 todia = matrix_converter("dia")
55 todok = matrix_converter("dok")
56 todense = matrix_converter("dense")
58 def tocsr(self):
59 "Converts to a Scipy sparse csr_matrix"
60 from scipy.sparse import csr_matrix
61 return csr_matrix((self.data, (self.row, self.col)))
63 def dot(self, arg):
64 "Returns dot product with arg."
65 return self.tocsr().dot(arg)
68class SolverLog:
69 "Adds a `write` method to list so it's file-like and can replace stdout."
70 def __init__(self, output=None, *, verbosity=0):
71 self.written = ""
72 self.verbosity = verbosity
73 self.output = output
75 def write(self, writ):
76 "Append and potentially write the new line."
77 if writ[:2] == "b'":
78 writ = writ[2:-1]
79 self.written += writ
80 if self.verbosity > 0: # pragma: no cover
81 self.output.write(writ)
83 def lines(self):
84 "Returns the lines presently written."
85 return self.written.split("\n")
87 def flush(self):
88 "Dummy function for I/O api compatibility"
91class DictOfLists(dict):
92 "A hierarchy of dicionaries, with lists at the bottom."
94 def append(self, sol):
95 "Appends a dict (of dicts) of lists to all held lists."
96 if not hasattr(self, "initialized"):
97 _enlist_dict(sol, self)
98 self.initialized = True # pylint: disable=attribute-defined-outside-init
99 else:
100 _append_dict(sol, self)
102 def atindex(self, i):
103 "Indexes into each list independently."
104 return self.__class__(_index_dict(i, self, self.__class__()))
106 def to_arrays(self):
107 "Converts all lists into array."
108 _enray(self, self)
111def _enlist_dict(d_in, d_out):
112 "Recursively copies d_in into d_out, placing non-dict items into lists."
113 for k, v in d_in.items():
114 if isinstance(v, dict):
115 d_out[k] = _enlist_dict(v, v.__class__())
116 else:
117 d_out[k] = [v]
118 assert set(d_in.keys()) == set(d_out.keys())
119 return d_out
122def _append_dict(d_in, d_out):
123 "Recursively travels dict d_out and appends items found in d_in."
124 for k, v in d_in.items():
125 if isinstance(v, dict):
126 d_out[k] = _append_dict(v, d_out[k])
127 else:
128 try:
129 d_out[k].append(v)
130 except KeyError as e:
131 raise RuntimeWarning("Key `%s` was added after the first sweep."
132 % k) from e
133 return d_out
136def _index_dict(idx, d_in, d_out):
137 "Recursively travels dict d_in, placing items at idx into dict d_out."
138 for k, v in d_in.items():
139 if isinstance(v, dict):
140 d_out[k] = _index_dict(idx, v, v.__class__())
141 else:
142 try:
143 d_out[k] = v[idx]
144 except (IndexError, TypeError): # if not an array, return as is
145 d_out[k] = v
146 return d_out
149def _enray(d_in, d_out):
150 "Recursively turns lists into numpy arrays."
151 for k, v in d_in.items():
152 if isinstance(v, dict):
153 d_out[k] = _enray(v, v.__class__())
154 else:
155 if len(v) == 1:
156 v, = v
157 else:
158 v = np.array(v)
159 d_out[k] = v
160 return d_out
163class HashVector(dict):
164 """A simple, sparse, string-indexed vector. Inherits from dict.
166 The HashVector class supports element-wise arithmetic:
167 any undeclared variables are assumed to have a value of zero.
169 Arguments
170 ---------
171 arg : iterable
173 Example
174 -------
175 >>> x = gpkit.nomials.Monomial("x")
176 >>> exp = gpkit.small_classes.HashVector({x: 2})
177 """
178 hashvalue = None
180 def __hash__(self):
181 "Allows HashVectors to be used as dictionary keys."
182 if self.hashvalue is None:
183 self.hashvalue = reduce(xor, map(hash, self.items()), 0)
184 return self.hashvalue
186 def copy(self):
187 "Return a copy of this"
188 hv = self.__class__(self)
189 hv.hashvalue = self.hashvalue
190 return hv
192 def __pow__(self, other):
193 "Accepts scalars. Return Hashvector with each value put to a power."
194 if isinstance(other, Numbers):
195 return self.__class__({k: v**other for (k, v) in self.items()})
196 return NotImplemented
198 def __mul__(self, other):
199 """Accepts scalars and dicts. Returns with each value multiplied.
201 If the other object inherits from dict, multiplication is element-wise
202 and their key's intersection will form the new keys."""
203 try:
204 return self.__class__({k: v*other for (k, v) in self.items()})
205 except: # pylint: disable=bare-except
206 return NotImplemented
208 def __add__(self, other):
209 """Accepts scalars and dicts. Returns with each value added.
211 If the other object inherits from dict, addition is element-wise
212 and their key's union will form the new keys."""
213 if isinstance(other, Numbers):
214 return self.__class__({k: v + other for (k, v) in self.items()})
215 if isinstance(other, dict):
216 sums = self.copy()
217 for key, value in other.items():
218 if key in sums:
219 svalue = sums[key]
220 if value == -svalue:
221 del sums[key] # remove zeros created by addition
222 else:
223 sums[key] = value + svalue
224 else:
225 sums[key] = value
226 sums.hashvalue = None
227 return sums
228 return NotImplemented
230 # pylint: disable=multiple-statements
231 def __neg__(self): return -1*self
232 def __sub__(self, other): return self + -other
233 def __rsub__(self, other): return other + -self
234 def __radd__(self, other): return self + other
235 def __truediv__(self, other): return self * other**-1
236 def __rtruediv__(self, other): return other * self**-1
237 def __rmul__(self, other): return self * other
240EMPTY_HV = HashVector()