1 #-*-coding:iso-8859-1-*-
3 # Copyright (C) 2008-2014 EDF R&D
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License.
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 # Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
24 Définit les outils généraux élémentaires.
26 Ce module est destiné à etre appelée par AssimilationStudy pour constituer
27 les objets élémentaires de l'algorithme.
29 __author__ = "Jean-Philippe ARGAUD"
35 # ==============================================================================
38 Classe générale de gestion d'un cache de calculs
41 toleranceInRedundancy = 1.e-18,
42 lenghtOfRedundancy = -1,
47 self.__tolerBP = float(toleranceInRedundancy)
48 self.__lenghtOR = int(lenghtOfRedundancy)
52 self.__listOPCP = [] # Operator Previous Calculated Points
53 self.__listOPCR = [] # Operator Previous Calculated Results
54 self.__listOPCN = [] # Operator Previous Calculated Point Norms
58 def wasCalculatedIn(self, xValue, info="" ):
59 self.__ac, self.__iac = False, -1
60 for i in xrange(len(self.__listOPCP)-1,-1,-1):
61 if xValue.size != self.__listOPCP[i].size:
63 if numpy.linalg.norm(numpy.ravel(xValue) - self.__listOPCP[i]) < self.__tolerBP * self.__listOPCN[i]:
64 self.__ac, self.__iac = True, i
68 def getValueInX(self, info="" ):
69 if self.__ac and (-1 < self.__iac < len(self.__listOPCR)):
70 __HX = self.__listOPCR[self.__iac]
71 self.__ac, self.__iac = False, -1
74 raise ValueError("CM Cas%s non encore disponible, vérifier au préalable son existence"%info)
76 def storeValueInX(self, xValue, HxValue ):
77 if self.__lenghtOR < 0: self.__lenghtOR = 2 * xValue.size + 2
78 if len(self.__listOPCP) > self.__lenghtOR:
79 self.__listOPCP.pop(0)
80 self.__listOPCR.pop(0)
81 self.__listOPCN.pop(0)
82 self.__listOPCP.append( copy.copy(numpy.ravel(xValue)) )
83 self.__listOPCR.append( copy.copy(HxValue) )
84 self.__listOPCN.append( numpy.linalg.norm(xValue) )
86 # ==============================================================================
89 Classe générale d'interface de type opérateur
96 def __init__(self, fromMethod=None, fromMatrix=None, avoidingRedundancy = True):
98 On construit un objet de ce type en fournissant à l'aide de l'un des
99 deux mots-clé, soit une fonction python, soit matrice.
101 - fromMethod : argument de type fonction Python
102 - fromMatrix : argument adapté au constructeur numpy.matrix
104 self.__NbCallsAsMatrix, self.__NbCallsAsMethod, self.__NbCallsOfCached = 0, 0, 0
105 self.__AvoidRC = bool( avoidingRedundancy )
106 if fromMethod is not None:
107 self.__Method = fromMethod
109 self.__Type = "Method"
110 elif fromMatrix is not None:
112 self.__Matrix = numpy.matrix( fromMatrix, numpy.float )
113 self.__Type = "Matrix"
122 def appliedTo(self, xValue):
124 Permet de restituer le résultat de l'application de l'opérateur à un
125 argument xValue. Cette méthode se contente d'appliquer, son argument
126 devant a priori être du bon type.
128 - xValue : argument adapté pour appliquer l'opérateur
130 if self.__AvoidRC and Operator.CM.wasCalculatedIn(xValue):
131 __alreadyCalculated = True
133 __alreadyCalculated = False
135 if __alreadyCalculated:
136 self.__addOneCacheCall()
137 HxValue = Operator.CM.getValueInX()
139 if self.__Matrix is not None:
140 self.__addOneMatrixCall()
141 HxValue = self.__Matrix * xValue
143 self.__addOneMethodCall()
144 HxValue = self.__Method( xValue )
146 Operator.CM.storeValueInX(xValue,HxValue)
150 def appliedControledFormTo(self, (xValue, uValue) ):
152 Permet de restituer le résultat de l'application de l'opérateur à une
153 paire (xValue, uValue). Cette méthode se contente d'appliquer, son
154 argument devant a priori être du bon type. Si la uValue est None,
155 on suppose que l'opérateur ne s'applique qu'à xValue.
157 - xValue : argument X adapté pour appliquer l'opérateur
158 - uValue : argument U adapté pour appliquer l'opérateur
160 if self.__Matrix is not None:
161 self.__addOneMatrixCall()
162 return self.__Matrix * xValue
163 elif uValue is not None:
164 self.__addOneMethodCall()
165 return self.__Method( (xValue, uValue) )
167 self.__addOneMethodCall()
168 return self.__Method( xValue )
170 def appliedInXTo(self, (xNominal, xValue) ):
172 Permet de restituer le résultat de l'application de l'opérateur à un
173 argument xValue, sachant que l'opérateur est valable en xNominal.
174 Cette méthode se contente d'appliquer, son argument devant a priori
175 être du bon type. Si l'opérateur est linéaire car c'est une matrice,
176 alors il est valable en tout point nominal et il n'est pas nécessaire
178 Arguments : une liste contenant
179 - xNominal : argument permettant de donner le point où l'opérateur
180 est construit pour etre ensuite appliqué
181 - xValue : argument adapté pour appliquer l'opérateur
183 if self.__Matrix is not None:
184 self.__addOneMatrixCall()
185 return self.__Matrix * xValue
187 self.__addOneMethodCall()
188 return self.__Method( (xNominal, xValue) )
190 def asMatrix(self, ValueForMethodForm = "UnknownVoidValue"):
192 Permet de renvoyer l'opérateur sous la forme d'une matrice
194 if self.__Matrix is not None:
195 self.__addOneMatrixCall()
197 elif ValueForMethodForm is not "UnknownVoidValue": # Ne pas utiliser "None"
198 self.__addOneMethodCall()
199 return numpy.matrix( self.__Method( (ValueForMethodForm, None) ) )
201 raise ValueError("Matrix form of the operator defined as a function/method requires to give an operating point.")
205 Renvoie la taille sous forme numpy si l'opérateur est disponible sous
206 la forme d'une matrice
208 if self.__Matrix is not None:
209 return self.__Matrix.shape
211 raise ValueError("Matrix form of the operator is not available, nor the shape")
213 def nbcalls(self, which=None):
215 Renvoie les nombres d'évaluations de l'opérateur
218 self.__NbCallsAsMatrix+self.__NbCallsAsMethod,
219 self.__NbCallsAsMatrix,
220 self.__NbCallsAsMethod,
221 self.__NbCallsOfCached,
222 Operator.NbCallsAsMatrix+Operator.NbCallsAsMethod,
223 Operator.NbCallsAsMatrix,
224 Operator.NbCallsAsMethod,
225 Operator.NbCallsOfCached,
227 if which is None: return __nbcalls
228 else: return __nbcalls[which]
230 def __addOneMatrixCall(self):
231 self.__NbCallsAsMatrix += 1 # Decompte local
232 Operator.NbCallsAsMatrix += 1 # Decompte global
234 def __addOneMethodCall(self):
235 self.__NbCallsAsMethod += 1 # Decompte local
236 Operator.NbCallsAsMethod += 1 # Decompte global
238 def __addOneCacheCall(self):
239 self.__NbCallsOfCached += 1 # Decompte local
240 Operator.NbCallsOfCached += 1 # Decompte global
242 # ==============================================================================
245 Classe générale d'interface de type algorithme
247 Elle donne un cadre pour l'écriture d'une classe élémentaire d'algorithme
248 d'assimilation, en fournissant un container (dictionnaire) de variables
249 persistantes initialisées, et des méthodes d'accès à ces variables stockées.
251 Une classe élémentaire d'algorithme doit implémenter la méthode "run".
253 def __init__(self, name):
255 L'initialisation présente permet de fabriquer des variables de stockage
256 disponibles de manière générique dans les algorithmes élémentaires. Ces
257 variables de stockage sont ensuite conservées dans un dictionnaire
258 interne à l'objet, mais auquel on accède par la méthode "get".
260 Les variables prévues sont :
261 - CostFunctionJ : fonction-cout globale, somme des deux parties suivantes
262 - CostFunctionJb : partie ébauche ou background de la fonction-cout
263 - CostFunctionJo : partie observations de la fonction-cout
264 - GradientOfCostFunctionJ : gradient de la fonction-cout globale
265 - GradientOfCostFunctionJb : gradient de la partie ébauche de la fonction-cout
266 - GradientOfCostFunctionJo : gradient de la partie observations de la fonction-cout
267 - CurrentState : état courant lors d'itérations
268 - Analysis : l'analyse
269 - Innovation : l'innovation : d = Y - H Xb
270 - SigmaObs2 : indicateur de correction optimale des erreurs d'observation
271 - SigmaBck2 : indicateur de correction optimale des erreurs d'ébauche
272 - MahalanobisConsistency : indicateur de consistance des covariances
273 - OMA : Observation moins Analysis : Y - Xa
274 - OMB : Observation moins Background : Y - Xb
275 - AMB : Analysis moins Background : Xa - Xb
276 - APosterioriCovariance : matrice A
277 On peut rajouter des variables à stocker dans l'initialisation de
278 l'algorithme élémentaire qui va hériter de cette classe
280 logging.debug("%s Initialisation"%str(name))
281 self._name = str( name )
282 self._parameters = {}
283 self.__required_parameters = {}
284 self.StoredVariables = {}
286 self.StoredVariables["CostFunctionJ"] = Persistence.OneScalar(name = "CostFunctionJ")
287 self.StoredVariables["CostFunctionJb"] = Persistence.OneScalar(name = "CostFunctionJb")
288 self.StoredVariables["CostFunctionJo"] = Persistence.OneScalar(name = "CostFunctionJo")
289 self.StoredVariables["GradientOfCostFunctionJ"] = Persistence.OneVector(name = "GradientOfCostFunctionJ")
290 self.StoredVariables["GradientOfCostFunctionJb"] = Persistence.OneVector(name = "GradientOfCostFunctionJb")
291 self.StoredVariables["GradientOfCostFunctionJo"] = Persistence.OneVector(name = "GradientOfCostFunctionJo")
292 self.StoredVariables["CurrentState"] = Persistence.OneVector(name = "CurrentState")
293 self.StoredVariables["Analysis"] = Persistence.OneVector(name = "Analysis")
294 self.StoredVariables["Innovation"] = Persistence.OneVector(name = "Innovation")
295 self.StoredVariables["SigmaObs2"] = Persistence.OneScalar(name = "SigmaObs2")
296 self.StoredVariables["SigmaBck2"] = Persistence.OneScalar(name = "SigmaBck2")
297 self.StoredVariables["MahalanobisConsistency"] = Persistence.OneScalar(name = "MahalanobisConsistency")
298 self.StoredVariables["OMA"] = Persistence.OneVector(name = "OMA")
299 self.StoredVariables["OMB"] = Persistence.OneVector(name = "OMB")
300 self.StoredVariables["BMA"] = Persistence.OneVector(name = "BMA")
301 self.StoredVariables["APosterioriCovariance"] = Persistence.OneMatrix(name = "APosterioriCovariance")
302 self.StoredVariables["SimulationQuantiles"] = Persistence.OneMatrix(name = "SimulationQuantiles")
304 def get(self, key=None):
306 Renvoie l'une des variables stockées identifiée par la clé, ou le
307 dictionnaire de l'ensemble des variables disponibles en l'absence de
308 clé. Ce sont directement les variables sous forme objet qui sont
309 renvoyées, donc les méthodes d'accès à l'objet individuel sont celles
310 des classes de persistance.
313 return self.StoredVariables[key]
315 return self.StoredVariables
317 def has_key(self, key=None):
319 Vérifie si l'une des variables stockées est identifiée par la clé.
321 return self.StoredVariables.has_key(key)
325 Renvoie la liste des clés de variables stockées.
327 return self.StoredVariables.keys()
329 def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Parameters=None):
331 Doit implémenter l'opération élémentaire de calcul d'assimilation sous
332 sa forme mathématique la plus naturelle possible.
334 raise NotImplementedError("Mathematical assimilation calculation has not been implemented!")
336 def defineRequiredParameter(self, name = None, default = None, typecast = None, message = None, minval = None, maxval = None, listval = None):
338 Permet de définir dans l'algorithme des paramètres requis et leurs
339 caractéristiques par défaut.
342 raise ValueError("A name is mandatory to define a required parameter.")
344 self.__required_parameters[name] = {
346 "typecast" : typecast,
352 logging.debug("%s %s (valeur par défaut = %s)"%(self._name, message, self.setParameterValue(name)))
354 def getRequiredParameters(self, noDetails=True):
356 Renvoie la liste des noms de paramètres requis ou directement le
357 dictionnaire des paramètres requis.
360 ks = self.__required_parameters.keys()
364 return self.__required_parameters
366 def setParameterValue(self, name=None, value=None):
368 Renvoie la valeur d'un paramètre requis de manière contrôlée
370 default = self.__required_parameters[name]["default"]
371 typecast = self.__required_parameters[name]["typecast"]
372 minval = self.__required_parameters[name]["minval"]
373 maxval = self.__required_parameters[name]["maxval"]
374 listval = self.__required_parameters[name]["listval"]
376 if value is None and default is None:
378 elif value is None and default is not None:
379 if typecast is None: __val = default
380 else: __val = typecast( default )
382 if typecast is None: __val = value
383 else: __val = typecast( value )
385 if minval is not None and __val < minval:
386 raise ValueError("The parameter named \"%s\" of value \"%s\" can not be less than %s."%(name, __val, minval))
387 if maxval is not None and __val > maxval:
388 raise ValueError("The parameter named \"%s\" of value \"%s\" can not be greater than %s."%(name, __val, maxval))
389 if listval is not None:
390 if typecast is list or typecast is tuple or type(__val) is list or type(__val) is tuple:
393 raise ValueError("The value \"%s\" of the parameter named \"%s\" is not allowed, it has to be in the list %s."%(v, name, listval))
394 elif __val not in listval:
395 raise ValueError("The value \"%s\" of the parameter named \"%s\" is not allowed, it has to be in the list %s."%( __val, name,listval))
398 def setParameters(self, fromDico={}):
400 Permet de stocker les paramètres reçus dans le dictionnaire interne.
402 self._parameters.update( fromDico )
403 for k in self.__required_parameters.keys():
404 if k in fromDico.keys():
405 self._parameters[k] = self.setParameterValue(k,fromDico[k])
407 self._parameters[k] = self.setParameterValue(k)
408 logging.debug("%s %s : %s"%(self._name, self.__required_parameters[k]["message"], self._parameters[k]))
410 # ==============================================================================
413 Classe générale d'interface de type diagnostic
415 Ce template s'utilise de la manière suivante : il sert de classe "patron" en
416 même temps que l'une des classes de persistance, comme "OneScalar" par
419 Une classe élémentaire de diagnostic doit implémenter ses deux méthodes, la
420 méthode "_formula" pour écrire explicitement et proprement la formule pour
421 l'écriture mathématique du calcul du diagnostic (méthode interne non
422 publique), et "calculate" pour activer la précédente tout en ayant vérifié
423 et préparé les données, et pour stocker les résultats à chaque pas (méthode
424 externe d'activation).
426 def __init__(self, name = "", parameters = {}):
427 self.name = str(name)
428 self.parameters = dict( parameters )
430 def _formula(self, *args):
432 Doit implémenter l'opération élémentaire de diagnostic sous sa forme
433 mathématique la plus naturelle possible.
435 raise NotImplementedError("Diagnostic mathematical formula has not been implemented!")
437 def calculate(self, *args):
439 Active la formule de calcul avec les arguments correctement rangés
441 raise NotImplementedError("Diagnostic activation method has not been implemented!")
443 # ==============================================================================
446 Classe générale d'interface de type covariance
449 name = "GenericCovariance",
451 asEyeByScalar = None,
452 asEyeByVector = None,
455 Permet de définir une covariance :
456 - asCovariance : entrée des données, comme une matrice compatible avec
457 le constructeur de numpy.matrix
458 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
459 multiplicatif d'une matrice de corrélation identité, aucune matrice
460 n'étant donc explicitement à donner
461 - asEyeByVector : entrée des données comme un seul vecteur de variance,
462 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
463 n'étant donc explicitement à donner
465 self.__name = str(name)
468 self.__is_scalar = False
469 self.__is_vector = False
470 self.__is_matrix = False
471 if asEyeByScalar is not None:
472 self.__is_scalar = True
473 self.__B = numpy.abs( float(asEyeByScalar) )
476 elif asEyeByVector is not None:
477 self.__is_vector = True
478 self.__B = numpy.abs( numpy.array( numpy.ravel( asEyeByVector ), float ) )
479 self.shape = (self.__B.size,self.__B.size)
480 self.size = self.__B.size**2
481 elif asCovariance is not None:
482 self.__is_matrix = True
483 self.__B = numpy.matrix( asCovariance, float )
484 self.shape = self.__B.shape
485 self.size = self.__B.size
488 # raise ValueError("The %s covariance matrix has to be specified either as a matrix, a vector for its diagonal or a scalar multiplying an identity matrix."%self.__name)
492 def __validate(self):
493 if self.ismatrix() and min(self.shape) != max(self.shape):
494 raise ValueError("The given matrix for %s is not a square one, its shape is %s. Please check your matrix input."%(self.__name,self.shape))
495 if self.isscalar() and self.__B <= 0:
496 raise ValueError("The %s covariance matrix is not positive-definite. Please check your scalar input %s."%(self.__name,self.__B_scalar))
497 if self.isvector() and (self.__B <= 0).any():
498 raise ValueError("The %s covariance matrix is not positive-definite. Please check your vector input."%(self.__name,))
499 if self.ismatrix() and logging.getLogger().level < logging.WARNING: # La verification n'a lieu qu'en debug
501 L = numpy.linalg.cholesky( self.__B )
503 raise ValueError("The %s covariance matrix is not symmetric positive-definite. Please check your matrix input."%(self.__name,))
506 return self.__is_scalar
509 return self.__is_vector
512 return self.__is_matrix
516 return Covariance(self.__name+"I", asCovariance = self.__B.I )
517 elif self.isvector():
518 return Covariance(self.__name+"I", asEyeByVector = 1. / self.__B )
519 elif self.isscalar():
520 return Covariance(self.__name+"I", asEyeByScalar = 1. / self.__B )
526 return Covariance(self.__name+"T", asCovariance = self.__B.T )
527 elif self.isvector():
528 return Covariance(self.__name+"T", asEyeByVector = self.__B )
529 elif self.isscalar():
530 return Covariance(self.__name+"T", asEyeByScalar = self.__B )
534 return Covariance(self.__name+"C", asCovariance = numpy.linalg.cholesky(self.__B) )
535 elif self.isvector():
536 return Covariance(self.__name+"C", asEyeByVector = numpy.sqrt( self.__B ) )
537 elif self.isscalar():
538 return Covariance(self.__name+"C", asEyeByScalar = numpy.sqrt( self.__B ) )
542 return Covariance(self.__name+"H", asCovariance = numpy.linalg.cholesky(self.__B).I )
543 elif self.isvector():
544 return Covariance(self.__name+"H", asEyeByVector = 1.0 / numpy.sqrt( self.__B ) )
545 elif self.isscalar():
546 return Covariance(self.__name+"H", asEyeByScalar = 1.0 / numpy.sqrt( self.__B ) )
548 def diag(self, msize=None):
550 return numpy.diag(self.__B)
551 elif self.isvector():
553 elif self.isscalar():
555 raise ValueError("the size of the %s covariance matrix has to be given in case of definition as a scalar over the diagonal."%(self.__name,))
557 return self.__B * numpy.ones(int(msize))
559 def asfullmatrix(self, msize=None):
562 elif self.isvector():
563 return numpy.matrix( numpy.diag(self.__B), float )
564 elif self.isscalar():
566 raise ValueError("the size of the %s covariance matrix has to be given in case of definition as a scalar over the diagonal."%(self.__name,))
568 return numpy.matrix( self.__B * numpy.eye(int(msize)), float )
570 def trace(self, msize=None):
572 return numpy.trace(self.__B)
573 elif self.isvector():
574 return float(numpy.sum(self.__B))
575 elif self.isscalar():
577 raise ValueError("the size of the %s covariance matrix has to be given in case of definition as a scalar over the diagonal."%(self.__name,))
579 return self.__B * int(msize)
582 return repr(self.__B)
587 def __add__(self, other):
589 return self.__B + numpy.asmatrix(other)
590 elif self.isvector() or self.isscalar():
591 _A = numpy.asarray(other)
592 _A.reshape(_A.size)[::_A.shape[1]+1] += self.__B
593 return numpy.asmatrix(_A)
595 def __radd__(self, other):
596 raise NotImplementedError("%s covariance matrix __radd__ method not available for %s type!"%(self.__name,type(other)))
598 def __sub__(self, other):
600 return self.__B - numpy.asmatrix(other)
601 elif self.isvector() or self.isscalar():
602 _A = numpy.asarray(other)
603 _A.reshape(_A.size)[::_A.shape[1]+1] = self.__B - _A.reshape(_A.size)[::_A.shape[1]+1]
604 return numpy.asmatrix(_A)
606 def __rsub__(self, other):
607 raise NotImplementedError("%s covariance matrix __rsub__ method not available for %s type!"%(self.__name,type(other)))
612 def __mul__(self, other):
613 if self.ismatrix() and isinstance(other,numpy.matrix):
614 return self.__B * other
615 elif self.ismatrix() and (isinstance(other,numpy.ndarray) \
616 or isinstance(other,list) \
617 or isinstance(other,tuple)):
618 if numpy.ravel(other).size == self.shape[1]: # Vecteur
619 return self.__B * numpy.asmatrix(numpy.ravel(other)).T
620 elif numpy.asmatrix(other).shape[0] == self.shape[1]: # Matrice
621 return self.__B * numpy.asmatrix(other)
623 raise ValueError("operands could not be broadcast together with shapes %s %s in %s matrix"%(self.shape,numpy.asmatrix(other).shape,self.__name))
624 elif self.isvector() and (isinstance(other,numpy.matrix) \
625 or isinstance(other,numpy.ndarray) \
626 or isinstance(other,list) \
627 or isinstance(other,tuple)):
628 if numpy.ravel(other).size == self.shape[1]: # Vecteur
629 return numpy.asmatrix(self.__B * numpy.ravel(other)).T
630 elif numpy.asmatrix(other).shape[0] == self.shape[1]: # Matrice
631 return numpy.asmatrix((self.__B * (numpy.asarray(other).transpose())).transpose())
633 raise ValueError("operands could not be broadcast together with shapes %s %s in %s matrix"%(self.shape,numpy.ravel(other).shape,self.__name))
634 elif self.isscalar() and isinstance(other,numpy.matrix):
635 return self.__B * other
636 elif self.isscalar() and (isinstance(other,numpy.ndarray) \
637 or isinstance(other,list) \
638 or isinstance(other,tuple)):
639 if len(numpy.asarray(other).shape) == 1 or numpy.asarray(other).shape[1] == 1 or numpy.asarray(other).shape[0] == 1:
640 return self.__B * numpy.asmatrix(numpy.ravel(other)).T
642 return self.__B * numpy.asmatrix(other)
644 raise NotImplementedError("%s covariance matrix __mul__ method not available for %s type!"%(self.__name,type(other)))
646 def __rmul__(self, other):
647 if self.ismatrix() and isinstance(other,numpy.matrix):
648 return other * self.__B
649 elif self.isvector() and isinstance(other,numpy.matrix):
650 if numpy.ravel(other).size == self.shape[0]: # Vecteur
651 return numpy.asmatrix(numpy.ravel(other) * self.__B)
652 elif numpy.asmatrix(other).shape[1] == self.shape[0]: # Matrice
653 return numpy.asmatrix(numpy.array(other) * self.__B)
655 raise ValueError("operands could not be broadcast together with shapes %s %s in %s matrix"%(self.shape,numpy.ravel(other).shape,self.__name))
656 elif self.isscalar() and isinstance(other,numpy.matrix):
657 return other * self.__B
659 raise NotImplementedError("%s covariance matrix __rmul__ method not available for %s type!"%(self.__name,type(other)))
664 # ==============================================================================
665 if __name__ == "__main__":
666 print '\n AUTODIAGNOSTIC \n'