Package empro :: Package toolkit :: Module dataset
[frames] | no frames]

Source Code for Module empro.toolkit.dataset

   1  # Copyright 1983-2019 Keysight Technologies, Inc, Keysight Confidential 
   2  ''' 
   3  The dataset toolkit module helps in manipulating datasets and retrieving them  
   4  from simulation results. 
   5  ''' 
   6   
   7  import cmath as _cmath 
   8  import math as _math 
   9  import numbers as _numbers 
  10  import operator as _operator 
  11   
  12  import empro.datasource as _datasource 
  13  from empro import _deprecation 
  14  from empro.datasource import _complexComponentArithmetic, _componentTrigonometric, _componentTrigonometric2, _extendedComponentArithmetic 
  15   
  16  try: 
  17      from collections.abc import Mapping as _Mapping 
  18  except ImportError: 
  19      from collections import Mapping as _Mapping # Python 2.7 compatibility 
20 21 -def sin(x):
22 ''' 23 Return sine of x, with x in radians. 24 x can either be a real or a complex dataset. 25 ''' 26 try: 27 return _callBuiltin('sin', x) 28 except TypeError: 29 pass 30 x = _enforceDimensioned(x) 31 try: 32 re, im = x.real, x.imag 33 except AttributeError: 34 return _componentTrigonometric(x, 'Sine') 35 else: 36 return ComplexDataSet(sin(re) * cosh(im), cos(re) * sinh(im), name = 'sin( %s )' % x.name)
37
38 39 -def cos(x):
40 ''' 41 Return cosine of x, with x in radians. 42 x can either be a real or a complex dataset. 43 ''' 44 try: 45 return _callBuiltin('cos', x) 46 except TypeError: 47 pass 48 x = _enforceDimensioned(x) 49 try: 50 re, im = x.real, x.imag 51 except AttributeError: 52 return _componentTrigonometric(x, 'Cosine') 53 else: 54 return ComplexDataSet(cos(re) * cosh(im), -sin(re) * sinh(im), name = 'cos( %s )' % x.name)
55
56 57 -def tan(x):
58 ''' 59 Return tangent of x, with x in radians. 60 x can either be a real or a complex dataset. 61 ''' 62 try: 63 return _callBuiltin('tan', x) 64 except TypeError: 65 pass 66 x = _enforceDimensioned(x) 67 try: 68 re, im = x.real, x.imag 69 except AttributeError: 70 return _componentTrigonometric(x, 'Tangent') 71 else: 72 return _named(sin(x) / cos(x), 'tan( %s )' % x.name)
73
74 75 -def asin(x):
76 ''' 77 Return arcsine of x, in radians 78 x must be a real dataset. 79 ''' 80 try: 81 return _callBuiltin('asin', x) 82 except TypeError: 83 return _componentTrigonometric(_enforceDimensioned(x), 'ArcSine')
84
85 86 -def acos(x):
87 ''' 88 Return arccosine of x, in radians 89 x must be a real dataset. 90 ''' 91 try: 92 return _callBuiltin('acos', x) 93 except TypeError: 94 return _componentTrigonometric(_enforceDimensioned(x), 'ArcCosine')
95
96 97 -def atan(x):
98 ''' 99 Return arctangent of x, in radians 100 x must be a real dataset. 101 ''' 102 try: 103 return _callBuiltin('atan', x) 104 except TypeError: 105 return _componentTrigonometric(_enforceDimensioned(x), 'ArcTangent')
106
107 108 -def atan2(y, x):
109 ''' 110 Return arctangent of y/x in radians, and places the angle in the quadrant 111 of vector (x, y). Thus atan2(1, 1) == pi/4 but atan2(-1, -1) == -3*pi/4, 112 while atan(1 / 1) == atan(-1 / -1) == pi/4. 113 x and y must be a real datasets. 114 ''' 115 try: 116 return _math.atan2(y, x) 117 except TypeError: 118 return _componentTrigonometric2(y, x, 'ArcTangent2')
119
120 121 -def cosh(x):
122 ''' 123 Return hyperbolic cosine of x. 124 x can either be a real or a complex dataset. 125 ''' 126 try: 127 return _callBuiltin('cosh', x) 128 except TypeError: 129 x = _enforceDimensioned(x) 130 return _named(.5 * (exp(x) + exp(-x)), name='cosh( %s )' % x.name)
131
132 133 -def sinh(x):
134 ''' 135 Return hyperbolic sine of x. 136 x can either be a real or a complex dataset. 137 ''' 138 try: 139 return _callBuiltin('sinh', x) 140 except TypeError: 141 x = _enforceDimensioned(x) 142 return _named(.5 * (exp(x) - exp(-x)), name='sinh( %s )' % x.name)
143
144 145 -def tanh(x):
146 ''' 147 Return hyperbolic tangent of x. 148 x can either be a real or a complex dataset. 149 ''' 150 try: 151 return _callBuiltin('tanh', x) 152 except TypeError: 153 x = _enforceDimensioned(x) 154 a, b = exp(x), exp(-x) 155 return _named((a - b) / (a + b), name='tanh( %s )' % x.name)
156
157 158 -def ceil(x):
159 ''' 160 Return the smallest integer greater than or equal to x. 161 x must be a float or a real dataset. 162 ''' 163 try: 164 return _math.ceil(x) 165 except TypeError: 166 return _extendedComponentArithmetic(_enforceDimensioned(x), 'Ceil')
167
168 169 -def floor(x):
170 ''' 171 Return the greatest integer smaller than or equal to x. 172 x must be a float or a real dataset. 173 ''' 174 try: 175 return _math.floor(x) 176 except TypeError: 177 return _extendedComponentArithmetic(_enforceDimensioned(x), 'Floor')
178
179 180 -def exp(x):
181 ''' 182 Return e ** x. 183 x can either be a real or a complex dataset. 184 ''' 185 try: 186 return _callBuiltin('exp', x) 187 except TypeError: 188 pass 189 x = _enforceDimensioned(x) 190 try: 191 re, im = x.real, x.imag 192 except AttributeError: 193 return _extendedComponentArithmetic(x, 'Exp') 194 else: 195 return exp(re) * ComplexDataSet(cos(re), sin(im))
196
197 198 -def log(x, base=None):
199 ''' 200 Return logarithm of x. 201 If base is not specified, the natural logarithm is returned. 202 x can either be a real or a complex dataset. 203 If x is complex, the cut will extend from 0 along the negative x-axis. 204 ''' 205 try: 206 return _callBuiltin('log', x) 207 except TypeError: 208 pass 209 x = _enforceDimensioned(x) 210 try: 211 re, im = x.real, x.imag 212 except AttributeError: 213 y = _extendedComponentArithmetic(x, 'Log') 214 else: 215 y = ComplexDataSet(log(abs(x), base), phase(x), name='log( %s )' % x.name) 216 if not base is None: 217 y /= _math.log(float(base)) 218 y.name = 'log( %s, base=%s )' % (x.name, base) 219 return y
220
221 222 -def log10(x):
223 ''' 224 Return base-10 logarithm of x. 225 x can either be a real or a complex dataset. 226 If x is complex, the cut will extend from 0 along the negative x-axis. 227 ''' 228 try: 229 return _callBuiltin('log10', x) 230 except TypeError: 231 pass 232 x = _enforceDimensioned(x) 233 try: 234 re, im = x.real, x.imag 235 except AttributeError: 236 return _extendedComponentArithmetic(x, 'Log10') 237 else: 238 arg = _named(phase(x) / _math.log(10), 'Phase( %s ) / Log(10)' % x.name) 239 return ComplexDataSet(log10(abs(x)), arg, name='log10( %s )' % x.name)
240
241 242 -def sqrt(x):
243 ''' 244 Return square root of x. 245 x must be a float or real dataset. 246 ''' 247 try: 248 return _callBuiltin('sqrt', x) 249 except TypeError: 250 return _extendedComponentArithmetic(_enforceDimensioned(x), 'Sqrt')
251
252 253 -def phase(x):
254 ''' 255 Return phase of x: atan2(x.imag, x.real) 256 x must be a complex number or a complex dataset. 257 The result will be between -pi and pi 258 ''' 259 try: 260 return _math.atan2(x.imag, x.real) 261 except TypeError: 262 x = _enforceDimensioned(x) 263 return _named(atan2(_imag(x), _real(x)), 'Phase( %s )' % x.name)
264
265 266 -def hypot(x, y):
267 ''' 268 Return Euclidean norm: sqrt(x ** 2 + y ** 2) 269 x and y must be real datasets. 270 ''' 271 if _isComplex(x) or _isComplex(y): 272 raise TypeError, "x and y must be real numbers or real datasets" 273 try: 274 return _math.hypot(x, y) 275 except TypeError: 276 x = _enforceDimensioned(x) 277 y = _enforceDimensioned(y) 278 return sqrt(x * x + y * y)
279
280 281 -def fft(x, size=None, dimension=None):
282 assert _isDataSet(x), x 283 if dimension is None: 284 if x.numberOfDimensions() == 1: 285 dimension = 0 286 else: 287 dimension = "Time" 288 dimIndex = list(x.dimensions()).index(x.dimension(dimension)) 289 def make(complexPart): 290 id, name = _buildIdAndName("%s( Fft( %s ) )", complexPart[:2], x) 291 y = _datasource.Discrete1DFftDataSet(id, complexPart, dimIndex, name) 292 try: 293 re, im = x.real, x.imag 294 except AttributeError: 295 y.input("real").append(x) 296 else: 297 y.input("real").append(re) 298 y.input("imaginary").append(im) 299 y.unitClass = x.unitClass 300 y.dimensionUnitClass = 'FREQUENCY' 301 y.fftSize = size or len(x) 302 return y
303 re, im = map(make, ("Real", "Imaginary")) 304 return ComplexDataSet(re, im, name="Fft( %s )" % x.name) 305
306 307 -def interpolate(x, dimensions):
308 if isinstance(x, DataSetMatrix): 309 return DataSetMatrix({key: interpolate(ds, dimensions) for key, ds in x.items()}, name=x.name) 310 assert _isDataSet(x), x 311 try: 312 re, im = x.real, x.imag 313 except AttributeError: 314 pass 315 else: 316 return ComplexDataSet(interpolate(re, dimensions), interpolate(im, dimensions), x.name) 317 y = _datasource.InterpolatedDataSet(x.name, x.id + "'") 318 y.unitClass = x.unitClass 319 _addDimensions(y, dimensions) 320 y.checkCompatibility(x) 321 y.input("input").append(x) 322 return y
323
324 325 -def transform(operator, *args, **kwargs):
326 ''' 327 Applies an element-wise operation on datasets. 328 329 Example: 330 def myfunc(a, b): 331 if a < 0: 332 return 0 333 return b 334 A, B = getResult(...), getResult(...) 335 C = transform(myfunc, A, b=B) 336 ''' 337 from empro.datasource import TransformDataSet 338 from itertools import chain 339 allArguments = tuple(chain(args, kwargs.values())) 340 assert len(allArguments) > 0, "You must specify at least one additional dataset argument" 341 _assertCompatibleDatasets(*allArguments) 342 try: 343 opName = operator.func_name 344 except AttributeError: 345 opName = unicode(operator) 346 id, name = _buildIdAndName( 347 '%s( ' + ', '.join(['%s'] * len(args) + ['%s=%s'] * len(kwargs)) + ' )', 348 *([opName] + [a.name for a in args] + sum([[k, v.name] for k, v in kwargs.items()], []))) 349 ds = TransformDataSet(id, operator, name) 350 _addDimensions(ds, allArguments[0].dimensions()) 351 for arg in args: 352 ds.input(TransformDataSet.ArgsId).append(arg) 353 for key, arg in kwargs.items(): 354 ds.addInput(key) 355 ds.input(key).append(arg) 356 return ds
357
358 359 -def function(operator):
360 ''' 361 A decorator to transforms a element-wise operation into a function on datasets. 362 363 Example: 364 @function 365 def myfunc(a, b): 366 if a < 0: 367 return 0 368 return b 369 A, B = getResult(...), getResult(...) 370 C = myfunc(A, b=B) 371 ''' 372 def fun(*args, **kwargs): 373 return transform(operator, *args, **kwargs)
374 fun.__name__ = operator.__name__ 375 fun.__doc__ = operator.__doc__ 376 return fun 377
378 379 -def makeDataSet(sequence, id=None, name=None, dimensions=None, unitClass='SCALAR'):
380 ''' 381 makeDataSet(sequence, id=None, name=None, dimensions=None, unitClass='SCALAR') -> DataSet 382 - sequence: a Python sequence (list, tuple, ...) of floats or complex values. 383 - id: optional, if not given a hashvalue is used as id. 384 - name: optional, if not given, the id is used. 385 - dimensions: a list of datasets to be used a dimensions 386 - unitClass: a string. 387 ''' 388 from empro.datasource import ValueListDataSet 389 ys = tuple(sequence) 390 id = id or ('#%s' % hash(ys)) 391 name = name or id 392 if any(_isComplex(y) for y in ys): 393 re = makeDataSet(map(_real, ys), id='Re( %s )' % id, name='Re( %s )' % name, dimensions=dimensions) 394 im = makeDataSet(map(_imag, ys), id='Im( %s )' % id, name='Im( %s )' % name, dimensions=dimensions) 395 return ComplexDataSet(re, im, name) 396 ds = ValueListDataSet(id, ys, name or id) 397 ds.unitClass = unitClass 398 _addDimensions(ds, dimensions or []) 399 return ds
400
401 402 -def makeRange(start, stop=None, step=1., id=None, name=None, unitClass='SCALAR'):
403 ''' 404 makeRange([start, ] stop [, step]) -> DataSet 405 406 Similar to Python's built-in range() function, but creates a dataset. 407 In contrary to range(), here start, stop and step can be floats. 408 ''' 409 if stop is None: 410 stop = start 411 start = 0 412 assert stop > start and step > 0 413 n = int(_math.ceil(float(stop - start) / float(step))) 414 Xs = [start + k * step for k in xrange(n)] 415 return makeDataSet(Xs, id=id, name=name, unitClass=unitClass)
416
417 418 -def reduceDimensionsToIndex(x, **fixedDimensions):
419 ''' 420 reduceDimensionsToIndex(x, a=1, b=2, ...) -> y 421 422 reduce the number of dimensions by fixing some to a single value. 423 - x is a DataSet or ComplexDataSet 424 - a, b, ... are the names of the dimensions you want to fixate, like Time, Frequency, Theta, ... 425 - 1, 2, ... are the indices within that dimension to fixate it to. 426 ''' 427 assert _isDataSet(x), x 428 try: 429 re, im = x.real, x.imag 430 except AttributeError: 431 pass 432 else: 433 return ComplexDataSet(reduceDimensionsToIndex(re, **fixedDimensions), reduceDimensionsToIndex(im, **fixedDimensions), x.name) 434 y = _datasource.DimensionReducingDataSet(x.id, x.name) 435 y.unitClass = x.unitClass 436 y.input("input").append(x) 437 dimKs = {dim.name: k for k, dim in enumerate(x.dimensions())} 438 for name, index in fixedDimensions.items(): 439 y.reduceDimensionToIndex(dimKs[name],index) 440 return y
441
442 443 444 -def getResult(context=None, **kwargs):
445 ''' 446 getResult(context, sim=None, run=None, object=None, result=None, 447 timeDependence=None, fieldScatter=None, component=None, transform=None, 448 complexPart=None, interpolation=None)) -> dataset 449 450 Lookup and return a dataset from the tree of simulation results. 451 452 A dataset is found by narrowing down a query, starting from the context: 453 - If context is a string, it shall be a projectId: the project path. 454 - If context is empro.activeProject, its rootDir shall be the projectId. 455 - If context is a L{Simulation}, its path shall be used to preset the 456 projectId and simulationId. 457 - If context is a L{ResultQuery}, its fields shall be used as starting 458 point of the search, until the first override or ambiguity. 459 - If context is an existing dataset that has a query attribute, the 460 query shall be used as context. 461 462 The other arguments can be used to refine the query. If the context has 463 already set the same field (if context is a L{ResultQuery}), your explicit 464 arguments take precedence: 465 - sim -> query.simulationId: an integer or a string like '000001' 466 - run -> query.runId: an integer or a string like 'Run0001' 467 - object -> query.outputObjectId: name as string like 'Port1' or full id 468 as pair of strings like ('CircuitComponent', 'Port1') 469 - timeDependence -> query.timeDepencence: 'NoTimeDependence, 'Transient', 470 'SteadyState' or 'Broadband', ... 471 - result -> query.resultType: 'V', 'I', ... 472 - fieldScatter -> query.fieldScatter: string 473 - component -> query.resultComponent: 'Scalar', 'VectorMagnitude', ... 474 - transform -> query.dataTransform: 'NoTransform', 'Fft', 'Dft' 475 - complexPart -> query.complexPart: 'Complex', 'Real', 'Imag', ... 476 - interplation -> query.surfaceInterpolationResolution: string 477 478 If a field is not specified (neither in context, neither as additional 479 argument), and no good default value can be found, an AmbiguityError will 480 be raised. 481 482 Following keyword arguments are acccepted as additional options: 483 - name: the name of the returned dataset 484 - fftSize: an integer to specify the number of samples in the FFT 485 transformation. Only valid when transform='Fft' is used. 486 - indexRanges: a dictionary of dimension name and range pairs. 487 ''' 488 from empro.output import ResultDataSet 489 query, followQuery = _startQuery(context) 490 491 followQuery = _setSim(query, followQuery, **kwargs) 492 followQuery = _setRun(query, followQuery, **kwargs) 493 followQuery = _setObject(query, followQuery, **kwargs) 494 495 availableTimeDependences = filter(lambda x: x != 'NoTimeDependence', query.availableTimeDependenceValues()) 496 followQuery = _setKey(query, followQuery, "timeDependence", "timeDependence", \ 497 availableTimeDependences, defaults=['Transient', 'Broadband'], **kwargs) 498 499 followQuery = _setKey(query, followQuery, "result", "resultType", query.availableResultTypeValues(), **kwargs) 500 followQuery = _setKey(query, followQuery, "fieldScatter", "fieldScatter", query.availableFieldScatterValues(), defaults=['TotalField'], **kwargs) 501 followQuery = _setKey(query, followQuery, "component", "resultComponent", query.availableResultComponentValues(), defaults=['Scalar', 'VectorMagnitude', 'Total'], **kwargs) 502 followQuery = _setKey(query, followQuery, "transform", "dataTransform", query.availableDataTransformValues(), defaults=['NoTransform', 'Fft'], **kwargs) 503 504 complexParts = list(query.availableComplexPartValues()) 505 if 'RealPart' in complexParts and 'ImaginaryPart' in complexParts: 506 complexParts.append('Complex') 507 key, followQuery = _selectKey(query, followQuery, "complexPart", 'complexPart', complexParts, defaults=['NotComplex', 'Complex'], **kwargs) 508 if followQuery == _OVERRIDE_QUERY: 509 if key == 'Complex': 510 name = kwargs.get('name') or '' 511 kwargs['name'] = '' 512 kwargs['complexPart'] = 'RealPart' 513 real = getResult(context, **kwargs) 514 kwargs['complexPart'] = 'ImaginaryPart' 515 imag = getResult(context, **kwargs) 516 return ComplexDataSet(real, imag, name) 517 query.complexPart = key 518 519 _setKey(query, followQuery, "interpolation", "surfaceInterpolationResolution", query.availableSurfaceInterpolationResolutionValues(), defaults=['NoInterpolation'], **kwargs) 520 521 query.fftSize = kwargs.get('fftSize') or 0 522 indexRanges = kwargs.get('indexRanges', {}) 523 for dim in query.availableDimensionIds(): 524 query.setDimensionRange(dim, indexRanges.get(dim, (0, -1))) 525 return ResultDataSet(unicode(query), query, kwargs.get('name') or '')
526
527 528 -class AmbiguityError(LookupError):
529 ''' 530 Exception raised by getResult if better specification of the desired dataset is required. 531 '''
532 - def __init__(self, attribute, candidates, msg=None):
533 self.attribute = attribute 534 self.candidates = candidates 535 self.__msg = msg
536
537 - def __str__(self):
538 return unicode(self.__msg or "Ambiguous default choice for %r, specify one of %r" % (self.attribute, self.candidates))
539
540 541 -class ComplexDataSet(object):
542 ''' 543 Wrapper around two DataSets to emulate a single dataset of complex numbers. 544 545 ComplexDataSet has two attributes real and imag which are instances of 546 L{empro.datasource.DataSet}. They should be of the same dataType and 547 share the same dimensions. ComplexDataSet emulates much of DataSet's 548 interface, but for complex numbers instead of floats. 549 '''
550 - def __init__(self, real, imag, name=None):
551 ''' 552 ComplexDataSet(real, imag [, name=None]) 553 554 construct a complex dataset from two real ones sharing the same 555 dataType and dimensions. If name is None, one will be derived 556 from real and imag. 557 ''' 558 _assertCompatibleDatasets(real, imag) 559 def assertComplexPart(dataset, complexPart): 560 try: 561 q = dataset.query 562 except AttributeError: 563 return 564 assert q.complexPart == complexPart, "%r != %r" % (q.complexPart, complexPart)
565 assertComplexPart(real, 'RealPart') 566 assertComplexPart(imag, 'ImaginaryPart') 567 self._real, self._imag = real, imag 568 self.name = name
569 570 @property
571 - def real(self):
572 return self._real
573 574 @property
575 - def imag(self):
576 return self._imag
577 578 @property
579 - def query(self):
580 try: 581 return self._real.query 582 except AttributeError: 583 return self._imag.query
584
585 - def _getName(self):
586 if self._name: 587 return self._name 588 import re 589 r = re.match(r'^Re\((.*)\)$', self.real.name, re.I) 590 i = re.match(r'^Im\((.*)\)$', self.imag.name, re.I) 591 if r and i and r.group(1) == i.group(1): 592 return r.group(1).strip() 593 return '%s + j * %s' % (self.real.name, self.imag.name)
594 - def _setName(self, name):
595 self._name = name 596 if name: 597 self.real.name = 'Re( %s )' % name 598 self.imag.name = 'Im( %s )' % name
599 name = property(_getName, _setName) 600
601 - def flat(self, index):
602 return complex(self.real.flat(index), self.imag.flat(index))
603
604 - def at(self, *args):
605 return complex(self.real.at(*args), self.imag.at(*args))
606
607 - def atValue(self, *args):
608 return complex(self.real.atValue(*args), self.imag.atValue(*args))
609
610 - def dataType(self):
611 return self.real.dataType()
612 613 @property
614 - def unitClass(self):
615 return self.getUnitClass()
616
617 - def getUnitClass(self):
618 return self.real.getUnitClass()
619
620 - def bounds(self):
621 reMin, reMax = self.real.bounds() 622 imMin, imMax = self.imag.bounds() 623 return complex(reMin, imMin), complex(reMax, imMax)
624
625 - def numberOfDimensions(self):
626 return self.real.numberOfDimensions()
627
628 - def dimension(self, index):
629 return self.real.dimension(index)
630
631 - def dimensions(self):
632 return self.real.dimensions()
633
634 - def size(self):
635 return len(self.real)
636
637 - def conjugate(self):
638 return ComplexDataSet(self.real, -self.imag)
639
640 - def reciprocal(self):
641 return self.conjugate() / self.__absSquare()
642
643 - def __len__(self):
644 return self.size()
645
646 - def __getitem__(self, index):
647 return complex(self.real[index], self.imag[index])
648
649 - def __neg__(self):
650 return ComplexDataSet(-self.real, -self.imag, name = '-%s' % self.name)
651
652 - def __pos__(self):
653 return self
654
655 - def __abs__(self):
656 result = sqrt(self.__absSquare()) 657 result.name = '| %s |' % self.name 658 result.unitClass = self.real.unitClass 659 return result
660
661 - def __add__(self, other):
662 return self._componentWiseOperator(other, _operator.__add__, '+')
663
664 - def __sub__(self, other):
665 return self._componentWiseOperator(other, _operator.__sub__, '-')
666
667 - def __mul__(self, other):
668 return self._complexComponentArithmetic(other, _operator.__mul__, '*', 'Multiply')
669
670 - def __truediv__(self, other):
671 return self._complexComponentArithmetic(other, _operator.__truediv__, '/', 'Divide')
672
673 - def __div__(self, other):
674 return self.__truediv__(other)
675
676 - def __radd__(self, other):
677 return self + other
678
679 - def __rsub__(self, other):
680 return -self + other
681
682 - def __rmul__(self, other):
683 return self * other
684
685 - def __rtruediv__(self, other):
686 return _named(self.reciprocal() * other, "%s / %s" % (_name(other), self.name))
687
688 - def __rdiv__(self, other):
689 return self.__rtruediv__(other)
690
691 - def __eq__(self, other):
692 return self.real == other.real and self.imag == other.imag
693
694 - def __ne__(self, other):
695 return not (self == other)
696
697 - def _componentWiseOperator(self, other, op, opName):
698 name = self._resultName(other, opName) 699 # other is ComplexDataSet or builtin number 700 try: 701 return ComplexDataSet(op(self.real, other.real), op(self.imag, other.imag), name) 702 except AttributeError: 703 pass 704 # other is a DataSet and has a query to tell us if it is real or imaginary 705 try: 706 q = other.query 707 except AttributeError: 708 pass 709 else: 710 if q.complexPart == 'RealPart' or q.complexPart == 'NoComplex': 711 return ComplexDataSet(op(self.real, other), self.imag, name) 712 if q.complexPart == 'ImaginaryPart': 713 return ComplexDataSet(self.real, op(self.imag, other), name) 714 raise TypeError, "other has bad complexPart: %r" % q.complexPart 715 # we assume it is real 716 return ComplexDataSet(op(self.real, other), self.imag, name)
717
718 - def _complexComponentArithmetic(self, other, op, opName, opTag):
719 name = self._resultName(other, opName) 720 # try other as a real dataset or a float 721 try: 722 return ComplexDataSet(op(self.real, other), op(self.imag, other), name) 723 except TypeError: 724 pass 725 # try other as complex _number_ 726 if isinstance(other, complex): 727 if op == _operator.__truediv__: 728 other = 1 / other 729 else: 730 assert op == _operator.__mul__ 731 return ComplexDataSet(self.real * other.real - self.imag * other.imag, self.real * other.imag + self.imag * other.real, name=name) 732 # we assume other is a ComplexDataSet 733 from empro.datasource import _complexComponentArithmetic 734 reA, imA, reB, imB = self.real, self.imag, other.real, other.imag 735 re, im = [_complexComponentArithmetic(reA, imA, reB, imB, opTag, part) for part in ("Real", "Imaginary")] 736 return ComplexDataSet(re, im, name)
737
738 - def __absSquare(self):
739 return _named(self.real * self.real + self.imag * self.imag, '| %s | ** 2' % self.name)
740
741 - def _resultName(self, other, opName):
742 return "%s %s %s" % (self.name, opName, _name(other))
743
744 745 746 -class DataSetMatrix(_Mapping):
747 ''' 748 Groups a number of DataSets as a matrix 749 '''
750 - def __init__(self, datasets, name=None):
751 _assertCompatibleDatasets(*datasets.values()) 752 rows, cols = _calculateMatrixShape(datasets) 753 self._rows, self._cols = rows, cols 754 self._datasets = datasets 755 self._name = name 756 self._zero = _constant(0, self._datasets.values()[0])
757 758 @property
759 - def rows(self):
760 return self._rows
761 762 @property
763 - def cols(self):
764 return self._cols
765 766 @property
767 - def name(self):
768 return self._name
769
770 - def keys(self):
771 return self._datasets.keys()
772
773 - def dimensions(self):
774 return self._datasets.values()[0].dimensions()
775
776 - def isSquare(self):
777 return self.rows == self.cols
778
779 - def isDiagonal(self):
780 return self.isSquare() and all(row == col for row, col in self)
781
782 - def __getitem__(self, index):
783 ''' 784 self[row, col] -> DataSet 785 786 In case self is a column matrix, you can index it by just the row index: self[row] -> DataSet 787 ''' 788 return self._datasets.get(self.__normalizeIndex(index), self._zero)
789
790 - def __setitem__(self, index, dataset):
791 ''' 792 self[row, col] = dataset 793 794 In case self is a column matrix, you can index it by just the row index: self[row] = dataset 795 ''' 796 _assertCompatibleDatasets(self[index], dataset) 797 self._datasets[self.__normalizeIndex(index)] = dataset
798
799 - def __normalizeIndex(self, index):
800 try: 801 row, col = index 802 except (TypeError, ValueError): 803 assert len(self.cols) == 1, "You can only access items by single subscript in case of a column matrix" 804 row, col = index, self.cols[0] 805 if not row in self.rows and col in self.cols: 806 raise KeyError((row, col)) 807 return row, col
808
809 - def __contains__(self, index):
810 return index in self._datasets
811
812 - def __iter__(self):
813 return self._datasets.__iter__()
814
815 - def iteritems(self):
816 return self._datasets.iteritems()
817
818 - def __len__(self):
819 return len(self._datasets)
820
821 - def __pos__(self):
822 return self
823
824 - def __neg__(self):
825 return self.__unaryOperation(_operator.__neg__, name="-%s" % self.name)
826
827 - def __abs__(self):
828 return self.__unaryOperation(_operator.__abs__, name="Abs(%s)" % self.name)
829
830 - def __unaryOperation(self, op, name):
831 return DataSetMatrix({index: op(dataset) for index, dataset in self.items()}, name=name)
832
833 - def __add__(self, other):
834 return self._componentWiseOperator(other, _operator.__add__, '+')
835
836 - def __sub__(self, other):
837 return self._componentWiseOperator(other, _operator.__sub__, '-')
838
839 - def __radd__(self, other):
840 return self + other
841
842 - def __rsub__(self, other):
843 return -self + other
844
845 - def _componentWiseOperator(self, other, op, opName):
846 name = self._resultName(other, opName) 847 if isinstance(other, DataSetMatrix): 848 assert self.rows == other.rows and self.cols == other.cols 849 keys = set(self.keys()) | set(other.keys()) 850 return DataSetMatrix({index: op(self[index], other[index]) for index in keys}, name=name) 851 keys = _full_matrix_keys(self.rows, self.cols) 852 return DataSetMatrix({index: op(self[index], other) for index in keys}, name=name)
853
854 - def __mul__(self, other):
855 if not isinstance(other, DataSetMatrix): 856 return DataSetMatrix({index: dataset * other for index, dataset in self.items()}, name="%s * %s" % (self.name, other)) 857 assert self.cols == other.rows 858 if self.isDiagonal(): 859 if other.isDiagonal(): 860 product = {(k, k): self[k, k] * other[k, k] for k in self.rows} 861 else: 862 product = {(row, col): self[row, row] * other[row, col] for row in self.rows for col in other.cols} 863 else: 864 if other.isDiagonal(): 865 product = {(row, col): self[row, col] * other[col, col] for row in self.rows for col in other.cols} 866 else: 867 dot = lambda a, b, row, col: sum(a[row, k] * b[k, col] for k in a.cols) 868 product = {(row, col): dot(self, other, row, col) for row in self.rows for col in other.cols} 869 return DataSetMatrix(product, name="%s * %s" % (self.name, other.name))
870
871 - def __rmul__(self, other):
872 assert not isinstance(other, DataSetMatrix) 873 return self * other
874
875 - def transposed(self):
876 ''' 877 returns the transposed matrix 878 ''' 879 items = dict([((col, row), value) for (row, col), value in self.items()]) 880 return DataSetMatrix(items, "Trans( %s )" % self.name)
881
882 - def inversed(self):
883 ''' 884 returns the inversed matrix 885 ''' 886 assert self.isSquare(), "Can only invert square matrices" 887 name, id = "Inv( %s )" % self.name, "Inv(%s)" % self.name 888 if self.isDiagonal(): 889 return DataSetMatrix({index: dataset.reciprocal() for index, dataset in self.items()}, name) 890 indices = list(enumerate(self.rows)) 891 if any(map(_isComplex, self._datasets.values())): 892 invOperator = _datasource.InverseComplexMatrixDataSet(name, len(self.rows), id) 893 _addDimensions(invOperator, self._datasets.values()[0].dimensions()) 894 for rowIndex, rowKey in indices: 895 for colIndex, colKey in indices: 896 item = self[rowKey, colKey] 897 assert _isDataSet(item), item 898 try: 899 re, im = item.real, item.imag 900 except AttributeError: 901 re = item 902 im = _constant(0, item) 903 invOperator.input("Row-%s-Real" % rowIndex).append(re) 904 invOperator.input("Row-%s-Imag" % rowIndex).append(im) 905 invItems = {} 906 for rowIndex, rowKey in indices: 907 for colIndex, colKey in indices: 908 item_name = '%(name)s[%(rowKey)s, %(colKey)s]' % vars() 909 def complex_part(k, op): 910 part_name = '%s( %s )' % (op, item_name) 911 part = _datasource.DimensionReducingDataSet(part_name, part_name) 912 part.input("input").append(invOperator) 913 part.reduceDimensionToIndex(0, rowIndex) 914 part.reduceDimensionToIndex(1, colIndex) 915 part.reduceDimensionToIndex(2, k) 916 return part
917 re = complex_part(0, 'Re') 918 im = complex_part(1, 'Im') 919 invItems[rowKey, colKey] = ComplexDataSet(re, im, item_name) 920 else: 921 invOperator = _datasource.InverseRealMatrixDataSet(name, len(self.rows), id) 922 _addDimensions(invOperator, self._datasets.values()[0].dimensions()) 923 for rowIndex, rowKey in indices: 924 for colIndex, colKey in indices: 925 invOperator.input("Row-%s" % rowIndex).append(self[rowKey, colKey]) 926 invItems = {} 927 for rowIndex, rowKey in indices: 928 for colIndex, colKey in indices: 929 item_name = '%(name)s[%(rowKey)s, %(colKey)s]' % vars() 930 item = _datasource.DimensionReducingDataSet(item_name, item_name) 931 item.input("input").append(invOperator) 932 item.reduceDimensionToIndex(0, rowIndex) 933 item.reduceDimensionToIndex(1, colIndex) 934 invItems[rowKey, colKey] = item 935 return DataSetMatrix(invItems, name)
936 937 @_deprecation.deprecated( 938 390, "Use obj.inversed() for better naming consistency")
939 - def inverse(self):
940 ''' 941 deprecated, use inversed() for greater naming consistency. 942 ''' 943 return self.inversed()
944
945 - def _resultName(self, other, opName):
946 return "%s %s %s" % (self.name, opName, _name(other))
947 948 949 950 # --- implementation ------------------------------------------------------------------------------- 951 952 953 _FOLLOW_QUERY, _OVERRIDE_QUERY = range(2)
954 955 956 -def _startQuery(context):
957 import empro 958 import os 959 context = context or empro.activeProject 960 try: 961 query = context.query 962 except AttributeError: 963 pass 964 else: 965 return query.clone(), _FOLLOW_QUERY 966 try: 967 path = context.rootDir # context as project 968 if not path: 969 raise ValueError, "You cannot use an unsaved project as context of your query" 970 except AttributeError: 971 try: 972 path = context.simulationPath() # context as simulation 973 except AttributeError: 974 path = context 975 if os.path.isfile(os.path.join(path, 'project.xsim')): 976 projectId, simId = os.path.split(path) 977 #if not os.path.isfile(os.path.join(projectId, '.nextSimulationNumber')): 978 # raise ValueError('%(path)r is not a valid simulation path' % vars()) 979 if os.path.basename(projectId) == "Simulations": 980 # if legacy project, need to move one directory up. 981 candidate = os.path.dirname(projectId) 982 if os.path.isfile(os.path.join(candidate, "eesof_empro.xml")): 983 projectId = candidate # legacy project. need to move one diretory up. 984 if os.path.isfile(os.path.join(candidate, "Project.xml")): 985 projectId = candidate # legacy project. need to move one diretory up. 986 else: 987 projectId = path 988 simId = None 989 if not empro.output.resultBrowser().addProject(projectId): 990 raise ValueError, "Failed to load results of %(projectId)r" % vars() 991 query = empro.output.ResultQuery() 992 query.projectId = projectId 993 if simId: 994 query.simulationId = simId 995 return query, _FOLLOW_QUERY
996
997 998 -def _setSim(query, followQuery, **kwargs):
999 if isinstance(kwargs.get('sim'), int): 1000 kwargs['sim'] = '%06d' % kwargs['sim'] 1001 return _setKey(query, followQuery, "sim", "simulationId", query.availableSimulationIds(), **kwargs)
1002
1003 1004 -def _setRun(query, followQuery, **kwargs):
1005 if isinstance(kwargs.get('run'), int): 1006 kwargs['run'] = 'Run%04d' % kwargs['run'] 1007 return _setKey(query, followQuery, "run", "runId", query.availableRunIds(), **kwargs)
1008
1009 1010 -def _setObject(query, followQuery, **kwargs):
1011 if isinstance(kwargs.get('object'), basestring): 1012 objName = kwargs['object'] 1013 candidates = [objId for objId in query.availableOutputObjectIds() if objId[1] == objName] 1014 if len(candidates) == 0: 1015 goodNames = list(set(zip(*query.availableOutputObjectIds())[1])) 1016 raise KeyError, "No output object found with name %(objName)r. Try one of %(goodNames)r" % vars() 1017 if len(candidates) > 1: 1018 raise AmbiguityError("object", candidates, \ 1019 "There's more than one output object with name %(objName)r. Try one of the following full IDs: %(candidates)r" % vars()) 1020 kwargs['object'] = candidates[0] 1021 return _setKey(query, followQuery, "object", "outputObjectId", query.availableOutputObjectIds(), **kwargs)
1022
1023 1024 -def _setKey(query, followQuery, argument, attr, availableKeys, defaults=None, **kwargs):
1025 key, followQuery = _selectKey(query, followQuery, argument, attr, availableKeys, defaults, **kwargs) 1026 if followQuery == _OVERRIDE_QUERY: 1027 query.__setattr__(attr, key) 1028 assert query.__getattribute__(attr) == key 1029 return followQuery
1030
1031 1032 -def _selectKey(query, followQuery, argument, attr, availableKeys, defaults=None, **kwargs):
1033 ''' 1034 the defaults should be in order: the first available default will be selected.\ 1035 ''' 1036 key = kwargs.get(argument) 1037 if key: 1038 if not key in availableKeys: 1039 raise KeyError, "%(key)r is not a valid value for %(argument)r. Use one of %(availableKeys)r" % vars() 1040 return key, _OVERRIDE_QUERY 1041 elif followQuery == _OVERRIDE_QUERY or not query.__getattribute__(attr) in availableKeys: 1042 assert len(availableKeys) > 0, query 1043 if len(availableKeys) > 1 and defaults: 1044 Xs = filter(lambda x: x in availableKeys, defaults)[:1] 1045 else: 1046 Xs = availableKeys 1047 if len(Xs) != 1: 1048 raise AmbiguityError(argument, availableKeys) 1049 return Xs[0], _OVERRIDE_QUERY 1050 return _OVERRIDE_QUERY 1051 return query.__getattribute__(attr), _FOLLOW_QUERY
1052
1053 1054 -def _name(x):
1055 try: 1056 return x.name 1057 except AttributeError: 1058 return unicode(x)
1059
1060 1061 -def _named(x, name):
1062 try: 1063 x.name = name 1064 except AttributeError: 1065 pass 1066 return x
1067
1068 1069 -def _buildIdAndName(template, *args):
1070 return _buildId(template, *args), _buildName(template, *args)
1071
1072 1073 -def _buildId(template, *args):
1074 return _buildTag(template, 'id', *args).replace(' ', '')
1075
1076 1077 -def _buildName(template, *args):
1078 return _buildTag(template, 'name', *args)
1079
1080 1081 -def _buildTag(template, attr, *args):
1082 args = tuple([getattr(x, attr, x) for x in args]) 1083 return template % args
1084
1085 1086 -def _enforceDimensioned(x):
1087 assert _isDataSet(x), x 1088 if _isComplex(x): 1089 assert x.dimensions() 1090 if not x.dimensions(): 1091 xx = _datasource.AssignDimensionOperation(x.id, x.name) 1092 xx.input("Input").append(x) 1093 xx.addDimension(x) 1094 x = xx 1095 return x
1096
1097 1098 -def _addDimensions(dataset, dimensions):
1099 if isinstance(dimensions, _datasource.DataSet): 1100 dataset.addDimension(dimensions) 1101 else: 1102 for dim in dimensions: 1103 dataset.addDimension(dim) 1104 return dataset
1105
1106 1107 -def _constant(x, other, name=None):
1108 # we cannot just use a ConstantDataSet and copy other's dimensions, as these dimensions are destroyed when other is. 1109 # so we use a hack to make sure we keep a reference to other by turning this into an operation. 1110 import operator 1111 assert operator.isNumberType(x), "%r is not a number." % x 1112 y = other * 0 1113 if x != 0: 1114 y += x 1115 return _named(y, name or unicode(x))
1116
1117 1118 -def _isComplex(x):
1119 if isinstance(x, _numbers.Real): 1120 return False 1121 try: 1122 re, im = x.real, x.imag 1123 except AttributeError: 1124 return False 1125 return True
1126
1127 1128 -def _isDataSet(x):
1129 return isinstance(x, _datasource.DataSet) or isinstance(x, ComplexDataSet)
1130
1131 1132 -def _real(x):
1133 try: 1134 return x.real 1135 except AttributeError: 1136 return x
1137
1138 1139 -def _imag(x):
1140 try: 1141 return x.imag 1142 except AttributeError: 1143 return 0
1144
1145 1146 -def _conjugate(x):
1147 try: 1148 return x.conjugate() 1149 except AttributeError: 1150 return x
1151
1152 1153 -def _inversed(x):
1154 try: 1155 return x.inversed() 1156 except AttributeError: 1157 pass 1158 try: 1159 return x.reciprocal() 1160 except AttributeError: 1161 return 1. / x
1162
1163 1164 -def _assertCompatibleDatasets(*datasets):
1165 ''' 1166 check that all datasets have the same datatypes and dimensions 1167 ''' 1168 assert all(_isDataSet(ds) for ds in datasets), "Only (complex) datasets are accepted: %r" % list(map(type, datasets)) 1169 if len(datasets) < 2: 1170 return 1171 reference = datasets[0] 1172 for other in datasets[1:]: 1173 assert other.dataType() == reference.dataType(), "All datasets must be of the same data type." 1174 assert len(other) == len(reference), "All datasets must be of the same length." 1175 assert other.numberOfDimensions() == reference.numberOfDimensions(), "All datasets must have the same number of dimensions." 1176 assert all(a == b for a, b in zip(other.dimensions(), reference.dimensions())), "All datasets must have compatible dimensions."
1177
1178 1179 -def _calculateMatrixShape(datasets):
1180 def sortedUnique(Xs): 1181 return tuple(sorted(set(Xs)))
1182 return map(sortedUnique, zip(*datasets.keys())) 1183
1184 1185 -def _full_matrix_keys(rows, cols):
1186 return [(row, col) for row in rows for col in cols]
1187
1188 1189 -def _callBuiltin(fun, *args):
1190 try: 1191 return getattr(_math, fun)(*args) 1192 except TypeError: 1193 return getattr(_cmath, fun)(*args)
1194
1195 1196 -def _apply(x, fun, name=None):
1197 try: 1198 return DataSetMatrix({k: fun(v) for k, v in x.items()}, name) 1199 except AttributeError: 1200 return fun(x)
1201
1202 1203 -def _identity(other):
1204 from empro.toolkit import dataset 1205 try: 1206 assert other.isSquare(), "matrix must be square" 1207 except AttributeError: 1208 pass 1209 else: 1210 one = dataset._constant(1, other.values()[0]) 1211 return dataset.DataSetMatrix({(k, k): one for k in other.rows}, "1") 1212 try: 1213 return dataset._constant(1, other) 1214 except AttributeError: 1215 return 1
1216