diff --git a/examples/2_double_slit/double_slit_engine.py b/examples/2_double_slit/double_slit_engine.py index fedb8c7..5c78130 100644 --- a/examples/2_double_slit/double_slit_engine.py +++ b/examples/2_double_slit/double_slit_engine.py @@ -1,55 +1,57 @@ import numpy as np import ufl import fenics as fen import mshr -from rrompy.utilities.base import affine_construct, nonaffine_construct +from rrompy.utilities.base.decorators import (affine_construct, + nonaffine_construct) from rrompy.hfengines.linear_problem import ScatteringProblemEngine -from rrompy.utilities.numerical import hashDerivativeToIdx as hashD +from rrompy.utilities.numerical.hash_derivative import ( + hashDerivativeToIdx as hashD) from rrompy.solver.fenics import fenZERO, fenics2Vector class DoubleSlitEngine(ScatteringProblemEngine): def __init__(self, k0:float, n:int): super().__init__(mu0 = [k0]) self._affinePoly = False delta, eps = .1, .01 mesh = mshr.generate_mesh( mshr.Circle(fen.Point(0., 0.), 5.) - mshr.Rectangle(fen.Point(-5., -delta), fen.Point(-.75, delta)) - mshr.Rectangle(fen.Point(-.5, -delta), fen.Point(.5, delta)) - mshr.Rectangle(fen.Point(.75, -delta), fen.Point(5., delta)), n) self.V = fen.FunctionSpace(mesh, "P", 1) self.DirichletBoundary = lambda x, on_boundary: (on_boundary and np.abs(x[1]) <= delta and np.abs(x[1]) > delta - eps) self.NeumannBoundary = lambda x, on_boundary: (on_boundary and np.abs(x[1]) <= delta - eps) self.RobinBoundary = "REST" def getDirichletValues(self, mu = []): mu = self.checkParameter(mu) x, y = fen.SpatialCoordinate(self.V.mesh())[:] c, s = .5, - .5 * 3. ** .5 muR, muI = np.real(mu[0])[0], np.imag(mu[0])[0] mod = - muI * (c * x + s * y) angle = muR * (c * x + s * y) DR = fen.exp(mod) * fen.cos(angle) DI = fen.exp(mod) * fen.sin(angle) DR = ufl.conditional(ufl.ge(y, 0), DR, fenZERO) DI = ufl.conditional(ufl.ge(y, 0), DI, fenZERO) return DR, DI @affine_construct def A(self, mu = [], der = 0): return ScatteringProblemEngine.A(self, mu, der) @nonaffine_construct def b(self, mu = [], der = 0): derI = hashD(der) if hasattr(der, "__len__") else der if derI < 0: return self.baselineb() if derI > 0: raise Exception("Derivatives not implemented.") fen0 = fen.inner(fenZERO, self.v) * fen.dx DR, DI = self.getDirichletValues(mu) DBCR = fen.DirichletBC(self.V, DR, self.DirichletBoundary) DBCI = fen.DirichletBC(self.V, DI, self.DirichletBoundary) return (fenics2Vector(fen0, {}, DBCR, 1) + 1.j * fenics2Vector(fen0, {}, DBCI, 1)) diff --git a/examples/3_sector_angle/sector_angle_engine.py b/examples/3_sector_angle/sector_angle_engine.py index 9356f52..eb738b5 100644 --- a/examples/3_sector_angle/sector_angle_engine.py +++ b/examples/3_sector_angle/sector_angle_engine.py @@ -1,43 +1,43 @@ import numpy as np import fenics as fen import mshr -from rrompy.utilities.base import nonaffine_construct +from rrompy.utilities.base.decorators import nonaffine_construct from rrompy.hfengines.linear_problem import HelmholtzProblemEngine class SectorAngleEngine(HelmholtzProblemEngine): def __init__(self, k0:float, t0:float, n:int): super().__init__(mu0 = [k0, t0]) self._affinePoly = False self.npar = 2 self.rescalingExp = [2., 1.] mesh = mshr.generate_mesh( mshr.Circle(fen.Point(0., 0.), 1.) - mshr.Rectangle(fen.Point(-1., -1.), fen.Point(0., 1.)) - mshr.Rectangle(fen.Point(-1., -1.), fen.Point(1., 0.)), n) self.V = fen.FunctionSpace(mesh, "P", 1) x, y = fen.SpatialCoordinate(mesh)[:] self.forcingTerm = [fen.exp(x + y) * (1. - x ** 2. - y ** 2.), fen.exp(x - y) * (1. - x ** 2. - y ** 2.)] self._tBoundary = np.nan def setBoundary(self, t:float): while hasattr(t, "__len__"): t = t[0] if not np.isclose(t, self._tBoundary): eps = 1e-2 self._tBoundary = t self.DirichletBoundary = lambda x, on_boundary: ( on_boundary and x[0] >= eps and x[1] <= eps + np.sin(t * np.pi / 2.)) self.NeumannBoundary = "REST" @nonaffine_construct def A(self, mu = [], der = 0): mu = self.checkParameter(mu) self.setBoundary(mu(1)) return HelmholtzProblemEngine.A(self, mu, der) @nonaffine_construct def b(self, mu = [], der = 0): mu = self.checkParameter(mu) self.setBoundary(mu(1)) return HelmholtzProblemEngine.b(self, mu, der) diff --git a/rrompy/hfengines/base/fenics_engine_base.py b/rrompy/hfengines/base/fenics_engine_base.py index d9fecd6..008d816 100644 --- a/rrompy/hfengines/base/fenics_engine_base.py +++ b/rrompy/hfengines/base/fenics_engine_base.py @@ -1,400 +1,400 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from os import path, mkdir import fenics as fen import numpy as np from matplotlib import pyplot as plt from .numpy_engine_base import NumpyEngineBase from rrompy.utilities.base.types import (Np1D, strLst, FenFunc, Tuple, List, FigHandle) -from rrompy.utilities.base import (purgeList, getNewFilename, - verbosityManager as vbMng) +from rrompy.utilities.base.data_structures import purgeList, getNewFilename +from rrompy.utilities.base import verbosityManager as vbMng from rrompy.solver.fenics import L2NormMatrix, fenplot, interp_project from .boundary_conditions import BoundaryConditions from rrompy.utilities.exception_manager import RROMPyException __all__ = ['FenicsEngineBase'] class FenicsEngineBase(NumpyEngineBase): """Generic solver for parametric fenics problems.""" def __init__(self, degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(verbosity = verbosity, timestamp = timestamp) self.BCManager = BoundaryConditions("Dirichlet") self.V = fen.FunctionSpace(fen.UnitSquareMesh(10, 10), "P", 1) self.degree_threshold = degree_threshold @property def V(self): """Value of V.""" return self._V @V.setter def V(self, V): if not type(V).__name__ == 'FunctionSpace': raise RROMPyException("V type not recognized.") self.dsToBeSet = True self._V = V self.u = fen.TrialFunction(V) self.v = fen.TestFunction(V) @property def spacedim(self): if hasattr(self, "_V"): return self.V.dim() return super().spacedim def autoSetDS(self): """Set FEniCS boundary measure based on boundary function handles.""" if self.dsToBeSet: vbMng(self, "INIT", "Initializing boundary measures.", 20) mesh = self.V.mesh() NB = self.NeumannBoundary RB = self.RobinBoundary boundary_markers = fen.MeshFunction("size_t", mesh, mesh.topology().dim() - 1) NB.mark(boundary_markers, 0) RB.mark(boundary_markers, 1) self.ds = fen.Measure("ds", domain = mesh, subdomain_data = boundary_markers) self.dsToBeSet = False vbMng(self, "DEL", "Done assembling boundary measures.", 20) def buildEnergyNormForm(self): """ Build sparse matrix (in CSR format) representative of scalar product. """ vbMng(self, "INIT", "Assembling energy matrix.", 20) self.energyNormMatrix = L2NormMatrix(self.V) vbMng(self, "DEL", "Done assembling energy matrix.", 20) def buildEnergyNormDualForm(self): """ Build sparse matrix (in CSR format) representative of dual scalar product without duality. """ if not hasattr(self, "energyNormMatrix"): self.buildEnergyNormForm() self.energyNormDualMatrix = self.energyNormMatrix def liftDirichletData(self) -> Np1D: """Lift Dirichlet datum.""" if not hasattr(self, "_liftedDirichletDatum"): liftRe = interp_project(self.DirichletDatum[0], self.V) liftIm = interp_project(self.DirichletDatum[1], self.V) self._liftedDirichletDatum = (np.array(liftRe.vector()) + 1.j * np.array(liftIm.vector())) return self._liftedDirichletDatum def reduceQuadratureDegree(self, fun:FenFunc, name:str): """Check whether to reduce compiler parameters to degree threshold.""" if not np.isinf(self.degree_threshold): from ufl.algorithms.estimate_degrees import ( estimate_total_polynomial_degree as ETPD) try: deg = ETPD(fun) except: return False if deg > self.degree_threshold: vbMng(self, "MAIN", ("Reducing quadrature degree from {} to {} for " "{}.").format(deg, self.degree_threshold, name), 15) return True return False def iterReduceQuadratureDegree(self, funsNames:List[Tuple[FenFunc, str]]): """ Iterate reduceQuadratureDegree over list and define reduce compiler parameters. """ if funsNames is not None: for fun, name in funsNames: if self.reduceQuadratureDegree(fun, name): return {"quadrature_degree" : self.degree_threshold} return {} def plot(self, u:Np1D, warping : List[callable] = None, is_state : bool = False, name : str = "u", save : str = None, what : strLst = 'all', forceNewFile : bool = True, saveFormat : str = "eps", saveDPI : int = 100, show : bool = True, fenplotArgs : dict = {}, **figspecs) -> Tuple[FigHandle, str]: """ Do some nice plots of the complex-valued function with given dofs. Args: u: numpy complex array with function dofs. warping(optional): Domain warping functions. is_state(optional): whether given u is value before multiplication by c. Defaults to False. name(optional): Name to be shown as title of the plots. Defaults to 'u'. save(optional): Where to save plot(s). Defaults to None, i.e. no saving. what(optional): Which plots to do. If list, can contain 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. forceNewFile(optional): Whether to create new output file. saveFormat(optional): Format for saved plot(s). Defaults to "eps". saveDPI(optional): DPI for saved plot(s). Defaults to 100. show(optional): Whether to show figure. Defaults to True. fenplotArgs(optional): Optional arguments for fenplot. figspecs(optional key args): Optional arguments for matplotlib figure creation. Returns: Output filename and figure handle. """ if not is_state and not self.isCEye: return super().plot(u, warping, False, name, save, what, forceNewFile, saveFormat, saveDPI, show, fenplotArgs, **figspecs) if isinstance(what, (str,)): if what.upper() == 'ALL': what = ['ABS', 'PHASE', 'REAL', 'IMAG'] else: what = [what] what = purgeList(what, ['ABS', 'PHASE', 'REAL', 'IMAG'], listname = self.name() + ".what", baselevel = 1) if len(what) == 0: return if 'figsize' not in figspecs.keys(): figspecs['figsize'] = plt.figaspect(1. / len(what)) subplotidx = 0 fig = plt.figure(**figspecs) plt.jet() if 'ABS' in what: uAb = fen.Function(self.V) uAb.vector().set_local(np.abs(u)) subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) p = fenplot(uAb, warping = warping, title = "|{0}|".format(name), **fenplotArgs) if self.V.mesh().geometric_dimension() > 1: fig.colorbar(p, ax = ax) if 'PHASE' in what: uPh = fen.Function(self.V) uPh.vector().set_local(np.angle(u)) subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) p = fenplot(uPh, warping = warping, title = "phase({0})".format(name), **fenplotArgs) if self.V.mesh().geometric_dimension() > 1: fig.colorbar(p, ax = ax) if 'REAL' in what: uRe = fen.Function(self.V) uRe.vector().set_local(np.real(u)) subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) p = fenplot(uRe, warping = warping, title = "Re({0})".format(name), **fenplotArgs) if self.V.mesh().geometric_dimension() > 1: fig.colorbar(p, ax = ax) if 'IMAG' in what: uIm = fen.Function(self.V) uIm.vector().set_local(np.imag(u)) subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) p = fenplot(uIm, warping = warping, title = "Im({0})".format(name), **fenplotArgs) if self.V.mesh().geometric_dimension() > 1: fig.colorbar(p, ax = ax) plt.tight_layout() if save is not None: save = save.strip() if forceNewFile: fileOut = getNewFilename("{}_fig_".format(save), saveFormat) else: fileOut = "{}_fig.{}".format(save, saveFormat) fig.savefig(fileOut, format = saveFormat, dpi = saveDPI) else: fileOut = None if show: plt.show() if fileOut is None: return fig return fig, fileOut def plotmesh(self, warping : List[callable] = None, name : str = "Mesh", save : str = None, forceNewFile : bool = True, saveFormat : str = "eps", saveDPI : int = 100, show : bool = True, fenplotArgs : dict = {}, **figspecs) -> Tuple[FigHandle, str]: """ Do a nice plot of the mesh. Args: u: numpy complex array with function dofs. warping(optional): Domain warping functions. name(optional): Name to be shown as title of the plots. Defaults to 'u'. save(optional): Where to save plot(s). Defaults to None, i.e. no saving. forceNewFile(optional): Whether to create new output file. saveFormat(optional): Format for saved plot(s). Defaults to "eps". saveDPI(optional): DPI for saved plot(s). Defaults to 100. show(optional): Whether to show figure. Defaults to True. fenplotArgs(optional): Optional arguments for fenplot. figspecs(optional key args): Optional arguments for matplotlib figure creation. Returns: Output filename and figure handle. """ fig = plt.figure(**figspecs) fenplot(self.V.mesh(), warping = warping, **fenplotArgs) plt.tight_layout() if save is not None: save = save.strip() if forceNewFile: fileOut = getNewFilename("{}_msh_".format(save), saveFormat) else: fileOut = "{}_msh.{}".format(save, saveFormat) fig.savefig(fileOut, format = saveFormat, dpi = saveDPI) else: fileOut = None if show: plt.show() if fileOut is None: return fig return fig, fileOut def outParaview(self, u:Np1D, warping : List[callable] = None, is_state : bool = False, name : str = "u", filename : str = "out", time : float = 0., what : strLst = 'all', forceNewFile : bool = True, folder : bool = False, filePW = None) -> str: """ Output complex-valued function with given dofs to ParaView file. Args: u: numpy complex array with function dofs. warping(optional): Domain warping functions. is_state(optional): whether given u is value before multiplication by c. Defaults to False. name(optional): Base name to be used for data output. filename(optional): Name of output file. time(optional): Timestamp. what(optional): Which plots to do. If list, can contain 'MESH', 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. forceNewFile(optional): Whether to create new output file. folder(optional): Whether to create an additional folder layer. filePW(optional): Fenics File entity (for time series). Returns: Output filename. """ if not is_state and not self.isCEye: raise RROMPyException(("Cannot output to Paraview non-state " "object.")) if isinstance(what, (str,)): if what.upper() == 'ALL': what = ['MESH', 'ABS', 'PHASE', 'REAL', 'IMAG'] else: what = [what] what = purgeList(what, ['MESH', 'ABS', 'PHASE', 'REAL', 'IMAG'], listname = self.name() + ".what", baselevel = 1) if len(what) == 0: return if filePW is None: if folder: if not path.exists(filename + "/"): mkdir(filename) idxpath = filename.rfind("/") filename += "/" + filename[idxpath + 1 :] if forceNewFile: filePW = fen.File(getNewFilename(filename, "pvd")) else: filePW = fen.File("{}.pvd".format(filename)) if warping is not None: fen.ALE.move(self.V.mesh(), interp_project(warping[0], self.V.mesh())) if what == ['MESH']: filePW << (self.V.mesh(), time) if 'ABS' in what: uAb = fen.Function(self.V, name = "{}_ABS".format(name)) uAb.vector().set_local(np.abs(u)) filePW << (uAb, time) if 'PHASE' in what: uPh = fen.Function(self.V, name = "{}_PHASE".format(name)) uPh.vector().set_local(np.angle(u)) filePW << (uPh, time) if 'REAL' in what: uRe = fen.Function(self.V, name = "{}_REAL".format(name)) uRe.vector().set_local(np.real(u)) filePW << (uRe, time) if 'IMAG' in what: uIm = fen.Function(self.V, name = "{}_IMAG".format(name)) uIm.vector().set_local(np.imag(u)) filePW << (uIm, time) if warping is not None: fen.ALE.move(self.V.mesh(), interp_project(warping[1], self.V.mesh())) return filePW def outParaviewTimeDomain(self, u:Np1D, omega:float, warping : List[callable] = None, is_state : bool = False, timeFinal : float = None, periodResolution : int = 20, name : str = "u", filename : str = "out", forceNewFile : bool = True, folder : bool = False) -> str: """ Output complex-valued function with given dofs to ParaView file, converted to time domain. Args: u: numpy complex array with function dofs. omega: frequency. warping(optional): Domain warping functions. is_state(optional): whether given u is value before multiplication by c. Defaults to False. timeFinal(optional): final time of simulation. periodResolution(optional): number of time steps per period. name(optional): Base name to be used for data output. filename(optional): Name of output file. forceNewFile(optional): Whether to create new output file. folder(optional): Whether to create an additional folder layer. Returns: Output filename. """ if not is_state and not self.isCEye: raise RROMPyException(("Cannot output to Paraview non-state " "object.")) if folder: if not path.exists(filename + "/"): mkdir(filename) idxpath = filename.rfind("/") filename += "/" + filename[idxpath + 1 :] if forceNewFile: filePW = fen.File(getNewFilename(filename, "pvd")) else: filePW = fen.File("{}.pvd".format(filename)) omega = np.abs(omega) t = 0. dt = 2. * np.pi / omega / periodResolution if timeFinal is None: timeFinal = 2. * np.pi / omega - dt if warping is not None: fen.ALE.move(self.V.mesh(), interp_project(warping[0], self.V.mesh())) for j in range(int(np.ceil(timeFinal / dt)) + 1): ut = fen.Function(self.V, name = name) ut.vector().set_local(np.real(u) * np.cos(omega * t) + np.imag(u) * np.sin(omega * t)) filePW << (ut, t) t += dt if warping is not None: fen.ALE.move(self.V.mesh(), interp_project(warping[1], self.V.mesh())) return filePW diff --git a/rrompy/hfengines/base/hfengine_base.py b/rrompy/hfengines/base/hfengine_base.py index 7a15a2c..b7ccf2d 100644 --- a/rrompy/hfengines/base/hfengine_base.py +++ b/rrompy/hfengines/base/hfengine_base.py @@ -1,279 +1,279 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from abc import abstractmethod import numpy as np import scipy.sparse as scsp from numbers import Number from copy import copy as softcopy -from rrompy.utilities.base import nonaffine_construct +from rrompy.utilities.base.decorators import nonaffine_construct from rrompy.utilities.base.types import (Np1D, Np2D, List, DictAny, paramVal, paramList, sampList) -from rrompy.utilities.numerical import (solve as tsolve, dot, customPInv, - rayleighQuotientIteration) +from rrompy.utilities.numerical import solve as tsolve, dot, customPInv +from rrompy.utilities.numerical.rayleigh_quotient_iteration import ( + rayleighQuotientIteration) from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.parameter import checkParameter, checkParameterList -from rrompy.solver import setupSolver +from rrompy.solver.linear_solver import setupSolver __all__ = ['HFEngineBase'] class HFEngineBase: """Generic solver for parametric problems.""" def __init__(self, verbosity : int = 10, timestamp : bool = True): self.verbosity = verbosity self.timestamp = timestamp self.setSolver("SPSOLVE", {"use_umfpack" : False}) self.npar = 0 self._C = None self.outputNormMatrix = 1. def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) def __dir_base__(self): return [x for x in self.__dir__() if x[:2] != "__"] def __deepcopy__(self, memo): return softcopy(self) @property def npar(self): """Value of npar.""" return self._npar @npar.setter def npar(self, npar): nparOld = self._npar if hasattr(self, "_npar") else -1 if npar != nparOld: self.rescalingExp = [1.] * npar self._npar = npar @property - @abstractmethod def spacedim(self): return 1 def checkParameter(self, mu:paramVal): return checkParameter(mu, self.npar) def checkParameterList(self, mu:paramList): return checkParameterList(mu, self.npar) def buildEnergyNormForm(self): """ Build sparse matrix (in CSR format) representative of scalar product. """ self.energyNormMatrix = 1. def buildEnergyNormDualForm(self): """ Build sparse matrix (in CSR format) representative of dual scalar product without duality. """ self.energyNormDualMatrix = 1. def innerProduct(self, u:Np2D, v:Np2D, onlyDiag : bool = False, dual : bool = False, is_state : bool = True) -> Np2D: """Scalar product.""" if is_state or self.isCEye: if dual: if not hasattr(self, "energyNormDualMatrix"): self.buildEnergyNormDualForm() energyMat = self.energyNormDualMatrix else: if not hasattr(self, "energyNormMatrix"): self.buildEnergyNormForm() energyMat = self.energyNormMatrix else: energyMat = self.outputNormMatrix if not isinstance(u, (np.ndarray,)): u = u.data if not isinstance(v, (np.ndarray,)): v = v.data if onlyDiag: return np.sum(dot(energyMat, u) * v.conj(), axis = 0) return dot(dot(energyMat, u).T, v.conj()).T def norm(self, u:Np2D, dual : bool = False, is_state : bool = True) -> Np1D: return np.abs(self.innerProduct(u, u, onlyDiag = True, dual = dual, is_state = is_state)) ** .5 def baselineA(self): """Return 0 of shape consistent with operator of linear system.""" d = self.As[0].shape[0] return scsp.csr_matrix((np.zeros(0), np.zeros(0), np.zeros(d + 1)), shape = (d, d), dtype = np.complex) def baselineb(self): """Return 0 of shape consistent with RHS of linear system.""" return np.zeros(self.spacedim, dtype = np.complex) @nonaffine_construct @abstractmethod def A(self, mu : paramVal = [], der : List[int] = 0) -> Np2D: """ Assemble terms of operator of linear system and return it (or its derivative) at a given parameter. """ return @nonaffine_construct @abstractmethod def b(self, mu : paramVal = [], der : List[int] = 0) -> Np1D: """ Assemble terms of RHS of linear system and return it (or its derivative) at a given parameter. """ return @property def C(self): """Value of C.""" if self._C is None: self._C = 1. return self._C @property def isCEye(self): return isinstance(self.C, Number) def applyC(self, u:sampList): """Apply LHS of linear system.""" return dot(self.C, u) def applyCpInv(self, u:sampList): """Apply pseudoinverse of LHS of linear system.""" return dot(customPInv(self.C), u) _isStateShiftZero = True def stateShift(self, mu : paramVal = []) -> Np1D: return np.zeros((self.spacedim, len(mu))) _isOutputShiftZero = True def outputShift(self, mu : paramVal = []) -> Np1D: return self.applyC(self.stateShift(mu)) def setSolver(self, solverType:str, solverArgs : DictAny = {}): """Choose solver type and parameters.""" self._solver, self._solverArgs = setupSolver(solverType, solverArgs) def solve(self, mu : paramList = [], RHS : sampList = None, return_state : bool = False, verbose : bool = False) -> sampList: """ Find solution of linear system. Args: mu: parameter value. RHS: RHS of linear system. If None, defaults to that of parametric system. Defaults to None. return_state: whether to return state before multiplication by c. Defaults to False. verbose: whether to notify for each solution computed. Defaults to False. """ from rrompy.sampling import sampleList, emptySampleList if mu == []: mu = self.mu0 mu = self.checkParameterList(mu)[0] if self.npar == 0: mu.reset((1, self.npar), mu.dtype) if len(mu) == 0: return emptySampleList() if RHS is None: RHS = [self.b(m) for m in mu] RHS = sampleList(RHS) mult = 0 if len(RHS) == 1 else 1 RROMPyAssert(mult * (len(mu) - 1) + 1, len(RHS), "Sample size") for j in range(len(mu)): u = tsolve(self.A(mu[j]), RHS[mult * j], self._solver, self._solverArgs) if j == 0: sol = emptySampleList() sol.reset((len(u), len(mu)), dtype = u.dtype) sol[j] = u if verbose and len(mu) > 1: print("." * (j % 5 != 4) + "*" * (j % 5 == 4), end = "") if return_state: sol = sampleList(sol - self.stateShift(mu)) else: sol = sampleList(self.applyC(sol.data) - self.outputShift(mu)) if verbose and len(mu) > 1: print() return sol def residual(self, mu : paramList = [], u : sampList = None, post_c : bool = True) -> sampList: """ Find residual of linear system for given approximate solution. Args: mu: parameter value. u: numpy complex array with function dofs. If None, set to 0. post_c: whether to post-process using c. Defaults to True. """ from rrompy.sampling import sampleList, emptySampleList if mu == []: mu = self.mu0 mu = self.checkParameterList(mu)[0] if self.npar == 0: mu.reset((1, self.npar), mu.dtype) if len(mu) == 0: return emptySampleList() v = sampleList(self.stateShift(mu)) if u is not None: v = v + sampleList(u) for j in range(len(mu)): r = self.b(mu[j]) - dot(self.A(mu[j]), v[j]) if post_c: if j == 0: res = np.empty((len(r), len(mu)), dtype = r.dtype) res[:, j] = r else: if j == 0: res = emptySampleList() res.reset((len(r), len(mu)), dtype = r.dtype) res[j] = r if post_c: res = sampleList(self.applyC(res)) return res def stabilityFactor(self, mu : paramList = [], u : sampList = None, nIterP : int = 10, nIterR : int = 10) -> sampList: """ Find stability factor of matrix of linear system using iterative inverse power iteration- and Rayleigh quotient-based procedure. Args: mu: parameter values. u: numpy complex arrays with function dofs. nIterP: number of iterations of power method. nIterR: number of iterations of Rayleigh quotient method. """ from rrompy.sampling import sampleList if mu == []: mu = self.mu0 mu = self.checkParameterList(mu)[0] if self.npar == 0: mu.reset((1, self.npar), mu.dtype) u = sampleList(u) solShift = self.stateShift(mu) if len(u) == len(mu): u = u + solShift else: u = sampleList(solShift) + np.tile(u.data, (1, len(mu))) stabFact = np.empty(len(mu), dtype = float) if not hasattr(self, "energyNormMatrix"): self.buildEnergyNormForm() for j in range(len(mu)): stabFact[j] = rayleighQuotientIteration(self.A(mu[j]), u[j], self.energyNormMatrix, 0., nIterP, nIterR, self._solver, self._solverArgs) return stabFact diff --git a/rrompy/hfengines/base/linear_affine_engine.py b/rrompy/hfengines/base/linear_affine_engine.py index dbe299c..27b9895 100644 --- a/rrompy/hfengines/base/linear_affine_engine.py +++ b/rrompy/hfengines/base/linear_affine_engine.py @@ -1,197 +1,198 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from abc import abstractmethod import numpy as np import scipy.sparse as scsp from copy import deepcopy as copy -from rrompy.utilities.base import affine_construct +from rrompy.utilities.base.decorators import affine_construct from rrompy.utilities.base.types import (Np1D, Np2D, List, ListAny, TupleAny, paramVal) from rrompy.utilities.expression import (expressionEvaluator, createMonomial, createMonomialList) -from rrompy.utilities.numerical import hashDerivativeToIdx as hashD +from rrompy.utilities.numerical.hash_derivative import ( + hashDerivativeToIdx as hashD) from rrompy.utilities.exception_manager import RROMPyException __all__ = ['LinearAffineEngine', 'checkIfAffine'] class LinearAffineEngine: """Generic solver for affine parametric problems.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._affinePoly = True self.nAs, self.nbs = 1, 1 @property def affinePoly(self): return self._affinePoly @property def nAs(self): """Value of nAs.""" return self._nAs @nAs.setter def nAs(self, nAs): nAsOld = self._nAs if hasattr(self, "_nAs") else -1 if nAs != nAsOld: self._nAs = nAs self.resetAs() @property def nbs(self): """Value of nbs.""" return self._nbs @nbs.setter def nbs(self, nbs): nbsOld = self._nbs if hasattr(self, "_nbs") else -1 if nbs != nbsOld: self._nbs = nbs self.resetbs() @property def spacedim(self): if (hasattr(self, "bs") and hasattr(self.bs, "__len__") and self.bs[0] is not None): return len(self.bs[0]) return super().spacedim def getMonomialSingleWeight(self, deg:List[int]): return createMonomial(deg, True) def getMonomialWeights(self, n:int): return createMonomialList(n, self.npar, True) def setAs(self, As:List[Np2D]): """Assign terms of operator of linear system.""" if len(As) != self.nAs: raise RROMPyException(("Expected number {} of terms of As not " "matching given list length {}.").format(self.nAs, len(As))) self.As = [copy(A) for A in As] def setthAs(self, thAs:List[List[TupleAny]]): """Assign terms of operator of linear system.""" if len(thAs) != self.nAs: raise RROMPyException(("Expected number {} of terms of thAs not " "matching given list length {}.").format(self.nAs, len(thAs))) self.thAs = copy(thAs) def setbs(self, bs:List[Np1D]): """Assign terms of RHS of linear system.""" if len(bs) != self.nbs: raise RROMPyException(("Expected number {} of terms of bs not " "matching given list length {}.").format(self.nbs, len(bs))) self.bs = [copy(b) for b in bs] def setthbs(self, thbs:List[List[TupleAny]]): """Assign terms of RHS of linear system.""" if len(thbs) != self.nbs: raise RROMPyException(("Expected number {} of terms of thbs not " "matching given list length {}.").format(self.nbs, len(thbs))) self.thbs = copy(thbs) def resetAs(self): """Reset (derivatives of) operator of linear system.""" if hasattr(self, "_nAs"): self.setAs([None] * self.nAs) self.setthAs([None] * self.nAs) def resetbs(self): """Reset (derivatives of) RHS of linear system.""" if hasattr(self, "_nbs"): self.setbs([None] * self.nbs) self.setthbs([None] * self.nbs) def _assembleObject(self, mu:paramVal, objs:ListAny, th:ListAny, derI:int) -> Np2D: """Assemble (derivative of) object from list of derivatives.""" mu = self.checkParameter(mu) rExp = self.rescalingExp muE = mu ** rExp obj = None for j in range(len(objs)): if len(th[j]) <= derI and th[j][-1] is not None: raise RROMPyException(("Cannot assemble operator. Non enough " "derivatives of theta provided.")) if len(th[j]) > derI and th[j][derI] is not None: expr = expressionEvaluator(th[j][derI], muE) if hasattr(expr, "__len__"): if len(expr) > 1: raise RROMPyException(("Size mismatch in value of " "theta function. Only scalars " "allowed.")) expr = expr[0] if obj is None: obj = expr * objs[j] else: obj = obj + expr * objs[j] return obj @abstractmethod def buildA(self): """Build terms of operator of linear system.""" if self.thAs[0] is None: self.thAs = self.getMonomialWeights(self.nAs) if self.As[0] is None: self.As[0] = scsp.eye(self.spacedim, dtype = np.complex, format = "csr") for j in range(1, self.nAs): if self.As[j] is None: self.As[j] = self.baselineA() @affine_construct def A(self, mu : paramVal = [], der : List[int] = 0) -> Np2D: """ Assemble terms of operator of linear system and return it (or its derivative) at a given parameter. """ derI = hashD(der) if hasattr(der, "__len__") else der if derI < 0 or derI > self.nAs - 1: return self.baselineA() self.buildA() assembledA = self._assembleObject(mu, self.As, self.thAs, derI) if assembledA is None: return self.baselineA() return assembledA @abstractmethod def buildb(self): """Build terms of RHS of linear system.""" if self.thbs[0] is None: self.thbs = self.getMonomialWeights(self.nbs) for j in range(self.nbs): if self.bs[j] is None: self.bs[j] = self.baselineb() @affine_construct def b(self, mu : paramVal = [], der : List[int] = 0) -> Np1D: """ Assemble terms of RHS of linear system and return it (or its derivative) at a given parameter. """ derI = hashD(der) if hasattr(der, "__len__") else der if derI < 0 or derI > self.nbs - 1: return self.baselineb() self.buildb() assembledb = self._assembleObject(mu, self.bs, self.thbs, derI) if assembledb is None: return self.baselineb() return assembledb def checkIfAffine(engine, msg : str = "apply method", noA : bool = False): msg = ("Cannot {} because of non-affine parametric dependence{}. Consider " "using DEIM to define a new engine.").format(msg, " of RHS" * noA) if (not (hasattr(engine.b, "is_affine") and engine.b.is_affine) or not (noA or (hasattr(engine.A, "is_affine") and engine.A.is_affine))): raise RROMPyException(msg) diff --git a/rrompy/hfengines/base/marginal_proxy_engine.py b/rrompy/hfengines/base/marginal_proxy_engine.py index 01fbf6f..bc4c1cc 100644 --- a/rrompy/hfengines/base/marginal_proxy_engine.py +++ b/rrompy/hfengines/base/marginal_proxy_engine.py @@ -1,171 +1,172 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import inspect import numpy as np from copy import copy as softcopy from rrompy.utilities.base.types import Np1D, paramVal, paramList, HFEng -from rrompy.utilities.base import (freepar as fp, affine_construct, - nonaffine_construct) +from rrompy.utilities.base import freepar as fp +from rrompy.utilities.base.decorators import (affine_construct, + nonaffine_construct) from rrompy.utilities.exception_manager import RROMPyException from rrompy.parameter import checkParameter, checkParameterList __all__ = ['MarginalProxyEngine'] def MarginalProxyEngine(HFEngine:HFEng, marginalized:Np1D): Aaff = hasattr(HFEngine.A, "is_affine") and HFEngine.A.is_affine baff = hasattr(HFEngine.b, "is_affine") and HFEngine.b.is_affine if Aaff: if baff: return MarginalProxyEngineAffineAb(HFEngine, marginalized) return MarginalProxyEngineAffineA(HFEngine, marginalized) if baff: return MarginalProxyEngineAffineb(HFEngine, marginalized) return MarginalProxyEngineNonAffine(HFEngine, marginalized) class MarginalProxyEngineNonAffine: """ Marginalized should prescribe fixed value for the marginalized parameters and leave freepar/None elsewhere. """ _allowedMuDependencies = ["A", "b", "checkParameter", "checkParameterList", "_assembleObject", "solve", "residual", "stabilityFactor", "stateShift", "outputShift"] def __init__(self, HFEngine:HFEng, marginalized:Np1D): self.baseHF = HFEngine self.marg = marginalized for name in HFEngine.__dir_base__(): att = getattr(HFEngine, name) if inspect.ismethod(att): attargs = inspect.getfullargspec(att).args if "mu" not in attargs: setattr(self.__class__, name, getattr(HFEngine, name)) else: if name not in self._allowedMuDependencies: raise RROMPyException(("Function {} depends on mu " "and was not accounted for. " "Must override.").format(name)) @property def affinePoly(self): return self.nparFixed == 0 and self.baseHF.affinePoly @property def freeLocations(self): return [x for x in range(self.baseHF.npar) if self.marg[x] == fp] @property def fixedLocations(self): return [x for x in range(self.baseHF.npar) if self.marg[x] != fp] @property def _freeLocationsInsert(self): return np.cumsum([m == fp for m in self.marg])[self.fixedLocations] @property def muFixed(self): muF = checkParameter([m for m in self.marg if m != fp]) if self.baseHF.npar - self.nparFree > 0: muF = muF[0] return muF @property def nparFree(self): """Value of nparFree.""" return len(self.freeLocations) @property def nparFixed(self): """Value of nparFixed.""" return len(self.fixedLocations) def name(self) -> str: return "{}-proxy for {}".format(self.freeLocations, self.baseHF.name()) def __str__(self) -> str: return self.name() def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) def __dir_base__(self): return [x for x in self.__dir__() if x[:2] != "__"] def __deepcopy__(self, memo): return softcopy(self) def completeMu(self, mu:paramVal) -> paramVal: mu = checkParameter(mu, self.nparFree) return np.insert(mu.data, self._freeLocationsInsert, self.muFixed, axis = 1) def completeMuList(self, mu:paramList) -> paramList: mu = checkParameterList(mu, self.nparFree)[0] return np.insert(mu.data, self._freeLocationsInsert, self.muFixed, axis = 1) @nonaffine_construct def A(self, mu : paramVal = [], *args, **kwargs): return self.baseHF.A(self.completeMu(mu), *args, **kwargs) @nonaffine_construct def b(self, mu : paramVal = [], *args, **kwargs): return self.baseHF.b(self.completeMu(mu), *args, **kwargs) def checkParameter(self, mu : paramVal = [], *args, **kwargs): return self.baseHF.checkParameter(self.completeMu(mu), *args, **kwargs) def checkParameterList(self, mu : paramList = [], *args, **kwargs): return self.baseHF.checkParameterList(self.completeMuList(mu), *args, **kwargs) def _assembleObject(self, mu : paramVal = [], *args, **kwargs): return self.baseHF._assembleObject(self.completeMu(mu), *args, **kwargs) def stateShift(self, mu : paramVal = [], *args, **kwargs): return self.baseHF.stateShift(self.completeMuList(mu), *args, **kwargs) def outputShift(self, mu : paramVal = [], *args, **kwargs): return self.baseHF.outputShift(self.completeMuList(mu), *args, **kwargs) def solve(self, mu : paramList = [], *args, **kwargs): return self.baseHF.solve(self.completeMuList(mu), *args, **kwargs) def residual(self, mu : paramList = [], *args, **kwargs): return self.baseHF.residual(self.completeMuList(mu), *args, **kwargs) def stabilityFactor(self, mu : paramList = [], *args, **kwargs): return self.baseHF.stabilityFactor(self.completeMuList(mu), *args, **kwargs) class MarginalProxyEngineAffineA(MarginalProxyEngineNonAffine): @affine_construct def A(self, mu : paramVal = [], *args, **kwargs): return self.baseHF.A(self.completeMu(mu), *args, **kwargs) class MarginalProxyEngineAffineb(MarginalProxyEngineNonAffine): @affine_construct def b(self, mu : paramVal = [], *args, **kwargs): return self.baseHF.b(self.completeMu(mu), *args, **kwargs) class MarginalProxyEngineAffineAb(MarginalProxyEngineAffineA, MarginalProxyEngineAffineb): pass diff --git a/rrompy/hfengines/base/numpy_engine_base.py b/rrompy/hfengines/base/numpy_engine_base.py index 2254531..1d66743 100644 --- a/rrompy/hfengines/base/numpy_engine_base.py +++ b/rrompy/hfengines/base/numpy_engine_base.py @@ -1,111 +1,111 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from matplotlib import pyplot as plt from .hfengine_base import HFEngineBase from rrompy.utilities.base.types import Np1D, strLst, List, Tuple, FigHandle -from rrompy.utilities.base import purgeList, getNewFilename +from rrompy.utilities.base.data_structures import purgeList, getNewFilename __all__ = ['NumpyEngineBase'] class NumpyEngineBase(HFEngineBase): """Generic solver for parametric matricial problems.""" def plot(self, u:Np1D, warping : List[callable] = None, is_state : bool = False, name : str = "u", save : str = None, what : strLst = 'all', forceNewFile : bool = True, saveFormat : str = "eps", saveDPI : int = 100, show : bool = True, pyplotArgs : dict = {}, **figspecs) -> Tuple[FigHandle, str]: """ Do some nice plots of the complex-valued function with given dofs. Args: u: numpy complex array with function dofs. name(optional): Name to be shown as title of the plots. Defaults to 'u'. is_state(optional): whether given u is value before multiplication by c. Defaults to False. save(optional): Where to save plot(s). Defaults to None, i.e. no saving. what(optional): Which plots to do. If list, can contain 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. forceNewFile(optional): Whether to create new output file. saveFormat(optional): Format for saved plot(s). Defaults to "eps". saveDPI(optional): DPI for saved plot(s). Defaults to 100. show(optional): Whether to show figure. Defaults to True. pyplotArgs(optional): Optional arguments for pyplot. figspecs(optional key args): Optional arguments for matplotlib figure creation. Returns: Output filename and figure handle. """ if isinstance(what, (str,)): if what.upper() == 'ALL': what = ['ABS', 'PHASE', 'REAL', 'IMAG'] else: what = [what] what = purgeList(what, ['ABS', 'PHASE', 'REAL', 'IMAG'], listname = self.name() + ".what", baselevel = 1) if len(what) == 0: return if 'figsize' not in figspecs.keys(): figspecs['figsize'] = plt.figaspect(1. / len(what)) if is_state or self.isCEye: idxs = np.arange(self.spacedim) else: idxs = np.arange(self.C.shape[0]) if warping is not None: idxs = warping[0](np.arange(self.spacedim)) subplotidx = 0 fig = plt.figure(**figspecs) plt.jet() if 'ABS' in what: subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) ax.plot(idxs, np.abs(u).flatten(), **pyplotArgs) ax.set_title("|{0}|".format(name)) if 'PHASE' in what: subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) ax.plot(idxs, np.angle(u).flatten(), **pyplotArgs) ax.set_title("phase({0})".format(name)) if 'REAL' in what: subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) ax.plot(idxs, np.real(u).flatten(), **pyplotArgs) ax.set_title("Re({0})".format(name)) if 'IMAG' in what: subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) ax.plot(idxs, np.imag(u).flatten(), **pyplotArgs) ax.set_title("Im({0})".format(name)) plt.tight_layout() if save is not None: save = save.strip() if forceNewFile: fileOut = getNewFilename("{}_fig_".format(save), saveFormat) else: fileOut = "{}_fig.{}".format(save, saveFormat) fig.savefig(fileOut, format = saveFormat, dpi = saveDPI) else: fileOut = None if show: plt.show() if fileOut is None: return fig return fig, fileOut diff --git a/rrompy/hfengines/base/vector_fenics_engine_base.py b/rrompy/hfengines/base/vector_fenics_engine_base.py index e0fdb40..8406aa1 100644 --- a/rrompy/hfengines/base/vector_fenics_engine_base.py +++ b/rrompy/hfengines/base/vector_fenics_engine_base.py @@ -1,146 +1,146 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import fenics as fen import numpy as np from matplotlib import pyplot as plt from .fenics_engine_base import FenicsEngineBase from rrompy.utilities.base.types import Np1D, List, strLst, Tuple, FigHandle -from rrompy.utilities.base import purgeList, getNewFilename +from rrompy.utilities.base.data_structures import purgeList, getNewFilename from rrompy.solver.fenics import fenplot __all__ = ['VectorFenicsEngineBase'] class VectorFenicsEngineBase(FenicsEngineBase): """Generic solver for parametric vector fenics problems.""" def __init__(self, degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) self.V = fen.VectorFunctionSpace(fen.UnitSquareMesh(10, 10), "P", 1) def plot(self, u:Np1D, warping : List[callable] = None, is_state : bool = False, name : str = "u", save : str = None, what : strLst = 'all', forceNewFile : bool = True, saveFormat : str = "eps", saveDPI : int = 100, show : bool = True, fenplotArgs : dict = {}, **figspecs) -> Tuple[List[FigHandle], List[str]]: """ Do some nice plots of the complex-valued function with given dofs. Args: u: numpy complex array with function dofs. warping(optional): Domain warping functions. is_state(optional): whether given u is value before multiplication by c. Defaults to False. name(optional): Name to be shown as title of the plots. Defaults to 'u'. save(optional): Where to save plot(s). Defaults to None, i.e. no saving. what(optional): Which plots to do. If list, can contain 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. forceNewFile(optional): Whether to create new output file. saveFormat(optional): Format for saved plot(s). Defaults to "eps". saveDPI(optional): DPI for saved plot(s). Defaults to. show(optional): Whether to show figure. Defaults to True. fenplotArgs(optional): Optional arguments for fenplot. figspecs(optional key args): Optional arguments for matplotlib figure creation. Returns: List of output filenames and list of figure handles. """ if not is_state and not self.isCEye: return super().plot(u, warping, False, name, save, what, forceNewFile, saveFormat, saveDPI, show, fenplotArgs, **figspecs) if isinstance(what, (str,)): if what.upper() == 'ALL': what = ['ABS', 'PHASE', 'REAL', 'IMAG'] else: what = [what] what = purgeList(what, ['ABS', 'PHASE', 'REAL', 'IMAG'], listname = self.name() + ".what", baselevel = 1) if len(what) == 0: return if 'figsize' not in figspecs.keys(): figspecs['figsize'] = plt.figaspect(1. / len(what)) figs = [None] * self.V.num_sub_spaces() fileOut = [None] * self.V.num_sub_spaces() for j in range(self.V.num_sub_spaces()): II = self.V.sub(j).dofmap().dofs() Vj = self.V.sub(j).collapse() subplotidx = 0 figs[j] = plt.figure(**figspecs) plt.jet() if 'ABS' in what: uAb = fen.Function(Vj) uAb.vector().set_local(np.abs(u[II])) subplotidx = subplotidx + 1 ax = figs[j].add_subplot(1, len(what), subplotidx) p = fenplot(uAb, warping = warping, title = "|{}_comp{}|".format(name, j), **fenplotArgs) if self.V.mesh().geometric_dimension() > 1: figs[j].colorbar(p, ax = ax) if 'PHASE' in what: uPh = fen.Function(Vj) uPh.vector().set_local(np.angle(u[II])) subplotidx = subplotidx + 1 ax = figs[j].add_subplot(1, len(what), subplotidx) p = fenplot(uPh, warping = warping, title = "phase({}_comp{})".format(name, j), **fenplotArgs) if self.V.mesh().geometric_dimension() > 1: figs[j].colorbar(p, ax = ax) if 'REAL' in what: uRe = fen.Function(Vj) uRe.vector().set_local(np.real(u[II])) subplotidx = subplotidx + 1 ax = figs[j].add_subplot(1, len(what), subplotidx) p = fenplot(uRe, warping = warping, title = "Re({}_comp{})".format(name, j), **fenplotArgs) if self.V.mesh().geometric_dimension() > 1: figs[j].colorbar(p, ax = ax) if 'IMAG' in what: uIm = fen.Function(Vj) uIm.vector().set_local(np.imag(u[II])) subplotidx = subplotidx + 1 ax = figs[j].add_subplot(1, len(what), subplotidx) p = fenplot(uIm, warping = warping, title = "Im({}_comp{})".format(name, j), **fenplotArgs) if self.V.mesh().geometric_dimension() > 1: figs[j].colorbar(p, ax = ax) plt.tight_layout() if save is not None: save = save.strip() if forceNewFile: fileOut = getNewFilename( "{}_comp{}_fig_".format(save, j), saveFormat) else: fileOut = "{}_comp{}_fig.{}".format(save,j,saveFormat) figs[j].savefig(fileOut, format = saveFormat, dpi = saveDPI) if show: plt.show() if fileOut[0] is None: return figs return figs, fileOut diff --git a/rrompy/parameter/parameter_list.py b/rrompy/parameter/parameter_list.py index 34f0942..f8c5295 100644 --- a/rrompy/parameter/parameter_list.py +++ b/rrompy/parameter/parameter_list.py @@ -1,232 +1,230 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from itertools import product as iterprod from copy import deepcopy as copy from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert from rrompy.utilities.base.types import Np2D __all__ = ['parameterList', 'emptyParameterList', 'checkParameterList'] def checkParameterList(mu, npar = None): if not isinstance(mu, (parameterList,)): mu = parameterList(mu, npar) else: if npar is not None: RROMPyAssert(mu.shape[1], npar, "Number of parameters") mu = copy(mu) return mu, len(mu) <= 1 def checkParameter(mu, npar = None): muL, wasPar = checkParameterList(mu, npar) if not wasPar: muL, wasPar = checkParameterList([mu], npar) if not wasPar: raise RROMPyException(("Only single parameter allowed. No " "parameter lists here.")) return muL def emptyParameterList(): return parameterList([[]]) def addMemberFromNumpyArray(self, fieldName): def objFunc(self, other): if not isinstance(other, (self.__class__,)): other = parameterList(other, self.shape[1]) return parameterList(getattr(np.ndarray, fieldName)(self.data, other.data)) setattr(self.__class__, fieldName, objFunc) def objIFunc(self, other): self.data = getattr(self.__class__, fieldName)(self, other).data setattr(self.__class__, "__i" + fieldName[2:], objIFunc) class parameterList: - """HERE""" - __all__ += [pre + post for pre, post in iterprod(["__", "__i"], ["add__", "sub__", "mul__", "div__", "truediv__", "floordiv__", "pow__"])] def __init__(self, data:Np2D, lengthCheck : int = None): if not hasattr(data, "__len__"): data = [data] elif isinstance(data, (self.__class__,)): data = data.data elif isinstance(data, (tuple,)): data = list(data) if (isinstance(data, (list,)) and len(data) > 0 and isinstance(data[0], (tuple,))): data = [list(x) for x in data] self.data = np.array(data, ndmin = 1, copy = 1) if self.data.ndim == 1: self.data = self.data[:, None] if np.size(self.data) > 0: self.data = self.data.reshape((len(self), -1)) if self.shape[0] * self.shape[1] == 0: lenEff = 0 if lengthCheck is None else lengthCheck self.reset((0, lenEff), self.dtype) if lengthCheck is not None: if lengthCheck != 1 and self.shape == (lengthCheck, 1): self.data = self.data.T RROMPyAssert(self.shape[1], lengthCheck, "Number of parameters") for fieldName in ["__add__", "__sub__", "__mul__", "__div__", "__truediv__", "__floordiv__", "__pow__"]: addMemberFromNumpyArray(self, fieldName) def __len__(self): return self.shape[0] def __str__(self): if len(self) == 0: selfstr = "[]" elif len(self) <= 3: selfstr = "[{}]".format(" ".join([str(x) for x in self.data])) else: selfstr = "[{} ..({}).. {}]".format(self[0], len(self) - 2, self[-1]) return selfstr def __repr__(self): return repr(self.data) @property def shape(self): return self.data.shape @property def size(self): return self.data.size @property def re(self): return parameterList(np.real(self.data)) @property def im(self): return parameterList(np.imag(self.data)) @property def abs(self): return parameterList(np.abs(self.data)) @property def angle(self): return parameterList(np.angle(self.data)) @property def conj(self): return parameterList(np.conj(self.data)) @property def dtype(self): return self.data.dtype def __getitem__(self, key): return self.data[key] def __call__(self, key, idx = None): if idx is None: return self.data[:, key] return self[key, idx] def __setitem__(self, key, value): if isinstance(key, (tuple, list,)): RROMPyAssert(len(key), len(value), "Slice length") for k, val in zip(key, value): self[k] = val else: self.data[key] = value def __eq__(self, other): if not hasattr(other, "shape") or self.shape != other.shape: return False if isinstance(other, self.__class__): other = other.data return np.allclose(self.data, other) def __contains__(self, item): return next((x for x in self if np.allclose(x[0], item)), -1) != -1 def __iter__(self): return iter([parameterList([x]) for x in self.data]) def __copy__(self): return parameterList(self.data) def __deepcopy__(self, memo): return parameterList(copy(self.data, memo)) def __neg__(self): return parameterList(-self.data) def __pos__(self): return copy(self) def reset(self, size, dtype = complex): self.data = np.empty(size, dtype = dtype) self.data[:] = np.nan def insert(self, items, idx = None): if isinstance(items, self.__class__): items = items.data else: items = np.array(items, ndmin = 2) if len(self) == 0: self.data = parameterList(items).data elif idx is None: self.data = np.append(self.data, items, axis = 0) else: self.data = np.insert(self.data, idx, items, axis = 0) def append(self, items): self.insert(items) def pop(self, idx = -1): self.data = np.delete(self.data, idx, axis = 0) def find(self, item): if len(self) == 0: return None return next((j for j in range(len(self)) if np.allclose(self[j], item)), None) def findall(self, item): if len(self) == 0: return [] return [j for j in range(len(self)) if np.allclose(self[j], item)] def sort(self, overwrite = False, *args, **kwargs): dataT = np.array([tuple(x[0]) for x in self], dtype = [(str(j), self.dtype) for j in range(self.shape[1])]) sortedP = parameterList([list(x) for x in np.sort(dataT, *args, **kwargs)]) if overwrite: self.data = sortedP.data return sortedP def unique(self, overwrite = False, *args, **kwargs): dataT = np.array([tuple(x[0]) for x in self], dtype = [(str(j), self.dtype) for j in range(self.shape[1])]) uniqueT = np.unique(dataT, *args, **kwargs) if isinstance(uniqueT, (tuple,)): extraT = uniqueT[1:] uniqueT = uniqueT[0] else: extraT = () uniqueP = parameterList([list(x) for x in uniqueT]) if overwrite: self.data = uniqueP.data uniqueP = (uniqueP,) + extraT if len(uniqueP) == 1: return uniqueP[0] return uniqueP diff --git a/rrompy/parameter/parameter_sampling/__init__.py b/rrompy/parameter/parameter_sampling/__init__.py index 7bef076..c819043 100644 --- a/rrompy/parameter/parameter_sampling/__init__.py +++ b/rrompy/parameter/parameter_sampling/__init__.py @@ -1,42 +1,36 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .generic_sampler import GenericSampler -from .generic_quadrature_sampler import GenericQuadratureSampler -from .generic_random_sampler import GenericRandomSampler from .manual_sampler import ManualSampler from .segment import QuadratureSampler, QuadratureSamplerTotal, RandomSampler from .shape import (FFTSampler, QuadratureBoxSampler, QuadratureCircleSampler, RandomBoxSampler, RandomCircleSampler) __all__ = [ - 'GenericSampler', - 'GenericQuadratureSampler', - 'GenericRandomSampler', 'ManualSampler', 'QuadratureSampler', 'QuadratureSamplerTotal', 'RandomSampler', 'FFTSampler', 'QuadratureBoxSampler', 'QuadratureCircleSampler', 'RandomBoxSampler', 'RandomCircleSampler' ] diff --git a/rrompy/parameter/parameter_sampling/manual_sampler.py b/rrompy/parameter/parameter_sampling/manual_sampler.py index 08526b3..2d9baad 100644 --- a/rrompy/parameter/parameter_sampling/manual_sampler.py +++ b/rrompy/parameter/parameter_sampling/manual_sampler.py @@ -1,65 +1,65 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy -from rrompy.parameter.parameter_sampling.generic_sampler import GenericSampler +from .generic_sampler import GenericSampler from rrompy.utilities.base.types import List, paramList from rrompy.parameter import checkParameterList __all__ = ['ManualSampler'] class ManualSampler(GenericSampler): """Manual generator of sample points.""" def __init__(self, lims:paramList, points:paramList, scalingExp : List[float] = None, normalFoci : List[np.complex] = [-1., 1.]): super().__init__(lims = lims, scalingExp = scalingExp) self.points = points self._normalFoci = normalFoci def normalFoci(self, d : int = 0): return self._normalFoci @property def points(self): """Value of points.""" return self._points @points.setter def points(self, points): points = checkParameterList(points, self.npar)[0] self._points = points def __str__(self) -> str: return "{}[{}]".format(self.name(), "_".join(map(str, self.points))) def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) def generatePoints(self, n:int, reorder : bool = True) -> paramList: """Array of sample points.""" if n > len(self.points): pts = copy(self.points) for j in range(int(np.ceil(n / len(self.points)))): pts.append(self.points) else: pts = self.points x = checkParameterList(pts[list(range(n))], self.npar)[0] return x diff --git a/rrompy/parameter/parameter_sampling/shape/__init__.py b/rrompy/parameter/parameter_sampling/shape/__init__.py index 40e0793..58c2a15 100644 --- a/rrompy/parameter/parameter_sampling/shape/__init__.py +++ b/rrompy/parameter/parameter_sampling/shape/__init__.py @@ -1,39 +1,33 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .generic_shape_sampler import GenericShapeSampler -from .generic_shape_quadrature_sampler import GenericShapeQuadratureSampler -from .generic_shape_random_sampler import GenericShapeRandomSampler from .fft_sampler import FFTSampler from .quadrature_box_sampler import QuadratureBoxSampler from .quadrature_circle_sampler import QuadratureCircleSampler from .random_box_sampler import RandomBoxSampler from .random_circle_sampler import RandomCircleSampler __all__ = [ - 'GenericShapeSampler', - 'GenericShapeQuadratureSampler', - 'GenericShapeRandomSampler', 'FFTSampler', 'QuadratureBoxSampler', 'QuadratureCircleSampler', 'RandomBoxSampler', 'RandomCircleSampler' ] diff --git a/rrompy/parameter/parameter_sampling/shape/generic_shape_quadrature_sampler.py b/rrompy/parameter/parameter_sampling/shape/generic_shape_quadrature_sampler.py index 9fb1718..eb7213a 100644 --- a/rrompy/parameter/parameter_sampling/shape/generic_shape_quadrature_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/generic_shape_quadrature_sampler.py @@ -1,55 +1,54 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from rrompy.parameter.parameter_sampling.shape.generic_shape_sampler import ( - GenericShapeSampler) +from .generic_shape_sampler import GenericShapeSampler from rrompy.parameter.parameter_sampling.generic_quadrature_sampler import ( _allowedQuadratureKinds) from rrompy.utilities.base.types import List, paramList from rrompy.utilities.exception_manager import RROMPyException __all__ = ['GenericShapeQuadratureSampler'] class GenericShapeQuadratureSampler(GenericShapeSampler): """Generator of quadrature sample points on shapes.""" def __init__(self, lims:paramList, kind : str = "UNIFORM", axisRatios : List[float] = None, scalingExp : List[float] = None): super().__init__(lims = lims, axisRatios = axisRatios, scalingExp = scalingExp) self._allowedKinds = _allowedQuadratureKinds self.kind = kind def __str__(self) -> str: return "{}_{}".format(super().__str__(), self.kind) def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) @property def kind(self): """Value of kind.""" return self._kind @kind.setter def kind(self, kind): kind = kind.upper().strip().replace(" ","") if kind not in self._allowedKinds: raise RROMPyException("Generator kind not recognized.") self._kind = kind diff --git a/rrompy/parameter/parameter_sampling/shape/generic_shape_random_sampler.py b/rrompy/parameter/parameter_sampling/shape/generic_shape_random_sampler.py index c1a52dc..ea20875 100644 --- a/rrompy/parameter/parameter_sampling/shape/generic_shape_random_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/generic_shape_random_sampler.py @@ -1,54 +1,53 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from rrompy.parameter.parameter_sampling.shape.generic_shape_sampler import ( - GenericShapeSampler) +from .generic_shape_sampler import GenericShapeSampler from rrompy.parameter.parameter_sampling.generic_random_sampler import ( _allowedRandomKinds) from rrompy.utilities.base.types import List, paramList from rrompy.utilities.exception_manager import RROMPyException __all__ = ['GenericShapeRandomSampler'] class GenericShapeRandomSampler(GenericShapeSampler): """Generator of random sample points on shapes.""" def __init__(self, lims:paramList, kind : str = "UNIFORM", axisRatios : List[float] = None, scalingExp : List[float] = None, seed : int = 42): super().__init__(lims = lims, axisRatios = axisRatios, scalingExp = scalingExp) self._allowedKinds = _allowedRandomKinds self.kind = kind self.seed = seed def __str__(self) -> str: return "{}_{}".format(super().__str__(), self.kind) def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) @property def kind(self): """Value of kind.""" return self._kind @kind.setter def kind(self, kind): if kind.upper() not in self._allowedKinds: raise RROMPyException("Generator kind not recognized.") self._kind = kind.upper() diff --git a/rrompy/reduction_methods/base/__init__.py b/rrompy/reduction_methods/base/__init__.py index f6aa877..3a18513 100644 --- a/rrompy/reduction_methods/base/__init__.py +++ b/rrompy/reduction_methods/base/__init__.py @@ -1,29 +1,27 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .generic_approximant import GenericApproximant from .rational_interpolant_utils import checkRobustTolerance from .reduced_basis_utils import projectAffineDecomposition __all__ = [ - 'GenericApproximant', 'checkRobustTolerance', 'projectAffineDecomposition' ] diff --git a/rrompy/reduction_methods/base/generic_approximant.py b/rrompy/reduction_methods/base/generic_approximant.py index d7219cd..d336dc5 100644 --- a/rrompy/reduction_methods/base/generic_approximant.py +++ b/rrompy/reduction_methods/base/generic_approximant.py @@ -1,848 +1,848 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from abc import abstractmethod import numpy as np from itertools import product as iterprod from copy import deepcopy as copy from os import remove as osrm from rrompy.sampling import SamplingEngineStandard, SamplingEngineStandardPOD from rrompy.utilities.base.types import (Np1D, DictAny, HFEng, List, Tuple, ListAny, strLst, paramVal, paramList, sampList) -from rrompy.utilities.base import (purgeDict, verbosityManager as vbMng, - getNewFilename) +from rrompy.utilities.base.data_structures import purgeDict, getNewFilename +from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPy_READY, RROMPy_FRAGILE) -from rrompy.utilities.base import pickleDump, pickleLoad +from rrompy.utilities.base.pickle_utilities import pickleDump, pickleLoad from rrompy.parameter import (emptyParameterList, checkParameter, checkParameterList) from rrompy.sampling import sampleList, emptySampleList __all__ = ['GenericApproximant'] def addNormFieldToClass(self, fieldName): def objFunc(self, mu:paramList, *args, **kwargs) -> Np1D: uV = getattr(self.__class__, "get" + fieldName)(self, mu) kwargs["is_state"] = False val = self.HFEngine.norm(uV, *args, **kwargs) return val setattr(self.__class__, "norm" + fieldName, objFunc) def addNormDualFieldToClass(self, fieldName): def objFunc(self, mu:paramList, *args, **kwargs) -> Np1D: uV = getattr(self.__class__, "get" + fieldName)(self, mu) kwargs["is_state"] = True if "dual" not in kwargs.keys(): kwargs["dual"] = True val = self.HFEngine.norm(uV, *args, **kwargs) return val setattr(self.__class__, "norm" + fieldName, objFunc) def addPlotFieldToClass(self, fieldName): def objFunc(self, mu:paramList, *args, **kwargs): uV = getattr(self.__class__, "get" + fieldName)(self, mu) if "name" in kwargs.keys(): nameBase = copy(kwargs["name"]) filesOut = [] for j, u in enumerate(uV): if "name" in kwargs.keys(): kwargs["name"] = nameBase + str(j) filesOut += [self.HFEngine.plot(u, *args, **kwargs)] if "name" in kwargs.keys(): kwargs["name"] = nameBase if filesOut[0] is None: return None return filesOut setattr(self.__class__, "plot" + fieldName, objFunc) def addPlotDualFieldToClass(self, fieldName): def objFunc(self, mu:paramList, *args, **kwargs): uV = getattr(self.__class__, "get" + fieldName)(self, mu) if "name" in kwargs.keys(): nameBase = copy(kwargs["name"]) filesOut = [] for j, u in enumerate(uV): if "name" in kwargs.keys(): kwargs["name"] = nameBase + str(j) filesOut += [self.HFEngine.plot(u, *args, **kwargs)] if "name" in kwargs.keys(): kwargs["name"] = nameBase if filesOut[0] is None: return None return filesOut setattr(self.__class__, "plot" + fieldName, objFunc) def addOutParaviewFieldToClass(self, fieldName): def objFunc(self, mu:paramVal, *args, **kwargs): if not hasattr(self.HFEngine, "outParaview"): raise RROMPyException(("High fidelity engine cannot output to " "Paraview.")) uV = getattr(self.__class__, "get" + fieldName)(self, mu) if "name" in kwargs.keys(): nameBase = copy(kwargs["name"]) filesOut = [] for j, u in enumerate(uV): if "name" in kwargs.keys(): kwargs["name"] = nameBase + str(j) filesOut += [self.HFEngine.outParaview(u, *args, **kwargs)] if "name" in kwargs.keys(): kwargs["name"] = nameBase if filesOut[0] is None: return None return filesOut setattr(self.__class__, "outParaview" + fieldName, objFunc) def addOutParaviewTimeDomainFieldToClass(self, fieldName): def objFunc(self, mu:paramVal, *args, **kwargs): if not hasattr(self.HFEngine, "outParaviewTimeDomain"): raise RROMPyException(("High fidelity engine cannot output to " "Paraview.")) uV = getattr(self.__class__, "get" + fieldName)(self, mu) omega = args.pop(0) if len(args) > 0 else np.real(mu) if "name" in kwargs.keys(): nameBase = copy(kwargs["name"]) filesOut = [] for j, u in enumerate(uV): if "name" in kwargs.keys(): kwargs["name"] = nameBase + str(j) filesOut += [self.HFEngine.outParaviewTimeDomain(u, omega, *args, **kwargs)] if "name" in kwargs.keys(): kwargs["name"] = nameBase if filesOut[0] is None: return None return filesOut setattr(self.__class__, "outParaviewTimeDomain" + fieldName, objFunc) class GenericApproximant: """ ABSTRACT ROM approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. trainedModel: Trained model evaluator. mu0: Default parameter. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList{Soft,Critical}. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of samples current approximant relies upon. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. S: Number of solution snapshots over which current approximant is based upon. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ __all__ += [ftype + dtype for ftype, dtype in iterprod( ["norm", "plot", "outParaview", "outParaviewTimeDomain"], ["HF", "RHS", "Approx", "Res", "Err"])] def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, approx_state : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() self._mode = RROMPy_READY self.approx_state = approx_state self.verbosity = verbosity self.timestamp = timestamp vbMng(self, "INIT", "Initializing engine of type {}.".format(self.name()), 10) self._HFEngine = HFEngine self.trainedModel = None self.lastSolvedHF = emptyParameterList() self.uHF = emptySampleList() self._addParametersToList(["POD"], [True], ["S"], [1]) if mu0 is None: if hasattr(self.HFEngine, "mu0"): self.mu0 = checkParameter(self.HFEngine.mu0) else: raise RROMPyException(("Center of approximation cannot be " "inferred from HF engine. Parameter " "required")) else: self.mu0 = checkParameter(mu0, self.HFEngine.npar) self.resetSamples() self.approxParameters = approxParameters self._postInit() ### add norm{HF,Err} methods """ Compute norm of * at arbitrary parameter. Args: mu: Target parameter. Returns: Target norm of *. """ for objName in ["HF", "Err"]: addNormFieldToClass(self, objName) ### add norm{RHS,Res} methods """ Compute norm of * at arbitrary parameter. Args: mu: Target parameter. Returns: Target norm of *. """ for objName in ["RHS", "Res"]: addNormDualFieldToClass(self, objName) ### add plot{HF,Approx,Err} methods """ Do some nice plots of * at arbitrary parameter. Args: mu: Target parameter. name(optional): Name to be shown as title of the plots. Defaults to 'u'. what(optional): Which plots to do. If list, can contain 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. save(optional): Where to save plot(s). Defaults to None, i.e. no saving. saveFormat(optional): Format for saved plot(s). Defaults to "eps". saveDPI(optional): DPI for saved plot(s). Defaults to 100. show(optional): Whether to show figure. Defaults to True. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ for objName in ["HF", "Approx", "Err"]: addPlotFieldToClass(self, objName) ### add plot{RHS,Res} methods """ Do some nice plots of * at arbitrary parameter. Args: mu: Target parameter. name(optional): Name to be shown as title of the plots. Defaults to 'u'. what(optional): Which plots to do. If list, can contain 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. save(optional): Where to save plot(s). Defaults to None, i.e. no saving. saveFormat(optional): Format for saved plot(s). Defaults to "eps". saveDPI(optional): DPI for saved plot(s). Defaults to 100. show(optional): Whether to show figure. Defaults to True. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ for objName in ["RHS", "Res"]: addPlotDualFieldToClass(self, objName) ### add outParaview{HF,RHS,Approx,Res,Err} methods """ Output * to ParaView file. Args: mu: Target parameter. name(optional): Base name to be used for data output. filename(optional): Name of output file. time(optional): Timestamp. what(optional): Which plots to do. If list, can contain 'MESH', 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. forceNewFile(optional): Whether to create new output file. filePW(optional): Fenics File entity (for time series). """ for objName in ["HF", "RHS", "Approx", "Res", "Err"]: addOutParaviewFieldToClass(self, objName) ### add outParaviewTimeDomain{HF,RHS,Approx,Res,Err} methods """ Output * to ParaView file, converted to time domain. Args: mu: Target parameter. omega(optional): frequency. timeFinal(optional): final time of simulation. periodResolution(optional): number of time steps per period. name(optional): Base name to be used for data output. filename(optional): Name of output file. forceNewFile(optional): Whether to create new output file. """ for objName in ["HF", "RHS", "Approx", "Res", "Err"]: addOutParaviewTimeDomainFieldToClass(self, objName) def _preInit(self): if not hasattr(self, "depth"): self.depth = 0 else: self.depth += 1 @property def tModelType(self): raise RROMPyException("No trainedModel type assigned.") def initializeModelData(self, datadict): - from .trained_model import TrainedModelData + from .trained_model.trained_model_data import TrainedModelData return (TrainedModelData(datadict["mu0"], datadict.pop("projMat"), datadict["scaleFactor"], datadict.pop("rescalingExp")), ["mu0", "scaleFactor", "mus"]) @property def parameterList(self): """Value of parameterListSoft + parameterListCritical.""" return self.parameterListSoft + self.parameterListCritical def _addParametersToList(self, whatSoft : strLst = [], defaultSoft : ListAny = [], whatCritical : strLst = [], defaultCritical : ListAny = [], toBeExcluded : strLst = []): if not hasattr(self, "parameterToBeExcluded"): self.parameterToBeExcluded = [] self.parameterToBeExcluded = toBeExcluded + self.parameterToBeExcluded if not hasattr(self, "parameterListSoft"): self.parameterListSoft = [] if not hasattr(self, "parameterDefaultSoft"): self.parameterDefaultSoft = {} if not hasattr(self, "parameterListCritical"): self.parameterListCritical = [] if not hasattr(self, "parameterDefaultCritical"): self.parameterDefaultCritical = {} for j, what in enumerate(whatSoft): if what not in self.parameterToBeExcluded: self.parameterListSoft = [what] + self.parameterListSoft self.parameterDefaultSoft[what] = defaultSoft[j] for j, what in enumerate(whatCritical): if what not in self.parameterToBeExcluded: self.parameterListCritical = ([what] + self.parameterListCritical) self.parameterDefaultCritical[what] = defaultCritical[j] def _postInit(self): if self.depth == 0: vbMng(self, "DEL", "Done initializing.", 10) del self.depth else: self.depth -= 1 def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) def setupSampling(self): """Setup sampling engine.""" RROMPyAssert(self._mode, message = "Cannot setup sampling engine.") if not hasattr(self, "_POD") or self._POD is None: return if self.POD: SamplingEngine = SamplingEngineStandardPOD else: SamplingEngine = SamplingEngineStandard self.samplingEngine = SamplingEngine(self.HFEngine, sample_state = self.approx_state, verbosity = self.verbosity) self.resetSamples() @property def HFEngine(self): """Value of HFEngine.""" return self._HFEngine @HFEngine.setter def HFEngine(self, HFEngine): raise RROMPyException("Cannot change HFEngine.") @property def mu0(self): """Value of mu0.""" return self._mu0 @mu0.setter def mu0(self, mu0): mu0 = checkParameter(mu0) if not hasattr(self, "_mu0") or mu0 != self.mu0: self.resetSamples() self._mu0 = mu0 @property def npar(self): """Number of parameters.""" return self.mu0.shape[1] @property def approxParameters(self): """Value of approximant parameters.""" return self._approxParameters @approxParameters.setter def approxParameters(self, approxParams): if not hasattr(self, "approxParameters"): self._approxParameters = {} approxParameters = purgeDict(approxParams, self.parameterList, dictname = self.name() + ".approxParameters", baselevel = 1) keyList = list(approxParameters.keys()) for key in self.parameterListCritical: if key in keyList: setattr(self, "_" + key, self.parameterDefaultCritical[key]) for key in self.parameterListSoft: if key in keyList: setattr(self, "_" + key, self.parameterDefaultSoft[key]) fragile = False for key in self.parameterListCritical: if key in keyList: val = approxParameters[key] else: val = getattr(self, "_" + key, None) if val is None: val = self.parameterDefaultCritical[key] getattr(self.__class__, key, None).fset(self, val) fragile = fragile or val is None for key in self.parameterListSoft: if key in keyList: val = approxParameters[key] else: val = getattr(self, "_" + key, None) if val is None: val = self.parameterDefaultSoft[key] getattr(self.__class__, key, None).fset(self, val) if fragile: self._mode = RROMPy_FRAGILE @property def POD(self): """Value of POD.""" return self._POD @POD.setter def POD(self, POD): if hasattr(self, "_POD"): PODold = self.POD else: PODold = -1 self._POD = POD self._approxParameters["POD"] = self.POD if PODold != self.POD: self.samplingEngine = None self.resetSamples() @property def approx_state(self): """Value of approx_state.""" return self._approx_state @approx_state.setter def approx_state(self, approx_state): if hasattr(self, "_approx_state"): approx_stateold = self.approx_state else: approx_stateold = -1 self._approx_state = approx_state if approx_stateold != self.approx_state: self.samplingEngine = None self.resetSamples() @property def S(self): """Value of S.""" return self._S @S.setter def S(self, S): if S <= 0: raise RROMPyException("S must be positive.") if hasattr(self, "_S") and self._S is not None: Sold = self.S else: Sold = -1 self._S = S self._approxParameters["S"] = self.S if Sold != self.S: self.resetSamples() @property def trainedModel(self): """Value of trainedModel.""" return self._trainedModel @trainedModel.setter def trainedModel(self, trainedModel): self._trainedModel = trainedModel if self._trainedModel is not None: self._trainedModel.reset() self.lastSolvedApproxReduced = emptyParameterList() self.lastSolvedApprox = emptyParameterList() self.uApproxReduced = emptySampleList() self.uApprox = emptySampleList() def resetSamples(self): if hasattr(self, "samplingEngine") and self.samplingEngine is not None: self.samplingEngine.resetHistory() else: self.setupSampling() self._mode = RROMPy_READY def plotSamples(self, *args, **kwargs) -> List[str]: """ Do some nice plots of the samples. Returns: Output filenames. """ RROMPyAssert(self._mode, message = "Cannot plot samples.") return self.samplingEngine.plotSamples(*args, **kwargs) def outParaviewSamples(self, *args, **kwargs) -> List[str]: """ Output samples to ParaView file. Returns: Output filenames. """ RROMPyAssert(self._mode, message = "Cannot output samples.") return self.samplingEngine.outParaviewSamples(*args, **kwargs) def outParaviewTimeDomainSamples(self, *args, **kwargs) -> List[str]: """ Output samples to ParaView file, converted to time domain. Returns: Output filenames. """ RROMPyAssert(self._mode, message = "Cannot output samples.") return self.samplingEngine.outParaviewTimeDomainSamples(*args, **kwargs) def setSamples(self, samplingEngine): """Copy samplingEngine and samples.""" vbMng(self, "INIT", "Transfering samples.", 10) self.samplingEngine = copy(samplingEngine) vbMng(self, "DEL", "Done transfering samples.", 10) def setTrainedModel(self, model): """Deepcopy approximation from trained model.""" if hasattr(model, "storeTrainedModel"): verb = model.verbosity model.verbosity = 0 fileOut = model.storeTrainedModel() model.verbosity = verb else: try: fileOut = getNewFilename("trained_model", "pkl") pickleDump(model.data.__dict__, fileOut) except: raise RROMPyException(("Failed to store model data. Parameter " "model must have either " "storeTrainedModel or " "data.__dict__ properties.")) self.loadTrainedModel(fileOut) osrm(fileOut) @abstractmethod def setupApprox(self) -> int: """ Setup approximant. (ABSTRACT) Any specialization should include something like if self.checkComputedApprox(): return RROMPyAssert(self._mode, message = "Cannot setup approximant.") ... self.trainedModel = ... self.trainedModel.data = ... self.trainedModel.data.approxParameters = copy( self.approxParameters) Returns > 0 if error was encountered, < 0 if no computation was necessary. """ pass def checkComputedApprox(self) -> bool: """ Check if setup of new approximant is not needed. Returns: True if new setup is not needed. False otherwise. """ return self._mode == RROMPy_FRAGILE or (self.trainedModel is not None and self.trainedModel.data.approxParameters == self.approxParameters) def _pruneBeforeEval(self, mu:paramList, field:str, append:bool, prune:bool) -> Tuple[paramList, Np1D, Np1D, bool]: mu = checkParameterList(mu, self.npar)[0] idx = np.empty(len(mu), dtype = np.int) if prune: jExtra = np.zeros(len(mu), dtype = bool) muExtra = emptyParameterList() lastSolvedMus = getattr(self, "lastSolved" + field) if (len(mu) > 0 and len(mu) == len(lastSolvedMus) and mu == lastSolvedMus): idx = np.arange(len(mu), dtype = np.int) return muExtra, jExtra, idx, True muKeep = copy(muExtra) for j in range(len(mu)): jPos = lastSolvedMus.find(mu[j]) if jPos is not None: idx[j] = jPos muKeep.append(mu[j]) else: jExtra[j] = True muExtra.append(mu[j]) if len(muKeep) > 0 and not append: lastSolvedu = getattr(self, "u" + field) idx[~jExtra] = getattr(self.__class__, "set" + field)(self, muKeep, lastSolvedu[idx[~jExtra]], append) append = True else: jExtra = np.ones(len(mu), dtype = bool) muExtra = mu return muExtra, jExtra, idx, append def _setObject(self, mu:paramList, field:str, object:sampList, append:bool) -> List[int]: newMus = checkParameterList(mu, self.npar)[0] newObj = sampleList(object) if append: getattr(self, "lastSolved" + field).append(newMus) getattr(self, "u" + field).append(newObj) Ltot = len(getattr(self, "u" + field)) return list(range(Ltot - len(newObj), Ltot)) setattr(self, "lastSolved" + field, copy(newMus)) setattr(self, "u" + field, copy(newObj)) return list(range(len(getattr(self, "u" + field)))) def setHF(self, muHF:paramList, uHF:sampleList, append : bool = False) -> List[int]: """Assign high fidelity solution.""" return self._setObject(muHF, "HF", uHF, append) def evalHF(self, mu:paramList, append : bool = False, prune : bool = True) -> List[int]: """ Find high fidelity solution with original parameters and arbitrary parameter. Args: mu: Target parameter. append(optional): Whether to append new HF solutions to old ones. prune(optional): Whether to remove duplicates of already appearing HF solutions. """ muExtra, jExtra, idx, append = self._pruneBeforeEval(mu, "HF", append, prune) if len(muExtra) > 0: vbMng(self, "INIT", "Solving HF model for mu = {}.".format(mu), 15) newuHFs = self.HFEngine.solve(muExtra) vbMng(self, "DEL", "Done solving HF model.", 15) idx[jExtra] = self.setHF(muExtra, newuHFs, append) return list(idx) def setApproxReduced(self, muApproxR:paramList, uApproxR:sampleList, append : bool = False) -> List[int]: """Assign high fidelity solution.""" return self._setObject(muApproxR, "ApproxReduced", uApproxR, append) def evalApproxReduced(self, mu:paramList, append : bool = False, prune : bool = True) -> List[int]: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. append(optional): Whether to append new HF solutions to old ones. prune(optional): Whether to remove duplicates of already appearing HF solutions. """ self.setupApprox() muExtra, jExtra, idx, append = self._pruneBeforeEval(mu, "ApproxReduced", append, prune) if len(muExtra) > 0: newuApproxs = self.trainedModel.getApproxReduced(muExtra) idx[jExtra] = self.setApproxReduced(muExtra, newuApproxs, append) return list(idx) def setApprox(self, muApprox:paramList, uApprox:sampleList, append : bool = False) -> List[int]: """Assign high fidelity solution.""" return self._setObject(muApprox, "Approx", uApprox, append) def evalApprox(self, mu:paramList, append : bool = False, prune : bool = True) -> List[int]: """ Evaluate approximant at arbitrary parameter. Args: mu: Target parameter. append(optional): Whether to append new HF solutions to old ones. prune(optional): Whether to remove duplicates of already appearing HF solutions. """ self.setupApprox() muExtra, jExtra, idx, append = self._pruneBeforeEval(mu, "Approx", append, prune) if len(muExtra) > 0: newuApproxs = self.trainedModel.getApprox(muExtra) idx[jExtra] = self.setApprox(muExtra, newuApproxs, append) return list(idx) def getHF(self, mu:paramList, append : bool = False, prune : bool = True) -> sampList: """ Get HF solution at arbitrary parameter. Args: mu: Target parameter. Returns: HFsolution. """ mu = checkParameterList(mu, self.npar)[0] idx = self.evalHF(mu, append = append, prune = prune) return self.uHF(idx) def getRHS(self, mu:paramList) -> sampList: """ Get linear system RHS at arbitrary parameter. Args: mu: Target parameter. Returns: Linear system RHS. """ return self.HFEngine.residual(mu, None) def getApproxReduced(self, mu:paramList, append : bool = False, prune : bool = True) -> sampList: """ Get approximant at arbitrary parameter. Args: mu: Target parameter. Returns: Reduced approximant. """ mu = checkParameterList(mu, self.npar)[0] idx = self.evalApproxReduced(mu, append = append, prune = prune) return self.uApproxReduced(idx) def getApprox(self, mu:paramList, append : bool = False, prune : bool = True) -> sampList: """ Get approximant at arbitrary parameter. Args: mu: Target parameter. Returns: Approximant. """ mu = checkParameterList(mu, self.npar)[0] idx = self.evalApprox(mu, append = append, prune = prune) return self.uApprox(idx) def getRes(self, mu:paramList) -> sampList: """ Get residual at arbitrary parameter. Args: mu: Target parameter. Returns: Approximant residual. """ if not self.HFEngine.isCEye: raise RROMPyException(("Residual of solution with non-scalar C " "not computable.")) return self.HFEngine.residual(mu, self.getApprox(mu) / self.HFEngine.C) def getErr(self, mu:paramList, append : bool = False, prune : bool = True) -> sampList: """ Get error at arbitrary parameter. Args: mu: Target parameter. Returns: Approximant error. """ return (self.getApprox(mu, append = append, prune =prune) - self.getHF(mu, append = append, prune = prune)) def normApprox(self, mu:paramList) -> float: """ Compute norm of approximant at arbitrary parameter. Args: mu: Target parameter. Returns: Target norm of approximant. """ if not (self.POD and self.HFEngine.isCEye): return self.HFEngine.norm(self.getApprox(mu), is_state = False) return np.linalg.norm(self.HFEngine.C * self.getApproxReduced(mu).data, axis = 0) def getPoles(self, *args, **kwargs) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ self.setupApprox() vbMng(self, "INIT", "Computing poles of model.", 20) poles = self.trainedModel.getPoles(*args, **kwargs) vbMng(self, "DEL", "Done computing poles.", 20) return poles def storeTrainedModel(self, filenameBase : str = "trained_model", forceNewFile : bool = True) -> str: """Store trained reduced model to file.""" self.setupApprox() vbMng(self, "INIT", "Storing trained model to file.", 20) if forceNewFile: filename = getNewFilename(filenameBase, "pkl") else: filename = "{}.pkl".format(filenameBase) pickleDump(self.trainedModel.data.__dict__, filename) vbMng(self, "DEL", "Done storing trained model.", 20) return filename def loadTrainedModel(self, filename:str): """Load trained reduced model from file.""" vbMng(self, "INIT", "Loading pre-trained model from file.", 20) datadict = pickleLoad(filename) self.mu0 = datadict["mu0"] self.scaleFactor = datadict["scaleFactor"] self.mus = datadict["mus"] trainedModel = self.tModelType() trainedModel.verbosity = self.verbosity trainedModel.timestamp = self.timestamp data, selfkeys = self.initializeModelData(datadict) for key in selfkeys: setattr(self, key, datadict.pop(key)) approxParameters = datadict.pop("approxParameters") data.approxParameters = copy(approxParameters) for apkey in data.approxParameters.keys(): self._approxParameters[apkey] = approxParameters.pop(apkey) setattr(self, "_" + apkey, self._approxParameters[apkey]) for key in datadict: setattr(data, key, datadict[key]) trainedModel.data = data self.trainedModel = trainedModel self._mode = RROMPy_FRAGILE vbMng(self, "DEL", "Done loading pre-trained model.", 20) diff --git a/rrompy/reduction_methods/base/reduced_basis_utils.py b/rrompy/reduction_methods/base/reduced_basis_utils.py index 5ebe3b9..2d1b8cd 100644 --- a/rrompy/reduction_methods/base/reduced_basis_utils.py +++ b/rrompy/reduction_methods/base/reduced_basis_utils.py @@ -1,67 +1,69 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from rrompy.utilities.base.types import (Np1D, Np2D, Tuple, List, ListAny, sampList) -from rrompy.utilities.numerical import hashIdxToDerivative as hashI, dot +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.hash_derivative import ( + hashIdxToDerivative as hashI) from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.sampling import sampleList __all__ = ['projectAffineDecomposition'] def projectAffineDecomposition(As:List[Np2D], bs:List[Np1D], pMat:sampList, ARBsOld : List[Np2D] = None, bRBsOld : List[Np1D] = None, pMatOld : sampList = None)\ -> Tuple[List[Np2D], List[Np1D]]: """Project affine decomposition of linear system onto basis.""" RROMPyAssert((ARBsOld is None, bRBsOld is None), (pMatOld is None, pMatOld is None), "Old affine projected terms") if isinstance(pMat, (sampleList,)): pMat = pMat.data pMatC = pMat.conj() ARBs = [None] * len(As) bRBs = [None] * len(bs) if pMatOld is None: for j in range(max(len(As), len(bs))): if j < len(As): ARBs[j] = dot(dot(As[j], pMat).T, pMatC).T if j < len(bs): bRBs[j] = dot(pMatC.T, bs[j]) else: RROMPyAssert((len(ARBsOld), len(bRBsOld)), (len(As), len(bs)), "Old affine projected terms") if isinstance(pMatOld, (sampleList,)): pMatOld = pMatOld.data pMatOldC = pMatOld.conj() Sold = pMatOld.shape[1] Snew = pMat.shape[1] for j in range(max(len(As), len(bs))): if j < len(As): ARBs[j] = np.empty((Sold + Snew, Sold + Snew), dtype = ARBsOld[j].dtype) ARBs[j][: Sold, : Sold] = ARBsOld[j] ARBs[j][: Sold, Sold :] = dot(dot(As[j], pMat).T, pMatOldC).T ARBs[j][Sold :, : Sold] = dot(dot(As[j], pMatOld).T, pMatC).T ARBs[j][Sold :, Sold :] = dot(dot(As[j], pMat).T, pMatC).T if j < len(bs): bRBs[j] = np.empty((Sold + Snew), dtype = bRBsOld[j].dtype) bRBs[j][: Sold] = bRBsOld[j] bRBs[j][Sold :] = dot(pMatC.T, bs[j]) return ARBs, bRBs diff --git a/rrompy/reduction_methods/base/trained_model/__init__.py b/rrompy/reduction_methods/base/trained_model/__init__.py index efb1e3a..ed60590 100644 --- a/rrompy/reduction_methods/base/trained_model/__init__.py +++ b/rrompy/reduction_methods/base/trained_model/__init__.py @@ -1,27 +1,18 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .trained_model import TrainedModel -from .trained_model_data import TrainedModelData - -__all__ = [ - 'TrainedModel', - 'TrainedModelData' - ] - - diff --git a/rrompy/reduction_methods/pivoted/__init__.py b/rrompy/reduction_methods/pivoted/__init__.py index acc9d89..32a578a 100644 --- a/rrompy/reduction_methods/pivoted/__init__.py +++ b/rrompy/reduction_methods/pivoted/__init__.py @@ -1,30 +1,27 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .generic_pivoted_approximant import GenericPivotedApproximant, PODGlobal from .rational_interpolant_pivoted import RationalInterpolantPivoted from .rational_interpolant_greedy_pivoted import RationalInterpolantGreedyPivoted __all__ = [ - 'GenericPivotedApproximant', - 'PODGlobal', 'RationalInterpolantPivoted', 'RationalInterpolantGreedyPivoted' ] diff --git a/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py b/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py index eb516d9..e43b294 100644 --- a/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py +++ b/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py @@ -1,485 +1,486 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from rrompy.reduction_methods.base.generic_approximant import ( GenericApproximant) from rrompy.utilities.poly_fitting.polynomial import polybases as ppb from rrompy.utilities.poly_fitting.radial_basis import polybases as rbpb from rrompy.utilities.poly_fitting.moving_least_squares import ( polybases as mlspb) from rrompy.sampling import (SamplingEnginePivoted, SamplingEnginePivotedPOD, SamplingEnginePivotedPODGlobal) from rrompy.utilities.base.types import paramList, ListAny from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical import reduceDegreeN +from rrompy.utilities.numerical.degree import reduceDegreeN from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) __all__ = ['GenericPivotedApproximant', 'PODGlobal'] PODGlobal = 2 class GenericPivotedApproximant(GenericApproximant): """ ROM pivoted approximant (with pole matching) computation for parametric problems (ABSTRACT). Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. directionPivot(optional): Pivot components. Defaults to [0]. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'cutOffTolerance': tolerance for ignoring parasitic poles; defaults to np.inf; - 'cutOffKind': kind of cut off strategy; available values include 'SOFT' and 'HARD'; defaults to 'HARD'; - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginal': marginal sample point generator; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to 'MONOMIAL'; - 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; - 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; defaults to -1; - 'interpRcondMarginal': tolerance for marginal interpolation; defaults to None. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. directionPivot: Pivot components. mus: Array of snapshot parameters. musMarginal: Array of marginal snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'matchingWeight': weight for pole matching optimization; - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'cutOffKind': kind of cut off strategy; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'MMarginal': degree of marginal interpolant; - 'polydegreetypeMarginal': type of polynomial degree for marginal; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; - 'interpRcondMarginal': tolerance for marginal interpolation. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginal': marginal sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. matchingWeight: Weight for pole matching optimization. cutOffTolerance: Tolerance for ignoring parasitic poles. cutOffKind: Kind of cut off strategy. S: Total number of pivot samples current approximant relies upon. samplerPivot: Pivot sample point generator. SMarginal: Total number of marginal samples current approximant relies upon. samplerMarginal: Marginal sample point generator. polybasisMarginal: Type of polynomial basis for marginal interpolation. MMarginal: Degree of marginal interpolant. polydegreetypeMarginal: Type of polynomial degree for marginal. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. nNearestNeighborMarginal: Number of marginal nearest neighbors considered if polybasisMarginal allows. interpRcondMarginal: Tolerance for marginal interpolation. muBoundsPivot: list of bounds for pivot parameter values. muBoundsMarginal: list of bounds for marginal parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ def __init__(self, directionPivot:ListAny, *args, **kwargs): self._preInit() if len(directionPivot) > 1: raise RROMPyException(("Exactly 1 pivot parameter allowed in pole " "matching.")) from rrompy.parameter.parameter_sampling import QuadratureSampler as QS QSBase = QS([[0], [1]], "UNIFORM") self._addParametersToList(["matchingWeight", "cutOffTolerance", "cutOffKind", "polybasisMarginal", "MMarginal", "polydegreetypeMarginal", "radialDirectionalWeightsMarginal", "nNearestNeighborMarginal", "interpRcondMarginal"], [1, np.inf, "HARD", "MONOMIAL", "AUTO", "TOTAL", [1], -1, -1], ["samplerPivot", "SMarginal", "samplerMarginal"], [QSBase, [1], QSBase]) del QS self._directionPivot = directionPivot super().__init__(*args, **kwargs) self._postInit() @property def tModelType(self): - from .trained_model import TrainedModelPivoted + from .trained_model.trained_model_pivoted import TrainedModelPivoted return TrainedModelPivoted def setupSampling(self): """Setup sampling engine.""" RROMPyAssert(self._mode, message = "Cannot setup sampling engine.") if not hasattr(self, "_POD") or self._POD is None: return if self.POD: if self.POD == PODGlobal: SamplingEngine = SamplingEnginePivotedPODGlobal else: SamplingEngine = SamplingEnginePivotedPOD else: SamplingEngine = SamplingEnginePivoted self.samplingEngine = SamplingEngine(self.HFEngine, self.directionPivot, sample_state = self.approx_state, verbosity = self.verbosity) def initializeModelData(self, datadict): if "directionPivot" in datadict.keys(): - from .trained_model import TrainedModelPivotedData + from .trained_model.trained_model_pivoted_data import ( + TrainedModelPivotedData) return (TrainedModelPivotedData(datadict["mu0"], datadict.pop("projMat"), datadict["scaleFactor"], datadict.pop("rescalingExp"), datadict["directionPivot"]), ["mu0", "scaleFactor", "directionPivot", "mus"]) else: return super().initializeModelData(datadict) @property def npar(self): """Number of parameters.""" if hasattr(self, "_temporaryPivot"): return self.nparPivot return super().npar @property def mus(self): """Value of mus. Its assignment may reset snapshots.""" return self._mus @mus.setter def mus(self, mus): musOld = copy(self.mus) if hasattr(self, '_mus') else None if (musOld is None or len(mus) != len(musOld) or not mus == musOld): self.resetSamples() self._mus = mus @property def matchingWeight(self): """Value of matchingWeight.""" return self._matchingWeight @matchingWeight.setter def matchingWeight(self, matchingWeight): self._matchingWeight = matchingWeight self._approxParameters["matchingWeight"] = self.matchingWeight @property def cutOffTolerance(self): """Value of cutOffTolerance.""" return self._cutOffTolerance @cutOffTolerance.setter def cutOffTolerance(self, cutOffTolerance): self._cutOffTolerance = cutOffTolerance self._approxParameters["cutOffTolerance"] = self.cutOffTolerance @property def cutOffKind(self): """Value of cutOffKind.""" return self._cutOffKind @cutOffKind.setter def cutOffKind(self, cutOffKind): cutOffKind = cutOffKind.upper() if cutOffKind not in ["SOFT", "HARD"]: RROMPyWarning(("Cut off kind not recognized. Overriding to " "'HARD'.")) cutOffKind = "HARD" self._cutOffKind = cutOffKind self._approxParameters["cutOffKind"] = self.cutOffKind @property def SMarginal(self): """Value of SMarginal.""" return self._SMarginal @SMarginal.setter def SMarginal(self, SMarginal): if SMarginal <= 0: raise RROMPyException("SMarginal must be positive.") if hasattr(self, "_SMarginal") and self._SMarginal is not None: Sold = self.SMarginal else: Sold = -1 self._SMarginal = SMarginal self._approxParameters["SMarginal"] = self.SMarginal if Sold != self.SMarginal: self.resetSamples() @property def polybasisMarginal(self): """Value of polybasisMarginal.""" return self._polybasisMarginal @polybasisMarginal.setter def polybasisMarginal(self, polybasisMarginal): try: polybasisMarginal = polybasisMarginal.upper().strip().replace(" ", "") if polybasisMarginal not in ppb + rbpb + mlspb: raise RROMPyException( "Prescribed marginal polybasis not recognized.") self._polybasisMarginal = polybasisMarginal except: RROMPyWarning(("Prescribed marginal polybasis not recognized. " "Overriding to 'MONOMIAL'.")) self._polybasisMarginal = "MONOMIAL" self._approxParameters["polybasisMarginal"] = self.polybasisMarginal @property def MMarginal(self): """Value of MMarginal.""" return self._MMarginal @MMarginal.setter def MMarginal(self, MMarginal): if isinstance(MMarginal, str): MMarginal = MMarginal.strip().replace(" ","") if "-" not in MMarginal: MMarginal = MMarginal + "-0" self._MMarginal_isauto = True self._MMarginal_shift = int(MMarginal.split("-")[-1]) MMarginal = 0 if MMarginal < 0: raise RROMPyException("MMarginal must be non-negative.") self._MMarginal = MMarginal self._approxParameters["MMarginal"] = self.MMarginal def _setMMarginalAuto(self): self.MMarginal = max(0, reduceDegreeN( len(self.musMarginal), len(self.musMarginal), self.nparMarginal, self.polydegreetypeMarginal ) - self._MMarginal_shift) vbMng(self, "MAIN", ("Automatically setting MMarginal to " "{}.").format(self.MMarginal), 25) @property def polydegreetypeMarginal(self): """Value of polydegreetypeMarginal.""" return self._polydegreetypeMarginal @polydegreetypeMarginal.setter def polydegreetypeMarginal(self, polydegreetypeM): try: polydegreetypeM = polydegreetypeM.upper().strip().replace(" ","") if polydegreetypeM not in ["TOTAL", "FULL"]: raise RROMPyException(("Prescribed polydegreetypeMarginal not " "recognized.")) self._polydegreetypeMarginal = polydegreetypeM except: RROMPyWarning(("Prescribed polydegreetypeMarginal not recognized. " "Overriding to 'TOTAL'.")) self._polydegreetypeMarginal = "TOTAL" self._approxParameters["polydegreetypeMarginal"] = ( self.polydegreetypeMarginal) @property def radialDirectionalWeightsMarginal(self): """Value of radialDirectionalWeightsMarginal.""" return self._radialDirectionalWeightsMarginal @radialDirectionalWeightsMarginal.setter def radialDirectionalWeightsMarginal(self, radialDirWeightsMarginal): if not hasattr(radialDirWeightsMarginal, "__len__"): radialDirWeightsMarginal = [radialDirWeightsMarginal] self._radialDirectionalWeightsMarginal = radialDirWeightsMarginal self._approxParameters["radialDirectionalWeightsMarginal"] = ( self.radialDirectionalWeightsMarginal) @property def nNearestNeighborMarginal(self): """Value of nNearestNeighborMarginal.""" return self._nNearestNeighborMarginal @nNearestNeighborMarginal.setter def nNearestNeighborMarginal(self, nNearestNeighborMarginal): self._nNearestNeighborMarginal = nNearestNeighborMarginal self._approxParameters["nNearestNeighborMarginal"] = ( self.nNearestNeighborMarginal) @property def interpRcondMarginal(self): """Value of interpRcondMarginal.""" return self._interpRcondMarginal @interpRcondMarginal.setter def interpRcondMarginal(self, interpRcondMarginal): self._interpRcondMarginal = interpRcondMarginal self._approxParameters["interpRcondMarginal"] = ( self.interpRcondMarginal) @property def directionPivot(self): """Value of directionPivot. Its assignment may reset snapshots.""" return self._directionPivot @directionPivot.setter def directionPivot(self, directionPivot): if hasattr(self, '_directionPivot'): directionPivotOld = copy(self.directionPivot) else: directionPivotOld = None if (directionPivotOld is None or len(directionPivot) != len(directionPivotOld) or not directionPivot == directionPivotOld): self.resetSamples() self._directionPivot = directionPivot @property def directionMarginal(self): return [x for x in range(self.HFEngine.npar) \ if x not in self.directionPivot] @property def nparPivot(self): return len(self.directionPivot) @property def nparMarginal(self): return self.npar - self.nparPivot @property def rescalingExpPivot(self): return [self.HFEngine.rescalingExp[x] for x in self.directionPivot] @property def rescalingExpMarginal(self): return [self.HFEngine.rescalingExp[x] for x in self.directionMarginal] @property def muBoundsPivot(self): """Value of muBoundsPivot.""" return self.samplerPivot.lims @property def muBoundsMarginal(self): """Value of muBoundsMarginal.""" return self.samplerMarginal.lims @property def samplerPivot(self): """Value of samplerPivot.""" return self._samplerPivot @samplerPivot.setter def samplerPivot(self, samplerPivot): if 'generatePoints' not in dir(samplerPivot): raise RROMPyException("Pivot sampler type not recognized.") if hasattr(self, '_samplerPivot') and self._samplerPivot is not None: samplerOld = self.samplerPivot self._samplerPivot = samplerPivot self._approxParameters["samplerPivot"] = self.samplerPivot.__str__() if not 'samplerOld' in locals() or samplerOld != self.samplerPivot: self.resetSamples() @property def samplerMarginal(self): """Value of samplerMarginal.""" return self._samplerMarginal @samplerMarginal.setter def samplerMarginal(self, samplerMarginal): if 'generatePoints' not in dir(samplerMarginal): raise RROMPyException("Marginal sampler type not recognized.") if (hasattr(self, '_samplerMarginal') and self._samplerMarginal is not None): samplerOld = self.samplerMarginal self._samplerMarginal = samplerMarginal self._approxParameters["samplerMarginal"] = ( self.samplerMarginal.__str__()) if not 'samplerOld' in locals() or samplerOld != self.samplerMarginal: self.resetSamples() def setSamples(self, samplingEngine): """Copy samplingEngine and samples.""" self.mus = copy(samplingEngine.mus[0]) for sEj in samplingEngine.mus[1:]: self.mus.append(sEj) super().setSamples(samplingEngine) def _finalizeMarginalization(self): vbMng(self, "INIT", "Recompressing by cut off.", 10) msg = self.trainedModel.recompressByCutOff( self.cutOffTolerance, self.cutOffKind, self.samplerPivot.normalFoci(), self.samplerPivot.groundPotential()) vbMng(self, "DEL", "Done recompressing." + msg, 10) interpPars = [self.verbosity >= 5, self.polydegreetypeMarginal == "TOTAL", {}] if self.polybasisMarginal not in ppb: interpPars[-1]["nNearestNeighbor"] = self.nNearestNeighborMarginal if self.polybasisMarginal in ppb + rbpb: interpPars += [{"rcond": self.interpRcondMarginal}] self.trainedModel.setupMarginalInterp(self, interpPars, hasattr(self, "_MMarginal_isauto"), self.radialDirectionalWeightsMarginal, hasattr(self, "_reduceDegreeNNoWarn")) self.trainedModel.data.approxParameters = copy(self.approxParameters) def computeScaleFactor(self): """Compute parameter rescaling factor.""" RROMPyAssert(self._mode, message = "Cannot compute rescaling factor.") self.scaleFactorPivot = .5 * np.abs( self.muBoundsPivot[0] ** self.rescalingExpPivot - self.muBoundsPivot[1] ** self.rescalingExpPivot) self.scaleFactorMarginal = .5 * np.abs( self.muBoundsMarginal[0] ** self.rescalingExpMarginal - self.muBoundsMarginal[1] ** self.rescalingExpMarginal) self.scaleFactor = np.empty(self.npar) self.scaleFactor[self.directionPivot] = self.scaleFactorPivot self.scaleFactor[self.directionMarginal] = self.scaleFactorMarginal def normApprox(self, mu:paramList) -> float: _PODOld = self.POD self._POD = self.POD == PODGlobal result = super().normApprox(mu) self._POD = _PODOld return result - \ No newline at end of file + diff --git a/rrompy/reduction_methods/pivoted/greedy/__init__.py b/rrompy/reduction_methods/pivoted/greedy/__init__.py index 5d473ca..2caf41a 100644 --- a/rrompy/reduction_methods/pivoted/greedy/__init__.py +++ b/rrompy/reduction_methods/pivoted/greedy/__init__.py @@ -1,29 +1,27 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .generic_pivoted_greedy_approximant import GenericPivotedGreedyApproximant from .rational_interpolant_pivoted_greedy import RationalInterpolantPivotedGreedy from .rational_interpolant_greedy_pivoted_greedy import RationalInterpolantGreedyPivotedGreedy __all__ = [ - 'GenericPivotedGreedyApproximant', 'RationalInterpolantPivotedGreedy', 'RationalInterpolantGreedyPivotedGreedy' ] diff --git a/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py b/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py index 327ce3b..6286694 100644 --- a/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py +++ b/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py @@ -1,454 +1,454 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from matplotlib import pyplot as plt -from rrompy.reduction_methods.pivoted import (GenericPivotedApproximant, - PODGlobal) +from rrompy.reduction_methods.pivoted.generic_pivoted_approximant import ( + GenericPivotedApproximant, PODGlobal) from rrompy.utilities.base.types import Np1D, Tuple, List, paramVal, paramList from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical import (pointMatching, chordalMetricAdjusted, - potential) +from rrompy.utilities.numerical.point_matching import (pointMatching, + chordalMetricAdjusted, potential) from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) from rrompy.parameter import checkParameterList, emptyParameterList __all__ = ['GenericPivotedGreedyApproximant'] class GenericPivotedGreedyApproximant(GenericPivotedApproximant): """ ROM pivoted greedy interpolant computation for parametric problems (ABSTRACT). Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. directionPivot(optional): Pivot components. Defaults to [0]. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'cutOffTolerance': tolerance for ignoring parasitic poles; defaults to np.inf; - 'cutOffKind': kind of cut off strategy; available values include 'SOFT' and 'HARD'; defaults to 'HARD'; - 'matchingWeightError': weight for pole matching optimization in error estimation; defaults to 0; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; defaults to 'AUTO', i.e. cutOffTolerance; - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': number of starting marginal samples; - 'samplerMarginalGrid': marginal sample point generator via sparse grid; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to 'MONOMIAL'; - 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; defaults to -1; - 'interpRcondMarginal': tolerance for marginal interpolation; defaults to None. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. directionPivot: Pivot components. mus: Array of snapshot parameters. musMarginal: Array of marginal snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'matchingWeight': weight for pole matching optimization; - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'cutOffKind': kind of cut off strategy; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'MMarginal': degree of marginal interpolant; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'polydegreetypeMarginal': type of polynomial degree for marginal; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; - 'interpRcondMarginal': tolerance for marginal interpolation. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginalGrid': marginal sample point generator via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. matchingWeight: Weight for pole matching optimization. cutOffTolerance: Tolerance for ignoring parasitic poles. cutOffKind: Kind of cut off strategy. matchingWeightError: Weight for pole matching optimization in error estimation. cutOffToleranceError: Tolerance for ignoring parasitic poles in error estimation. S: Total number of pivot samples current approximant relies upon. samplerPivot: Pivot sample point generator. SMarginal: Total number of marginal samples current approximant relies upon. samplerMarginalGrid: Marginal sample point generator via sparse grid. polybasisMarginal: Type of polynomial basis for marginal interpolation. MMarginal: Degree of marginal interpolant. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. polydegreetypeMarginal: Type of polynomial degree for marginal. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. nNearestNeighborMarginal: Number of marginal nearest neighbors considered if polybasisMarginal allows. interpRcondMarginal: Tolerance for marginal interpolation. muBoundsPivot: list of bounds for pivot parameter values. muBoundsMarginal: list of bounds for marginal parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ def __init__(self, *args, **kwargs): self._preInit() from rrompy.parameter import localSparseGrid as SG SGBase = SG([[0.], [1.]], "UNIFORM") self._addParametersToList(["matchingWeightError", "cutOffToleranceError", "greedyTolMarginal", "maxIterMarginal"], [0., "AUTO", 1e-1, 1e2], ["samplerMarginalGrid"], [SGBase], toBeExcluded = ["samplerMarginal"]) super().__init__(*args, **kwargs) self._postInit() @property def muBoundsMarginal(self): """Value of muBoundsMarginal.""" return self.samplerMarginalGrid.lims @property def samplerMarginalGrid(self): """Value of samplerMarginalGrid.""" return self._samplerMarginalGrid @samplerMarginalGrid.setter def samplerMarginalGrid(self, samplerMarginalGrid): if 'refine' not in dir(samplerMarginalGrid): raise RROMPyException("Marginal sampler type not recognized.") if (hasattr(self, '_samplerMarginalGrid') and self._samplerMarginalGrid is not None): samplerOld = self.samplerMarginalGrid self._samplerMarginalGrid = samplerMarginalGrid self._approxParameters["samplerMarginalGrid"] = ( self.samplerMarginalGrid.__str__()) if (not 'samplerOld' in locals() or samplerOld != self.samplerMarginalGrid): self.resetSamples() @property def matchingWeightError(self): """Value of matchingWeightError.""" return self._matchingWeightError @matchingWeightError.setter def matchingWeightError(self, matchingWeightError): self._matchingWeightError = matchingWeightError self._approxParameters["matchingWeightError"] = ( self.matchingWeightError) @property def cutOffToleranceError(self): """Value of cutOffToleranceError.""" return self._cutOffToleranceError @cutOffToleranceError.setter def cutOffToleranceError(self, cutOffToleranceError): if isinstance(cutOffToleranceError, (str,)): cutOffToleranceError = cutOffToleranceError.upper()\ .strip().replace(" ","") if cutOffToleranceError != "AUTO": RROMPyWarning(("String value of cutOffToleranceError not " "recognized. Overriding to 'AUTO'.")) cutOffToleranceError == "AUTO" self._cutOffToleranceError = cutOffToleranceError self._approxParameters["cutOffToleranceError"] = ( self.cutOffToleranceError) @property def greedyTolMarginal(self): """Value of greedyTolMarginal.""" return self._greedyTolMarginal @greedyTolMarginal.setter def greedyTolMarginal(self, greedyTolMarginal): if greedyTolMarginal < 0: raise RROMPyException("greedyTolMarginal must be non-negative.") if (hasattr(self, "_greedyTolMarginal") and self.greedyTolMarginal is not None): greedyTolMarginalold = self.greedyTolMarginal else: greedyTolMarginalold = -1 self._greedyTolMarginal = greedyTolMarginal self._approxParameters["greedyTolMarginal"] = self.greedyTolMarginal if greedyTolMarginalold != self.greedyTolMarginal: self.resetSamples() @property def maxIterMarginal(self): """Value of maxIterMarginal.""" return self._maxIterMarginal @maxIterMarginal.setter def maxIterMarginal(self, maxIterMarginal): if maxIterMarginal <= 0: raise RROMPyException("maxIterMarginal must be positive.") if (hasattr(self, "_maxIterMarginal") and self.maxIterMarginal is not None): maxIterMarginalold = self.maxIterMarginal else: maxIterMarginalold = -1 self._maxIterMarginal = maxIterMarginal self._approxParameters["maxIterMarginal"] = self.maxIterMarginal if maxIterMarginalold != self.maxIterMarginal: self.resetSamples() def resetSamples(self): """Reset samples.""" super().resetSamples() if not hasattr(self, "_temporaryPivot"): self._mus = emptyParameterList() self.musMarginal = emptyParameterList() if hasattr(self, "samplerMarginalGrid"): self.samplerMarginalGrid.reset() if hasattr(self, "samplingEngine") and self.samplingEngine is not None: self.samplingEngine.resetHistory() def errorEstimatorMarginal(self, return_max : bool = False) -> Np1D: vbMng(self, "INIT", "Matching poles.", 10) self.trainedModel.initializeFromRational( self.HFEngine, self.matchingWeight, self.POD == PODGlobal, self.approx_state) vbMng(self, "DEL", "Done matching poles.", 10) self._finalizeMarginalization() _tMdataFull = copy(self.trainedModel.data) vbMng(self.trainedModel, "INIT", "Evaluating error estimator at mu = {}.".format( self.trainedModel.data.musMarginal), 10) err = np.zeros(len(self.trainedModel.data.musMarginal)) if len(err) <= 1: err[:] = np.inf else: if self.cutOffToleranceError == "AUTO": cutOffTolErr = self.cutOffTolerance else: cutOffTolErr = self.cutOffToleranceError if not hasattr(self, "_MMarginal_isauto"): if not hasattr(self, "_MMarginalOriginal"): self._MMarginalOriginal = self.MMarginal self.MMarginal = self._MMarginalOriginal _musMExcl = None self.verbosity -= 35 self.trainedModel.verbosity -= 35 foci = self.samplerPivot.normalFoci() ground = self.samplerPivot.groundPotential() for j in range(len(err)): jEff = j - (j > 0) muTest = self.trainedModel.data.musMarginal[jEff] polesEx = self.trainedModel.data.HIs[jEff].poles idxExEff = np.where(potential(polesEx, foci) - ground <= cutOffTolErr * ground)[0] polesEx = polesEx[idxExEff] if self.matchingWeightError != 0: resEx = self.trainedModel.data.HIs[jEff].coeffs[idxExEff] else: resEx = None if j > 0: self.musMarginal.insert(_musMExcl, j - 1) _musMExcl = self.musMarginal[j] self.musMarginal.pop(j) if len(polesEx) == 0: continue self.trainedModel.updateEffectiveSamples( self.HFEngine, [j], self.matchingWeight, self.POD == PODGlobal, self.approx_state) self._reduceDegreeNNoWarn = 1 self._finalizeMarginalization() polesAp = self.trainedModel.interpolateMarginalPoles(muTest)[ ..., 0] idxApEff = np.where(potential(polesAp, foci) - ground <= cutOffTolErr * ground)[0] polesAp = polesAp[idxApEff] if self.matchingWeightError != 0: resAp = self.trainedModel.interpolateMarginalCoeffs( muTest)[idxApEff, :, 0] if self.POD != PODGlobal: resEx = self.trainedModel.data.projMat.dot(resEx.T) resAp = self.trainedModel.data.projMat.dot(resAp.T) else: resAp = None dist = chordalMetricAdjusted( polesEx, polesAp, self.matchingWeightError, resEx, resAp, self.HFEngine, self.approx_state) pmR, pmC = pointMatching(dist) err[j] = np.mean(dist[pmR, pmC]) self.trainedModel.updateEffectiveSamples(self.HFEngine, None, self.matchingWeight, self.POD == PODGlobal, self.approx_state) if not hasattr(self, "_MMarginal_isauto"): self.MMarginal = self._MMarginalOriginal self.musMarginal.append(_musMExcl) self.verbosity += 35 self.trainedModel.verbosity += 35 self.trainedModel.data = _tMdataFull del self._reduceDegreeNNoWarn vbMng(self.trainedModel, "DEL", "Done evaluating error estimator", 10) if not return_max: return err idxMaxEst = np.where(err > self.greedyTolMarginal)[0] return err, idxMaxEst, err[idxMaxEst] def plotEstimatorMarginal(self, est:Np1D, idxMax:List[int], estMax:List[float]): if not (np.any(np.isnan(est)) or np.any(np.isinf(est))): fig = plt.figure(figsize = plt.figaspect(1. / self.nparMarginal)) for jpar in range(self.nparMarginal): ax = fig.add_subplot(1, self.nparMarginal, 1 + jpar) musre = copy(self.trainedModel.data.musMarginal.re.data) errCP = copy(est) idx = np.delete(np.arange(self.nparMarginal), jpar) while len(musre) > 0: if self.nparMarginal == 1: currIdx = np.arange(len(musre)) else: currIdx = np.where(np.isclose(np.sum( np.abs(musre[:, idx] - musre[0, idx]), 1), 0.))[0] currIdxSorted = currIdx[np.argsort(musre[currIdx, jpar])] ax.semilogy(musre[currIdxSorted, jpar], errCP[currIdxSorted], 'k.-', linewidth = 1) musre = np.delete(musre, currIdx, 0) errCP = np.delete(errCP, currIdx) ax.semilogy(self.musMarginal.re(jpar), (self.greedyTolMarginal,) * len(self.musMarginal), '*m') if len(idxMax) > 0 and estMax is not None: ax.semilogy(self.trainedModel.data.musMarginal.re( idxMax, jpar), estMax, 'xr') ax.grid() plt.tight_layout() plt.show() def _addMarginalSample(self, mus:paramList): mus = checkParameterList(mus, self.nparMarginal)[0] if len(mus) == 0: return nmus = len(mus) vbMng(self, "MAIN", ("Adding marginal sample point{} no. {}{} at {} to training " "set.").format("s" * (nmus > 1), len(self.musMarginal) + 1, "--{}".format(len(self.musMarginal) + nmus) * (nmus > 1), mus), 2) self.musMarginal.append(mus) self.setupApproxPivoted(mus) self._SMarginal = len(self.musMarginal) self._approxParameters["SMarginal"] = self.SMarginal def greedyNextSampleMarginal(self, muidx:int, plotEst : str = "NONE") \ -> Tuple[Np1D, int, float, paramVal]: RROMPyAssert(self._mode, message = "Cannot add greedy sample.") idxAdded = self.samplerMarginalGrid.refine(muidx) self._addMarginalSample(self.samplerMarginalGrid.points[idxAdded]) errorEstTest, muidx, maxErrorEst = self.errorEstimatorMarginal(True) if plotEst == "ALL": self.plotEstimatorMarginal(errorEstTest, muidx, maxErrorEst) return (errorEstTest, muidx, maxErrorEst, self.samplerMarginalGrid.points[muidx]) def _preliminaryTrainingMarginal(self): """Initialize starting snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") if np.sum(self.samplingEngine.nsamples) > 0: return self.resetSamples() idx = [0] while self.samplerMarginalGrid.npoints < self.SMarginal: idx = self.samplerMarginalGrid.refine(idx) self._addMarginalSample(self.samplerMarginalGrid.points) def setupApproxPivoted(self, mu:paramVal) -> int: if self.checkComputedApproxPivoted(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") raise RROMPyException("Must override.") def setupApprox(self, plotEst : str = "NONE") -> int: """Compute greedy snapshots of solution map.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") vbMng(self, "INIT", "Starting computation of snapshots.", 2) self._preliminaryTrainingMarginal() muidx, max2ErrorEst, firstGreedyIter = [], np.inf, True while firstGreedyIter or (max2ErrorEst > self.greedyTolMarginal and self.samplerMarginalGrid.npoints < self.maxIterMarginal): errorEstTest, muidx, maxErrorEst, mu = \ self.greedyNextSampleMarginal(muidx, plotEst) if len(maxErrorEst) > 0: max2ErrorEst = np.max(maxErrorEst) vbMng(self, "MAIN", ("Uniform testing error estimate " "{:.4e}.").format(max2ErrorEst), 2) else: max2ErrorEst = 0. firstGreedyIter = False if plotEst == "LAST": self.plotEstimatorMarginal(errorEstTest, muidx, maxErrorEst) vbMng(self, "DEL", ("Done computing snapshots (final snapshot count: " "{}).").format(np.sum(self.samplingEngine.nsamples)), 2) return 0 def checkComputedApprox(self) -> bool: return (super().checkComputedApprox() and len(self.mus) == len(self.trainedModel.data.mus)) def checkComputedApproxPivoted(self) -> bool: return (super().checkComputedApprox() and len(self.musMarginal) == len(self.trainedModel.data.musMarginal)) diff --git a/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_greedy_pivoted_greedy.py b/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_greedy_pivoted_greedy.py index 842719a..ee7c925 100644 --- a/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_greedy_pivoted_greedy.py +++ b/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_greedy_pivoted_greedy.py @@ -1,373 +1,374 @@ #Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .generic_pivoted_greedy_approximant import GenericPivotedGreedyApproximant from rrompy.utilities.numerical import dot from rrompy.reduction_methods.standard.greedy import RationalInterpolantGreedy from rrompy.reduction_methods.standard.greedy.generic_greedy_approximant \ import pruneSamples -from rrompy.reduction_methods.pivoted import (RationalInterpolantGreedyPivoted, - PODGlobal) +from rrompy.reduction_methods.pivoted import RationalInterpolantGreedyPivoted +from rrompy.reduction_methods.pivoted.generic_pivoted_approximant import ( + PODGlobal) from rrompy.utilities.base.types import Np1D, Tuple, paramVal, paramList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.parameter import emptyParameterList __all__ = ['RationalInterpolantGreedyPivotedGreedy'] class RationalInterpolantGreedyPivotedGreedy(GenericPivotedGreedyApproximant, RationalInterpolantGreedyPivoted): """ ROM greedy pivoted greedy rational interpolant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. directionPivot(optional): Pivot components. Defaults to [0]. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'cutOffTolerance': tolerance for ignoring parasitic poles; defaults to np.inf; - 'cutOffKind': kind of cut off strategy; available values include 'SOFT' and 'HARD'; defaults to 'HARD'; - 'matchingWeightError': weight for pole matching optimization in error estimation; defaults to 0; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; defaults to 'AUTO', i.e. cutOffTolerance; - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': number of starting marginal samples; - 'samplerMarginalGrid': marginal sample point generator via sparse grid; - 'polybasis': type of polynomial basis for pivot interpolation; defaults to 'MONOMIAL'; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to 'MONOMIAL'; - 'greedyTol': uniform error tolerance for greedy algorithm; defaults to 1e-2; - 'collinearityTol': collinearity tolerance for greedy algorithm; defaults to 0.; - 'maxIter': maximum number of greedy steps; defaults to 1e2; - 'nTestPoints': number of test points; defaults to 5e2; - 'trainSetGenerator': training sample points generator; defaults to sampler; - 'errorEstimatorKind': kind of error estimator; available values include 'AFFINE', 'DISCREPANCY', 'INTERPOLATORY', 'LOOK_AHEAD', and 'NONE'; defaults to 'NONE'; - 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; defaults to -1; - 'interpRcond': tolerance for pivot interpolation; defaults to None; - 'interpRcondMarginal': tolerance for marginal interpolation; defaults to None; - 'robustTol': tolerance for robust rational denominator management; defaults to 0. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. directionPivot: Pivot components. mus: Array of snapshot parameters. musMarginal: Array of marginal snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'matchingWeight': weight for pole matching optimization; - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'cutOffKind': kind of cut off strategy; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'polybasis': type of polynomial basis for pivot interpolation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'greedyTol': uniform error tolerance for greedy algorithm; - 'collinearityTol': collinearity tolerance for greedy algorithm; - 'maxIter': maximum number of greedy steps; - 'nTestPoints': number of test points; - 'trainSetGenerator': training sample points generator; - 'errorEstimatorKind': kind of error estimator; - 'MMarginal': degree of marginal interpolant; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'polydegreetypeMarginal': type of polynomial degree for marginal; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; - 'interpRcond': tolerance for pivot interpolation; - 'interpRcondMarginal': tolerance for marginal interpolation; - 'robustTol': tolerance for robust rational denominator management. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginalGrid': marginal sample point generator via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. matchingWeight: Weight for pole matching optimization. cutOffTolerance: Tolerance for ignoring parasitic poles. cutOffKind: Kind of cut off strategy. matchingWeightError: Weight for pole matching optimization in error estimation. cutOffToleranceError: Tolerance for ignoring parasitic poles in error estimation. S: Total number of pivot samples current approximant relies upon. samplerPivot: Pivot sample point generator. SMarginal: Total number of marginal samples current approximant relies upon. samplerMarginalGrid: Marginal sample point generator via sparse grid. polybasis: Type of polynomial basis for pivot interpolation. polybasisMarginal: Type of polynomial basis for marginal interpolation. greedyTol: uniform error tolerance for greedy algorithm. collinearityTol: Collinearity tolerance for greedy algorithm. maxIter: maximum number of greedy steps. nTestPoints: number of starting training points. trainSetGenerator: training sample points generator. errorEstimatorKind: kind of error estimator. MMarginal: Degree of marginal interpolant. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. polydegreetypeMarginal: Type of polynomial degree for marginal. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. nNearestNeighborMarginal: Number of marginal nearest neighbors considered if polybasisMarginal allows. interpRcond: Tolerance for pivot interpolation. interpRcondMarginal: Tolerance for marginal interpolation. robustTol: Tolerance for robust rational denominator management. muBoundsPivot: list of bounds for pivot parameter values. muBoundsMarginal: list of bounds for marginal parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ @property def sampleBatchSize(self): """Value of sampleBatchSize.""" return 1 @property def sampleBatchIdx(self): """Value of sampleBatchIdx.""" return self.S def _finalizeSnapshots(self): self.samplingEngine = self._samplingEngineOld for muM, sEN in zip(self.musMargLoc, self.samplingEngs): self.samplingEngine.samples += [sEN.samples] self.samplingEngine.nsamples += [sEN.nsamples] self.samplingEngine.mus += [sEN.mus] self.samplingEngine.musMarginal.append(muM) self.samplingEngine._derIdxs += [[(0,) * self.npar] for _ in range(sEN.nsamples)] if self.POD: self.samplingEngine.RPOD += [sEN.RPOD] self.samplingEngine.samples_full += [copy(sEN.samples_full)] if self.POD == PODGlobal: self.samplingEngine.coalesceSamples(self.interpRcondMarginal) else: self.samplingEngine.coalesceSamples() def greedyNextSample(self, muidx:int, plotEst : str = "NONE")\ -> Tuple[Np1D, int, float, paramVal]: """Compute next greedy snapshot of solution map.""" RROMPyAssert(self._mode, message = "Cannot add greedy sample.") mus = copy(self.muTest[muidx]) self.muTest.pop(muidx) for j, mu in enumerate(mus): vbMng(self, "MAIN", ("Adding sample point no. {} at {} to training " "set.").format(len(self.mus) + 1, mu), 2) self.mus.append(mu) self._S = len(self.mus) self._approxParameters["S"] = self.S if (self.samplingEngine.nsamples <= len(mus) - j - 1 or not np.allclose(mu, self.samplingEngine.mus.data[j - len(mus)])): self.samplingEngine.nextSample(mu) if self._isLastSampleCollinear(): vbMng(self, "MAIN", ("Collinearity above tolerance detected. Starting " "preemptive greedy loop termination."), 2) self._collinearityFlag = 1 errorEstTest = np.empty(len(self.muTest)) errorEstTest[:] = np.nan return errorEstTest, [-1], np.nan, np.nan errorEstTest, muidx, maxErrorEst = self.errorEstimator(self.muTest, True) if plotEst == "ALL": self.plotEstimator(errorEstTest, muidx, maxErrorEst) return errorEstTest, muidx, maxErrorEst, self.muTest[muidx] def _preliminaryTraining(self): """Initialize starting snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") if self.samplingEngine.nsamples > 0: return self.resetSamples() musPivot = self.trainSetGenerator.generatePoints(self.S) while len(musPivot) > self.S: musPivot.pop() muTestBasePivot = self.samplerPivot.generatePoints(self.nTestPoints, False) idxPop = pruneSamples( muTestBasePivot ** self.HFEngine.rescalingExp[self.directionPivot[0]], musPivot ** self.HFEngine.rescalingExp[self.directionPivot[0]], 1e-10 * self.scaleFactor[0]) muTestBasePivot.pop(idxPop) self.mus = emptyParameterList() self.mus.reset((self.S - 1, self.HFEngine.npar)) self.muTest = emptyParameterList() self.muTest.reset((len(muTestBasePivot) + 1, self.HFEngine.npar)) for k in range(self.S - 1): self.mus.data[k, self.directionPivot] = musPivot[k].data self.mus.data[k, self.directionMarginal] = self.musMargLoc[-1].data for k in range(len(muTestBasePivot)): self.muTest.data[k, self.directionPivot] = muTestBasePivot[k].data self.muTest.data[k, self.directionMarginal] = ( self.musMargLoc[-1].data) self.muTest.data[-1, self.directionPivot] = musPivot[-1].data self.muTest.data[-1, self.directionMarginal] = self.musMargLoc[-1].data if len(self.mus) > 0: vbMng(self, "MAIN", ("Adding first {} sample point{} at {} to training " "set.").format(self.S - 1, "" + "s" * (self.S > 2), self.mus), 2) self.samplingEngine.iterSample(self.mus) self._S = len(self.mus) self._approxParameters["S"] = self.S def setupApproxPivoted(self, mus:paramList) -> int: if self.checkComputedApproxPivoted(): return -1 if not hasattr(self, "_plotEstPivot"): self._plotEstPivot = "NONE" RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 10) self.computeScaleFactor() if self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": np.zeros((0, 0)), "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp, "directionPivot": self.directionPivot} self.trainedModel.data = self.initializeModelData(datadict)[0] self.trainedModel.data.Qs, self.trainedModel.data.Ps = [], [] _trainedModelOld = copy(self.trainedModel) self._scaleFactorOldPivot = copy(self.scaleFactor) self.scaleFactor = self.scaleFactorPivot self._temporaryPivot = 1 self._samplingEngineOld = copy(self.samplingEngine) self.musMargLoc, self.samplingEngs = [], [None] * len(mus) Qs, Ps = [None] * len(mus), [None] * len(mus) self.verbosity -= 15 S0 = copy(self.S) for j, mu in enumerate(mus): RationalInterpolantGreedy.setupSampling(self) self.trainedModel = None self.musMargLoc += [mu] RationalInterpolantGreedy.setupApprox(self, self._plotEstPivot) self.samplingEngs[j] = copy(self.samplingEngine) Qs[j] = copy(self.trainedModel.data.Q) Ps[j] = copy(self.trainedModel.data.P) self._S = S0 self.scaleFactor = self._scaleFactorOldPivot del self._scaleFactorOldPivot, self._temporaryPivot self._finalizeSnapshots() del self._samplingEngineOld, self.musMargLoc, self.samplingEngs self._mus = self.samplingEngine.musCoalesced self.trainedModel = _trainedModelOld self.trainedModel.data.mus = copy(self.mus) self.trainedModel.data.musMarginal = copy(self.musMarginal) padRight = (self.samplingEngine.nsamplesTot - self.trainedModel.data.projMat.shape[1]) nmusOld = len(self.trainedModel.data.Ps) for j in range(nmusOld): nsj = self.samplingEngine.nsamples[j] self.trainedModel.data.Ps[j].pad(0, padRight) self.trainedModel.data.HIs[j].pad(0, padRight) padLeft = self.trainedModel.data.projMat.shape[1] for j in range(len(mus)): nsj = self.samplingEngine.nsamples[nmusOld + j] if self.POD == PODGlobal: rRightj = self.samplingEngine.RPODCPart[:, padLeft : padLeft + nsj] Ps[j].postmultiplyTensorize(rRightj.T) else: padRight -= nsj Ps[j].pad(padLeft, padRight) padLeft += nsj pMat = self.samplingEngine.samplesCoalesced.data pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat self.trainedModel.data.projMat = pMatEff self.trainedModel.data.Qs += Qs self.trainedModel.data.Ps += Ps self.trainedModel.data.approxParameters = copy(self.approxParameters) self.verbosity += 15 vbMng(self, "DEL", "Done setting up approximant.", 10) return 0 def setupApprox(self, plotEst : str = "NONE") -> int: if self.checkComputedApprox(): return -1 if '_' not in plotEst: plotEst = plotEst + "_NONE" plotEstM, self._plotEstPivot = plotEst.split("_") val = super().setupApprox(plotEstM) return val - \ No newline at end of file + diff --git a/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py b/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py index 2dd56e8..64ac2f9 100644 --- a/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py +++ b/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py @@ -1,319 +1,320 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .generic_pivoted_greedy_approximant import GenericPivotedGreedyApproximant from rrompy.utilities.numerical import dot from rrompy.reduction_methods.standard import RationalInterpolant -from rrompy.reduction_methods.pivoted import (RationalInterpolantPivoted, - PODGlobal) +from rrompy.reduction_methods.pivoted import RationalInterpolantPivoted +from rrompy.reduction_methods.pivoted.generic_pivoted_approximant import ( + PODGlobal) from rrompy.utilities.base.types import paramList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.parameter import checkParameterList, emptyParameterList __all__ = ['RationalInterpolantPivotedGreedy'] class RationalInterpolantPivotedGreedy(GenericPivotedGreedyApproximant, RationalInterpolantPivoted): """ ROM pivoted greedy rational interpolant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. directionPivot(optional): Pivot components. Defaults to [0]. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'cutOffTolerance': tolerance for ignoring parasitic poles; defaults to np.inf; - 'cutOffKind': kind of cut off strategy; available values include 'SOFT' and 'HARD'; defaults to 'HARD'; - 'matchingWeightError': weight for pole matching optimization in error estimation; defaults to 0; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; defaults to 'AUTO', i.e. cutOffTolerance; - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': number of starting marginal samples; - 'samplerMarginalGrid': marginal sample point generator via sparse grid; - 'polybasis': type of polynomial basis for pivot interpolation; defaults to 'MONOMIAL'; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to 'MONOMIAL'; - 'M': degree of rational interpolant numerator; defaults to 'AUTO', i.e. maximum allowed; - 'N': degree of rational interpolant denominator; defaults to 'AUTO', i.e. maximum allowed; - 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; - 'radialDirectionalWeights': radial basis weights for pivot numerator; defaults to 1; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'nNearestNeighbor': number of pivot nearest neighbors considered if polybasis allows; defaults to -1; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; defaults to -1; - 'interpRcond': tolerance for pivot interpolation; defaults to None; - 'interpRcondMarginal': tolerance for marginal interpolation; defaults to None; - 'robustTol': tolerance for robust rational denominator management; defaults to 0; - 'correctorForce': whether corrector should forcefully delete bad poles; defaults to False; - 'correctorTol': tolerance for corrector step; defaults to 0., i.e. no bad poles; - 'correctorMaxIter': maximum number of corrector iterations; defaults to 1. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. directionPivot: Pivot components. mus: Array of snapshot parameters. musPivot: Array of pivot snapshot parameters. musMarginal: Array of marginal snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'matchingWeight': weight for pole matching optimization; - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'cutOffKind': kind of cut off strategy; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'polybasis': type of polynomial basis for pivot interpolation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'MMarginal': degree of marginal interpolant; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'polydegreetypeMarginal': type of polynomial degree for marginal; - 'radialDirectionalWeights': radial basis weights for pivot numerator; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'nNearestNeighbor': number of pivot nearest neighbors considered if polybasis allows; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; - 'interpRcond': tolerance for pivot interpolation; - 'interpRcondMarginal': tolerance for marginal interpolation; - 'robustTol': tolerance for robust rational denominator management; - 'correctorForce': whether corrector should forcefully delete bad poles; - 'correctorTol': tolerance for corrector step; - 'correctorMaxIter': maximum number of corrector iterations. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginalGrid': marginal sample point generator via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. matchingWeight: Weight for pole matching optimization. cutOffTolerance: Tolerance for ignoring parasitic poles. cutOffKind: Kind of cut off strategy. matchingWeightError: Weight for pole matching optimization in error estimation. cutOffToleranceError: Tolerance for ignoring parasitic poles in error estimation. S: Total number of pivot samples current approximant relies upon. samplerPivot: Pivot sample point generator. SMarginal: Total number of marginal samples current approximant relies upon. samplerMarginalGrid: Marginal sample point generator via sparse grid. polybasis: Type of polynomial basis for pivot interpolation. polybasisMarginal: Type of polynomial basis for marginal interpolation. M: Degree of rational interpolant numerator. N: Degree of rational interpolant denominator. MMarginal: Degree of marginal interpolant. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. polydegreetypeMarginal: Type of polynomial degree for marginal. radialDirectionalWeights: Radial basis weights for pivot numerator. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. nNearestNeighbor: Number of pivot nearest neighbors considered if polybasis allows. nNearestNeighborMarginal: Number of marginal nearest neighbors considered if polybasisMarginal allows. interpRcond: Tolerance for pivot interpolation. interpRcondMarginal: Tolerance for marginal interpolation. robustTol: Tolerance for robust rational denominator management. correctorForce: Whether corrector should forcefully delete bad poles. correctorTol: Tolerance for corrector step. correctorMaxIter: Maximum number of corrector iterations. muBoundsPivot: list of bounds for pivot parameter values. muBoundsMarginal: list of bounds for marginal parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ def _finalizeSnapshots(self): self.samplingEngine = self._samplingEngineOld for muM, sEN in zip(self.musMargLoc, self.samplingEngs): self.samplingEngine.samples += [sEN.samples] self.samplingEngine.nsamples += [sEN.nsamples] self.samplingEngine.mus += [sEN.mus] self.samplingEngine.musMarginal.append(muM) self.samplingEngine._derIdxs += [[(0,) * self.npar] for _ in range(sEN.nsamples)] if self.POD: self.samplingEngine.RPOD += [sEN.RPOD] self.samplingEngine.samples_full += [copy(sEN.samples_full)] if self.POD == PODGlobal: self.samplingEngine.coalesceSamples(self.interpRcondMarginal) else: self.samplingEngine.coalesceSamples() def computeSnapshots(self): """Compute snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start snapshot computation.") vbMng(self, "INIT", "Starting computation of snapshots.", 5) self.musPivot = self.samplerPivot.generatePoints(self.S) while len(self.musPivot) > self.S: self.musPivot.pop() self.mus = emptyParameterList() self.mus.reset((self.S, self.HFEngine.npar)) self.samplingEngine.resetHistory() for k in range(self.S): self.mus.data[k, self.directionPivot] = self.musPivot[k].data self.mus.data[k, self.directionMarginal] = self.musMargLoc[-1].data self.samplingEngine.iterSample(self.mus) vbMng(self, "DEL", "Done computing snapshots.", 5) self._m_selfmus = copy(self.mus) self._mus = self.musPivot self._m_mu0 = copy(self.mu0) self._m_HFErescalingExp = copy(self.HFEngine.rescalingExp) self._mu0 = checkParameterList(self.mu0(self.directionPivot), 1)[0] self.HFEngine.rescalingExp = [self.HFEngine.rescalingExp[ self.directionPivot[0]]] def setupApproxPivoted(self, mus:paramList) -> int: if self.checkComputedApproxPivoted(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 10) self.computeScaleFactor() if self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": np.zeros((0, 0)), "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp, "directionPivot": self.directionPivot} self.trainedModel.data = self.initializeModelData(datadict)[0] self.trainedModel.data.Qs, self.trainedModel.data.Ps = [], [] _trainedModelOld = copy(self.trainedModel) self._scaleFactorOldPivot = copy(self.scaleFactor) self.scaleFactor = self.scaleFactorPivot self._temporaryPivot = 1 self._samplingEngineOld = copy(self.samplingEngine) self.musMargLoc, self.samplingEngs = [], [None] * len(mus) Qs, Ps = [None] * len(mus), [None] * len(mus) self.verbosity -= 15 for j, mu in enumerate(mus): RationalInterpolant.setupSampling(self) self.trainedModel = None self.musMargLoc += [mu] RationalInterpolant.setupApprox(self) self._mu0 = self._m_mu0 self._mus = self._m_selfmus self.HFEngine.rescalingExp = self._m_HFErescalingExp del self._m_mu0, self._m_selfmus, self._m_HFErescalingExp self.samplingEngs[j] = copy(self.samplingEngine) Qs[j] = copy(self.trainedModel.data.Q) Ps[j] = copy(self.trainedModel.data.P) self.scaleFactor = self._scaleFactorOldPivot del self._scaleFactorOldPivot, self._temporaryPivot self._finalizeSnapshots() del self._samplingEngineOld, self.musMargLoc, self.samplingEngs self._mus = self.samplingEngine.musCoalesced self.trainedModel = _trainedModelOld self.trainedModel.data.mus = copy(self.mus) self.trainedModel.data.musMarginal = copy(self.musMarginal) padRight = (self.samplingEngine.nsamplesTot - self.trainedModel.data.projMat.shape[1]) nmusOld = len(self.trainedModel.data.Ps) for j in range(nmusOld): nsj = self.samplingEngine.nsamples[j] self.trainedModel.data.Ps[j].pad(0, padRight) self.trainedModel.data.HIs[j].pad(0, padRight) padLeft = self.trainedModel.data.projMat.shape[1] for j in range(len(mus)): nsj = self.samplingEngine.nsamples[nmusOld + j] if self.POD == PODGlobal: rRightj = self.samplingEngine.RPODCPart[:, padLeft : padLeft + nsj] Ps[j].postmultiplyTensorize(rRightj.T) else: padRight -= nsj Ps[j].pad(padLeft, padRight) padLeft += nsj pMat = self.samplingEngine.samplesCoalesced.data pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat self.trainedModel.data.projMat = pMatEff self.trainedModel.data.Qs += Qs self.trainedModel.data.Ps += Ps self.verbosity += 15 vbMng(self, "DEL", "Done setting up approximant.", 10) return 0 diff --git a/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py b/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py index 99416ae..bb1cc6d 100644 --- a/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py +++ b/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py @@ -1,435 +1,436 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .generic_pivoted_approximant import GenericPivotedApproximant, PODGlobal from rrompy.reduction_methods.standard.greedy.rational_interpolant_greedy \ import RationalInterpolantGreedy from rrompy.reduction_methods.standard.greedy.generic_greedy_approximant \ import pruneSamples from rrompy.utilities.base.types import Np1D from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical import totalDegreeN, dot +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.degree import totalDegreeN from rrompy.utilities.poly_fitting.polynomial import polyvander as pv from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.parameter import emptyParameterList, checkParameterList __all__ = ['RationalInterpolantGreedyPivoted'] class RationalInterpolantGreedyPivoted(GenericPivotedApproximant, RationalInterpolantGreedy): """ ROM pivoted rational interpolant (with pole matching) computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. directionPivot(optional): Pivot components. Defaults to [0]. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'cutOffTolerance': tolerance for ignoring parasitic poles; defaults to np.inf; - 'cutOffKind': kind of cut off strategy; available values include 'SOFT' and 'HARD'; defaults to 'HARD'; - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginal': marginal sample point generator; - 'polybasis': type of polynomial basis for pivot interpolation; defaults to 'MONOMIAL'; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to 'MONOMIAL'; - 'greedyTol': uniform error tolerance for greedy algorithm; defaults to 1e-2; - 'collinearityTol': collinearity tolerance for greedy algorithm; defaults to 0.; - 'maxIter': maximum number of greedy steps; defaults to 1e2; - 'nTestPoints': number of test points; defaults to 5e2; - 'trainSetGenerator': training sample points generator; defaults to sampler; - 'errorEstimatorKind': kind of error estimator; available values include 'AFFINE', 'DISCREPANCY', 'INTERPOLATORY', 'LOOK_AHEAD', and 'NONE'; defaults to 'NONE'; - 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; - 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; defaults to -1; - 'interpRcond': tolerance for pivot interpolation; defaults to None; - 'interpRcondMarginal': tolerance for marginal interpolation; defaults to None; - 'robustTol': tolerance for robust rational denominator management; defaults to 0. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. directionPivot: Pivot components. mus: Array of snapshot parameters. musMarginal: Array of marginal snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'matchingWeight': weight for pole matching optimization; - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'cutOffKind': kind of cut off strategy; - 'polybasis': type of polynomial basis for pivot interpolation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'greedyTol': uniform error tolerance for greedy algorithm; - 'collinearityTol': collinearity tolerance for greedy algorithm; - 'maxIter': maximum number of greedy steps; - 'nTestPoints': number of test points; - 'trainSetGenerator': training sample points generator; - 'errorEstimatorKind': kind of error estimator; - 'MMarginal': degree of marginal interpolant; - 'polydegreetypeMarginal': type of polynomial degree for marginal; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'nNearestNeighbor': number of pivot nearest neighbors considered if polybasis allows; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; - 'interpRcond': tolerance for pivot interpolation; - 'interpRcondMarginal': tolerance for marginal interpolation; - 'robustTol': tolerance for robust rational denominator management; - 'correctorForce': whether corrector should forcefully delete bad poles; - 'correctorTol': tolerance for corrector step; - 'correctorMaxIter': maximum number of corrector iterations. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginal': marginal sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. matchingWeight: Weight for pole matching optimization. cutOffTolerance: Tolerance for ignoring parasitic poles. cutOffKind: Kind of cut off strategy. S: Total number of pivot samples current approximant relies upon. samplerPivot: Pivot sample point generator. SMarginal: Total number of marginal samples current approximant relies upon. samplerMarginal: Marginal sample point generator. polybasis: Type of polynomial basis for pivot interpolation. polybasisMarginal: Type of polynomial basis for marginal interpolation. greedyTol: uniform error tolerance for greedy algorithm. collinearityTol: Collinearity tolerance for greedy algorithm. maxIter: maximum number of greedy steps. nTestPoints: number of starting training points. trainSetGenerator: training sample points generator. errorEstimatorKind: kind of error estimator. MMarginal: Degree of marginal interpolant. polydegreetypeMarginal: Type of polynomial degree for marginal. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. nNearestNeighborMarginal: Number of marginal nearest neighbors considered if polybasisMarginal allows. interpRcond: Tolerance for pivot interpolation. interpRcondMarginal: Tolerance for marginal interpolation. robustTol: Tolerance for robust rational denominator management. muBoundsPivot: list of bounds for pivot parameter values. muBoundsMarginal: list of bounds for marginal parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. Q: Numpy 1D vector containing complex coefficients of approximant denominator. P: Numpy 2D vector whose columns are FE dofs of coefficients of approximant numerator. """ def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(toBeExcluded = ["sampler"]) super().__init__(*args, **kwargs) self._postInit() @property def tModelType(self): if hasattr(self, "_temporaryPivot"): return RationalInterpolantGreedy.tModelType.fget(self) return super().tModelType @property def polybasis0(self): if "_" in self.polybasis: return self.polybasis.split("_")[0] return self.polybasis def _polyvanderAuxiliary(self, mus, deg, *args): degEff = [0] * self.npar degEff[self.directionPivot[0]] = deg return pv(mus, degEff, *args) def _marginalizeMiscellanea(self, forward:bool): if forward: self._m_mu0 = copy(self.mu0) self._m_selfmus = copy(self.mus) self._m_HFErescalingExp = copy(self.HFEngine.rescalingExp) self._mu0 = checkParameterList(self.mu0(self.directionPivot), 1)[0] self._mus = checkParameterList(self.mus(self.directionPivot), 1)[0] self.HFEngine.rescalingExp = [self.HFEngine.rescalingExp[ self.directionPivot[0]]] else: self._mu0 = self._m_mu0 self._mus = self._m_selfmus self.HFEngine.rescalingExp = self._m_HFErescalingExp del self._m_mu0, self._m_selfmus, self._m_HFErescalingExp def _marginalizeTrainedModel(self, forward:bool): if forward: del self._temporaryPivot self.trainedModel.data.mu0 = self.mu0 self.trainedModel.data.scaleFactor = [1.] * self.npar self.trainedModel.data.scaleFactor[self.directionPivot[0]] = ( self.scaleFactor[0]) self.trainedModel.data.rescalingExp = self.HFEngine.rescalingExp Qc = np.zeros((len(self.trainedModel.data.Q.coeffs),) * self.npar, dtype = self.trainedModel.data.Q.coeffs.dtype) Pc = np.zeros((len(self.trainedModel.data.P.coeffs),) * self.npar + (self.trainedModel.data.P.coeffs.shape[1],), dtype = self.trainedModel.data.P.coeffs.dtype) for j in range(len(self.trainedModel.data.Q.coeffs)): Qc[(0,) * self.directionPivot[0] + (j,) + (0,) * (self.npar - self.directionPivot[0] - 1)] = ( self.trainedModel.data.Q.coeffs[j]) for j in range(len(self.trainedModel.data.P.coeffs)): for k in range(self.trainedModel.data.P.coeffs.shape[1]): Pc[(0,) * self.directionPivot[0] + (j,) + (0,) * (self.npar - self.directionPivot[0] - 1) + (k,)] = self.trainedModel.data.P.coeffs[j, k] self.trainedModel.data.Q.coeffs = Qc self.trainedModel.data.P.coeffs = Pc self._m_musUniqueCN = copy(self._musUniqueCN) musUniqueCNAux = np.zeros((self.S, self.npar), dtype = self._musUniqueCN.dtype) musUniqueCNAux[:, self.directionPivot[0]] = self._musUniqueCN(0) self._musUniqueCN = checkParameterList(musUniqueCNAux, self.npar)[0] self._m_derIdxs = copy(self._derIdxs) for j in range(len(self._derIdxs)): for l in range(len(self._derIdxs[j])): derjl = self._derIdxs[j][l][0] self._derIdxs[j][l] = [0] * self.npar self._derIdxs[j][l][self.directionPivot[0]] = derjl else: self._temporaryPivot = 1 self.trainedModel.data.mu0 = checkParameterList( self.mu0(self.directionPivot), 1)[0] self.trainedModel.data.scaleFactor = self.scaleFactor self.trainedModel.data.rescalingExp = self.HFEngine.rescalingExp[ self.directionPivot[0]] self.trainedModel.data.Q.coeffs = self.trainedModel.data.Q.coeffs[ (0,) * self.directionPivot[0] + (slice(None),) + (0,) * (self.HFEngine.npar - 1 - self.directionPivot[0])] self.trainedModel.data.P.coeffs = self.trainedModel.data.P.coeffs[ (0,) * self.directionPivot[0] + (slice(None),) + (0,) * (self.HFEngine.npar - 1 - self.directionPivot[0])] self._musUniqueCN = copy(self._m_musUniqueCN) self._derIdxs = copy(self._m_derIdxs) del self._m_musUniqueCN, self._m_derIdxs self.trainedModel.data.npar = self.npar self.trainedModel.data.Q.npar = self.npar self.trainedModel.data.P.npar = self.npar def errorEstimator(self, mus:Np1D, return_max : bool = False) -> Np1D: """Standard residual-based error estimator.""" self._marginalizeMiscellanea(True) setupOK = self.setupApproxLocal() self._marginalizeMiscellanea(False) if setupOK > 0: err = np.empty(len(mus)) err[:] = np.nan if not return_max: return err return err, - setupOK, np.nan self._marginalizeTrainedModel(True) errRes = super().errorEstimator(mus, return_max) self._marginalizeTrainedModel(False) return errRes def _preliminaryTraining(self): """Initialize starting snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") S = self.S self.sampleBatchIdx, self.sampleBatchSize, self._S = -1, 0, 0 nextBatchSize = 1 while self._S + nextBatchSize <= S: self.sampleBatchIdx += 1 self.sampleBatchSize = nextBatchSize self._S += self.sampleBatchSize nextBatchSize = totalDegreeN(self.npar - 1, self.sampleBatchIdx + 1) self.resetSamples() musPivot = self.trainSetGenerator.generatePoints(self.S) while len(musPivot) > self.S: musPivot.pop() muTestPivot = self.samplerPivot.generatePoints(self.nTestPoints, False) idxPop = pruneSamples(muTestPivot ** self.HFEngine.rescalingExp[ self.directionPivot[0]], musPivot ** self.HFEngine.rescalingExp[ self.directionPivot[0]], 1e-10 * self.scaleFactor[0]) self.mus = emptyParameterList() self.mus.reset((self.S, self.npar + len(self.musMargLoc))) muTestBase = emptyParameterList() muTestBase.reset((len(muTestPivot), self.npar + len(self.musMargLoc))) for k in range(self.S): self.mus.data[k, self.directionPivot] = musPivot[k].data self.mus.data[k, self.directionMarginal] = self.musMargLoc.data for k in range(len(muTestPivot)): muTestBase.data[k, self.directionPivot] = muTestPivot[k].data muTestBase.data[k, self.directionMarginal] = self.musMargLoc.data muTestBase.pop(idxPop) muLast = copy(self.mus[-1]) self.mus.pop() if len(self.mus) > 0: vbMng(self, "MAIN", ("Adding first {} sample point{} at {} to training " "set.").format(self.S - 1, "" + "s" * (self.S > 2), self.mus), 2) self.samplingEngine.iterSample(self.mus) self._S = len(self.mus) self._approxParameters["S"] = self.S self.muTest = emptyParameterList() self.muTest.reset((len(muTestBase) + 1, self.mus.shape[1])) self.muTest.data[: -1] = muTestBase.data self.muTest.data[-1] = muLast.data def _finalizeSnapshots(self): self.setupSampling() self.samplingEngine.resetHistory(len(self.musMarginal)) for j in range(len(self.musMarginal)): self.samplingEngine.setsample(self.samplingEngs[j].samples, j, False) self.samplingEngine.mus[j] = copy(self.samplingEngs[j].mus) self.samplingEngine.musMarginal[j] = copy(self.musMarginal[j]) self.samplingEngine.nsamples[j] = self.samplingEngs[j].nsamples if self.POD: self.samplingEngine.RPOD[j] = self.samplingEngs[j].RPOD self.samplingEngine.samples_full[j].data = ( self.samplingEngs[j].samples_full.data) if self.POD == PODGlobal: self.samplingEngine.coalesceSamples(self.interpRcondMarginal) else: self.samplingEngine.coalesceSamples() def setupApprox(self, *args, **kwargs) -> int: """Compute rational interpolant.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 5) self.musMarginal = self.samplerMarginal.generatePoints(self.SMarginal) while len(self.musMarginal) > self.SMarginal: self.musMarginal.pop() S0 = copy(self.S) Qs, Ps = [None] * len(self.musMarginal), [None] * len(self.musMarginal) self.samplingEngs = [None] * len(self.musMarginal) self.computeScaleFactor() self._scaleFactorOldPivot = copy(self.scaleFactor) self.scaleFactor = self.scaleFactorPivot self._temporaryPivot = 1 for j in range(len(self.musMarginal)): self._S = S0 self.musMargLoc = self.musMarginal[j] RationalInterpolantGreedy.setupSampling(self) self.trainedModel = None self.verbosity -= 5 self.samplingEngine.verbosity -= 5 super().setupApprox(*args, **kwargs) self.verbosity += 5 self.samplingEngine.verbosity += 5 self.samplingEngs[j] = copy(self.samplingEngine) Qs[j] = copy(self.trainedModel.data.Q) Ps[j] = copy(self.trainedModel.data.P) self.scaleFactor = self._scaleFactorOldPivot del self._scaleFactorOldPivot, self._temporaryPivot self._finalizeSnapshots() del self.musMargLoc, self.samplingEngs self._mus = self.samplingEngine.musCoalesced padLeft = 0 if self.POD != PODGlobal: padRight = self.samplingEngine.nsamplesTot for j in range(len(self.musMarginal)): nsj = self.samplingEngine.nsamples[j] if self.POD == PODGlobal: rRightj = self.samplingEngine.RPODCPart[:, padLeft : padLeft + nsj] Ps[j].postmultiplyTensorize(rRightj.T) else: padRight -= nsj Ps[j].pad(padLeft, padRight) padLeft += nsj pMat = self.samplingEngine.samplesCoalesced.data pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": pMatEff, "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp, "directionPivot": self.directionPivot} self.trainedModel.data = self.initializeModelData(datadict)[0] self.trainedModel.data.mus = copy(self.mus) self.trainedModel.data.musMarginal = copy(self.musMarginal) self.trainedModel.data.Qs, self.trainedModel.data.Ps = Qs, Ps vbMng(self, "INIT", "Matching poles.", 10) self.trainedModel.initializeFromRational( self.HFEngine, self.matchingWeight, self.POD == PODGlobal, self.approx_state) vbMng(self, "DEL", "Done matching poles.", 10) self._finalizeMarginalization() vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 diff --git a/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py b/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py index 3e1d9c1..b33b33b 100644 --- a/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py +++ b/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py @@ -1,361 +1,362 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .generic_pivoted_approximant import GenericPivotedApproximant, PODGlobal from rrompy.reduction_methods.standard.rational_interpolant import ( RationalInterpolant) from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical import dot, nextDerivativeIndices +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.hash_derivative import nextDerivativeIndices from rrompy.utilities.exception_manager import RROMPyAssert, RROMPyWarning from rrompy.parameter import emptyParameterList __all__ = ['RationalInterpolantPivoted'] class RationalInterpolantPivoted(GenericPivotedApproximant, RationalInterpolant): """ ROM pivoted rational interpolant (with pole matching) computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. directionPivot(optional): Pivot components. Defaults to [0]. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'cutOffTolerance': tolerance for ignoring parasitic poles; defaults to np.inf; - 'cutOffKind': kind of cut off strategy; available values include 'SOFT' and 'HARD'; defaults to 'HARD'; - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginal': marginal sample point generator; - 'polybasis': type of polynomial basis for pivot interpolation; defaults to 'MONOMIAL'; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to 'MONOMIAL'; - 'M': degree of rational interpolant numerator; defaults to 'AUTO', i.e. maximum allowed; - 'N': degree of rational interpolant denominator; defaults to 'AUTO', i.e. maximum allowed; - 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; - 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; - 'radialDirectionalWeights': radial basis weights for pivot numerator; defaults to 1; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'nNearestNeighbor': number of pivot nearest neighbors considered if polybasis allows; defaults to -1; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; defaults to -1; - 'interpRcond': tolerance for pivot interpolation; defaults to None; - 'interpRcondMarginal': tolerance for marginal interpolation; defaults to None; - 'robustTol': tolerance for robust rational denominator management; defaults to 0; - 'correctorForce': whether corrector should forcefully delete bad poles; defaults to False; - 'correctorTol': tolerance for corrector step; defaults to 0., i.e. no bad poles; - 'correctorMaxIter': maximum number of corrector iterations; defaults to 1. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. directionPivot: Pivot components. mus: Array of snapshot parameters. musPivot: Array of pivot snapshot parameters. musMarginal: Array of marginal snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'matchingWeight': weight for pole matching optimization; - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'cutOffKind': kind of cut off strategy; - 'polybasis': type of polynomial basis for pivot interpolation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'MMarginal': degree of marginal interpolant; - 'polydegreetypeMarginal': type of polynomial degree for marginal; - 'radialDirectionalWeights': radial basis weights for pivot numerator; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'nNearestNeighbor': number of pivot nearest neighbors considered if polybasis allows; - 'nNearestNeighborMarginal': number of marginal nearest neighbors considered if polybasisMarginal allows; - 'interpRcond': tolerance for pivot interpolation; - 'interpRcondMarginal': tolerance for marginal interpolation; - 'robustTol': tolerance for robust rational denominator management; - 'correctorForce': whether corrector should forcefully delete bad poles; - 'correctorTol': tolerance for corrector step; - 'correctorMaxIter': maximum number of corrector iterations. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of pivot samples current approximant relies upon; - 'samplerPivot': pivot sample point generator; - 'SMarginal': total number of marginal samples current approximant relies upon; - 'samplerMarginal': marginal sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. matchingWeight: Weight for pole matching optimization. cutOffTolerance: Tolerance for ignoring parasitic poles. cutOffKind: Kind of cut off strategy. S: Total number of pivot samples current approximant relies upon. samplerPivot: Pivot sample point generator. SMarginal: Total number of marginal samples current approximant relies upon. samplerMarginal: Marginal sample point generator. polybasis: Type of polynomial basis for pivot interpolation. polybasisMarginal: Type of polynomial basis for marginal interpolation. M: Numerator degree of approximant. N: Denominator degree of approximant. MMarginal: Degree of marginal interpolant. polydegreetypeMarginal: Type of polynomial degree for marginal. radialDirectionalWeights: Radial basis weights for pivot numerator. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. nNearestNeighbor: Number of pivot nearest neighbors considered if polybasis allows. nNearestNeighborMarginal: Number of marginal nearest neighbors considered if polybasisMarginal allows. interpRcond: Tolerance for pivot interpolation. interpRcondMarginal: Tolerance for marginal interpolation. robustTol: Tolerance for robust rational denominator management. correctorForce: Whether corrector should forcefully delete bad poles. correctorTol: Tolerance for corrector step. correctorMaxIter: Maximum number of corrector iterations. muBoundsPivot: list of bounds for pivot parameter values. muBoundsMarginal: list of bounds for marginal parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. Q: Numpy 1D vector containing complex coefficients of approximant denominator. P: Numpy 2D vector whose columns are FE dofs of coefficients of approximant numerator. """ def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(toBeExcluded = ["polydegreetype", "sampler"]) super().__init__(*args, **kwargs) self._postInit() @property def polydegreetype(self): """Value of polydegreetype.""" return "TOTAL" @polydegreetype.setter def polydegreetype(self, polydegreetype): RROMPyWarning(("polydegreetype is used just to simplify inheritance, " "and its value cannot be changed from 'TOTAL'.")) @property def polybasis0(self): if "_" in self.polybasis: return self.polybasis.split("_")[0] return self.polybasis @property def correctorTol(self): """Value of correctorTol.""" return self._correctorTol @correctorTol.setter def correctorTol(self, correctorTol): if correctorTol < 0. or (correctorTol > 0. and self.nparPivot > 1): RROMPyWarning(("Overriding prescribed corrector tolerance " "to 0.")) correctorTol = 0. self._correctorTol = correctorTol self._approxParameters["correctorTol"] = self.correctorTol @property def correctorMaxIter(self): """Value of correctorMaxIter.""" return self._correctorMaxIter @correctorMaxIter.setter def correctorMaxIter(self, correctorMaxIter): if correctorMaxIter < 1 or (correctorMaxIter > 1 and self.nparPivot > 1): RROMPyWarning(("Overriding prescribed max number of corrector " "iterations to 1.")) correctorMaxIter = 1 self._correctorMaxIter = correctorMaxIter self._approxParameters["correctorMaxIter"] = self.correctorMaxIter def _setupInterpolationIndices(self): """Setup parameters for polyvander.""" RROMPyAssert(self._mode, message = "Cannot setup interpolation indices.") if (self._musUniqueCN is None or len(self._reorder) != len(self.musPivot)): try: muPC = self.trainedModel.centerNormalizePivot(self.musPivot) except: muPC = self.trainedModel.centerNormalize(self.musPivot) self._musUniqueCN, musIdxsTo, musIdxs, musCount = (muPC.unique( return_index = True, return_inverse = True, return_counts = True)) self._musUnique = self.musPivot[musIdxsTo] self._derIdxs = [None] * len(self._musUniqueCN) self._reorder = np.empty(len(musIdxs), dtype = int) filled = 0 for j, cnt in enumerate(musCount): self._derIdxs[j] = nextDerivativeIndices([], self.nparPivot, cnt) jIdx = np.nonzero(musIdxs == j)[0] self._reorder[jIdx] = np.arange(filled, filled + cnt) filled += cnt def computeSnapshots(self): """Compute snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start snapshot computation.") if self.samplingEngine.nsamplesTot != self.S * self.SMarginal: self.computeScaleFactor() self.resetSamples() vbMng(self, "INIT", "Starting computation of snapshots.", 5) self.musPivot = self.samplerPivot.generatePoints(self.S) while len(self.musPivot) > self.S: self.musPivot.pop() self.musMarginal = self.samplerMarginal.generatePoints( self.SMarginal) while len(self.musMarginal) > self.SMarginal: self.musMarginal.pop() self.mus = emptyParameterList() self.mus.reset((self.S * self.SMarginal, self.HFEngine.npar)) self.samplingEngine.resetHistory(self.SMarginal) for j, muMarg in enumerate(self.musMarginal): for k in range(j * self.S, (j + 1) * self.S): self.mus.data[k, self.directionPivot] = ( self.musPivot[k - j * self.S].data) self.mus.data[k, self.directionMarginal] = muMarg.data self.samplingEngine.iterSample(self.musPivot, self.musMarginal) self._finalizeSnapshots() vbMng(self, "DEL", "Done computing snapshots.", 5) def _finalizeSnapshots(self): if self.POD == PODGlobal: self.samplingEngine.coalesceSamples(self.interpRcondMarginal) else: self.samplingEngine.coalesceSamples() def setupApprox(self) -> int: """Compute rational interpolant.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 5) self.computeSnapshots() pMat = self.samplingEngine.samplesCoalesced.data pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat if self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": pMatEff, "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp, "directionPivot": self.directionPivot} self.trainedModel.data = self.initializeModelData(datadict)[0] else: self.trainedModel = self.trainedModel self.trainedModel.data.projMat = copy(pMatEff) N0 = copy(self.N) Qs, Ps = [None] * len(self.musMarginal), [None] * len(self.musMarginal) self._temporaryPivot = 1 padLeft = 0 if self.POD: self._RPODOldPivot = copy(self.samplingEngine.RPODCoalesced) else: self._samplesOldPivot = copy(self.samplingEngine.samples) padRight = self.samplingEngine.nsamplesTot self._scaleFactorOldPivot = copy(self.scaleFactor) self.scaleFactor = self.scaleFactorPivot for j in range(len(self.musMarginal)): self.N = N0 if self.POD: self.samplingEngine.RPOD = ( self._RPODOldPivot[:, padLeft : padLeft + self.S]) else: self.samplingEngine.samples = self._samplesOldPivot[j] padRight -= self.S self.verbosity -= 5 self._iterCorrector() self.verbosity += 5 Qs[j] = copy(self.trainedModel.data.Q) Ps[j] = copy(self.trainedModel.data.P) del self.trainedModel.data.Q, self.trainedModel.data.P if not self.POD: Ps[j].pad(padLeft, padRight) padLeft += self.S if self.POD: self.samplingEngine.RPODCoalesced = copy(self._RPODOldPivot) del self._RPODOldPivot else: self.samplingEngine.samples = copy(self._samplesOldPivot) del self._samplesOldPivot self.scaleFactor = self._scaleFactorOldPivot del self._temporaryPivot, self._scaleFactorOldPivot self.trainedModel.data.mus = copy(self.mus) self.trainedModel.data.musPivot = copy(self.musPivot) self.trainedModel.data.musMarginal = copy(self.musMarginal) self.trainedModel.data.Qs, self.trainedModel.data.Ps = Qs, Ps vbMng(self, "INIT", "Matching poles.", 10) self.trainedModel.initializeFromRational( self.HFEngine, self.matchingWeight, self.POD == PODGlobal, self.approx_state) vbMng(self, "DEL", "Done matching poles.", 10) self._finalizeMarginalization() vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 diff --git a/rrompy/reduction_methods/pivoted/trained_model/__init__.py b/rrompy/reduction_methods/pivoted/trained_model/__init__.py index 3a3a6a8..ed60590 100644 --- a/rrompy/reduction_methods/pivoted/trained_model/__init__.py +++ b/rrompy/reduction_methods/pivoted/trained_model/__init__.py @@ -1,27 +1,18 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .trained_model_pivoted_data import TrainedModelPivotedData -from .trained_model_pivoted import TrainedModelPivoted - -__all__ = [ - 'TrainedModelPivotedData', - 'TrainedModelPivoted' - ] - - diff --git a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted.py b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted.py index 2c5bd26..f29a97f 100644 --- a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted.py +++ b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted.py @@ -1,486 +1,487 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from scipy.special import factorial as fact from copy import deepcopy as copy from itertools import combinations -from rrompy.reduction_methods.standard.trained_model import ( - TrainedModelRational) +from rrompy.reduction_methods.standard.trained_model.trained_model_rational \ + import TrainedModelRational from rrompy.utilities.base.types import (Np1D, Np2D, List, ListAny, paramVal, paramList, sampList, HFEng) from rrompy.utilities.base import verbosityManager as vbMng, freepar as fp -from rrompy.utilities.numerical import (pointMatching, chordalMetricAdjusted, - potential, reduceDegreeN) +from rrompy.utilities.numerical.point_matching import (pointMatching, + chordalMetricAdjusted, potential) +from rrompy.utilities.numerical.degree import reduceDegreeN from rrompy.utilities.poly_fitting.polynomial import (polybases as ppb, PolynomialInterpolator as PI) from rrompy.utilities.poly_fitting.radial_basis import (polybases as rbpb, RadialBasisInterpolator as RBI) from rrompy.utilities.poly_fitting.moving_least_squares import ( MovingLeastSquaresInterpolator as MLSI) from rrompy.utilities.poly_fitting.heaviside import (heavisideUniformShape, rational2heaviside, HeavisideInterpolator as HI) from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) from rrompy.parameter import checkParameter, checkParameterList from rrompy.sampling import emptySampleList __all__ = ['TrainedModelPivoted'] class TrainedModelPivoted(TrainedModelRational): """ ROM approximant evaluation for pivoted approximants (with pole matching). Attributes: Data: dictionary with all that can be pickled. """ def centerNormalizePivot(self, mu : paramList = [], mu0 : paramVal = None) -> paramList: """ Compute normalized parameter to be plugged into approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.data.mu0Pivot. Returns: Normalized parameter. """ mu = checkParameterList(mu, self.data.nparPivot)[0] if mu0 is None: mu0 = self.data.mu0Pivot rad = ((mu ** self.data.rescalingExpPivot - mu0 ** self.data.rescalingExpPivot) / self.data.scaleFactorPivot) return rad def centerNormalizeMarginal(self, mu : paramList = [], mu0 : paramVal = None) -> paramList: """ Compute normalized parameter to be plugged into approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.data.mu0Marginal. Returns: Normalized parameter. """ mu = checkParameterList(mu, self.data.nparMarginal)[0] if mu0 is None: mu0 = self.data.mu0Marginal rad = ((mu ** self.data.rescalingExpMarginal - mu0 ** self.data.rescalingExpMarginal) / self.data.scaleFactorMarginal) return rad def updateEffectiveSamples(self, HFEngine:HFEng, exclude : List[int] = None, matchingWeight : float = 1., POD : bool = True, is_state : bool = True): if hasattr(self, "_idxExcl"): for j, excl in enumerate(self._idxExcl): self.data.musMarginal.insert(self._musMExcl[j], excl) self.data.HIs.insert(excl, self._HIsExcl[j]) self.data.Ps.insert(excl, self._PsExcl[j]) self.data.Qs.insert(excl, self._QsExcl[j]) if exclude is None: exclude = [] self._idxExcl, self._musMExcl = list(np.sort(exclude)), [] self._HIsExcl, self._PsExcl, self._QsExcl = [], [], [] for excl in self._idxExcl[::-1]: self._musMExcl = [self.data.musMarginal[excl]] + self._musMExcl self.data.musMarginal.pop(excl) self._HIsExcl = [self.data.HIs.pop(excl)] + self._HIsExcl self._PsExcl = [self.data.Ps.pop(excl)] + self._PsExcl self._QsExcl = [self.data.Qs.pop(excl)] + self._QsExcl poles = [hi.poles for hi in self.data.HIs] coeffs = [hi.coeffs for hi in self.data.HIs] self.initializeFromLists(poles, coeffs, self.data.HIs[0].polybasis, HFEngine, matchingWeight, POD, is_state) def setupMarginalInterp(self, approx, interpPars:ListAny, MMAuto:bool, rDWM : Np1D = None, noWarnReduceAuto : bool = True): vbMng(self, "INIT", "Starting computation of marginal interpolator.", 12) musMCN = self.centerNormalizeMarginal(self.data.musMarginal) pbM = approx.polybasisMarginal if pbM not in ppb: rDWMEf = np.array(rDWM) self.data.marginalInterp = [] for ipts, pts in enumerate(self.data.suppEffPts): mI = [None] * len(musMCN) if len(pts) > 0: musMCNEff = musMCN[pts] if MMAuto: if ipts > 0: verb = approx.verbosity approx.verbosity = 0 _musM = approx.musMarginal approx.musMarginal = musMCNEff approx._setMMarginalAuto() if ipts > 0: approx.musMarginal = _musM approx.verbosity = verb if ipts == 0: _MMarginalEffective = approx.MMarginal if not MMAuto: approx.MMarginal = _MMarginalEffective MM = reduceDegreeN(approx.MMarginal, len(musMCNEff), self.data.nparMarginal, approx.polydegreetypeMarginal) if MM < approx.MMarginal: if ipts == 0 and not noWarnReduceAuto: RROMPyWarning(("MMarginal too large compared to " "SMarginal. Reducing MMarginal by " "{}").format(approx.MMarginal - MM)) approx.MMarginal = MM MMEff = approx.MMarginal for j in range(len(musMCNEff)): canonicalj = 1. * (np.arange(len(musMCNEff)) == j) while MMEff >= 0 and (pbM in ppb or rDWMEf[0] <= rDWM[0] * 2 ** 6): pParRest = copy(interpPars) if pbM in ppb: p = PI() else: pParRest = [rDWMEf] + pParRest p = RBI() if pbM in rbpb else MLSI() wellCond, msg = p.setupByInterpolation(musMCNEff, canonicalj, MMEff, pbM, *pParRest) vbMng(self, "MAIN", msg, 30) if wellCond: break if pbM in ppb: vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing " "MMarginal by 1."), 35) MMEff -= 1 else: vbMng(self, "MAIN", ("Polyfit is poorly conditioned. " "Multiplying radialDirectionalWeightsMarginal " "by 2."), 35) rDWMEf *= 2. if MMEff < 0 or (pbM not in ppb and rDWMEf[0] > rDWM[0] * 2 ** 6): raise RROMPyException(("Instability in computation of " "interpolant. Aborting.")) if pbM in ppb: MMEff = approx.MMarginal else: rDWMEf = np.array(rDWM) mI[pts[j]] = copy(p) self.data.marginalInterp += [mI] approx.MMarginal = _MMarginalEffective vbMng(self, "DEL", "Done computing marginal interpolator.", 12) def initializeFromLists(self, poles:ListAny, coeffs:ListAny, basis:str, HFEngine:HFEng, matchingWeight : float = 1., POD : bool = True, is_state : bool = True): """Initialize Heaviside representation.""" musM = self.data.musMarginal margAbsDist = np.sum(np.abs(np.repeat(musM.data, len(musM), 0) - np.tile(musM.data, [len(musM), 1]) ), axis = 1).reshape(len(musM), len(musM)) explored = [0] unexplored = list(range(1, len(musM))) poles, coeffs = heavisideUniformShape(poles, coeffs) N = len(poles[0]) for _ in range(1, len(musM)): minIdx = np.argmin(np.concatenate([margAbsDist[ex, unexplored] \ for ex in explored])) minIex = explored[minIdx // len(unexplored)] minIunex = unexplored[minIdx % len(unexplored)] resex = coeffs[minIex][: N] resunex = coeffs[minIunex][: N] if matchingWeight != 0 and not POD: resex = self.data.projMat.dot(resex.T) resunex = self.data.projMat.dot(resunex.T) dist = chordalMetricAdjusted(poles[minIex], poles[minIunex], matchingWeight, resex, resunex, HFEngine, is_state) reordering = pointMatching(dist)[1] poles[minIunex] = poles[minIunex][reordering] coeffs[minIunex][: N] = coeffs[minIunex][reordering] explored += [minIunex] unexplored.remove(minIunex) HIs = [] for pls, cfs in zip(poles, coeffs): hsi = HI() hsi.poles = pls hsi.coeffs = cfs hsi.npar = 1 hsi.polybasis = basis HIs += [hsi] self.data.HIs = HIs self.data.suppEffPts = [np.arange(len(self.data.HIs))] self.data.suppEffIdx = np.zeros(N, dtype = int) def initializeFromRational(self, HFEngine:HFEng, matchingWeight : float = 1., POD : bool = True, is_state : bool = True): """Initialize Heaviside representation.""" RROMPyAssert(self.data.nparPivot, 1, "Number of pivot parameters") poles, coeffs = [], [] for Q, P in zip(self.data.Qs, self.data.Ps): cfs, pls, basis = rational2heaviside(P, Q) poles += [pls] coeffs += [cfs] self.initializeFromLists(poles, coeffs, basis, HFEngine, matchingWeight, POD, is_state) def recompressByCutOff(self, tol:float, kind:str, foci:List[np.complex], ground:float) -> str: N = len(self.data.HIs[0].poles) mu0 = np.mean(foci) goodLocPoles = np.array([potential(hi.poles, foci) - ground <= tol * ground for hi in self.data.HIs]) self.data.suppEffPts = [np.arange(len(self.data.HIs))] self.data.suppEffIdx = np.zeros(N, dtype = int) if np.all(goodLocPoles): return " No poles erased." kind = kind.upper().strip().replace(" ","") goodAllPoles = np.all(goodLocPoles, axis = 0) badPoles = np.logical_not(goodAllPoles) if kind == "HARD": keepPole = np.where(goodAllPoles)[0] halfPole = np.empty(0, dtype = int) removePole = np.where(badPoles)[0] elif kind == "SOFT": goodSomePoles = np.any(goodLocPoles, axis = 0) keepPole = np.where(goodSomePoles)[0] halfPole = np.where(np.logical_and(badPoles, goodSomePoles))[0] removePole = np.where(np.logical_not(goodSomePoles))[0] else: raise RROMPyException("Cutoff kind not recognized.") if len(removePole) > 0: keepCoeff = np.append(keepPole, np.append([N], np.arange(N + 1, len(self.data.HIs[0].coeffs)))) for hi in self.data.HIs: polyCorrection = np.zeros_like(hi.coeffs[0, :]) for j in removePole: if not np.isinf(hi.poles[j]): polyCorrection += hi.coeffs[j, :] / (mu0 - hi.poles[j]) if len(hi.coeffs) == N: hi.coeffs = np.vstack((hi.coeffs, polyCorrection)) else: hi.coeffs[N, :] += polyCorrection hi.poles = hi.poles[keepPole] hi.coeffs = hi.coeffs[keepCoeff, :] for idxR in halfPole: pts = np.where(goodLocPoles[:, idxR])[0] idxEff = len(self.data.suppEffPts) for idEff, prevPts in enumerate(self.data.suppEffPts): if len(prevPts) == len(pts): if np.allclose(prevPts, pts): idxEff = idEff break if idxEff == len(self.data.suppEffPts): self.data.suppEffPts += [pts] self.data.suppEffIdx[idxR] = idxEff self.data.suppEffIdx = self.data.suppEffIdx[keepPole] return (" Hard-erased {} pole".format(len(removePole)) + "s" * (len(removePole) != 1) + " and soft-erased {} pole".format(len(halfPole)) + "s" * (len(halfPole) != 1) + ".") def _interpolateMarginal(self, muC : paramList, objs : ListAny) -> Np2D: res = np.zeros(objs[0].shape + (len(muC),), dtype = objs[0].dtype) for suppIdx in range(len(self.data.suppEffPts)): i = np.where(self.data.suppEffIdx == suppIdx)[0] if suppIdx == 0: i = np.append(i, np.arange(len(self.data.suppEffIdx), len(res))) if len(i) > 0: for mIj, obj in zip(self.data.marginalInterp[suppIdx], objs): if mIj is not None: res[i] += np.expand_dims(obj[i], - 1) * mIj(muC) return res def interpolateMarginalInterpolator(self, mu : paramVal = []) -> Np1D: """Obtain interpolated approximant interpolator.""" mu = checkParameter(mu, self.data.nparMarginal)[0] hsi = HI() hsi.poles = self.interpolateMarginalPoles(mu)[..., 0] hsi.coeffs = self.interpolateMarginalCoeffs(mu)[..., 0] hsi.npar = 1 hsi.polybasis = self.data.HIs[0].polybasis return hsi def interpolateMarginalPoles(self, mu : paramList = []) -> Np1D: """Obtain interpolated approximant poles.""" mu = checkParameterList(mu, self.data.nparMarginal)[0] muC = self.centerNormalizeMarginal(mu) vbMng(self, "INIT", "Interpolating marginal poles at mu = {}.".format(mu), 95) intMPoles = self._interpolateMarginal(muC, [hi.poles for hi in self.data.HIs]) vbMng(self, "DEL", "Done interpolating marginal poles.", 95) return intMPoles def interpolateMarginalCoeffs(self, mu : paramList = []) -> Np1D: """Obtain interpolated approximant coefficients.""" mu = checkParameterList(mu, self.data.nparMarginal)[0] muC = self.centerNormalizeMarginal(mu) vbMng(self, "INIT", "Interpolating marginal coefficients at mu = {}.".format(mu), 95) intMCoeffs = self._interpolateMarginal(muC, [hi.coeffs for hi in self.data.HIs]) vbMng(self, "DEL", "Done interpolating marginal coefficients.", 95) return intMCoeffs def getApproxReduced(self, mu : paramList = []) -> sampList: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. """ RROMPyAssert(self.data.nparPivot, 1, "Number of pivot parameters") mu = checkParameterList(mu, self.data.npar)[0] if (not hasattr(self, "lastSolvedApproxReduced") or self.lastSolvedApproxReduced != mu): vbMng(self, "INIT", "Evaluating approximant at mu = {}.".format(mu), 12) self.uApproxReduced = emptySampleList() for i, muPL in enumerate(mu): muL = self.centerNormalizePivot([muPL(0, x) \ for x in self.data.directionPivot]) muM = [muPL(0, x) for x in self.data.directionMarginal] vbMng(self, "INIT", "Assembling reduced model for mu = {}.".format(muPL), 87) hsL = self.interpolateMarginalInterpolator(muM) vbMng(self, "DEL", "Done assembling reduced model.", 87) uAppR = hsL(muL) if i == 0: self.uApproxReduced.reset((len(uAppR), len(mu)), dtype = uAppR.dtype) self.uApproxReduced[i] = uAppR vbMng(self, "DEL", "Done evaluating approximant.", 12) self.lastSolvedApproxReduced = mu return self.uApproxReduced def getPVal(self, mu : paramList = []) -> sampList: """ Evaluate rational numerator at arbitrary parameter. Args: mu: Target parameter. """ RROMPyAssert(self.data.nparPivot, 1, "Number of pivot parameters") mu = checkParameterList(mu, self.data.npar)[0] p = emptySampleList() p.reset((len(self.data.HIs[0].coeffs.shape[1]), len(mu))) for i, muPL in enumerate(mu): muL = self.centerNormalizePivot([muPL(0, x) \ for x in self.data.directionPivot]) muM = [muPL(0, x) for x in self.data.directionMarginal] hsL = self.interpolateMarginalInterpolator(muM) p[i] = hsL(muL) * np.prod(muL(0, 0) - hsL.poles) return p def getQVal(self, mu:Np1D, der : List[int] = None, scl : Np1D = None) -> Np1D: """ Evaluate rational denominator at arbitrary parameter. Args: mu: Target parameter. der(optional): Derivatives to take before evaluation. """ RROMPyAssert(self.data.nparPivot, 1, "Number of pivot parameters") mu = checkParameterList(mu, self.data.npar)[0] muP = self.centerNormalizePivot(checkParameterList( mu.data[:, self.data.directionPivot], self.data.nparPivot)[0]) muM = checkParameterList(mu.data[:, self.data.directionMarginal], self.data.nparMarginal)[0] if der is None: derP, derM = 0, [0] else: derP = der[self.data.directionPivot[0]] derM = [der[x] for x in self.data.directionMarginal] if np.any(np.array(derM) != 0): raise RROMPyException(("Derivatives of Q with respect to marginal " "parameters not allowed.")) sclP = 1 if scl is None else scl[self.data.directionPivot[0]] derVal = np.zeros(len(mu), dtype = np.complex) N = len(self.data.HIs[0].poles) if derP == N: derVal[:] = 1. elif derP >= 0 and derP < N: pls = self.interpolateMarginalPoles(muM).T plsDist = muP.data.reshape(-1, 1) - pls for terms in combinations(np.arange(N), N - derP): derVal += np.prod(plsDist[:, list(terms)], axis = 1) return sclP ** derP * fact(derP) * derVal def getPoles(self, *args, **kwargs) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ RROMPyAssert(self.data.nparPivot, 1, "Number of pivot parameters") if len(args) + len(kwargs) > 1: raise RROMPyException(("Wrong number of parameters passed. " "Only 1 available.")) elif len(args) + len(kwargs) == 1: if len(args) == 1: mVals = args[0] else: mVals = kwargs["marginalVals"] if not hasattr(mVals, "__len__"): mVals = [mVals] mVals = list(mVals) else: mVals = [fp] try: rDim = mVals.index(fp) if rDim < len(mVals) - 1 and fp in mVals[rDim + 1 :]: raise except: raise RROMPyException(("Exactly 1 'freepar' entry in " "marginalVals must be provided.")) if rDim != self.data.directionPivot[0]: raise RROMPyException(("'freepar' entry in marginalVals must " "coincide with pivot direction.")) mVals[rDim] = self.data.mu0(rDim) mMarg = [mVals[j] for j in range(len(mVals)) if j != rDim] roots = self.interpolateMarginalPoles(mMarg)[..., 0] return np.power(self.data.mu0(rDim) ** self.data.rescalingExp[rDim] + self.data.scaleFactor[rDim] * roots, 1. / self.data.rescalingExp[rDim]) def getResidues(self, *args, **kwargs) -> Np1D: """ Obtain approximant residues. Returns: Numpy matrix with residues as columns. """ pls = self.getPoles(*args, **kwargs) if len(args) == 1: mVals = args[0] elif len(args) == 0: mVals = [None] else: mVals = kwargs["marginalVals"] if not hasattr(mVals, "__len__"): mVals = [mVals] mVals = list(mVals) rDim = mVals.index(fp) mMarg = [mVals[j] for j in range(len(mVals)) if j != rDim] residues = self.interpolateMarginalCoeffs(mMarg)[: len(pls), :, 0] res = self.data.projMat.dot(residues.T) return pls, res diff --git a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_data.py b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_data.py index 3d3693e..e465334 100644 --- a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_data.py +++ b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_data.py @@ -1,72 +1,73 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from rrompy.reduction_methods.base.trained_model import TrainedModelData +from rrompy.reduction_methods.base.trained_model.trained_model_data import ( + TrainedModelData) from rrompy.utilities.base.types import Np2D, List, ListAny, paramVal from rrompy.parameter import checkParameterList __all__ = ['TrainedModelPivotedData'] class TrainedModelPivotedData(TrainedModelData): """ROM approximant evaluation data (must be pickle-able).""" def __init__(self, mu0:paramVal, projMat:Np2D, scaleFactor : ListAny = [1.], rescalingExp : List[float] = [1.], directionPivot : ListAny = [0]): super().__init__(mu0, projMat, scaleFactor, rescalingExp) self.directionPivot = directionPivot @property def directionMarginal(self): return tuple([x for x in range(self.npar) \ if x not in self.directionPivot]) @property def mu0Pivot(self): return checkParameterList(self.mu0(0, self.directionPivot), self.nparPivot)[0] @property def mu0Marginal(self): return checkParameterList(self.mu0(0, self.directionMarginal), self.nparMarginal)[0] @property def nparPivot(self): return len(self.directionPivot) @property def nparMarginal(self): return self.npar - self.nparPivot @property def rescalingExpPivot(self): return [self.rescalingExp[x] for x in self.directionPivot] @property def rescalingExpMarginal(self): return [self.rescalingExp[x] for x in self.directionMarginal] @property def scaleFactorPivot(self): return [self.scaleFactor[x] for x in self.directionPivot] @property def scaleFactorMarginal(self): return [self.scaleFactor[x] for x in self.directionMarginal] diff --git a/rrompy/reduction_methods/standard/__init__.py b/rrompy/reduction_methods/standard/__init__.py index 31e9780..5155dab 100644 --- a/rrompy/reduction_methods/standard/__init__.py +++ b/rrompy/reduction_methods/standard/__init__.py @@ -1,35 +1,33 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .generic_standard_approximant import GenericStandardApproximant from .nearest_neighbor import NearestNeighbor from .rational_interpolant import RationalInterpolant from .rational_moving_least_squares import RationalMovingLeastSquares from .rational_pade import RationalPade from .reduced_basis import ReducedBasis __all__ = [ - 'GenericStandardApproximant', 'NearestNeighbor', 'RationalInterpolant', 'RationalMovingLeastSquares', 'RationalPade', 'ReducedBasis' ] diff --git a/rrompy/reduction_methods/standard/greedy/__init__.py b/rrompy/reduction_methods/standard/greedy/__init__.py index 623f762..bbb221e 100644 --- a/rrompy/reduction_methods/standard/greedy/__init__.py +++ b/rrompy/reduction_methods/standard/greedy/__init__.py @@ -1,29 +1,27 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .generic_greedy_approximant import GenericGreedyApproximant from .rational_interpolant_greedy import RationalInterpolantGreedy from .reduced_basis_greedy import ReducedBasisGreedy __all__ = [ - 'GenericGreedyApproximant', 'RationalInterpolantGreedy', 'ReducedBasisGreedy' ] diff --git a/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py b/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py index 969bd74..bc16b73 100644 --- a/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py +++ b/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py @@ -1,644 +1,645 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from matplotlib import pyplot as plt from rrompy.hfengines.base.linear_affine_engine import checkIfAffine -from rrompy.reduction_methods.standard import GenericStandardApproximant +from rrompy.reduction_methods.standard.generic_standard_approximant import ( + GenericStandardApproximant) from rrompy.utilities.base.types import (Np1D, Np2D, Tuple, List, normEng, paramVal, paramList, sampList) from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.numerical import dot from rrompy.utilities.expression import expressionEvaluator from rrompy.solver import normEngine from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) from rrompy.parameter import checkParameterList, emptyParameterList __all__ = ['GenericGreedyApproximant'] def localL2Distance(mus:Np2D, badmus:Np2D) -> Np2D: return np.linalg.norm(np.tile(mus[..., np.newaxis], [1, 1, len(badmus)]) - badmus[..., np.newaxis].T, axis = 1) def pruneSamples(mus:paramList, badmus:paramList, tol : float = 1e-8) -> Np1D: """Remove from mus all the elements which are too close to badmus.""" if len(badmus) == 0: return mus proximity = np.min(localL2Distance(mus.data, badmus.data), axis = 1) return np.arange(len(mus))[proximity <= tol] class GenericGreedyApproximant(GenericStandardApproximant): """ ROM greedy interpolant computation for parametric problems (ABSTRACT). Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': number of starting training points; - 'sampler': sample point generator; - 'greedyTol': uniform error tolerance for greedy algorithm; defaults to 1e-2; - 'collinearityTol': collinearity tolerance for greedy algorithm; defaults to 0.; - 'maxIter': maximum number of greedy steps; defaults to 1e2; - 'nTestPoints': number of test points; defaults to 5e2; - 'trainSetGenerator': training sample points generator; defaults to sampler. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots. - 'greedyTol': uniform error tolerance for greedy algorithm; - 'collinearityTol': collinearity tolerance for greedy algorithm; - 'maxIter': maximum number of greedy steps; - 'nTestPoints': number of test points; - 'trainSetGenerator': training sample points generator. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: whether to compute POD of snapshots. S: number of test points. sampler: Sample point generator. greedyTol: Uniform error tolerance for greedy algorithm. collinearityTol: Collinearity tolerance for greedy algorithm. maxIter: maximum number of greedy steps. nTestPoints: number of starting training points. trainSetGenerator: training sample points generator. muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. estimatorNormEngine: Engine for estimator norm computation. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(["greedyTol", "collinearityTol", "maxIter", "nTestPoints"], [1e-2, 0., 1e2, 5e2], ["trainSetGenerator"], ["AUTO"]) super().__init__(*args, **kwargs) self._postInit() @property def greedyTol(self): """Value of greedyTol.""" return self._greedyTol @greedyTol.setter def greedyTol(self, greedyTol): if greedyTol < 0: raise RROMPyException("greedyTol must be non-negative.") if hasattr(self, "_greedyTol") and self.greedyTol is not None: greedyTolold = self.greedyTol else: greedyTolold = -1 self._greedyTol = greedyTol self._approxParameters["greedyTol"] = self.greedyTol if greedyTolold != self.greedyTol: self.resetSamples() @property def collinearityTol(self): """Value of collinearityTol.""" return self._collinearityTol @collinearityTol.setter def collinearityTol(self, collinearityTol): if collinearityTol < 0: raise RROMPyException("collinearityTol must be non-negative.") if (hasattr(self, "_collinearityTol") and self.collinearityTol is not None): collinearityTolold = self.collinearityTol else: collinearityTolold = -1 self._collinearityTol = collinearityTol self._approxParameters["collinearityTol"] = self.collinearityTol if collinearityTolold != self.collinearityTol: self.resetSamples() @property def maxIter(self): """Value of maxIter.""" return self._maxIter @maxIter.setter def maxIter(self, maxIter): if maxIter <= 0: raise RROMPyException("maxIter must be positive.") if hasattr(self, "_maxIter") and self.maxIter is not None: maxIterold = self.maxIter else: maxIterold = -1 self._maxIter = maxIter self._approxParameters["maxIter"] = self.maxIter if maxIterold != self.maxIter: self.resetSamples() @property def nTestPoints(self): """Value of nTestPoints.""" return self._nTestPoints @nTestPoints.setter def nTestPoints(self, nTestPoints): if nTestPoints <= 0: raise RROMPyException("nTestPoints must be positive.") if not np.isclose(nTestPoints, np.int(nTestPoints)): raise RROMPyException("nTestPoints must be an integer.") nTestPoints = np.int(nTestPoints) if hasattr(self, "_nTestPoints") and self.nTestPoints is not None: nTestPointsold = self.nTestPoints else: nTestPointsold = -1 self._nTestPoints = nTestPoints self._approxParameters["nTestPoints"] = self.nTestPoints if nTestPointsold != self.nTestPoints: self.resetSamples() @property def trainSetGenerator(self): """Value of trainSetGenerator.""" return self._trainSetGenerator @trainSetGenerator.setter def trainSetGenerator(self, trainSetGenerator): if (isinstance(trainSetGenerator, (str,)) and trainSetGenerator.upper() == "AUTO"): trainSetGenerator = self.sampler if 'generatePoints' not in dir(trainSetGenerator): raise RROMPyException("trainSetGenerator type not recognized.") if (hasattr(self, '_trainSetGenerator') and self.trainSetGenerator not in [None, "AUTO"]): trainSetGeneratorOld = self.trainSetGenerator self._trainSetGenerator = trainSetGenerator self._approxParameters["trainSetGenerator"] = self.trainSetGenerator if (not 'trainSetGeneratorOld' in locals() or trainSetGeneratorOld != self.trainSetGenerator): self.resetSamples() def resetSamples(self): """Reset samples.""" super().resetSamples() self._mus = emptyParameterList() def initEstimatorNormEngine(self, normEngn : normEng = None): """Initialize estimator norm engine.""" if (normEngn is not None or not hasattr(self, "estimatorNormEngine") or self.estimatorNormEngine is None): if normEngn is None: if self.approx_state: if not hasattr(self.HFEngine, "energyNormDualMatrix"): self.HFEngine.buildEnergyNormDualForm() estimatorEnergyMatrix = self.HFEngine.energyNormDualMatrix else: estimatorEnergyMatrix = self.HFEngine.outputNormMatrix else: if hasattr(normEngn, "buildEnergyNormDualForm"): if not hasattr(normEngn, "energyNormDualMatrix"): normEngn.buildEnergyNormDualForm() estimatorEnergyMatrix = normEngn.energyNormDualMatrix else: estimatorEnergyMatrix = normEngn self.estimatorNormEngine = normEngine(estimatorEnergyMatrix) def _affineResidualMatricesContraction(self, rb:Np2D, rA : Np2D = None) \ -> Tuple[Np1D, Np1D, Np1D]: self.assembleReducedResidualBlocks(full = rA is not None) # 'ij,jk,ik->k', resbb, radiusb, radiusb.conj() ff = np.sum(self.trainedModel.data.resbb.dot(rb) * rb.conj(), axis = 0) if rA is None: return ff # 'ijk,jkl,il->l', resAb, radiusA, radiusb.conj() Lf = np.sum(np.tensordot(self.trainedModel.data.resAb, rA, 2) * rb.conj(), axis = 0) # 'ijkl,klt,ijt->t', resAA, radiusA, radiusA.conj() LL = np.sum(np.tensordot(self.trainedModel.data.resAA, rA, 2) * rA.conj(), axis = (0, 1)) return ff, Lf, LL def getErrorEstimatorAffine(self, mus:Np1D) -> Np1D: """Standard residual estimator.""" checkIfAffine(self.HFEngine, "apply affinity-based error estimator") self.HFEngine.buildA() self.HFEngine.buildb() mus = checkParameterList(mus, self.npar)[0] verb = self.trainedModel.verbosity self.trainedModel.verbosity = 0 uApproxRs = self.getApproxReduced(mus) muTestEff = mus ** self.HFEngine.rescalingExp radiusA = np.empty((len(self.HFEngine.thAs), len(mus)), dtype = np.complex) radiusb = np.empty((len(self.HFEngine.thbs), len(mus)), dtype = np.complex) for j, thA in enumerate(self.HFEngine.thAs): radiusA[j] = expressionEvaluator(thA[0], muTestEff) for j, thb in enumerate(self.HFEngine.thbs): radiusb[j] = expressionEvaluator(thb[0], muTestEff) radiusA = np.expand_dims(uApproxRs.data, 1) * radiusA ff, Lf, LL = self._affineResidualMatricesContraction(radiusb, radiusA) err = np.abs((LL - 2. * np.real(Lf) + ff) / ff) ** .5 self.trainedModel.verbosity = verb return err def errorEstimator(self, mus:Np1D, return_max : bool = False) -> Np1D: setupOK = self.setupApproxLocal() if setupOK > 0: err = np.empty(len(mus)) err[:] = np.nan if not return_max: return err return err, - setupOK, np.nan mus = checkParameterList(mus, self.npar)[0] vbMng(self.trainedModel, "INIT", "Evaluating error estimator at mu = {}.".format(mus), 10) err = self.getErrorEstimatorAffine(mus) vbMng(self.trainedModel, "DEL", "Done evaluating error estimator", 10) if not return_max: return err idxMaxEst = [np.argmax(err)] return err, idxMaxEst, err[idxMaxEst] def _isLastSampleCollinear(self) -> bool: """Check collinearity of last sample.""" if self.collinearityTol <= 0.: return False if self.POD: reff = self.samplingEngine.RPOD[:, -1] else: RROMPyWarning(("Repeated orthogonalization of the samples for " "collinearity check. Consider setting POD to " "True.")) if not hasattr(self, "_PODEngine"): from rrompy.sampling.base.pod_engine import PODEngine self._PODEngine = PODEngine(self.HFEngine) reff = self._PODEngine.generalizedQR(self.samplingEngine.samples, only_R = True, is_state = True)[:, -1] cLevel = np.abs(reff[-1]) / np.linalg.norm(reff) cLevel = np.inf if np.isclose(cLevel, 0.) else cLevel ** -1. vbMng(self, "MAIN", "Collinearity indicator {:.4e}.".format(cLevel), 5) return cLevel > self.collinearityTol def plotEstimator(self, est:Np1D, idxMax:List[int], estMax:List[float]): if not (np.any(np.isnan(est)) or np.any(np.isinf(est))): fig = plt.figure(figsize = plt.figaspect(1. / self.npar)) for jpar in range(self.npar): ax = fig.add_subplot(1, self.npar, 1 + jpar) musre = copy(self.muTest.re.data) errCP = copy(est) idx = np.delete(np.arange(self.npar), jpar) while len(musre) > 0: if self.npar == 1: currIdx = np.arange(len(musre)) else: currIdx = np.where(np.isclose(np.sum( np.abs(musre[:, idx] - musre[0, idx]), 1), 0.))[0] ax.semilogy(musre[currIdx, jpar], errCP[currIdx], 'k', linewidth = 1) musre = np.delete(musre, currIdx, 0) errCP = np.delete(errCP, currIdx) ax.semilogy([self.muBounds.re(0, jpar), self.muBounds.re(-1, jpar)], [self.greedyTol] * 2, 'r--') ax.semilogy(self.mus.re(jpar), 2. * self.greedyTol * np.ones(len(self.mus)), '*m') if len(idxMax) > 0 and estMax is not None: ax.semilogy(self.muTest.re(idxMax, jpar), estMax, 'xr') ax.grid() plt.tight_layout() plt.show() def greedyNextSample(self, muidx:int, plotEst : str = "NONE")\ -> Tuple[Np1D, int, float, paramVal]: """Compute next greedy snapshot of solution map.""" RROMPyAssert(self._mode, message = "Cannot add greedy sample.") mus = copy(self.muTest[muidx]) self.muTest.pop(muidx) for j, mu in enumerate(mus): vbMng(self, "MAIN", ("Adding sample point no. {} at {} to training " "set.").format(len(self.mus) + 1, mu), 2) self.mus.append(mu) self._S = len(self.mus) self._approxParameters["S"] = self.S if (self.samplingEngine.nsamples <= len(mus) - j - 1 or not np.allclose(mu, self.samplingEngine.mus.data[j - len(mus)])): self.samplingEngine.nextSample(mu) if self._isLastSampleCollinear(): vbMng(self, "MAIN", ("Collinearity above tolerance detected. Starting " "preemptive greedy loop termination."), 2) self._collinearityFlag = 1 errorEstTest = np.empty(len(self.muTest)) errorEstTest[:] = np.nan return errorEstTest, [-1], np.nan, np.nan errorEstTest, muidx, maxErrorEst = self.errorEstimator(self.muTest, True) if plotEst == "ALL": self.plotEstimator(errorEstTest, muidx, maxErrorEst) return errorEstTest, muidx, maxErrorEst, self.muTest[muidx] def _preliminaryTraining(self): """Initialize starting snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") if self.samplingEngine.nsamples > 0: return self.computeScaleFactor() self.resetSamples() self.mus = self.trainSetGenerator.generatePoints(self.S) while len(self.mus) > self.S: self.mus.pop() muTestBase = self.sampler.generatePoints(self.nTestPoints, False) idxPop = pruneSamples(muTestBase ** self.HFEngine.rescalingExp, self.mus ** self.HFEngine.rescalingExp, 1e-10 * self.scaleFactor[0]) muTestBase.pop(idxPop) muLast = copy(self.mus[-1]) self.mus.pop() if len(self.mus) > 0: vbMng(self, "MAIN", ("Adding first {} sample point{} at {} to training " "set.").format(self.S - 1, "" + "s" * (self.S > 2), self.mus), 2) self.samplingEngine.iterSample(self.mus) self._S = len(self.mus) self._approxParameters["S"] = self.S self.muTest = emptyParameterList() self.muTest.reset((len(muTestBase) + 1, self.mus.shape[1])) self.muTest[: -1] = muTestBase.data self.muTest[-1] = muLast.data def setupApproxLocal(self) -> int: if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") raise RROMPyException("Must override.") def setupApprox(self, plotEst : str = "NONE") -> int: """Compute greedy snapshots of solution map.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") vbMng(self, "INIT", "Starting computation of snapshots.", 2) self._collinearityFlag = 0 self._preliminaryTraining() muidx, firstGreedyIter = [len(self.muTest) - 1], True errorEstTest, maxErrorEst = [np.inf], np.inf max2ErrorEst, trainedModelOld = np.inf, None while firstGreedyIter or (len(self.muTest) > 0 and (maxErrorEst is None or max2ErrorEst > self.greedyTol) and self.samplingEngine.nsamples < self.maxIter): muTestOld, errorEstTestOld = self.muTest, errorEstTest muidxOld, maxErrorEstOld = muidx, maxErrorEst errorEstTest, muidx, maxErrorEst, mu = self.greedyNextSample( muidx, plotEst) if maxErrorEst is not None and (np.any(np.isnan(maxErrorEst)) or np.any(np.isinf(maxErrorEst))): if self._collinearityFlag == 0 and not firstGreedyIter: RROMPyWarning(("Instability in a posteriori " "estimator. Starting preemptive greedy " "loop termination.")) self.muTest, errorEstTest = muTestOld, errorEstTestOld if firstGreedyIter: self.mus.pop(-1) self.samplingEngine.popSample() if muidx < 0: self.trainedModel = None raise RROMPyException(("Instability in approximant " "computation. Aborting greedy " "iterations.")) else: self._approxParameters = ( trainedModelOld.data.approxParameters) self._S = trainedModelOld.data.approxParameters["S"] self._approxParameters["S"] = self.S self.trainedModel.data = copy(trainedModelOld.data) muidx, maxErrorEst = muidxOld, maxErrorEstOld break if maxErrorEst is not None: max2ErrorEst = np.max(maxErrorEst) vbMng(self, "MAIN", ("Uniform testing error estimate " "{:.4e}.").format(max2ErrorEst), 2) if firstGreedyIter: trainedModelOld = copy(self.trainedModel) else: trainedModelOld.data = copy(self.trainedModel.data) firstGreedyIter = False if (maxErrorEst is None or max2ErrorEst <= self.greedyTol or np.any(np.isnan(maxErrorEst)) or np.any(np.isinf(maxErrorEst))): while self.samplingEngine.nsamples > self.S: self.samplingEngine.popSample() while len(self.mus) > self.S: self.mus.pop(-1) else: self._S = self.samplingEngine.nsamples self._approxParameters["S"] = self.S while len(self.mus) < self.S: self.mus.append(self.samplingEngine.mus[len(self.mus)]) self.setupApproxLocal() if plotEst == "LAST": self.plotEstimator(errorEstTest, muidx, maxErrorEst) vbMng(self, "DEL", ("Done computing snapshots (final snapshot count: " "{}).").format(self.samplingEngine.nsamples), 2) return 0 def checkComputedApprox(self) -> bool: """ Check if setup of new approximant is not needed. Returns: True if new setup is not needed. False otherwise. """ return (super().checkComputedApprox() and len(self.mus) == self.trainedModel.data.projMat.shape[1]) def assembleReducedResidualGramian(self, pMat:sampList): """ Build residual gramian of reduced linear system through projections. """ self.initEstimatorNormEngine() if (not hasattr(self.trainedModel.data, "gramian") or self.trainedModel.data.gramian is None): gramian = self.estimatorNormEngine.innerProduct(pMat, pMat) else: Sold = self.trainedModel.data.gramian.shape[0] S = len(self.mus) if Sold > S: gramian = self.trainedModel.data.gramian[: S, : S] else: idxOld = list(range(Sold)) idxNew = list(range(Sold, S)) gramian = np.empty((S, S), dtype = np.complex) gramian[: Sold, : Sold] = self.trainedModel.data.gramian gramian[: Sold, Sold :] = ( self.estimatorNormEngine.innerProduct(pMat(idxNew), pMat(idxOld))) gramian[Sold :, : Sold] = gramian[: Sold, Sold :].T.conj() gramian[Sold :, Sold :] = ( self.estimatorNormEngine.innerProduct(pMat(idxNew), pMat(idxNew))) self.trainedModel.data.gramian = gramian def assembleReducedResidualBlocksbb(self, bs:List[Np1D]): """ Build blocks (of type bb) of reduced linear system through projections. """ self.initEstimatorNormEngine() nbs = len(bs) if (not hasattr(self.trainedModel.data, "resbb") or self.trainedModel.data.resbb is None): resbb = np.empty((nbs, nbs), dtype = np.complex) for i in range(nbs): Mbi = bs[i] resbb[i, i] = self.estimatorNormEngine.innerProduct(Mbi, Mbi) for j in range(i): Mbj = bs[j] resbb[i, j] = self.estimatorNormEngine.innerProduct(Mbj, Mbi) for i in range(nbs): for j in range(i + 1, nbs): resbb[i, j] = resbb[j, i].conj() self.trainedModel.data.resbb = resbb def assembleReducedResidualBlocksAb(self, As:List[Np2D], bs:List[Np1D], pMat:sampList): """ Build blocks (of type Ab) of reduced linear system through projections. """ self.initEstimatorNormEngine() nAs = len(As) nbs = len(bs) S = len(self.mus) if (not hasattr(self.trainedModel.data, "resAb") or self.trainedModel.data.resAb is None): if not isinstance(pMat, (np.ndarray,)): pMat = pMat.data resAb = np.empty((nbs, S, nAs), dtype = np.complex) for j in range(nAs): MAj = dot(As[j], pMat) for i in range(nbs): Mbi = bs[i] resAb[i, :, j] = self.estimatorNormEngine.innerProduct(MAj, Mbi) else: Sold = self.trainedModel.data.resAb.shape[1] if Sold == S: return if Sold > S: resAb = self.trainedModel.data.resAb[:, : S, :] else: if not isinstance(pMat, (np.ndarray,)): pMat = pMat.data resAb = np.empty((nbs, S, nAs), dtype = np.complex) resAb[:, : Sold, :] = self.trainedModel.data.resAb for j in range(nAs): MAj = dot(As[j], pMat[:, Sold :]) for i in range(nbs): Mbi = bs[i] resAb[i, Sold :, j] = ( self.estimatorNormEngine.innerProduct(MAj, Mbi)) self.trainedModel.data.resAb = resAb def assembleReducedResidualBlocksAA(self, As:List[Np2D], pMat:sampList): """ Build blocks (of type AA) of reduced linear system through projections. """ self.initEstimatorNormEngine() nAs = len(As) S = len(self.mus) if (not hasattr(self.trainedModel.data, "resAA") or self.trainedModel.data.resAA is None): if not isinstance(pMat, (np.ndarray,)): pMat = pMat.data resAA = np.empty((S, nAs, S, nAs), dtype = np.complex) for i in range(nAs): MAi = dot(As[i], pMat) resAA[:, i, :, i] = ( self.estimatorNormEngine.innerProduct(MAi, MAi)) for j in range(i): MAj = dot(As[j], pMat) resAA[:, i, :, j] = ( self.estimatorNormEngine.innerProduct(MAj, MAi)) for i in range(nAs): for j in range(i + 1, nAs): resAA[:, i, :, j] = resAA[:, j, :, i].T.conj() else: Sold = self.trainedModel.data.resAA.shape[0] if Sold == S: return if Sold > S: resAA = self.trainedModel.data.resAA[: S, :, : S, :] else: if not isinstance(pMat, (np.ndarray,)): pMat = pMat.data resAA = np.empty((S, nAs, S, nAs), dtype = np.complex) resAA[: Sold, :, : Sold, :] = self.trainedModel.data.resAA for i in range(nAs): MAi = dot(As[i], pMat) resAA[: Sold, i, Sold :, i] = ( self.estimatorNormEngine.innerProduct(MAi[:, Sold :], MAi[:, : Sold])) resAA[Sold :, i, : Sold, i] = resAA[: Sold, i, Sold :, i].T.conj() resAA[Sold :, i, Sold :, i] = ( self.estimatorNormEngine.innerProduct(MAi[:, Sold :], MAi[:, Sold :])) for j in range(i): MAj = dot(As[j], pMat) resAA[: Sold, i, Sold :, j] = ( self.estimatorNormEngine.innerProduct(MAj[:, Sold :], MAi[:, : Sold])) resAA[Sold :, i, : Sold, j] = ( self.estimatorNormEngine.innerProduct(MAj[:, : Sold], MAi[:, Sold :])) resAA[Sold :, i, Sold :, j] = ( self.estimatorNormEngine.innerProduct(MAj[:, Sold :], MAi[:, Sold :])) for i in range(nAs): for j in range(i + 1, nAs): resAA[: Sold, i, Sold :, j] = ( resAA[Sold :, j, : Sold, i].T.conj()) resAA[Sold :, i, : Sold, j] = ( resAA[: Sold, j, Sold :, i].T.conj()) resAA[Sold :, i, Sold :, j] = ( resAA[Sold :, j, Sold :, i].T.conj()) self.trainedModel.data.resAA = resAA def assembleReducedResidualBlocks(self, full : bool = False): """Build affine blocks of affine decomposition of residual.""" if full: checkIfAffine(self.HFEngine, "assemble reduced residual blocks") else: checkIfAffine(self.HFEngine, "assemble reduced RHS blocks", True) self.HFEngine.buildb() self.assembleReducedResidualBlocksbb(self.HFEngine.bs) if full: pMat = self.samplingEngine.samples self.HFEngine.buildA() self.assembleReducedResidualBlocksAb(self.HFEngine.As, self.HFEngine.bs, pMat) self.assembleReducedResidualBlocksAA(self.HFEngine.As, pMat) diff --git a/rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py b/rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py index fbaa248..f5ca226 100644 --- a/rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py +++ b/rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py @@ -1,571 +1,572 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.hfengines.base.linear_affine_engine import checkIfAffine from .generic_greedy_approximant import GenericGreedyApproximant from rrompy.utilities.poly_fitting.polynomial import (polybases, PolynomialInterpolator as PI, polyvanderTotal as pvT) -from rrompy.utilities.numerical import totalDegreeN, dot +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.degree import totalDegreeN from rrompy.utilities.expression import expressionEvaluator from rrompy.reduction_methods.standard import RationalInterpolant from rrompy.utilities.base.types import Np1D, Tuple, paramVal, List from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.poly_fitting import customFit from rrompy.utilities.exception_manager import (RROMPyWarning, RROMPyException, RROMPyAssert, RROMPy_FRAGILE) from rrompy.parameter import checkParameterList from rrompy.sampling import sampleList, emptySampleList __all__ = ['RationalInterpolantGreedy'] class RationalInterpolantGreedy(GenericGreedyApproximant, RationalInterpolant): """ ROM greedy rational interpolant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': number of starting training points; - 'sampler': sample point generator; - 'greedyTol': uniform error tolerance for greedy algorithm; defaults to 1e-2; - 'collinearityTol': collinearity tolerance for greedy algorithm; defaults to 0.; - 'maxIter': maximum number of greedy steps; defaults to 1e2; - 'nTestPoints': number of test points; defaults to 5e2; - 'trainSetGenerator': training sample points generator; defaults to sampler; - 'polybasis': type of basis for interpolation; defaults to 'MONOMIAL'; - 'errorEstimatorKind': kind of error estimator; available values include 'AFFINE', 'DISCREPANCY', 'INTERPOLATORY', 'LOOK_AHEAD', 'LOOK_AHEAD_OUTPUT', and 'NONE'; defaults to 'NONE'; - 'interpRcond': tolerance for interpolation; defaults to None; - 'robustTol': tolerance for robust rational denominator management; defaults to 0. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults and must be True. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots. - 'greedyTol': uniform error tolerance for greedy algorithm; - 'collinearityTol': collinearity tolerance for greedy algorithm; - 'maxIter': maximum number of greedy steps; - 'nTestPoints': number of test points; - 'trainSetGenerator': training sample points generator; - 'errorEstimatorKind': kind of error estimator; - 'interpRcond': tolerance for interpolation; - 'robustTol': tolerance for robust rational denominator management. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: whether to compute POD of snapshots. S: number of test points. sampler: Sample point generator. greedyTol: uniform error tolerance for greedy algorithm. collinearityTol: Collinearity tolerance for greedy algorithm. maxIter: maximum number of greedy steps. nTestPoints: number of starting training points. trainSetGenerator: training sample points generator. robustTol: tolerance for robust rational denominator management. errorEstimatorKind: kind of error estimator. interpRcond: tolerance for interpolation. robustTol: tolerance for robust rational denominator management. muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. estimatorNormEngine: Engine for estimator norm computation. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ _allowedEstimatorKinds = ["AFFINE", "DISCREPANCY", "INTERPOLATORY", "LOOK_AHEAD", "LOOK_AHEAD_OUTPUT", "NONE"] def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(["errorEstimatorKind"], ["DISCREPANCY"], toBeExcluded = ["M", "N", "polydegreetype", "radialDirectionalWeights", "nNearestNeighbor"]) super().__init__(*args, **kwargs) if not self.approx_state and self.errorEstimatorKind not in [ "LOOK_AHEAD", "LOOK_AHEAD_OUTPUT", "NONE"]: raise RROMPyException(("Must compute greedy approximation of " "state, unless error estimator allows " "otherwise.")) self.M, self.N = ("AUTO",) * 2 self._postInit() @property def approx_state(self): """Value of approx_state.""" return self._approx_state @approx_state.setter def approx_state(self, approx_state): RationalInterpolant.approx_state.fset(self, approx_state) if (not self.approx_state and hasattr(self, "_errorEstimatorKind") and self.errorEstimatorKind not in [ "LOOK_AHEAD", "LOOK_AHEAD_OUTPUT", "NONE"]): raise RROMPyException(("Must compute greedy approximation of " "state, unless error estimator allows " "otherwise.")) @property def E(self): """Value of E.""" self._E = self.sampleBatchIdx - 1 return self._E @E.setter def E(self, E): RROMPyWarning(("E is used just to simplify inheritance, and its value " "cannot be changed from that of sampleBatchIdx - 1.")) def _setMAuto(self): self.M = self.E def _setNAuto(self): self.N = self.E @property def polydegreetype(self): """Value of polydegreetype.""" return "TOTAL" @polydegreetype.setter def polydegreetype(self, polydegreetype): RROMPyWarning(("polydegreetype is used just to simplify inheritance, " "and its value cannot be changed from 'TOTAL'.")) @property def polybasis(self): """Value of polybasis.""" return self._polybasis @polybasis.setter def polybasis(self, polybasis): try: polybasis = polybasis.upper().strip().replace(" ","") if polybasis not in polybases: raise RROMPyException("Sample type not recognized.") self._polybasis = polybasis except: RROMPyWarning(("Prescribed polybasis not recognized. Overriding " "to 'MONOMIAL'.")) self._polybasis = "MONOMIAL" self._approxParameters["polybasis"] = self.polybasis @property def errorEstimatorKind(self): """Value of errorEstimatorKind.""" return self._errorEstimatorKind @errorEstimatorKind.setter def errorEstimatorKind(self, errorEstimatorKind): errorEstimatorKind = errorEstimatorKind.upper() if errorEstimatorKind not in self._allowedEstimatorKinds: RROMPyWarning(("Error estimator kind not recognized. Overriding " "to 'NONE'.")) errorEstimatorKind = "NONE" self._errorEstimatorKind = errorEstimatorKind self._approxParameters["errorEstimatorKind"] = self.errorEstimatorKind if (self.errorEstimatorKind not in [ "LOOK_AHEAD", "LOOK_AHEAD_OUTPUT", "NONE"] and hasattr(self, "_approx_state") and not self.approx_state): raise RROMPyException(("Must compute greedy approximation of " "state, unless error estimator allows " "otherwise.")) def _polyvanderAuxiliary(self, mus, deg, *args): return pvT(mus, deg, *args) def getErrorEstimatorDiscrepancy(self, mus:Np1D) -> Np1D: """Discrepancy-based residual estimator.""" checkIfAffine(self.HFEngine, "apply discrepancy-based error estimator") mus = checkParameterList(mus, self.npar)[0] muCTest = self.trainedModel.centerNormalize(mus) verb = self.trainedModel.verbosity self.trainedModel.verbosity = 0 QTest = self.trainedModel.getQVal(mus) QTzero = np.where(QTest == 0.)[0] if len(QTzero) > 0: RROMPyWarning(("Adjusting estimator to avoid division by " "numerically zero denominator.")) QTest[QTzero] = np.finfo(np.complex).eps / (1. + self.N) self.HFEngine.buildA() self.HFEngine.buildb() nAs, nbs = self.HFEngine.nAs, self.HFEngine.nbs muTrainEff = self.mus ** self.HFEngine.rescalingExp muTestEff = mus ** self.HFEngine.rescalingExp PTrain = self.trainedModel.getPVal(self.mus).data.T QTrain = self.trainedModel.getQVal(self.mus) QTzero = np.where(QTrain == 0.)[0] if len(QTzero) > 0: RROMPyWarning(("Adjusting estimator to avoid division by " "numerically zero denominator.")) QTrain[QTzero] = np.finfo(np.complex).eps / (1. + self.N) PTest = self.trainedModel.getPVal(mus).data radiusAbTrain = np.empty((self.S, nAs * self.S + nbs), dtype = np.complex) radiusA = np.empty((self.S, nAs, len(mus)), dtype = np.complex) radiusb = np.empty((nbs, len(mus)), dtype = np.complex) for j, thA in enumerate(self.HFEngine.thAs): idxs = j * self.S + np.arange(self.S) radiusAbTrain[:, idxs] = expressionEvaluator(thA[0], muTrainEff, (self.S, 1)) * PTrain radiusA[:, j] = PTest * expressionEvaluator(thA[0], muTestEff, (len(mus),)) for j, thb in enumerate(self.HFEngine.thbs): idx = nAs * self.S + j radiusAbTrain[:, idx] = QTrain * expressionEvaluator(thb[0], muTrainEff, (self.S,)) radiusb[j] = QTest * expressionEvaluator(thb[0], muTestEff, (len(mus),)) QRHSNorm2 = self._affineResidualMatricesContraction(radiusb) vanTrain = self._polyvanderAuxiliary(self._musUniqueCN, self.E, self.polybasis0, self._derIdxs, self._reorder) interpPQ = customFit(vanTrain, radiusAbTrain, rcond = self.interpRcond) vanTest = self._polyvanderAuxiliary(muCTest, self.E, self.polybasis0) DradiusAb = vanTest.dot(interpPQ) radiusA = (radiusA - DradiusAb[:, : - nbs].reshape(len(mus), -1, self.S).T) radiusb = radiusb - DradiusAb[:, - nbs :].T ff, Lf, LL = self._affineResidualMatricesContraction(radiusb, radiusA) err = np.abs((LL - 2. * np.real(Lf) + ff) / QRHSNorm2) ** .5 self.trainedModel.verbosity = verb return err def getErrorEstimatorInterpolatory(self, mus:Np1D) -> Np1D: """Interpolation-based residual estimator.""" errTest, QTest, idxMaxEst = self._EIMStep(mus) self.initEstimatorNormEngine() mu_muTestSample = mus[idxMaxEst] app_muTestSample = self.getApproxReduced(mu_muTestSample) if self._mode == RROMPy_FRAGILE: if not self.HFEngine.isCEye: raise RROMPyException(("Cannot compute INTERPOLATORY residual " "estimator in fragile mode for " "non-scalar C.")) app_muTestSample = dot(self.trainedModel.data.projMat, app_muTestSample.data) else: app_muTestSample = dot(self.samplingEngine.samples, app_muTestSample) resmu = self.HFEngine.residual(mu_muTestSample, app_muTestSample, post_c = False) RHSmu = self.HFEngine.residual(mu_muTestSample, None, post_c = False) ressamples = (self.estimatorNormEngine.norm(resmu) / self.estimatorNormEngine.norm(RHSmu)) musT = copy(self.mus) musT.append(mu_muTestSample) musT = self.trainedModel.centerNormalize(musT) musC = self.trainedModel.centerNormalize(mus) resT = np.zeros(len(musT), dtype = np.complex) err = np.zeros(len(mus)) for l in range(len(mu_muTestSample)): resT[len(self.mus) + l] = ressamples[l] * QTest[idxMaxEst[l]] p = PI() wellCond, msg = p.setupByInterpolation(musT, resT, self.E + 1, self.polybasis, self.verbosity >= 15, True, {}, {"rcond": self.interpRcond}) err += np.abs(p(musC)) resT[len(self.mus) + l] = 0. err /= QTest vbMng(self, "MAIN", msg, 15) return err def getErrorEstimatorLookAhead(self, mus:Np1D, force_output : bool = False) \ -> Tuple[Np1D, List[int]]: """Residual estimator based on look-ahead idea.""" errTest, QTest, idxMaxEst = self._EIMStep(mus) _approx_state_old = self.approx_state if force_output and _approx_state_old: self._approx_state = False self.initEstimatorNormEngine() self._approx_state = _approx_state_old mu_muTestSample = mus[idxMaxEst] app_muTestSample = self.getApproxReduced(mu_muTestSample) if self._mode == RROMPy_FRAGILE: app_muTestSample = dot(self.trainedModel.data.projMat, app_muTestSample.data) else: app_muTestSample = dot(self.samplingEngine.samples, app_muTestSample) for j, mu in enumerate(mu_muTestSample): uEx = self.samplingEngine.nextSample(mu) if hasattr(self.samplingEngine, "samples_full"): uEx = self.samplingEngine.samples_full[-1] if j == 0: solmu = emptySampleList() solmu.reset((len(uEx), len(mu_muTestSample)), dtype = uEx.dtype) solmu[j] = uEx if force_output and self.approx_state: solmu = sampleList(self.HFEngine.applyC(solmu.data)) app_muTestSample = sampleList(self.HFEngine.applyC( app_muTestSample.data)) errmu = solmu - app_muTestSample errsamples = (self.estimatorNormEngine.norm(errmu) / self.estimatorNormEngine.norm(solmu)) musT = copy(self.mus) musT.append(mu_muTestSample) musT = self.trainedModel.centerNormalize(musT) musC = self.trainedModel.centerNormalize(mus) errT = np.zeros(len(musT), dtype = np.complex) err = np.zeros(len(mus)) for l in range(len(mu_muTestSample)): errT[len(self.mus) + l] = errsamples[l] * QTest[idxMaxEst[l]] p = PI() wellCond, msg = p.setupByInterpolation(musT, errT, self.E + 1, self.polybasis, self.verbosity >= 15, True, {}, {"rcond": self.interpRcond}) err += np.abs(p(musC)) errT[len(self.mus) + l] = 0. err /= QTest vbMng(self, "MAIN", msg, 15) return err, idxMaxEst def getErrorEstimatorNone(self, mus:Np1D) -> Np1D: """EIM-based residual estimator.""" err = np.max(self._EIMStep(mus, True), axis = 1) err *= self.greedyTol / np.mean(err) return err def _EIMStep(self, mus:Np1D, only_one : bool = False) -> Tuple[Np1D, Np1D, List[int]]: """Residual estimator based on look-ahead idea.""" mus = checkParameterList(mus, self.npar)[0] verb = self.trainedModel.verbosity self.trainedModel.verbosity = 0 QTest = self.trainedModel.getQVal(mus) QTzero = np.where(QTest == 0.)[0] if len(QTzero) > 0: RROMPyWarning(("Adjusting estimator to avoid division by " "numerically zero denominator.")) QTest[QTzero] = np.finfo(np.complex).eps / (1. + self.N) QTest = np.abs(QTest) muCTest = self.trainedModel.centerNormalize(mus) muCTrain = self.trainedModel.centerNormalize(self.mus) vanTest = self._polyvanderAuxiliary(muCTest, self.E, self.polybasis) vanTestNext = self._polyvanderAuxiliary(muCTest, self.E + 1, self.polybasis)[:, vanTest.shape[1] :] idxsTest = np.arange(vanTestNext.shape[1]) basis = np.zeros((len(idxsTest), 0), dtype = float) idxMaxEst = [] while len(idxsTest) > 0: vanTrial = self._polyvanderAuxiliary(muCTrain, self.E, self.polybasis) vanTrialNext = self._polyvanderAuxiliary(muCTrain, self.E + 1, self.polybasis)[:, vanTrial.shape[1] :] vanTrial = np.hstack((vanTrial, vanTrialNext.dot(basis).reshape( len(vanTrialNext), basis.shape[1]))) valuesTrial = vanTrialNext[:, idxsTest] vanTestEff = np.hstack((vanTest, vanTestNext.dot(basis).reshape( len(vanTestNext), basis.shape[1]))) vanTestNextEff = vanTestNext[:, idxsTest] try: coeffTest = np.linalg.solve(vanTrial, valuesTrial) except np.linalg.LinAlgError as e: raise RROMPyException(e) errTest = (np.abs(vanTestNextEff - vanTestEff.dot(coeffTest)) / np.expand_dims(QTest, 1)) if only_one: self.trainedModel.verbosity = verb return errTest idxMaxErr = np.unravel_index(np.argmax(errTest), errTest.shape) idxMaxEst += [idxMaxErr[0]] muCTrain.append(muCTest[idxMaxErr[0]]) basis = np.pad(basis, [(0, 0), (0, 1)], "constant") basis[idxsTest[idxMaxErr[1]], -1] = 1. idxsTest = np.delete(idxsTest, idxMaxErr[1]) self.trainedModel.verbosity = verb return errTest, QTest, idxMaxEst def errorEstimator(self, mus:Np1D, return_max : bool = False) -> Np1D: """Standard residual-based error estimator.""" setupOK = self.setupApproxLocal() if setupOK > 0: err = np.empty(len(mus)) err[:] = np.nan if not return_max: return err return err, - setupOK, np.nan mus = checkParameterList(mus, self.npar)[0] vbMng(self.trainedModel, "INIT", "Evaluating error estimator at mu = {}.".format(mus), 10) if self.errorEstimatorKind == "AFFINE": err = self.getErrorEstimatorAffine(mus) else: self._setupInterpolationIndices() if self.errorEstimatorKind == "DISCREPANCY": err = self.getErrorEstimatorDiscrepancy(mus) elif self.errorEstimatorKind == "INTERPOLATORY": err = self.getErrorEstimatorInterpolatory(mus) elif self.errorEstimatorKind[: 10] == "LOOK_AHEAD": err, idxMaxEst = self.getErrorEstimatorLookAhead(mus, self.errorEstimatorKind == "LOOK_AHEAD_OUTPUT") else: #if self.errorEstimatorKind == "NONE": err = self.getErrorEstimatorNone(mus) vbMng(self.trainedModel, "DEL", "Done evaluating error estimator", 10) if not return_max: return err if self.errorEstimatorKind[: 10] != "LOOK_AHEAD": idxMaxEst = np.empty(self.sampleBatchSize, dtype = int) errCP = copy(err) for j in range(self.sampleBatchSize): k = np.argmax(errCP) idxMaxEst[j] = k if j + 1 < self.sampleBatchSize: musZero = self.trainedModel.centerNormalize(mus, mus[k]) errCP *= np.linalg.norm(musZero.data, axis = 1) return err, idxMaxEst, err[idxMaxEst] def plotEstimator(self, *args, **kwargs): super().plotEstimator(*args, **kwargs) if self.errorEstimatorKind == "NONE": vbMng(self, "MAIN", ("Warning! Error estimator has been arbitrarily normalized " "before plotting."), 5) def greedyNextSample(self, *args, **kwargs) -> Tuple[Np1D, int, float, paramVal]: """Compute next greedy snapshot of solution map.""" RROMPyAssert(self._mode, message = "Cannot add greedy sample.") self.sampleBatchIdx += 1 self.sampleBatchSize = totalDegreeN(self.npar - 1, self.sampleBatchIdx) err, muidx, maxErr, muNext = super().greedyNextSample(*args, **kwargs) if maxErr is not None and (np.any(np.isnan(maxErr)) or np.any(np.isinf(maxErr))): self.sampleBatchIdx -= 1 self.sampleBatchSize = totalDegreeN(self.npar - 1, self.sampleBatchIdx) if (self.errorEstimatorKind == "NONE" and not np.isnan(maxErr) and not np.isinf(maxErr)): maxErr = None return err, muidx, maxErr, muNext def _preliminaryTraining(self): """Initialize starting snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") if self.samplingEngine.nsamples > 0: return S = self.S self.sampleBatchIdx, self.sampleBatchSize, self._S = -1, 0, 0 nextBatchSize = 1 while self._S + nextBatchSize <= S: self.sampleBatchIdx += 1 self.sampleBatchSize = nextBatchSize self._S += self.sampleBatchSize nextBatchSize = totalDegreeN(self.npar - 1, self.sampleBatchIdx + 1) super()._preliminaryTraining() def setupApproxLocal(self) -> int: """Compute rational interpolant.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") self.verbosity -= 10 vbMng(self, "INIT", "Setting up local approximant.", 5) pMat = self.samplingEngine.samples.data pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat if self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": pMatEff, "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp} self.trainedModel.data = self.initializeModelData(datadict)[0] else: self.trainedModel = self.trainedModel self.trainedModel.data.projMat = copy(pMatEff) self.trainedModel.data.mus = copy(self.mus) self.trainedModel.data.mus = copy(self.mus) self.catchInstability = 2 unstable = False if self.E > 0: try: Q = self._setupDenominator()[0] except RROMPyException as RE: RROMPyWarning("Downgraded {}: {}".format(RE.__class__.__name__, RE)) vbMng(self, "DEL", "", 7) unstable = True else: Q = PI() Q.coeffs = np.ones((1,) * self.npar, dtype = np.complex) Q.npar = self.npar Q.polybasis = self.polybasis if not unstable: self.trainedModel.data.Q = copy(Q) try: P = copy(self._setupNumerator()) except RROMPyException as RE: RROMPyWarning("Downgraded {}: {}".format(RE.__class__.__name__, RE)) vbMng(self, "DEL", "", 7) unstable = True if not unstable: self.trainedModel.data.P = copy(P) self.trainedModel.data.approxParameters = copy( self.approxParameters) vbMng(self, "DEL", "Done setting up local approximant.", 5) self.catchInstability = 0 self.verbosity += 10 return 1 * unstable def loadTrainedModel(self, filename:str): """Load trained reduced model from file.""" super().loadTrainedModel(filename) self.sampleBatchIdx, self.sampleBatchSize, _S = -1, 0, 0 nextBatchSize = 1 while _S + nextBatchSize <= self.S + 1: self.sampleBatchIdx += 1 self.sampleBatchSize = nextBatchSize _S += self.sampleBatchSize nextBatchSize = totalDegreeN(self.npar - 1, self.sampleBatchIdx + 1) diff --git a/rrompy/reduction_methods/standard/nearest_neighbor.py b/rrompy/reduction_methods/standard/nearest_neighbor.py index fa4bab9..31a6f44 100644 --- a/rrompy/reduction_methods/standard/nearest_neighbor.py +++ b/rrompy/reduction_methods/standard/nearest_neighbor.py @@ -1,111 +1,112 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy from .generic_standard_approximant import GenericStandardApproximant from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.numerical import dot from rrompy.utilities.exception_manager import RROMPyAssert __all__ = ['NearestNeighbor'] class NearestNeighbor(GenericStandardApproximant): """ ROM nearest neighbor approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults and must be True. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ @property def tModelType(self): - from .trained_model import TrainedModelNearestNeighbor + from .trained_model.trained_model_nearest_neighbor import ( + TrainedModelNearestNeighbor) return TrainedModelNearestNeighbor def setupApprox(self) -> int: """Compute RB projection matrix.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 5) self.computeScaleFactor() self.computeSnapshots() pMat = self.samplingEngine.samples.data pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat if self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": pMatEff, "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp} self.trainedModel.data = self.initializeModelData(datadict)[0] else: self.trainedModel = self.trainedModel self.trainedModel.data.projMat = copy(pMatEff) if self.POD: self.trainedModel.data.matR = copy(self.samplingEngine.RPOD) else: #self.trainedModel.data.matR = np.eye(self.S) self.trainedModel.data.matR = 1. self.trainedModel.data.mus = copy(self.mus) self.trainedModel.data.approxParameters = copy(self.approxParameters) vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 diff --git a/rrompy/reduction_methods/standard/rational_interpolant.py b/rrompy/reduction_methods/standard/rational_interpolant.py index a510443..68d0b16 100644 --- a/rrompy/reduction_methods/standard/rational_interpolant.py +++ b/rrompy/reduction_methods/standard/rational_interpolant.py @@ -1,686 +1,687 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.reduction_methods.base import checkRobustTolerance from .generic_standard_approximant import GenericStandardApproximant from rrompy.utilities.poly_fitting.polynomial import ( polybases as ppb, polyfitname, polyvander as pvP, polyvanderTotal as pvTP, polyTimes, polyTimesTable, vanderInvTable, PolynomialInterpolator as PI) from rrompy.utilities.poly_fitting.heaviside import rational2heaviside from rrompy.utilities.poly_fitting.radial_basis import (polybases as rbpb, RadialBasisInterpolator as RBI) from rrompy.utilities.poly_fitting.moving_least_squares import ( polybases as mlspb, MovingLeastSquaresInterpolator as MLSI) from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List, sampList from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical import (customPInv, dot, reduceDegreeN, - degreeTotalToFull, fullDegreeMaxMask, - totalDegreeMaxMask, - nextDerivativeIndices, potential) +from rrompy.utilities.numerical import customPInv, dot, potential +from rrompy.utilities.numerical.hash_derivative import nextDerivativeIndices +from rrompy.utilities.numerical.degree import (reduceDegreeN, + degreeTotalToFull, fullDegreeMaxMask, + totalDegreeMaxMask) from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) __all__ = ['RationalInterpolant'] class RationalInterpolant(GenericStandardApproximant): """ ROM rational interpolant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator; - 'polybasis': type of polynomial basis for interpolation; defaults to 'MONOMIAL'; - 'M': degree of rational interpolant numerator; defaults to 'AUTO', i.e. maximum allowed; - 'N': degree of rational interpolant denominator; defaults to 'AUTO', i.e. maximum allowed; - 'polydegreetype': type of polynomial degree; defaults to 'TOTAL'; - 'radialDirectionalWeights': radial basis weights for interpolant numerator; defaults to 1; - 'nNearestNeighbor': mumber of nearest neighbors considered in numerator if polybasis allows; defaults to -1; - 'interpRcond': tolerance for interpolation; defaults to None; - 'robustTol': tolerance for robust rational denominator management; defaults to 0; - 'correctorForce': whether corrector should forcefully delete bad poles; defaults to False; - 'correctorTol': tolerance for corrector step; defaults to 0., i.e. no bad poles; - 'correctorMaxIter': maximum number of corrector iterations; defaults to 1. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'polybasis': type of polynomial basis for interpolation; - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'polydegreetype': type of polynomial degree; - 'radialDirectionalWeights': radial basis weights for interpolant numerator; - 'nNearestNeighbor': mumber of nearest neighbors considered in numerator if polybasis allows; - 'interpRcond': tolerance for interpolation via numpy.polyfit; - 'robustTol': tolerance for robust rational denominator management; - 'correctorForce': whether corrector should forcefully delete bad poles; - 'correctorTol': tolerance for corrector step; - 'correctorMaxIter': maximum number of corrector iterations. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. polybasis: type of polynomial basis for interpolation. M: Numerator degree of approximant. N: Denominator degree of approximant. polydegreetype: Type of polynomial degree. radialDirectionalWeights: Radial basis weights for interpolant numerator. nNearestNeighbor: Number of nearest neighbors considered in numerator if polybasis allows. interpRcond: Tolerance for interpolation via numpy.polyfit. robustTol: Tolerance for robust rational denominator management. correctorForce: Whether corrector should forcefully delete bad poles. correctorTol: Tolerance for corrector step. correctorMaxIter: Maximum number of corrector iterations. muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. Q: Numpy 1D vector containing complex coefficients of approximant denominator. P: Numpy 2D vector whose columns are FE dofs of coefficients of approximant numerator. """ def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(["polybasis", "M", "N", "polydegreetype", "radialDirectionalWeights", "nNearestNeighbor", "interpRcond", "robustTol", "correctorForce", "correctorTol", "correctorMaxIter"], ["MONOMIAL", "AUTO", "AUTO", "TOTAL", [1], -1, -1, 0, False, 0., 1]) super().__init__(*args, **kwargs) self.catchInstability = 0 self._postInit() @property def tModelType(self): - from .trained_model import TrainedModelRational + from .trained_model.trained_model_rational import TrainedModelRational return TrainedModelRational @property def polybasis(self): """Value of polybasis.""" return self._polybasis @polybasis.setter def polybasis(self, polybasis): try: polybasis = polybasis.upper().strip().replace(" ","") if polybasis not in ppb + rbpb + mlspb: raise RROMPyException("Prescribed polybasis not recognized.") self._polybasis = polybasis except: RROMPyWarning(("Prescribed polybasis not recognized. Overriding " "to 'MONOMIAL'.")) self._polybasis = "MONOMIAL" self._approxParameters["polybasis"] = self.polybasis @property def polybasis0(self): if "_" in self.polybasis: return self.polybasis.split("_")[0] return self.polybasis @property def interpRcond(self): """Value of interpRcond.""" return self._interpRcond @interpRcond.setter def interpRcond(self, interpRcond): self._interpRcond = interpRcond self._approxParameters["interpRcond"] = self.interpRcond @property def radialDirectionalWeights(self): """Value of radialDirectionalWeights.""" return self._radialDirectionalWeights @radialDirectionalWeights.setter def radialDirectionalWeights(self, radialDirectionalWeights): if not hasattr(radialDirectionalWeights, "__len__"): radialDirectionalWeights = [radialDirectionalWeights] self._radialDirectionalWeights = radialDirectionalWeights self._approxParameters["radialDirectionalWeights"] = ( self.radialDirectionalWeights) @property def nNearestNeighbor(self): """Value of nNearestNeighbor.""" return self._nNearestNeighbor @nNearestNeighbor.setter def nNearestNeighbor(self, nNearestNeighbor): self._nNearestNeighbor = nNearestNeighbor self._approxParameters["nNearestNeighbor"] = self.nNearestNeighbor @property def M(self): """Value of M.""" return self._M @M.setter def M(self, M): if isinstance(M, str): M = M.strip().replace(" ","") if "-" not in M: M = M + "-0" self._M_isauto, self._M_shift = True, int(M.split("-")[-1]) M = 0 if M < 0: raise RROMPyException("M must be non-negative.") self._M = M self._approxParameters["M"] = self.M def _setMAuto(self): self.M = max(0, reduceDegreeN(self.S, self.S, self.npar, self.polydegreetype) - self._M_shift) vbMng(self, "MAIN", "Automatically setting M to {}.".format(self.M), 25) @property def N(self): """Value of N.""" return self._N @N.setter def N(self, N): if isinstance(N, str): N = N.strip().replace(" ","") if "-" not in N: N = N + "-0" self._N_isauto, self._N_shift = True, int(N.split("-")[-1]) N = 0 if N < 0: raise RROMPyException("N must be non-negative.") self._N = N self._approxParameters["N"] = self.N def _setNAuto(self): self.N = max(0, reduceDegreeN(self.S, self.S, self.npar, self.polydegreetype) - self._N_shift) vbMng(self, "MAIN", "Automatically setting N to {}.".format(self.N), 25) @property def polydegreetype(self): """Value of polydegreetype.""" return self._polydegreetype @polydegreetype.setter def polydegreetype(self, polydegreetype): try: polydegreetype = polydegreetype.upper().strip().replace(" ","") if polydegreetype not in ["TOTAL", "FULL"]: raise RROMPyException(("Prescribed polydegreetype not " "recognized.")) self._polydegreetype = polydegreetype except: RROMPyWarning(("Prescribed polydegreetype not recognized. " "Overriding to 'TOTAL'.")) self._polydegreetype = "TOTAL" self._approxParameters["polydegreetype"] = self.polydegreetype @property def robustTol(self): """Value of tolerance for robust rational denominator management.""" return self._robustTol @robustTol.setter def robustTol(self, robustTol): if robustTol < 0.: RROMPyWarning(("Overriding prescribed negative robustness " "tolerance to 0.")) robustTol = 0. self._robustTol = robustTol self._approxParameters["robustTol"] = self.robustTol @property def correctorForce(self): """Value of correctorForce.""" return self._correctorForce @correctorForce.setter def correctorForce(self, correctorForce): self._correctorForce = correctorForce self._approxParameters["correctorForce"] = self.correctorForce @property def correctorTol(self): """Value of correctorTol.""" return self._correctorTol @correctorTol.setter def correctorTol(self, correctorTol): if correctorTol < 0. or (correctorTol > 0. and self.npar > 1): RROMPyWarning(("Overriding prescribed corrector tolerance " "to 0.")) correctorTol = 0. self._correctorTol = correctorTol self._approxParameters["correctorTol"] = self.correctorTol @property def correctorMaxIter(self): """Value of correctorMaxIter.""" return self._correctorMaxIter @correctorMaxIter.setter def correctorMaxIter(self, correctorMaxIter): if correctorMaxIter < 1 or (correctorMaxIter > 1 and self.npar > 1): RROMPyWarning(("Overriding prescribed max number of corrector " "iterations to 1.")) correctorMaxIter = 1 self._correctorMaxIter = correctorMaxIter self._approxParameters["correctorMaxIter"] = self.correctorMaxIter def resetSamples(self): """Reset samples.""" super().resetSamples() self._musUniqueCN = None self._derIdxs = None self._reorder = None def _setupInterpolationIndices(self): """Setup parameters for polyvander.""" if self._musUniqueCN is None or len(self._reorder) != len(self.mus): self._musUniqueCN, musIdxsTo, musIdxs, musCount = ( self.trainedModel.centerNormalize(self.mus).unique( return_index = True, return_inverse = True, return_counts = True)) self._musUnique = self.mus[musIdxsTo] self._derIdxs = [None] * len(self._musUniqueCN) self._reorder = np.empty(len(musIdxs), dtype = int) filled = 0 for j, cnt in enumerate(musCount): self._derIdxs[j] = nextDerivativeIndices([], self.mus.shape[1], cnt) jIdx = np.nonzero(musIdxs == j)[0] self._reorder[jIdx] = np.arange(filled, filled + cnt) filled += cnt def _setupDenominator(self): """Compute rational denominator.""" RROMPyAssert(self._mode, message = "Cannot setup denominator.") vbMng(self, "INIT", "Starting computation of denominator.", 7) if hasattr(self, "_N_isauto"): self._setNAuto() else: N = reduceDegreeN(self.N, self.S, self.npar, self.polydegreetype) if N < self.N: RROMPyWarning(("N too large compared to S. Reducing N by " "{}").format(self.N - N)) self.N = N while self.N > 0: invD, fitinv = self._computeInterpolantInverseBlocks() idxSamplesEff = list(range(self.S)) if self.POD: ev, eV = self.findeveVGQR( self.samplingEngine.RPOD[:, idxSamplesEff], invD) else: ev, eV = self.findeveVGExplicit( self.samplingEngine.samples(idxSamplesEff), invD) nevBad = checkRobustTolerance(ev, self.robustTol) if nevBad <= 1: break if self.catchInstability > 0: raise RROMPyException(("Instability in denominator " "computation: eigenproblem is poorly " "conditioned."), self.catchInstability == 1) vbMng(self, "MAIN", ("Smallest {} eigenvalues below tolerance. " "Reducing N by 1.").format(nevBad), 10) self.N = self.N - 1 if self.N <= 0: self.N = 0 eV = np.ones((1, 1)) q = PI() q.npar = self.npar q.polybasis = self.polybasis0 if self.polydegreetype == "TOTAL": q.coeffs = degreeTotalToFull(tuple([self.N + 1] * self.npar), self.npar, eV[:, 0]) else: q.coeffs = eV[:, 0].reshape([self.N + 1] * self.npar) vbMng(self, "DEL", "Done computing denominator.", 7) return q, fitinv def _setupNumerator(self): """Compute rational numerator.""" RROMPyAssert(self._mode, message = "Cannot setup numerator.") vbMng(self, "INIT", "Starting computation of numerator.", 7) self._setupInterpolationIndices() Qevaldiag = polyTimesTable(self.trainedModel.data.Q, self._musUniqueCN, self._reorder, self._derIdxs, np.power(self.scaleFactor, -1.)) if self.POD: Qevaldiag = Qevaldiag.dot(self.samplingEngine.RPOD.T) if hasattr(self, "radialDirectionalWeights"): rDW = copy(self.radialDirectionalWeights) if hasattr(self, "_M_isauto"): self._setMAuto() M = self.M else: M = reduceDegreeN(self.M, self.S, self.npar, self.polydegreetype) if M < self.M: RROMPyWarning(("M too large compared to S. Reducing M by " "{}").format(self.M - M)) self.M = M while (self.M >= 0 and (not hasattr(self, "radialDirectionalWeights") or self.radialDirectionalWeights[0] <= rDW[0] * 2 ** 6)): pParRest = [self.verbosity >= 5, self.polydegreetype == "TOTAL", {"derIdxs": self._derIdxs, "reorder": self._reorder, "scl": np.power(self.scaleFactor, -1.)}] if self.polybasis in ppb: p = PI() else: pParRest = [self.radialDirectionalWeights] + pParRest pParRest[-1]["nNearestNeighbor"] = self.nNearestNeighbor p = RBI() if self.polybasis in rbpb else MLSI() if self.polybasis in ppb + rbpb: pParRest += [{"rcond": self.interpRcond}] wellCond, msg = p.setupByInterpolation(self._musUniqueCN, Qevaldiag, self.M, self.polybasis, *pParRest) vbMng(self, "MAIN", msg, 5) if wellCond: break if self.catchInstability > 0: raise RROMPyException(("Instability in numerator computation: " "polyfit is poorly conditioned."), self.catchInstability == 1) if self.polybasis in ppb: vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing " "M by 1."), 10) self.M = self.M - 1 else: vbMng(self, "MAIN", ("Polyfit is poorly conditioned. " "Multiplying radialDirectionalWeights by " "2."), 10) for j in range(self.npar): self._radialDirectionalWeights[j] *= 2. if self.M < 0 or (hasattr(self, "radialDirectionalWeights") and self.radialDirectionalWeights[0] > rDW[0] * 2 ** 6): raise RROMPyException(("Instability in computation of numerator. " "Aborting.")) if self.polybasis in ppb: self.M = M else: self.radialDirectionalWeights = rDW vbMng(self, "DEL", "Done computing numerator.", 7) return p def setupApprox(self) -> int: """Compute rational interpolant.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 5) self.computeSnapshots() pMat = self.samplingEngine.samples.data pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat if self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": pMatEff, "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp} self.trainedModel.data = self.initializeModelData(datadict)[0] else: self.trainedModel = self.trainedModel self.trainedModel.data.projMat = copy(pMatEff) self.trainedModel.data.mus = copy(self.mus) self._iterCorrector() self.trainedModel.data.approxParameters = copy(self.approxParameters) vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 def _iterCorrector(self): if self.correctorTol > 0. and (self.correctorMaxIter > 1 or self.correctorForce): vbMng(self, "INIT", "Starting correction iterations.", 7) self._Qhat = PI() self._Qhat.npar = self.npar self._Qhat.polybasis = "MONOMIAL" self._Qhat.coeffs = np.ones(1, dtype = np.complex) if self.POD: self._RPODOld = copy(self.samplingEngine.RPOD) else: self._samplesOld = copy(self.samplingEngine.samples) for j in range(self.correctorMaxIter): if self.N > 0 or (hasattr(self, "_N_isauto") and self.S > self.npar): Q = self._setupDenominator()[0] else: Q = PI() Q.coeffs = np.ones((1,) * self.npar, dtype = np.complex) Q.npar = self.npar Q.polybasis = self.polybasis self.N = 0 if j == 0: _N0 = self.N self.trainedModel.data.Q = Q self.trainedModel.data.P = self._setupNumerator() self._applyCorrector(j) if self.N <= 0: break self.N = _N0 if self.correctorTol <= 0. or (self.correctorMaxIter <= 1 and not self.correctorForce): return if self.POD: self.samplingEngine.RPOD = self._RPODOld del self._RPODOld else: self.samplingEngine.samples = self._samplesOld del self._samplesOld if self.correctorForce: QOld, QOldBasis = [1.], "MONOMIAL" else: QOld, QOldBasis = Q.coeffs, self.polybasis Q = polyTimes(self._Qhat.coeffs, QOld, Pbasis = self._Qhat.polybasis, Qbasis = QOldBasis, Rbasis = self.polybasis) del self._Qhat gamma = np.linalg.norm(Q) self.trainedModel.data.Q.coeffs = np.pad(Q, (0, self.N - len(Q) + 1), "constant") / gamma if self.correctorForce: self.trainedModel.data.P = self._setupNumerator() else: self.trainedModel.data.P.coeffs /= gamma vbMng(self, "DEL", "Terminated correction iterations.", 7) def _applyCorrector(self, j:int): if self.correctorTol <= 0. or (j >= self.correctorMaxIter - 1 and not self.correctorForce): self.N = 0 return cfs, pls, _ = rational2heaviside(self.trainedModel.data.P, self.trainedModel.data.Q) cfs = cfs[: self.N] if self.POD: resEff = np.linalg.norm(cfs, axis = 1) else: resEff = self.HFEngine.norm(self.samplingEngine.samples.dot(cfs.T), is_state = self.approx_state) goodPole = np.logical_not(np.isinf(pls)) potentialGood = (potential(pls[goodPole], self.sampler.normalFoci()) / self.sampler.groundPotential()) potentialGood[potentialGood < 1.] = 1. resEff[goodPole] /= potentialGood resEff /= np.max(resEff) idxKeep = np.where(resEff >= self.correctorTol)[0] vbMng(self, "MAIN", ("Correction iteration no. {}: {} out of {} residuals satisfy " "tolerance.").format(j + 1, len(idxKeep), self.N), 10) self.N -= len(idxKeep) if self.N <= 0 and not self.correctorForce: return for i in idxKeep: self._Qhat.coeffs = polyTimes(self._Qhat.coeffs, [- pls[i], 1.], Pbasis = self._Qhat.polybasis, Rbasis = self._Qhat.polybasis) self._Qhat.coeffs /= np.linalg.norm(self._Qhat.coeffs) if self.N <= 0: return vbMng(self, "MAIN", ("Removing poles at (normalized positions): " "{}.").format(pls[resEff < self.correctorTol]), 65) That = polyTimesTable(self._Qhat, self._musUniqueCN, self._reorder, self._derIdxs, np.power(self.scaleFactor, -1.)).T if self.POD: self.samplingEngine.RPOD = self._RPODOld.dot(That) else: self.samplingEngine.samples = self._samplesOld.dot(That) def _computeInterpolantInverseBlocks(self) -> Tuple[List[Np2D], Np2D]: """ Compute inverse factors for minimal interpolant target functional. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") self._setupInterpolationIndices() TEGen = pvTP if self.polydegreetype == "TOTAL" else pvP TEGenPar = [self.polybasis0, self._derIdxs, self._reorder, np.power(self.scaleFactor, -1.)] while self.N >= 0: if self.polydegreetype == "TOTAL": Neff = self.N idxsB = totalDegreeMaxMask(self.N, self.npar) else: #if self.polydegreetype == "FULL": Neff = [self.N] * self.npar idxsB = fullDegreeMaxMask(self.N, self.npar) TE = TEGen(self._musUniqueCN, Neff, *TEGenPar) fitOut = customPInv(TE, rcond = self.interpRcond, full = True) vbMng(self, "MAIN", ("Fitting {} samples with degree {} through {}... " "Conditioning of pseudoinverse system: {:.4e}.").format( TE.shape[0], self.N, polyfitname(self.polybasis0), fitOut[1][1][0] / fitOut[1][1][-1]), 5) if fitOut[1][0] == TE.shape[1]: fitinv = fitOut[0][idxsB, :] break if self.catchInstability > 0: raise RROMPyException(("Instability in denominator " "computation: polyfit is poorly " "conditioned."), self.catchInstability == 1) vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing N " "by 1."), 10) self.N = self.N - 1 if self.N < 0: raise RROMPyException(("Instability in computation of " "denominator. Aborting.")) invD = vanderInvTable(fitinv, idxsB, self._reorder, self._derIdxs) for k in range(len(invD)): invD[k] = dot(invD[k], TE) return invD, fitinv def findeveVGExplicit(self, sampleE:sampList, invD:List[Np2D]) -> Tuple[Np1D, Np2D]: """ Compute explicitly eigenvalues and eigenvectors of rational denominator matrix. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") nEnd = invD[0].shape[1] eWidth = len(invD) vbMng(self, "INIT", "Building gramian matrix.", 10) gramian = self.HFEngine.innerProduct(sampleE, sampleE, is_state = self.approx_state) G = np.zeros((nEnd, nEnd), dtype = np.complex) for k in range(eWidth): G += dot(dot(gramian, invD[k]).T, invD[k].conj()).T vbMng(self, "DEL", "Done building gramian.", 10) vbMng(self, "INIT", "Solving eigenvalue problem for gramian matrix.", 7) try: ev, eV = np.linalg.eigh(G) except np.linalg.LinAlgError as e: raise RROMPyException(e) vbMng(self, "MAIN", ("Solved eigenvalue problem of size {} with condition number " "{:.4e}.").format(nEnd, ev[-1] / ev[0]), 5) vbMng(self, "DEL", "Done solving eigenvalue problem.", 7) return ev, eV def findeveVGQR(self, RPODE:Np2D, invD:List[Np2D]) -> Tuple[Np1D, Np2D]: """ Compute eigenvalues and eigenvectors of rational denominator matrix through SVD of R factor. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") nEnd = invD[0].shape[1] S = RPODE.shape[0] eWidth = len(invD) vbMng(self, "INIT", "Building half-gramian matrix stack.", 10) Rstack = np.zeros((S * eWidth, nEnd), dtype = np.complex) for k in range(eWidth): Rstack[k * S : (k + 1) * S, :] = dot(RPODE, invD[k]) vbMng(self, "DEL", "Done building half-gramian.", 10) vbMng(self, "INIT", "Solving svd for square root of gramian matrix.", 7) try: _, s, eV = np.linalg.svd(Rstack, full_matrices = False) except np.linalg.LinAlgError as e: raise RROMPyException(e) ev = s[::-1] eV = eV[::-1, :].T.conj() vbMng(self, "MAIN", ("Solved svd problem of size {} x {} with condition number " "{:.4e}.").format(*Rstack.shape, s[0] / s[-1]), 5) vbMng(self, "DEL", "Done solving svd.", 7) return ev, eV def getResidues(self, *args, **kwargs) -> Np1D: """ Obtain approximant residues. Returns: Matrix with residues as columns. """ return self.trainedModel.getResidues(*args, **kwargs) diff --git a/rrompy/reduction_methods/standard/rational_moving_least_squares.py b/rrompy/reduction_methods/standard/rational_moving_least_squares.py index 4ced047..2a4ad31 100644 --- a/rrompy/reduction_methods/standard/rational_moving_least_squares.py +++ b/rrompy/reduction_methods/standard/rational_moving_least_squares.py @@ -1,333 +1,335 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .rational_interpolant import RationalInterpolant from rrompy.utilities.poly_fitting.polynomial import (polybases as ppb, polyvander as pvP, polyvanderTotal as pvTP) from rrompy.utilities.base.types import Np2D from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical import (fullDegreeMaxMask, totalDegreeMaxMask, - dot) +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.degree import (fullDegreeMaxMask, + totalDegreeMaxMask) from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) __all__ = ['RationalMovingLeastSquares'] class RationalMovingLeastSquares(RationalInterpolant): """ ROM rational moving LS interpolant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator; - 'polybasis': type of polynomial basis for interpolation; defaults to 'MONOMIAL'; - 'M': degree of rational interpolant numerator; defaults to 'AUTO', i.e. maximum allowed; - 'N': degree of rational interpolant denominator; defaults to 'AUTO', i.e. maximum allowed; - 'polydegreetype': type of polynomial degree; defaults to 'TOTAL'; - 'radialBasis': numerator radial basis type; defaults to 'GAUSSIAN'; - 'radialDirectionalWeights': radial basis weights for interpolant numerator; defaults to 1; - 'nNearestNeighbor': number of nearest neighbors considered in numerator if radialBasis allows; defaults to -1; - 'radialBasisDen': denominator radial basis type; defaults to 'GAUSSIAN'; - 'radialDirectionalWeightsDen': radial basis weights for interpolant denominator; defaults to 1; - 'nNearestNeighborDen': number of nearest neighbors considered in denominator if radialBasisDen allows; defaults to -1; - 'interpRcond': tolerance for interpolation; defaults to None; - 'robustTol': tolerance for robust rational denominator management; defaults to 0. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'polybasis': type of polynomial basis for interpolation; - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'polydegreetype': type of polynomial degree; - 'radialBasis': numerator radial basis type; - 'radialDirectionalWeights': radial basis weights for interpolant numerator; - 'nNearestNeighbor': number of nearest neighbors considered in numerator if radialBasis allows; - 'radialBasisDen': denominator radial basis type; - 'radialDirectionalWeightsDen': radial basis weights for interpolant denominator; - 'nNearestNeighborDen': number of nearest neighbors considered in denominator if radialBasisDen allows; - 'interpRcond': tolerance for interpolation via numpy.polyfit; - 'robustTol': tolerance for robust rational denominator management. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. polybasis: type of polynomial basis for interpolation. M: Numerator degree of approximant. N: Denominator degree of approximant. polydegreetype: Type of polynomial degree. radialBasis: Numerator radial basis type. radialDirectionalWeights: Radial basis weights for interpolant numerator. nNearestNeighbor: Number of nearest neighbors considered in numerator if radialBasis allows. radialBasisDen: Denominator radial basis type. radialDirectionalWeightsDen: Radial basis weights for interpolant denominator. nNearestNeighborDen: Number of nearest neighbors considered in denominator if radialBasisDen allows. interpRcond: Tolerance for interpolation via numpy.polyfit. robustTol: Tolerance for robust rational denominator management. muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. Q: Numpy 1D vector containing complex coefficients of approximant denominator. P: Numpy 2D vector whose columns are FE dofs of coefficients of approximant numerator. """ def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(["radialBasis", "radialBasisDen", "radialDirectionalWeightsDen", "nNearestNeighborDen"], ["GAUSSIAN", "GAUSSIAN", 1, -1], toBeExcluded = ["correctorForce", "correctorTol", "correctorMaxIter"]) super().__init__(*args, **kwargs) self.catchInstability = 0 self._postInit() @property def correctorForce(self): """Value of correctorForce.""" return False @correctorForce.setter def correctorForce(self, correctorForce): RROMPyWarning(("correctorForce is used just to simplify inheritance, " "and its value cannot be changed from False.")) @property def correctorTol(self): """Value of correctorTol.""" return 0. @correctorTol.setter def correctorTol(self, correctorTol): RROMPyWarning(("correctorTol is used just to simplify inheritance, " "and its value cannot be changed from 0.")) @property def correctorMaxIter(self): """Value of correctorMaxIter.""" return 1 @correctorMaxIter.setter def correctorMaxIter(self, correctorMaxIter): RROMPyWarning(("correctorMaxIter is used just to simplify " "inheritance, and its value cannot be changed from 1.")) @property def tModelType(self): - from .trained_model import TrainedModelRationalMLS + from .trained_model.trained_model_rational_mls import ( + TrainedModelRationalMLS) return TrainedModelRationalMLS @property def polybasis(self): """Value of polybasis.""" return self._polybasis @polybasis.setter def polybasis(self, polybasis): try: polybasis = polybasis.upper().strip().replace(" ","") if polybasis not in ppb: raise RROMPyException("Prescribed polybasis not recognized.") self._polybasis = polybasis except: RROMPyWarning(("Prescribed polybasis not recognized. Overriding " "to 'MONOMIAL'.")) self._polybasis = "MONOMIAL" self._approxParameters["polybasis"] = self.polybasis @property def radialBasis(self): """Value of radialBasis.""" return self._radialBasis @radialBasis.setter def radialBasis(self, radialBasis): self._radialBasis = radialBasis self._approxParameters["radialBasis"] = self.radialBasis @property def radialBasisDen(self): """Value of radialBasisDen.""" return self._radialBasisDen @radialBasisDen.setter def radialBasisDen(self, radialBasisDen): self._radialBasisDen = radialBasisDen self._approxParameters["radialBasisDen"] = self.radialBasisDen @property def radialDirectionalWeightsDen(self): """Value of radialDirectionalWeightsDen.""" return self._radialDirectionalWeightsDen @radialDirectionalWeightsDen.setter def radialDirectionalWeightsDen(self, radialDirectionalWeightsDen): self._radialDirectionalWeightsDen = radialDirectionalWeightsDen self._approxParameters["radialDirectionalWeightsDen"] = ( self.radialDirectionalWeightsDen) @property def nNearestNeighborDen(self): """Value of nNearestNeighborDen.""" return self._nNearestNeighborDen @nNearestNeighborDen.setter def nNearestNeighborDen(self, nNearestNeighborDen): self._nNearestNeighborDen = nNearestNeighborDen self._approxParameters["nNearestNeighborDen"] = ( self.nNearestNeighborDen) def _setupDenominator(self) -> Np2D: """Compute rational denominator.""" RROMPyAssert(self._mode, message = "Cannot setup denominator.") vbMng(self, "INIT", "Starting computation of denominator-related blocks.", 7) self._setupInterpolationIndices() if self.polydegreetype == "TOTAL": TN = pvTP(self._musUniqueCN, self.N, self.polybasis0, self._derIdxs, self._reorder, scl = np.power(self.scaleFactor, -1.)) else: #if self.polydegreetype == "FULL": TN = pvP(self._musUniqueCN, [self.N] * self.npar, self.polybasis0, self._derIdxs, self._reorder, scl = np.power(self.scaleFactor, -1.)) TNTen = np.zeros((self.S, self.S, TN.shape[1]), dtype = TN.dtype) TNTen[np.arange(self.S), np.arange(self.S)] = TN if self.POD: TNTen = dot(self.samplingEngine.RPOD, TNTen) vbMng(self, "DEL", "Done computing denominator-related blocks.", 7) return TN, TNTen def _setupNumerator(self) -> Np2D: """Compute rational numerator.""" RROMPyAssert(self._mode, message = "Cannot setup numerator.") vbMng(self, "INIT", "Starting computation of denominator-related blocks.", 7) self._setupInterpolationIndices() if self.polydegreetype == "TOTAL": TM = pvTP(self._musUniqueCN, self.M, self.polybasis0, self._derIdxs, self._reorder, scl = np.power(self.scaleFactor, -1.)) else: #if self.polydegreetype == "FULL": TM = pvP(self._musUniqueCN, [self.M] * self.npar, self.polybasis0, self._derIdxs, self._reorder, scl = np.power(self.scaleFactor, -1.)) vbMng(self, "DEL", "Done computing denominator-related blocks.", 7) return TM def setupApprox(self) -> int: """Compute rational interpolant.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 5) self.computeSnapshots() pMat = self.samplingEngine.samples.data pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat if self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": pMatEff, "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp} data = self.initializeModelData(datadict)[0] data.POD = self.POD data.polybasis = self.polybasis data.polydegreetype = self.polydegreetype data.radialBasis = self.radialBasis data.radialWeights = self.radialDirectionalWeights data.nNearestNeighbor = self.nNearestNeighbor data.radialBasisDen = self.radialBasisDen data.radialWeightsDen = self.radialDirectionalWeightsDen data.nNearestNeighborDen = self.nNearestNeighborDen data.interpRcond = self.interpRcond self.trainedModel.data = data else: self.trainedModel = self.trainedModel self.trainedModel.data.projMat = copy(pMatEff) if not self.POD: self.trainedModel.data.gramian = self.HFEngine.innerProduct( self.samplingEngine.samples, self.samplingEngine.samples, is_state = self.approx_state) self.trainedModel.data.mus = copy(self.mus) self.trainedModel.data.M = self.M self.trainedModel.data.N = self.N QVan, self.trainedModel.data.QBlocks = self._setupDenominator() self.trainedModel.data.PVan = self._setupNumerator() if self.polydegreetype == "TOTAL": degreeMaxMask = totalDegreeMaxMask else: #if self.polydegreetype == "FULL": degreeMaxMask = fullDegreeMaxMask if self.N > self.M: self.trainedModel.data.QVan = QVan self.trainedModel.data.domQIdxs = degreeMaxMask(self.N, self.npar) else: self.trainedModel.data.domQIdxs = degreeMaxMask(self.M, self.npar) self.trainedModel.data.approxParameters = copy(self.approxParameters) vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 diff --git a/rrompy/reduction_methods/standard/rational_pade.py b/rrompy/reduction_methods/standard/rational_pade.py index 545c4d4..3933917 100644 --- a/rrompy/reduction_methods/standard/rational_pade.py +++ b/rrompy/reduction_methods/standard/rational_pade.py @@ -1,315 +1,315 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.reduction_methods.base import checkRobustTolerance from .rational_interpolant import RationalInterpolant from rrompy.utilities.poly_fitting.polynomial import ( polybases as ppb, polyfitname, polyvander as pvP, polyvanderTotal as pvTP, polyTimesTable, vanderInvTable, PolynomialInterpolator as PI) from rrompy.utilities.poly_fitting.radial_basis import (polybases as rbpb, RadialBasisInterpolator as RBI) from rrompy.utilities.poly_fitting.moving_least_squares import ( MovingLeastSquaresInterpolator as MLSI) from rrompy.utilities.base.types import Np2D, Tuple, List from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical import (customPInv, dot, fullDegreeN, - totalDegreeN, reduceDegreeN, - degreeTotalToFull, fullDegreeMaxMask, - totalDegreeMaxMask) +from rrompy.utilities.numerical import customPInv, dot +from rrompy.utilities.numerical.degree import (fullDegreeN, totalDegreeN, + reduceDegreeN, degreeTotalToFull, + fullDegreeMaxMask, totalDegreeMaxMask) from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) __all__ = ['RationalPade'] class RationalPade(RationalInterpolant): """ ROM rational Pade' computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator; - 'polybasis': type of polynomial basis for interpolation; defaults to 'MONOMIAL'; - 'M': degree of rational interpolant numerator; defaults to 'AUTO', i.e. maximum allowed; - 'N': degree of rational interpolant denominator; defaults to 'AUTO', i.e. maximum allowed; - 'polydegreetype': type of polynomial degree; defaults to 'TOTAL'; - 'radialDirectionalWeights': radial basis weights for interpolant numerator; defaults to 1; - 'nNearestNeighbor': mumber of nearest neighbors considered in numerator if polybasis allows; defaults to -1; - 'interpRcond': tolerance for interpolation; defaults to None; - 'robustTol': tolerance for robust rational denominator management; defaults to 0; - 'correctorForce': whether corrector should forcefully delete bad poles; defaults to False; - 'correctorTol': tolerance for corrector step; defaults to 0., i.e. no bad poles; - 'correctorMaxIter': maximum number of corrector iterations; defaults to 1. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'polybasis': type of polynomial basis for interpolation; - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'polydegreetype': type of polynomial degree; - 'radialDirectionalWeights': radial basis weights for interpolant numerator; - 'nNearestNeighbor': mumber of nearest neighbors considered in numerator if polybasis allows; - 'interpRcond': tolerance for interpolation via numpy.polyfit; - 'robustTol': tolerance for robust rational denominator management; - 'correctorForce': whether corrector should forcefully delete bad poles; - 'correctorTol': tolerance for corrector step; - 'correctorMaxIter': maximum number of corrector iterations. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. polybasis: type of polynomial basis for interpolation. M: Numerator degree of approximant. N: Denominator degree of approximant. polydegreetype: Type of polynomial degree. radialDirectionalWeights: Radial basis weights for interpolant numerator. nNearestNeighbor: Number of nearest neighbors considered in numerator if polybasis allows. interpRcond: Tolerance for interpolation via numpy.polyfit. robustTol: Tolerance for robust rational denominator management. correctorForce: Whether corrector should forcefully delete bad poles. correctorTol: Tolerance for corrector step. correctorMaxIter: Maximum number of corrector iterations. muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. Q: Numpy 1D vector containing complex coefficients of approximant denominator. P: Numpy 2D vector whose columns are FE dofs of coefficients of approximant numerator. """ def _setupInterpolationIndices(self): """Setup parameters for polyvander.""" super()._setupInterpolationIndices() if len(self._musUniqueCN) > 1: raise RROMPyException(("Cannot apply centered-like method with " "more than one distinct sample point.")) def _setupDenominator(self): """Compute rational denominator.""" RROMPyAssert(self._mode, message = "Cannot setup denominator.") vbMng(self, "INIT", "Starting computation of denominator.", 7) cfun = totalDegreeN if self.polydegreetype == "TOTAL" else fullDegreeN if hasattr(self, "_N_isauto"): self._setNAuto() else: N = reduceDegreeN(self.N, self.S, self.npar, self.polydegreetype) if N < self.N: RROMPyWarning(("N too large compared to S. Reducing N by " "{}").format(self.N - N)) self.N = N while self.N > 0: invD, fitinv = self._computeInterpolantInverseBlocks() Seff = cfun(self.N, self.npar) idxSamplesEff = list(range(self.S - Seff, self.S)) if self.POD: ev, eV = self.findeveVGQR( self.samplingEngine.RPOD[:, idxSamplesEff], invD) else: ev, eV = self.findeveVGExplicit( self.samplingEngine.samples(idxSamplesEff), invD) nevBad = checkRobustTolerance(ev, self.robustTol) if nevBad <= 1: break if self.catchInstability > 0: raise RROMPyException(("Instability in denominator " "computation: eigenproblem is poorly " "conditioned."), self.catchInstability == 1) RROMPyWarning(("Smallest {} eigenvalues below tolerance. Reducing " "N by 1.").format(nevBad)) self.N = self.N - 1 if self.N <= 0: self.N = 0 eV = np.ones((1, 1)) q = PI() q.npar = self.npar q.polybasis = self.polybasis0 if self.polydegreetype == "TOTAL": q.coeffs = degreeTotalToFull(tuple([self.N + 1] * self.npar), self.npar, eV[:, 0]) else: q.coeffs = eV[:, 0].reshape([self.N + 1] * self.npar) vbMng(self, "DEL", "Done computing denominator.", 7) return q, fitinv def _setupNumerator(self): """Compute rational numerator.""" RROMPyAssert(self._mode, message = "Cannot setup numerator.") vbMng(self, "INIT", "Starting computation of numerator.", 7) self._setupInterpolationIndices() Qevaldiag = polyTimesTable(self.trainedModel.data.Q, self._musUniqueCN, self._reorder, self._derIdxs, np.power(self.scaleFactor, -1.)) if self.POD: Qevaldiag = Qevaldiag.dot(self.samplingEngine.RPOD.T) if hasattr(self, "radialDirectionalWeights"): rDW = copy(self.radialDirectionalWeights) cfun = totalDegreeN if self.polydegreetype == "TOTAL" else fullDegreeN if hasattr(self, "_M_isauto"): self._setMAuto() M = self.M else: M = reduceDegreeN(self.M, self.S, self.npar, self.polydegreetype) if M < self.M: RROMPyWarning(("M too large compared to S. Reducing M by " "{}").format(self.M - M)) self.M = M while (self.M >= 0 and (not hasattr(self, "radialDirectionalWeights") or self.radialDirectionalWeights[0] <= rDW[0] * 2 ** 6)): Seff = cfun(self.M, self.npar) pParRest = [self.verbosity >= 5, self.polydegreetype == "TOTAL", {"derIdxs": [self._derIdxs[0][: Seff]], "reorder": self._reorder[: Seff], "scl": np.power(self.scaleFactor, -1.)}] if self.polybasis in ppb: p = PI() else: pParRest = [self.radialDirectionalWeights] + pParRest pParRest[-1]["nNearestNeighbor"] = self.nNearestNeighbor p = RBI() if self.polybasis in rbpb else MLSI() if self.polybasis in ppb + rbpb: pParRest += [{"rcond": self.interpRcond}] wellCond, msg = p.setupByInterpolation(self._musUniqueCN, Qevaldiag[: Seff, : Seff], self.M, self.polybasis, *pParRest) vbMng(self, "MAIN", msg, 5) if wellCond: break if self.catchInstability > 0: raise RROMPyException(("Instability in numerator computation: " "polyfit is poorly conditioned."), self.catchInstability == 1) if self.polybasis in ppb: vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing " "M by 1."), 10) self.M = self.M - 1 else: vbMng(self, "MAIN", ("Polyfit is poorly conditioned. " "Multiplying radialDirectionalWeights " "by 2."), 10) for j in range(self.npar): self._radialDirectionalWeights[j] *= 2. if self.M < 0 or (hasattr(self, "radialDirectionalWeights") and self.radialDirectionalWeights[0] > rDW[0] * 2 ** 6): raise RROMPyException(("Instability in computation of numerator. " "Aborting.")) if self.polybasis in ppb: self.M = M else: self.radialDirectionalWeights = rDW vbMng(self, "DEL", "Done computing numerator.", 7) return p def _computeInterpolantInverseBlocks(self) -> Tuple[List[Np2D], Np2D]: """ Compute inverse factors for minimal interpolant target functional. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") self._setupInterpolationIndices() if self.polydegreetype == "TOTAL": cfun, TEGen = totalDegreeN, pvTP else: cfun, TEGen = fullDegreeN, pvP while self.N >= 0: Seff = cfun(self.N, self.npar) TEGenPar = [self.polybasis0, [self._derIdxs[0][: Seff]], self._reorder[: Seff], np.power(self.scaleFactor, -1.)] if self.polydegreetype == "TOTAL": Neff = self.N idxsB = totalDegreeMaxMask(self.N, self.npar) else: #if self.polydegreetype == "FULL": Neff = [self.N] * self.npar idxsB = fullDegreeMaxMask(self.N, self.npar) TE = TEGen(self._musUniqueCN, Neff, *TEGenPar) fitOut = customPInv(TE, rcond = self.interpRcond, full = True) vbMng(self, "MAIN", ("Fitting {} samples with degree {} through {}... " "Conditioning of pseudoinverse system: {:.4e}.").format( TE.shape[0], self.N, polyfitname(self.polybasis0), fitOut[1][1][0] / fitOut[1][1][-1]), 5) if fitOut[1][0] == TE.shape[1]: fitinv = fitOut[0][idxsB, :] break if self.catchInstability > 0: raise RROMPyException(("Instability in denominator " "computation: polyfit is poorly " "conditioned."), self.catchInstability == 1) vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing N " "by 1."), 10) self.N = self.N - 1 if self.N < 0: raise RROMPyException(("Instability in computation of " "denominator. Aborting.")) Seff = cfun(self.N, self.npar) invD = vanderInvTable(fitinv, idxsB, self._reorder[: Seff], [self._derIdxs[0][: Seff]]) for k in range(len(invD)): invD[k] = dot(invD[k], TE) return invD, fitinv diff --git a/rrompy/reduction_methods/standard/reduced_basis.py b/rrompy/reduction_methods/standard/reduced_basis.py index 28e0996..d6abce7 100644 --- a/rrompy/reduction_methods/standard/reduced_basis.py +++ b/rrompy/reduction_methods/standard/reduced_basis.py @@ -1,213 +1,214 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .generic_standard_approximant import GenericStandardApproximant from rrompy.hfengines.base.linear_affine_engine import checkIfAffine from rrompy.reduction_methods.base.reduced_basis_utils import \ projectAffineDecomposition from rrompy.utilities.base.types import Np1D, Np2D, List, Tuple, sampList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.numerical import dot from rrompy.utilities.exception_manager import (RROMPyWarning, RROMPyException, RROMPyAssert) __all__ = ['ReducedBasis'] class ReducedBasis(GenericStandardApproximant): """ ROM RB approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator; - 'R': rank for Galerkin projection; defaults to 'AUTO', i.e. maximum allowed; - 'PODTolerance': tolerance for snapshots POD; defaults to -1. Defaults to empty dict. approx_state(optional): Whether to approximate state. Defaults and must be True. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - 'R': rank for Galerkin projection; - 'PODTolerance': tolerance for snapshots POD. parameterListCritical: Recognized keys of critical approximant parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. R: Rank for Galerkin projection. muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uApproxReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApproxReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as sampleList. lastSolvedApprox: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(["R", "PODTolerance"], ["AUTO", -1]) super().__init__(*args, **kwargs) checkIfAffine(self.HFEngine, "apply RB method") if not self.approx_state: raise RROMPyException("Must compute RB approximation of state.") self._postInit() @property def tModelType(self): - from .trained_model import TrainedModelReducedBasis + from .trained_model.trained_model_reduced_basis import ( + TrainedModelReducedBasis) return TrainedModelReducedBasis @property def R(self): """Value of R. Its assignment may change S.""" return self._R @R.setter def R(self, R): if isinstance(R, str): R = R.strip().replace(" ","") if "-" not in R: R = R + "-0" self._R_isauto, self._R_shift = True, int(R.split("-")[-1]) R = 0 if R < 0: raise RROMPyException("R must be non-negative.") self._R = R self._approxParameters["R"] = self.R def _setRAuto(self): self.R = max(0, self.S - self._R_shift) vbMng(self, "MAIN", "Automatically setting R to {}.".format(self.R), 25) @property def PODTolerance(self): """Value of PODTolerance.""" return self._PODTolerance @PODTolerance.setter def PODTolerance(self, PODTolerance): self._PODTolerance = PODTolerance self._approxParameters["PODTolerance"] = self.PODTolerance def _setupProjectionMatrix(self): """Compute projection matrix.""" RROMPyAssert(self._mode, message = "Cannot setup numerator.") vbMng(self, "INIT", "Starting computation of projection matrix.", 7) if hasattr(self, "_R_isauto"): self._setRAuto() else: if self.S < self.R: RROMPyWarning(("R too large compared to S. Reducing R by " "{}").format(self.R - self.S)) self.S = self.S try: if self.POD: U, s, _ = np.linalg.svd(self.samplingEngine.RPOD) s = s ** 2. else: Gramian = self.HFEngine.innerProduct( self.samplingEngine.samples, self.samplingEngine.samples, is_state = True) U, s, _ = np.linalg.svd(Gramian) except np.linalg.LinAlgError as e: raise RROMPyException(e) snorm = np.cumsum(s[::-1]) / np.sum(s) nPODTrunc = min(self.S - np.argmax(snorm > self.PODTolerance), self.R) pMat = dot(self.samplingEngine.samples, U[:, : nPODTrunc]) vbMng(self, "MAIN", ("Assembling {}x{} projection matrix from {} " "samples.").format(*(pMat.shape), self.S), 5) vbMng(self, "DEL", "Done computing projection matrix.", 7) return pMat def setupApprox(self) -> int: """Compute RB projection matrix.""" if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 5) self.computeSnapshots() pMat = self._setupProjectionMatrix().data pMatEff = dot(self.HFEngine.C, pMat) if self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "projMat": pMatEff, "scaleFactor": self.scaleFactor, "rescalingExp": self.HFEngine.rescalingExp} data = self.initializeModelData(datadict)[0] data.affinePoly = self.HFEngine.affinePoly data.thAs, data.thbs = self.HFEngine.thAs, self.HFEngine.thbs self.trainedModel.data = data else: self.trainedModel = self.trainedModel self.trainedModel.data.projMat = copy(pMatEff) self.trainedModel.data.mus = copy(self.mus) ARBs, bRBs = self.assembleReducedSystem(pMat) self.trainedModel.data.ARBs = ARBs self.trainedModel.data.bRBs = bRBs self.trainedModel.data.approxParameters = copy(self.approxParameters) vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 def assembleReducedSystem(self, pMat : sampList = None, pMatOld : sampList = None)\ -> Tuple[List[Np2D], List[Np1D]]: """Build affine blocks of RB linear system through projections.""" if pMat is None: self.setupApprox() ARBs = self.trainedModel.data.ARBs bRBs = self.trainedModel.data.bRBs else: self.HFEngine.buildA() self.HFEngine.buildb() vbMng(self, "INIT", "Projecting affine terms of HF model.", 10) ARBsOld = None if pMatOld is None else self.trainedModel.data.ARBs bRBsOld = None if pMatOld is None else self.trainedModel.data.bRBs ARBs, bRBs = projectAffineDecomposition(self.HFEngine.As, self.HFEngine.bs, pMat, ARBsOld, bRBsOld, pMatOld) vbMng(self, "DEL", "Done projecting affine terms.", 10) return ARBs, bRBs diff --git a/rrompy/reduction_methods/standard/trained_model/__init__.py b/rrompy/reduction_methods/standard/trained_model/__init__.py index 432662b..ed60590 100644 --- a/rrompy/reduction_methods/standard/trained_model/__init__.py +++ b/rrompy/reduction_methods/standard/trained_model/__init__.py @@ -1,31 +1,18 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .trained_model_nearest_neighbor import TrainedModelNearestNeighbor -from .trained_model_rational import TrainedModelRational -from .trained_model_rational_mls import TrainedModelRationalMLS -from .trained_model_reduced_basis import TrainedModelReducedBasis - -__all__ = [ - 'TrainedModelNearestNeighbor', - 'TrainedModelRational', - 'TrainedModelRationalMLS', - 'TrainedModelReducedBasis' - ] - - diff --git a/rrompy/reduction_methods/standard/trained_model/trained_model_nearest_neighbor.py b/rrompy/reduction_methods/standard/trained_model/trained_model_nearest_neighbor.py index 5e2623a..9d763bd 100644 --- a/rrompy/reduction_methods/standard/trained_model/trained_model_nearest_neighbor.py +++ b/rrompy/reduction_methods/standard/trained_model/trained_model_nearest_neighbor.py @@ -1,100 +1,101 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from numbers import Number -from rrompy.reduction_methods.base.trained_model import TrainedModel +from rrompy.reduction_methods.base.trained_model.trained_model import ( + TrainedModel) from rrompy.utilities.base.types import Np1D, paramVal, paramList, sampList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.parameter import checkParameterList from rrompy.sampling import sampleList __all__ = ['TrainedModelNearestNeighbor'] class TrainedModelNearestNeighbor(TrainedModel): """ ROM approximant evaluation for nearest neighbor approximant. Attributes: Data: dictionary with all that can be pickled. """ def centerNormalize(self, mu : paramList = [], mu0 : paramVal = None) -> paramList: """ Compute normalized parameter to be plugged into approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.data.mu0. Returns: Normalized parameter. """ mu = checkParameterList(mu, self.data.npar)[0] if mu0 is None: mu0 = self.data.mu0 rad = ((mu ** self.data.rescalingExp - mu0 ** self.data.rescalingExp) / self.data.scaleFactor) return rad def getNearestNeighbor(self, mu : paramList = []) -> Np1D: """ Find nearest neighbor to arbitrary parameter. Args: mu: Target parameter. """ mu = checkParameterList(mu, self.data.npar)[0] vbMng(self, "INIT", "Finding nearest neighbor to mu = {}.".format(mu), 22) if not hasattr(self.data, "musCentered"): self.data.musCentered = self.centerNormalize(self.data.mus) muTile = np.repeat(self.data.musCentered.data.reshape( self.data.musCentered.shape + (1,)), len(mu), axis = -1) muCN = self.centerNormalize(mu).data.T ids = np.argmin(np.linalg.norm(muTile - muCN, axis = 1), axis = 0) vbMng(self, "DEL", "Done finding nearest neighbor.", 22) return ids def getApproxReduced(self, mu : paramList = []) -> sampList: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. """ mu = checkParameterList(mu, self.data.npar)[0] if (not hasattr(self, "lastSolvedApproxReduced") or self.lastSolvedApproxReduced != mu): vbMng(self, "INIT", "Evaluating approximant at mu = {}.".format(mu), 12) ids = self.getNearestNeighbor(mu) if isinstance(self.data.matR, Number): matRed = np.zeros((self.data.projMat.shape[1], len(ids))) matRed[ids, np.arange(len(ids))] = self.data.matR else: matRed = self.data.matR[:, ids] self.uApproxReduced = sampleList(matRed) vbMng(self, "DEL", "Done evaluating approximant.", 12) self.lastSolvedApproxReduced = mu return self.uApproxReduced def getPoles(self, *args, **kwargs) -> Np1D: """Obtain approximant poles.""" return np.empty(0) diff --git a/rrompy/reduction_methods/standard/trained_model/trained_model_rational.py b/rrompy/reduction_methods/standard/trained_model/trained_model_rational.py index f21518f..0c03679 100644 --- a/rrompy/reduction_methods/standard/trained_model/trained_model_rational.py +++ b/rrompy/reduction_methods/standard/trained_model/trained_model_rational.py @@ -1,176 +1,177 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np -from rrompy.reduction_methods.base.trained_model import TrainedModel +from rrompy.reduction_methods.base.trained_model.trained_model import ( + TrainedModel) from rrompy.utilities.base.types import (Np1D, List, paramVal, paramList, sampList) from rrompy.utilities.base import verbosityManager as vbMng, freepar as fp from rrompy.utilities.exception_manager import RROMPyException, RROMPyWarning from rrompy.parameter import (checkParameter, checkParameterList, emptyParameterList) from rrompy.sampling import sampleList __all__ = ['TrainedModelRational'] class TrainedModelRational(TrainedModel): """ ROM approximant evaluation for rational approximant. Attributes: Data: dictionary with all that can be pickled. """ def centerNormalize(self, mu : paramList = [], mu0 : paramVal = None) -> paramList: """ Compute normalized parameter to be plugged into approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.data.mu0. Returns: Normalized parameter. """ mu = checkParameterList(mu, self.data.npar)[0] if mu0 is None: mu0 = self.data.mu0 rad = ((mu ** self.data.rescalingExp - mu0 ** self.data.rescalingExp) / self.data.scaleFactor) return rad def getPVal(self, mu : paramList = []) -> sampList: """ Evaluate rational numerator at arbitrary parameter. Args: mu: Target parameter. """ mu = checkParameterList(mu, self.data.npar)[0] vbMng(self, "INIT", "Evaluating numerator at mu = {}.".format(mu), 17) muCenter = self.centerNormalize(mu) p = sampleList(self.data.P(muCenter)) vbMng(self, "DEL", "Done evaluating numerator.", 17) return p def getQVal(self, mu:Np1D, der : List[int] = None, scl : Np1D = None) -> Np1D: """ Evaluate rational denominator at arbitrary parameter. Args: mu: Target parameter. der(optional): Derivatives to take before evaluation. """ mu = checkParameterList(mu, self.data.npar)[0] vbMng(self, "INIT", "Evaluating denominator at mu = {}.".format(mu), 17) muCenter = self.centerNormalize(mu) q = self.data.Q(muCenter, der, scl) vbMng(self, "DEL", "Done evaluating denominator.", 17) return q def getApproxReduced(self, mu : paramList = []) -> sampList: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. """ mu = checkParameterList(mu, self.data.npar)[0] if (not hasattr(self, "lastSolvedApproxReduced") or self.lastSolvedApproxReduced != mu): vbMng(self, "INIT", "Evaluating approximant at mu = {}.".format(mu), 12) QV = self.getQVal(mu) QVzero = np.where(QV == 0.)[0] if len(QVzero) > 0: RROMPyWarning(("Adjusting approximation to avoid division by " "numerically zero denominator.")) QV[QVzero] = np.finfo(np.complex).eps / (1. + self.data.Q.deg[0]) self.uApproxReduced = self.getPVal(mu) / QV vbMng(self, "DEL", "Done evaluating approximant.", 12) self.lastSolvedApproxReduced = mu return self.uApproxReduced def getPoles(self, *args, **kwargs) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ if len(args) + len(kwargs) > 1: raise RROMPyException(("Wrong number of parameters passed. " "Only 1 available.")) elif len(args) + len(kwargs) == 1: if len(args) == 1: mVals = args[0] else: mVals = kwargs["marginalVals"] if not hasattr(mVals, "__len__"): mVals = [mVals] mVals = list(mVals) else: mVals = [fp] try: rDim = mVals.index(fp) if rDim < len(mVals) - 1 and fp in mVals[rDim + 1 :]: raise except: raise RROMPyException(("Exactly 1 'freepar' entry in " "marginalVals must be provided.")) mVals[rDim] = self.data.mu0(rDim) mVals = self.centerNormalize(checkParameter(mVals, len(mVals))) mVals = list(mVals.data.flatten()) mVals[rDim] = fp return np.power(self.data.mu0(rDim) ** self.data.rescalingExp[rDim] + self.data.scaleFactor[rDim] * self.data.Q.roots(mVals), 1. / self.data.rescalingExp[rDim]) def getResidues(self, *args, **kwargs) -> Np1D: """ Obtain approximant residues. Returns: Numpy matrix with residues as columns. """ pls = self.getPoles(*args, **kwargs) if len(args) == 1: mVals = args[0] elif len(args) == 0: mVals = [None] else: mVals = kwargs["marginalVals"] if not hasattr(mVals, "__len__"): mVals = [mVals] mVals = list(mVals) rDim = mVals.index(fp) poles = emptyParameterList() poles.reset((len(pls), self.data.npar), dtype = pls.dtype) for k, pl in enumerate(pls): poles[k] = mVals poles.data[k, rDim] = pl QV = self.getQVal(poles, list(1 * (np.arange(self.data.npar) == rDim))) QVzero = np.where(QV == 0.)[0] if len(QVzero) > 0: RROMPyWarning(("Adjusting residuals to avoid division by " "numerically zero denominator.")) QV[QVzero] = np.finfo(np.complex).eps / (1. + self.data.Q.deg[0]) res = self.data.projMat.dot(self.getPVal(poles).data) / QV return pls, res diff --git a/rrompy/reduction_methods/standard/trained_model/trained_model_rational_mls.py b/rrompy/reduction_methods/standard/trained_model/trained_model_rational_mls.py index 81ebb8b..329c5b5 100644 --- a/rrompy/reduction_methods/standard/trained_model/trained_model_rational_mls.py +++ b/rrompy/reduction_methods/standard/trained_model/trained_model_rational_mls.py @@ -1,190 +1,191 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from .trained_model_rational import TrainedModelRational from rrompy.utilities.base.types import Np1D, paramVal, paramList, sampList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.poly_fitting.moving_least_squares import mlsweights from rrompy.utilities.poly_fitting.polynomial import ( PolynomialInterpolator as PI) -from rrompy.utilities.numerical import customPInv, degreeTotalToFull +from rrompy.utilities.numerical import customPInv +from rrompy.utilities.numerical.degree import degreeTotalToFull from rrompy.parameter import checkParameterList from rrompy.sampling import emptySampleList from rrompy.utilities.exception_manager import RROMPyException, RROMPyWarning __all__ = ['TrainedModelRationalMLS'] class TrainedModelRationalMLS(TrainedModelRational): """ ROM approximant evaluation for rational moving least squares approximant. Attributes: Data: dictionary with all that can be pickled. """ def reset(self): super().reset() self.lastSetupMu = None def assembleReducedModel(self, mu:paramVal): if not (hasattr(self.data, "lastSetupMu") and self.data.lastSetupMu == mu): vbMng(self, "INIT", "Assembling reduced model for mu = {}."\ .format(mu), 17) vbMng(self, "INIT", "Starting computation of denominator.", 35) muC = self.centerNormalize(mu) muSC = self.centerNormalize(self.data.mus) wQ = mlsweights(muC, muSC, self.data.radialBasisDen, directionalWeights = self.data.radialWeightsDen, nNearestNeighbor = self.data.nNearestNeighborDen) if self.data.N > self.data.M: PQVan = self.data.QVan else: PQVan = self.data.PVan VQAdjW = PQVan.conj().T * wQ VQAdjWVQ = VQAdjW.dot(PQVan) interpPseudoInverse, info = customPInv(VQAdjWVQ, full = True, rcond = self.data.interpRcond) interpPseudoInverse = interpPseudoInverse.dot(VQAdjW).dot( self.data.QBlocks) if info[0] < interpPseudoInverse.shape[-1]: q = np.zeros(interpPseudoInverse.shape[-1], dtype = np.complex) q[0] = 1. else: halfGram = interpPseudoInverse[self.data.domQIdxs] if self.data.POD: Rstack = halfGram.reshape(-1, halfGram.shape[-1]) vbMng(self, "INIT", "Solving svd for square root of gramian matrix.", 67) try: _, s, eV = np.linalg.svd(Rstack, full_matrices = False) except np.linalg.LinAlgError as e: raise RROMPyException(e) condN = s[0] / s[-1] q = eV[-1, :].T.conj() vbMng(self, "MAIN", ("Solved svd problem of size {} x {} with condition " "number {:.4e}.").format(*Rstack.shape, condN), 55) vbMng(self, "DEL", "Done solving svd.", 67) else: RRstack = np.tensordot(self.trainedModel.gramian, halfGram, 1).reshape(-1, halfGram.shape[-1]) RLstack = halfGram.reshape(-1, halfGram.shape[-1]) gram = RLstack.T.conj().dot(RRstack) vbMng(self, "INIT", "Solving eigenvalue problem for gramian matrix.", 67) try: ev, eV = np.linalg.eigh(gram) except np.linalg.LinAlgError as e: raise RROMPyException(e) condN = ev[-1] / ev[0] q = eV[:, 0] vbMng(self, "MAIN", ("Solved eigenvalue problem of size {} with " "condition number {:.4e}.").format(gram.shape[0], condN), 55) vbMng(self, "DEL", "Done solving eigenvalue problem.", 67) self.data.Q = PI() self.data.Q.npar = self.npar self.data.Q.polybasis = self.data.polybasis if self.data.polydegreetype == "TOTAL": self.data.Q.coeffs = degreeTotalToFull( (self.data.N + 1,) * self.npar, self.npar, q) else: self.data.Q.coeffs = q.reshape((self.data.N + 1,) * self.npar) vbMng(self, "DEL", "Done computing denominator.", 35) vbMng(self, "INIT", "Starting computation of numerator.", 35) self.data.P = PI() self.data.P.npar = self.npar self.data.P.polybasis = self.data.polybasis wP = mlsweights(muC, muSC, self.data.radialBasis, directionalWeights = self.data.radialWeights, nNearestNeighbor = self.data.nNearestNeighbor) VAdjW = self.data.PVan.conj().T * wP VAdjWV = VAdjW.dot(self.data.PVan) interpPPseudoInverse = customPInv(VAdjWV, self.data.interpRcond) Pcoeffs = np.tensordot(interpPPseudoInverse.dot(VAdjW), self.data.QBlocks.dot(q), ([1], [1])) if self.data.polydegreetype == "TOTAL": self.data.P.coeffs = degreeTotalToFull( (self.data.M + 1,) * self.npar + (self.data.QBlocks.shape[0],), self.npar, Pcoeffs) else: self.data.P.coeffs = Pcoeffs.reshape( (self.data.M + 1,) * self.npar + (self.data.QBlocks.shape[0],)) vbMng(self, "DEL", "Done computing numerator.", 35) vbMng(self, "DEL", "Done assembling reduced model.", 17) self.data.lastSetupMu = mu def getApproxReduced(self, mu : paramList = []) -> sampList: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. """ mu = checkParameterList(mu, self.data.npar)[0] if (not hasattr(self, "lastSolvedApproxReduced") or self.lastSolvedApproxReduced != mu): vbMng(self, "INIT", "Evaluating approximant at mu = {}.".format(mu), 12) self.uApproxReduced = emptySampleList() for i in range(len(mu)): self.assembleReducedModel(mu[i]) vbMng(self, "INIT", "Solving reduced model for mu = {}.".format(mu[i]), 15) Qv = self.getQVal(mu[i]) if Qv == 0.: RROMPyWarning(("Adjusting approximation to avoid division " "by numerically zero denominator.")) Qv = np.finfo(np.complex).eps / (1. + self.data.Q.deg[0]) uAppR = self.getPVal(mu[i]) / Qv if i == 0: self.uApproxReduced.reset((uAppR.shape[0], len(mu)), dtype = uAppR.dtype) self.uApproxReduced[i] = uAppR vbMng(self, "DEL", "Done solving reduced model.", 15) vbMng(self, "DEL", "Done evaluating approximant.", 12) self.lastSolvedApproxReduced = mu return self.uApproxReduced def getPoles(self, *args, mu : paramVal = None, **kwargs) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ if mu is None: mu = self.data.mu0 self.assembleReducedModel(mu) return super().getPoles(*args, **kwargs) def getResidues(self, *args, mu : paramVal = None, **kwargs) -> Np1D: """ Obtain approximant residues. Returns: Numpy matrix with residues as columns. """ if mu is None: mu = self.data.mu0 self.assembleReducedModel(mu) return super().getResidues(*args, **kwargs) diff --git a/rrompy/reduction_methods/standard/trained_model/trained_model_reduced_basis.py b/rrompy/reduction_methods/standard/trained_model/trained_model_reduced_basis.py index b131433..4b9d180 100644 --- a/rrompy/reduction_methods/standard/trained_model/trained_model_reduced_basis.py +++ b/rrompy/reduction_methods/standard/trained_model/trained_model_reduced_basis.py @@ -1,124 +1,126 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np -from rrompy.reduction_methods.base.trained_model import TrainedModel +from rrompy.reduction_methods.base.trained_model.trained_model import ( + TrainedModel) from rrompy.utilities.base.types import (Np1D, ListAny, paramVal, paramList, sampList) from rrompy.utilities.base import verbosityManager as vbMng, freepar as fp -from rrompy.utilities.numerical import (eigvalsNonlinearDense, - marginalizePolyList) +from rrompy.utilities.numerical.marginalize_poly_list import marginalizePolyList +from rrompy.utilities.numerical.nonlinear_eigenproblem import ( + eigvalsNonlinearDense) from rrompy.utilities.expression import expressionEvaluator from rrompy.utilities.exception_manager import RROMPyException, RROMPyWarning from rrompy.parameter import checkParameter, checkParameterList from rrompy.sampling import emptySampleList __all__ = ['TrainedModelReducedBasis'] class TrainedModelReducedBasis(TrainedModel): """ ROM approximant evaluation for RB approximant. Attributes: Data: dictionary with all that can be pickled. """ def reset(self): super().reset() if hasattr(self, "data") and hasattr(self.data, "lastSetupMu"): self.data.lastSetupMu = None def assembleReducedModel(self, mu:paramVal): mu = checkParameter(mu, self.data.npar) if not (hasattr(self.data, "lastSetupMu") and self.data.lastSetupMu == mu): vbMng(self, "INIT", "Assembling reduced model for mu = {}."\ .format(mu), 17) muEff = mu ** self.data.rescalingExp self.data.ARBmu, self.data.bRBmu = 0., 0. for thA, ARB in zip(self.data.thAs, self.data.ARBs): self.data.ARBmu = (expressionEvaluator(thA[0], muEff) * ARB + self.data.ARBmu) for thb, bRB in zip(self.data.thbs, self.data.bRBs): self.data.bRBmu = (expressionEvaluator(thb[0], muEff) * bRB + self.data.bRBmu) vbMng(self, "DEL", "Done assembling reduced model.", 17) self.data.lastSetupMu = mu def getApproxReduced(self, mu : paramList = []) -> sampList: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. """ mu = checkParameterList(mu, self.data.npar)[0] if (not hasattr(self, "lastSolvedApproxReduced") or self.lastSolvedApproxReduced != mu): vbMng(self, "INIT", "Computing RB solution at mu = {}.".format(mu), 12) self.uApproxReduced = emptySampleList() for i in range(len(mu)): self.assembleReducedModel(mu[i]) vbMng(self, "INIT", "Solving reduced model for mu = {}.".format(mu[i]), 15) try: uAppR = np.linalg.solve(self.data.ARBmu, self.data.bRBmu) except np.linalg.LinAlgError as e: raise RROMPyException(e) if i == 0: #self.data.ARBs[0].shape[-1], len(mu) self.uApproxReduced.reset((len(uAppR), len(mu)), dtype = uAppR.dtype) self.uApproxReduced[i] = uAppR vbMng(self, "DEL", "Done solving reduced model.", 15) vbMng(self, "DEL", "Done computing RB solution.", 12) self.lastSolvedApproxReduced = mu return self.uApproxReduced def getPoles(self, marginalVals : ListAny = [fp], jSupp : int = 1, **kwargs) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ if not self.data.affinePoly: RROMPyWarning(("Unable to compute approximate poles due " "to parametric dependence (detected non-" "polynomial). Change HFEngine.affinePoly to True " "if necessary.")) return if not hasattr(marginalVals, "__len__"): marginalVals = [marginalVals] mVals = list(marginalVals) try: rDim = mVals.index(fp) if rDim < len(mVals) - 1 and fp in mVals[rDim + 1 :]: raise except: raise RROMPyException(("Exactly 1 'freepar' entry in " "marginalVals must be provided.")) ARBs = self.data.ARBs if self.data.npar > 1: mVals[rDim] = self.data.mu0(rDim) mVals = checkParameter(mVals).data.flatten() mVals[rDim] = fp ARBs = marginalizePolyList(ARBs, mVals, "auto") ev = eigvalsNonlinearDense(ARBs, jSupp = jSupp, **kwargs) return np.power(ev, 1. / self.data.rescalingExp[rDim]) diff --git a/rrompy/sampling/base/__init__.py b/rrompy/sampling/base/__init__.py index 643e6c4..341a023 100644 --- a/rrompy/sampling/base/__init__.py +++ b/rrompy/sampling/base/__init__.py @@ -1,29 +1,25 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from .pod_engine import PODEngine -from .sampling_engine_base import SamplingEngineBase -from .sampling_engine_base_pivoted import SamplingEngineBasePivoted __all__ = [ - 'PODEngine', - 'SamplingEngineBase', - 'SamplingEngineBasePivoted' + 'PODEngine' ] diff --git a/rrompy/sampling/base/sampling_engine_base.py b/rrompy/sampling/base/sampling_engine_base.py index 35d6c26..1f51e84 100644 --- a/rrompy/sampling/base/sampling_engine_base.py +++ b/rrompy/sampling/base/sampling_engine_base.py @@ -1,205 +1,213 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # +from abc import abstractmethod import numpy as np from rrompy.utilities.base.types import (Np1D, HFEng, List, paramVal, paramList, sampList, Tuple, FigHandle) from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.exception_manager import RROMPyWarning from rrompy.parameter import (emptyParameterList, checkParameter, checkParameterList) from rrompy.sampling import sampleList, emptySampleList __all__ = ['SamplingEngineBase'] class SamplingEngineBase: - """HERE""" - def __init__(self, HFEngine:HFEng, sample_state : bool = False, verbosity : int = 10, timestamp : bool = True): self.sample_state = sample_state self.verbosity = verbosity self.timestamp = timestamp vbMng(self, "INIT", "Initializing sampling engine of type {}.".format(self.name()), 10) self.HFEngine = HFEngine vbMng(self, "DEL", "Done initializing sampling engine.", 10) def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) @property def HFEngine(self): """Value of HFEngine. Its assignment resets history.""" return self._HFEngine @HFEngine.setter def HFEngine(self, HFEngine): self._HFEngine = HFEngine self.resetHistory() def resetHistory(self): self.samples = emptySampleList() self.nsamples = 0 self.mus = emptyParameterList() self._derIdxs = [] def setsample(self, u:sampList, overwrite : bool = False): if overwrite: self.samples[self.nsamples] = u else: if self.nsamples == 0: self.samples = sampleList(u) else: self.samples.append(u) def popSample(self): if hasattr(self, "nsamples") and self.nsamples > 1: if self.samples.shape[1] > self.nsamples: RROMPyWarning(("More than 'nsamples' memory allocated for " "samples. Popping empty sample column.")) self.nsamples += 1 self.nsamples -= 1 self.samples.pop() self.mus.pop() else: self.resetHistory() def preallocateSamples(self, u:sampList, mu:paramVal, n:int): self.samples.reset((u.shape[0], n), u.dtype) self.samples[0] = u mu = checkParameter(mu, self.HFEngine.npar) self.mus.reset((n, self.HFEngine.npar)) self.mus[0] = mu[0] def solveLS(self, mu : paramList = [], RHS : sampList = None) -> sampList: """ Solve linear system. Args: mu: Parameter value. Returns: Solution of system. """ mu = checkParameterList(mu, self.HFEngine.npar)[0] vbMng(self, "INIT", "Solving HF model for mu = {}.".format(mu), 15) u = self.HFEngine.solve(mu, RHS, return_state = self.sample_state, verbose = (self.verbosity >= 20)) vbMng(self, "DEL", "Done solving HF model.", 15) return u + @abstractmethod + def nextSample(self, mu:paramVal, overwrite : bool = False, + postprocess : bool = True) -> Np1D: + pass + + @abstractmethod + def iterSample(self, mus:paramList) -> sampList: + pass + def plotSamples(self, warpings : List[List[callable]] = None, name : str = "u", **kwargs) -> Tuple[List[FigHandle], List[str]]: """ Do some nice plots of the samples. Args: warpings(optional): Domain warping functions. name(optional): Name to be shown as title of the plots. Defaults to 'u'. Returns: Output filenames and figure handles. """ if warpings is None: warpings = [None] * self.nsamples figs = [None] * self.nsamples filesOut = [None] * self.nsamples for j in range(self.nsamples): pltOut = self.HFEngine.plot(self.samples[j], warpings[j], self.sample_state, "{}_{}".format(name, j), **kwargs) if isinstance(pltOut, (tuple,)): figs[j], filesOut[j] = pltOut else: figs[j] = pltOut if filesOut[0] is None: return figs return figs, filesOut def outParaviewSamples(self, warpings : List[List[callable]] = None, name : str = "u", filename : str = "out", times : Np1D = None, **kwargs) -> List[str]: """ Output samples to ParaView file. Args: warpings(optional): Domain warping functions. name(optional): Base name to be used for data output. filename(optional): Name of output file. times(optional): Timestamps. Returns: Output filenames. """ if warpings is None: warpings = [None] * self.nsamples if times is None: times = [0.] * self.nsamples filesOut = [None] * self.nsamples for j in range(self.nsamples): filesOut[j] = self.HFEngine.outParaview(self.samples[j], warpings[j], self.sample_state, "{}_{}".format(name, j), "{}_{}".format(filename, j), times[j], **kwargs) if filesOut[0] is None: return None return filesOut def outParaviewTimeDomainSamples(self, omegas : Np1D = None, warpings : List[List[callable]] = None, timeFinal : Np1D = None, periodResolution : List[int] = 20, name : str = "u", filename : str = "out", **kwargs) -> List[str]: """ Output samples to ParaView file, converted to time domain. Args: omegas(optional): frequencies. timeFinal(optional): final time of simulation. periodResolution(optional): number of time steps per period. name(optional): Base name to be used for data output. filename(optional): Name of output file. Returns: Output filename. """ if omegas is None: omegas = np.real(self.mus) if warpings is None: warpings = [None] * self.nsamples if not isinstance(timeFinal, (list, tuple,)): timeFinal = [timeFinal] * self.nsamples if not isinstance(periodResolution, (list, tuple,)): periodResolution = [periodResolution] * self.nsamples filesOut = [None] * self.nsamples for j in range(self.nsamples): filesOut[j] = self.HFEngine.outParaviewTimeDomain(self.samples[j], omegas[j], warpings[j], self.sample_state, timeFinal[j], periodResolution[j], "{}_{}".format(name, j), "{}_{}".format(filename, j), **kwargs) if filesOut[0] is None: return None return filesOut diff --git a/rrompy/sampling/base/sampling_engine_base_pivoted.py b/rrompy/sampling/base/sampling_engine_base_pivoted.py index d9ba0b9..d6a4631 100644 --- a/rrompy/sampling/base/sampling_engine_base_pivoted.py +++ b/rrompy/sampling/base/sampling_engine_base_pivoted.py @@ -1,253 +1,261 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # +from abc import abstractmethod import numpy as np from rrompy.utilities.base.types import (Np1D, HFEng, List, ListAny, paramVal, paramList, sampList, Tuple, FigHandle) from rrompy.utilities.base import verbosityManager as vbMng, freepar as fp from rrompy.utilities.exception_manager import RROMPyWarning from rrompy.parameter import (emptyParameterList, checkParameter, checkParameterList) from rrompy.sampling import emptySampleList from .sampling_engine_base import sampleList, SamplingEngineBase __all__ = ['SamplingEngineBasePivoted'] class SamplingEngineBasePivoted(SamplingEngineBase): - """HERE""" - def __init__(self, HFEngine:HFEng, directionPivot:ListAny, *args, **kwargs): self.directionPivot = directionPivot self.HFEngineMarginalized = None super().__init__(HFEngine, *args, **kwargs) @property def directionMarginal(self): return tuple([x for x in range(self.HFEngine.npar) \ if x not in self.directionPivot]) @property def nPivot(self): return len(self.directionPivot) @property def nMarginal(self): return len(self.directionMarginal) @property def nsamplesTot(self): return np.sum(self.nsamples) def resetHistory(self, j : int = 0): self.samples = [emptySampleList() for _ in range(j)] self.nsamples = [0] * j self.mus = [emptyParameterList() for _ in range(j)] self.musMarginal = emptyParameterList() self.musMarginal.reset((j, self.nMarginal)) self._derIdxs = [[] for _ in range(j)] self.resetHistoryCoalesced() def resetHistoryCoalesced(self): self.samplesCoalesced = emptySampleList() def setsample(self, u:sampList, j:int, overwrite : bool = False) -> Np1D: if overwrite: self.samples[j][self.nsamples[j]] = u else: if self.nsamples[j] == 0: self.samples[j] = sampleList(u) else: self.samples[j].append(u) def popSample(self, j:int): if hasattr(self, "nsamples") and self.nsamples[j] > 1: if self.samples[j].shape[1] > self.nsamples[j]: RROMPyWarning(("More than 'nsamples' memory allocated for " "samples. Popping empty sample column.")) self.nsamples[j] += 1 self.nsamples[j] -= 1 self.samples[j].pop() self.mus[j].pop() self.resetHistoryCoalesced() else: self.resetHistory() def preallocateSamples(self, u:sampList, mu:paramVal, n:int, j:int): self.samples[j].reset((u.shape[0], n), u.dtype) self.samples[j][0] = u mu = checkParameter(mu, self.nPivot) self.mus[j].reset((n, self.nPivot)) self.mus[j][0] = mu[0] def coalesceSamples(self): vbMng(self, "INIT", "Coalescing samples.", 7) self.musCoalesced = emptyParameterList() for j, mPs in enumerate(self.mus): muEff = [fp] * self.HFEngine.npar for k, x in enumerate(self.directionMarginal): muEff[x] = self.musMarginal(j, k) for l in range(len(mPs)): for k, x in enumerate(self.directionPivot): muEff[x] = mPs(l, k) self.musCoalesced.append(muEff) self.nsamplesCoalesced = np.sum(self.nsamples) self.samplesCoalesced = emptySampleList() self.samplesCoalesced.reset((self.samples[0].shape[0], self.nsamplesCoalesced), self.samples[0].dtype) run_idx = 0 for samp in self.samples: slen = samp.shape[1] self.samplesCoalesced.data[:, run_idx : run_idx + slen] = samp.data run_idx += slen vbMng(self, "DEL", "Done coalescing samples.", 7) def solveLS(self, mu : paramList = [], RHS : sampList = None) -> sampList: """ Solve linear system. Args: mu: Parameter value. Returns: Solution of system. """ mu = checkParameterList(mu, self.nPivot)[0] vbMng(self, "INIT", ("Solving HF model for muPivot = {} and muMarginal = " "{}.").format(mu, self.HFEngineMarginalized.muFixed), 15) u = self.HFEngineMarginalized.solve(mu, RHS, return_state = self.sample_state, verbose = (self.verbosity >= 20)) vbMng(self, "DEL", "Done solving HF model.", 15) return u + @abstractmethod + def nextSample(self, mu:paramVal, j:int, overwrite : bool = False, + postprocess : bool = True) -> Np1D: + pass + + @abstractmethod + def iterSample(self, mus:paramList, musM:paramList) -> sampList: + pass + def plotSamples(self, warpings : List[List[List[callable]]] = None, name : str = "u", **kwargs) -> Tuple[List[List[FigHandle]], List[List[str]]]: """ Do some nice plots of the samples. Args: warpings(optional): Domain warping functions. name(optional): Name to be shown as title of the plots. Defaults to 'u'. Returns: Output filenames and figure handles. """ if warpings is None: warpings = [[None] * self.nsamples[i] \ for i in range(len(self.nsamples))] figs = [] filesOut = [] for i in range(len(self.nsamples)): figsi = [None] * self.nsamples[i] filesOuti = [None] * self.nsamples[i] for j in range(self.nsamples[i]): pltOut = self.HFEngine.plot(self.samples[i][j], warpings[i][j], self.sample_state, "{}_{}_{}".format(name, i, j), **kwargs) if isinstance(pltOut, (tuple,)): figsi[j], filesOuti[j] = pltOut else: figsi[j] = pltOut figs += [figsi] filesOut += [filesOuti] if filesOut[0][0] is None: return figs return figs, filesOut def outParaviewSamples(self, warpings : List[List[List[callable]]] = None, name : str = "u", filename : str = "out", times : List[Np1D] = None, **kwargs) -> List[List[str]]: """ Output samples to ParaView file. Args: warpings(optional): Domain warping functions. name(optional): Base name to be used for data output. filename(optional): Name of output file. times(optional): Timestamps. Returns: Output filenames. """ if warpings is None: warpings = [[None] * self.nsamples[i] \ for i in range(len(self.nsamples))] if times is None: times = [[0.] * self.nsamples[i] \ for i in range(len(self.nsamples))] filesOut = [] for i in range(len(self.nsamples)): filesOuti = [None] * self.nsamples[i] for j in range(self.nsamples[i]): filesOuti[j] = self.HFEngine.outParaview(self.samples[i][j], warpings[i][j], self.sample_state, "{}_{}_{}".format(name, i, j), "{}_{}_{}".format(filename, i, j), times[i][j], **kwargs) filesOut += [filesOuti] if filesOut[0][0] is None: return None return filesOut def outParaviewTimeDomainSamples(self, omegas : Np1D = None, warpings : List[List[List[callable]]] = None, timeFinal : Np1D = None, periodResolution : List[List[int]] = 20, name : str = "u", filename : str = "out", **kwargs) -> List[List[str]]: """ Output samples to ParaView file, converted to time domain. Args: omegas(optional): frequencies. warpings(optional): Domain warping functions. timeFinal(optional): final time of simulation. periodResolution(optional): number of time steps per period. name(optional): Base name to be used for data output. filename(optional): Name of output file. Returns: Output filenames. """ if omegas is None: omegas = [np.real(self.mus[i]) \ for i in range(len(self.nsamples))] if warpings is None: warpings = [[None] * self.nsamples[i] \ for i in range(len(self.nsamples))] if not isinstance(timeFinal, (list, tuple,)): timeFinal = [[timeFinal] * self.nsamples[i] \ for i in range(len(self.nsamples))] if not isinstance(periodResolution, (list, tuple,)): periodResolution = [[periodResolution] * self.nsamples[i] \ for i in range(len(self.nsamples))] filesOut = [] for i in range(len(self.nsamples)): filesOuti = [None] * self.nsamples[i] for j in range(self.nsamples[i]): filesOuti[j] = self.HFEngine.outParaviewTimeDomain( self.samples[i][j], omegas[i][j], warpings[i][j], self.sample_state, timeFinal[i][j], periodResolution[i][j], "{}_{}_{}".format(name, i, j), "{}_{}_{}".format(filename, i, j), **kwargs) filesOut += [filesOuti] if filesOut[0][0] is None: return None return filesOut diff --git a/rrompy/sampling/pivoted/sampling_engine_pivoted.py b/rrompy/sampling/pivoted/sampling_engine_pivoted.py index be06b82..85bf162 100644 --- a/rrompy/sampling/pivoted/sampling_engine_pivoted.py +++ b/rrompy/sampling/pivoted/sampling_engine_pivoted.py @@ -1,125 +1,124 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.sampling.base.sampling_engine_base_pivoted import ( SamplingEngineBasePivoted) from rrompy.hfengines.base import MarginalProxyEngine from rrompy.utilities.base.types import Np1D, paramVal, paramList, sampList from rrompy.utilities.base import verbosityManager as vbMng, freepar as fp from rrompy.utilities.exception_manager import RROMPyException -from rrompy.utilities.numerical import nextDerivativeIndices, dot +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.hash_derivative import nextDerivativeIndices from rrompy.parameter import checkParameter, checkParameterList __all__ = ['SamplingEnginePivoted'] class SamplingEnginePivoted(SamplingEngineBasePivoted): - """HERE""" - def preprocesssamples(self, idxs:Np1D, j:int) -> sampList: if self.samples[j] is None or len(self.samples[j]) == 0: return return self.samples[j](idxs) def postprocessu(self, u:sampList, j:int, overwrite : bool = False): self.setsample(u, j, overwrite) def postprocessuBulk(self, j:int): pass def _getSampleConcurrence(self, mu:paramVal, j:int, previous:Np1D) -> sampList: if not (self.sample_state or self.HFEngine.isCEye): raise RROMPyException(("Derivatives of solution with non-scalar " "C not computable.")) if not self.HFEngine._isStateShiftZero: raise RROMPyException(("Derivatives of solution with non-zero " "solution shift not computable.")) if len(previous) >= len(self._derIdxs[j]): self._derIdxs[j] += nextDerivativeIndices( self._derIdxs[j], self.nPivot, len(previous) + 1 - len(self._derIdxs[j])) derIdx = self._derIdxs[j][len(previous)] mu = checkParameter(mu, self.nPivot) samplesOld = self.preprocesssamples(previous, j) RHS = self.HFEngineMarginalized.b(mu, derIdx) for j, derP in enumerate(self._derIdxs[j][: len(previous)]): diffP = [x - y for (x, y) in zip(derIdx, derP)] if np.all([x >= 0 for x in diffP]): RHS -= dot(self.HFEngineMarginalized.A(mu, diffP), samplesOld[j]) return self.solveLS(mu, RHS = RHS) def nextSample(self, mu:paramVal, j:int, overwrite : bool = False, postprocess : bool = True) -> Np1D: mu = checkParameter(mu, self.nPivot) muidxs = self.mus[j].findall(mu[0]) if len(muidxs) > 0: u = self._getSampleConcurrence(mu, j, np.sort(muidxs)) else: u = self.solveLS(mu) if postprocess: self.postprocessu(u, j, overwrite = overwrite) else: self.setsample(u, j, overwrite) if overwrite: self.mus[j][self.nsamples[j]] = mu[0] else: self.mus[j].append(mu) self.nsamples[j] += 1 return self.samples[j][self.nsamples[j] - 1] def iterSample(self, mus:paramList, musM:paramList) -> sampList: mus = checkParameterList(mus, self.nPivot)[0] musM = checkParameterList(musM, self.nMarginal)[0] vbMng(self, "INIT", "Starting sampling iterations.", 5) n = len(mus) m = len(musM) if n <= 0: raise RROMPyException("Number of samples must be positive.") if m <= 0: raise RROMPyException(("Number of marginal samples must be " "positive.")) repeatedSamples = len(mus.unique()) != n for j in range(m): muMEff = [fp] * self.HFEngine.npar for k, x in enumerate(self.directionMarginal): muMEff[x] = musM(j, k) self.HFEngineMarginalized = MarginalProxyEngine(self.HFEngine, list(muMEff)) if repeatedSamples: for k in range(n): vbMng(self, "MAIN", "Computing sample {} / {} for marginal {} / {}."\ .format(k + 1, n, j, m), 10) self.nextSample(mus[k], j, overwrite = (k > 0), postprocess = False) if n > 1 and k == 0: self.preallocateSamples(self.samples[j][0], mus[0], n, j) else: self.setsample(self.solveLS(mus), j, overwrite = False) self.mus[j] = copy(mus) self.nsamples[j] = n if len(self.musMarginal) > j: self.musMarginal[j] = copy(musM[j]) else: self.musMarginal.append(musM[j]) self.postprocessuBulk(j) vbMng(self, "DEL", "Finished sampling iterations.", 5) return self.samples[j] diff --git a/rrompy/sampling/pivoted/sampling_engine_pivoted_pod.py b/rrompy/sampling/pivoted/sampling_engine_pivoted_pod.py index 2105d24..4f9961e 100644 --- a/rrompy/sampling/pivoted/sampling_engine_pivoted_pod.py +++ b/rrompy/sampling/pivoted/sampling_engine_pivoted_pod.py @@ -1,118 +1,116 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from scipy.linalg import block_diag from rrompy.sampling.base.pod_engine import PODEngine from .sampling_engine_pivoted import SamplingEnginePivoted from rrompy.utilities.base.types import Np1D, paramVal, sampList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.sampling import sampleList, emptySampleList __all__ = ['SamplingEnginePivotedPOD'] class SamplingEnginePivotedPOD(SamplingEnginePivoted): - """HERE""" - @property def HFEngine(self): """Value of HFEngine. Its assignment resets history.""" return self._HFEngine @HFEngine.setter def HFEngine(self, HFEngine): self._HFEngine = HFEngine self.resetHistory() self.PODEngine = PODEngine(self._HFEngine) def resetHistory(self, j : int = 0): super().resetHistory(j) self.samples_full = [emptySampleList() for _ in range(j)] self.RPOD = [np.zeros((0, 0), dtype = np.complex) for _ in range(j)] def resetHistoryCoalesced(self): super().resetHistoryCoalesced() self.RPODCoalesced = np.zeros((0, 0), dtype = np.complex) self.samples_fullCoalesced = emptySampleList() def popSample(self, j:int): if hasattr(self, "nsamples") and self.nsamples[j] > 1: self.RPOD[j] = self.RPOD[j][: -1, : -1] self.samples_full[j].pop() super().popSample(j) def coalesceSamples(self): vbMng(self, "INIT", "Coalescing samples.", 7) verb = self.verbosity self.verbosity = 0 super().coalesceSamples() self.verbosity = verb self.RPODCoalesced = block_diag(*(self.RPOD)) self.samples_fullCoalesced = emptySampleList() self.samples_fullCoalesced.reset((self.samples_full[0].shape[0], self.nsamplesCoalesced), self.samples_full[0].dtype) ci = 0 for j, samp_full in enumerate(self.samples_full): Rheg = samp_full.shape[1] self.samples_fullCoalesced.data[:, ci : ci + Rheg] = samp_full.data ci += Rheg vbMng(self, "DEL", "Done coalescing samples.", 7) def preprocesssamples(self, idxs:Np1D, j:int) -> sampList: if self.samples_full[j] is None or len(self.samples_full[j]) == 0: return return self.samples_full[j](idxs) def setsample(self, u:sampList, j:int, overwrite : bool = False): super().setsample(u, j, overwrite) if overwrite: self.samples_full[j][self.nsamples[j]] = u else: if self.nsamples[j] == 0: self.samples_full[j] = sampleList(u) else: self.samples_full[j].append(u) def postprocessu(self, u:sampList, j:int, overwrite : bool = False): if overwrite: self.samples_full[j][self.nsamples[j]] = u else: if self.nsamples[j] == 0: self.samples_full[j] = sampleList(u) else: self.samples_full[j].append(u) vbMng(self, "INIT", "Starting orthogonalization.", 20) u, r, _ = self.PODEngine.GS(u, self.samples[j], is_state = self.sample_state) self.RPOD[j] = np.pad(self.RPOD[j], ((0, 1), (0, 1)), 'constant') self.RPOD[j][:, -1] = r vbMng(self, "DEL", "Done orthogonalizing.", 20) super().setsample(u, j, overwrite) def postprocessuBulk(self, j:int): vbMng(self, "INIT", "Starting orthogonalization for marginal no {}.".format(j), 40) u, self.RPOD[j] = self.PODEngine.generalizedQR(self.samples_full[j], is_state = self.sample_state) vbMng(self, "DEL", "Done orthogonalizing.", 40) self.samples[j] = sampleList(u) def preallocateSamples(self, u:Np1D, mu:paramVal, n:int, j:int): super().preallocateSamples(u, mu, n, j) self.samples_full[j].reset((u.shape[0], n), u.dtype) self.samples_full[j][0] = u diff --git a/rrompy/sampling/pivoted/sampling_engine_pivoted_pod_global.py b/rrompy/sampling/pivoted/sampling_engine_pivoted_pod_global.py index 941a242..9df6643 100644 --- a/rrompy/sampling/pivoted/sampling_engine_pivoted_pod_global.py +++ b/rrompy/sampling/pivoted/sampling_engine_pivoted_pod_global.py @@ -1,49 +1,47 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from .sampling_engine_pivoted_pod import SamplingEnginePivotedPOD from rrompy.utilities.base import verbosityManager as vbMng __all__ = ['SamplingEnginePivotedPODGlobal'] class SamplingEnginePivotedPODGlobal(SamplingEnginePivotedPOD): - """HERE""" - def resetHistoryCoalesced(self): super().resetHistoryCoalesced() self.RPODCPart = np.zeros((0, 0), dtype = np.complex) def coalesceSamples(self, tol : float = 1e-12): vbMng(self, "INIT", "Coalescing samples.", 7) verb = self.verbosity self.verbosity = 0 super().coalesceSamples() self.verbosity = verb self.samplesCoalesced, self.RPODCPart = self.PODEngine.generalizedQR( self.samplesCoalesced, is_state = self.sample_state) self.RPODCoalesced = self.RPODCPart.dot(self.RPODCoalesced) RCdiag = np.abs(np.diag(self.RPODCoalesced)) RCdiag /= RCdiag[0] ntrunc = np.nonzero(RCdiag < tol)[0] if len(ntrunc) > 0: self.samplesCoalesced.data = self.samplesCoalesced.data[:, : ntrunc[0]] self.RPODCoalesced = self.RPODCoalesced[: ntrunc[0], :] vbMng(self, "DEL", "Done coalescing samples.", 7) diff --git a/rrompy/sampling/sample_list.py b/rrompy/sampling/sample_list.py index 75ac132..3366fea 100644 --- a/rrompy/sampling/sample_list.py +++ b/rrompy/sampling/sample_list.py @@ -1,226 +1,224 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.utilities.base.types import Np1D, List from rrompy.utilities.numerical import dot __all__ = ['emptySampleList', 'sampleList'] def emptySampleList(): return sampleList(np.empty((0, 0))) class sampleList: - """HERE""" - def __init__(self, data:List[Np1D], lengthCheck : int = None, deep : bool = True): if isinstance(data, (self.__class__,)): data = data.data if isinstance(data, (np.ndarray,)): self.data = copy(data) if deep else data if self.data.ndim <= 1: self.data.shape = (self.data.shape[0], 1) else: if not isinstance(data, (list,)): data = [data] self.data = np.empty((len(data[0]), len(data)), dtype = data[0].dtype) for j, par in enumerate(data): self[j] = copy(data[j]) if deep else data[j] if j == 0 and lengthCheck is None: lengthCheck = self.shape[0] RROMPyAssert(len(data[j]), lengthCheck, "Number of parameters") def __len__(self): return self.shape[1] def __str__(self): return str(self.data) def __repr__(self): return repr(self.data) @property def shape(self): return self.data.shape @property def re(self): return sampleList(np.real(self.data)) @property def im(self): return sampleList(np.imag(self.data)) @property def abs(self): return sampleList(np.abs(self.data)) @property def angle(self): return sampleList(np.angle(self.data)) def conj(self): return sampleList(np.conj(self.data)) @property def T(self): return sampleList(self.data.T) @property def H(self): return sampleList(self.data.T.conj()) @property def dtype(self): return self.data.dtype @dtype.setter def dtype(self, dtype): self.data.dtype = dtype def __getitem__(self, key): return self.data[:, key] def __call__(self, key): return sampleList(self.data[:, key]) def __setitem__(self, key, value): if isinstance(value, self.__class__): value = value.data if isinstance(key, (tuple, list,)): RROMPyAssert(len(key), len(value), "Slice length") for k, val in zip(key, value): self[k] = val else: self.data[:, key] = value.flatten() def __iter__(self): return self.data.T.__iter__() def __eq__(self, other): if not hasattr(other, "shape") or self.shape != other.shape: return False if isinstance(other, self.__class__): fac = other.data else: fac = other return np.allclose(self.data, fac) def __ne__(self, other): return not self == other def __copy__(self): return sampleList(self.data) def __deepcopy__(self, memo): return sampleList(copy(self.data, memo)) def __add__(self, other): if isinstance(other, self.__class__): RROMPyAssert(self.shape, other.shape, "Sample shape") fac = other.data else: fac = other return sampleList(self.data + fac) def __iadd__(self, other): self.data = (self + other).data return self def __sub__(self, other): if isinstance(other, self.__class__): RROMPyAssert(self.shape, other.shape, "Sample shape") fac = other.data else: fac = other return sampleList(self.data - fac) def __isub__(self, other): self.data = (self - other).data return self def __mul__(self, other): if isinstance(other, self.__class__): RROMPyAssert(self.shape, other.shape, "Sample shape") fac = other.data else: fac = other return sampleList(self.data * fac) def __imul__(self, other): self.data = (self * other).data return self def __truediv__(self, other): if isinstance(other, self.__class__): RROMPyAssert(self.shape, other.shape, "Sample shape") fac = other.data else: fac = other return sampleList(self.data / fac) def __idiv__(self, other): self.data = (self / other).data return self def __pow__(self, other): if isinstance(other, self.__class__): RROMPyAssert(self.shape, other.shape, "Sample shape") fac = other.data else: fac = other return sampleList(np.power(self.data, fac)) def __ipow__(self, other): self.data = (self ** other).data return self def __neg__(self): return sampleList(- self.data) def __pos__(self): return sampleList(self.data) def reset(self, size, dtype = np.complex): self.data = np.empty(size, dtype = dtype) self.data[:] = np.nan def append(self, items): if isinstance(items, self.__class__): fac = items.data else: fac = np.array(items, ndmin = 2) self.data = np.append(self.data, fac, axis = 1) def pop(self, idx = -1): self.data = np.delete(self.data, idx, axis = 1) def dot(self, other, sampleListOut : bool = True): if isinstance(other, self.__class__): other = other.data try: prod = dot(self.data, other) except: prod = dot(other.T, self.data.T).T if sampleListOut: prod = sampleList(prod) return prod diff --git a/rrompy/sampling/standard/sampling_engine_standard.py b/rrompy/sampling/standard/sampling_engine_standard.py index 258d6a0..387f888 100644 --- a/rrompy/sampling/standard/sampling_engine_standard.py +++ b/rrompy/sampling/standard/sampling_engine_standard.py @@ -1,105 +1,104 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.sampling.base.sampling_engine_base import SamplingEngineBase from rrompy.utilities.base.types import Np1D, paramVal, paramList, sampList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.exception_manager import RROMPyException -from rrompy.utilities.numerical import nextDerivativeIndices, dot +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.hash_derivative import nextDerivativeIndices from rrompy.parameter import checkParameter, checkParameterList __all__ = ['SamplingEngineStandard'] class SamplingEngineStandard(SamplingEngineBase): - """HERE""" - def preprocesssamples(self, idxs:Np1D) -> sampList: if self.samples is None or len(self.samples) == 0: return return self.samples(idxs) def postprocessu(self, u:sampList, overwrite : bool = False): self.setsample(u, overwrite) def postprocessuBulk(self): pass def _getSampleConcurrence(self, mu:paramVal, previous:Np1D) -> sampList: if not (self.sample_state or self.HFEngine.isCEye): raise RROMPyException(("Derivatives of solution with non-scalar " "C not computable.")) if not self.HFEngine._isStateShiftZero: raise RROMPyException(("Derivatives of solution with non-zero " "solution shift not computable.")) if len(previous) >= len(self._derIdxs): self._derIdxs += nextDerivativeIndices(self._derIdxs, self.HFEngine.npar, len(previous) + 1 - len(self._derIdxs)) derIdx = self._derIdxs[len(previous)] mu = checkParameter(mu, self.HFEngine.npar) samplesOld = self.preprocesssamples(previous) RHS = self.HFEngine.b(mu, derIdx) for j, derP in enumerate(self._derIdxs[: len(previous)]): diffP = [x - y for (x, y) in zip(derIdx, derP)] if np.all([x >= 0 for x in diffP]): RHS -= dot(self.HFEngine.A(mu, diffP), samplesOld[j]) return self.solveLS(mu, RHS = RHS) - def nextSample(self, mu : paramVal = [], overwrite : bool = False, + def nextSample(self, mu:paramVal, overwrite : bool = False, postprocess : bool = True) -> Np1D: mu = checkParameter(mu, self.HFEngine.npar) muidxs = self.mus.findall(mu[0]) if len(muidxs) > 0: u = self._getSampleConcurrence(mu, np.sort(muidxs)) else: u = self.solveLS(mu) if postprocess: self.postprocessu(u, overwrite = overwrite) else: self.setsample(u, overwrite) if overwrite: self.mus[self.nsamples] = mu[0] else: self.mus.append(mu) self.nsamples += 1 return self.samples[self.nsamples - 1] def iterSample(self, mus:paramList) -> sampList: mus = checkParameterList(mus, self.HFEngine.npar)[0] vbMng(self, "INIT", "Starting sampling iterations.", 5) n = len(mus) if n <= 0: raise RROMPyException(("Number of samples must be positive.")) self.resetHistory() if len(mus.unique()) != n: for j in range(n): vbMng(self, "MAIN", "Computing sample {} / {}.".format(j + 1, n), 7) self.nextSample(mus[j], overwrite = (j > 0), postprocess = False) if n > 1 and j == 0: self.preallocateSamples(self.samples[0], mus[0], n) else: self.setsample(self.solveLS(mus), overwrite = False) self.mus = copy(mus) self.nsamples = n self.postprocessuBulk() vbMng(self, "DEL", "Finished sampling iterations.", 5) return self.samples diff --git a/rrompy/sampling/standard/sampling_engine_standard_pod.py b/rrompy/sampling/standard/sampling_engine_standard_pod.py index cc7dd7c..bf29279 100644 --- a/rrompy/sampling/standard/sampling_engine_standard_pod.py +++ b/rrompy/sampling/standard/sampling_engine_standard_pod.py @@ -1,91 +1,89 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from rrompy.sampling.base.pod_engine import PODEngine from .sampling_engine_standard import SamplingEngineStandard from rrompy.utilities.base.types import Np1D, paramVal, sampList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.sampling import sampleList, emptySampleList __all__ = ['SamplingEngineStandardPOD'] class SamplingEngineStandardPOD(SamplingEngineStandard): - """HERE""" - @property def HFEngine(self): """Value of HFEngine. Its assignment resets history.""" return self._HFEngine @HFEngine.setter def HFEngine(self, HFEngine): SamplingEngineStandard.HFEngine.fset(self, HFEngine) self.PODEngine = PODEngine(self._HFEngine) def resetHistory(self): super().resetHistory() self.samples_full = emptySampleList() self.RPOD = np.zeros((0, 0), dtype = np.complex) def popSample(self): if hasattr(self, "nsamples") and self.nsamples > 1: self.RPOD = self.RPOD[: -1, : -1] self.samples_full.pop() super().popSample() def preprocesssamples(self, idxs:Np1D) -> sampList: if self.samples_full is None or len(self.samples_full) == 0: return return self.samples_full(idxs) def setsample(self, u:sampList, overwrite : bool = False): super().setsample(u, overwrite) if overwrite: self.samples_full[self.nsamples] = u else: if self.nsamples == 0: self.samples_full = sampleList(u) else: self.samples_full.append(u) def postprocessu(self, u:sampList, overwrite : bool = False): if overwrite: self.samples_full[self.nsamples] = u else: if self.nsamples == 0: self.samples_full = sampleList(u) else: self.samples_full.append(u) vbMng(self, "INIT", "Starting orthogonalization.", 20) u, r, _ = self.PODEngine.GS(u, self.samples, is_state = self.sample_state) self.RPOD = np.pad(self.RPOD, ((0, 1), (0, 1)), 'constant') self.RPOD[:, -1] = r vbMng(self, "DEL", "Done orthogonalizing.", 20) super().setsample(u, overwrite) def postprocessuBulk(self): vbMng(self, "INIT", "Starting orthogonalization.", 10) u, self.RPOD = self.PODEngine.generalizedQR(self.samples_full, is_state = self.sample_state) vbMng(self, "DEL", "Done orthogonalizing.", 10) self.samples = sampleList(u) def preallocateSamples(self, u:Np1D, mu:paramVal, n:int): super().preallocateSamples(u, mu, n) self.samples_full.reset((u.shape[0], n), u.dtype) self.samples_full[0] = u diff --git a/rrompy/solver/__init__.py b/rrompy/solver/__init__.py index 391cfa5..efe2441 100644 --- a/rrompy/solver/__init__.py +++ b/rrompy/solver/__init__.py @@ -1,34 +1,33 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .linear_solver import RROMPyLinearSolvers, setupSolver +from .linear_solver import RROMPyLinearSolvers from .norm_utilities import (Np2DLike, Np2DLikeInv, Np2DLikeInvLowRank, normEngine) __all__ = [ 'RROMPyLinearSolvers', - 'setupSolver', 'Np2DLike', 'Np2DLikeInv', 'Np2DLikeInvLowRank', 'normEngine' ] diff --git a/rrompy/solver/linear_solver.py b/rrompy/solver/linear_solver.py index 53a33d3..6810d08 100644 --- a/rrompy/solver/linear_solver.py +++ b/rrompy/solver/linear_solver.py @@ -1,71 +1,71 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy.linalg as npla import scipy.sparse.linalg as scspla -from rrompy.utilities.base import purgeDict +from rrompy.utilities.base.data_structures import purgeDict from rrompy.utilities.base.types import Tuple, DictAny from rrompy.utilities.exception_manager import RROMPyException __all__ = ['RROMPyLinearSolvers', 'setupSolver'] RROMPyLinearSolvers = { "SOLVE" : (lambda A, b, kwargs: npla.solve(A, b, **kwargs), []), "LSTSQ" : (lambda A, b, kwargs: npla.lstsq(A, b, **kwargs)[0], ["rcond"]), "SPSOLVE" : (lambda A, b, kwargs: scspla.spsolve(A, b, **kwargs), ["permc_spec", "use_umfpack"]), "BICG" : (lambda A, b, kwargs: scspla.bicg(A, b, **kwargs)[0], ["x0", "tol", "maxiter", "M", "callback", "atol"]), "BICGSTAB" : (lambda A, b, kwargs: scspla.bicgstab(A, b, **kwargs)[0], ["x0", "tol", "maxiter", "M", "callback", "atol"]), "CG" : (lambda A, b, kwargs: scspla.cg(A, b, **kwargs)[0], ["x0", "tol", "maxiter", "M", "callback", "atol"]), "CGS" : (lambda A, b, kwargs: scspla.cgs(A, b, **kwargs)[0], ["x0", "tol", "maxiter", "M", "callback", "atol"]), "GMRES" : (lambda A, b, kwargs: scspla.gmres(A, b, **kwargs)[0], ["x0", "tol", "restart", "maxiter", "M", "callback", "restrt", "atol"]), "LGMRES" : (lambda A, b, kwargs: scspla.lgmres(A, b, **kwargs)[0], ["x0", "tol", "maxiter", "M", "callback", "inner_m", "outer_k", "outer_v", "store_outer_Av", "prepend_outer_v", "atol"]), "MINRES" : (lambda A, b, kwargs: scspla.minres(A, b, **kwargs)[0], ["x0", "shift", "tol", "maxiter", "M", "callback", "show", "check"]), "QMR" : (lambda A, b, kwargs: scspla.qmr(A, b, **kwargs)[0], ["x0", "tol", "maxiter", "M1", "M2", "callback", "atol"]), "GCROTMK" : (lambda A, b, kwargs: scspla.gcrotmk(A, b, **kwargs)[0], ["x0", "tol", "maxiter", "M", "callback", "m", "k", "CU", "discard_C", "truncate", "atol"]) } def setupSolver(solverType:str, solverArgs : DictAny = {})\ -> Tuple[callable, DictAny]: solverType = solverType.upper() if solverType not in RROMPyLinearSolvers.keys(): raise RROMPyException(("Solver type not recognized. Check allowed " "values in RROMPyLinearSolvers.keys().")) solver, solverArgsList = RROMPyLinearSolvers[solverType] solverArgs = purgeDict(solverArgs, solverArgsList, dictname = "{}.kwargs".format(solverType), baselevel = 1) return solver, solverArgs diff --git a/rrompy/solver/norm_utilities.py b/rrompy/solver/norm_utilities.py index 75298a1..358d53c 100644 --- a/rrompy/solver/norm_utilities.py +++ b/rrompy/solver/norm_utilities.py @@ -1,91 +1,91 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from abc import abstractmethod import numpy as np from numbers import Number from copy import deepcopy as copy from rrompy.utilities.base.types import Np1D, Np2D, DictAny from rrompy.utilities.numerical import dot as tdot, solve as tsolve -from rrompy.solver.linear_solver import setupSolver +from .linear_solver import setupSolver from rrompy.utilities.exception_manager import RROMPyException __all__ = ['Np2DLike', 'Np2DLikeInv', 'Np2DLikeInvLowRank', 'normEngine'] -@abstractmethod class Np2DLike: + @abstractmethod def dot(self, u:Np2D) -> Np2D: pass class Np2DLikeInv(Np2DLike): def __init__(self, K:Np2D, M:Np2D, solverType:str, solverArgs:DictAny): self.K, self.M = K, M self.MH = np.conj(M) if isinstance(self.M, Number) else M.T.conj() try: self.solver, self.solverArgs = setupSolver(solverType, solverArgs) except: self.solver, self.solverArgs = solverType, solverArgs def dot(self, u:Np2D) -> Np2D: return tdot(self.MH, tsolve(self.K, tdot(self.M, u), self.solver, self.solverArgs)).reshape(u.shape) @property def shape(self): if isinstance(self.M, Number): return self.K.shape return (self.MH.shape[0], self.M.shape[1]) class Np2DLikeInvLowRank(Np2DLike): def __init__(self, K:Np2D, M:Np2D, solverType:str, solverArgs:DictAny, rank:int, oversampling : int = 10, seed : int = 420): sizeO = K.shape[1] if hasattr(K, "shape") else M.shape[1] if rank > sizeO: raise RROMPyException(("Cannot select compressed rank larger than " "original size.")) if oversampling < 0: raise RROMPyException("Oversampling parameter must be positive.") HF = Np2DLikeInv(K, M, solverType, solverArgs) np.random.seed(seed) xs = np.random.randn(sizeO, rank + oversampling) samples = HF.dot(xs) try: Q, _ = np.linalg.qr(samples, mode = "reduced") R = HF.dot(Q).T.conj() # assuming HF (i.e. K) hermitian... U, s, Vh = np.linalg.svd(R, full_matrices = False) self.L = Q.dot(U[:, : rank]) * s[: rank] self.R = Vh[: rank, :] except np.linalg.LinAlgError as e: raise RROMPyException(e) def dot(self, u:Np2D) -> Np2D: return tdot(self.L, tdot(self.R, u)).reshape(u.shape) @property def shape(self): return (self.L.shape[0], self.R.shape[1]) class normEngine: def __init__(self, normMatrix:Np2D): self.normMatrix = copy(normMatrix) def innerProduct(self, u:Np2D, v:Np2D, onlyDiag : bool = False) -> Np2D: if not isinstance(u, (np.ndarray,)): u = u.data if not isinstance(v, (np.ndarray,)): v = v.data if onlyDiag: return np.sum(tdot(self.normMatrix, u) * v.conj(), axis = 0) return tdot(tdot(self.normMatrix, u).T, v.conj()).T def norm(self, u:Np2D) -> Np1D: return np.power(np.abs(self.innerProduct(u, u, onlyDiag = True)), .5) diff --git a/rrompy/utilities/base/__init__.py b/rrompy/utilities/base/__init__.py index 96c7395..7f12ffd 100644 --- a/rrompy/utilities/base/__init__.py +++ b/rrompy/utilities/base/__init__.py @@ -1,45 +1,29 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .decorators import affine_construct, nonaffine_construct -from .find_dict_str_key import findDictStrKey -from .get_new_filename import getNewFilename -from .pickle_utilities import pickleDump, pickleLoad -from .purge_dict import purgeDict -from .purge_list import purgeList -from . import types as Types from .verbosity_depth import verbosityDepth, verbosityManager freepar = None __all__ = [ - 'affine_construct', - 'nonaffine_construct', - 'findDictStrKey', - 'getNewFilename', - 'pickleDump', - 'pickleLoad', - 'purgeDict', - 'purgeList', - 'Types', 'verbosityDepth', 'verbosityManager', 'freepar' ] diff --git a/rrompy/utilities/base/purge_dict.py b/rrompy/utilities/base/data_structures.py similarity index 52% rename from rrompy/utilities/base/purge_dict.py rename to rrompy/utilities/base/data_structures.py index 3ace6e4..116b9ce 100644 --- a/rrompy/utilities/base/purge_dict.py +++ b/rrompy/utilities/base/data_structures.py @@ -1,42 +1,77 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from rrompy.utilities.base.find_dict_str_key import findDictStrKey -from rrompy.utilities.base.types import ListAny, DictAny +import os +import time +from rrompy.utilities.base.types import Any, DictAny, ListAny from rrompy.utilities.exception_manager import RROMPyWarning -__all__ = ['purgeDict'] +__all__ = ['findDictStrKey', 'purgeDict', 'purgeList'] + +def findDictStrKey(key:Any, keyList:ListAny): + for akey in keyList: + if isinstance(key, str) and key.lower() == akey.lower(): + return akey + return None def purgeDict(dct:DictAny, allowedKeys : ListAny = [], silent : bool = False, complement : bool = False, dictname : str = "", baselevel : int = 0) -> DictAny: if dictname != "": dictname = " in " + dictname dctcp = {} for key in dct.keys(): akey = findDictStrKey(key, allowedKeys) if (akey is None) != complement: if not silent: RROMPyWarning(("Ignoring key {0}{2} with value " "{1}.").format(key, dct[key], dictname), baselevel) else: if akey is None: akey = key dctcp[akey] = dct[key] return dctcp + +def purgeList(lst:ListAny, allowedEntries : ListAny = [], + silent : bool = False, complement : bool = False, + listname : str = "", baselevel : int = 0) -> ListAny: + if listname != "": + listname = " in " + listname + lstcp = [] + for x in lst: + ax = findDictStrKey(x, allowedEntries) + if (ax is None) != complement: + if not silent: + RROMPyWarning("Ignoring entry {0}{1}.".format(x, listname), + baselevel) + else: + lstcp = lstcp + [ax] + return lstcp + +def getNewFilename(prefix : str = "", extension : str = "dat", + timestamp : bool = True) -> str: + extra = "" + if timestamp: extra = time.strftime("_%y-%m-%d_%H:%M:%S", time.localtime()) + filenameBase = "{}{}.{}".format(prefix, extra, extension) + idx = 0 + filename = filenameBase + while os.path.exists(filename): + idx += 1 + filename = filenameBase + "{}".format(idx) + return filename diff --git a/rrompy/utilities/base/find_dict_str_key.py b/rrompy/utilities/base/find_dict_str_key.py deleted file mode 100644 index 60168fb..0000000 --- a/rrompy/utilities/base/find_dict_str_key.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (C) 2018 by the RROMPy authors -# -# This file is part of RROMPy. -# -# RROMPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# RROMPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with RROMPy. If not, see . -# - -from rrompy.utilities.base.types import Any, ListAny - -__all__ = ['findDictStrKey'] - -def findDictStrKey(key:Any, keyList:ListAny): - for akey in keyList: - if isinstance(key, str) and key.lower() == akey.lower(): - return akey - return None - diff --git a/rrompy/utilities/base/get_new_filename.py b/rrompy/utilities/base/get_new_filename.py deleted file mode 100644 index 60aba6b..0000000 --- a/rrompy/utilities/base/get_new_filename.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (C) 2018 by the RROMPy authors -# -# This file is part of RROMPy. -# -# RROMPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# RROMPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with RROMPy. If not, see . -# - -import os -import time - -__all__ = ['getNewFilename'] - -def getNewFilename(prefix : str = "", extension : str = "dat", - timestamp : bool = True) -> str: - extra = "" - if timestamp: extra = time.strftime("_%y-%m-%d_%H:%M:%S", time.localtime()) - filenameBase = "{}{}.{}".format(prefix, extra, extension) - idx = 0 - filename = filenameBase - while os.path.exists(filename): - idx += 1 - filename = filenameBase + "{}".format(idx) - return filename diff --git a/rrompy/utilities/base/purge_list.py b/rrompy/utilities/base/purge_list.py deleted file mode 100644 index 8cd28b2..0000000 --- a/rrompy/utilities/base/purge_list.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2018 by the RROMPy authors -# -# This file is part of RROMPy. -# -# RROMPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# RROMPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with RROMPy. If not, see . -# - -from rrompy.utilities.base.find_dict_str_key import findDictStrKey -from rrompy.utilities.base.types import ListAny -from rrompy.utilities.exception_manager import RROMPyWarning - -__all__ = ['purgeList'] - -def purgeList(lst:ListAny, allowedEntries : ListAny = [], - silent : bool = False, complement : bool = False, - listname : str = "", baselevel : int = 0) -> ListAny: - if listname != "": - listname = " in " + listname - lstcp = [] - for x in lst: - ax = findDictStrKey(x, allowedEntries) - if (ax is None) != complement: - if not silent: - RROMPyWarning("Ignoring entry {0}{1}.".format(x, listname), - baselevel) - else: - lstcp = lstcp + [ax] - return lstcp - diff --git a/rrompy/utilities/expression/__init__.py b/rrompy/utilities/expression/__init__.py index b22ee8d..a24f3b6 100644 --- a/rrompy/utilities/expression/__init__.py +++ b/rrompy/utilities/expression/__init__.py @@ -1,34 +1,28 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .keys import (expressionKeysUnary, expressionKeysUnaryParam, - expressionKeysBinary, expressionKeysBinaryParam) from .expression_evaluator import expressionEvaluator from .monomial_creator import createMonomial, createMonomialList __all__ = [ - 'expressionKeysUnary', - 'expressionKeysUnaryParam', - 'expressionKeysBinary', - 'expressionKeysBinaryParam', 'expressionEvaluator', 'createMonomial', 'createMonomialList' ] diff --git a/rrompy/utilities/expression/monomial_creator.py b/rrompy/utilities/expression/monomial_creator.py index dcbffe1..a90335c 100644 --- a/rrompy/utilities/expression/monomial_creator.py +++ b/rrompy/utilities/expression/monomial_creator.py @@ -1,57 +1,58 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np -from rrompy.utilities.numerical import (multibinom, nextDerivativeIndices, - hashIdxToDerivative as hashI, - hashDerivativeToIdx as hashD) +from rrompy.utilities.numerical.factorials import multibinom +from rrompy.utilities.numerical.hash_derivative import (nextDerivativeIndices, + hashIdxToDerivative as hashI, + hashDerivativeToIdx as hashD) from rrompy.utilities.base.types import List, TupleAny __all__ = ["createMonomial", "createMonomialList"] def createMonomial(deg:List[int], return_derivatives : bool = False) -> List[List[TupleAny]]: if not hasattr(deg, "__len__"): deg = [deg] dim = len(deg) degj = hashD(deg) expr = [] for k in range(degj * return_derivatives + 1): degder = hashI(k, dim) derdiff = [x - y for (x, y) in zip(deg, degder)] if all([d >= 0 for d in derdiff]): mult = multibinom(deg, degder) if np.sum(derdiff) == 0: exprLoc = (mult,) else: exprLoc = ("prod", {"axis" : 1}, ("data", "x", "**", derdiff)) if not np.isclose(mult, 1): exprLoc = exprLoc + ("*", mult,) expr += [exprLoc] else: expr += [(0.,)] if return_derivatives: expr += [None] return expr def createMonomialList(n:int, dim:int, return_derivatives : bool = False) -> List[List[TupleAny]]: derIdxs = nextDerivativeIndices([], dim, n) idxList = [] for j, der in enumerate(derIdxs): idxList += [createMonomial(der, return_derivatives)] return idxList diff --git a/rrompy/utilities/numerical/__init__.py b/rrompy/utilities/numerical/__init__.py index f3374ca..1d1051e 100644 --- a/rrompy/utilities/numerical/__init__.py +++ b/rrompy/utilities/numerical/__init__.py @@ -1,76 +1,39 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from .custom_pinv import customPInv -from .degree import (fullDegreeN, totalDegreeN, reduceDegreeN, fullDegreeSet, - totalDegreeSet, degreeTotalToFull, fullDegreeMaxMask, - totalDegreeMaxMask) -from .factorials import multibinom, multifactorial from .halton import haltonGenerate -from .hash_derivative import (nextDerivativeIndices, hashDerivativeToIdx, - hashIdxToDerivative) from .kroneckerer import kroneckerer from .low_discrepancy import lowDiscrepancy -from .marginalize_poly_list import marginalizePolyList -from .nonlinear_eigenproblem import (linearizeDense, eigNonlinearDense, - eigvalsNonlinearDense) -from .number_theory import squareResonances -from .point_matching import (pointMatching, potential, angleTable, - chordalMetricTable, chordalMetricAdjusted) +from .point_matching import pointMatching, potential from .quadrature_points import quadraturePointsGenerate -from .rayleigh_quotient_iteration import rayleighQuotientIteration from .sobol import sobolGenerate from .tensor_la import dot, solve -freepar = None - __all__ = [ - 'freepar', 'customPInv', - 'fullDegreeN', - 'totalDegreeN', - 'reduceDegreeN', - 'fullDegreeSet', - 'totalDegreeSet', - 'degreeTotalToFull', - 'fullDegreeMaxMask', - 'totalDegreeMaxMask', - 'multibinom', - 'multifactorial', 'haltonGenerate', - 'nextDerivativeIndices', - 'hashDerivativeToIdx', - 'hashIdxToDerivative', 'kroneckerer', 'lowDiscrepancy', - 'marginalizePolyList', - 'linearizeDense', - 'eigNonlinearDense', - 'eigvalsNonlinearDense', - 'squareResonances', 'pointMatching', 'potential', - 'angleTable', - 'chordalMetricTable', - 'chordalMetricAdjusted', 'quadraturePointsGenerate', - 'rayleighQuotientIteration', 'sobolGenerate', 'dot', 'solve' ] diff --git a/rrompy/utilities/poly_fitting/__init__.py b/rrompy/utilities/poly_fitting/__init__.py index 7655844..dc4d3ac 100644 --- a/rrompy/utilities/poly_fitting/__init__.py +++ b/rrompy/utilities/poly_fitting/__init__.py @@ -1,27 +1,25 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from .custom_fit import customFit -from .interpolator import GenericInterpolator __all__ = [ - 'customFit', - 'GenericInterpolator' + 'customFit' ] diff --git a/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py b/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py index c0d644b..ac3b776 100644 --- a/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py +++ b/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py @@ -1,74 +1,72 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from rrompy.utilities.base.types import (List, ListAny, Np1D, paramList, interpEng) from rrompy.utilities.base import freepar as fp from rrompy.utilities.poly_fitting.polynomial.polynomial_interpolator import ( PolynomialInterpolator) from rrompy.utilities.poly_fitting.polynomial.roots import polyroots from .val import polyval from .heaviside_to_from_affine import affine2heaviside from .heaviside_to_from_rational import heaviside2rational, rational2heaviside from rrompy.utilities.exception_manager import RROMPyAssert __all__ = ['HeavisideInterpolator'] class HeavisideInterpolator(PolynomialInterpolator): - """HERE""" - def __init__(self, other = None): if other is None: return self.poles = other.poles super().__init__(other) def __call__(self, mu:paramList, der : List[int] = None, scl : Np1D = None): return polyval(mu, self.coeffs, self.poles, self.polybasis) def __copy__(self): return HeavisideInterpolator(self) def __deepcopy__(self, memo): other = HeavisideInterpolator() other.poles, other.coeffs, other.npar, other.polybasis = copy( (self.poles, self.coeffs, self.npar, self.polybasis), memo) return other def setupFromAffine(self, As:ListAny, bs:ListAny, jSupp : int = 1): self.coeffs, self.poles, self.polybasis = affine2heaviside(As, bs, jSupp) def setupFromRational(self, num:interpEng, den:interpEng, murange : Np1D = np.array([-1., 1.]), scl : Np1D = None, scalingExp : List[float] = None): self.coeffs, self.poles, self.polybasis = rational2heaviside(num, den, murange, scl, scalingExp) def roots(self, marginalVals : ListAny = [fp], murange : Np1D = None, scalingExp : List[float] = None): RROMPyAssert(self.shape, (1,), "Shape of output") RROMPyAssert(marginalVals, [fp], "Marginal values") basisN = self.polybasis.split("_")[0] coeffsN = heaviside2rational(self.coeffs, self.poles, murange, basisN, scalingExp = scalingExp)[0] return polyroots(coeffsN, basisN) diff --git a/rrompy/utilities/poly_fitting/heaviside/heaviside_to_from_affine.py b/rrompy/utilities/poly_fitting/heaviside/heaviside_to_from_affine.py index 931b7d7..0045e23 100644 --- a/rrompy/utilities/poly_fitting/heaviside/heaviside_to_from_affine.py +++ b/rrompy/utilities/poly_fitting/heaviside/heaviside_to_from_affine.py @@ -1,95 +1,96 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from scipy.special import binom import scipy.sparse as sp from rrompy.utilities.base.types import (Np1D, Np2D, List, ListAny, Tuple, paramVal) -from rrompy.utilities.numerical import eigNonlinearDense, dot, solve +from rrompy.utilities.numerical import dot, solve +from rrompy.utilities.numerical.nonlinear_eigenproblem import eigNonlinearDense from rrompy.utilities.exception_manager import RROMPyException from rrompy.parameter import checkParameter __all__ = ['heaviside2affine', 'affine2heaviside'] def heaviside2affine(c:Np2D, poles:Np1D, mu : paramVal = [], basis : str = "MONOMIAL_HEAVISIDE", sparse : bool = False) \ -> Tuple[Np2D, List[Np2D], List[Np1D]]: mu = checkParameter(mu, 1)(0, 0) n, d = len(poles), len(c) - len(poles) basisN = basis.split("_")[0] if basisN not in ["MONOMIAL", "CHEBYSHEV", "LEGENDRE"]: raise RROMPyException("Polynomial basis not recognized.") if sparse: A0 = sp.spdiags([np.concatenate((- mu - poles, np.ones(d)))], [0], n + d, n + d) A1 = sp.spdiags([np.concatenate((np.ones(n), np.zeros(d)))], [0], n + d, n + d) else: A0 = np.diag(np.concatenate((mu - poles, np.ones(d)))) A1 = np.diag(np.concatenate((np.ones(n), np.zeros(d)))) As = [A0, A1] bs = np.zeros((d, n + d), dtype = poles.dtype) bs[0, :] = 1. if d > 0: bs[0, n + 1 :] = 0. if d > 1: bs[1, n + 1] = 1. for j in range(2, d): if basisN == "MONOMIAL": bs[j, n + j] = 1. else: alpha = - 1. if basisN == "CHEBYSHEV" else 1. / j - 1. bs[:, n + j] = alpha * bs[:, n + j - 2] bs[1 :, n + j] += (1. - alpha) * bs[: -1, n + j - 1] bs = list(bs) return c.reshape(c.shape[0], -1).T, As, bs def affine2heaviside(As:ListAny, bs:ListAny, jSupp : int = 1) -> Tuple[Np2D, Np1D, str]: if jSupp != 1 and not (isinstance(jSupp, (int,)) and jSupp.upper() == "COMPANION"): raise RROMPyException(("Affine to heaviside conversion does not allow " "nonlinear eigenproblem support outside first " "block row.")) N = len(As) M = len(bs) n = As[0].shape[0] if N == 1: poles = np.empty(0, dtype = np.complex) Q = np.eye(n) else: basis = "MONOMIAL_HEAVISIDE" poles, P, Q = eigNonlinearDense(As, jSupp = jSupp, return_inverse = True) P = P[- n :, :] Q = Q[:, : n] bEffs = np.array([dot(Q, solve(As[-1], b, np.linalg.solve, {})) for b in bs]) if N == 1: c = bEffs else: c = np.zeros((len(poles) + M - 1, As[0].shape[1]), dtype = np.complex) for l, pl in enumerate(poles): for i in range(M): c[l, :] = pl ** i * bEffs[i, l] * P[:, l] for l in range(M - 1): for i in range(l + 1, M): c[len(poles) + l, :] = dot(P, poles ** (i- 1 - l) * bEffs[i, :]) return c, poles, basis diff --git a/rrompy/utilities/poly_fitting/interpolator.py b/rrompy/utilities/poly_fitting/interpolator.py index fd08cd3..34f3bf7 100644 --- a/rrompy/utilities/poly_fitting/interpolator.py +++ b/rrompy/utilities/poly_fitting/interpolator.py @@ -1,41 +1,39 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from abc import abstractmethod from rrompy.utilities.base.types import List, paramList __all__ = ['GenericInterpolator'] class GenericInterpolator: - """HERE""" - @abstractmethod def __init__(self, other = None): pass @abstractmethod def __call__(self, mu:paramList, der : List[int] = None): pass @abstractmethod def __copy__(self): pass @abstractmethod def __deepcopy__(self, memo): pass diff --git a/rrompy/utilities/poly_fitting/moving_least_squares/moving_least_squares_interpolator.py b/rrompy/utilities/poly_fitting/moving_least_squares/moving_least_squares_interpolator.py index 5dd78d1..a333338 100644 --- a/rrompy/utilities/poly_fitting/moving_least_squares/moving_least_squares_interpolator.py +++ b/rrompy/utilities/poly_fitting/moving_least_squares/moving_least_squares_interpolator.py @@ -1,144 +1,142 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from rrompy.utilities.base.types import (List, ListAny, DictAny, Np1D, Np2D, paramList) from rrompy.utilities.numerical import customPInv, dot from .vander import mlsweights from rrompy.utilities.poly_fitting.interpolator import GenericInterpolator from rrompy.utilities.poly_fitting.polynomial.vander import (polyvander as pv, polyvanderTotal as pvT) from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert from rrompy.parameter import checkParameterList __all__ = ['MovingLeastSquaresInterpolator'] class MovingLeastSquaresInterpolator(GenericInterpolator): - """HERE""" - def __init__(self, other = None): if other is None: return self.support = other.support self.localProjector = other.localProjector self.localVanders = other.localVanders self.suppValues = other.suppValues self.directionalWeights = other.directionalWeights self.degree = other.degree self.npar = other.npar self.radialbasis = other.radialbasis self.polybasis = other.polybasis self.evalParams = other.evalParams self.totalDegree = other.totalDegree @property def shape(self): sh = self.suppValues.shape[1 :] if self.suppValues.ndim > 1 else 1 return sh @property def deg(self): return self.degree def __call__(self, mu:paramList, der : List[int] = None, scl : Np1D = None): if der is not None and np.sum(der) > 0: raise RROMPyException(("Cannot take derivatives of moving least " "squares function.")) mu = checkParameterList(mu, self.npar)[0] sh = self.shape if sh == 1: sh = tuple([]) values = np.empty((len(mu),) + sh, dtype = np.complex) for i, m in enumerate(mu): weights = mlsweights(m, self.support, self.radialbasis, directionalWeights = self.directionalWeights, nNearestNeighbor = self.evalParams["nNearestNeighbor"]) weights /= np.linalg.norm(weights) vanderLS = np.sum(self.localVanders * weights, axis = 2) RHSLS = dot(self.localProjector * weights, self.suppValues) if self.totalDegree: vanderEval = pvT(m, self.deg[0], self.polybasis, **self.evalParams) else: vanderEval = pv(m, self.deg, self.polybasis, **self.evalParams) vanderEval = vanderEval.flatten() values[i] = dot(vanderEval, dot(customPInv(vanderLS), RHSLS)) return values def __copy__(self): return MovingLeastSquaresInterpolator(self) def __deepcopy__(self, memo): other = MovingLeastSquaresInterpolator() (other.support, other.localProjector, other.localVanders, other.suppValues, other.directionalWeights, other.degree, other.npar, other.radialbasis, other.polybasis, other.evalParams, other.totalDegree) = copy( (self.support, self.localProjector, self.localVanders, self.suppValues, self.directionalWeights, self.degree, self.npar, self.radialbasis, self.polybasis, self.evalParams, self.totalDegree), memo) return other def postmultiplyTensorize(self, A:Np2D): RROMPyAssert(A.shape[0], self.shape[-1], "Shape of output") self.suppValues = self.suppValues.dot(A) def pad(self, nleft : List[int] = None, nright : List[int] = None): if nleft is None: nleft = [0] * len(self.shape) if nright is None: nright = [0] * len(self.shape) if not hasattr(nleft, "__len__"): nleft = [nleft] if not hasattr(nright, "__len__"): nright = [nright] RROMPyAssert(len(self.shape), len(nleft), "Shape of output") RROMPyAssert(len(self.shape), len(nright), "Shape of output") padwidth = [(0, 0)] + [(l, r) for l, r in zip(nleft, nright)] self.suppValues = np.pad(self.suppValues, padwidth, "constant", constant_values = (0., 0.)) def setupByInterpolation(self, support:paramList, values:ListAny, deg:int, polybasis : str = "MONOMIAL_GAUSSIAN", directionalWeights : Np1D = None, totalDegree : bool = True, vanderCoeffs : DictAny = {}): support = checkParameterList(support)[0] self.support = copy(support) if "reorder" in vanderCoeffs.keys(): self.support = self.support[vanderCoeffs["reorder"]] if "nNearestNeighbor" not in vanderCoeffs.keys(): vanderCoeffs["nNearestNeighbor"] = -1 self.npar = support.shape[1] if directionalWeights is None: directionalWeights = np.ones(self.npar) self.directionalWeights = directionalWeights self.polybasis, self.radialbasis, _ = polybasis.split("_") self.totalDegree = totalDegree self.evalParams = vanderCoeffs if totalDegree: vander = pvT(support, deg, self.polybasis, **vanderCoeffs) if not hasattr(deg, "__len__"): deg = [deg] * self.npar else: if not hasattr(deg, "__len__"): deg = [deg] * self.npar vander = pv(support, deg, self.polybasis, **vanderCoeffs) self.degree = deg self.localProjector = vander.T.conj() self.localVanders = np.array([np.outer(van, van.conj()) \ for van in vander]) self.localVanders = np.swapaxes(self.localVanders, 0, 2) self.suppValues = np.array(values) diff --git a/rrompy/utilities/poly_fitting/polynomial/polynomial_algebra.py b/rrompy/utilities/poly_fitting/polynomial/polynomial_algebra.py index 35c61e9..d50df84 100644 --- a/rrompy/utilities/poly_fitting/polynomial/polynomial_algebra.py +++ b/rrompy/utilities/poly_fitting/polynomial/polynomial_algebra.py @@ -1,144 +1,146 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List, interpEng from .vander import polyvander from .polynomial_interpolator import PolynomialInterpolator -from rrompy.utilities.numerical import (multifactorial, customPInv, - hashDerivativeToIdx as hashD, - hashIdxToDerivative as hashI) +from rrompy.utilities.numerical import customPInv +from rrompy.utilities.numerical.factorials import multifactorial +from rrompy.utilities.numerical.hash_derivative import ( + hashDerivativeToIdx as hashD, + hashIdxToDerivative as hashI) from rrompy.utilities.exception_manager import RROMPyException __all__ = ['changePolyBasis', 'polyTimes', 'polyDivide', 'polyTimesTable', 'vanderInvTable', 'blockDiagDer'] def changePolyBasis(P:Np2D, dim : int = None, basis0 : str = "MONOMIAL", basisF : str = "MONOMIAL") -> Np2D: if basis0 == basisF: return P if dim is None: dim = P.ndim if basis0 != "MONOMIAL" and basisF != "MONOMIAL": return changePolyBasis(changePolyBasis(P, dim, basis0, "MONOMIAL"), dim, "MONOMIAL", basisF) basisD = basisF if basis0 == "MONOMIAL" else basis0 R = copy(P) N = np.max(P.shape[: dim]) - 1 vander = polyvander([0], N, basisD, [list(range(N + 1))]) if basis0 == "MONOMIAL": vander = customPInv(vander) for j in range(dim): R = np.tensordot(vander, R, (-1, j)) return R def polyTimes(P:Np2D, Q:Np2D, dim : int = None, Pbasis : str = "MONOMIAL", Qbasis : str = "MONOMIAL", Rbasis : str = "MONOMIAL") -> Np2D: if not isinstance(P, (np.ndarray,)): P = np.array(P) if not isinstance(Q, (np.ndarray,)): Q = np.array(Q) P = changePolyBasis(P, dim, Pbasis, "MONOMIAL") Q = changePolyBasis(Q, dim, Qbasis, "MONOMIAL") if dim is None: dim = P.ndim if dim <= 0: return R = np.zeros([x + y - 1 for (x, y) in zip(P.shape[: dim], Q.shape[: dim])], dtype = P.dtype) if dim == 1: for j, Qj in enumerate(Q): R[j : j + len(P)] = R[j : j + len(P)] + Qj * P else: for j, Qj in enumerate(Q): for l, Pl in enumerate(P): R[j + l] = R[j + l] + polyTimes(Pl, Qj, dim - 1) return changePolyBasis(R, dim, "MONOMIAL", Rbasis) def polyDivide(P:Np2D, Q:Np2D, dim : int = None, Pbasis : str = "MONOMIAL", Qbasis : str = "MONOMIAL", Rbasis : str = "MONOMIAL") -> Tuple[Np2D, Np2D]: if not isinstance(P, (np.ndarray,)): P = np.array(P) if not isinstance(Q, (np.ndarray,)): Q = np.array(Q) P = changePolyBasis(P, dim, Pbasis, "MONOMIAL") Pc = copy(P) Q = changePolyBasis(Q, dim, Qbasis, "MONOMIAL") if dim is None: dim = P.ndim if dim <= 0: return R = np.zeros([x - y + 1 for (x, y) in zip(P.shape[: dim], Q.shape[: dim])], dtype = P.dtype) if dim == 1: for i in range(len(R) - 1, -1, -1): try: R[i] = Pc[-1] / Q[-1] except: raise RROMPyException(("Numerical instability in polynomial " "quotient.")) Pc = Pc[: -1] for j, Qj in enumerate(Q[::-1]): if j > 0: Pc[-j] = Pc[-j] - R[i] * Qj else: raise RROMPyException(("Quotient of multivariate polynomials not " "supported.")) return (changePolyBasis(R, dim, "MONOMIAL", Rbasis), changePolyBasis(Pc, dim, "MONOMIAL", Rbasis)) def polyTimesTable(P:interpEng, mus:Np1D, reorder:List[int], derIdxs:List[List[List[int]]], scl : Np1D = None) -> Np2D: if not isinstance(P, PolynomialInterpolator): raise RROMPyException(("Polynomial to evaluate must be a polynomial " "interpolator.")) Pvals = [[0.] * len(derIdx) for derIdx in derIdxs] for j, derIdx in enumerate(derIdxs): nder = len(derIdx) for der in range(nder): derI = hashI(der, P.npar) Pvals[j][der] = P([mus[j]], derI, scl) / multifactorial(derI) return blockDiagDer(Pvals, reorder, derIdxs) def vanderInvTable(vanInv:Np2D, idxs:List[int], reorder:List[int], derIdxs:List[List[List[int]]]) -> Np2D: S = len(reorder) Ts = [None] * len(idxs) for k in range(len(idxs)): invLocs = [None] * len(derIdxs) idxGlob = 0 for j, derIdx in enumerate(derIdxs): nder = len(derIdx) idxGlob += nder idxLoc = np.arange(S)[np.logical_and(reorder >= idxGlob - nder, reorder < idxGlob)] invLocs[j] = vanInv[k, idxLoc] Ts[k] = blockDiagDer(invLocs, reorder, derIdxs, [2, 1, 0]) return Ts def blockDiagDer(vals:List[Np1D], reorder:List[int], derIdxs:List[List[List[int]]], permute : List[int] = None) -> Np2D: S = len(reorder) T = np.zeros((S, S), dtype = np.complex) if permute is None: permute = [0, 1, 2] idxGlob = 0 for j, derIdx in enumerate(derIdxs): nder = len(derIdx) idxGlob += nder idxLoc = np.arange(S)[np.logical_and(reorder >= idxGlob - nder, reorder < idxGlob)] val = vals[j] for derI, derIdxI in enumerate(derIdx): for derJ, derIdxJ in enumerate(derIdx): diffIdx = [x - y for (x, y) in zip(derIdxI, derIdxJ)] if all([x >= 0 for x in diffIdx]): diffj = hashD(diffIdx) i1, i2, i3 = np.array([derI, derJ, diffj])[permute] T[idxLoc[i1], idxLoc[i2]] = val[i3] return T diff --git a/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py b/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py index 0eb05ff..53283d5 100644 --- a/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py +++ b/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py @@ -1,129 +1,128 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from rrompy.utilities.base.types import (List, ListAny, DictAny, Np1D, Np2D, paramList) from rrompy.utilities.base import freepar as fp from rrompy.utilities.poly_fitting.interpolator import GenericInterpolator from rrompy.utilities.poly_fitting.custom_fit import customFit from .base import polyfitname from .val import polyval from .roots import polyroots from .vander import polyvander as pv, polyvanderTotal as pvT -from rrompy.utilities.numerical import degreeTotalToFull, dot +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.degree import degreeTotalToFull from rrompy.utilities.exception_manager import RROMPyAssert, RROMPyException from rrompy.parameter import checkParameterList __all__ = ['PolynomialInterpolator'] class PolynomialInterpolator(GenericInterpolator): - """HERE""" - def __init__(self, other = None): if other is None: return self.coeffs = other.coeffs self.npar = other.npar self.polybasis = other.polybasis @property def shape(self): if self.coeffs.ndim > self.npar: sh = self.coeffs.shape[self.npar :] else: sh = tuple([1]) return sh @property def deg(self): return [x - 1 for x in self.coeffs.shape[: self.npar]] def __getitem__(self, key): return self.coeffs[key] def __call__(self, mu:paramList, der : List[int] = None, scl : Np1D = None): return polyval(mu, self.coeffs, self.polybasis, der, scl) def __copy__(self): return PolynomialInterpolator(self) def __deepcopy__(self, memo): other = PolynomialInterpolator() other.coeffs, other.npar, other.polybasis = copy( (self.coeffs, self.npar, self.polybasis), memo) return other def pad(self, nleft : List[int] = None, nright : List[int] = None): if nleft is None: nleft = [0] * len(self.shape) if nright is None: nright = [0] * len(self.shape) if not hasattr(nleft, "__len__"): nleft = [nleft] if not hasattr(nright, "__len__"): nright = [nright] RROMPyAssert(len(self.shape), len(nleft), "Shape of output") RROMPyAssert(len(self.shape), len(nright), "Shape of output") padwidth = [(0, 0)] * self.npar padwidth = padwidth + [(l, r) for l, r in zip(nleft, nright)] self.coeffs = np.pad(self.coeffs, padwidth, "constant", constant_values = (0., 0.)) def postmultiplyTensorize(self, A:Np2D): RROMPyAssert(A.shape[0], self.shape[-1], "Shape of output") self.coeffs = dot(self.coeffs, A) def setupByInterpolation(self, support:paramList, values:ListAny, deg:int, polybasis : str = "MONOMIAL", verbose : bool = True, totalDegree : bool = True, vanderCoeffs : DictAny = {}, fitCoeffs : DictAny = {}): support = checkParameterList(support)[0] self.npar = support.shape[1] self.polybasis = polybasis if totalDegree: vander = pvT(support, deg, basis = polybasis, **vanderCoeffs) else: if not hasattr(deg, "__len__"): deg = [deg] * self.npar vander = pv(support, deg, basis = polybasis, **vanderCoeffs) outDim = values.shape[1:] values = values.reshape(values.shape[0], -1) fitOut = customFit(vander, values, full = True, **fitCoeffs) P = fitOut[0] if verbose: msg = ("Fitting {} samples with degree {} through {}... " "Conditioning of LS system: {:.4e}.").format( len(vander), deg, polyfitname(self.polybasis), fitOut[1][2][0] / fitOut[1][2][-1]) else: msg = None if totalDegree: self.coeffs = degreeTotalToFull(tuple([deg + 1] * self.npar) + outDim, self.npar, P) else: self.coeffs = P.reshape(tuple([d + 1 for d in deg]) + outDim) return fitOut[1][1] == vander.shape[1], msg def roots(self, marginalVals : ListAny = [fp]): RROMPyAssert(self.shape, (1,), "Shape of output") RROMPyAssert(len(marginalVals), self.npar, "Number of parameters") try: rDim = marginalVals.index(fp) if rDim < len(marginalVals) - 1 and fp in marginalVals[rDim + 1 :]: raise except: raise RROMPyException(("Exactly 1 'freepar' entry in " "marginalVals must be provided.")) return polyroots(self.coeffs, self.polybasis, marginalVals) diff --git a/rrompy/utilities/poly_fitting/polynomial/vander.py b/rrompy/utilities/poly_fitting/polynomial/vander.py index 3f218d9..4b26d46 100644 --- a/rrompy/utilities/poly_fitting/polynomial/vander.py +++ b/rrompy/utilities/poly_fitting/polynomial/vander.py @@ -1,138 +1,138 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from .der import polyder from rrompy.utilities.base.types import Np1D, Np2D, List, paramList -from rrompy.utilities.numerical import totalDegreeSet +from rrompy.utilities.numerical.degree import totalDegreeSet from rrompy.parameter import checkParameterList from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert __all__ = ['polyvander', 'polyvanderTotal'] def firstDerTransition(dim:int, TDirac:List[Np2D], basis:str, scl : Np1D = None) -> Np2D: C_m = np.zeros((dim, len(TDirac), len(TDirac)), dtype = float) for j, Tj in enumerate(TDirac): m, om = [0] * dim, [(0, 0)] * dim for idx in range(dim): m[idx], om[idx] = 1, (0, 1) J_der = polyder(Tj, basis, m, scl) if J_der.size != len(TDirac): J_der = np.pad(J_der, mode = "constant", pad_width = om) C_m[idx, :, j] = np.ravel(J_der) m[idx], om[idx] = 0, (0, 0) return C_m def countDerDirections(n:int, base:int, digits:int, idx:int): if digits == 0: return [] dig = n % base return [(idx, dig)] * (dig > 0) + countDerDirections( (n - dig) // base, base, digits - 1, idx + 1) def polyvander(x:paramList, degs:List[int], basis:str, derIdxs : List[List[List[int]]] = None, reorder : List[int] = None, scl : Np1D = None) -> Np2D: """ Compute full Hermite-Vandermonde matrix with specified derivative directions. E.g. assume that we want to obtain the Vandermonde matrix for (value, derx, derx2) at x = [0, 0], (value, dery) at x = [1, 0], (dery, derxy) at x = [0, 0], of degree 3 in x and 1 in y, using Chebyshev polynomials. This can be done by polyvander([[0, 0], [1, 0]], # unique sample points [3, 1], # polynomial degree "chebyshev", # polynomial family [ [[0, 0], [1, 0], [2, 0], [0, 1], [1, 1]], # derivative directions at first point [[0, 0], [0, 1]] # derivative directions at second point ], [0, 1, 2, 5, 6, 3, 4] # reorder indices ) """ if not isinstance(degs, (list,tuple,np.ndarray,)): degs = [degs] dim = len(degs) x = checkParameterList(x, dim)[0] x_un, idx_un = x.unique(return_inverse = True) if len(x_un) < len(x): raise RROMPyException("Sample points must be distinct.") del x_un try: vanderbase = {"CHEBYSHEV" : np.polynomial.chebyshev.chebvander, "LEGENDRE" : np.polynomial.legendre.legvander, "MONOMIAL" : np.polynomial.polynomial.polyvander }[basis.upper()] except: raise RROMPyException("Polynomial basis not recognized.") VanBase = vanderbase(x(0), degs[0]) for j in range(1, dim): VNext = vanderbase(x(j), degs[j]) for jj in range(j): VNext = np.expand_dims(VNext, 1) VanBase = VanBase[..., None] * VNext VanBase = VanBase.reshape((len(x), -1)) if derIdxs is None or VanBase.shape[-1] <= 1: Van = VanBase else: derFlat, idxRep = [], [] for j, derIdx in enumerate(derIdxs): derFlat += derIdx[:] idxRep += [j] * len(derIdx[:]) for j in range(len(derFlat)): if not hasattr(derFlat[j], "__len__"): derFlat[j] = [derFlat[j]] RROMPyAssert(len(derFlat[j]), dim, "Number of dimensions") TDirac = [y.reshape([d + 1 for d in degs]) for y in np.eye(VanBase.shape[-1], dtype = int)] Cs_loc = firstDerTransition(dim, TDirac, basis, scl) Van = np.empty((len(derFlat), VanBase.shape[-1]), dtype = VanBase.dtype) for j in range(len(derFlat)): Van[j, :] = VanBase[idxRep[j], :] for k in range(dim): for der in range(derFlat[j][k]): Van[j, :] = Van[j, :].dot(Cs_loc[k]) / (der + 1) if reorder is not None: Van = Van[reorder, :] return Van def polyvanderTotal(x:paramList, deg:int, basis:str, derIdxs : List[List[List[int]]] = None, reorder : List[int] = None, scl : Np1D = None) -> Np2D: """ Compute full total degree Hermite-Vandermonde matrix with specified derivative directions. """ x = checkParameterList(x)[0] degs = [deg] * x.shape[1] VanBase = polyvander(x, degs, basis, derIdxs, reorder, scl) derIdxs, mask = totalDegreeSet(deg, x.shape[1], return_mask = True) ordIdxs = np.empty(len(derIdxs), dtype = int) derTotal = np.array([np.sum(y) for y in derIdxs]) idxPrev = 0 rangeAux = np.arange(len(derIdxs)) for j in range(deg + 1): idxLocal = rangeAux[derTotal == j][::-1] idxPrev += len(idxLocal) ordIdxs[idxPrev - len(idxLocal) : idxPrev] = idxLocal return VanBase[:, mask][:, ordIdxs] diff --git a/rrompy/utilities/poly_fitting/radial_basis/radial_basis_interpolator.py b/rrompy/utilities/poly_fitting/radial_basis/radial_basis_interpolator.py index 483b218..9b2ba80 100644 --- a/rrompy/utilities/poly_fitting/radial_basis/radial_basis_interpolator.py +++ b/rrompy/utilities/poly_fitting/radial_basis/radial_basis_interpolator.py @@ -1,143 +1,144 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from rrompy.utilities.base.types import (List, ListAny, DictAny, Np1D, Np2D, paramList) from rrompy.utilities.poly_fitting.interpolator import GenericInterpolator from rrompy.utilities.poly_fitting.custom_fit import customFit from .base import polyfitname from .val import polyval from .vander import polyvander as pv, polyvanderTotal as pvT -from rrompy.utilities.numerical import degreeTotalToFull, dot +from rrompy.utilities.numerical import dot +from rrompy.utilities.numerical.degree import degreeTotalToFull from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert from rrompy.parameter import checkParameterList __all__ = ['RadialBasisInterpolator'] class RadialBasisInterpolator(GenericInterpolator): def __init__(self, other = None): if other is None: return self.support = other.support self.coeffsGlobal = other.coeffsGlobal self.coeffsLocal = other.coeffsLocal self.directionalWeights = other.directionalWeights self.npar = other.npar self.polybasis = other.polybasis self.nNearestNeighbor = other.nNearestNeighbor @property def shape(self): sh = self.coeffsLocal.shape[1 :] if self.coeffsLocal.ndim > 1 else 1 return sh @property def deg(self): return [x - 1 for x in self.coeffsGlobal.shape[: self.npar]] def __call__(self, mu:paramList, der : List[int] = None, scl : Np1D = None): if der is not None and np.sum(der) > 0: raise RROMPyException(("Cannot take derivatives of radial basis " "function.")) return polyval(mu, self.coeffsGlobal, self.coeffsLocal, self.support, self.directionalWeights, self.polybasis, self.nNearestNeighbor) def __copy__(self): return RadialBasisInterpolator(self) def __deepcopy__(self, memo): other = RadialBasisInterpolator() (other.support, other.coeffsGlobal, other.coeffsLocal, other.directionalWeights, other.npar, other.polybasis, other.nNearestNeighbor) = copy( (self.support, self.coeffsGlobal, self.coeffsLocal, self.directionalWeights, self.npar, self.polybasis, self.nNearestNeighbor), memo) return other def postmultiplyTensorize(self, A:Np2D): RROMPyAssert(A.shape[0], self.shape[-1], "Shape of output") self.coeffsLocal = dot(self.coeffsLocal, A) self.coeffsGlobal = dot(self.coeffsGlobal, A) def pad(self, nleft : List[int] = None, nright : List[int] = None): if nleft is None: nleft = [0] * len(self.shape) if nright is None: nright = [0] * len(self.shape) if not hasattr(nleft, "__len__"): nleft = [nleft] if not hasattr(nright, "__len__"): nright = [nright] RROMPyAssert(len(self.shape), len(nleft), "Shape of output") RROMPyAssert(len(self.shape), len(nright), "Shape of output") padwidth = [(0, 0)] + [(l, r) for l, r in zip(nleft, nright)] self.coeffsLocal = np.pad(self.coeffsLocal, padwidth, "constant", constant_values = (0., 0.)) padwidth = [(0, 0)] * (self.npar - 1) + padwidth self.coeffsGlobal = np.pad(self.coeffsGlobal, padwidth, "constant", constant_values = (0., 0.)) def setupByInterpolation(self, support:paramList, values:ListAny, deg:int, polybasis : str = "MONOMIAL_GAUSSIAN", directionalWeights : Np1D = None, verbose : bool = True, totalDegree : bool = True, vanderCoeffs : DictAny = {}, fitCoeffs : DictAny = {}): support = checkParameterList(support)[0] self.support = copy(support) if "reorder" in vanderCoeffs.keys(): self.support = self.support[vanderCoeffs["reorder"]] if "nNearestNeighbor" in vanderCoeffs.keys(): self.nNearestNeighbor = vanderCoeffs["nNearestNeighbor"] else: self.nNearestNeighbor = -1 self.npar = support.shape[1] if directionalWeights is None: directionalWeights = np.ones(self.npar) self.directionalWeights = directionalWeights self.polybasis = polybasis if totalDegree: vander = pvT(support, deg, basis = polybasis, directionalWeights = self.directionalWeights, **vanderCoeffs) else: if not hasattr(deg, "__len__"): deg = [deg] * self.npar vander = pv(support, deg, basis = polybasis, directionalWeights = self.directionalWeights, **vanderCoeffs) outDim = values.shape[1:] values = values.reshape(values.shape[0], -1) values = np.pad(values, ((0, len(vander) - len(values)), (0, 0)), "constant") fitOut = customFit(vander, values, full = True, **fitCoeffs) P = fitOut[0][len(support) :] if verbose: msg = ("Fitting {}+{} samples with degree {} through {}... " "Conditioning of LS system: {:.4e}.").format( len(support), len(vander) - len(support), deg, polyfitname(self.polybasis), fitOut[1][2][0] / fitOut[1][2][-1]) else: msg = None self.coeffsLocal = fitOut[0][: len(support)] if totalDegree: self.coeffsGlobal = degreeTotalToFull(tuple([deg + 1] * self.npar) + outDim, self.npar, P) else: self.coeffsGlobal = P.reshape(tuple([d + 1 for d in deg]) + outDim) return fitOut[1][1] == vander.shape[1], msg diff --git a/tests/utilities/basic_routines.py b/tests/utilities/basic_routines.py index 55d620b..86ab5f5 100644 --- a/tests/utilities/basic_routines.py +++ b/tests/utilities/basic_routines.py @@ -1,67 +1,68 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import pytest import numpy as np from rrompy.utilities.exception_manager import RROMPyException, RROMPyWarning -from rrompy.utilities.base import (findDictStrKey, purgeDict, purgeList, - verbosityDepth) +from rrompy.utilities.base.data_structures import (findDictStrKey, purgeDict, + purgeList) +from rrompy.utilities.base import verbosityDepth from rrompy.utilities.numerical import lowDiscrepancy def test_exception(): with pytest.raises(Exception): raise RROMPyException("This is an exception!") def test_warning(capsys): RROMPyWarning("This is a warning.") out, err = capsys.readouterr() assert "This is a warning." in out assert len(err) == 0 def test_dict_list(): dictBase = {str(x): str(x**2) for x in range(10)} assert findDictStrKey("-1", dictBase.keys()) is None assert findDictStrKey("5", dictBase.keys()) == "5" dictPurged = purgeDict(dictBase, [str(x) for x in range(4)], silent = True) assert len(dictPurged.keys()) == 4 listBase = list(dictBase.values()) listPurged = purgeList(listBase, [str(x**2) for x in range(4,6)], silent = True) assert len(listPurged) == 2 def test_vandercorput(): idxs = lowDiscrepancy(8) assert np.all(idxs == np.array([0,4,2,6,1,5,3,7])) def test_verbositydepth(capsys): verbosityDepth("INIT", "Start message", timestamp = False) out, err = capsys.readouterr() assert out == "┌Start message\n" assert len(err) == 0 verbosityDepth("MAIN", "Main message", end = "\n\n", timestamp = False) out, err = capsys.readouterr() assert out == "├Main message\n\n" assert len(err) == 0 verbosityDepth("INIT", "Start message2") verbosityDepth("DEL", "Delete message2") verbosityDepth("DEL", "Delete message") with pytest.raises(Exception): verbosityDepth("DEL", "Wrong deletion") diff --git a/tests/utilities/poly_fitting.py b/tests/utilities/poly_fitting.py index 909452e..9dfb196 100644 --- a/tests/utilities/poly_fitting.py +++ b/tests/utilities/poly_fitting.py @@ -1,116 +1,116 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from rrompy.utilities.poly_fitting import customFit from rrompy.utilities.poly_fitting.polynomial import (polybases, polyfitname, polydomcoeff, polyval, polyroots, polyvander) -from rrompy.utilities.numerical import nextDerivativeIndices +from rrompy.utilities.numerical.hash_derivative import nextDerivativeIndices from rrompy.parameter import checkParameterList def test_chebyshev(): assert "CHEBYSHEV" in polybases fitname = polyfitname("CHEBYSHEV") domcoeff = polydomcoeff(5, "CHEBYSHEV") assert fitname == "chebfit" assert np.isclose(domcoeff, 16, rtol = 1e-5) assert np.allclose(polyroots((-1, 1, -1, 1), "CHEBYSHEV"), np.array([-.5, 0., 1.]), rtol = 1e-5) Phi = polyvander(np.arange(5), 4, "CHEBYSHEV") y = 2. * np.arange(5) cFit = customFit(Phi, y) assert np.allclose(cFit, [0, 2, 0, 0, 0], rtol = 1e-5) assert np.allclose(polyval(np.arange(5), cFit, "CHEBYSHEV"), y, rtol = 1e-5) assert np.allclose(polyval(np.arange(5), cFit, "CHEBYSHEV", m = 1), 2. * np.ones(5), rtol = 1e-5) def test_legendre(): assert "LEGENDRE" in polybases fitname = polyfitname("LEGENDRE") domcoeff = polydomcoeff([0, 5], "LEGENDRE") assert fitname == "legfit" assert np.allclose(domcoeff, [1., 63. / 8], rtol = 1e-5) assert np.allclose(polyroots((1, 2, 3, 4), "LEGENDRE"), np.array([-0.85099543, -0.11407192, 0.51506735]), rtol = 1e-5) Phi = polyvander(np.arange(5), 4, "LEGENDRE") y = 2. * np.arange(5) cFit = customFit(Phi, y) assert np.allclose(cFit, [0, 2, 0, 0, 0], rtol = 1e-5) assert np.allclose(polyval(np.arange(5), cFit, "LEGENDRE"), y, rtol = 1e-5) assert np.allclose(polyval(np.arange(5), cFit, "LEGENDRE", m = 1), 2. * np.ones(5), rtol = 1e-5) def test_monomial(): assert "MONOMIAL" in polybases fitname = polyfitname("MONOMIAL") domcoeff = polydomcoeff(5, "MONOMIAL") assert fitname == "polyfit" assert np.isclose(domcoeff, 1., rtol = 1e-5) assert np.allclose(polyroots([0.+0.j, 1.+0.j, 0.+0.j, 1.+0.j], "MONOMIAL"), np.array([0., 1.j, -1.j]), rtol = 1e-5) Phi = polyvander(np.arange(5), 4, "MONOMIAL") y = 2. * np.arange(5) cFit = customFit(Phi, y) assert np.allclose(cFit, [0, 2, 0, 0, 0], rtol = 1e-5) assert np.allclose(polyval(np.arange(5), cFit, "MONOMIAL"), y, rtol = 1e-5) assert np.allclose(polyval(np.arange(5), cFit, "MONOMIAL", m = 1), 2. * np.ones(5), rtol = 1e-5) def test_cheb_confluence(): x = np.arange(5) x = np.delete(x, 3) Phi = polyvander(x, 4, "CHEBYSHEV", [[0, 1]] + [[0]] * 3, reorder = [0, 2, 3, 1, 4]) y = 2. * np.arange(5) y[3] = 2. cFit = customFit(Phi, y) mask = np.arange(len(y)) != 3 assert np.allclose(cFit, [0, 2, 0, 0, 0], rtol = 1e-5) assert np.allclose(polyval(x, cFit, "CHEBYSHEV"), y[mask], rtol = 1e-5) assert np.allclose(polyval(x[0], cFit, "CHEBYSHEV", m = 1), y[~mask], rtol = 1e-5) def test_mon_confluence_2d(): x, _ = checkParameterList([[0, 0], [1, 1], [-1, -1]]) y = np.array([3., 5., 1., 2., 12., -2.]).reshape((-1, 1)) # 3+y+5x+2xy+x2y Phi = polyvander(x, [2, 1], "MONOMIAL", [[[0, 0], [1, 0], [0, 1], [1, 1]]] + [[[0, 0]]] * 2) cFit = customFit(Phi, y).reshape((3, 2)) mask = np.array([0, 4, 5]) assert np.allclose(cFit.flatten(), [3, 1, 5, 2, 0, 1], atol = 1e-5) assert np.allclose(polyval(x, cFit, "MONOMIAL").flatten(), y[mask].flatten(), rtol = 1e-5) assert np.allclose(polyval([x[0]], cFit, "MONOMIAL", m = [1, 0]), y[1], rtol = 1e-5) assert np.allclose(polyval([x[0]], cFit, "MONOMIAL", m = [0, 1]), y[2], rtol = 1e-5) assert np.allclose(polyval([x[0]], cFit, "MONOMIAL", m = [1, 1]), y[3], rtol = 1e-5) def test_derivative_indices_4d(): idxs = nextDerivativeIndices([], 4, 70) idxMag = [np.sum(idx) for idx in idxs] idxMagUnique, idxMagCount = np.unique(idxMag, return_counts = True) idxMagCount = np.cumsum(idxMagCount) assert np.allclose(idxMagUnique, np.arange(5), atol = 1e-10) assert np.allclose(idxMagCount, [1, 5, 15, 35, 70], atol = 1e-10) diff --git a/tests/utilities/radial_fitting.py b/tests/utilities/radial_fitting.py index ec346c8..4dedfaf 100644 --- a/tests/utilities/radial_fitting.py +++ b/tests/utilities/radial_fitting.py @@ -1,165 +1,165 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from rrompy.utilities.poly_fitting import customFit from rrompy.utilities.poly_fitting.radial_basis import (radialGaussian, thinPlateSpline, multiQuadric, polybases, polyfitname, polydomcoeff, polyval, polyvander, polyvanderTotal) -from rrompy.utilities.numerical import degreeTotalToFull +from rrompy.utilities.numerical.degree import degreeTotalToFull from rrompy.parameter import checkParameterList def test_monomial_gaussian(): polyrbname = "MONOMIAL_GAUSSIAN" assert polyrbname in polybases fitname = polyfitname(polyrbname) domcoeff = polydomcoeff(5, polyrbname) assert fitname == "polyfit_gaussian" assert np.isclose(domcoeff, 1., rtol = 1e-5) directionalWeights = np.array([5.]) xSupp = checkParameterList(np.arange(-1, 3), 1)[0] cRBCoeffs = np.array([-1., 3., -3., 1., 1., 2., -.5]) globalCoeffs = cRBCoeffs[4 :] localCoeffs = cRBCoeffs[: 4] ySupp = 1 + 2. * xSupp.data - .5 * xSupp.data ** 2. xx = np.linspace(-2., 3., 100) yy = polyval(checkParameterList(xx, 1)[0], globalCoeffs, localCoeffs, xSupp, directionalWeights, polyrbname) yyman = 1. + 2. * xx - .5 * xx ** 2. for j, xc in enumerate(np.arange(-1, 3)): r2j = (5. * (xx - xc)) ** 2. rbj = radialGaussian(r2j) assert np.allclose(rbj, np.exp(-.5 * r2j)) yyman += localCoeffs[j] * rbj ySupp += localCoeffs[j] * radialGaussian((directionalWeights[0] * (xSupp.data - xc)) ** 2.) assert np.allclose(yy, yyman, atol = 1e-5) VanT = polyvander(xSupp, [2], polyrbname, directionalWeights = directionalWeights) ySupp = np.pad(ySupp.flatten(), (0, len(VanT) - len(xSupp)), "constant") out = customFit(VanT, ySupp) assert np.allclose(out, cRBCoeffs, atol = 1e-8) def test_legendre_thinplate(): polyrbname = "LEGENDRE_THINPLATE" assert polyrbname in polybases fitname = polyfitname(polyrbname) domcoeff = polydomcoeff(5, polyrbname) assert fitname == "legfit_thinplate" assert np.isclose(domcoeff, 63. / 8, rtol = 1e-5) directionalWeights = np.array([.5]) xSupp = checkParameterList(np.arange(-1, 3), 1)[0] cRBCoeffs = np.array([-1., 3., -3., 1., 1., 2., -.5]) localCoeffs = cRBCoeffs[: 4] globalCoeffs = cRBCoeffs[4 :] ySupp = 1 + 2. * xSupp.data - .5 * (.5 * (3. * xSupp.data ** 2. - 1.)) xx = np.linspace(-2., 3., 100) yy = polyval(checkParameterList(xx, 1)[0], globalCoeffs, localCoeffs, xSupp, directionalWeights, polyrbname) yyman = 1. + 2. * xx - .5 * (.5 * (3. * xx ** 2. - 1.)) for j, xc in enumerate(np.arange(-1, 3)): r2j = (directionalWeights[0] * (xx - xc)) ** 2. rbj = thinPlateSpline(r2j) assert np.allclose(rbj, .5 * r2j * np.log(np.finfo(float).eps + r2j)) yyman += localCoeffs[j] * rbj ySupp += localCoeffs[j] * thinPlateSpline((directionalWeights[0] * (xSupp.data - xc)) ** 2.) assert np.allclose(yy, yyman, atol = 1e-5) VanT = polyvander(xSupp, [2], polyrbname, directionalWeights = directionalWeights) ySupp = np.pad(ySupp.flatten(), (0, len(VanT) - len(xSupp)), "constant") out = customFit(VanT, ySupp) assert np.allclose(out, cRBCoeffs, atol = 1e-8) def test_chebyshev_multiquadric(): polyrbname = "CHEBYSHEV_MULTIQUADRIC" assert polyrbname in polybases fitname = polyfitname(polyrbname) domcoeff = polydomcoeff(5, polyrbname) assert fitname == "chebfit_multiquadric" assert np.isclose(domcoeff, 16, rtol = 1e-5) directionalWeights = np.array([1.]) xSupp = checkParameterList(np.arange(-1, 3), 1)[0] cRBCoeffs = np.array([-1., 3., -3., 1., 1., 2., -.5]) localCoeffs = cRBCoeffs[: 4] globalCoeffs = cRBCoeffs[4 :] ySupp = 1 + 2. * xSupp.data - .5 * (2. * xSupp.data ** 2. - 1.) xx = np.linspace(-2., 3., 100) yy = polyval(checkParameterList(xx, 1)[0], globalCoeffs, localCoeffs, xSupp, directionalWeights, polyrbname) yyman = 1. + 2. * xx - .5 * (2. * xx ** 2. - 1.) for j, xc in enumerate(np.arange(-1, 3)): r2j = (directionalWeights[0] * (xx - xc)) ** 2. rbj = multiQuadric(r2j) assert np.allclose(rbj, np.power(r2j + 1, -.5)) yyman += localCoeffs[j] * rbj ySupp += localCoeffs[j] * multiQuadric((directionalWeights[0] * (xSupp.data - xc)) ** 2.) assert np.allclose(yy, yyman, atol = 1e-5) VanT = polyvander(xSupp, [2], polyrbname, directionalWeights = directionalWeights) ySupp = np.pad(ySupp.flatten(), (0, len(VanT) - len(xSupp)), "constant") out = customFit(VanT, ySupp) assert np.allclose(out, cRBCoeffs, atol = 1e-8) def test_total_degree_2d(): values = lambda x, y: (x - 3.) ** 2. * y - (x + 1.) * y ** 2. polyrbname = "CHEBYSHEV_GAUSSIAN" xs, ys = np.meshgrid(np.linspace(0., 4., 5), np.linspace(0., 4., 4)) xySupp = np.concatenate((xs.reshape(-1, 1), ys.reshape(-1, 1)), axis = 1) zs = values(xs, ys) zSupp = zs.flatten() deg = 3 directionalWeights = [2., 1.] VanT = polyvanderTotal(xySupp, deg, polyrbname, directionalWeights = directionalWeights) cFit = np.linalg.solve(VanT, np.pad(zSupp, (0, len(VanT) - len(zSupp)), 'constant')) globCoeff = degreeTotalToFull([deg + 1] * 2, 2, cFit[len(zSupp) :]) localCoeffs = cFit[: len(zSupp)] globalCoeffs = globCoeff xx, yy = np.meshgrid(np.linspace(0., 4., 100), np.linspace(0., 4., 100)) xxyy = np.concatenate((xx.reshape(-1, 1), yy.reshape(-1, 1)), axis = 1) zz = polyval(xxyy, globalCoeffs, localCoeffs, xySupp, directionalWeights, polyrbname).reshape(xx.shape) zzex = values(xx, yy) error = np.abs(zz - zzex) print(np.max(error)) assert np.max(error) < 1e-10