Package pod
[frames] | no frames]

Source Code for Package pod

  1  """ 
  2  Framework for performing a Proper Orthogonal Decomposition (POD). 
  3   
  4  Useful references: 
  5    - http://en.wikipedia.org/wiki/Homogeneous_coordinates 
  6    - http://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations 
  7   
  8  Usage example: 
  9   
 10  >>> import pod 
 11  >>> refs = [ [-4.0, -1.0], 
 12  >>>          [-2.0, -1.0], 
 13  >>>          [3.0, 4.0] ] 
 14  >>> target = [-2.0, 1.5] 
 15  >>> decomposition = pod.decompose(target, refs, epsilon=1E-6, max_iter=90) 
 16  >>> print decomposition.get_decomposition() 
 17  [-1.9999991745134178, 1.4999993808850638] 
 18  >>> print decomposition.get_reference_weights() 
 19  [0.96153806466991254, 0.0, 0.61538436138874408] 
 20   
 21  The example above shows the reconstruction of the target using 3 reference 
 22  signals, from which only reference 1 and reference 3 are useful (reference 2 
 23  is assigned a weight of 0). 
 24   
 25  @author: Christophe Alexandre <ch.alexandre at bluewin dot ch> 
 26   
 27  @license: 
 28  Copyright(C) 2010 Christophe Alexandre 
 29   
 30  This program is free software: you can redistribute it and/or modify 
 31  it under the terms of the GNU Lesser General Public License as published by 
 32  the Free Software Foundation, either version 3 of the License, or 
 33  (at your option) any later version. 
 34   
 35  This program is distributed in the hope that it will be useful, 
 36  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 37  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 38  GNU General Public License for more details. 
 39   
 40  You should have received a copy of the GNU Lesser General Public License 
 41  along with this program.  If not, see <http://www.gnu.org/licenses/lgpl.txt>. 
 42  """ 
 43  __all__ = ['decompose', 'combined_distance', 'DecompositionBasic'] 
 44   
 45  import os 
 46  import logging 
 47   
 48  import vecspace 
 49  from util import NullHandler 
 50   
 51  _h = NullHandler() 
 52  _logger = logging.getLogger('pod') 
 53  _logger.addHandler(_h) 
 54           
