1 """Contains the classes that are used to define the dependency network.
3 Copyright (C) 2013, Joshua More and Michele Ceriotti
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program 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
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http.//www.gnu.org/licenses/>.
19 The classes defined in this module overload the standard __get__ and __set__
20 routines of the numpy ndarray class and standard library object class so that
21 they automatically keep track of whether anything they depend on has been
22 altered, and so only recalculate their value when necessary.
24 Basic quantities that depend on nothing else can be manually altered in the
25 usual way, all other quantities are updated automatically and cannot be changed
28 The exceptions to this are synchronized properties, which are in effect
29 multiple basic quantities all related to each other, for example the bead and
30 normal mode representations of the positions and momenta. In this case any of
31 the representations can be set manually, and all the other representations
34 For a more detailed discussion, see the reference manual.
37 depend_base: Base depend class with the generic methods and attributes.
38 depend_value: Depend class for scalar objects.
39 depend_array: Depend class for arrays.
40 synchronizer: Class that holds the different objects that are related to each
41 other and keeps track of which property has been set manually.
42 dobject: An extension of the standard library object that overloads
43 __getattribute__ and __setattribute__, so that we can use the
44 standard syntax for setting and getting the depend object,
45 i.e. foo = value, not foo.set(value).
48 dget: Gets the dependencies of a depend object.
49 dset: Sets the dependencies of a depend object.
50 depstrip: Used on a depend_array object, to access its value without
51 needing the depend machinery, and so much more quickly. Must not be used
52 if the value of the array is to be changed.
53 depcopy: Copies the dependencies from one object to another
54 deppipe: Used to make two objects be synchronized to the same value.
57 __all__
= ['depend_base', 'depend_value', 'depend_array', 'synchronizer',
58 'dobject', 'dget', 'dset', 'depstrip', 'depcopy', 'deppipe']
61 from ipi
.utils
.messages
import verbosity
, warning
63 class synchronizer(object):
64 """Class to implement synched objects.
66 Holds the objects used to keep two or more objects in step with each other.
67 This is shared between all the synched objects.
70 synched: A dictionary containing all the synched objects, of the form
71 {"name": depend object}.
72 manual: A string containing the name of the object being manually changed.
75 def __init__(self
, deps
=None):
76 """Initialises synchronizer.
79 deps: Optional dictionary giving the synched objects of the form
80 {"name": depend object}.
91 #TODO put some error checks in the init to make sure that the object is initialized from consistent synchro and func states
92 class depend_base(object):
93 """Base class for dependency handling.
95 Builds the majority of the machinery required for the different depend
96 objects. Contains functions to add and remove dependencies, the tainting
97 mechanism by which information about which objects have been updated is
98 passed around the dependency network, and the manual and automatic update
99 functions to check that depend objects with functions are not manually
100 updated and that synchronized objects are kept in step with the one manually
104 _tainted: An array containing one boolean, which is True if one of the
105 dependencies has been changed since the last time the value was
107 _func: A function name giving the method of calculating the value,
108 if required. None otherwise.
109 _name: The name of the depend base object.
110 _synchro: A synchronizer object to deal with synched objects, if
111 required. None otherwise.
112 _dependants: A list containing all objects dependent on the self.
115 def __init__(self
, name
, synchro
=None, func
=None, dependants
=None, dependencies
=None, tainted
=None):
116 """Initialises depend_base.
118 An unusual initialisation routine, as it has to be able to deal with the
119 depend array mechanism for returning slices as new depend arrays.
121 This is the reason for the penultimate if statement; it automatically
122 taints objects created from scratch but does nothing to slices which are
125 Also, the last if statement makes sure that if a synchronized property is
126 sliced, this initialization routine does not automatically set it to the
127 manually updated property.
130 name: A string giving the name of self.
131 tainted: An optional array containing one boolean which is True if one
132 of the dependencies has been changed.
133 func: An optional argument that can be specified either by a function
134 name, or for synchronized values a dictionary of the form
135 {"name": function name}; where "name" is one of the other
136 synched objects and function name is the name of a function to
137 get the object "name" from self.
138 synchro: An optional synchronizer object.
139 dependants: An optional list containing objects that depend on self.
140 dependencies: An optional list containing objects that self
144 self
._dependants
= []
146 tainted
= np
.array([True],bool)
147 if dependants
is None:
149 if dependencies
is None:
151 self
._tainted
= tainted
155 self
.add_synchro(synchro
)
157 for item
in dependencies
:
158 item
.add_dependant(self
, tainted
)
160 self
._dependants
= dependants
162 # Don't taint self if the object is a primitive one. However, do propagate tainting to dependants if required.
164 if self
._func
is None:
165 self
.taint(taintme
=False)
167 self
.taint(taintme
=tainted
)
170 def add_synchro(self
, synchro
=None):
171 """ Links depend object to a synchronizer. """
173 self
._synchro
= synchro
174 if not self
._synchro
is None and not self
._name
in self
._synchro
.synced
:
175 self
._synchro
.synced
[self
._name
] = self
176 self
._synchro
.manual
= self
._name
179 def add_dependant(self
, newdep
, tainted
=True):
180 """Adds a dependant property.
183 newdep: The depend object to be added to the dependency list.
184 tainted: A boolean that decides whether newdep should be tainted.
188 self
._dependants
.append(newdep
)
190 newdep
.taint(taintme
=True)
192 def add_dependency(self
, newdep
, tainted
=True):
193 """Adds a dependency.
196 newdep: The depend object self now depends upon.
197 tainted: A boolean that decides whether self should
198 be tainted. True by default.
201 newdep
._dependants
.append(self
)
203 self
.taint(taintme
=True)
205 def taint(self
,taintme
=True):
206 """Recursively sets tainted flag on dependent objects.
208 The main function dealing with the dependencies. Taints all objects
209 further down the dependency tree until either all objects have been
210 tainted, or it reaches only objects that have already been tainted. Note
211 that in the case of a dependency loop the initial setting of _tainted to
212 True prevents an infinite loop occuring.
214 Also, in the case of a synchro object, the manually set quantity is not
215 tainted, as it is assumed that synchro objects only depend on each other.
218 taintme: A boolean giving whether self should be tainted at the end.
222 self
._tainted
[:] = True
223 for item
in self
._dependants
:
224 if (not item
._tainted
[0]):
226 if not self
._synchro
is None:
227 for v
in self
._synchro
.synced
.values():
228 if (not v
._tainted
[0]) and (not v
is self
):
229 v
.taint(taintme
=True)
230 self
._tainted
[:] = (taintme
and (not self
._name
== self
._synchro
.manual
))
232 self
._tainted
[:] = taintme
235 """Returns tainted flag."""
237 return self
._tainted
[0]
239 def update_auto(self
):
240 """Automatic update routine.
242 Updates the value when get has been called and self has been tainted.
245 if not self
._synchro
is None:
246 if (not self
._name
== self
._synchro
.manual
):
247 self
.set(self
._func
[self
._synchro
.manual
](), manual
=False)
249 warning(self
._name
+ " probably shouldn't be tainted (synchro)", verbosity
.low
)
250 elif not self
._func
is None:
251 self
.set(self
._func
(), manual
=False)
253 warning(self
._name
+ " probably shouldn't be tainted (value)", verbosity
.low
)
255 def update_man(self
):
256 """Manual update routine.
258 Updates the value when the value has been manually set. Also raises an
259 exception if a calculated quantity has been manually set. Also starts the
263 NameError: If a calculated quantity has been manually set.
266 if not self
._synchro
is None:
267 self
._synchro
.manual
= self
._name
268 for v
in self
._synchro
.synced
.values():
269 v
.taint(taintme
=True)
270 self
._tainted
[:] = False
271 elif not self
._func
is None:
272 raise NameError("Cannot set manually the value of the automatically-computed property <" + self
._name
+ ">")
274 self
.taint(taintme
=False)
276 def set(self
, value
, manual
=False):
277 """Dummy setting routine."""
282 """Dummy getting routine."""
286 class depend_value(depend_base
):
287 """Scalar class for dependency handling.
290 _value: The value associated with self.
293 def __init__(self
, name
, value
=None, synchro
=None, func
=None, dependants
=None, dependencies
=None, tainted
=None):
294 """Initialises depend_value.
297 name: A string giving the name of self.
298 value: The value of the object. Optional.
299 tainted: An optional array giving the tainted flag. Default is [True].
300 func: An optional argument that can be specified either by a function
301 name, or for synchronized values a dictionary of the form
302 {"name": function name}; where "name" is one of the other
303 synched objects and function name is the name of a function to
304 get the object "name" from self.
305 synchro: An optional synchronizer object.
306 dependants: An optional list containing objects that depend on self.
307 dependencies: An optional list containing objects that self
312 super(depend_value
,self
).__init
__(name
, synchro
, func
, dependants
, dependencies
, tainted
)
315 """Returns value, after recalculating if necessary.
317 Overwrites the standard method of getting value, so that value
318 is recalculated if tainted.
323 self
.taint(taintme
=False)
327 def __get__(self
, instance
, owner
):
328 """Overwrites standard get function."""
332 def set(self
, value
, manual
=True):
333 """Alters value and taints dependencies.
335 Overwrites the standard method of setting value, so that dependent
336 quantities are tainted, and so we check that computed quantities are not
341 self
.taint(taintme
=False)
345 def __set__(self
, instance
, value
):
346 """Overwrites standard set function."""
351 class depend_array(np
.ndarray
, depend_base
):
352 """Array class for dependency handling.
354 Differs from depend_value as arrays handle getting items in a different
355 way to scalar quantities, and as there needs to be support for slicing an
356 array. Initialisation is also done in a different way for ndarrays.
359 _bval: The base deparray storage space. Equal to depstrip(self) unless
363 def __new__(cls
, value
, name
, synchro
=None, func
=None, dependants
=None, dependencies
=None, tainted
=None, base
=None):
364 """Creates a new array from a template.
366 Called whenever a new instance of depend_array is created. Casts the
367 array base into an appropriate form before passing it to
368 __array_finalize__().
374 obj
= np
.asarray(value
).view(cls
)
377 def __init__(self
, value
, name
, synchro
=None, func
=None, dependants
=None, dependencies
=None, tainted
=None, base
=None):
378 """Initialises depend_array.
380 Note that this is only called when a new array is created by an
381 explicit constructor.
384 name: A string giving the name of self.
385 value: The (numpy) array to serve as the memory base.
386 tainted: An optional array giving the tainted flag. Default is [True].
387 func: An optional argument that can be specified either by a function
388 name, or for synchronized values a dictionary of the form
389 {"name": function name}; where "name" is one of the other
390 synched objects and function name is the name of a function to
391 get the object "name" from self.
392 synchro: An optional synchronizer object.
393 dependants: An optional list containing objects that depend on self.
394 dependencies: An optional list containing objects that self
398 super(depend_array
,self
).__init
__(name
, synchro
, func
, dependants
, dependencies
, tainted
)
405 def copy(self
, order
='C', maskna
=None):
406 """Wrapper for numpy copy mechanism."""
408 # Sets a flag and hands control to the numpy copy
410 return super(depend_array
,self
).copy(order
)
412 def __array_finalize__(self
, obj
):
413 """Deals with properly creating some arrays.
415 In the case where a function acting on a depend array returns a ndarray,
416 this casts it into the correct form and gives it the
417 depend machinery for other methods to be able to act upon it. New
418 depend_arrays will next be passed to __init__ ()to be properly
419 initialized, but some ways of creating arrays do not call __new__() or
420 __init__(), so need to be initialized.
423 depend_base
.__init
__(self
, name
="")
425 if type(obj
) is depend_array
:
426 # We are in a view cast or in new from template. Unfortunately
427 # there is no sure way to tell (or so it seems). Hence we need to
428 # handle special cases, and hope we are in a view cast otherwise.
429 if hasattr(obj
,"_fcopy"):
430 del(obj
._fcopy
) # removes the "copy flag"
431 self
._bval
= depstrip(self
)
433 # Assumes we are in view cast, so copy over the attributes from the
434 # parent object. Typical case: when transpose is performed as a
436 super(depend_array
,self
).__init
__(obj
._name
, obj
._synchro
, obj
._func
, obj
._dependants
, None, obj
._tainted
)
437 self
._bval
= obj
._bval
439 # Most likely we came here on the way to init.
440 # Just sets a defaults for safety
441 self
._bval
= depstrip(self
)
444 def __array_prepare__(self
, arr
, context
=None):
445 """Prepare output array for ufunc.
447 Depending on the context we try to understand if we are doing an
448 in-place operation (in which case we want to keep the return value a
449 deparray) or we are generating a new array as a result of the ufunc.
450 In this case there is no way to know if dependencies should be copied,
451 so we strip and return a ndarray.
454 if context
is None or len(context
) < 2 or not type(context
[0]) is np
.ufunc
:
455 # It is not clear what we should do. If in doubt, strip dependencies.
456 return np
.ndarray
.__array
_prepare
__(self
.view(np
.ndarray
),arr
.view(np
.ndarray
),context
)
457 elif len(context
[1]) > context
[0].nin
and context
[0].nout
> 0:
458 # We are being called by a ufunc with a output argument, which is being
459 # actually used. Most likely, something like an increment,
460 # so we pass on a deparray
461 return super(depend_array
,self
).__array
_prepare
__(arr
,context
)
463 # Apparently we are generating a new array.
464 # We have no way of knowing its
465 # dependencies, so we'd better return a ndarray view!
466 return np
.ndarray
.__array
_prepare
__(self
.view(np
.ndarray
),arr
.view(np
.ndarray
),context
)
468 def __array_wrap__(self
, arr
, context
=None):
469 """ Wraps up output array from ufunc.
471 See docstring of __array_prepare__().
474 if context
is None or len(context
) < 2 or not type(context
[0]) is np
.ufunc
:
475 return np
.ndarray
.__array
_wrap
__(self
.view(np
.ndarray
),arr
.view(np
.ndarray
),context
)
476 elif len(context
[1]) > context
[0].nin
and context
[0].nout
> 0:
477 return super(depend_array
,self
).__array
_wrap
__(arr
,context
)
479 return np
.ndarray
.__array
_wrap
__(self
.view(np
.ndarray
),arr
.view(np
.ndarray
),context
)
481 # whenever possible in compound operations just return a regular ndarray
482 __array_priority__
= -1.0
484 def reshape(self
, newshape
):
485 """Changes the shape of the base array.
488 newshape: A tuple giving the desired shape of the new array.
491 A depend_array with the dimensions given by newshape.
494 return depend_array(depstrip(self
).reshape(newshape
), name
=self
._name
, synchro
=self
._synchro
, func
=self
._func
, dependants
=self
._dependants
, tainted
=self
._tainted
, base
=self
._bval
)
497 """Makes the base array one dimensional.
503 return self
.reshape(self
.size
)
506 def __scalarindex(index
, depth
=1):
507 """Checks if an index points at a scalar value.
509 Used so that looking up one item in an array returns a scalar, whereas
510 looking up a slice of the array returns a new array with the same
511 dependencies as the original, so that changing the slice also taints
515 index: the index to be checked.
516 depth: the rank of the array which is being accessed. Default value
520 A logical stating whether a __get__ instruction based
521 on index would return a scalar.
524 if (np
.isscalar(index
) and depth
<= 1):
526 elif (isinstance(index
, tuple) and len(index
)==depth
):
527 #if the index is a tuple check it does not contain slices
529 if not np
.isscalar(i
): return False
533 def __getitem__(self
,index
):
534 """Returns value[index], after recalculating if necessary.
536 Overwrites the standard method of getting value, so that value
537 is recalculated if tainted. Scalar slices are returned as an ndarray,
538 so without depend machinery. If you need a "scalar depend" which
539 behaves as a slice, just create a 1x1 matrix, e.g b=a(7,1:2)
542 index: A slice variable giving the appropriate slice to be read.
547 self
.taint(taintme
=False)
549 if (self
.__scalarindex
(index
, self
.ndim
)):
550 return depstrip(self
)[index
]
552 return depend_array(depstrip(self
)[index
], name
=self
._name
, synchro
=self
._synchro
, func
=self
._func
, dependants
=self
._dependants
, tainted
=self
._tainted
, base
=self
._bval
)
555 def __getslice__(self
,i
,j
):
556 """Overwrites standard get function."""
558 return self
.__getitem
__(slice(i
,j
,None))
561 """Alternative to standard get function."""
563 return self
.__get
__(slice(None,None,None))
565 def __get__(self
, instance
, owner
):
566 """Overwrites standard get function."""
568 # It is worth duplicating this code that is also used in __getitem__ as this
569 # is called most of the time, and we avoid creating a load of copies pointing to the same depend_array
573 self
.taint(taintme
=False)
577 def __setitem__(self
,index
,value
,manual
=True):
578 """Alters value[index] and taints dependencies.
580 Overwrites the standard method of setting value, so that dependent
581 quantities are tainted, and so we check that computed quantities are not
585 index: A slice variable giving the appropriate slice to be read.
586 value: The new value of the slice.
587 manual: Optional boolean giving whether the value has been changed
588 manually. True by default.
591 self
.taint(taintme
=False)
593 depstrip(self
)[index
] = value
595 elif index
== slice(None,None,None):
596 self
._bval
[index
] = value
598 raise IndexError("Automatically computed arrays should span the whole parent")
600 def __setslice__(self
,i
,j
,value
):
601 """Overwrites standard set function."""
603 return self
.__setitem
__(slice(i
,j
),value
)
605 def set(self
, value
, manual
=True):
606 """Alterative to standard set function.
612 self
.__setitem
__(slice(None,None),value
=value
,manual
=manual
)
614 def __set__(self
, instance
, value
):
615 """Overwrites standard set function."""
617 self
.__setitem
__(slice(None,None),value
=value
)
620 # np.dot and other numpy.linalg functions have the nasty habit to
621 # view cast to generate the output. Since we don't want to pass on
622 # dependencies to the result of these functions, and we can't use
623 # the ufunc mechanism to demote the class type to ndarray, we must
624 # overwrite np.dot and other similar functions.
625 # BEGINS NUMPY FUNCTIONS OVERRIDE
633 return __dp_dot(da
,db
)
636 # ENDS NUMPY FUNCTIONS OVERRIDE
638 def dget(obj
,member
):
639 """Takes an object and retrieves one of its attributes.
641 Note that this is necessary as calling it in the standard way calls the
642 __get__() function of member.
645 obj: A user defined class.
646 member: A string giving the name of an attribute of obj.
649 KeyError: If member is not an attribute of obj.
655 return obj
.__dict
__[member
]
657 def dset(obj
,member
,value
,name
=None):
658 """Takes an object and sets one of its attributes.
660 Necessary for editing any depend object, and should be used for
661 initialising them as well, as often initialization occurs more than once,
662 with the second time effectively being an edit.
665 obj: A user defined class.
666 member: A string giving the name of an attribute of obj.
667 value: The new value of member.
668 name: New name of member.
671 KeyError: If member is not an attribute of obj.
674 obj
.__dict
__[member
] = value
676 obj
.__dict
__[member
]._name
= name
679 """Removes dependencies from a depend_array.
681 Takes a depend_array and returns its value as a ndarray, effectively
682 stripping the dependencies from the ndarray. This speeds up a lot of
683 calculations involving these arrays. Must only be used if the value of the
684 array is not going to be changed.
687 deparray: A depend_array.
690 A ndarray with the same value as deparray.
693 if isinstance(da
, depend_array
): # only bother to strip dependencies if the array actually IS a depend_array
695 # print "!!! WARNING depstrip called on tainted array WARNING !!!!!" # I think we can safely assume that when we call depstrip the array has been cleared already but I am not 100% sure so better check - and in case raise the update
696 return da
.view(np
.ndarray
)
700 def deppipe(objfrom
,memberfrom
,objto
,memberto
):
701 """Synchronizes two depend objects.
703 Takes two depend objects, and makes one of them depend on the other in such
704 a way that both keep the same value. Used for attributes such as temperature
705 that are used in many different modules, and so need different depend objects
706 in each, but which should all have the same value.
709 objfrom: An object containing memberfrom.
710 memberfrom: The base depend object.
711 objto: An object containing memberto.
712 memberto: The depend object that should be equal to memberfrom.
715 dfrom
= dget(objfrom
,memberfrom
)
716 dto
= dget(objto
,memberto
)
717 dto
._func
= lambda : dfrom
.get()
718 dto
.add_dependency(dfrom
)
720 def depcopy(objfrom
,memberfrom
,objto
,memberto
):
721 """Copies the dependencies of one depend object to another.
726 dfrom
= dget(objfrom
,memberfrom
)
727 dto
= dget(objto
,memberto
)
728 dto
._dependants
= dfrom
._dependants
729 dto
._synchro
= dfrom
._synchro
730 dto
.add_synchro(dfrom
._synchro
)
731 dto
._tainted
= dfrom
._tainted
732 dto
._func
= dfrom
._func
733 if hasattr(dfrom
,"_bval"):
734 dto
._bval
= dfrom
._bval
737 class dobject(object):
738 """Class that allows standard notation to be used for depend objects."""
740 def __getattribute__(self
, name
):
741 """Overwrites standard __getattribute__().
743 This changes the standard __getattribute__() function of any class that
744 subclasses dobject such that depend objects are called with their own
745 __get__() function rather than the standard one.
748 value
= object.__getattribute
__(self
, name
)
749 if hasattr(value
, '__get__'):
750 value
= value
.__get
__(self
, self
.__class
__)
753 def __setattr__(self
, name
, value
):
754 """Overwrites standard __setattribute__().
756 This changes the standard __setattribute__() function of any class that
757 subclasses dobject such that depend objects are called with their own
758 __set__() function rather than the standard one.
762 obj
= object.__getattribute
__(self
, name
)
763 except AttributeError:
766 if hasattr(obj
, '__set__'):
767 return obj
.__set
__(self
, value
)
768 return object.__setattr
__(self
, name
, value
)