Hot-keys on this page
r m x p 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(list):
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 list.__init__(self)
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 if writ != "\n":
80 self.append(writ.rstrip("\n"))
81 if self.verbosity > 0: # pragma: no cover
82 self.output.write(writ)
85class DictOfLists(dict):
86 "A hierarchy of dicionaries, with lists at the bottom."
88 def append(self, sol):
89 "Appends a dict (of dicts) of lists to all held lists."
90 if not hasattr(self, "initialized"):
91 _enlist_dict(sol, self)
92 self.initialized = True # pylint: disable=attribute-defined-outside-init
93 else:
94 _append_dict(sol, self)
96 def atindex(self, i):
97 "Indexes into each list independently."
98 return self.__class__(_index_dict(i, self, self.__class__()))
100 def to_arrays(self):
101 "Converts all lists into array."
102 _enray(self, self)
105def _enlist_dict(d_in, d_out):
106 "Recursively copies d_in into d_out, placing non-dict items into lists."
107 for k, v in d_in.items():
108 if isinstance(v, dict):
109 d_out[k] = _enlist_dict(v, v.__class__())
110 else:
111 d_out[k] = [v]
112 assert set(d_in.keys()) == set(d_out.keys())
113 return d_out
116def _append_dict(d_in, d_out):
117 "Recursively travels dict d_out and appends items found in d_in."
118 for k, v in d_in.items():
119 if isinstance(v, dict):
120 d_out[k] = _append_dict(v, d_out[k])
121 else:
122 d_out[k].append(v)
123 return d_out
126def _index_dict(idx, d_in, d_out):
127 "Recursively travels dict d_in, placing items at idx into dict d_out."
128 for k, v in d_in.items():
129 if isinstance(v, dict):
130 d_out[k] = _index_dict(idx, v, v.__class__())
131 else:
132 try:
133 d_out[k] = v[idx]
134 except (IndexError, TypeError): # if not an array, return as is
135 d_out[k] = v
136 return d_out
139def _enray(d_in, d_out):
140 "Recursively turns lists into numpy arrays."
141 for k, v in d_in.items():
142 if isinstance(v, dict):
143 d_out[k] = _enray(v, v.__class__())
144 else:
145 if len(v) == 1:
146 v, = v
147 else:
148 v = np.array(v)
149 d_out[k] = v
150 return d_out
153class HashVector(dict):
154 """A simple, sparse, string-indexed vector. Inherits from dict.
156 The HashVector class supports element-wise arithmetic:
157 any undeclared variables are assumed to have a value of zero.
159 Arguments
160 ---------
161 arg : iterable
163 Example
164 -------
165 >>> x = gpkit.nomials.Monomial("x")
166 >>> exp = gpkit.small_classes.HashVector({x: 2})
167 """
168 hashvalue = None
170 def __hash__(self):
171 "Allows HashVectors to be used as dictionary keys."
172 if self.hashvalue is None:
173 self.hashvalue = reduce(xor, map(hash, self.items()), 0)
174 return self.hashvalue
176 def copy(self):
177 "Return a copy of this"
178 hv = self.__class__(self)
179 hv.hashvalue = self.hashvalue
180 return hv
182 def __pow__(self, other):
183 "Accepts scalars. Return Hashvector with each value put to a power."
184 if isinstance(other, Numbers):
185 return self.__class__({k: v**other for (k, v) in self.items()})
186 return NotImplemented
188 def __mul__(self, other):
189 """Accepts scalars and dicts. Returns with each value multiplied.
191 If the other object inherits from dict, multiplication is element-wise
192 and their key's intersection will form the new keys."""
193 try:
194 return self.__class__({k: v*other for (k, v) in self.items()})
195 except: # pylint: disable=bare-except
196 return NotImplemented
198 def __add__(self, other):
199 """Accepts scalars and dicts. Returns with each value added.
201 If the other object inherits from dict, addition is element-wise
202 and their key's union will form the new keys."""
203 if isinstance(other, Numbers):
204 return self.__class__({k: v + other for (k, v) in self.items()})
205 if isinstance(other, dict):
206 sums = self.copy()
207 for key, value in other.items():
208 if key in sums:
209 svalue = sums[key]
210 if value == -svalue:
211 del sums[key] # remove zeros created by addition
212 else:
213 sums[key] = value + svalue
214 else:
215 sums[key] = value
216 sums.hashvalue = None
217 return sums
218 return NotImplemented
220 # pylint: disable=multiple-statements
221 def __neg__(self): return -1*self
222 def __sub__(self, other): return self + -other
223 def __rsub__(self, other): return other + -self
224 def __radd__(self, other): return self + other
225 def __truediv__(self, other): return self * other**-1
226 def __rtruediv__(self, other): return other * self**-1
227 def __rmul__(self, other): return self * other
230EMPTY_HV = HashVector()