Hide keyboard shortcuts

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 

6 

7Strings = (str,) 

8Numbers = (int, float, np.number, Quantity) 

9 

10 

11class FixedScalarMeta(type): 

12 "Metaclass to implement instance checking for fixed scalars" 

13 def __instancecheck__(cls, obj): 

14 return hasattr(obj, "hmap") and len(obj.hmap) == 1 and not obj.vks 

15 

16 

17class FixedScalar(metaclass=FixedScalarMeta): # pylint: disable=no-init 

18 "Instances of this class are scalar Nomials with no variables" 

19 

20 

21class Count: 

22 "Like python 2's itertools.count, for Python 3 compatibility." 

23 def __init__(self): 

24 self.count = -1 

25 

26 def next(self): 

27 "Increment self.count and return it" 

28 self.count += 1 

29 return self.count 

30 

31 

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_ 

39 

40 

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] 

47 

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) 

51 

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") 

57 

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))) 

62 

63 def dot(self, arg): 

64 "Returns dot product with arg." 

65 return self.tocsr().dot(arg) 

66 

67 

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 

74 

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) 

83 

84 

85class DictOfLists(dict): 

86 "A hierarchy of dicionaries, with lists at the bottom." 

87 

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) 

95 

96 def atindex(self, i): 

97 "Indexes into each list independently." 

98 return self.__class__(_index_dict(i, self, self.__class__())) 

99 

100 def to_arrays(self): 

101 "Converts all lists into array." 

102 _enray(self, self) 

103 

104 

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 

114 

115 

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 

124 

125 

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 

137 

138 

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 

151 

152 

153class HashVector(dict): 

154 """A simple, sparse, string-indexed vector. Inherits from dict. 

155 

156 The HashVector class supports element-wise arithmetic: 

157 any undeclared variables are assumed to have a value of zero. 

158 

159 Arguments 

160 --------- 

161 arg : iterable 

162 

163 Example 

164 ------- 

165 >>> x = gpkit.nomials.Monomial("x") 

166 >>> exp = gpkit.small_classes.HashVector({x: 2}) 

167 """ 

168 hashvalue = None 

169 

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 

175 

176 def copy(self): 

177 "Return a copy of this" 

178 hv = self.__class__(self) 

179 hv.hashvalue = self.hashvalue 

180 return hv 

181 

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 

187 

188 def __mul__(self, other): 

189 """Accepts scalars and dicts. Returns with each value multiplied. 

190 

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 

197 

198 def __add__(self, other): 

199 """Accepts scalars and dicts. Returns with each value added. 

200 

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 

219 

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 

228 

229 

230EMPTY_HV = HashVector()