55 -def combined_distance(generator_weight):
56 """ 57 Distance function used for ordering the projections. 58 59 A weight of 0.0 defines the distance to the projected point, while a weight 60 of 1.0 defines the distance relative to the point generating the line. 61 62 At each iteration step the current point is projected onto the closest of 63 all lines. 64 65 @param generator_weight: how much weight is assigned to the generator point 66 @type generator_weight: float usually in range [0.0, 1.0] 67 """ 68 69 def func(start_point, projected_point, 70 generator_point, w=generator_weight): 71 d1 = start_point.sub(generator_point).norm() 72 d2 = start_point.sub(projected_point).norm() 73 return w * d1 + (1.0 - w) * d2
74 75 return func 76
77 -def decompose(source, references, 78 epsilon=1E-10, max_iter=20, 79 max_factors=None, max_weight=None, 80 distance=combined_distance(0.0) 81 ):
82 """ 83 Decomposing the source using the proposed reference points. 84 85 @param source: input point 86 @type source: list 87 @param references: list of reference points 88 @type references: list 89 @param epsilon: limit of the error sequence for stopping iteration 90 @type epsilon: float 91 @param max_iter: safeguard for stopping iteration 92 @type max_iter: int 93 @param max_factors: limit for the number of reference points, None allowing to use all of them 94 @type max_factors: int 95 @param distance: function used for finding the closest line to project on 96 @type distance: a function of start point, projected point, generator point 97 @return: decomposition details 98 @rtype: IterativeDecomposition 99 """ 100 r = BaseDecomposition(references, epsilon, max_iter, max_factors, max_weight) 101 r.resolve(source) 102 return r
103
104 -class IterativeDecomposition(object):
105 """ 106 Decomposition interface definition. 107 """ 108
109 - def __init__(self, references, epsilon=1E-10, max_iter=20, max_factors=None):
110 assert len(references) > 0, 'at least one reference is required' 111 dim = len(references[0]) 112 for r in references: 113 assert len(r) == dim 114 115 self._vector_space = vecspace.VectorSpace(dim) 116 self._reference_points = [] 117 self._ignores = [] 118 for count, r in enumerate(references): 119 ref = self._vector_space.define_point(*r) 120 if ref in self._reference_points: 121 logging.warning('filtered out redundant reference %d' % count) 122 self._ignores.append(ref) 123 124 elif ref.norm() == 0.0: 125 logging.warning('filtered out reference at origin %d' % count) 126 self._ignores.append(ref) 127 128 self._reference_points.append(ref) 129 130 self._weights = dict() 131 for p in self._reference_points: 132 self._weights[p] = 0.0 133 134 self._epsilon = epsilon 135 self._start = None 136 self._max_iter = max_iter 137 if max_factors is None: 138 self._max_factors = len(self._reference_points) 139 140 else: 141 self._max_factors = max_factors 142 143 self._error_norm = None
144
145 - def _compute_decomposition(self):
146 """ 147 Computes current decomposition result on the fly. 148 149 @return: the current relicate 150 @rtype: linalg.Point 151 """ 152 decomposition = self._vector_space.origin 153 for d in self._weights.keys(): 154 w_d = self._weights[d] 155 decomposition = decomposition.add(d.scale(w_d)) 156 return decomposition
157
158 - def resolve(self, point):
159 """ 160 Iterates decomposition until convergence or max iteration is reached. 161 162 @param point: coordinates of the point to be decompositiond 163 @type point: list 164 @return: coordinates of decompositiond point 165 @rtype: list 166 """ 167 pass
168
169 - def get_reference_weight(self, position):
170 """ 171 Returns the weight assigned to the reference provided in the constructor 172 at the indicated position. 173 174 @param position: position of the reference in the list provided in the constructor 175 @type position: int 176 """ 177 ref = self._reference_points[position] 178 return self._weights[ref]
179
180 - def get_reference_weights(self):
181 """ 182 Returns the weights assigned to the references in order to construct the 183 proposed input. 184 """ 185 return [self._weights[self._reference_points[i]] 186 for i in range(len(self._reference_points))]
187
188 - def get_decomposition(self):
189 """ 190 Returns the result of the decomposition process. 191 192 @return: decomposition result 193 @rtype: list 194 """ 195 return self._compute_decomposition().get_data()
196
197 - def get_error_norm(self):
198 """ 199 Returns a measure of the decomposition error. 200 201 @return: length of the difference between the result and the initial point 202 @rtype: float 203 """ 204 return self._compute_decomposition().sub(self._start).norm()
205
206 - def get_principal_component(self, rank):
207 """ 208 Returns the rank-th reference influencing the input variable 209 (main component: rank = 0), multiplied by its assigned weight. 210 211 @param rank: the rank of the reference (0 means principal component) 212 @return: a reference vector 213 """ 214 ref_weights = self.get_reference_weights() 215 sorted_weights = [(pos - 1, weight) 216 for pos, weight in enumerate(ref_weights)] 217 sorted_weights.sort(lambda w1, w2: cmp(abs(w2[1]), abs(w1[1]))) 218 max_abs_weight_pos = sorted_weights[rank][0] 219 weight = sorted_weights[rank][1] 220 main_component = self._reference_points[max_abs_weight_pos].scale(weight) 221 return main_component.get_data()
222
223 - def get_principal_component_index(self, rank):
224 """ 225 Returns the position in the initial reference list of the rank-th 226 reference influencing the input variable (main component: rank = 0). 227 228 @param rank: the rank of the reference (0 means principal component) 229 @return: position in the initial reference list 230 """ 231 ref_weights = self.get_reference_weights() 232 sorted_weights = [(pos - 1, weight) 233 for pos, weight in enumerate(ref_weights)] 234 sorted_weights.sort(lambda w1, w2: cmp(abs(w2[1]), abs(w1[1]))) 235 max_abs_weight_pos = sorted_weights[rank][0] 236 return max_abs_weight_pos
237
238 - def __repr__(self):
239 out = 'reference points:' + os.linesep 240 for p in self._reference_points: 241 out += str(p) + os.linesep 242 out += 'weightings:' + os.linesep 243 out += str(self._weights) 244 return out
245
246 -class BaseDecomposition(IterativeDecomposition):
247 """ 248 Decomposition on a set of reference points. 249 """ 250
251 - def __init__(self, references, 252 epsilon=1E-10, 253 max_iter=20, 254 max_factors=None, 255 max_weight=None, 256 distance=combined_distance(0.0)):
257 """ 258 @param distance: function of start point, projected point and generator point 259 """ 260 IterativeDecomposition.__init__(self, references, epsilon, max_iter, max_factors) 261 self._max_weight = max_weight 262 self._distance = distance
263
264 - def _project_point(self, point, reference_points):
265 """ Projects onto the closest of the straight lines defined by 266 the reference points. 267 """ 268 # computes projection of source point to each subspace defined by ref points 269 origin = self._vector_space.origin 270 if point.sub(origin).norm() <= self._epsilon: 271 # already matched: do nothing 272 return point 273 274 #_logger.debug('projecting ' + str(point)) 275 projections = dict() 276 distances = dict() 277 for ref in reference_points: 278 279 line = self._vector_space.define_line(ref, origin) 280 #_logger.debug('computing projection onto ' + str(line)) 281 ref_proj = line.project(point) 282 projections[ref] = ref_proj.projected 283 distances[ref] = self._distance(point, ref_proj.projected, ref) 284 _logger.debug('distance to reference %.3f' % distances[ref]) 285 286 ref_points = [] 287 for p in reference_points: 288 if self._max_weight is None: 289 ref_points.append(p) 290 291 else: 292 additional_weight = projections[p].units(p) 293 suggested_weight = self._weights[p] + additional_weight 294 if abs(suggested_weight) < self._max_weight: 295 ref_points.append(p) 296 297 if len(ref_points) == 0: 298 # no eligible point left 299 return point 300 301 # finds main driver (shortest distance to ref line) 302 def by_dist(ref1, ref2, d=distances): 303 return cmp(d[ref1], d[ref2])
304 305 ref_points.sort(by_dist) 306 307 closest = ref_points[0] 308 additional_weight = projections[closest].units(closest) 309 self._weights[closest] += additional_weight 310 _logger.debug('closest driver: %s, weight=%f' % (str(closest), self._weights[closest])) 311 312 return point.sub(projections[closest])
313
314 - def resolve(self, point):
315 """ 316 Iterates decomposition until convergence or max iteration is reached. 317 318 @param point: coordinates of the point to be decompositiond 319 @type point: list 320 @return: coordinates of decompositiond point 321 @rtype: list 322 """ 323 IterativeDecomposition.resolve(self, point) 324 _logger.debug(' ------------- STARTING PROCESS -------------') 325 target = self._vector_space.define_point(*point) 326 self._start = target 327 reference_points = [ref for ref in self._reference_points if ref not in self._ignores] 328 projector = self._project_point(target, reference_points) 329 diff = None 330 _logger.debug('distance to projection: %f' % projector.norm()) 331 #_logger.debug('drivers: ' + str(self._weights)) 332 i = 0 333 while (diff is None) or (diff > self._epsilon and i < self._max_iter): 334 i = i + 1 335 previous = self._compute_decomposition() 336 _logger.debug(' ------------- ITERATION %d -------------' % i) 337 projector = self._project_point(projector, reference_points) 338 enabled_drivers = [p for p in self._weights.keys() if abs(self._weights[p]) > 0.0] 339 drivers_count = len(enabled_drivers) 340 _logger.debug('number of drivers: %d' % drivers_count) 341 if drivers_count >= self._max_factors: 342 # limits number of drivers 343 _logger.debug('count limit reached for drivers: %d' % self._max_factors) 344 reference_points = enabled_drivers 345 346 decomposition = self._compute_decomposition() 347 diff = decomposition.sub(previous).norm() 348 _logger.debug('improvement: %f' % diff) 349 _logger.debug('distance to projection: %f' % projector.norm()) 350 #_logger.debug('drivers: ' + str(self._weights)) 351 _logger.debug('decomposition: ' + str(decomposition)) 352 353 _logger.debug('start:' + str(target)) 354 _logger.debug('diff:' + str(decomposition.sub(target))) 355 return decomposition.get_data()
356
357 -class DecompositionBasic(BaseDecomposition):
358
359 - def __init__(self, ref1, ref2, max_iter):
360 BaseDecomposition.__init__(self, [ref1, ref2], max_iter=max_iter)
361