class LightVector(LightTensor): ################## # Initialization # ################## def __new__(cls, iterable): return np.array(iterable).view(cls) ################## # Static Methods # ################## @staticmethod def l_sub(a, b): return LightVector(a.view(np.ndarray) - b.view(np.ndarray)) @staticmethod def l_add(a, b): return LightVector(a.view(np.ndarray) + b.view(np.ndarray)) ########### # Methods # ########### #-----------------------------# # Methods that return scalars # #-----------------------------# def magnitude(self): """ The magnitude of the vector. """ return math.sqrt(self.dot(self)) # Aliases norm = function_alias('norm', magnitude) def size(self): """ The number of entries in the vector """ return self.shape[0] #---------------------------------------# # "Light" versions of methods in Vector # #---------------------------------------# def normalize(self): """ Performs an in-place normalization of `self`. :Examples: >>> Vector(2.0, 0.0, 0.0).normalize() Vector([ 1., 0., 0.]) >>> Vector(0.5, 0.5, 0.5).normalize() Vector([ 0.57735027, 0.57735027, 0.57735027]) >>> v = Vector(-0.5, 0.5, -0.5).normalize() >>> v Vector([-0.57735027, 0.57735027, -0.57735027]) >>> v.magnitude() 1.0 """ if self.is_zero(): raise ZeroDivisionError( "can't normalize a zero vector. (A zero vector, " "in this case, is one with a magnitude less than {0}. " "The vector you tried to normalize has a magnitude of " "{1}".format(self.zero_cutoff, self.magnitude())) norm = self.magnitude() for i in xrange(self.size()): self[i] /= norm return self def l_normalized(self): """ Same as `l_normalize()`, but does not modify self """ ret_val = copy(self) ret_val.normalize() return ret_val.view(LightVector) normalized = function_alias('normalized', l_normalized) def l_cross(self, other): """ Returns cross product of self with other. Wrapper to `numpy.cross` """ return l_cross(self, other) #----------------------------------------------------------------------# # Boolean-returning methods for determing aspects of the vector `self` # #----------------------------------------------------------------------# def is_cartesian(self): """ Returns True if the vector is Cartesian (i.e. if the vector is two or three dimensional). """ return self.size() == 2 or self.size() == 3 def is_zero_vector(self, cutoff=None): """ Alias for Tensor.is_zero() """ return self.is_zero(cutoff)
'AngularUnit', 'ElectricChargeUnit', 'MassUnit', 'TimeUnit' ] ############# # Utilities # ############# def isunit(unit): if isinstance(unit, Unit) or isinstance(unit, CompositeUnit): return True else: return False is_unit = function_alias('is_unit', isunit) def plural(unit): # pragma: no cover if not isunit(unit): raise TypeError return unit.__plural__ def convert_units(val, from_unit, to_unit): if not isunit(from_unit): raise UnknownUnitError(from_unit) if not isunit(to_unit): raise UnknownUnitError(to_unit) if from_unit == to_unit: return val return val * from_unit.to(to_unit) convert = function_alias('convert', convert_units)
from grendel.util.units.unit import isunit from grendel.util.metaclasses import Immutable __all__ = ["hasunits", "has_units", "strip_units", "stripunits", "ValueWithUnits", "Unitized"] ##################### # Utility functions # ##################### def has_units(obj): return hasattr(obj, "units") and obj.units is not None hasunits = function_alias("hasunits", has_units) # TODO Document this # TODO optional kwarg to strip units after converting to default units (which is non-trivial for composite units) @typechecked(convert_to=(None, isunit), assume_units=(None, isunit)) def strip_units(obj, convert_to=None, assume_units=None): """ Strips the units off of a unitized object (or, if the object is not `Unitized`, just return it). If `convert_to` is given, convert to these units if `obj` is `Unitized`. If `assume_units` is given, `obj` is assumed to have the units given by this argument if (and only if) it is not an instance of `Unitized`. """ if assume_units and not convert_to: raise TypeError( "call of strip_units with 'assume_units'" " argument must also have a 'convert_to' argument"
return ', '.join(fxn(i) for i in l[:-1]) + (',' if oxford_comma else '') + ' and ' + fxn(l[-1]) def orjoin(iterable, fxn=str, oxford_comma=True): l = list(iterable) length = len(l) if length == 0: return '<empty list>' elif length == 1: return fxn(l[0]) elif length == 2: return fxn(l[0]) + ' or ' + fxn(l[1]) else: return ', '.join(fxn(i) for i in l[:-1]) + (',' if oxford_comma else '') + ' or ' + fxn(l[-1]) ##################### # Dependent Imports # ##################### from grendel.util.exceptions import raises_error ########### # Aliases # ########### indent = function_alias('indent', indented) shortstr = function_alias('shortstr', short_str)
from grendel.util.metaclasses import Immutable __all__ = [ 'hasunits', 'has_units', 'strip_units', 'stripunits', 'ValueWithUnits', 'Unitized' ] ##################### # Utility functions # ##################### def has_units(obj): return hasattr(obj, 'units') and obj.units is not None hasunits = function_alias('hasunits', has_units) # TODO Document this # TODO optional kwarg to strip units after converting to default units (which is non-trivial for composite units) @typechecked( convert_to=(None, isunit), assume_units=(None, isunit)) def strip_units(obj, convert_to=None, assume_units=None): """ Strips the units off of a unitized object (or, if the object is not `Unitized`, just return it). If `convert_to` is given, convert to these units if `obj` is `Unitized`. If `assume_units` is given, `obj` is assumed to have the units given by this argument if (and only if) it is not an instance of `Unitized`. """ if assume_units and not convert_to: raise TypeError("call of strip_units with 'assume_units'"
class overloaded(MethodLike): """ Overload a function the Pythonic (at least, Python 2) way. Types can be specified using the `overloaded` instance decorator method `overload_with` (which is aliased as `overload`, `submethod`, and `subfunction`). :Examples: >>> @overloaded ... def test(): ... raise TypeError ... >>> @test.overload_with(int, int) ... def test(a, b=5): ... return a+b ... >>> @test.overload_with(str) ... def test(string, *args, **kwargs): ... return string + (' ' if len(args) else '') + ', '.join(list(args) + kwargs.keys()) ... >>> @test.overload_with(list, func=callable) ... def test(lst, func, *args): ... for item in lst: ... if func(item, *args): ... return item ... >>> test(1, 3) 4 >>> test(17) 22 >>> test("Hello") 'Hello' >>> test("Hello", "World") 'Hello World' >>> test("Hello", "Earth", "Mars", "Jupiter") 'Hello Earth, Mars, Jupiter' >>> test([1, 2, 3], lambda x: x**2 % 2 == 0) 2 >>> test([1, 2, 3], lambda x, y: x**2 % y == 0, 1) 1 >>> test(3.14159, 3.234) Traceback (most recent call last): ... TypeError: invalid function signature test(float, float). Available signatures are: test(a: int, b: int=5) Not valid signature because: argument 'a' is not of the correct type test(string: str, *args, **kwargs) Not valid signature because: argument 'string' is not of the correct type test(lst: list, func: <something for which callable(func) returns True>, *args) Not valid signature because: argument 'lst' is not of the correct type >>> # giving multiple values for a keyword argument means there is no match. For instance: >>> test('Hello', 'World', string='this is wrong') Traceback (most recent call last): ... TypeError: invalid function signature test(str, str, string=str). Available signatures are: test(a: int, b: int=5) Not valid signature because: argument 'a' is not of the correct type test(string: str, *args, **kwargs) Not valid signature because: got multiple values for keyword argument 'string' test(lst: list, func: <something for which callable(func) returns True>, *args) Not valid signature because: argument 'lst' is not of the correct type >>> # Methods in classes >>> class Foo(object): ... @overloaded ... def bar(self, *args, **kwargs): pass ... ... @bar.overload_with(a=int, b=int) ... def bar(self, a, b): ... if not hasattr(self, 'total'): ... self.total = 0 ... self.total += a + b ... return a + b ... >>> foo = Foo() >>> foo.bar(1,2) 3 >>> foo.bar(4,5) 9 >>> foo.total 12 >>> Foo.bar(foo, 7, 8) 15 >>> foo.total 27 """ ###################### # Private Attributes # ###################### _orig_func = None _versions = None ################## # Initialization # ################## def __init__(self, f): self._orig_func = f self.__name__ = f.__name__ self.__doc__ = f.__doc__ self.__dict__.update(f.__dict__) self._versions = [] ################### # Special Methods # ################### def __get__(self, instance, owner): # Mimic the behavior of the built-in function type return types.MethodType(self, instance, owner) def __call__(self, *args, **kwargs): fail_reasons = [] for typespec in self._versions: fargs, fkwargs_or_fail_reason = typespec.get_arg_list( *args, **kwargs) if fargs is not None: return typespec.func(*fargs, **fkwargs_or_fail_reason) else: fail_reasons.append(fkwargs_or_fail_reason or '<unknown reason for failed match>') # Should we fall back on the original function's code here? If so, we should catch and reraise any type errors raise TypeError( "invalid function signature {0}.\n Available signatures are:\n{1}" .format( argtypespec.get_call_signature(self.__name__, *args, **kwargs), indented('\n'.join( line + "\n" + indented("Not valid signature because: " + reason) for line, reason in zip( self._build_allowed_string().splitlines(), fail_reasons))))) ########### # Methods # ########### def getargspec(self): """ Pass-through to original function to conform with the `FunctionLike` protocol """ return inspect.getargspec(self._orig_func) def overload_with(self, *typespecs_or_func, **kwargs): """ Add a overloaded version of the decorated function to the available options for a given overloaded function. Types can be specified several ways. The simplest is to give the types as plain old arguments to the `overload_with` decorator. The order of the arguments given corresponds to the order of the arguments in the function definition. For example: >>> @overloaded ... def foo(): ... pass ... >>> @foo.overload_with(int, str) ... def foo(num, word): ... return word * num ... >>> foo(3, 'hello') 'hellohellohello' >>> foo('hello', 3) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: invalid function signature ... If fewer types are specified than arguments, it is assumed that each of the remaining arguments can be any type. (specifying `None` for the type has the same effect). Keyword arguments may also be given to `overload_with`, where the keyword must correspond to the argument name in the function definition. Here is an example of this specification technique (which may be mixed with the previous technique), using the overloaded function `foo` from above: >>> @foo.overload_with(int, int, c=int, string=str) ... def foo(a, b, string, c=1): ... return string*a*b*c ... >>> foo(2, 3, 'a') 'aaaaaa' Finally, types may be specified by way of Python 3 function annotations (this is the prefered technique if the code is only to be used in Python 3) by using the `overload_with` decorator without arguments. The type of a given argument need not be an instance of `type`. It can also be any callable that can be called with one argument, `None`, an `IterableOf` instance, a string, or a tuple of `type` any of these. The way these work is as follows: * If the type is a callable that does not return `False` (or anything with a truth value of False) when called with the argument value, the argument is considered to be of the correct type. * If one of the types in a sequence is `None` and the argument is `None`, the argument is considered to have the correct type. [#f2]_ * If the type is an instance if `IterableOf` (or one of its subtypes), the argument is considered to be the correct type if and only if all of the items in the container are considered the correct type when the criteria described here are applied with the `types` attribute of the `IterableOf` instance * If the type is a string, the string is evaluated in the context of the overloaded function's globals, and the return value of this evaluation is used as the type (which may, in turn, be any of the options described here except for a string). This is particularly useful when the function to be overloaded is a method that takes as an argument an instance of that method's parent class. Since the parent class will not be defined at the time of the function definition, using the parent class's name as an identifier will not work. * Finally, if a `tuple` (or any other `Iterable`) of type specifications is given, the argument is considered to have the correct type if any of the type specifications in the list lead to the correct type. For example: >>> def str_reversed(var): ... return ''.join(reversed(var)) if isinstance(var, str) else NotImplemented ... >>> @foo.overload_with( ... bar=lambda x: x == str_reversed(x), ... n=(int, lambda x: isinstance(x, float) and round(x) == x) ... ) ... def foo(bar, n): ... return bar * int(n) + ' is a palendrome!' ... >>> @foo.overload_with(baz=str) ... def foo(baz, n=3): ... return baz * int(n) + ' is not a palendrome.' ... >>> foo('racecar', 2.0) 'racecarracecar is a palendrome!' >>> foo('asdf') 'asdfasdfasdf is not a palendrome.' The above example also illustrates the order of precidence for overloaded functions: if a call is allowed to multiple versions of the function, the first to be defined is used. (Notice that the first call in the above example matches both overloaded versions of `foo` defined in the example). ..warning :: To allow for the use Python 3 function annotations, specifying a single argument to `overload_with` that is a callable will assume that the Python 3 type specification method is being used, as described above. This could cause problems if what is actually intended is that there be only one type-constrained argument which returns `True` for an arbitrary callable. To get around this, simply specify the type callable for the single argument using the keyword form or as a single item tuple. If the callable you want to use for this specification is a builtin such as `callable` or `int`, this shouldn't be an issue. .. rubric:: Footnotes .. [#f2] Note that if the type is `None` and it is not in a sequence, or a length one tuple `(None,)` is given, this is considered a match for all types. This is mostly to allow arguments to be left out of the specification (in particular, the first argument of most methods, `self`) """ # TODO check to make sure type specifications are valid if sys.version_info > (3, 0) \ and len(kwargs) == 0 \ and len(typespecs_or_func) == 1 \ and inspect.isfunction(typespecs_or_func[0]) \ and not inspect.isbuiltin(typespecs_or_func[0]): func = typespecs_or_func[0] if hasattr(func, 'func_annotations'): # Python 3 version with func_annotations: argspec = inspect.getargspec(func) # for now, ignore varargs and keywords annotations annotations = func.func_annotations typespec_dict = {} for arg in argspec: typespec_dict[arg] = func.func_annotations[arg] self._versions.append(argtypespec(func, typespec_dict)) return self else: raise TypeError( "when not using Python 3 function annotations, " "{}.overload_with must\nbe called with " "type specifications as arguments".format(self.__name__)) else: def _get_func_decorator(f): argspec = inspect.getargspec(f) typespec_dict = {} # remember zip always truncates at the length of the shortest iterator for spec, argname in zip(typespecs_or_func, argspec.args): typespec_dict[argname] = spec for argname, spec in kwargs.items(): if argname in typespec_dict: raise TypeError( "multiple type specifications given for " "argument {} to overloaded function {}()".format( argname, self.__name__)) if argname not in argspec.args: raise TypeError( "type specifiation for unknown argument {} given to overloaded " "function {}()".format(argname, self.__name__)) typespec_dict[argname] = spec # fill in None for anything missing, indicating any type will do for argname in argspec.args: if argname not in typespec_dict: typespec_dict[argname] = None self._versions.append(argtypespec(f, typespec_dict)) return self return _get_func_decorator submethod = function_alias('submethod', overload_with) subfunction = function_alias('subfuntion', overload_with) overload = function_alias('overload', overload_with) ################### # Private Methods # ################### def _build_allowed_string(self): return '\n'.join(typespec.signature for typespec in self._versions)
Derivative properties can be automatically or manually transformed to other representations. """ if not issubclass(prop, MolecularProperty): raise TypeError if raises_error(int, order): raise TypeError name = '_{1}Derivative{0}'.format(order, prop.__name__) if name in DerivativeProperty.known_subclasses: ret_val = PartiallyConstructed(DerivativeProperty.known_subclasses[name]) else: ret = type(name, (DerivativeProperty,), {}) DerivativeProperty.known_subclasses[name] = ret ret_val = PartiallyConstructed(ret) ret_val.with_attributes(order=order, representation=representation) return ret_val PropertyDerivative = function_alias('PropertyDerivative', MolecularPropertyDerivative) Gradient = EnergyGradient = Forces = PropertyDerivative(Energy, order=1) Hessian = EnergyHessian = PropertyDerivative(Energy, order=2) ##################### # Dependent Imports # ##################### from grendel.differentiation.derivative_tensor import RepresentationDependentTensor from grendel.representations.internal_representation import InternalRepresentation from grendel.representations.representation import Representation
__all__ = [ 'hasunits', 'has_units', 'strip_units', 'stripunits', 'ValueWithUnits', 'Unitized' ] ##################### # Utility functions # ##################### def has_units(obj): return hasattr(obj, 'units') and obj.units is not None hasunits = function_alias('hasunits', has_units) # TODO Document this # TODO optional kwarg to strip units after converting to default units (which is non-trivial for composite units) @typechecked(convert_to=(None, isunit), assume_units=(None, isunit)) def strip_units(obj, convert_to=None, assume_units=None): """ Strips the units off of a unitized object (or, if the object is not `Unitized`, just return it). If `convert_to` is given, convert to these units if `obj` is `Unitized`. If `assume_units` is given, `obj` is assumed to have the units given by this argument if (and only if) it is not an instance of `Unitized`. """ if assume_units and not convert_to: raise TypeError( "call of strip_units with 'assume_units'"
class Representation(Freezable): """ Superclass of all the representations types. :Attributes: molecule : `Molecule` The `Molecule` object represented by `self`. coords : list of `Coordinate` The coordinates that make up the representation """ __metaclass__ = ABCMeta ############## # Attributes # ############## molecule = FreezableAttribute(name="molecule", doc=""" The `Molecule` object represented by `self`. """) coords = FreezableListAttribute(name="coords", doc=""" The coordinates that make up the representation """) units = ReadOnlyAttribute(name='units', doc=""" The units to use for the coordinates created, or if the created coordinates vary in units, a `dict` of `UnitGenre`, `Unit` pairs. Must be passed into constructor; defaults to `genre.default`, where `genre` is the `UnitGenre` subclass of the applicable units. """) ################### # Special Methods # ################### def __getitem__(self, item): return self.coords[item] def __len__(self): return len(self.coords) def __iter__(self): for coord in self.coords: yield coord ############## # Properties # ############## @property def values(self): vals = [c.value for c in self.coords] return Vector(vals) value = values #################### # Abstract Methods # #################### @abstractmethod def add_coordinate_copy(self, coordinate): return NotImplemented @abstractmethod def copy_with_molecule(self, molecule): """ Make a copy of `self` that is the same in every way except for the `molecule` attribute. New Coordinate objects are created using the `Coordinate.copy_for_representation()` method for each element of `self.coords`. This is an abstract method that *must* be implemented by all Representation subclasses. """ return NotImplemented @abstractmethod def displaced_by(self, disp, tol=None, maxiter=None): """ Apply the `Displacement` instance `disp` to the molecule and current representation, generating a new molecule and a new representation (which start as a `deepcopy` and the return value of `Representation.copy_with_molecule`, respectively) with the displacement applied. This is an abstract method that *must* be implemented by all Representation subclasses. """ return NotImplemented ########### # Methods # ########### def values_for_molecule(self, mol): return Vector([c.value_for_molecule(mol) for c in self.coords]) value_for_molecule = function_alias('value_for_molecule', values_for_molecule) def values_for_matrix(self, mat): return Vector([c.value_for_molecule_matrix(mat) for c in self.coords]) value_for_matrix = function_alias('value_for_matrix', values_for_matrix)
class ComputationResultGetter(ResultGetter): """ """ #################### # Class Attributes # #################### known_result_getters = {} directory_series = {} ############## # Attributes # ############## computations = None computation_kwargs = None use_directory_series = None ################## # Initialization # ################## def __new__(cls, use_directory_series=False, **kwargs): """ """ # First set ourselves up and parse our own kwargs... # Now remove the molecule property, if it's present, so the kwargs are usable for lots of stuff... kwargs.pop('molecule', None) # Check to see if an identical ComputationResultGetter exists try: key = frozenset((k, v) for k, v in kwargs.iteritems()) except TypeError: key = frozenset((id(k), id(v)) for k, v in kwargs.iteritems()) if key in ComputationResultGetter.known_result_getters: ret_val = ComputationResultGetter.known_result_getters[key] else: ret_val = object.__new__(cls) ret_val.use_directory_series = use_directory_series if use_directory_series: raise NotImplementedError # Then pass the remaining kwargs to computations when they are generated... ret_val.computation_kwargs = copy(kwargs) ComputationResultGetter.known_result_getters[key] = ret_val ret_val.computations = [] return ret_val ########### # Methods # ########### def can_get_property_for_molecule(self, molecule, property, details=None): try: comp = self._get_comp(molecule, property, details, True) if comp.runner is not None: comp.runner.validate() if comp.input_generator is not None: comp.input_generator.validate() if comp.output_parser is not None: comp.output_parser.validate() except ComputationUnavailableError: return False return True can_get_property = function_alias('can_get_property', can_get_property_for_molecule) def has_property_for_molecule(self, molecule, property, details=None, verbose=False): comp = self._get_comp(molecule, property, details, False) if comp is None: return False return comp.has_property(property, details) has_property = function_alias('has_property', can_get_property_for_molecule) def get_computation_for_property(self, molecule, property, details=None): comp = self._get_comp(molecule, property, details, True) # Try to generate any errors that would cause can_get_property_for_molecule to fail... if comp.runner is not None: comp.runner.validate() if comp.input_generator is not None: comp.input_generator.validate() if comp.output_parser is not None: comp.output_parser.validate() # Return it, whether or not it has been run. return comp def get_property_for_molecule(self, molecule, property, details=None): comp = self.get_computation_for_property(molecule, property, details) # See if we already have the property... result = comp.get_property(property, details) if result is not None: return result else: # Now run the calculation comp.run() return comp.get_property(property, details) def add_computation(self, comp): self.computations.append(comp) ################### # Private Methods # ################### def _get_comp(self, molecule, property, details, generate): for comp in self.computations: # TODO offer both "is" and "==" (as well as "is_same_molecule") options if comp.molecule is molecule: for prop in comp.properties: if MolecularProperty.is_same_property(prop, property): if details is None or details.is_subset_of( prop.details): return comp # well, we couldn't find one. Should we make one? if generate: if 'property' in self.computation_kwargs: prop = self.computation_kwargs['property'] if not MolecularProperty.is_same_property(prop, property): raise ComputationUnavailableError( "property mismatch. Can't generate computation" " of property '{}'".format( MolecularProperty.property_type_of( property).__name__)) # This will append the computation to the self.computations list automatically comp = Computation(molecule=molecule, result_getter=self, **self.computation_kwargs) else: # This will append the computation to the self.computations list automatically comp = Computation(molecule=molecule, property=property, result_getter=self, **self.computation_kwargs) return comp else: return None
top='-', left='|', right='|', bottom='-', top_left='+', top_right=None, bottom_left=None, bottom_right=None ): if top_right is None: top_right = top_left if bottom_left is None: bottom_left = top_left if bottom_right is None: bottom_right = top_right rv = top_left + (top*(width-2)) + top_right + "\n" rv += left + "{{0:^{0}}}".format(width-2).format(text) + right + "\n" rv += bottom_left + (bottom*(width-2)) + bottom_right return rv ##################### # Dependent Imports # ##################### from grendel.util.exceptions import raises_error ########### # Aliases # ########### indent = function_alias('indent', indented) shortstr = function_alias('shortstr', short_str)