Source code for pyforce.tools.functions_list

# List of functions for storage
# Author: Stefano Riva, PhD Student, NRG, Politecnico di Milano
# Latest Code Update: 24 May 2024
# Latest Doc  Update: 24 May 2024

import numpy as np
import warnings
from sklearn.model_selection import train_test_split as sk_split

from dolfinx.fem import (Function, FunctionSpace)
from petsc4py import PETSc

# Class to define a list of function, useful to collect snapshots and basis functions
[docs] class FunctionsList(): r""" A class wrapping a list of functions. They are stored as a `list` of `np.ndarray`. Parameters ---------- function_space : FunctionSpace, optional Functional Space onto which the Function are defined. If not provided, `dofs` must be specified. dofs : int, optional Degrees of freedom of the functions :math:`\mathcal{N}_h`. Required if `function_space` is not provided. """ def __init__(self, function_space : FunctionSpace = None, dofs: int = None) -> None: self.fun_space = function_space self._list = list() if self.fun_space: tmp_fun = Function(self.fun_space).copy() self.fun_shape = tmp_fun.x.array.shape[0] elif dofs is not None: self.fun_shape = dofs else: raise ValueError("Either `function_space` or `dofs` must be provided.") def __call__(self, idx) -> np.ndarray: """ Defining the class as callable, returns the idx-th element of the list """ return self._list[idx] def __len__(self) -> int: """ Defining the length of the class as the length of the stored list """ return len(self._list)
[docs] def append(self, dofs_fun: np.ndarray) -> None: """ Extend the current list by adding a new function. The dolfinx.fem.Function element is stored in a list as a numpy array, to avoid problems when the number of elements becomes large. The input can be either a `np.ndarray` or a `dolfinx.fem.Function`, in the latter case it is mapped to `np.ndarray`. Parameters ---------- dofs_fun : np.ndarray Functions to be appended. """ if isinstance(dofs_fun, Function): assert dofs_fun.x.array.shape[0] == self.fun_shape, "The input function dofs_fun has "+str(dofs_fun.x.array.shape[0])+", instead of "+str(self.fun_shape) self._list.append(dofs_fun.x.array[:]) else: assert dofs_fun.shape[0] == self.fun_shape, "The input function dofs_fun has "+str(dofs_fun.shape[0])+", instead of "+str(self.fun_shape) self._list.append(dofs_fun)
[docs] def delete(self, idx: int) -> None: """ Delete a single element in position `idx`. Parameters ---------- idx : int Integers indicating the position inside the `_list` to delete. """ del self._list[idx]
[docs] def copy(self): """ Defining the copy of the `_list` of elements """ return self._list.copy()
[docs] def clear(self) -> None: """Clear the storage.""" self._list = list()
[docs] def sort(self, order: list) -> None: """ Sorting the list according to order - iterable of indices. Parameters ---------- order : list List of indices for the sorting. """ tmp = self._list.copy() self.clear() assert len(tmp) == len(order), "The order vector ("+str(len(order))+") must have the same length of the list ("+str(len(tmp))+")" for ii in range(len(order)): self.append(tmp[order[ii]])
[docs] def return_matrix(self) -> np.ndarray: r""" Returns the list of arrays as a matrix :math:`S\in\mathbb{R}^{\mathcal{N}_h\times N_s}`. """ return np.asarray(self._list).T
[docs] def map(self, idx: int) -> Function: """ Mapping the element in position `idx` into a `dolfinx.fem.Function`. Parameters ---------- idx : int Integers indicating the position inside the `_list`. Returns ------- eval_fun : Function Evaluated dofs into a Function """ if not self.fun_space: raise ValueError("Function space not defined.") eval_fun = Function(self.fun_space).copy() with eval_fun.vector.localForm() as loc: loc.set(0.0) eval_fun.x.array[:] = self._list[idx] eval_fun.x.scatter_forward() return eval_fun
[docs] def lin_combine(self, vec: np.ndarray, use_numpy=True) -> np.ndarray: r""" Linearly combine functions (`a_i = vec[i]`) in the list :math:`\{\phi_i\}_{i=0}^N`: .. math:: \sum_{i=0}^N a_i\cdot \phi_i given `N = len(vec)` and :math:`\mathbf{a}\in\mathbb{R}^N`. Parameters ---------- vec : np.ndarray Iterable containing the coefficients of the linear combination. use_numpy : boolean, optional (Default=True) If `True` the functions are treated as `np.ndarray`, otherwise the formulation in `dolfinx` is used. Returns ------- combination : np.ndarray or Function Function object storing the result of the linear combination """ if use_numpy or not self.fun_space: combination = np.zeros(self.fun_shape,) for ii in range(len(vec)): combination += vec[ii] * self._list[ii] return combination else: combination = Function(self.fun_space).copy() with combination.vector.localForm() as loc: loc.set(0.0) for ii in range(len(vec)): combination.vector.axpy(vec[ii], self.map(ii).vector) combination.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) return combination
[docs] def train_test_split(params: list, fun_list: FunctionsList, test_size: float = 0.33, random_state: int = 42): """ This function can be used to create a train and test using `sklearn` capabilities. Parameters ---------- params : list List of parameters to be split. fun_list: FunctionsList Object containing the functions as a list of arrays to convert. test_size : float DimensionFunctional space of the functions (the dofs should be compliant). random_state : int, optional (Default = 42) Random seed for the splitting algorithm. Returns ------- train_params : list List of the train parameters. test_params : list List of the test parameters. train_fun : list List of the train functions. test_fun : list List of the test functions. """ assert len(fun_list) == len(params), "Snapshots and parameters must have the same length." res = sk_split(params, fun_list._list, test_size=test_size, random_state=random_state) # Store results - X train_params = res[0] test_params = res[1] # Store results - Y if fun_list is not None: train_fun = FunctionsList(function_space = fun_list.fun_space) test_fun = FunctionsList(function_space = fun_list.fun_space) else: train_fun = FunctionsList(dofs=fun_list.fun_shape) test_fun = FunctionsList(dofs=fun_list.fun_shape) for train in res[2]: train_fun.append(train) for test in res[3]: test_fun.append(test) return train_params, test_params, train_fun, test_fun
##################################################################################################################### # class FunctionsList(): # r""" # A class wrapping a list of functions. They are stored as a `list` of `np.ndarray`. # Parameters # ---------- # function_space : FunctionSpace # Functional Space onto which the Function are defined. # """ # def __init__(self, function_space: FunctionSpace) -> None: # self.fun_space = function_space # self._list = list() # tmp_fun = Function(self.fun_space).copy() # self.fun_shape = tmp_fun.x.array.shape[0] # this must be a int # def __call__(self, idx) -> np.ndarray: # """ # Defining the class as callable, returns the idx-th element of the list # """ # return self._list[idx] # def __len__(self) -> int: # """ # Defining the length of the class as the length of the stored list # """ # return len(self._list) # def append(self, dofs_fun: np.ndarray) -> None: # """ # Extend the current list by adding a new function. # The dolfinx.fem.Function element is stored in a list as a numpy array, to avoid problems when the number of elements becomes large. # The input can be either a `np.ndarray` or a `dolfinx.fem.Function`, in the latter case it is mapped to `np.ndarray`. # Parameters # ---------- # dofs_fun : np.ndarray # Functions to be appended. # """ # if isinstance(dofs_fun, Function): # assert dofs_fun.x.array.shape[0] == self.fun_shape, "The input function dofs_fun has "+str(dofs_fun.x.array.shape[0])+", instead of "+str(self.fun_shape) # self._list.append(dofs_fun.x.array[:]) # else: # assert dofs_fun.shape[0] == self.fun_shape, "The input function dofs_fun has "+str(dofs_fun.shape[0])+", instead of "+str(self.fun_shape) # self._list.append(dofs_fun) # def delete(self, idx: int) -> None: # """ # Delete a single element in position `idx`. # Parameters # ---------- # idx : int # Integers indicating the position inside the `_list` to delete. # """ # del self._list[idx] # def copy(self): # """ # Defining the copy of the `_list` of elements # """ # return self._list.copy() # def clear(self) -> None: # """Clear the storage.""" # self._list = list() # def sort(self, order: list) -> None: # """ # Sorting the list according to order - iterable of indices. # Parameters # ---------- # order : list # List of indices for the sorting. # """ # tmp = self._list.copy() # self.clear() # assert len(tmp) == len(order), "The order vector ("+str(len(order))+") must have the same length of the list ("+str(len(tmp))+")" # for ii in range(len(order)): # self.append(tmp[order[ii]]) # def return_matrix(self) -> np.ndarray: # r""" # Returns the list of arrays as a matrix :math:`S\in\mathbb{R}^{\mathcal{N}_h\times N_s}`. # """ # return np.asarray(self._list).T # def map(self, idx: int) -> Function: # """ # Mapping the element in position `idx` into a `dolfinx.fem.Function`. # Parameters # ---------- # idx : int # Integers indicating the position inside the `_list`. # Returns # ------- # eval_fun : Function # Evaluated dofs into a Function # """ # eval_fun = Function(self.fun_space).copy() # with eval_fun.vector.localForm() as loc: # loc.set(0.0) # eval_fun.x.array[:] = self._list[idx] # eval_fun.x.scatter_forward() # return eval_fun # def lin_combine(self, vec: np.ndarray, use_numpy=True) -> Function: # r""" # Linearly combine functions (`a_i = vec[i]`) in the list :math:`\{\phi_i\}_{i=0}^N`: # .. math:: # \sum_{i=0}^N a_i\cdot \phi_i # given `N = len(vec)` and :math:`\mathbf{a}\in\mathbb{R}^N`. # Parameters # ---------- # vec : np.ndarray # Iterable containing the coefficients of the linear combination. # use_numpy : boolean, optional (Default=True) # If `True` the functions are treated as `np.ndarray`, otherwise the formulation in `dolfinx` is used. # Returns # ------- # combination : Function # Function object storing the result of the linear combination # """ # if use_numpy: # combination = np.zeros(self.fun_shape,) # for ii in range(len(vec)): # combination += vec[ii] * self._list[ii] # return combination # else: # combination = Function(self.fun_space).copy() # with combination.vector.localForm() as loc: # loc.set(0.0) # for ii in range(len(vec)): # combination.vector.axpy(vec[ii], self.map(ii).vector) # combination.vector.ghostUpdate(addv=PETSc.InsertMode.INSERT, mode=PETSc.ScatterMode.FORWARD) # return combination # # Class to define a list of function, useful to collect snapshots and basis functions # class FunctionsMatrix(): # r""" # A class wrapping a list of functions without relying on *dolfinx* capabilities. They are stored as a `list` of `np.ndarray`. # Parameters # ---------- # dofs : int # Degrees of freedom of the functions :math:`\mathcal{N}_h`. # """ # def __init__(self, dofs: int) -> None: # self.dofs = dofs # self._list = list() # def __call__(self, idx) -> np.ndarray: # """ # Defining the class as callable, returns the idx-th element of the list # """ # return self._list[idx] # def __len__(self) -> int: # """ # Defining the length of the class as the length of the stored list # """ # return len(self._list) # def return_matrix(self) -> np.ndarray: # r""" # Returns the list of arrays as a matrix :math:`S\in\mathbb{R}^{\mathcal{N}_h\times N_s}`. # """ # return np.asarray(self._list).T # def append(self, dofs_fun: np.ndarray) -> None: # """ # Extend the current list by adding a new function. The input must be a `np.ndarray` object. # Parameters # ---------- # dofs_fun : np.ndarray # Functions to be appended. # """ # if isinstance(dofs_fun, Function): # assert dofs_fun.x.array.shape[0] == self.dofs, "The input function dofs_fun has "+str(dofs_fun.x.array.shape[0])+", instead of "+str(self.fun_shape) # self._list.append(dofs_fun.x.array[:]) # else: # assert dofs_fun.shape[0] == self.dofs, "The input function dofs_fun has "+str(dofs_fun.shape[0])+", instead of "+str(self.fun_shape) # self._list.append(dofs_fun) # def delete(self, idx: int) -> None: # """ # Delete a single element in position `idx`. # Parameters # ---------- # idx : int # Integers indicating the position inside the `_list` to delete. # """ # del self._list[idx] # def copy(self): # """ # Defining the copy of the `_list` of elements # """ # return self._list.copy() # def clear(self) -> None: # """Clear the storage.""" # self._list = list() # def sort(self, order: list) -> None: # """ # Sorting the list according to order - iterable of indices. # Parameters # ---------- # order : list # List of indices for the sorting. # """ # tmp = self._list.copy() # self.clear() # assert len(tmp) == len(order) # for ii in range(len(order)): # self.append(tmp[order[ii]]) # def lin_combine(self, vec: np.ndarray) -> Function: # r""" # Linearly combine functions (`a_i = vec[i]`) in the list :math:`\{\phi_i\}_{i=0}^N`: # .. math:: # \sum_{i=0}^N a_i\cdot \phi_i # given `N = len(vec)` and :math:`\mathbf{a}\in\mathbb{R}^N`. # Parameters # ---------- # vec : np.ndarray # Iterable containing the coefficients of the linear combination. # Returns # ------- # combination : Function # Function object storing the result of the linear combination # """ # combination = np.zeros((self.dofs,)) # for ii in range(len(vec)): # combination += vec[ii] * self._list[ii] # return combination # def fun_list_2_fun_matrix(fun_list: FunctionsList): # """ # This function can be used to convert a `FunctionsList` object to a `FunctionsMatrix` one. # Parameters # ---------- # fun_list : FunctionsList # Object with the list of functions to convert. # Returns # ------- # fun_matrix: FunctionsMatrix # Object containing the functions list converted to a list of arrays, not relying on *dolfinx*. # """ # fun_matrix = FunctionsMatrix(fun_list.fun_shape[0]) # for ii in range(len(fun_list)): # fun_matrix.append(fun_list(ii)) # return fun_matrix # def fun_matrix_2_fun_list(fun_matrix: FunctionsMatrix, V: FunctionSpace): # """ # This function can be used to convert a `FunctionsMatrix` object to a `FunctionsList` one. # Parameters # ---------- # fun_matrix: FunctionsMatrix # Object containing the functions as a list of arrays to convert. # V : FunctionSpace # Functional space of the functions (the dofs should be compliant). # Returns # ------- # fun_list : FunctionsList # Object with the list of functions converted. # """ # fun_list = FunctionsList(V) # assert fun_list.fun_shape == fun_matrix.dofs, "The fun_list dofs are "+str(fun_list.fun_shape)+", whereas the fun_matrix are "+str(fun_matrix.dofs) # for ii in range(len(fun_matrix)): # fun_list.append(fun_matrix(ii)) # return fun_list