Source code for underworld.function.view

##~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~##
##                                                                                   ##
##  This file forms part of the Underworld geophysics modelling application.         ##
##                                                                                   ##
##  For full license and copyright information, please refer to the LICENSE.md file  ##
##  located at the project root, or contact the authors.                             ##
##                                                                                   ##
##~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~##
"""
This module includes functions which provide views into the results of
function queries.  These functions never modify query data. 
"""

import underworld.libUnderworld.libUnderworldPy.Function as _cfn
from . import _function

[docs]class min_max(_function.Function): """ This function records the min & max result from a queried function. Note that this function simply records the min/max values encountered when it is evaluated. Therefore, if it has not been evaluated at all, the values returned via one of its methods ('min_local', 'min_global', etc ) will simply be initialisation values. For vector input types, this function will report on the magnitude of the vector. Parameters ---------- fn: underworld.function.Function The primary function. If `fn_norm` is not provided, this is used to calculate the min_max. Results from this function are always passed back. fn_norm: underworld.function.Function This function returns a norm like quantity by which the min and max are determined. For example, where the primary function is a vector quantity, this function might calculate the magnitude of that vector. This function must return a scalar result, and must be provided where the primary function is non-scalar. See the example below for usage. fn_auxiliary: underworld.function.Function An auxiliary function which is evaluated at the location of the min/max. For example, often the coordinate where the min/max values occur are required, and so the user may pass in fn.input() as the auxiliary function to achieve this Example ------- Create a simple function which returns two times its input: >>> import underworld as uw >>> import underworld.function as fn >>> import numpy as np >>> fn_simple = fn.input()[0]*2. Let's wrap it with a min_max function: >>> fn_minmax_simple = fn.view.min_max(fn_simple) Now do an evaluation: >>> fn_minmax_simple.evaluate(5.) array([[ 10.]]) Since there's only been one evaluation, min and max values should be identical: >>> fn_minmax_simple.min_global() 10.0 >>> fn_minmax_simple.max_global() 10.0 Do another evaluation: >>> fn_minmax_simple.evaluate(-3.) array([[-6.]]) Now check min and max again: >>> fn_minmax_simple.min_global() -6.0 >>> fn_minmax_simple.max_global() 10.0 Note that if we only evaluate the subject function, no min/max values are recorded: >>> fn_simple.evaluate(3000.) array([[ 6000.]]) >>> fn_minmax_simple.max_global() 10.0 Also note that for vector valued subject function, `fn_norm` must be provided: >>> fn_vec = fn.input()*(1.,1.) >>> fn_vec_mm = fn.view.min_max(fn_vec) >>> fn_vec_mm.evaluate( 2. ) Traceback (most recent call last): ... RuntimeError: Issue utilising function of class 'min_max' constructed at: <BLANKLINE> --- CONSTRUCTION TIME STACK --- Error message: Argument function does not return scalar results. You must also provide a function which calculates the required norm like quantity via the `fn_norm` parameter. >>> fn_vec_mm = fn.view.min_max(fn_vec, fn_norm=fn.math.dot(fn_vec,fn_vec)) >>> fn_vec_mm.evaluate( 2. ) array([[ 2., 2.]]) >>> fn_vec_mm.max_global() 8.0 >>> fn_vec_mm.evaluate( -1. ) array([[-1., -1.]]) >>> fn_vec_mm.min_global() 2.0 >>> fn_vec_mm.max_global() 8.0 To obtain the min/max values across a MeshVariable object, you will need to evaluate the function across all nodes of the MeshVariable: >>> mesh = uw.mesh.FeMesh_Cartesian() >>> meshvariable = uw.mesh.MeshVariable( mesh, 1 ) >>> meshvariable.data[:] = np.random.randint(100,size=meshvariable.data.shape) # init with random data >>> fn_mv = fn.view.min_max(meshvariable) # create min_max view wrapper >>> ignore = fn_mv.evaluate(mesh) # this call will evaluate at all nodes >>> np.allclose(fn_mv.min_local(),meshvariable.data.min()) True >>> np.allclose(fn_mv.max_local(),meshvariable.data.max()) True Note that when operating in parallel, the `min_global()` and `max_global()` methods are a good option for extracting discrete object global min/max values, as the numpy views will only report the local min/max values. Also note that since min_max views only record results as they are evaluated, if the underlying subject function min/max values change, this will not be recorded by the min_max view until its evaluate encounters the new min/max values: >>> meshvariable.data[3] = 1000 # change some random value >>> np.allclose(fn_mv.max_local(),meshvariable.data.max()) # check again, it should be false False >>> ignore = fn_mv.evaluate(mesh) # evaluate across all nodes again >>> np.allclose(fn_mv.max_local(),meshvariable.data.max()) # check again True Similarly, the view's min/max values are only updated when smaller/larger min/max values are encountered. So, if the underlying subject function's maximum (for example) is reduced, the view will not record this if its currently stored value exceeds the new maximum. A call to `reset()` is required: >>> fn_mv.max_local() 1000.0 >>> meshvariable.data[3] = 500 # reduce max >>> ignore = fn_mv.evaluate(mesh) # evaluate across all nodes again >>> fn_mv.max_local() # note that it still records old value 1000.0 >>> fn_mv.reset() # now re-init view's min/max >>> ignore = fn_mv.evaluate(mesh) # evaluate across all nodes again >>> fn_mv.max_local() # it should now record new value 500.0 The auxiliary function allows you to obtain secondary information at the function minimum. One common use case would be to obtain a location where the min/max was obtained: >>> fn_mv = fn.view.min_max(meshvariable, fn_auxiliary=fn.input()) >>> meshvariable.data[1] = 1000.0 # set second node to have the highest value >>> ignore = fn_mv.evaluate(mesh) >>> fn_mv.max_global() 1000.0 >>> np.allclose( mesh.data[1], fn_mv.max_global_auxiliary() ) # ensure max is obtain at required mesh node. True """ def __init__(self, fn, fn_norm=None, fn_auxiliary=None, *args, **kwargs): _fn = _function.Function.convert(fn) if _fn == None: raise ValueError( "provided 'fn' must a 'Function' or convertible.") self._fn = _fn fn_norm_cself = None if fn_norm: _fn_norm = _function.Function.convert(fn_norm) if _fn_norm == None: raise ValueError( "provided 'fn_norm' must a 'Function' or convertible.") self._fn_norm = _fn_norm fn_norm_cself = _fn_norm._fncself fn_auxiliary_cself = None if fn_auxiliary: _fn_auxiliary = _function.Function.convert(fn_auxiliary) if _fn_auxiliary == None: raise ValueError( "provided 'fn_auxiliary' must a 'Function' or convertible.") self._fn_auxiliary = _fn_auxiliary fn_auxiliary_cself = _fn_auxiliary._fncself # create c instance self._fncself = _cfn.MinMax( self._fn._fncself, fn_norm_cself, fn_auxiliary_cself ) # build parent super(min_max,self).__init__(argument_fns=[fn,],**kwargs)
[docs] def min_local(self): """ Returns the minimum value encountered locally on the current process. Returns ------- double: minimum value """ return self._fncself.getMin()
[docs] def max_local(self): """ Returns the max value encountered locally on the current process. Returns ------- double: maximum value """ return self._fncself.getMax()
[docs] def min_global(self): """ Returns the minimum value encountered across all processes. Notes ----- This method must be called by collectively all processes. Returns ------- double: minimum value """ return self._fncself.getMinGlobal()
[docs] def max_global(self): """ Returns the maximum value encountered across all processes. Notes ----- This method must be called by collectively all processes. Returns ------- double: maximum value """ return self._fncself.getMaxGlobal()
def _functionio_for_numpy(self,function_io_guy): """ This method simply takes a swig proxy to a FunctionIO, and returns the data as a numpy array. """ # create input function just so we can process using query inputfn = _function.input() # create function_io iterator func_io_it = _cfn.FunctionIOIter(function_io_guy) # process return _cfn.Query(inputfn._fncself).query(func_io_it)
[docs] def min_local_auxiliary(self): """ Returns the results of the auxiliary function evaluated at the location corresponding to the primary function minimum. This method only considers results on the current process. Returns ------- FunctionIO: value at local minimum. """ return self._functionio_for_numpy(self._fncself.getMinAux())
[docs] def max_local_auxiliary(self): """ Returns the results of the auxiliary function evaluated at the location corresponding to the primary function maximum. This method only considers results on the current process. Returns ------- FunctionIO: value at local maximum. """ return self._functionio_for_numpy(self._fncself.getMaxAux())
[docs] def min_global_auxiliary(self): """ Returns the results of the auxiliary function evaluated at the location corresponding to the primary function minimum. This method considers results across all processes (ie, globally). Notes ----- This method must be called by collectively all processes. Returns ------- FunctionIO: value at global minimum. """ # first make sure that we have determined the rank with the min self.min_global() import underworld as uw # if we are the rank with the min result, extract result if uw.mpi.rank == self.min_rank(): auxout = self.min_local_auxiliary() else: auxout = None from mpi4py import MPI comm = MPI.COMM_WORLD # broadcast data = comm.bcast(auxout, root=self.min_rank()) return data
[docs] def max_global_auxiliary(self): """ Returns the results of the auxiliary function evaluated at the location corresponding to the primary function maximum. This method considers results across all processes (ie, globally). Notes ----- This method must be called by collectively all processes. Returns ------- FunctionIO: value at global maximum. """ # first make sure that we have determined the rank with the max self.max_global() import underworld as uw # if we are the rank with the max result, extract result if uw.mpi.rank == self.max_rank(): auxout = self.max_local_auxiliary() else: auxout = None from mpi4py import MPI comm = MPI.COMM_WORLD # broadcast data = comm.bcast(auxout, root=self.max_rank()) return data
[docs] def min_rank(self): """ Returns the rank where the minimum occurs. Note that this method will return -1 until min_global has been called. Returns ------- int: rank """ rank = self._fncself.getMinRank() if rank < 0: raise RuntimeError("You must run one of the `min_global` MinMax routines first to determine \ which rank contains the minimum value.") return rank
[docs] def max_rank(self): """ Returns the rank where the maximum occurs. Note that this method will return -1 until max_global has been called. Returns ------- int: rank """ rank = self._fncself.getMaxRank() if rank < 0: raise RuntimeError("You must run one of the `max_global` MinMax routines first to determine \ which rank contains the maximum value.") return rank
[docs] def reset(self): """ Resets the minimum and maximum values. """ return self._fncself.reset()
[docs]class count(_function.Function): """ This function simply records the number of times a function has been called. Example ------- Create a simple function which returns two times its input: >>> import underworld as uw >>> import underworld.function as fn >>> import numpy as np >>> fn_simple = fn.input() >>> fn_count = fn.view.count(fn_simple) Now do an evaluation: >>> result = fn_count.evaluate(5.) Check count: >>> fn_count.count() 1 Do a few more evaluations: >>> result = fn_count.evaluate(5.) >>> result = fn_count.evaluate(5.) >>> result = fn_count.evaluate(5.) >>> fn_count.count() 4 Reset and go again >>> fn_count.reset() >>> result = fn_count.evaluate(5.) >>> result = fn_count.evaluate(5.) >>> fn_count.count() 2 """ def __init__(self, fn): _fn = _function.Function.convert(fn) if _fn == None: raise ValueError( "provided 'fn' must a 'Function' or convertible.") self._fn = _fn # create c instance self._fncself = _cfn.Count( self._fn._fncself ) # build parent super(count,self).__init__(argument_fns=[fn,])
[docs] def count(self): """ Returns the function call count. Returns ------- int: count """ return self._fncself.count
[docs] def reset(self): """ Resets the count. """ return self._fncself.reset()