diff --git a/VERSION b/VERSION index c20c8ac..e6b7b62 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.6 \ No newline at end of file +2.7 \ No newline at end of file diff --git a/examples/1_symmetric_disk/symmetric_disk.py b/examples/1_symmetric_disk/symmetric_disk.py index d0d9eb8..236972b 100644 --- a/examples/1_symmetric_disk/symmetric_disk.py +++ b/examples/1_symmetric_disk/symmetric_disk.py @@ -1,86 +1,89 @@ import numpy as np from symmetric_disk_engine import SymmetricDiskEngine as engine from rrompy.reduction_methods import ( NearestNeighbor as NN, RationalInterpolant as RI, ReducedBasis as RB, RationalInterpolantGreedy as RIG, ReducedBasisGreedy as RBG) +from rrompy.parameter import parameterMap as pMap from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, EmptySampler as ES) ks = [10., 20.] k0, n = np.mean(np.power(ks, 2.)) ** .5, 150 solver = engine(k0, n) k = 12. for method in ["RI", "RB", "RI_GREEDY", "RB_GREEDY"]: print("Testing {} method".format(method)) if method == "RI": params = {'S':40, 'POD':True, 'polybasis':"CHEBYSHEV", - 'sampler':QS(ks, "CHEBYSHEV", scalingExp = 2.)} + 'sampler':QS(ks, "CHEBYSHEV", parameterMap = pMap(2.))} algo = RI if method == "RB": params = {'S':40, 'POD':True, - 'sampler':QS(ks, "CHEBYSHEV", scalingExp = 2.)} + 'sampler':QS(ks, "CHEBYSHEV", parameterMap = pMap(2.))} algo = RB if method == "RI_GREEDY": params = {'S':10, 'POD':True, 'polybasis':"LEGENDRE", 'greedyTol':1e-2, - 'sampler':QS(ks, "UNIFORM", scalingExp = 2.), + 'sampler':QS(ks, "UNIFORM", parameterMap = pMap(2.)), 'errorEstimatorKind':"DISCREPANCY", - 'trainSetGenerator':QS(ks, "CHEBYSHEV", scalingExp = 2.)} + 'trainSetGenerator':QS(ks, "CHEBYSHEV", + parameterMap = pMap(2.))} algo = RIG if method == "RB_GREEDY": params = {'S':10, 'POD':True, 'greedyTol':1e-2, - 'sampler':QS(ks, "UNIFORM", scalingExp = 2.), - 'trainSetGenerator':QS(ks, "CHEBYSHEV", scalingExp = 2.)} + 'sampler':QS(ks, "UNIFORM", parameterMap = pMap(2.)), + 'trainSetGenerator':QS(ks, "CHEBYSHEV", + parameterMap = pMap(2.))} algo = RBG approx = algo(solver, mu0 = k0, approx_state = True, approxParameters = params, verbosity = 20) if len(method) == 2: approx.setupApprox() else: approx.setupApprox("LAST") print("--- Approximant ---") approx.plotApprox(k, name = 'u_app') approx.plotHF(k, name = 'u_HF') approx.plotErr(k, name = 'err_app') approx.plotRes(k, name = 'res_app') normErr = approx.normErr(k)[0] normSol = approx.normHF(k)[0] normRes = approx.normRes(k)[0] normRHS = approx.normRHS(k)[0] print("SolNorm:\t{:.5e}\nErr_app: \t{:.5e}\nErrRel_app:\t{:.5e}".format( normSol, normErr, normErr / normSol)) print("RHSNorm:\t{:.5e}\nRes_app: \t{:.5e}\nResRel_app:\t{:.5e}".format( normRHS, normRes, normRes / normRHS)) print("--- Closest snapshot ---") approxNN = NN(solver, mu0 = k0, approx_state = True, verbosity = 0, approxParameters = {'S':approx.S, 'POD':True, 'sampler':ES()}) approxNN.setSamples(approx.samplingEngine) approxNN.plotApprox(k, name = 'u_close') approxNN.plotHF(k, name = 'u_HF') approxNN.plotErr(k, name = 'err_close') approxNN.plotRes(k, name = 'res_close') normErr = approxNN.normErr(k)[0] normSol = approxNN.normHF(k)[0] normRes = approxNN.normRes(k)[0] normRHS = approxNN.normRHS(k)[0] print("SolNorm:\t{:.5e}\nErr_close:\t{:.5e}\nErrRel_close:\t{:.5e}".format( normSol, normErr, normErr / normSol)) print("RHSNorm:\t{:.5e}\nRes_close:\t{:.5e}\nResRel_close:\t{:.5e}".format( normRHS, normRes, normRes / normRHS)) if method[:2] == "RI": poles, residues = approx.getResidues() if method[:2] == "RB": poles = approx.getPoles() print("Poles:\n{}".format(poles)) if method[:2] == "RI": for pol, res in zip(poles, residues): solver.plot(res) print("pole = {:.5e}".format(pol)) print("\n") diff --git a/examples/1_symmetric_disk/symmetric_disk_engine.py b/examples/1_symmetric_disk/symmetric_disk_engine.py index 468ba01..be459e4 100644 --- a/examples/1_symmetric_disk/symmetric_disk_engine.py +++ b/examples/1_symmetric_disk/symmetric_disk_engine.py @@ -1,12 +1,12 @@ import fenics as fen import mshr -from rrompy.hfengines.linear_problem import HelmholtzProblemEngine +from rrompy.hfengines.fenics_engines import HelmholtzProblemEngine class SymmetricDiskEngine(HelmholtzProblemEngine): def __init__(self, k0:float, n:int): super().__init__(mu0 = [k0]) mesh = mshr.generate_mesh(mshr.Circle(fen.Point(0., 0.), 1.), n) self.V = fen.FunctionSpace(mesh, "P", 1) x, y = fen.SpatialCoordinate(self.V.mesh())[:] self.forcingTerm = [fen.exp(x + y) * (1. - x ** 2. - y ** 2.), fen.exp(x - y) * (1. - x ** 2. - y ** 2.)] diff --git a/examples/2_double_slit/double_slit_engine.py b/examples/2_double_slit/double_slit_engine.py index 5c78130..4c4b5ab 100644 --- a/examples/2_double_slit/double_slit_engine.py +++ b/examples/2_double_slit/double_slit_engine.py @@ -1,57 +1,57 @@ import numpy as np import ufl import fenics as fen import mshr from rrompy.utilities.base.decorators import (affine_construct, nonaffine_construct) -from rrompy.hfengines.linear_problem import ScatteringProblemEngine +from rrompy.hfengines.fenics_engines import ScatteringProblemEngine 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.py b/examples/3_sector_angle/sector_angle.py index 359c4cb..7d8f2c4 100644 --- a/examples/3_sector_angle/sector_angle.py +++ b/examples/3_sector_angle/sector_angle.py @@ -1,109 +1,110 @@ import numpy as np import matplotlib.pyplot as plt from sector_angle_engine import SectorAngleEngine as engine from rrompy.reduction_methods import (NearestNeighbor as NN, RationalInterpolantPivoted as RIP, RationalInterpolantGreedyPivoted as RIGP) from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, EmptySampler as ES) ks, ts = [10., 15.], [.4, .6] k0, t0, n = np.mean(np.power(ks, 2.)) ** .5, np.mean(ts), 50 solver = engine(k0, t0, n) murange = [[ks[0], ts[0]], [ks[-1], ts[-1]]] mu = [12., .535] fighandles = [] for method in ["RI", "RI_GREEDY"]: print("Testing {} method".format(method)) if method == "RI": params = {'S':20, "paramsMarginal":{"MMarginal": 3}, 'SMarginal':11, 'POD':True, 'polybasis':"CHEBYSHEV", 'polybasisMarginal':"MONOMIAL_GAUSSIAN", 'radialDirectionalWeightsMarginal': 100., 'matchingWeight':1., 'cutOffTolerance': 2., 'samplerPivot':QS(ks, "CHEBYSHEV", 2.), 'samplerMarginal':QS(ts, "UNIFORM")} algo = RIP if method == "RI_GREEDY": params = {'S':10, "paramsMarginal":{"MMarginal": 3}, 'SMarginal':11, 'POD':True, 'polybasis':"LEGENDRE", 'polybasisMarginal':"MONOMIAL_GAUSSIAN", 'radialDirectionalWeightsMarginal': 100., 'matchingWeight':1., 'cutOffTolerance': 2., 'samplerPivot':QS(ks, "UNIFORM", 2.), 'greedyTol':1e-3, 'errorEstimatorKind':"LOOK_AHEAD_RES", 'trainSetGenerator':QS(ks, "CHEBYSHEV", 2.), 'samplerMarginal':QS(ts, "UNIFORM")} algo = RIGP approx = algo([0], solver, mu0 = [k0, t0], approx_state = True, approxParameters = params, verbosity = 10, storeAllSamples = True) if len(method) == 2: approx.setupApprox() else: approx.setupApprox("LAST") print("--- Approximant ---") approx.plotApprox(mu, name = 'u_app') approx.plotHF(mu, name = 'u_HF') approx.plotErr(mu, name = 'err_app') approx.plotRes(mu, name = 'res_app') normErr = approx.normErr(mu)[0] normSol = approx.normHF(mu)[0] normRes = approx.normRes(mu)[0] normRHS = approx.normRHS(mu)[0] print("SolNorm:\t{:.5e}\nErr_app: \t{:.5e}\nErrRel_app:\t{:.5e}".format( normSol, normErr, normErr / normSol)) print("RHSNorm:\t{:.5e}\nRes_app: \t{:.5e}\nResRel_app:\t{:.5e}".format( normRHS, normRes, normRes / normRHS)) print("--- Closest snapshot ---") paramsNN = {'S':len(approx.mus), 'POD':True, 'sampler':ES()} approxNN = NN(solver, mu0 = [k0, t0], approx_state = True, approxParameters = paramsNN, verbosity = 0) approxNN.setSamples(approx.storedSamplesFilenames) + approx.purgeStoredSamples() approxNN.plotApprox(mu, name = 'u_close') approxNN.plotHF(mu, name = 'u_HF') approxNN.plotErr(mu, name = 'err_close') approxNN.plotRes(mu, name = 'res_close') normErr = approxNN.normErr(mu)[0] normSol = approxNN.normHF(mu)[0] normRes = approxNN.normRes(mu)[0] normRHS = approxNN.normRHS(mu)[0] print("SolNorm:\t{:.5e}\nErr_close:\t{:.5e}\nErrRel_close:\t{:.5e}".format( normSol, normErr, normErr / normSol)) print("RHSNorm:\t{:.5e}\nRes_close:\t{:.5e}\nResRel_close:\t{:.5e}".format( normRHS, normRes, normRes / normRHS)) verb = approx.verbosity approx.verbosity = 0 tspace = np.linspace(ts[0], ts[-1], 100) for j, t in enumerate(tspace): pls = approx.getPoles([None, t]) pls[np.abs(np.imag(pls ** 2.)) > 1e-5] = np.nan if j == 0: poles = np.empty((len(tspace), len(pls))) poles[j] = np.real(pls) approx.verbosity = verb fighandles += [plt.figure(figsize = (12, 5))] ax1 = fighandles[-1].add_subplot(1, 2, 1) ax2 = fighandles[-1].add_subplot(1, 2, 2) ax1.plot(poles, tspace) ax1.set_ylim(ts) ax1.set_xlabel('mu_1') ax1.set_ylabel('mu_2') ax1.grid() ax2.plot(poles, tspace) for mm in approx.musMarginal: ax2.plot(ks, [mm[0, 0]] * 2, 'k--', linewidth = 1) ax2.set_xlim(ks) ax2.set_ylim(ts) ax2.set_xlabel('mu_1') ax2.set_ylabel('mu_2') ax2.grid() plt.show() print("\n") diff --git a/examples/3_sector_angle/sector_angle_engine.py b/examples/3_sector_angle/sector_angle_engine.py index 1d6f218..cd767dc 100644 --- a/examples/3_sector_angle/sector_angle_engine.py +++ b/examples/3_sector_angle/sector_angle_engine.py @@ -1,43 +1,44 @@ import numpy as np import fenics as fen import mshr from rrompy.utilities.base.decorators import nonaffine_construct -from rrompy.hfengines.linear_problem import HelmholtzProblemEngine +from rrompy.hfengines.fenics_engines import HelmholtzProblemEngine +from rrompy.parameter import parameterMap as pMap 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.] + self.parameterMap = pMap([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(self.V.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/examples/4_funnel_output/funnel_output_engine.py b/examples/4_funnel_output/funnel_output_engine.py index 3a87687..a9b5e89 100644 --- a/examples/4_funnel_output/funnel_output_engine.py +++ b/examples/4_funnel_output/funnel_output_engine.py @@ -1,35 +1,35 @@ import numpy as np import fenics as fen import mshr -from rrompy.hfengines.linear_problem import ScatteringProblemEngine +from rrompy.hfengines.fenics_engines import ScatteringProblemEngine from rrompy.solver.fenics import fenics2Sparse class FunnelOutputEngine(ScatteringProblemEngine): def __init__(self, k0:float, n:int): super().__init__(mu0 = [k0]) mesh = mshr.generate_mesh(mshr.Circle(fen.Point(0., 0.), 5.) - mshr.Rectangle(fen.Point(-5., -5.), fen.Point(-1., 5.)) - mshr.Rectangle(fen.Point(-1., -5.), fen.Point(0., -1.)) - mshr.Rectangle(fen.Point(-1., 1.), fen.Point(0., 5.)), n) self.V = fen.FunctionSpace(mesh, "P", 1) eps = 1e-4 self.DirichletBoundary = (lambda x, on_boundary: on_boundary and x[0] < -1. + eps) self.NeumannBoundary = (lambda x, on_boundary: on_boundary and x[0] >= -1. + eps and x[0] < eps) self.RobinBoundary = "REST" y = fen.SpatialCoordinate(self.V.mesh())[1] self.DirichletDatum = 1. + .25 * fen.sin(.5 * np.pi * y) self.autoSetDS() l2R0 = fen.inner(self.u, self.v) * self.ds(1) L2R0 = fenics2Sparse(l2R0, {}, None, -1) bcR = np.where(np.abs(L2R0.dot(np.ones(L2R0.shape[1]))) > 1e-10)[0] bcR = bcR[np.argsort(self.V.tabulate_dof_coordinates()[bcR, 1])] self._C = np.zeros((len(bcR), L2R0.shape[1])) self._C[np.arange(len(bcR)), bcR] = 1. self.outputNormMatrix = L2R0[bcR][:, bcR] diff --git a/examples/5_anisotropic_square/anisotropic_square.py b/examples/5_anisotropic_square/anisotropic_square.py index 4aa4a83..2738c6c 100644 --- a/examples/5_anisotropic_square/anisotropic_square.py +++ b/examples/5_anisotropic_square/anisotropic_square.py @@ -1,110 +1,77 @@ import numpy as np import matplotlib.pyplot as plt from itertools import product from anisotropic_square_engine import (AnisotropicSquareEngine as engine, AnisotropicSquareEnginePoles as plsEx) -from rrompy.reduction_methods import (NearestNeighbor as NN, +from rrompy.reduction_methods import ( RationalInterpolantGreedyPivotedGreedy as RIGPG) from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, - SparseGridSampler as SGS, - EmptySampler as ES) + SparseGridSampler as SGS) zs, Ls = [10., 50.], [.2, 1.2] z0, L0, n = np.mean(zs), np.mean(Ls), 50 murange = [[zs[0], Ls[0]], [zs[-1], Ls[-1]]] np.random.seed(4020) mu = [zs[0] + np.random.rand() * (zs[-1] - zs[0]), Ls[0] + np.random.rand() * (Ls[-1] - Ls[0])] solver = engine(z0, L0, n) fighandles = [] params = {"POD": True, "nTestPoints": 100, "greedyTol": 1e-4, "S": 3, "polybasisMarginal": "MONOMIAL_WENDLAND", "polybasis": "LEGENDRE", 'samplerPivot':QS(zs, "UNIFORM"), 'trainSetGenerator':QS(zs, "UNIFORM"), 'errorEstimatorKind':"LOOK_AHEAD_RES", - "paramsMarginal":{"MMarginal": 2}, "SMarginal": 3, + 'errorEstimatorKindMarginal':"LOOK_AHEAD_RECOVER", + "SMarginal": 3, "paramsMarginal": {"MMarginal": 2, + "radialDirectionalWeightsMarginalAdapt": [1e9, 1e12]}, "greedyTolMarginal": 1e-2, "samplerMarginal":SGS(Ls), "radialDirectionalWeightsMarginal": [4.], "matchingWeight": 1.} -for tol, shared in product([1., 3.], [1., 0.]): +for shared, tol in product([1., 0.], [1., 3.]): print("Testing cutoff tolerance {} with shared ratio {}.".format(tol, shared)) params['cutOffTolerance'] = tol - params['cutOffSharedRatio'] = shared + params['sharedRatio'] = shared approx = RIGPG([0], solver, mu0 = [z0, L0], approx_state = True, - approxParameters = params, verbosity = 5, - storeAllSamples = True) - approx.setupApprox("LAST") - - print("--- Approximant ---") - approx.plotApprox(mu, name = 'u_app') - approx.plotHF(mu, name = 'u_HF') - approx.plotErr(mu, name = 'err_app') - approx.plotRes(mu, name = 'res_app') - normErr = approx.normErr(mu)[0] - normSol = approx.normHF(mu)[0] - normRes = approx.normRes(mu)[0] - normRHS = approx.normRHS(mu)[0] - print("SolNorm:\t{:.5e}\nErr_app: \t{:.5e}\nErrRel_app:\t{:.5e}".format( - normSol, normErr, normErr / normSol)) - print("RHSNorm:\t{:.5e}\nRes_app: \t{:.5e}\nResRel_app:\t{:.5e}".format( - normRHS, normRes, normRes / normRHS)) - - print("--- Closest snapshot ---") - paramsNN = {'S':len(approx.mus), 'POD':False, 'sampler':ES()} - approxNN = NN(solver, mu0 = [z0, L0], approx_state = True, - approxParameters = paramsNN, verbosity = 0) - approxNN.setSamples(approx.storedSamplesFilenames) - approxNN.plotApprox(mu, name = 'u_close') - approxNN.plotHF(mu, name = 'u_HF') - approxNN.plotErr(mu, name = 'err_close') - approxNN.plotRes(mu, name = 'res_close') - normErr = approxNN.normErr(mu)[0] - normSol = approxNN.normHF(mu)[0] - normRes = approxNN.normRes(mu)[0] - normRHS = approxNN.normRHS(mu)[0] - print("SolNorm:\t{:.5e}\nErr_close:\t{:.5e}\nErrRel_close:\t{:.5e}".format( - normSol, normErr, normErr / normSol)) - print("RHSNorm:\t{:.5e}\nRes_close:\t{:.5e}\nResRel_close:\t{:.5e}".format( - normRHS, normRes, normRes / normRHS)) - + approxParameters = params, verbosity = 5) + approx.setupApprox("ALL") verb = approx.verbosity approx.verbosity = 0 tspace = np.linspace(Ls[0], Ls[-1], 100) for j, t in enumerate(tspace): plsE = plsEx(t, 0., zs[-1]) pls = approx.getPoles([None, t]) pls[np.abs(np.imag(pls)) > 1e-5] = np.nan if j == 0: polesE = np.empty((len(tspace), len(plsE))) poles = np.empty((len(tspace), len(pls))) polesE[:] = np.nan if len(plsE) > polesE.shape[1]: nanR = np.empty((len(tspace), len(plsE) - polesE.shape[1])) nanR[:] = np.nan polesE = np.hstack((polesE, nanR)) polesE[j, : len(plsE)] = np.real(plsE) poles[j] = np.real(pls) approx.verbosity = verb fighandles += [plt.figure(figsize = (17, 5))] ax1 = fighandles[-1].add_subplot(1, 2, 1) ax2 = fighandles[-1].add_subplot(1, 2, 2) ax1.plot(poles, tspace) ax1.set_ylim(Ls) ax1.set_xlabel('mu_1') ax1.set_ylabel('mu_2') ax1.grid() ax2.plot(polesE, tspace, 'k-.', linewidth = 1) ax2.plot(poles, tspace) for mm in approx.musMarginal: ax2.plot(zs, [mm[0, 0]] * 2, 'k--', linewidth = 1) ax2.set_xlim(zs) ax2.set_ylim(Ls) ax2.set_xlabel('mu_1') ax2.set_ylabel('mu_2') ax2.grid() plt.show() print("\n") diff --git a/examples/5_anisotropic_square/anisotropic_square_engine.py b/examples/5_anisotropic_square/anisotropic_square_engine.py index 06d46cf..dfee5f9 100644 --- a/examples/5_anisotropic_square/anisotropic_square_engine.py +++ b/examples/5_anisotropic_square/anisotropic_square_engine.py @@ -1,64 +1,64 @@ import numpy as np import fenics as fen -import mshr import ufl -from rrompy.hfengines.linear_problem import HelmholtzProblemEngine +from rrompy.hfengines.fenics_engines import HelmholtzProblemEngine from rrompy.solver.fenics import fenONE, fenZERO, fenics2Sparse +from rrompy.parameter import parameterMap as pMap class AnisotropicSquareEngine(HelmholtzProblemEngine): def __init__(self, k2:float, L2:float, n:int): super().__init__(mu0 = [k2, L2]) self._affinePoly = True self.nAs = 3 self.npar = 2 - self.rescalingExp = [1., 1.] + self.parameterMap = pMap(1., 2) self.V = fen.FunctionSpace(fen.UnitSquareMesh(n, n), "P", 1) eps = 1e-6 self.DirichletBoundary = lambda x, on_boundary: (on_boundary and x[1] < eps) self.NeumannBoundary = "REST" x, y = fen.SpatialCoordinate(self.V.mesh())[:] self.NeumannDatum = ufl.conditional(ufl.ge(y, 1. - eps), fen.cos(np.pi * x), fenZERO) self.forcingTerm = ufl.conditional(ufl.ge(y, .5), fenONE, fenZERO) * ( 5 * ufl.conditional(ufl.lt(x, .1), fenONE, fenZERO) - 5 * ufl.conditional(ufl.And(ufl.gt(x, .2), ufl.lt(x, .3)), fenONE, fenZERO) + 10 * ufl.conditional(ufl.And(ufl.gt(x, .45), ufl.lt(x, .55)), fenONE, fenZERO) - 5 * ufl.conditional(ufl.And(ufl.gt(x, .7), ufl.lt(x, .8)), fenONE, fenZERO) + 5 * ufl.conditional(ufl.gt(x, .9), fenONE, fenZERO)) 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.autoSetDS() DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a0 = fen.dot(self.u.dx(0), self.v.dx(0)) * fen.dx self.As[0] = fenics2Sparse(a0, {}, DirichletBC0, 1) if self.As[1] is None: self.autoSetDS() DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a1 = - fen.dot(self.u, self.v) * fen.dx self.As[1] = fenics2Sparse(a1, {}, DirichletBC0, 0) if self.As[2] is None: self.autoSetDS() DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a2 = fen.dot(self.u.dx(1), self.v.dx(1)) * fen.dx self.As[2] = fenics2Sparse(a2, {}, DirichletBC0, 0) def AnisotropicSquareEnginePoles(L2:float, k2min:float, k2max:float): poles = [] for alpha in np.arange(np.ceil((k2max) ** .5 / np.pi)): p = (np.pi * alpha) ** 2. pkmin = np.ceil(max(0., (k2min - p) * 4 / L2) ** .5 / np.pi) pkmin += 1 - (pkmin % 2) pkmax = np.floor(max(0., (k2max - p) * 4 / L2) ** .5 / np.pi) for beta in np.arange(pkmin, pkmax + 1, 2): poles += [p + L2 * (np.pi * beta / 2.) ** 2.] return np.unique(poles) diff --git a/examples/5_anisotropic_square/anisotropic_square.py b/examples/5_anisotropic_square/anisotropic_square_test_cutoff.py old mode 100644 new mode 100755 similarity index 51% copy from examples/5_anisotropic_square/anisotropic_square.py copy to examples/5_anisotropic_square/anisotropic_square_test_cutoff.py index 4aa4a83..1786c06 --- a/examples/5_anisotropic_square/anisotropic_square.py +++ b/examples/5_anisotropic_square/anisotropic_square_test_cutoff.py @@ -1,110 +1,78 @@ import numpy as np import matplotlib.pyplot as plt from itertools import product from anisotropic_square_engine import (AnisotropicSquareEngine as engine, AnisotropicSquareEnginePoles as plsEx) -from rrompy.reduction_methods import (NearestNeighbor as NN, +from rrompy.reduction_methods import ( RationalInterpolantGreedyPivotedGreedy as RIGPG) from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, - SparseGridSampler as SGS, - EmptySampler as ES) + SparseGridSampler as SGS) zs, Ls = [10., 50.], [.2, 1.2] z0, L0, n = np.mean(zs), np.mean(Ls), 50 murange = [[zs[0], Ls[0]], [zs[-1], Ls[-1]]] np.random.seed(4020) mu = [zs[0] + np.random.rand() * (zs[-1] - zs[0]), Ls[0] + np.random.rand() * (Ls[-1] - Ls[0])] solver = engine(z0, L0, n) fighandles = [] params = {"POD": True, "nTestPoints": 100, "greedyTol": 1e-4, "S": 3, - "polybasisMarginal": "MONOMIAL_WENDLAND", "polybasis": "LEGENDRE", 'samplerPivot':QS(zs, "UNIFORM"), - 'trainSetGenerator':QS(zs, "UNIFORM"), + 'sharedRatio': 0., "maxIterMarginal":20, + 'cutOffToleranceError': 1., 'trainSetGenerator':QS(zs, "UNIFORM"), 'errorEstimatorKind':"LOOK_AHEAD_RES", - "paramsMarginal":{"MMarginal": 2}, "SMarginal": 3, + 'errorEstimatorKindMarginal':"LOOK_AHEAD_RECOVER", + "SMarginal": 3, "paramsMarginal": {"MMarginal": 2, + "radialDirectionalWeightsMarginalAdapt": [1e9, 1e12]}, "greedyTolMarginal": 1e-2, "samplerMarginal":SGS(Ls), "radialDirectionalWeightsMarginal": [4.], "matchingWeight": 1.} -for tol, shared in product([1., 3.], [1., 0.]): - print("Testing cutoff tolerance {} with shared ratio {}.".format(tol, - shared)) - params['cutOffTolerance'] = tol - params['cutOffSharedRatio'] = shared +for cutOffTolerance, polybasisMarginal in product([1., np.inf], + ["MONOMIAL_WENDLAND", "PIECEWISE_LINEAR_UNIFORM"]): + print("Testing cutoff tolerance {} with marginal basis {}.".format( + cutOffTolerance, polybasisMarginal)) + params['cutOffTolerance'] = cutOffTolerance + params["polybasisMarginal"] = polybasisMarginal approx = RIGPG([0], solver, mu0 = [z0, L0], approx_state = True, - approxParameters = params, verbosity = 5, - storeAllSamples = True) - approx.setupApprox("LAST") - - print("--- Approximant ---") - approx.plotApprox(mu, name = 'u_app') - approx.plotHF(mu, name = 'u_HF') - approx.plotErr(mu, name = 'err_app') - approx.plotRes(mu, name = 'res_app') - normErr = approx.normErr(mu)[0] - normSol = approx.normHF(mu)[0] - normRes = approx.normRes(mu)[0] - normRHS = approx.normRHS(mu)[0] - print("SolNorm:\t{:.5e}\nErr_app: \t{:.5e}\nErrRel_app:\t{:.5e}".format( - normSol, normErr, normErr / normSol)) - print("RHSNorm:\t{:.5e}\nRes_app: \t{:.5e}\nResRel_app:\t{:.5e}".format( - normRHS, normRes, normRes / normRHS)) - - print("--- Closest snapshot ---") - paramsNN = {'S':len(approx.mus), 'POD':False, 'sampler':ES()} - approxNN = NN(solver, mu0 = [z0, L0], approx_state = True, - approxParameters = paramsNN, verbosity = 0) - approxNN.setSamples(approx.storedSamplesFilenames) - approxNN.plotApprox(mu, name = 'u_close') - approxNN.plotHF(mu, name = 'u_HF') - approxNN.plotErr(mu, name = 'err_close') - approxNN.plotRes(mu, name = 'res_close') - normErr = approxNN.normErr(mu)[0] - normSol = approxNN.normHF(mu)[0] - normRes = approxNN.normRes(mu)[0] - normRHS = approxNN.normRHS(mu)[0] - print("SolNorm:\t{:.5e}\nErr_close:\t{:.5e}\nErrRel_close:\t{:.5e}".format( - normSol, normErr, normErr / normSol)) - print("RHSNorm:\t{:.5e}\nRes_close:\t{:.5e}\nResRel_close:\t{:.5e}".format( - normRHS, normRes, normRes / normRHS)) - + approxParameters = params, verbosity = 5) + approx.setupApprox("ALL") verb = approx.verbosity approx.verbosity = 0 tspace = np.linspace(Ls[0], Ls[-1], 100) for j, t in enumerate(tspace): plsE = plsEx(t, 0., zs[-1]) pls = approx.getPoles([None, t]) pls[np.abs(np.imag(pls)) > 1e-5] = np.nan if j == 0: polesE = np.empty((len(tspace), len(plsE))) poles = np.empty((len(tspace), len(pls))) polesE[:] = np.nan if len(plsE) > polesE.shape[1]: nanR = np.empty((len(tspace), len(plsE) - polesE.shape[1])) nanR[:] = np.nan polesE = np.hstack((polesE, nanR)) polesE[j, : len(plsE)] = np.real(plsE) poles[j] = np.real(pls) approx.verbosity = verb fighandles += [plt.figure(figsize = (17, 5))] ax1 = fighandles[-1].add_subplot(1, 2, 1) ax2 = fighandles[-1].add_subplot(1, 2, 2) ax1.plot(poles, tspace) ax1.set_ylim(Ls) ax1.set_xlabel('mu_1') ax1.set_ylabel('mu_2') ax1.grid() ax2.plot(polesE, tspace, 'k-.', linewidth = 1) ax2.plot(poles, tspace) for mm in approx.musMarginal: ax2.plot(zs, [mm[0, 0]] * 2, 'k--', linewidth = 1) ax2.set_xlim(zs) ax2.set_ylim(Ls) ax2.set_xlabel('mu_1') ax2.set_ylabel('mu_2') ax2.grid() plt.show() print("\n") diff --git a/examples/6_boundary_value_problem_1D/boundary_value_problem_1D_engine.py b/examples/6_boundary_value_problem_1D/boundary_value_problem_1D_engine.py index 77b3468..45f5018 100644 --- a/examples/6_boundary_value_problem_1D/boundary_value_problem_1D_engine.py +++ b/examples/6_boundary_value_problem_1D/boundary_value_problem_1D_engine.py @@ -1,40 +1,34 @@ import numpy as np import scipy.sparse as scsp -from rrompy.hfengines.base import LinearAffineEngine, NumpyEngineBase +from rrompy.hfengines.scipy_engines import EigenproblemEngine -class BVP1DEngine(LinearAffineEngine, NumpyEngineBase): +class BVP1DEngine(EigenproblemEngine): """ Second order finite differences (uniform grid) for y''(x)+ky(x)=f(x) (0= km, np.real(pls) <= kM)] diff --git a/examples/7_MHD/mhd.py b/examples/7_MHD/mhd.py index 02b90fb..bb8482c 100644 --- a/examples/7_MHD/mhd.py +++ b/examples/7_MHD/mhd.py @@ -1,73 +1,73 @@ import numpy as np import matplotlib.pyplot as plt from mhd_engine import MHDEngine as engine from rrompy.reduction_methods import (RationalInterpolant as RI, RationalInterpolantGreedy as RIG) from rrompy.parameter.parameter_sampling import (FFTSampler as FFTS, QuadratureCircleSampler as QCS, QuadratureBoxSampler as QBS) ks = [-.35 + .5j, 0. + .5j] k0 = np.mean(ks) solver = engine(5) kEffDelta = .1 * (ks[1] - ks[0]) kEff = np.real([ks[0] - kEffDelta, ks[1] + kEffDelta]) iEff = kEff - .5 * np.sum(np.real(ks)) + np.imag(ks[0]) nPoles = 50 polesEx = solver.getPolesExact(nPoles, k0) for corrector in [False, True]: for method in ["FFT", "BOX", "GREEDY"]: print("Testing {} method with{} corrector".format(method, "out" * (not corrector))) if method == "FFT": params = {'S':64, 'POD':True, 'polybasis':"MONOMIAL", - 'sampler':FFTS(ks), 'correctorTol':1e-5} + 'sampler':FFTS(ks), 'residueTol':1e-5} algo = RI if method == "BOX": params = {'S':64, 'POD':True, 'polybasis':"MONOMIAL", - 'sampler':QBS(ks), 'correctorTol':1e-5} + 'sampler':QBS(ks), 'residueTol':1e-5} algo = RI if method == "GREEDY": params = {'S':30, 'POD':True, 'greedyTol':1e-2, 'polybasis':"MONOMIAL", 'sampler':QCS(ks), 'errorEstimatorKind':"LOOK_AHEAD", 'nTestPoints':10000, - 'trainSetGenerator':FFTS(ks), 'correctorTol':1e-5} + 'trainSetGenerator':FFTS(ks), 'residueTol':1e-5} algo = RIG params['correctorForce'] = corrector approx = algo(solver, mu0 = k0, approx_state = True, approxParameters = params, verbosity = 10) approx.setupApprox() poles, residues = approx.getResidues() inRange = np.logical_and( np.logical_and(np.real(poles) >= kEff[0], np.real(poles) <= kEff[1]), np.logical_and(np.imag(poles) >= iEff[0], np.imag(poles) <= iEff[1])) polesEff = poles[inRange] resNormEff = np.linalg.norm(residues, axis = 1)[inRange] rLm = np.min(np.log(resNormEff)) rLmM = np.max(np.log(resNormEff)) - rLm fig = plt.figure(figsize = (10, 10)) ax = fig.add_subplot(1, 1, 1) if method == "GREEDY": ax.plot(approx.muTest.re.data.flatten(), approx.muTest.im.data.flatten(), 'k,', alpha = 0.25) for pl, rN in zip(polesEff, resNormEff): if corrector: alpha = .35 + .4 * (np.log(rN) - rLm) / rLmM else: alpha = .1 + .65 * (np.log(rN) - rLm) / rLmM ax.annotate("{0:.0e}".format(rN), (np.real(pl), np.imag(pl)), alpha = alpha) ax.plot(np.real(pl), np.imag(pl), 'r+', alpha = alpha + .25) ax.plot(approx.mus.re.data.flatten(), approx.mus.im.data.flatten(), 'k.') ax.plot(np.real(polesEx), np.imag(polesEx), 'bx') ax.set_xlim(kEff) ax.set_ylim(iEff) ax.grid() plt.tight_layout() plt.show() print("\n") diff --git a/examples/7_MHD/mhd_engine.py b/examples/7_MHD/mhd_engine.py index d56f9d3..aa8988f 100644 --- a/examples/7_MHD/mhd_engine.py +++ b/examples/7_MHD/mhd_engine.py @@ -1,23 +1,17 @@ import numpy as np import scipy.io as scio import scipy.sparse as sp -from rrompy.hfengines.base import LinearAffineEngine, NumpyEngineBaseTensorized +from rrompy.hfengines.scipy_engines import TensorizedEigenproblemEngine -class MHDEngine(LinearAffineEngine, NumpyEngineBaseTensorized): +class MHDEngine(TensorizedEigenproblemEngine): """ From Matrix Market: //math.nist.gov/MatrixMarket/data/NEP/mhd/mhd.html """ def __init__(self, ncol : int = 1, seed : int = 31415): - super().__init__() - self.npar = 1 - self.nAs, self.nbs = 2, 1 - self.nports = ncol - A0 = sp.csr_matrix(scio.mmread("mhd4800a.mtx"), dtype = np.complex) - self.As = [A0, - scio.mmread("mhd4800b.mtx").tocsr()] - np.random.seed(seed) - B = np.random.randn(self.As[0].shape[0], ncol) - self.bs[0] = (B / np.linalg.norm(B, axis = 0)).flatten() + A = sp.csr_matrix(scio.mmread("mhd4800a.mtx"), dtype = np.complex) + B = - scio.mmread("mhd4800b.mtx").tocsr() + super().__init__([A, B], seed, ncol) def getPolesExact(self, k:int, sigma:np.complex): return sp.linalg.eigs(self.As[0], k, - self.As[1], sigma, return_eigenvectors = False) diff --git a/examples/8_damped_mass_chain/damped_mass_chain.py b/examples/8_damped_mass_chain/damped_mass_chain.py new file mode 100644 index 0000000..30f1f30 --- /dev/null +++ b/examples/8_damped_mass_chain/damped_mass_chain.py @@ -0,0 +1,186 @@ +### example from Lohmann, Eid. Efficient Order Reduction of Parametric and +### Nonlinear Models by Superposition of Locally Reduced Models. +from copy import deepcopy as copy +import numpy as np +import matplotlib.pyplot as plt +from rrompy.reduction_methods import (NearestNeighbor as NN, + RationalInterpolant as RI, + RationalInterpolantGreedy as RIG, + RationalInterpolantPivoted as RIP, + RationalInterpolantGreedyPivoted as RIGP) +from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, + EmptySampler as ES) +from damped_mass_chain_engine import (bode as bode0, bodeLog, MassChainEngine, + MassChainEngineLog, AugmentedMassChainEngine, AugmentedMassChainEngineLog) +from rrompy.utilities.base.decorators import addWhiteNoise + +########################## +fullModelOrder = 1 #+ 1 +SMarginal = 1 #* 2 +state = 0 #+ 1 +noise_level = 0 #+ 1e-5 +LS = 0 #+ 6 +########################## + +modelSign = "Surrogate modeling for frequency response of " +if fullModelOrder == 1: modelSign += "augmented " +modelSign += "damper-mass-spring model" +if SMarginal > 1: modelSign += " with 1 design parameter" +modelSign += ".\nOutput is " +if state: + modelSign += "vector of mass displacements. " +else: + modelSign += "displacement of last mass. " +if LS: + modelSign += "Least-squares: S - N - 1 = {}. ".format(LS) +else: + modelSign += "Interpolatory: S = N + 1. " +modelSign += "Noise level: {}.\n".format(noise_level) +print(modelSign) + +M = [np.array([1., 5., 25., 125.])] +N = len(M[0]) +D = [np.zeros((N, N))] +D[0][0, 1], D[0][1, 2], D[0][2, 3], D[0][3, 3] = .1, .4, 1.6, 0. +D[0] = D[0] + D[0].T +K = [np.zeros((N, N))] +K[0][0, 1], K[0][1, 2], K[0][2, 3], K[0][0, 3], K[0][3, 3] = 9., 3., 1., 1., 2. +K[0] = K[0] + K[0].T +B = np.append(27., np.zeros(N - 1)).reshape(-1, 1) +if SMarginal > 1: + M += [np.zeros(N)] + D += [np.zeros((N, N))] + D[1][3, 3] = 1. + D[1] = D[1] + D[1].T + K += [np.zeros((N, N))] + K[1][0, 3], K[1][3, 3] = 2., 2. + K[1] = K[1] + K[1].T +if state: + C = np.eye(4) +else: + C = np.append(np.zeros(N - 1), 1.).reshape(1, -1) + +for logspace in range(2): + print("Approximation in l{}space".format("og" * logspace + + "in" * (not logspace))) + if logspace: + bode = bodeLog + if fullModelOrder == 1: + engine = AugmentedMassChainEngineLog + else: + engine = MassChainEngineLog + else: + bode = bode0 + if fullModelOrder == 1: + engine = AugmentedMassChainEngine + else: + engine = MassChainEngine + solver = addWhiteNoise(noise_level)(engine)(M, D, K, B, C) + + ss, mu = [1e-2, 1e1], [] + s0 = 10. ** np.mean(np.log10(ss)) + freq = np.logspace(np.log10(ss[0]), np.log10(ss[1]), 100) + if logspace: + ss, freq = [np.log10(ss[0]), np.log10(ss[1])], np.log10(freq) + s0, parameterMap = np.log10(s0), 1. + else: + parameterMap = {"F": [("log10", "x")], "B": [(10., "**", "x")]} + krange = [[ss[0]], [ss[-1]]] + k0, srange = [s0], copy(krange) + if SMarginal > 1: + ms = [0., 1.] + m0, mrange = np.mean(ms), [[ms[0]], [ms[-1]]] + krange[0] += mrange[0] + krange[1] += mrange[1] + k0 += [m0] + mu = [.5 * (ms[1] - ms[0]) / (SMarginal - 1)] + if not logspace: + parameterMap["F"] += [("x")] + parameterMap["B"] += [("x")] + + for method in ["RI", "RI_GREEDY"]: + print("Testing {} method".format(method)) + + if method == "RI": + params = {'S':15, 'POD':True, 'polybasis':"CHEBYSHEV"} + if LS: params["N"] = params["S"] - 1 - LS + if SMarginal > 1: + algo = RIP + else: + params['sampler'] = QS(srange, "CHEBYSHEV", parameterMap) + algo = RI + if method == "RI_GREEDY": + params = {'S':5, 'POD':True, 'polybasis':"LEGENDRE", + 'greedyTol':1e-2, 'errorEstimatorKind':"DISCREPANCY", + 'trainSetGenerator':QS(srange, "CHEBYSHEV", + parameterMap)} + if SMarginal > 1: + algo = RIGP + else: + params['sampler'] = QS(srange, "UNIFORM", parameterMap) + algo = RIG + if SMarginal > 1: + params["paramsMarginal"] = {"MMarginal": SMarginal - 1} + params['SMarginal'] = SMarginal + params['polybasisMarginal'] = "MONOMIAL" + params['radialDirectionalWeightsMarginal'] = [2. / (ms[1] - ms[0])] + params['matchingWeight'] = 1. + #params['cutOffTolerance'] = 2. + params['samplerPivot'] = QS(srange, "UNIFORM", parameterMap) + params['samplerMarginal'] = QS(mrange, "UNIFORM") + approx = algo([0], solver, mu0 = k0, approx_state = True, + approxParameters = params, verbosity = 5, + storeAllSamples = True) + else: + approx = algo(solver, mu0 = k0, approx_state = True, + approxParameters = params, verbosity = 5) + if "GREEDY" in method: + approx.setupApprox("LAST") + else: + approx.setupApprox() + + approxNN = NN(solver, mu0 = k0, approx_state = True, verbosity = 5, + approxParameters = {'S':len(approx.mus), + 'POD':params['POD'], 'sampler':ES()}) + if SMarginal > 1: + approxNN.setSamples(approx.storedSamplesFilenames) + approx.purgeStoredSamples() + for m in approx.musMarginal: + bode(freq, m[0], + [approx.getHF, approx.getApprox, approxNN.getApprox]) + else: + approxNN.setSamples(approx.samplingEngine) + bode(freq, mu, [approx.getHF, approx.getApprox, approxNN.getApprox]) + if SMarginal > 1: + bode(freq, [1.5 * ms[1]], + [approx.getHF, approx.getApprox, approxNN.getApprox]) + bode(freq, [2. * ms[1]], + [approx.getHF, approx.getApprox, approxNN.getApprox]) + verb = approx.verbosity + approx.verbosity = 0 + mspace = np.linspace(ms[0], ms[-1], 10) + for j, t in enumerate(mspace): + pls = approx.getPoles([None, t]) + if j == 0: + poles = np.empty((len(mspace), len(pls)), + dtype = np.complex) + poles[j] = pls + for j, t in enumerate(approx.musMarginal): + pls = approx.getPoles([None, t[0][0]]) + if j == 0: + polesE = np.empty((SMarginal, len(pls)), + dtype = np.complex) + polesE[j] = pls + approx.verbosity = verb + fig = plt.figure(figsize = (10, 6)) + ax = fig.add_subplot(1, 1, 1) + ax.plot(np.real(poles), np.imag(poles), '--') + ax.plot(np.real(polesE), np.imag(polesE), 'ko', markersize = 4) + ax.set_xlabel('Real') + ax.set_ylabel('Imag') + ax.grid() + plt.show() + else: + poles = approx.getPoles() + print("Poles:\n{}".format(poles)) + print("\n") diff --git a/examples/8_damped_mass_chain/damped_mass_chain_engine.py b/examples/8_damped_mass_chain/damped_mass_chain_engine.py new file mode 100644 index 0000000..9432cb3 --- /dev/null +++ b/examples/8_damped_mass_chain/damped_mass_chain_engine.py @@ -0,0 +1,79 @@ +### example from Lohmann, Eid. Efficient Order Reduction of Parametric and +### Nonlinear Models by Superposition of Locally Reduced Models. +import numpy as np +import matplotlib.pyplot as plt +from rrompy.hfengines.scipy_engines import (OscillatorProblemEngine, + AugmentedOscillatorProblemEngine) + +lines = ["-", "--", ":", "-."] * 5 +def bode(freq, mus, models): + x = np.hstack((freq.reshape(-1, 1), np.tile(np.array(mus).reshape(1, -1), + (len(freq), 1)))) + fig = plt.figure(figsize = (7.5, 4.5)) + ax = fig.add_subplot(1, 1, 1) + for model, line in zip(models, lines[: len(models)]): + ax.semilogx(x[:, 0], 20 * np.log10(np.abs(model(x).data.T)), line) + ax.set_xlim(freq[0], freq[-1]) + ax.set_xlabel("omega") + ax.set_ylabel("output") + ax.set_title(str(mus)) + ax.grid() + plt.show() + return fig + +def bodeLin(freq, mus, models): + x = np.hstack((freq.reshape(-1, 1), np.tile(np.array(mus).reshape(1, -1), + (len(freq), 1)))) + fig = plt.figure(figsize = (7.5, 4.5)) + ax = fig.add_subplot(1, 1, 1) + for model, line in zip(models, lines[: len(models)]): + ax.plot(x[:, 0], 20 * np.log10(np.abs(model(x).data.T)), line) + ax.set_xlim(freq[0], freq[-1]) + ax.set_xlabel("omega") + ax.set_ylabel("output") + ax.set_title(str(mus)) + ax.grid() + plt.show() + return fig + +def bodeLog(freq, mus, models): + x = np.hstack((freq.reshape(-1, 1), np.tile(np.array(mus).reshape(1, -1), + (len(freq), 1)))) + fig = plt.figure(figsize = (7.5, 4.5)) + ax = fig.add_subplot(1, 1, 1) + for model, line in zip(models, lines[: len(models)]): + ax.semilogx(10. ** x[:, 0], 20 * np.log10(np.abs(model(x).data.T)), + line) + ax.set_xlim(10 ** freq[0], 10 ** freq[-1]) + ax.set_xlabel("omega") + ax.set_ylabel("output") + ax.set_title(str(mus)) + ax.grid() + plt.show() + return fig + +class MassChainEngine(OscillatorProblemEngine): pass + +class AugmentedMassChainEngine(AugmentedOscillatorProblemEngine): pass + +class MassChainEngineLog(MassChainEngine): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._affinePoly = False + deg = [1] + [0] * (self.npar - 1) + for j in range(self.npar): + self.thAs[3 * j] = [self.thAs[3 * j][0]] + self.thAs[3 * j + 1] = [self.thAs[3 * j][0] + ('*', (10., "**", + ("prod", {"axis" : 1}, ("data", "x", "**", deg))))] + self.thAs[3 * j + 2] = [self.thAs[3 * j][0] + ('*', (100., "**", + ("prod", {"axis" : 1}, ("data", "x", "**", deg))))] + +class AugmentedMassChainEngineLog(AugmentedMassChainEngine): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._affinePoly = False + deg = [1] + [0] * (self.npar - 1) + for j in range(self.npar): + self.thAs[2 * j] = [self.thAs[2 * j][0]] + self.thAs[2 * j + 1] = [self.thAs[2 * j][0] + ('*', (10., "**", + ("prod", {"axis" : 1}, ("data", "x", "**", deg))))] diff --git a/rrompy/hfengines/base/__init__.py b/rrompy/hfengines/base/__init__.py index a0d375b..241a91b 100644 --- a/rrompy/hfengines/base/__init__.py +++ b/rrompy/hfengines/base/__init__.py @@ -1,42 +1,42 @@ # 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 .boundary_conditions import BoundaryConditions from .fenics_engine_base import FenicsEngineBase, FenicsEngineBaseTensorized from .hfengine_base import HFEngineBase from .linear_affine_engine import LinearAffineEngine, checkIfAffine from .marginal_proxy_engine import MarginalProxyEngine -from .numpy_engine_base import NumpyEngineBase, NumpyEngineBaseTensorized +from .scipy_engine_base import ScipyEngineBase, ScipyEngineBaseTensorized from .vector_fenics_engine_base import VectorFenicsEngineBase, VectorFenicsEngineBaseTensorized __all__ = [ 'BoundaryConditions', 'FenicsEngineBase', 'FenicsEngineBaseTensorized', 'HFEngineBase', 'LinearAffineEngine', 'checkIfAffine', 'MarginalProxyEngine', - 'NumpyEngineBase', - 'NumpyEngineBaseTensorized', + 'ScipyEngineBase', + 'ScipyEngineBaseTensorized', 'VectorFenicsEngineBase', 'VectorFenicsEngineBaseTensorized' ] diff --git a/rrompy/hfengines/base/fenics_engine_base.py b/rrompy/hfengines/base/fenics_engine_base.py index 1c0ac28..4957995 100644 --- a/rrompy/hfengines/base/fenics_engine_base.py +++ b/rrompy/hfengines/base/fenics_engine_base.py @@ -1,514 +1,514 @@ # 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, checknports +from .scipy_engine_base import ScipyEngineBase, checknports from rrompy.utilities.base.types import (Np1D, strLst, FenFunc, Tuple, List, FigHandle) 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, serializeFunctionSpace) from .boundary_conditions import BoundaryConditions from rrompy.utilities.exception_manager import RROMPyException from rrompy.utilities.parallel import (SELF, masterCore, bcast, indicesScatter, - listGather, updateSerialIndex) + listGather) __all__ = ['FenicsEngineBase', 'FenicsEngineBaseTensorized'] def plottingBaseFen(u, fig, V, what, nRows, subplotidx, warping, name, colorbar, fenplotArgs): if 'ABS' in what: uAb = fen.Function(V) uAb.vector().set_local(np.abs(u)) subplotidx = subplotidx + 1 ax = fig.add_subplot(nRows, len(what), subplotidx) p = fenplot(uAb, warping = warping, title = "|{}|".format(name), **fenplotArgs) if colorbar: fig.colorbar(p, ax = ax) if 'PHASE' in what: uPh = fen.Function(V) uPh.vector().set_local(np.angle(u)) subplotidx = subplotidx + 1 ax = fig.add_subplot(nRows, len(what), subplotidx) p = fenplot(uPh, warping = warping, title = "phase({})".format(name), **fenplotArgs) if colorbar: fig.colorbar(p, ax = ax) if 'REAL' in what: uRe = fen.Function(V) uRe.vector().set_local(np.real(u)) subplotidx = subplotidx + 1 ax = fig.add_subplot(nRows, len(what), subplotidx) p = fenplot(uRe, warping = warping, title = "Re({})".format(name), **fenplotArgs) if colorbar: fig.colorbar(p, ax = ax) if 'IMAG' in what: uIm = fen.Function(V) uIm.vector().set_local(np.imag(u)) subplotidx = subplotidx + 1 ax = fig.add_subplot(nRows, len(what), subplotidx) p = fenplot(uIm, warping = warping, title = "Im({})".format(name), **fenplotArgs) if colorbar: fig.colorbar(p, ax = ax) -class FenicsEngineBase(NumpyEngineBase): +class FenicsEngineBase(ScipyEngineBase): """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(SELF, 1, 1), "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 = serializeFunctionSpace(V) self.u = fen.TrialFunction(self._V) self.v = fen.TestFunction(self._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, colorMap : str = "jet", 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. colorMap(optional): Pyplot colormap. Defaults to 'jet'. 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, colorMap, 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 out = None if masterCore(): if 'figsize' not in figspecs.keys(): figspecs['figsize'] = plt.figaspect(1. / len(what)) fig = plt.figure(**figspecs) plt.set_cmap(colorMap) plottingBaseFen(u, fig, self.V, what, 1, 0, warping, name, self.V.mesh().geometric_dimension() > 1, fenplotArgs) 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() out = fig if fileOut is None else (fig, fileOut) return bcast(out) 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. """ out = None if masterCore(): 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() out = fig if fileOut is None else (fig, fileOut) return bcast(out) 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 filePW = None if masterCore(): 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 bcast(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.")) filePW = None if masterCore(): 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 bcast(filePW) class FenicsEngineBaseTensorized(FenicsEngineBase): """The number of tensorized dimensions should be assigned to nports.""" 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, colorMap : str = "jet", 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. colorMap(optional): Pyplot colormap. Defaults to 'jet'. fenplotArgs(optional): Optional arguments for fenplot. figspecs(optional key args): Optional arguments for matplotlib figure creation. Returns: Output filename and figure handle. """ nP = checknports(self) if not is_state and not self.isCEye: return super().plot(u.reshape(-1, nP), warping, False, name, save, what, forceNewFile, saveFormat, saveDPI, show, colorMap, 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 out = None if masterCore(): if 'figsize' not in figspecs.keys(): figspecs['figsize'] = plt.figaspect(1. / len(what)) figspecs['figsize'][1] *= nP fig = plt.figure(**figspecs) plt.set_cmap(colorMap) for i in range(nP): plottingBaseFen(u[i :: nP], fig, self.V, what, nP, i * len(what), warping, "{}_port{}".format(name, i + 1), self.V.mesh().geometric_dimension() > 1, fenplotArgs) 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() out = fig if fileOut is None else (fig, fileOut) return bcast(out) def outParaview(self, u:Np1D, *args, **kwargs) -> List[str]: nP = checknports(self) idx = indicesScatter(nP)[0] filesOut = [] if len(idx) > 0: for j in idx: filesOut += super().outParaview(u[j :: nP], *args, **kwargs) filesOut = listGather(filesOut) if filesOut[0] is None: return None return filesOut def outParaviewTimeDomain(self, u:Np1D, *args, **kwargs) -> List[str]: nP = checknports(self) idx = indicesScatter(nP)[0] filesOut = [] if len(idx) > 0: for j in idx: filesOut += super().outParaviewTimeDomain(u[j :: nP], *args, **kwargs) filesOut = listGather(filesOut) if filesOut[0] is None: return None return filesOut diff --git a/rrompy/hfengines/base/hfengine_base.py b/rrompy/hfengines/base/hfengine_base.py index 558b9b3..93976ae 100644 --- a/rrompy/hfengines/base/hfengine_base.py +++ b/rrompy/hfengines/base/hfengine_base.py @@ -1,262 +1,272 @@ # 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, deepcopy as copy +from copy import copy as softcopy from rrompy.utilities.base.decorators import nonaffine_construct -from rrompy.utilities.base.types import (Np1D, Np2D, Tuple, List, ListAny, - DictAny, paramVal, paramList, - sampList) +from rrompy.utilities.base.types import (Np1D, Np2D, List, DictAny, paramVal, + paramList, sampList) from rrompy.utilities.numerical import solve as tsolve, dot, customPInv -from rrompy.utilities.numerical.rayleigh_quotient_iteration import ( - rayleighQuotientIteration) +from rrompy.utilities.expression import expressionEvaluator from rrompy.utilities.exception_manager import RROMPyAssert -from rrompy.parameter import checkParameter, checkParameterList +from rrompy.sampling.sample_list import sampleList +from rrompy.parameter import (checkParameter, checkParameterList, + parameterList, parameterMap as pMap) from rrompy.solver.linear_solver import setupSolver -from rrompy.utilities.parallel import (COMM, poolRank, masterCore, listScatter, - matrixGatherv) +from rrompy.utilities.parallel import (poolRank, masterCore, listScatter, + matrixGatherv, isend, recv) __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.parameterMap = pMap(1., npar) self._npar = npar @property def spacedim(self): return 1 - def checkParameter(self, mu:paramVal): + def checkParameter(self, mu:paramVal) -> paramVal: muP = checkParameter(mu, self.npar) if self.npar == 0: muP.reset((1, 0), muP.dtype) return muP - def checkParameterList(self, mu:paramList): - muL = checkParameterList(mu, self.npar) - if self.npar == 0: muL[0].reset((1, 0), muL[0].dtype) + def checkParameterList(self, mu:paramList, + check_if_single : bool = False) -> paramList: + muL = checkParameterList(mu, self.npar, check_if_single) return muL + def mapParameterList(self, mu:paramList, direct : str = "F", + idx : List[int] = None) -> paramList: + if idx is None: idx = np.arange(self.npar) + muMapped = checkParameterList(mu, len(idx)) + for j, d in enumerate(idx): + muMapped.data[:, j] = expressionEvaluator( + self.parameterMap[direct][d], + muMapped(j)).flatten() + return muMapped + 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 isinstance(u, (parameterList, sampleList)): u = u.data + if isinstance(v, (parameterList, sampleList)): 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.""" if (hasattr(self, "As") and hasattr(self.As, "__len__") and self.As[0] is not None): d = self.As[0].shape[0] else: d = self.spacedim 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) 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) -> 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. """ from rrompy.sampling import sampleList, emptySampleList if mu == []: mu = self.mu0 - mu = self.checkParameterList(mu)[0] + mu = self.checkParameterList(mu) if len(mu) == 0: return emptySampleList() mu, idx, sizes = listScatter(mu, return_sizes = True) - mu = self.checkParameterList(mu)[0] + mu = self.checkParameterList(mu) req, emptyCores = [], np.where(np.logical_not(sizes))[0] if len(mu) == 0: - uL, uT = COMM.recv(source = 0, tag = poolRank()) + uL, uT = recv(source = 0, tag = poolRank()) sol = np.empty((uL, 0), dtype = uT) else: if RHS is None: RHS = sampleList([self.b(m) for m in mu]) else: RHS = sampleList(RHS) if len(RHS) > 1: RHS = sampleList([RHS[i] for i in idx]) mult = 0 if len(RHS) == 1 else 1 RROMPyAssert(mult * (len(mu) - 1) + 1, len(RHS), "Sample size") for j, mj in enumerate(mu): u = tsolve(self.A(mj), RHS[mult * j], self._solver, self._solverArgs) if j == 0: sol = np.empty((len(u), len(mu)), dtype = u.dtype) if masterCore(): for dest in emptyCores: - req += [COMM.isend((len(u), u.dtype), dest = dest, - tag = dest)] + req += [isend((len(u), u.dtype), dest = dest, + tag = dest)] sol[:, j] = u if not return_state: sol = self.applyC(sol) for r in req: r.wait() return sampleList(matrixGatherv(sol, sizes)) 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] + mu = self.checkParameterList(mu) if len(mu) == 0: return emptySampleList() mu, idx, sizes = listScatter(mu, return_sizes = True) - mu = self.checkParameterList(mu)[0] + mu = self.checkParameterList(mu) req, emptyCores = [], np.where(np.logical_not(sizes))[0] if len(mu) == 0: - uL, uT = COMM.recv(source = 0, tag = poolRank()) + uL, uT = recv(source = 0, tag = poolRank()) res = np.empty((uL, 0), dtype = uT) else: v = sampleList(np.zeros((self.spacedim, len(mu)))) if u is not None: u = sampleList(u) v = v + sampleList([u[i] for i in idx]) for j, (mj, vj) in enumerate(zip(mu, v)): r = self.b(mj) - dot(self.A(mj), vj) if j == 0: res = np.empty((len(r), len(mu)), dtype = r.dtype) if masterCore(): for dest in emptyCores: - req += [COMM.isend((len(r), r.dtype), dest = dest, - tag = dest)] + req += [isend((len(r), r.dtype), dest = dest, + tag = dest)] res[:, j] = r if post_c: res = self.applyC(res) for r in req: r.wait() return sampleList(matrixGatherv(res, sizes)) diff --git a/rrompy/hfengines/base/linear_affine_engine.py b/rrompy/hfengines/base/linear_affine_engine.py index 61030f5..3bacc52 100644 --- a/rrompy/hfengines/base/linear_affine_engine.py +++ b/rrompy/hfengines/base/linear_affine_engine.py @@ -1,199 +1,197 @@ # 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 .hfengine_base import HFEngineBase 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.hash_derivative import ( hashDerivativeToIdx as hashD) from rrompy.utilities.exception_manager import RROMPyException __all__ = ['LinearAffineEngine', 'checkIfAffine'] class LinearAffineEngine(HFEngineBase): """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 + muE = self.mapParameterList(mu) 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 5efc26f..4b4d90c 100644 --- a/rrompy/hfengines/base/marginal_proxy_engine.py +++ b/rrompy/hfengines/base/marginal_proxy_engine.py @@ -1,160 +1,158 @@ # 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 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"] 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 completeMu(self, mu:paramVal): + mu = checkParameter(mu, self.nparFree, return_data = True) + return np.insert(mu, 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) + def completeMuList(self, mu:paramList): + mu = checkParameterList(mu, self.nparFree, return_data = True) + return np.insert(mu, 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 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) 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/scipy_engine_base.py similarity index 97% rename from rrompy/hfengines/base/numpy_engine_base.py rename to rrompy/hfengines/base/scipy_engine_base.py index fae58aa..61dde66 100644 --- a/rrompy/hfengines/base/numpy_engine_base.py +++ b/rrompy/hfengines/base/scipy_engine_base.py @@ -1,127 +1,127 @@ # 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.data_structures import purgeList, getNewFilename from rrompy.utilities.exception_manager import RROMPyException from rrompy.utilities.parallel import masterCore, bcast -__all__ = ['NumpyEngineBase', 'NumpyEngineBaseTensorized'] +__all__ = ['ScipyEngineBase', 'ScipyEngineBaseTensorized'] def checknports(eng) -> int: if not hasattr(eng, "nports"): raise RROMPyException(("Engine.nports should be assigned before using " "tensorized plotting functionalities.")) return eng.nports -class NumpyEngineBase(HFEngineBase): +class ScipyEngineBase(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, colorMap : str = "jet", 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. colorMap(optional): Pyplot colormap. Defaults to 'jet'. 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 out = None if masterCore(): if 'figsize' not in figspecs.keys(): figspecs['figsize'] = plt.figaspect(1. / len(what)) idxs = np.arange(len(u)) if warping is not None: idxs = warping[0](idxs) subplotidx = 0 fig = plt.figure(**figspecs) plt.set_cmap(colorMap) if 'ABS' in what: subplotidx = subplotidx + 1 ax = fig.add_subplot(1, len(what), subplotidx) ax.plot(idxs, np.abs(u), **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), **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), **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), **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() out = fig if fileOut is None else (fig, fileOut) return bcast(out) -class NumpyEngineBaseTensorized(NumpyEngineBase): +class ScipyEngineBaseTensorized(ScipyEngineBase): """The number of tensorized dimensions should be assigned to nports.""" def plot(self, u:Np1D, *args, **kwargs) -> Tuple[FigHandle, str]: return super().plot(u.reshape(-1, checknports(self)), *args, **kwargs) diff --git a/rrompy/hfengines/fenics_engines/__init__.py b/rrompy/hfengines/fenics_engines/__init__.py new file mode 100644 index 0000000..8ea6b7f --- /dev/null +++ b/rrompy/hfengines/fenics_engines/__init__.py @@ -0,0 +1,46 @@ +# 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 .laplace_base_problem_engine import LaplaceBaseProblemEngine +from .helmholtz_problem_engine import (HelmholtzProblemEngine, + ScatteringProblemEngine) +from .helmholtz_problem_engine_augmented import (HelmholtzProblemEngineAugmented, + ScatteringProblemEngineAugmented) +from .linear_elasticity_problem_engine import LinearElasticityProblemEngine +from .linear_elasticity_helmholtz_problem_engine import ( + LinearElasticityHelmholtzProblemEngine, + LinearElasticityHelmholtzProblemEngineDamped) +from .linear_elasticity_helmholtz_problem_engine_augmented import ( + LinearElasticityHelmholtzProblemEngineAugmented, + LinearElasticityHelmholtzProblemEngineDampedAugmented) + +__all__ = [ + 'LaplaceBaseProblemEngine', + 'HelmholtzProblemEngine', + 'ScatteringProblemEngine', + 'HelmholtzProblemEngineAugmented', + 'ScatteringProblemEngineAugmented', + 'LinearElasticityProblemEngine', + 'LinearElasticityHelmholtzProblemEngine', + 'LinearElasticityHelmholtzProblemEngineDamped', + 'LinearElasticityHelmholtzProblemEngineAugmented', + 'LinearElasticityHelmholtzProblemEngineDampedAugmented' + ] + + + diff --git a/rrompy/hfengines/linear_problem/scattering_problem_engine.py b/rrompy/hfengines/fenics_engines/helmholtz_problem_engine.py similarity index 52% rename from rrompy/hfengines/linear_problem/scattering_problem_engine.py rename to rrompy/hfengines/fenics_engines/helmholtz_problem_engine.py index cb1f536..2dd4a45 100644 --- a/rrompy/hfengines/linear_problem/scattering_problem_engine.py +++ b/rrompy/hfengines/fenics_engines/helmholtz_problem_engine.py @@ -1,137 +1,228 @@ # 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 numpy import inf import fenics as fen -from rrompy.utilities.base.types import paramVal -from rrompy.solver.fenics import fenZERO +from .laplace_base_problem_engine import LaplaceBaseProblemEngine +from rrompy.solver.fenics import fenZERO, fenONE, fenics2Sparse from rrompy.utilities.base import verbosityManager as vbMng -from .helmholtz_problem_engine import HelmholtzProblemEngine from rrompy.utilities.exception_manager import RROMPyWarning -from rrompy.solver.fenics import fenics2Sparse +from rrompy.parameter import parameterMap as pMap -__all__ = ['ScatteringProblemEngine'] +__all__ = ['HelmholtzProblemEngine', 'ScatteringProblemEngine'] + +class HelmholtzProblemEngine(LaplaceBaseProblemEngine): + """ + Solver for generic Helmholtz problems with parametric wavenumber. + - \nabla \cdot (a \nabla u) - omega^2 * n**2 * u = f in \Omega + u = u0 on \Gamma_D + \partial_nu = g1 on \Gamma_N + \partial_nu + h u = g2 on \Gamma_R + + Attributes: + verbosity: Verbosity level. + BCManager: Boundary condition manager. + V: Real FE space. + u: Generic trial functions for variational form evaluation. + v: Generic test functions for variational form evaluation. + As: Scipy sparse array representation (in CSC format) of As. + bs: Numpy array representation of bs. + cs: Numpy array representation of cs. + energyNormMatrix: Scipy sparse matrix representing inner product over + V. + energyNormDualMatrix: Scipy sparse matrix representing dual inner + product between Riesz representers V-V. + degree_threshold: Threshold for ufl expression interpolation degree. + omega: Value of omega. + diffusivity: Value of a. + refractionIndex: Value of n. + forcingTerm: Value of f. + DirichletDatum: Value of u0. + NeumannDatum: Value of g1. + RobinDatumG: Value of g2. + RobinDatumH: Value of h. + DirichletBoundary: Function handle to \Gamma_D. + NeumannBoundary: Function handle to \Gamma_N. + RobinBoundary: Function handle to \Gamma_R. + ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). + dsToBeSet: Whether ds needs to be set. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._affinePoly = True + self.nAs = 2 + self.parameterMap = pMap([2.] + [1.] * (self.npar - 1)) + self.refractionIndex = fenONE + + @property + def refractionIndex(self): + """Value of n.""" + return self._refractionIndex + @refractionIndex.setter + def refractionIndex(self, refractionIndex): + self.resetAs() + if not isinstance(refractionIndex, (list, tuple,)): + refractionIndex = [refractionIndex, fenZERO] + self._refractionIndex = refractionIndex + + 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.autoSetDS() + vbMng(self, "INIT", "Assembling operator term A0.", 20) + DirichletBC0 = fen.DirichletBC(self.V, fenZERO, + self.DirichletBoundary) + aRe, aIm = self.diffusivity + hRe, hIm = self.RobinDatumH + termNames = ["diffusivity", "RobinDatumH"] + parsRe = self.iterReduceQuadratureDegree(zip( + [aRe, hRe], + [x + "Real" for x in termNames])) + parsIm = self.iterReduceQuadratureDegree(zip( + [aIm, hIm], + [x + "Imag" for x in termNames])) + a0Re = (aRe * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx + + hRe * fen.dot(self.u, self.v) * self.ds(1)) + a0Im = (aIm * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx + + hIm * fen.dot(self.u, self.v) * self.ds(1)) + self.As[0] = (fenics2Sparse(a0Re, parsRe, DirichletBC0, 1) + + 1.j * fenics2Sparse(a0Im, parsIm, DirichletBC0, 0)) + vbMng(self, "DEL", "Done assembling operator term.", 20) + if self.As[1] is None: + vbMng(self, "INIT", "Assembling operator term A1.", 20) + DirichletBC0 = fen.DirichletBC(self.V, fenZERO, + self.DirichletBoundary) + nRe, nIm = self.refractionIndex + n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm + parsRe = self.iterReduceQuadratureDegree(zip([n2Re], + ["refractionIndexSquaredReal"])) + parsIm = self.iterReduceQuadratureDegree(zip([n2Im], + ["refractionIndexSquaredImag"])) + a1Re = - n2Re * fen.dot(self.u, self.v) * fen.dx + a1Im = - n2Im * fen.dot(self.u, self.v) * fen.dx + self.As[1] = (fenics2Sparse(a1Re, parsRe, DirichletBC0, 0) + + 1.j * fenics2Sparse(a1Im, parsIm, DirichletBC0, 0)) + vbMng(self, "DEL", "Done assembling operator term.", 20) class ScatteringProblemEngine(HelmholtzProblemEngine): """ Solver for scattering problems with parametric wavenumber. - \nabla \cdot (a \nabla u) - omega^2 * n**2 * u = f in \Omega u = u0 on \Gamma_D \partial_nu = g1 on \Gamma_N \partial_nu +- i omega u = g2 on \Gamma_R Attributes: verbosity: Verbosity level. BCManager: Boundary condition manager. V: Real FE space. u: Generic trial functions for variational form evaluation. v: Generic test functions for variational form evaluation. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. cs: Numpy array representation of cs. energyNormMatrix: Scipy sparse matrix representing inner product over V. energyNormDualMatrix: Scipy sparse matrix representing dual inner product between Riesz representers V-V. degree_threshold: Threshold for ufl expression interpolation degree. signR: Sign in ABC. omega: Value of omega. diffusivity: Value of a. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. RobinDatumG: Value of g2. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). dsToBeSet: Whether ds needs to be set. """ - def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = inf, - verbosity : int = 10, timestamp : bool = True): + def __init__(self, *args, **kwargs): self.silenceWarnings = True - super().__init__(mu0 = mu0, degree_threshold = degree_threshold, - verbosity = verbosity, timestamp = timestamp) + super().__init__(*args, **kwargs) self._affinePoly = True del self.silenceWarnings self.nAs = 3 - self.rescalingExp = [1.] + self.rescalingExp[1 :] + self.parameterMap = pMap(1., self.npar) self.signR = - 1. @property def RobinDatumH(self): """Value of h.""" return self.signR * self.omega @RobinDatumH.setter def RobinDatumH(self, RobinDatumH): if not hasattr(self, "silenceWarnings"): RROMPyWarning(("Scattering problems do not allow changes of h. " "Ignoring assignment.")) return @property def signR(self): """Value of signR.""" return self._signR @signR.setter def signR(self, signR): self.resetAs() self._signR = signR 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: vbMng(self, "INIT", "Assembling operator term A0.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) aRe, aIm = self.diffusivity parsRe = self.iterReduceQuadratureDegree(zip([aRe], ["diffusivityReal"])) parsIm = self.iterReduceQuadratureDegree(zip([aIm], ["diffusivityImag"])) a0Re = aRe * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx a0Im = aIm * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx self.As[0] = (fenics2Sparse(a0Re, parsRe, DirichletBC0, 1) + 1.j * fenics2Sparse(a0Im, parsIm, DirichletBC0, 0)) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[1] is None: self.autoSetDS() vbMng(self, "INIT", "Assembling operator term A1.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a1 = fen.dot(self.u, self.v) * self.ds(1) self.As[1] = (self.signR * 1.j * fenics2Sparse(a1, {}, DirichletBC0, 0)) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[2] is None: vbMng(self, "INIT", "Assembling operator term A2.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) nRe, nIm = self.refractionIndex n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm parsRe = self.iterReduceQuadratureDegree(zip([n2Re], ["refractionIndexSquaredReal"])) parsIm = self.iterReduceQuadratureDegree(zip([n2Im], ["refractionIndexSquaredImag"])) a2Re = - n2Re * fen.dot(self.u, self.v) * fen.dx a2Im = - n2Im * fen.dot(self.u, self.v) * fen.dx self.As[2] = (fenics2Sparse(a2Re, parsRe, DirichletBC0, 0) + 1.j * fenics2Sparse(a2Im, parsIm, DirichletBC0, 0)) vbMng(self, "DEL", "Done assembling operator term.", 20) diff --git a/rrompy/hfengines/fenics_engines/helmholtz_problem_engine_augmented.py b/rrompy/hfengines/fenics_engines/helmholtz_problem_engine_augmented.py new file mode 100755 index 0000000..b682999 --- /dev/null +++ b/rrompy/hfengines/fenics_engines/helmholtz_problem_engine_augmented.py @@ -0,0 +1,267 @@ +# 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 numpy import pad +from scipy.sparse import eye, bmat, block_diag +from .helmholtz_problem_engine import (HelmholtzProblemEngine, + ScatteringProblemEngine) +from rrompy.solver.fenics import (augmentedH1NormMatrix, + augmentedHminus1NormMatrix) +from rrompy.utilities.base import verbosityManager as vbMng +from rrompy.utilities.exception_manager import RROMPyException +from rrompy.parameter import parameterMap as pMap + +__all__ = ['HelmholtzProblemEngineAugmented', + 'ScatteringProblemEngineAugmented'] + +class HelmholtzProblemEngineAugmented(HelmholtzProblemEngine): + """ + Solver for generic Helmholtz problems with parametric wavenumber. + - \nabla \cdot (a \nabla u) - omega * n**2 * v = f in \Omega + omega * u = v in \overline{\Omega} + u = u0 on \Gamma_D + \partial_nu = g1 on \Gamma_N + \partial_nu + h u = g2 on \Gamma_R + + Attributes: + verbosity: Verbosity level. + BCManager: Boundary condition manager. + V: Real FE space. + u: Generic trial functions for variational form evaluation. + v: Generic test functions for variational form evaluation. + As: Scipy sparse array representation (in CSC format) of As. + bs: Numpy array representation of bs. + cs: Numpy array representation of cs. + energyNormMatrix: Scipy sparse matrix representing inner product over + V. + energyNormDualMatrix: Scipy sparse matrix representing dual inner + product between Riesz representers V-V. + degree_threshold: Threshold for ufl expression interpolation degree. + omega: Value of omega. + diffusivity: Value of a. + refractionIndex: Value of n. + forcingTerm: Value of f. + DirichletDatum: Value of u0. + NeumannDatum: Value of g1. + RobinDatumG: Value of g2. + RobinDatumH: Value of h. + DirichletBoundary: Function handle to \Gamma_D. + NeumannBoundary: Function handle to \Gamma_N. + RobinBoundary: Function handle to \Gamma_R. + ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). + dsToBeSet: Whether ds needs to be set. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.parameterMap = pMap(1., self.npar) + + @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 2 * super().spacedim + + def buildEnergyNormForm(self): + """ + Build sparse matrix (in CSR format) representative of scalar product. + """ + vbMng(self, "INIT", "Assembling energy matrix.", 20) + self.energyNormMatrix = augmentedH1NormMatrix(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. + """ + vbMng(self, "INIT", "Assembling energy dual matrix.", 20) + self.energyNormDualMatrix = augmentedHminus1NormMatrix(self.V, + compressRank = self._energyDualNormCompress) + vbMng(self, "DEL", "Done assembling energy dual matrix.", 20) + + def buildA(self): + """Build terms of operator of linear system.""" + ANone = any([A is None for A in self.As]) + if not ANone: return + self.nAs = 2 + super().buildA() + I = eye(self.spacedim // 2) + self.As[0] = block_diag((self.As[0], I), format = "csr") + self.As[1] = bmat([[None, self.As[1]], [- I, None]], format = "csr") + + def buildb(self): + """Build terms of operator of linear system.""" + bNone = any([b is None for b in self.bs]) + if not bNone: return + self.nbs = 1 + dim = self.spacedim // 2 + super().buildb() + self.bs[0] = pad(self.bs[0], (0, dim), "constant") + + def plot(self, u, warping = None, is_state = False, name = "u", + save = None, what = 'all', forceNewFile = True, + saveFormat = "eps", saveDPI = 100, show = True, colorMap = "jet", + fenplotArgs = {}, **figspecs): + uh = u[: self.spacedim // 2] if is_state or self.isCEye else u + return super().plot(uh, warping, is_state, name, save, what, + forceNewFile, saveFormat, saveDPI, show, + colorMap, fenplotArgs, **figspecs) + + def outParaview(self, u, warping = None, is_state = False, name = "u", + filename = "out", time = 0., what = 'all', + forceNewFile = True, folder = False, filePW = None): + if not is_state and not self.isCEye: + raise RROMPyException(("Cannot output to Paraview non-state " + "object.")) + return super().outParaview(u[: self.spacedim // 2], warping, is_state, + name, filename, time, what, forceNewFile, + folder, filePW) + + def outParaviewTimeDomain(self, u, omega, warping = None, is_state = False, + timeFinal = None, periodResolution = 20, + name = "u", filename = "out", + forceNewFile = True, folder = False): + if not is_state and not self.isCEye: + raise RROMPyException(("Cannot output to Paraview non-state " + "object.")) + return super().outParaviewTimeDomain(u[: self.spacedim // 2], omega, + warping, is_state, timeFinal, + periodResolution, name, filename, + forceNewFile, folder) + +class ScatteringProblemEngineAugmented(ScatteringProblemEngine): + """ + Solver for scattering problems with parametric wavenumber. + - \nabla \cdot (a \nabla u) - omega * n**2 * v = f in \Omega + omega * u = v in \overline{\Omega} + u = u0 on \Gamma_D + \partial_nu = g1 on \Gamma_N + \partial_nu +- i v = g2 on \Gamma_R + + Attributes: + verbosity: Verbosity level. + BCManager: Boundary condition manager. + V: Real FE space. + u: Generic trial functions for variational form evaluation. + v: Generic test functions for variational form evaluation. + As: Scipy sparse array representation (in CSC format) of As. + bs: Numpy array representation of bs. + cs: Numpy array representation of cs. + energyNormMatrix: Scipy sparse matrix representing inner product over + V. + energyNormDualMatrix: Scipy sparse matrix representing dual inner + product between Riesz representers V-V. + degree_threshold: Threshold for ufl expression interpolation degree. + signR: Sign in ABC. + omega: Value of omega. + diffusivity: Value of a. + forcingTerm: Value of f. + DirichletDatum: Value of u0. + NeumannDatum: Value of g1. + RobinDatumG: Value of g2. + DirichletBoundary: Function handle to \Gamma_D. + NeumannBoundary: Function handle to \Gamma_N. + RobinBoundary: Function handle to \Gamma_R. + ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). + dsToBeSet: Whether ds needs to be set. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.nAs = 2 + self._weight0 = 1. + + @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 2 * super().spacedim + + def buildEnergyNormForm(self): + """ + Build sparse matrix (in CSR format) representative of scalar product. + """ + vbMng(self, "INIT", "Assembling energy matrix.", 20) + self.energyNormMatrix = augmentedH1NormMatrix(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. + """ + vbMng(self, "INIT", "Assembling energy dual matrix.", 20) + self.energyNormDualMatrix = augmentedHminus1NormMatrix(self.V, + compressRank = self._energyDualNormCompress) + vbMng(self, "DEL", "Done assembling energy dual matrix.", 20) + + def buildA(self): + """Build terms of operator of linear system.""" + ANone = any([A is None for A in self.As]) + if not ANone: return + self.nAs = 3 + super().buildA() + self._nAs = 2 + I = eye(self.spacedim // 2) + self.As[0] = bmat([[self.As[0], self._weight0 * self.As[1]], + [None, I]], format = "csr") + self.As[1] = bmat([[(1. - self._weight0) * self.As[1], self.As[2]], + [- I, None]], format = "csr") + self.thAs.pop() + self.As.pop() + + def buildb(self): + """Build terms of operator of linear system.""" + bNone = any([b is None for b in self.bs]) + if not bNone: return + self.nbs = 1 + dim = self.spacedim // 2 + super().buildb() + self.bs[0] = pad(self.bs[0], (0, dim), "constant") + + def plot(self, u, warping = None, is_state = False, name = "u", + save = None, what = 'all', forceNewFile = True, + saveFormat = "eps", saveDPI = 100, show = True, colorMap = "jet", + fenplotArgs = {}, **figspecs): + uh = u[: self.spacedim // 2] if is_state or self.isCEye else u + return super().plot(uh, warping, is_state, name, save, what, + forceNewFile, saveFormat, saveDPI, show, + colorMap, fenplotArgs, **figspecs) + + def outParaview(self, u, warping = None, is_state = False, name = "u", + filename = "out", time = 0., what = 'all', + forceNewFile = True, folder = False, filePW = None): + if not is_state and not self.isCEye: + raise RROMPyException(("Cannot output to Paraview non-state " + "object.")) + return super().outParaview(u[: self.spacedim // 2], warping, is_state, + name, filename, time, what, forceNewFile, + folder, filePW) + + def outParaviewTimeDomain(self, u, omega, warping = None, is_state = False, + timeFinal = None, periodResolution = 20, + name = "u", filename = "out", + forceNewFile = True, folder = False): + if not is_state and not self.isCEye: + raise RROMPyException(("Cannot output to Paraview non-state " + "object.")) + return super().outParaviewTimeDomain(u[: self.spacedim // 2], omega, + warping, is_state, timeFinal, + periodResolution, name, filename, + forceNewFile, folder) diff --git a/rrompy/hfengines/linear_problem/laplace_base_problem_engine.py b/rrompy/hfengines/fenics_engines/laplace_base_problem_engine.py similarity index 100% rename from rrompy/hfengines/linear_problem/laplace_base_problem_engine.py rename to rrompy/hfengines/fenics_engines/laplace_base_problem_engine.py diff --git a/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine_damped.py b/rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine.py similarity index 51% rename from rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine_damped.py rename to rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine.py index f6208ea..06149ea 100644 --- a/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine_damped.py +++ b/rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine.py @@ -1,156 +1,285 @@ # 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 import fenics as fen -from .linear_elasticity_helmholtz_problem_engine import \ - LinearElasticityHelmholtzProblemEngine +from .linear_elasticity_problem_engine import LinearElasticityProblemEngine from rrompy.utilities.base.types import paramVal -from rrompy.solver.fenics import fenZERO, fenZEROS +from rrompy.solver.fenics import (fenZERO, fenZEROS, fenONE, fenics2Sparse, + elasticNormMatrix, elasticDualNormMatrix) from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.solver.fenics import fenics2Sparse +from rrompy.parameter import parameterMap as pMap -__all__ = ['LinearElasticityHelmholtzProblemEngineDamped'] +__all__ = ['LinearElasticityHelmholtzProblemEngine', + 'LinearElasticityHelmholtzProblemEngineDamped'] + +class LinearElasticityHelmholtzProblemEngine(LinearElasticityProblemEngine): + """ + Solver for generic linear elasticity Helmholtz problems with parametric + wavenumber. + - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) + - rho_ * mu^2 * u = f in \Omega + u = u0 on \Gamma_D + \partial_nu = g1 on \Gamma_N + \partial_nu + h u = g2 on \Gamma_R + + Attributes: + verbosity: Verbosity level. + BCManager: Boundary condition manager. + V: Real vector FE space. + u: Generic vector trial functions for variational form evaluation. + v: Generic vector test functions for variational form evaluation. + As: Scipy sparse array representation (in CSC format) of As. + bs: Numpy array representation of bs. + cs: Numpy array representation of cs. + energyNormMatrix: Scipy sparse matrix representing inner product over + V. + energyNormDualMatrix: Scipy sparse matrix representing dual inner + product between Riesz representers V-V. + degree_threshold: Threshold for ufl expression interpolation degree. + omega: Value of omega. + lambda_: Value of lambda_. + mu_: Value of mu_. + forcingTerm: Value of f. + DirichletDatum: Value of u0. + NeumannDatum: Value of g1. + RobinDatumG: Value of g2. + RobinDatumH: Value of h. + DirichletBoundary: Function handle to \Gamma_D. + NeumannBoundary: Function handle to \Gamma_N. + RobinBoundary: Function handle to \Gamma_R. + ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). + dsToBeSet: Whether ds needs to be set. + """ + + def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = np.inf, + verbosity : int = 10, timestamp : bool = True): + super().__init__(mu0 = mu0, degree_threshold = degree_threshold, + verbosity = verbosity, timestamp = timestamp) + self._affinePoly = True + self.nAs = 2 + self.omega = np.abs(self.mu0(0, 0)) + self.rho_ = fenONE + self.parameterMap = pMap([2.] + [1.] * (self.npar - 1)) + + @property + def rho_(self): + """Value of rho_.""" + return self._rho_ + @rho_.setter + def rho_(self, rho_): + self.resetAs() + if not isinstance(rho_, (list, tuple,)): + rho_ = [rho_, fenZERO] + self._rho_ = rho_ + + def buildEnergyNormForm(self): # energy + omega norm + """ + Build sparse matrix (in CSR format) representative of scalar product. + """ + vbMng(self, "INIT", "Assembling energy matrix.", 20) + self.energyNormMatrix = elasticNormMatrix( + self.V, self.lambda_[0], self.mu_[0], + np.abs(self.omega)**2 * self.rho_[0]) + vbMng(self, "DEL", "Done assembling energy matrix.", 20) + + def buildEnergyNormDualForm(self): + """ + Build sparse matrix (in CSR format) representative of dual scalar + product without duality. + """ + vbMng(self, "INIT", "Assembling energy dual matrix.", 20) + self.energyNormDualMatrix = elasticDualNormMatrix( + self.V, self.lambda_[0], self.mu_[0], + np.abs(self.omega)**2 * self.rho_[0], + compressRank = self._energyDualNormCompress) + vbMng(self, "DEL", "Done assembling energy dual matrix.", 20) + + 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.autoSetDS() + vbMng(self, "INIT", "Assembling operator term A0.", 20) + DirichletBC0 = fen.DirichletBC(self.V, + fenZEROS(self.V.mesh().topology().dim()), + self.DirichletBoundary) + lambda_Re, lambda_Im = self.lambda_ + mu_Re, mu_Im = self.mu_ + hRe, hIm = self.RobinDatumH + termNames = ["lambda_", "mu_", "RobinDatumH"] + parsRe = self.iterReduceQuadratureDegree(zip( + [lambda_Re, mu_Re, hRe], + [x + "Real" for x in termNames])) + parsIm = self.iterReduceQuadratureDegree(zip( + [lambda_Im, mu_Re, hIm], + [x + "Imag" for x in termNames])) + epsilon = lambda u: 0.5 * (fen.grad(u) + fen.nabla_grad(u)) + sigma = lambda u, l_, m_: ( + l_ * fen.div(u) * fen.Identity(u.geometric_dimension()) + + 2. * m_ * epsilon(u)) + a0Re = (fen.inner(sigma(self.u, lambda_Re, mu_Re), + epsilon(self.v)) * fen.dx + + hRe * fen.inner(self.u, self.v) * self.ds(1)) + a0Im = (fen.inner(sigma(self.u, lambda_Im, mu_Im), + epsilon(self.v)) * fen.dx + + hIm * fen.inner(self.u, self.v) * self.ds(1)) + self.As[0] = (fenics2Sparse(a0Re, parsRe, DirichletBC0, 1) + + 1.j * fenics2Sparse(a0Im, parsIm, DirichletBC0, 0)) + vbMng(self, "DEL", "Done assembling operator term.", 20) + if self.As[1] is None: + vbMng(self, "INIT", "Assembling operator term A1.", 20) + DirichletBC0 = fen.DirichletBC(self.V, + fenZEROS(self.V.mesh().topology().dim()), + self.DirichletBoundary) + rho_Re, rho_Im = self.rho_ + parsRe = self.iterReduceQuadratureDegree(zip([rho_Re], + ["rho_Real"])) + parsIm = self.iterReduceQuadratureDegree(zip([rho_Im], + ["rho_Imag"])) + a1Re = - rho_Re * fen.inner(self.u, self.v) * fen.dx + a1Im = - rho_Im * fen.inner(self.u, self.v) * fen.dx + self.As[1] = (fenics2Sparse(a1Re, parsRe, DirichletBC0, 0) + + 1.j * fenics2Sparse(a1Im, parsIm, DirichletBC0, 0)) + vbMng(self, "DEL", "Done assembling operator term.", 20) class LinearElasticityHelmholtzProblemEngineDamped( LinearElasticityHelmholtzProblemEngine): """ Solver for generic linear elasticity Helmholtz problems with parametric wavenumber. - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) - rho_ * (mu^2 - i * eta * mu) * u = f in \Omega u = u0 on \Gamma_D \partial_nu = g1 on \Gamma_N \partial_nu + h u = g2 on \Gamma_R Attributes: verbosity: Verbosity level. BCManager: Boundary condition manager. V: Real vector FE space. u: Generic vector trial functions for variational form evaluation. v: Generic vector test functions for variational form evaluation. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. cs: Numpy array representation of cs. energyNormMatrix: Scipy sparse matrix representing inner product over V. energyNormDualMatrix: Scipy sparse matrix representing dual inner product between Riesz representers V-V. degree_threshold: Threshold for ufl expression interpolation degree. omega: Value of omega. lambda_: Value of lambda_. mu_: Value of mu_. eta: Value of eta. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. RobinDatumG: Value of g2. RobinDatumH: Value of h. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). dsToBeSet: Whether ds needs to be set. """ - def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = np.inf, - verbosity : int = 10, timestamp : bool = True): - super().__init__(mu0 = [mu0], degree_threshold = degree_threshold, - verbosity = verbosity, timestamp = timestamp) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self._affinePoly = True self.nAs = 3 self.eta = fenZERO - self.rescalingExp = [1.] + self.rescalingExp[1 :] + self.parameterMap = pMap(1., self.npar) @property def eta(self): """Value of eta.""" return self._eta @eta.setter def eta(self, eta): self.resetAs() if not isinstance(eta, (list, tuple,)): eta = [eta, fenZERO] self._eta = eta 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.autoSetDS() vbMng(self, "INIT", "Assembling operator term A0.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) lambda_Re, lambda_Im = self.lambda_ mu_Re, mu_Im = self.mu_ hRe, hIm = self.RobinDatumH termNames = ["lambda_", "mu_", "RobinDatumH"] parsRe = self.iterReduceQuadratureDegree(zip( [lambda_Re, mu_Re, hRe], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip( [lambda_Im, mu_Re, hIm], [x + "Imag" for x in termNames])) epsilon = lambda u: 0.5 * (fen.grad(u) + fen.nabla_grad(u)) sigma = lambda u, l_, m_: ( l_ * fen.div(u) * fen.Identity(u.geometric_dimension()) + 2. * m_ * epsilon(u)) a0Re = (fen.inner(sigma(self.u, lambda_Re, mu_Re), epsilon(self.v)) * fen.dx + hRe * fen.inner(self.u, self.v) * self.ds(1)) a0Im = (fen.inner(sigma(self.u, lambda_Im, mu_Im), epsilon(self.v)) * fen.dx + hIm * fen.inner(self.u, self.v) * self.ds(1)) self.As[0] = (fenics2Sparse(a0Re, parsRe, DirichletBC0, 1) + 1.j * fenics2Sparse(a0Im, parsIm, DirichletBC0, 0)) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[1] is None: vbMng(self, "INIT", "Assembling operator term A1.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) rho_Re, rho_Im = self.rho_ eta_Re, eta_Im = self.eta termNames = ["rho_", "eta"] parsRe = self.iterReduceQuadratureDegree(zip([rho_Re, eta_Re], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip([rho_Im, eta_Im], [x + "Imag" for x in termNames])) a1Re = - ((eta_Re * rho_Im + eta_Im * rho_Re) * fen.inner(self.u, self.v)) * fen.dx a1Im = ((eta_Re * rho_Re - eta_Im * rho_Im) * fen.inner(self.u, self.v)) * fen.dx self.As[1] = (fenics2Sparse(a1Re, parsRe, DirichletBC0, 0) + 1.j * fenics2Sparse(a1Im, parsIm, DirichletBC0, 0)) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[2] is None: vbMng(self, "INIT", "Assembling operator term A2.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) rho_Re, rho_Im = self.rho_ parsRe = self.iterReduceQuadratureDegree(zip([rho_Re], ["rho_Real"])) parsIm = self.iterReduceQuadratureDegree(zip([rho_Im], ["rho_Imag"])) a2Re = - rho_Re * fen.inner(self.u, self.v) * fen.dx a2Im = - rho_Im * fen.inner(self.u, self.v) * fen.dx self.As[2] = (fenics2Sparse(a2Re, parsRe, DirichletBC0, 0) + 1.j * fenics2Sparse(a2Im, parsIm, DirichletBC0, 0)) vbMng(self, "DEL", "Done assembling operator term.", 20) diff --git a/rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine_augmented.py b/rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine_augmented.py new file mode 100755 index 0000000..75ae923 --- /dev/null +++ b/rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine_augmented.py @@ -0,0 +1,280 @@ +# 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.sparse import eye, bmat, block_diag +from .linear_elasticity_helmholtz_problem_engine import ( + LinearElasticityHelmholtzProblemEngine, + LinearElasticityHelmholtzProblemEngineDamped) +from rrompy.solver.fenics import (augmentedElasticNormMatrix, + augmentedElasticDualNormMatrix) +from rrompy.utilities.base import verbosityManager as vbMng +from rrompy.utilities.exception_manager import RROMPyException +from rrompy.parameter import parameterMap as pMap + +__all__ = ['LinearElasticityHelmholtzProblemEngineAugmented', + 'LinearElasticityHelmholtzProblemEngineDampedAugmented'] + +class LinearElasticityHelmholtzProblemEngineAugmented( + LinearElasticityHelmholtzProblemEngine): + """ + Solver for generic linear elasticity Helmholtz problems with parametric + wavenumber. + - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) + - rho_ * mu * v = f in \Omega + mu * u = v in \overline{\Omega} + u = u0 on \Gamma_D + \partial_nu = g1 on \Gamma_N + \partial_nu + h u = g2 on \Gamma_R + + Attributes: + verbosity: Verbosity level. + BCManager: Boundary condition manager. + V: Real vector FE space. + u: Generic vector trial functions for variational form evaluation. + v: Generic vector test functions for variational form evaluation. + As: Scipy sparse array representation (in CSC format) of As. + bs: Numpy array representation of bs. + cs: Numpy array representation of cs. + energyNormMatrix: Scipy sparse matrix representing inner product over + V. + energyNormDualMatrix: Scipy sparse matrix representing dual inner + product between Riesz representers V-V. + degree_threshold: Threshold for ufl expression interpolation degree. + omega: Value of omega. + lambda_: Value of lambda_. + mu_: Value of mu_. + forcingTerm: Value of f. + DirichletDatum: Value of u0. + NeumannDatum: Value of g1. + RobinDatumG: Value of g2. + RobinDatumH: Value of h. + DirichletBoundary: Function handle to \Gamma_D. + NeumannBoundary: Function handle to \Gamma_N. + RobinBoundary: Function handle to \Gamma_R. + ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). + dsToBeSet: Whether ds needs to be set. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.parameterMap = pMap(1., self.npar) + + @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 2 * super().spacedim + + def buildEnergyNormForm(self): + """ + Build sparse matrix (in CSR format) representative of scalar product. + """ + vbMng(self, "INIT", "Assembling energy matrix.", 20) + self.energyNormMatrix = augmentedElasticNormMatrix(self.V, + self.lambda_[0], self.mu_[0]) + vbMng(self, "DEL", "Done assembling energy matrix.", 20) + + def buildEnergyNormDualForm(self): + """ + Build sparse matrix (in CSR format) representative of dual scalar + product without duality. + """ + vbMng(self, "INIT", "Assembling energy dual matrix.", 20) + self.energyNormDualMatrix = augmentedElasticDualNormMatrix( + self.V, self.lambda_[0], self.mu_[0], + compressRank = self._energyDualNormCompress) + vbMng(self, "DEL", "Done assembling energy dual matrix.", 20) + + def buildA(self): + """Build terms of operator of linear system.""" + ANone = any([A is None for A in self.As]) + if not ANone: return + self.nAs = 2 + super().buildA() + I = eye(self.spacedim // 2) + self.As[0] = block_diag((self.As[0], I), format = "csr") + self.As[1] = bmat([[None, self.As[1]], [- I, None]], format = "csr") + + def buildb(self): + """Build terms of operator of linear system.""" + bNone = any([b is None for b in self.bs]) + if not bNone: return + self.nbs = 1 + dim = self.spacedim // 2 + super().buildb() + self.bs[0] = np.pad(self.bs[0], (0, dim), "constant") + + def plot(self, u, warping = None, is_state = False, name = "u", + save = None, what = 'all', forceNewFile = True, + saveFormat = "eps", saveDPI = 100, show = True, colorMap = "jet", + fenplotArgs = {}, **figspecs): + uh = u[: self.spacedim // 2] if is_state or self.isCEye else u + return super().plot(uh, warping, is_state, name, save, what, + forceNewFile, saveFormat, saveDPI, show, + colorMap, fenplotArgs, **figspecs) + + def outParaview(self, u, warping = None, is_state = False, name = "u", + filename = "out", time = 0., what = 'all', + forceNewFile = True, folder = False, filePW = None): + if not is_state and not self.isCEye: + raise RROMPyException(("Cannot output to Paraview non-state " + "object.")) + return super().outParaview(u[: self.spacedim // 2], warping, is_state, + name, filename, time, what, forceNewFile, + folder, filePW) + + def outParaviewTimeDomain(self, u, omega, warping = None, is_state = False, + timeFinal = None, periodResolution = 20, + name = "u", filename = "out", + forceNewFile = True, folder = False): + if not is_state and not self.isCEye: + raise RROMPyException(("Cannot output to Paraview non-state " + "object.")) + return super().outParaviewTimeDomain(u[: self.spacedim // 2], omega, + warping, is_state, timeFinal, + periodResolution, name, filename, + forceNewFile, folder) + +class LinearElasticityHelmholtzProblemEngineDampedAugmented( + LinearElasticityHelmholtzProblemEngineDamped): + """ + Solver for generic linear elasticity Helmholtz problems with parametric + wavenumber. + - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) + - rho_ * (mu - i * eta) * v = f in \Omega + mu * u = v in \overline{\Omega} + u = u0 on \Gamma_D + \partial_nu = g1 on \Gamma_N + \partial_nu + h u = g2 on \Gamma_R + + Attributes: + verbosity: Verbosity level. + BCManager: Boundary condition manager. + V: Real vector FE space. + u: Generic vector trial functions for variational form evaluation. + v: Generic vector test functions for variational form evaluation. + As: Scipy sparse array representation (in CSC format) of As. + bs: Numpy array representation of bs. + cs: Numpy array representation of cs. + energyNormMatrix: Scipy sparse matrix representing inner product over + V. + energyNormDualMatrix: Scipy sparse matrix representing dual inner + product between Riesz representers V-V. + degree_threshold: Threshold for ufl expression interpolation degree. + omega: Value of omega. + lambda_: Value of lambda_. + mu_: Value of mu_. + eta: Value of eta. + forcingTerm: Value of f. + DirichletDatum: Value of u0. + NeumannDatum: Value of g1. + RobinDatumG: Value of g2. + RobinDatumH: Value of h. + DirichletBoundary: Function handle to \Gamma_D. + NeumannBoundary: Function handle to \Gamma_N. + RobinBoundary: Function handle to \Gamma_R. + ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). + dsToBeSet: Whether ds needs to be set. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.nAs = 2 + self._weight0 = 1. + + @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 2 * super().spacedim + + def buildEnergyNormForm(self): + """ + Build sparse matrix (in CSR format) representative of scalar product. + """ + vbMng(self, "INIT", "Assembling energy matrix.", 20) + self.energyNormMatrix = augmentedElasticNormMatrix(self.V, + self.lambda_[0], self.mu_[0]) + vbMng(self, "DEL", "Done assembling energy matrix.", 20) + + def buildEnergyNormDualForm(self): + """ + Build sparse matrix (in CSR format) representative of dual scalar + product without duality. + """ + vbMng(self, "INIT", "Assembling energy dual matrix.", 20) + self.energyNormDualMatrix = augmentedElasticDualNormMatrix( + self.V, self.lambda_[0], self.mu_[0], + compressRank = self._energyDualNormCompress) + vbMng(self, "DEL", "Done assembling energy dual matrix.", 20) + + def buildA(self): + """Build terms of operator of linear system.""" + ANone = any([A is None for A in self.As]) + if not ANone: return + self.nAs = 3 + super().buildA() + self._nAs = 2 + I = eye(self.spacedim // 2) + self.As[0] = bmat([[self.As[0], self._weight0 * self.As[1]], + [None, I]], format = "csr") + self.As[1] = bmat([[(1. - self._weight0) * self.As[1], self.As[2]], + [- I, None]], format = "csr") + self.thAs.pop() + self.As.pop() + + def buildb(self): + """Build terms of operator of linear system.""" + bNone = any([b is None for b in self.bs]) + if not bNone: return + self.nbs = 1 + dim = self.spacedim // 2 + super().buildb() + self.bs[0] = np.pad(self.bs[0], (0, dim), "constant") + + def plot(self, u, warping = None, is_state = False, name = "u", + save = None, what = 'all', forceNewFile = True, + saveFormat = "eps", saveDPI = 100, show = True, colorMap = "jet", + fenplotArgs = {}, **figspecs): + uh = u[: self.spacedim // 2] if is_state or self.isCEye else u + return super().plot(uh, warping, is_state, name, save, what, + forceNewFile, saveFormat, saveDPI, show, + colorMap, fenplotArgs, **figspecs) + + def outParaview(self, u, warping = None, is_state = False, name = "u", + filename = "out", time = 0., what = 'all', + forceNewFile = True, folder = False, filePW = None): + if not is_state and not self.isCEye: + raise RROMPyException(("Cannot output to Paraview non-state " + "object.")) + return super().outParaview(u[: self.spacedim // 2], warping, is_state, + name, filename, time, what, forceNewFile, + folder, filePW) + + def outParaviewTimeDomain(self, u, omega, warping = None, is_state = False, + timeFinal = None, periodResolution = 20, + name = "u", filename = "out", + forceNewFile = True, folder = False): + if not is_state and not self.isCEye: + raise RROMPyException(("Cannot output to Paraview non-state " + "object.")) + return super().outParaviewTimeDomain(u[: self.spacedim // 2], omega, + warping, is_state, timeFinal, + periodResolution, name, filename, + forceNewFile, folder) diff --git a/rrompy/hfengines/vector_linear_problem/linear_elasticity_problem_engine.py b/rrompy/hfengines/fenics_engines/linear_elasticity_problem_engine.py similarity index 100% rename from rrompy/hfengines/vector_linear_problem/linear_elasticity_problem_engine.py rename to rrompy/hfengines/fenics_engines/linear_elasticity_problem_engine.py diff --git a/rrompy/hfengines/linear_problem/helmholtz_problem_engine.py b/rrompy/hfengines/linear_problem/helmholtz_problem_engine.py deleted file mode 100644 index 881e4eb..0000000 --- a/rrompy/hfengines/linear_problem/helmholtz_problem_engine.py +++ /dev/null @@ -1,124 +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 numpy as np -import fenics as fen -from .laplace_base_problem_engine import LaplaceBaseProblemEngine -from rrompy.utilities.base.types import paramVal -from rrompy.solver.fenics import fenZERO, fenONE -from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.solver.fenics import fenics2Sparse - -__all__ = ['HelmholtzProblemEngine'] - -class HelmholtzProblemEngine(LaplaceBaseProblemEngine): - """ - Solver for generic Helmholtz problems with parametric wavenumber. - - \nabla \cdot (a \nabla u) - omega^2 * n**2 * u = f in \Omega - u = u0 on \Gamma_D - \partial_nu = g1 on \Gamma_N - \partial_nu + h u = g2 on \Gamma_R - - Attributes: - verbosity: Verbosity level. - BCManager: Boundary condition manager. - V: Real FE space. - u: Generic trial functions for variational form evaluation. - v: Generic test functions for variational form evaluation. - As: Scipy sparse array representation (in CSC format) of As. - bs: Numpy array representation of bs. - cs: Numpy array representation of cs. - energyNormMatrix: Scipy sparse matrix representing inner product over - V. - energyNormDualMatrix: Scipy sparse matrix representing dual inner - product between Riesz representers V-V. - degree_threshold: Threshold for ufl expression interpolation degree. - omega: Value of omega. - diffusivity: Value of a. - refractionIndex: Value of n. - forcingTerm: Value of f. - DirichletDatum: Value of u0. - NeumannDatum: Value of g1. - RobinDatumG: Value of g2. - RobinDatumH: Value of h. - DirichletBoundary: Function handle to \Gamma_D. - NeumannBoundary: Function handle to \Gamma_N. - RobinBoundary: Function handle to \Gamma_R. - ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). - dsToBeSet: Whether ds needs to be set. - """ - - def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = np.inf, - verbosity : int = 10, timestamp : bool = True): - super().__init__(mu0 = mu0, degree_threshold = degree_threshold, - verbosity = verbosity, timestamp = timestamp) - self._affinePoly = True - self.nAs = 2 - self.rescalingExp = [2.] + self.rescalingExp[1 :] - self.refractionIndex = fenONE - - @property - def refractionIndex(self): - """Value of n.""" - return self._refractionIndex - @refractionIndex.setter - def refractionIndex(self, refractionIndex): - self.resetAs() - if not isinstance(refractionIndex, (list, tuple,)): - refractionIndex = [refractionIndex, fenZERO] - self._refractionIndex = refractionIndex - - 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.autoSetDS() - vbMng(self, "INIT", "Assembling operator term A0.", 20) - DirichletBC0 = fen.DirichletBC(self.V, fenZERO, - self.DirichletBoundary) - aRe, aIm = self.diffusivity - hRe, hIm = self.RobinDatumH - termNames = ["diffusivity", "RobinDatumH"] - parsRe = self.iterReduceQuadratureDegree(zip( - [aRe, hRe], - [x + "Real" for x in termNames])) - parsIm = self.iterReduceQuadratureDegree(zip( - [aIm, hIm], - [x + "Imag" for x in termNames])) - a0Re = (aRe * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx - + hRe * fen.dot(self.u, self.v) * self.ds(1)) - a0Im = (aIm * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx - + hIm * fen.dot(self.u, self.v) * self.ds(1)) - self.As[0] = (fenics2Sparse(a0Re, parsRe, DirichletBC0, 1) - + 1.j * fenics2Sparse(a0Im, parsIm, DirichletBC0, 0)) - vbMng(self, "DEL", "Done assembling operator term.", 20) - if self.As[1] is None: - vbMng(self, "INIT", "Assembling operator term A1.", 20) - DirichletBC0 = fen.DirichletBC(self.V, fenZERO, - self.DirichletBoundary) - nRe, nIm = self.refractionIndex - n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm - parsRe = self.iterReduceQuadratureDegree(zip([n2Re], - ["refractionIndexSquaredReal"])) - parsIm = self.iterReduceQuadratureDegree(zip([n2Im], - ["refractionIndexSquaredImag"])) - a1Re = - n2Re * fen.dot(self.u, self.v) * fen.dx - a1Im = - n2Im * fen.dot(self.u, self.v) * fen.dx - self.As[1] = (fenics2Sparse(a1Re, parsRe, DirichletBC0, 0) - + 1.j * fenics2Sparse(a1Im, parsIm, DirichletBC0, 0)) - vbMng(self, "DEL", "Done assembling operator term.", 20) diff --git a/rrompy/hfengines/linear_problem/__init__.py b/rrompy/hfengines/scipy_engines/__init__.py similarity index 69% rename from rrompy/hfengines/linear_problem/__init__.py rename to rrompy/hfengines/scipy_engines/__init__.py index 6dab090..b91e816 100644 --- a/rrompy/hfengines/linear_problem/__init__.py +++ b/rrompy/hfengines/scipy_engines/__init__.py @@ -1,30 +1,30 @@ # 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 .laplace_base_problem_engine import LaplaceBaseProblemEngine -from .helmholtz_problem_engine import HelmholtzProblemEngine -from .scattering_problem_engine import ScatteringProblemEngine +from .eigenproblem_engine import EigenproblemEngine, TensorizedEigenproblemEngine +from .oscillator_problem_engine import OscillatorProblemEngine, AugmentedOscillatorProblemEngine __all__ = [ - 'LaplaceBaseProblemEngine', - 'HelmholtzProblemEngine', - 'ScatteringProblemEngine' + 'EigenproblemEngine', + 'TensorizedEigenproblemEngine', + 'OscillatorProblemEngine', + 'AugmentedOscillatorProblemEngine' ] diff --git a/rrompy/hfengines/scipy_engines/eigenproblem_engine.py b/rrompy/hfengines/scipy_engines/eigenproblem_engine.py new file mode 100644 index 0000000..a3980c2 --- /dev/null +++ b/rrompy/hfengines/scipy_engines/eigenproblem_engine.py @@ -0,0 +1,70 @@ +# 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.hfengines.base.linear_affine_engine import LinearAffineEngine +from rrompy.hfengines.base.scipy_engine_base import (ScipyEngineBase, + ScipyEngineBaseTensorized) +from rrompy.utilities.base.types import List, Np1D, Np2D + +__all__ = ['EigenproblemEngine', 'TensorizedEigenproblemEngine'] + +class EigenproblemEngine(LinearAffineEngine, ScipyEngineBase): + """ + Solver for generic eigenvalue-like problems. + (A_0 + \mu_1 A_1 + ... + \mu_N A_N) u(\mu) = f + """ + + def __init__(self, As:List[Np2D], f : Np1D = 420, verbosity : int = 10, + timestamp : bool = True): + super().__init__(verbosity = verbosity, timestamp = timestamp) + self._affinePoly = True + self.npar, self.nAs, self.nbs = len(As) - 1, len(As), 1 + self.As = As + if np.any([isinstance(A, (np.ndarray,)) for A in As]): + for j in range(self.nAs): + if not isinstance(self.As[j], (np.ndarray,)): + self.As[j] = self.As[j].todense() + self.setSolver("SOLVE") + if isinstance(f, (Number,)): + np.random.seed(f) + f = np.random.randn(self.As[0].shape[0]) + f /= np.linalg.norm(f) + else: + f = np.array(f).flatten() + self.bs = [f] + +class TensorizedEigenproblemEngine(EigenproblemEngine, + ScipyEngineBaseTensorized): + """ + Solver for generic eigenvalue-like problems. + (A_0 + \mu_1 A_1 + ... + \mu_N A_N) U(\mu) = U + """ + + def __init__(self, As:List[Np2D], f : Np1D = 420, ncol : int = 1, + verbosity : int = 10, timestamp : bool = True): + if isinstance(f, (Number,)): + np.random.seed(f) + f = np.random.randn(As[0].shape[0], ncol) + f = (f / np.linalg.norm(f, axis = 0)) + else: + f = np.array(f).reshape(-1, ncol) + self.nports = f.shape[1] + super().__init__(As = As, f = f, verbosity = verbosity, + timestamp = timestamp) diff --git a/rrompy/hfengines/scipy_engines/oscillator_problem_engine.py b/rrompy/hfengines/scipy_engines/oscillator_problem_engine.py new file mode 100755 index 0000000..01ef9bd --- /dev/null +++ b/rrompy/hfengines/scipy_engines/oscillator_problem_engine.py @@ -0,0 +1,123 @@ +# 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 +import scipy.sparse as scsp +from numbers import Number +from rrompy.hfengines.base.linear_affine_engine import LinearAffineEngine +from rrompy.hfengines.base.scipy_engine_base import ScipyEngineBaseTensorized +from rrompy.utilities.base.types import List, Np1D, Np2D +from rrompy.utilities.exception_manager import RROMPyAssert + +__all__ = ['OscillatorProblemEngine', 'AugmentedOscillatorProblemEngine'] + +class OscillatorProblemEngine(LinearAffineEngine, ScipyEngineBaseTensorized): + """ + Solver for damped mass oscillator problems. + Each i represents the factor in front of mu_i, except for i=0, which is a + constant. For each i: + (mass_i)_j = mass of j-th mass + (damping_i)_jk = damping between j-th and k-th mass + (stiffness_i)_jk = stiffness between j-th and k-th mass + Others: + input_ij = stiffness between i-th input and j-th mass + output_ij = contribution of j-th mass's position to i-th output + """ + + def __init__(self, mass:List[Np1D], damping:List[Np2D], + stiffness:List[Np2D], input:Np2D, output:Np2D, + inputPosition : bool = True, verbosity : int = 10, + timestamp : bool = True): + super().__init__(verbosity = verbosity, timestamp = timestamp) + self._affinePoly = True + self.npar = len(mass) + RROMPyAssert(len(damping), self.npar, "Number of parameters") + RROMPyAssert(len(stiffness), self.npar, "Number of parameters") + input = np.array(input) + if input.ndim < 2: input = input.reshape(-1, 1) + self.nports = input.shape[1] + self.nAs, self.As, self.thAs = 3 * self.npar, [], [] + self.nbs, self.bs = 1, [input.flatten()] + self._C = output + for j in range(self.npar): + deg = [0] * (self.npar - 1) + if j != 0: deg[j - 1] = 1 + K = - stiffness[j] + Dj = - 1.j * damping[j] + Kcorr = - np.array(np.sum(K, axis = 1)).flatten() + if j == 0 and inputPosition: + Kcorr += np.array(np.sum(input, axis = 1)).flatten() + Djcorr = - np.array(np.sum(Dj, axis = 1)).flatten() + if isinstance(Dj, (np.ndarray,)): + Mm = np.diag(- mass[j]) + K = K + np.diag(Kcorr - np.diag(K)) + Dj = Dj + np.diag(Djcorr - np.diag(Dj)) + else: + Mm = scsp.diags(- mass[j], 0, format = Dj.format) + K = K + scsp.diags([Kcorr - K.diagonal()], [0]) + Dj = Dj + scsp.diags([Djcorr - Dj.diagonal()], [0]) + self.As += [K, Dj, Mm] + self.thAs += [self.getMonomialSingleWeight([0] + deg), + self.getMonomialSingleWeight([1] + deg), + self.getMonomialSingleWeight([2] + deg)] + if isinstance(self.As[0], (np.ndarray,)): + self.setSolver("SOLVE") + else: + self.setSolver("SPSOLVE", {"use_umfpack" : False}) + +class AugmentedOscillatorProblemEngine(OscillatorProblemEngine): + """ + Solver for damped mass oscillator problems. + Each i represents the factor in front of mu_i, except for i=0, which is a + constant. For each i: + (mass_i)_j = mass of j-th mass + (damping_i)_jk = damping between j-th and k-th mass + (stiffness_i)_jk = stiffness between j-th and k-th mass + Others: + input_ij = stiffness between i-th input and j-th mass + output_ij = contribution of j-th mass's position to i-th output + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + N, self._nAs = self.As[0].shape[1], 2 * self.npar + self.bs = [np.pad(self.bs[0].reshape(-1, self.nports), + [(N, 0), (0, 0)], 'constant').flatten()] + if (not isinstance(self._C, (Number,)) + and self._C.shape[1] != 2 * N): + RROMPyAssert(self._C.shape[1], N, "Shape of output") + self._C = np.pad(self._C, [(0, 0), (0, N)], + 'constant') + As, thAs = [], [] + for j in range(self.npar): + K, Dj, Mm = self.As[3 * j], self.As[3 * j + 1], self.As[3 * j + 2] + if isinstance(Dj, (np.ndarray,)): + I, Z = 1.j * np.eye(N), np.zeros((N, N)) + Deff = np.block([[Z, - I], [K, Dj]]) + Meff = np.block([[I, Z], [Z, Mm]]) + else: + I = 1.j * scsp.eye(N) + Deff = scsp.bmat([[None, - I], [K, Dj]], format = Mm.format) + Meff = scsp.block_diag([I, Mm], format = Mm.format) + As += [Deff, Meff] + thAs += [self.thAs[3 * j], self.thAs[3 * j + 1]] + self.As, self.thAs = As, thAs + if isinstance(self.As[0], (np.ndarray,)): + self.setSolver("SOLVE") + else: + self.setSolver("SPSOLVE", {"use_umfpack" : False}) diff --git a/rrompy/hfengines/vector_linear_problem/__init__.py b/rrompy/hfengines/vector_linear_problem/__init__.py deleted file mode 100644 index 689207b..0000000 --- a/rrompy/hfengines/vector_linear_problem/__init__.py +++ /dev/null @@ -1,30 +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 .linear_elasticity_problem_engine import LinearElasticityProblemEngine -from .linear_elasticity_helmholtz_problem_engine import LinearElasticityHelmholtzProblemEngine -from .linear_elasticity_helmholtz_problem_engine_damped import LinearElasticityHelmholtzProblemEngineDamped - -__all__ = [ - 'LinearElasticityProblemEngine', - 'LinearElasticityHelmholtzProblemEngine', - 'LinearElasticityHelmholtzProblemEngineDamped' - ] - - - diff --git a/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine.py b/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine.py deleted file mode 100644 index b5857ab..0000000 --- a/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine.py +++ /dev/null @@ -1,158 +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 numpy as np -import fenics as fen -from .linear_elasticity_problem_engine import LinearElasticityProblemEngine -from rrompy.utilities.base.types import paramVal -from rrompy.solver.fenics import (fenZERO, fenZEROS, fenONE, elasticNormMatrix, - elasticDualNormMatrix) -from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.solver.fenics import fenics2Sparse - -__all__ = ['LinearElasticityHelmholtzProblemEngine'] - -class LinearElasticityHelmholtzProblemEngine(LinearElasticityProblemEngine): - """ - Solver for generic linear elasticity Helmholtz problems with parametric - wavenumber. - - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) - - rho_ * mu^2 * u = f in \Omega - u = u0 on \Gamma_D - \partial_nu = g1 on \Gamma_N - \partial_nu + h u = g2 on \Gamma_R - - Attributes: - verbosity: Verbosity level. - BCManager: Boundary condition manager. - V: Real vector FE space. - u: Generic vector trial functions for variational form evaluation. - v: Generic vector test functions for variational form evaluation. - As: Scipy sparse array representation (in CSC format) of As. - bs: Numpy array representation of bs. - cs: Numpy array representation of cs. - energyNormMatrix: Scipy sparse matrix representing inner product over - V. - energyNormDualMatrix: Scipy sparse matrix representing dual inner - product between Riesz representers V-V. - degree_threshold: Threshold for ufl expression interpolation degree. - omega: Value of omega. - lambda_: Value of lambda_. - mu_: Value of mu_. - forcingTerm: Value of f. - DirichletDatum: Value of u0. - NeumannDatum: Value of g1. - RobinDatumG: Value of g2. - RobinDatumH: Value of h. - DirichletBoundary: Function handle to \Gamma_D. - NeumannBoundary: Function handle to \Gamma_N. - RobinBoundary: Function handle to \Gamma_R. - ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). - dsToBeSet: Whether ds needs to be set. - """ - - def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = np.inf, - verbosity : int = 10, timestamp : bool = True): - super().__init__(mu0 = [mu0], degree_threshold = degree_threshold, - verbosity = verbosity, timestamp = timestamp) - self._affinePoly = True - self.nAs = 2 - self.omega = np.abs(self.mu0(0, 0)) - self.rho_ = fenONE - self.rescalingExp = [2.] + self.rescalingExp[1 :] - - @property - def rho_(self): - """Value of rho_.""" - return self._rho_ - @rho_.setter - def rho_(self, rho_): - self.resetAs() - if not isinstance(rho_, (list, tuple,)): - rho_ = [rho_, fenZERO] - self._rho_ = rho_ - - def buildEnergyNormForm(self): # energy + omega norm - """ - Build sparse matrix (in CSR format) representative of scalar product. - """ - vbMng(self, "INIT", "Assembling energy matrix.", 20) - self.energyNormMatrix = elasticNormMatrix( - self.V, self.lambda_[0], self.mu_[0], - np.abs(self.omega)**2 * self.rho_[0]) - vbMng(self, "DEL", "Done assembling energy matrix.", 20) - - def buildEnergyNormDualForm(self): - """ - Build sparse matrix (in CSR format) representative of dual scalar - product without duality. - """ - vbMng(self, "INIT", "Assembling energy dual matrix.", 20) - self.energyNormDualMatrix = elasticDualNormMatrix( - self.V, self.lambda_[0], self.mu_[0], - np.abs(self.omega)**2 * self.rho_[0], - compressRank = self._energyDualNormCompress) - vbMng(self, "DEL", "Done assembling energy dual matrix.", 20) - - 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.autoSetDS() - vbMng(self, "INIT", "Assembling operator term A0.", 20) - DirichletBC0 = fen.DirichletBC(self.V, - fenZEROS(self.V.mesh().topology().dim()), - self.DirichletBoundary) - lambda_Re, lambda_Im = self.lambda_ - mu_Re, mu_Im = self.mu_ - hRe, hIm = self.RobinDatumH - termNames = ["lambda_", "mu_", "RobinDatumH"] - parsRe = self.iterReduceQuadratureDegree(zip( - [lambda_Re, mu_Re, hRe], - [x + "Real" for x in termNames])) - parsIm = self.iterReduceQuadratureDegree(zip( - [lambda_Im, mu_Re, hIm], - [x + "Imag" for x in termNames])) - epsilon = lambda u: 0.5 * (fen.grad(u) + fen.nabla_grad(u)) - sigma = lambda u, l_, m_: ( - l_ * fen.div(u) * fen.Identity(u.geometric_dimension()) - + 2. * m_ * epsilon(u)) - a0Re = (fen.inner(sigma(self.u, lambda_Re, mu_Re), - epsilon(self.v)) * fen.dx - + hRe * fen.inner(self.u, self.v) * self.ds(1)) - a0Im = (fen.inner(sigma(self.u, lambda_Im, mu_Im), - epsilon(self.v)) * fen.dx - + hIm * fen.inner(self.u, self.v) * self.ds(1)) - self.As[0] = (fenics2Sparse(a0Re, parsRe, DirichletBC0, 1) - + 1.j * fenics2Sparse(a0Im, parsIm, DirichletBC0, 0)) - vbMng(self, "DEL", "Done assembling operator term.", 20) - if self.As[1] is None: - vbMng(self, "INIT", "Assembling operator term A1.", 20) - DirichletBC0 = fen.DirichletBC(self.V, - fenZEROS(self.V.mesh().topology().dim()), - self.DirichletBoundary) - rho_Re, rho_Im = self.rho_ - parsRe = self.iterReduceQuadratureDegree(zip([rho_Re], - ["rho_Real"])) - parsIm = self.iterReduceQuadratureDegree(zip([rho_Im], - ["rho_Imag"])) - a1Re = - rho_Re * fen.inner(self.u, self.v) * fen.dx - a1Im = - rho_Im * fen.inner(self.u, self.v) * fen.dx - self.As[1] = (fenics2Sparse(a1Re, parsRe, DirichletBC0, 0) - + 1.j * fenics2Sparse(a1Im, parsIm, DirichletBC0, 0)) - vbMng(self, "DEL", "Done assembling operator term.", 20) diff --git a/rrompy/parameter/__init__.py b/rrompy/parameter/__init__.py index 55e1350..ede96f8 100644 --- a/rrompy/parameter/__init__.py +++ b/rrompy/parameter/__init__.py @@ -1,29 +1,31 @@ # 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 .parameter_list import (parameterList, emptyParameterList, checkParameter, checkParameterList) +from .parameter_map import parameterMap __all__ = [ 'parameterList', 'emptyParameterList', 'checkParameter', - 'checkParameterList' + 'checkParameterList', + 'parameterMap' ] diff --git a/rrompy/parameter/parameter_list.py b/rrompy/parameter/parameter_list.py index f8c5295..ec0ccb6 100644 --- a/rrompy/parameter/parameter_list.py +++ b/rrompy/parameter/parameter_list.py @@ -1,230 +1,234 @@ # 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): +def checkParameterList(mu, npar = None, check_if_single : bool = False, + return_data : bool = False): 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 + if npar == 0: mu.reset((1, 0), mu.dtype) + if return_data: mu = mu.data + if check_if_single: return mu, len(mu) <= 1 + return mu -def checkParameter(mu, npar = None): - muL, wasPar = checkParameterList(mu, npar) +def checkParameter(mu, npar = None, return_data : bool = False): + muL, wasPar = checkParameterList(mu, npar, True, return_data) if not wasPar: - muL, wasPar = checkParameterList([mu], npar) + muL, wasPar = checkParameterList([mu], npar, True, return_data) 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: __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,)): + if isinstance(key, (tuple, list, np.ndarray)): 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_map.py b/rrompy/parameter/parameter_map.py new file mode 100644 index 0000000..2452b88 --- /dev/null +++ b/rrompy/parameter/parameter_map.py @@ -0,0 +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 . +# + +import numpy as np +from numbers import Number +from rrompy.utilities.base.types import DictAny +from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert + +__all__ = ['parameterMap'] + +def parameterMap(pMap = 1., npar : int = None) -> DictAny: + if isinstance(pMap, (Number,)): + if npar is None: npar = 1 + pMap = [pMap] * npar + if isinstance(pMap, (tuple,)): pMap = list(pMap) + if isinstance(pMap, (dict,)): + if (("F" not in pMap.keys() and "f" not in pMap.keys()) + or ("B" not in pMap.keys() and "b" not in pMap.keys())): + raise RROMPyException("Keys missing from parameter map dict.") + parameterMap = {} + parameterMap["F"] = pMap["F"] if "F" in pMap.keys() else pMap["f"] + parameterMap["B"] = pMap["B"] if "B" in pMap.keys() else pMap["b"] + return parameterMap + if isinstance(pMap, (list,)): + if npar is not None: + RROMPyAssert(len(pMap), npar, + "Length of parameter map scaling exponent.") + parameterMap = {"F":[], "B":[]} + for e in pMap: + if np.isclose(e, 1.): + parameterMap["F"] += [('x')] + parameterMap["B"] += [('x')] + else: + parameterMap["F"] += [('x', '**', e)] + parameterMap["B"] += [('x', '**', 1. / e)] + return parameterMap + raise RROMPyException(("Parameter map not recognized. Only dict with keys " + "'F' and 'B', or list of scaling exponents are " + "allowed.")) diff --git a/rrompy/parameter/parameter_sampling/empty_sampler.py b/rrompy/parameter/parameter_sampling/empty_sampler.py index 02a52b0..af854d2 100644 --- a/rrompy/parameter/parameter_sampling/empty_sampler.py +++ b/rrompy/parameter/parameter_sampling/empty_sampler.py @@ -1,40 +1,40 @@ # 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 numpy import nan from .generic_sampler import GenericSampler -from rrompy.utilities.base.types import List, paramList +from rrompy.utilities.base.types import DictAny, paramList from rrompy.parameter import emptyParameterList __all__ = ['EmptySampler'] class EmptySampler(GenericSampler): """Placeholder generator of sample points.""" def __init__(self, lims : paramList = [[-1.], [1.]], - scalingExp : List[float] = None): - super().__init__(lims = lims, scalingExp = scalingExp) + parameterMap : DictAny = 1.): + super().__init__(lims = lims, parameterMap = parameterMap) def normalFoci(self, *args, **kwargs): return [nan, nan] def __str__(self) -> str: return self.name() def generatePoints(self, *args, **kwargs) -> paramList: return emptyParameterList() diff --git a/rrompy/parameter/parameter_sampling/generic_quadrature_sampler.py b/rrompy/parameter/parameter_sampling/generic_quadrature_sampler.py index f0e9356..e9f0f96 100644 --- a/rrompy/parameter/parameter_sampling/generic_quadrature_sampler.py +++ b/rrompy/parameter/parameter_sampling/generic_quadrature_sampler.py @@ -1,53 +1,49 @@ # 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 rrompy.utilities.base.types import List, paramList +from rrompy.utilities.base.types import DictAny, paramList from rrompy.utilities.exception_manager import RROMPyException _allowedQuadratureKinds = ["UNIFORM", "CHEBYSHEV", "EXTENDEDCHEBYSHEV", "GAUSSLEGENDRE", "EXTENDEDGAUSSLEGENDRE"] __all__ = ['GenericQuadratureSampler'] class GenericQuadratureSampler(GenericSampler): """Generator of quadrature sample points.""" def __init__(self, lims:paramList, kind : str = "UNIFORM", - scalingExp : List[float] = None): - super().__init__(lims = lims, scalingExp = scalingExp) + parameterMap : DictAny = 1.): + super().__init__(lims = lims, parameterMap = parameterMap) 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/generic_random_sampler.py b/rrompy/parameter/parameter_sampling/generic_random_sampler.py index 104b1d6..8341c46 100644 --- a/rrompy/parameter/parameter_sampling/generic_random_sampler.py +++ b/rrompy/parameter/parameter_sampling/generic_random_sampler.py @@ -1,51 +1,78 @@ # 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 .generic_sampler import GenericSampler -from rrompy.utilities.base.types import List, paramList +from rrompy.utilities.base.types import List, DictAny, paramList from rrompy.utilities.exception_manager import RROMPyException +from rrompy.parameter.parameter_list import emptyParameterList _allowedRandomKinds = ["UNIFORM", "HALTON", "SOBOL"] __all__ = ['GenericRandomSampler'] class GenericRandomSampler(GenericSampler): """Generator of random sample points.""" def __init__(self, lims:paramList, kind : str = "UNIFORM", - scalingExp : List[float] = None, seed : int = 42): - super().__init__(lims = lims, scalingExp = scalingExp) + parameterMap : DictAny = 1., refinementFactor : float = 1., + seed : int = 42): + super().__init__(lims = lims, parameterMap = parameterMap) self._allowedKinds = _allowedRandomKinds self.kind = kind + self.refinementFactor = refinementFactor self.seed = seed - + self.reset() + def __str__(self) -> str: return "{}_{}".format(super().__str__(), self.kind) - def __repr__(self) -> str: - return self.__str__() + " at " + hex(id(self)) + @property + def npoints(self): + """Number of points.""" + return len(self.points) @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() + + def reset(self): + self.points = emptyParameterList() + + def generatePoints(self, n:int, reorder : bool = True) -> paramList: + """Array of quadrature points.""" + if self.kind == "UNIFORM": + np.random.seed(self.seed) + else: + self.seedLoc = self.seed + self.reset() + rF, self.refinementFactor = self.refinementFactor, 1. + _ = self.refine([None] * n) + self.refinementFactor = rF + return self.points + + @abstractmethod + def refine(self, active : List[int] = None) -> List[int]: + pass diff --git a/rrompy/parameter/parameter_sampling/generic_sampler.py b/rrompy/parameter/parameter_sampling/generic_sampler.py index 1b9b47f..3f68250 100644 --- a/rrompy/parameter/parameter_sampling/generic_sampler.py +++ b/rrompy/parameter/parameter_sampling/generic_sampler.py @@ -1,96 +1,94 @@ # 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 abc import abstractmethod -from rrompy.utilities.base.types import List, paramList -from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert -from rrompy.parameter import checkParameterList +from rrompy.utilities.base.types import List, DictAny, paramList +from rrompy.utilities.expression import expressionEvaluator +from rrompy.utilities.exception_manager import RROMPyException +from rrompy.parameter import checkParameterList, parameterMap as pMap __all__ = ['GenericSampler'] class GenericSampler: """ABSTRACT. Generic generator of sample points.""" - def __init__(self, lims:paramList, scalingExp : List[float] = None): + def __init__(self, lims:paramList, parameterMap : DictAny = 1.): self.lims = lims - self.scalingExp = scalingExp + self.parameterMap = pMap(parameterMap, self.npar) def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return "{}[{}_{}]".format(self.name(), self.lims[0], self.lims[1]) def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) def __eq__(self, other) -> bool: if (not hasattr(other, "__dict__") or self.__dict__.keys() != other.__dict__.keys()): return False for key in self.__dict__: val = self.__dict__[key] if isinstance(val, (np.ndarray,)): if not np.allclose(val, other.__dict__[key]): return False else: if val != other.__dict__[key]: return False return True @property def npar(self): """Number of parameters.""" return self._lims.shape[1] def normalFoci(self, d : int = 0): return [-1., 1.] def groundPotential(self, d : int = 0): fp = self.normalFoci(d)[1] fpa = np.abs(fp) if np.isclose(fpa, 0.) or np.isclose(fpa, 1.): return 1. return (1. + np.abs(1. - fp ** 2.) ** .5) / fpa @property def lims(self): """Value of lims.""" return self._lims @lims.setter def lims(self, lims): - lims = checkParameterList(lims)[0] + lims = checkParameterList(lims) if len(lims) != 2: raise RROMPyException("2 limits must be specified.") self._lims = lims - @property - def scalingExp(self): - """Value of scalingExp.""" - return self._scalingExp - @scalingExp.setter - def scalingExp(self, scalingExp): - if scalingExp is None: - scalingExp = [1.] * self.npar - if not hasattr(scalingExp, "__len__"): scalingExp = [scalingExp] - RROMPyAssert(self.npar, len(scalingExp), "Number of scaling terms") - self._scalingExp = np.array(scalingExp) + def mapParameterList(self, mu:paramList, direct : str = "F", + idx : List[int] = None) -> paramList: + if idx is None: idx = np.arange(self.npar) + muMapped = checkParameterList(mu, len(idx)) + for j, d in enumerate(idx): + muMapped.data[:, j] = expressionEvaluator( + self.parameterMap[direct][d], + muMapped(j)).flatten() + return muMapped @abstractmethod def generatePoints(self, n:int, reorder : bool = True) -> paramList: """Array of points.""" pass - diff --git a/rrompy/parameter/parameter_sampling/manual_sampler.py b/rrompy/parameter/parameter_sampling/manual_sampler.py index 2d9baad..a00c0f5 100644 --- a/rrompy/parameter/parameter_sampling/manual_sampler.py +++ b/rrompy/parameter/parameter_sampling/manual_sampler.py @@ -1,65 +1,61 @@ # 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 .generic_sampler import GenericSampler -from rrompy.utilities.base.types import List, paramList +from rrompy.utilities.base.types import List, DictAny, 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, + parameterMap : DictAny = 1., normalFoci : List[np.complex] = [-1., 1.]): - super().__init__(lims = lims, scalingExp = scalingExp) + super().__init__(lims = lims, parameterMap = parameterMap) 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] + points = checkParameterList(points, self.npar) 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] + x = checkParameterList(pts[list(range(n))], self.npar) return x - diff --git a/rrompy/parameter/parameter_sampling/segment/quadrature_sampler.py b/rrompy/parameter/parameter_sampling/segment/quadrature_sampler.py index 043ad36..c167860 100644 --- a/rrompy/parameter/parameter_sampling/segment/quadrature_sampler.py +++ b/rrompy/parameter/parameter_sampling/segment/quadrature_sampler.py @@ -1,52 +1,48 @@ # 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.parameter.parameter_sampling.generic_quadrature_sampler import ( GenericQuadratureSampler) from rrompy.utilities.base.types import paramList from rrompy.utilities.numerical import (lowDiscrepancy, kroneckerer, quadraturePointsGenerate) -from rrompy.parameter import checkParameterList __all__ = ['QuadratureSampler'] class QuadratureSampler(GenericQuadratureSampler): """Generator of quadrature sample points.""" def generatePoints(self, n:int, reorder : bool = True) -> paramList: """Array of sample points.""" n1d = int(np.ceil(n ** (1. / self.npar))) nleft, nright = 1, n1d ** self.npar xmat = np.empty((nright, self.npar), dtype = self.lims.dtype) + limsE = self.mapParameterList(self.lims) for d in range(self.npar): nright //= n1d - a = self.lims(0, d) ** self.scalingExp[d] - b = self.lims(1, d) ** self.scalingExp[d] + a, b = limsE(d) c, r = (a + b) / 2., (a - b) / 2. xd = c + r * quadraturePointsGenerate(n1d, self.kind) - xd **= 1. / self.scalingExp[d] xmat[:, d] = kroneckerer(xd, nleft, nright) nleft *= n1d nright = n1d ** self.npar if nright > 1 and reorder: fejerOrdering = [nright - 1] + lowDiscrepancy(nright - 1) xmat = xmat[fejerOrdering, :] - x = checkParameterList(xmat, self.npar)[0] - return x - + return self.mapParameterList(xmat, "B") diff --git a/rrompy/parameter/parameter_sampling/segment/quadrature_sampler_total.py b/rrompy/parameter/parameter_sampling/segment/quadrature_sampler_total.py index 4f5198b..bbf3fdd 100644 --- a/rrompy/parameter/parameter_sampling/segment/quadrature_sampler_total.py +++ b/rrompy/parameter/parameter_sampling/segment/quadrature_sampler_total.py @@ -1,64 +1,62 @@ # 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, factorial as fact from .quadrature_sampler import QuadratureSampler from rrompy.utilities.base.types import paramList from rrompy.utilities.numerical import lowDiscrepancy from rrompy.parameter import checkParameterList __all__ = ['QuadratureSamplerTotal'] class QuadratureSamplerTotal(QuadratureSampler): """ Generator of quadrature sample points for total degree polynomial computations. """ def generatePoints(self, n:int, reorder : bool = True) -> paramList: """Array of sample points.""" d = self.npar n1d = int((fact(d) * n) ** (1. / d)) while binom(n1d + d - 1, d) > n: n1d -= 1 x = super().generatePoints(n1d ** d, reorder = False) nTot = n1d ** d indicesBase = np.zeros(nTot, dtype = int) if reorder: idxBase = ([y + 1 for y in lowDiscrepancy(n1d - 1, inverse = True)] + [0]) else: idxBase = list(range(n1d)) linearIdxs = np.array(idxBase) nleft, nright = 1, nTot for j in range(d): nright //= n1d kronIn = np.repeat(linearIdxs, nright) indicesBase += np.tile(kronIn, nleft) nleft *= n1d keepIdxs = np.zeros(nTot, dtype = bool) keepIdxs[indicesBase < n1d] = True - xmat = x.data[keepIdxs, :] + xmat = x[keepIdxs] if reorder: nx = len(xmat) fejerOrdering = [nx - 1] + lowDiscrepancy(nx - 1, inverse = True) xmat = xmat[fejerOrdering, :] - x = checkParameterList(xmat, d)[0] - return x - + return checkParameterList(xmat, d) diff --git a/rrompy/parameter/parameter_sampling/segment/random_sampler.py b/rrompy/parameter/parameter_sampling/segment/random_sampler.py index a755c90..c1515f5 100644 --- a/rrompy/parameter/parameter_sampling/segment/random_sampler.py +++ b/rrompy/parameter/parameter_sampling/segment/random_sampler.py @@ -1,47 +1,55 @@ # 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 numbers import Number import numpy as np from rrompy.parameter.parameter_sampling.generic_random_sampler import ( GenericRandomSampler) -from rrompy.utilities.numerical import haltonGenerate, sobolGenerate -from rrompy.utilities.base.types import paramList -from rrompy.parameter import checkParameterList +from rrompy.utilities.numerical.halton import haltonGenerate +from rrompy.utilities.numerical.sobol import sobolGenerate +from rrompy.utilities.base.types import List __all__ = ['RandomSampler'] class RandomSampler(GenericRandomSampler): """Generator of (quasi-)random sample points.""" - def generatePoints(self, n:int, reorder : bool = True) -> paramList: - """Array of quadrature points.""" + def refine(self, active : List[int] = None) -> List[int]: + if active is None: + n = self.npoints + elif isinstance(active, (Number,)): + n = active + else: + n = len(active) + n = int(n * self.refinementFactor) if self.kind == "UNIFORM": - np.random.seed(self.seed) xmat = np.random.uniform(size = (n, self.npar)) elif self.kind == "HALTON": - xmat = haltonGenerate(self.npar, n, self.seed) + xmat, self.seedLoc = haltonGenerate(self.npar, n, self.seedLoc, + return_seed = True) else: - xmat = sobolGenerate(self.npar, n, self.seed) + xmat, self.seedLoc = sobolGenerate(self.npar, n, self.seedLoc, + return_seed = True) + limsE = self.mapParameterList(self.lims) for d in range(self.npar): - a = self.lims(0, d) ** self.scalingExp[d] - b = self.lims(1, d) ** self.scalingExp[d] + a, b = limsE(d) xmat[:, d] = a + (b - a) * xmat[:, d] - xmat[:, d] **= 1. / self.scalingExp[d] - x = checkParameterList(xmat, self.npar)[0] - return x - + pts = self.mapParameterList(xmat, "B") + idx = np.arange(n, dtype = int) + len(self.points) + for pj in pts: self.points.append(pj) + return list(idx) diff --git a/rrompy/parameter/parameter_sampling/shape/fft_sampler.py b/rrompy/parameter/parameter_sampling/shape/fft_sampler.py index 1104159..e9fb583 100644 --- a/rrompy/parameter/parameter_sampling/shape/fft_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/fft_sampler.py @@ -1,51 +1,48 @@ # 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 .generic_shape_sampler import GenericShapeSampler from rrompy.utilities.base.types import paramList from rrompy.utilities.numerical import lowDiscrepancy, kroneckerer -from rrompy.parameter import checkParameterList __all__ = ['FFTSampler'] class FFTSampler(GenericShapeSampler): """Generator of FFT-type sample points on scaled roots of unity.""" def generatePoints(self, n:int, reorder : bool = True) -> paramList: """Array of sample points.""" n1d = int(np.ceil(n ** (1. / self.npar))) nleft, nright = 1, n1d ** self.npar xmat = np.empty((nright, self.npar), dtype = np.complex) + limsE = self.mapParameterList(self.lims) for d in range(self.npar): nright //= n1d - a = self.lims(0, d) ** self.scalingExp[d] - b = self.lims(1, d) ** self.scalingExp[d] + a, b = limsE(d) c, r = (a + b) / 2., (a - b) / 2. unitpts = np.exp(1.j * np.linspace(0, 2 * np.pi, n1d + 1)[:-1]) unitpts = (np.real(unitpts) + 1.j * self.axisRatios[d] * np.imag(unitpts)) xd = c + r * unitpts - xd **= 1. / self.scalingExp[d] if n1d > 1 and reorder: fejerOrdering = [n1d - 1] + lowDiscrepancy(n1d - 1) xd = xd[fejerOrdering] xmat[:, d] = kroneckerer(xd, nleft, nright) nleft *= n1d - x = checkParameterList(xmat, self.npar)[0] - return x + return self.mapParameterList(xmat, "B") 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 eb7213a..65a4701 100644 --- a/rrompy/parameter/parameter_sampling/shape/generic_shape_quadrature_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/generic_shape_quadrature_sampler.py @@ -1,54 +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 rrompy.parameter.parameter_sampling.generic_quadrature_sampler import ( - _allowedQuadratureKinds) -from rrompy.utilities.base.types import List, paramList -from rrompy.utilities.exception_manager import RROMPyException + GenericQuadratureSampler) +from rrompy.utilities.base.types import List, DictAny, paramList __all__ = ['GenericShapeQuadratureSampler'] -class GenericShapeQuadratureSampler(GenericShapeSampler): +class GenericShapeQuadratureSampler(GenericQuadratureSampler, + 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 - + axisRatios : List[float] = None, parameterMap : DictAny = 1.): + super().__init__(lims = lims, kind = kind, parameterMap = parameterMap) + self.axisRatios = axisRatios 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 ea20875..75c4fa3 100644 --- a/rrompy/parameter/parameter_sampling/shape/generic_shape_random_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/generic_shape_random_sampler.py @@ -1,53 +1,35 @@ # 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 rrompy.parameter.parameter_sampling.generic_random_sampler import ( - _allowedRandomKinds) -from rrompy.utilities.base.types import List, paramList -from rrompy.utilities.exception_manager import RROMPyException + GenericRandomSampler) +from rrompy.utilities.base.types import List, DictAny, paramList __all__ = ['GenericShapeRandomSampler'] -class GenericShapeRandomSampler(GenericShapeSampler): +class GenericShapeRandomSampler(GenericRandomSampler, 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() + axisRatios : List[float] = None, parameterMap : DictAny = 1., + refinementFactor : float = 1., seed : int = 42): + super().__init__(lims = lims, kind = kind, parameterMap = parameterMap, + refinementFactor = refinementFactor) + self.axisRatios = axisRatios + \ No newline at end of file diff --git a/rrompy/parameter/parameter_sampling/shape/generic_shape_sampler.py b/rrompy/parameter/parameter_sampling/shape/generic_shape_sampler.py index 09c1403..c46ed25 100644 --- a/rrompy/parameter/parameter_sampling/shape/generic_shape_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/generic_shape_sampler.py @@ -1,48 +1,48 @@ # 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.parameter.parameter_sampling.generic_sampler import GenericSampler -from rrompy.utilities.base.types import List, paramList +from rrompy.utilities.base.types import List, DictAny, paramList from rrompy.utilities.exception_manager import RROMPyAssert __all__ = ['GenericShapeSampler'] class GenericShapeSampler(GenericSampler): """Generator of sample points on boxes or ellipses.""" def __init__(self, lims:paramList, axisRatios : List[float] = None, - scalingExp : List[float] = None): - super().__init__(lims = lims, scalingExp = scalingExp) + parameterMap : DictAny = 1.): + super().__init__(lims = lims, parameterMap = parameterMap) self.axisRatios = axisRatios def normalFoci(self, d : int = 0): focus = (1. + 0.j - self.axisRatios[d] ** 2.) ** .5 return [- focus, focus] @property def axisRatios(self): """Value of axisRatios.""" return self._axisRatios @axisRatios.setter def axisRatios(self, axisRatios): if axisRatios is None: axisRatios = [1.] * self.npar if not hasattr(axisRatios, "__len__"): axisRatios = [axisRatios] RROMPyAssert(self.npar, len(axisRatios), "Number of axis ratios terms") self._axisRatios = [np.abs(x) for x in axisRatios] diff --git a/rrompy/parameter/parameter_sampling/shape/quadrature_box_sampler.py b/rrompy/parameter/parameter_sampling/shape/quadrature_box_sampler.py index 20b5080..f61d64b 100644 --- a/rrompy/parameter/parameter_sampling/shape/quadrature_box_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/quadrature_box_sampler.py @@ -1,58 +1,55 @@ # 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 .generic_shape_quadrature_sampler import GenericShapeQuadratureSampler from rrompy.utilities.base.types import paramList from rrompy.utilities.numerical import (lowDiscrepancy, kroneckerer, quadraturePointsGenerate) -from rrompy.parameter import checkParameterList __all__ = ['QuadratureBoxSampler'] class QuadratureBoxSampler(GenericShapeQuadratureSampler): """Generator of quadrature sample points on boxes.""" def generatePoints(self, n:int, reorder : bool = True) -> paramList: """Array of sample points.""" n1d = int(np.ceil(n ** (1. / self.npar))) xds, nds = [None] * self.npar, [None] * self.npar + limsE = self.mapParameterList(self.lims) for d in range(self.npar): ax = self.axisRatios[d] - a = self.lims(0, d) ** self.scalingExp[d] - b = self.lims(1, d) ** self.scalingExp[d] + a, b = limsE(d) c, r = (a + b) / 2., (a - b) / 2. n1dx = int(np.ceil((n1d / ax) ** .5)) n1dy = int(np.ceil(n1d / n1dx)) Xdx, Xdy = np.meshgrid(quadraturePointsGenerate(n1dx, self.kind), quadraturePointsGenerate(n1dy, self.kind)) Z = Xdx.flatten() + 1.j * ax * Xdy.flatten() - xds[d] = (c + r * Z) ** (1. / self.scalingExp[d]) + xds[d] = c + r * Z nds[d] = len(Z) nleft, nright = 1, np.prod(nds) xmat = np.empty((nright, self.npar), dtype = np.complex) for d in range(self.npar): nright //= nds[d] xmat[:, d] = kroneckerer(xds[d], nleft, nright) nleft *= nds[d] if len(xmat) > 1 and reorder: fejerOrdering = [len(xmat) - 1] + lowDiscrepancy(len(xmat) - 1) xmat = xmat[fejerOrdering, :] - x = checkParameterList(xmat, self.npar)[0] - return x - + return self.mapParameterList(xmat, "B") diff --git a/rrompy/parameter/parameter_sampling/shape/quadrature_circle_sampler.py b/rrompy/parameter/parameter_sampling/shape/quadrature_circle_sampler.py index 94b1f20..eabc845 100644 --- a/rrompy/parameter/parameter_sampling/shape/quadrature_circle_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/quadrature_circle_sampler.py @@ -1,65 +1,63 @@ # 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 .generic_shape_quadrature_sampler import GenericShapeQuadratureSampler from rrompy.utilities.base.types import paramList from rrompy.utilities.numerical import (lowDiscrepancy, kroneckerer, potential, quadraturePointsGenerate) -from rrompy.parameter import checkParameterList __all__ = ['QuadratureCircleSampler'] class QuadratureCircleSampler(GenericShapeQuadratureSampler): """Generator of quadrature sample points on ellipses.""" def generatePoints(self, n:int, reorder : bool = True) -> paramList: """Array of sample points.""" n1d = int(np.ceil(n ** (1. / self.npar))) xds, nds = [None] * self.npar, [None] * self.npar + limsE = self.mapParameterList(self.lims) for d in range(self.npar): ax = self.axisRatios[d] scorebase = self.groundPotential(d) - a = self.lims(0, d) ** self.scalingExp[d] - b = self.lims(1, d) ** self.scalingExp[d] + a, b = limsE(d) c, r = (a + b) / 2., (a - b) / 2. n1dx = int(np.ceil((n1d * 4. / np.pi / ax) ** .5)) n1dy = int(np.ceil(n1d * 4. / np.pi / n1dx)) even, Z = True, [] while len(Z) < n1d: Xdx, Xdy = np.meshgrid( quadraturePointsGenerate(n1dx, self.kind), quadraturePointsGenerate(n1dy, self.kind)) Z = Xdx.flatten() + 1.j * ax * Xdy.flatten() ptscore = potential(Z, self.normalFoci(d)) Z = Z[ptscore <= scorebase] if even: n1dx += 1 else: n1dy += 1 - xds[d] = (c + r * Z) ** (1. / self.scalingExp[d]) + xds[d] = c + r * Z nds[d] = len(Z) nleft, nright = 1, np.prod(nds) xmat = np.empty((nright, self.npar), dtype = np.complex) for d in range(self.npar): nright //= nds[d] xmat[:, d] = kroneckerer(xds[d], nleft, nright) nleft *= nds[d] if len(xmat) > 1 and reorder: fejerOrdering = [len(xmat) - 1] + lowDiscrepancy(len(xmat) - 1) xmat = xmat[fejerOrdering, :] - x = checkParameterList(xmat, self.npar)[0] - return x + return self.mapParameterList(xmat, "B") diff --git a/rrompy/parameter/parameter_sampling/shape/random_box_sampler.py b/rrompy/parameter/parameter_sampling/shape/random_box_sampler.py index 0fc7a57..f1e872f 100644 --- a/rrompy/parameter/parameter_sampling/shape/random_box_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/random_box_sampler.py @@ -1,59 +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 . # +from numbers import Number import numpy as np from .generic_shape_random_sampler import GenericShapeRandomSampler from rrompy.utilities.numerical import haltonGenerate, sobolGenerate -from rrompy.utilities.base.types import paramList -from rrompy.parameter import checkParameterList +from rrompy.utilities.base.types import List __all__ = ['RandomBoxSampler'] class RandomBoxSampler(GenericShapeRandomSampler): """Generator of (quasi-)random sample points on boxes.""" - def generatePoints(self, n:int, reorder : bool = True) -> paramList: - """Array of quadrature points.""" + def refine(self, active : List[int] = None) -> List[int]: + if active is None: + n = self.npoints + elif isinstance(active, (Number,)): + n = int(active) + else: + n = len(active) + n = int(n * self.refinementFactor) nEff = int(np.ceil(n * np.prod( [max(x, 1. / x) for x in self.axisRatios]))) xmat2 = [] while len(xmat2) < n: if self.kind == "UNIFORM": - np.random.seed(self.seed) xmat2 = np.random.uniform(size = (nEff, 2 * self.npar)) elif self.kind == "HALTON": - xmat2 = haltonGenerate(2 * self.npar, nEff, self.seed) + xmat2, self.seedLoc = haltonGenerate(2 * self.npar, nEff, + self.seedLoc, + return_seed = True) else: - xmat2 = sobolGenerate(2 * self.npar, nEff, self.seed) + xmat2, self.seedLoc = sobolGenerate(2 * self.npar, nEff, + self.seed, + return_seed = True) for d in range(self.npar): ax = self.axisRatios[d] if ax <= 1.: xmat2 = xmat2[xmat2[:, 2 * d + 1] <= ax] else: xmat2 = xmat2[xmat2[:, 2 * d] <= 1. / ax] xmat2[:, 2 * d : 2 * d + 2] *= ax nEff += 1 xmat = np.empty((n, self.npar), dtype = np.complex) + limsE = self.mapParameterList(self.lims) for d in range(self.npar): - a = self.lims(0, d) ** self.scalingExp[d] - b = self.lims(1, d) ** self.scalingExp[d] + a, b = limsE(d) xmat[:, d] = a + (b - a) * (xmat2[: n, 2 * d] + 1.j * self.axisRatios[d] * (xmat2[: n, 2 * d + 1] - .5)) - xmat[:, d] **= 1. / self.scalingExp[d] - x = checkParameterList(xmat, self.npar)[0] - return x + pts = self.mapParameterList(xmat, "B") + idx = np.arange(n, dtype = int) + len(self.points) + for pj in pts: self.points.append(pj) + return list(idx) diff --git a/rrompy/parameter/parameter_sampling/shape/random_circle_sampler.py b/rrompy/parameter/parameter_sampling/shape/random_circle_sampler.py index bf52024..5a1de1f 100644 --- a/rrompy/parameter/parameter_sampling/shape/random_circle_sampler.py +++ b/rrompy/parameter/parameter_sampling/shape/random_circle_sampler.py @@ -1,62 +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 . # +from numbers import Number import numpy as np from .generic_shape_random_sampler import GenericShapeRandomSampler from rrompy.utilities.numerical import (haltonGenerate, sobolGenerate, potential) -from rrompy.utilities.base.types import paramList -from rrompy.parameter import checkParameterList +from rrompy.utilities.base.types import List __all__ = ['RandomCircleSampler'] class RandomCircleSampler(GenericShapeRandomSampler): """Generator of (quasi-)random sample points on ellipses.""" - def generatePoints(self, n:int, reorder : bool = True) -> paramList: - """Array of quadrature points.""" + def refine(self, active : List[int] = None) -> List[int]: + if active is None: + n = self.npoints + elif isinstance(active, (Number,)): + n = active + else: + n = len(active) + n = int(n * self.refinementFactor) nEff = int(np.ceil(n * (4. / np.pi) ** self.npar * np.prod( [max(x, 1. / x) for x in self.axisRatios]))) xmat2 = [] while len(xmat2) < n: if self.kind == "UNIFORM": - np.random.seed(self.seed) xmat2 = np.random.uniform(size = (nEff, 2 * self.npar)) elif self.kind == "HALTON": - xmat2 = haltonGenerate(2 * self.npar, nEff, self.seed) + xmat2, self.seedLoc = haltonGenerate(2 * self.npar, nEff, + self.seedLoc, + return_seed = True) else: - xmat2 = sobolGenerate(2 * self.npar, nEff, self.seed) + xmat2, self.seedLoc = sobolGenerate(2 * self.npar, nEff, + self.seed, + return_seed = True) xmat2 = xmat2 * 2. - 1. for d in range(self.npar): ax = self.axisRatios[d] if ax > 1.: xmat2[:, 2 * d : 2 * d + 2] *= ax Z = xmat2[:, 2 * d] + 1.j * ax * xmat2[:, 2 * d + 1] ptscore = potential(Z, self.normalFoci(d)) xmat2 = xmat2[ptscore <= self.groundPotential(d)] nEff += 1 xmat = np.empty((n, self.npar), dtype = np.complex) + limsE = self.mapParameterList(self.lims) for d in range(self.npar): ax = self.axisRatios[d] - a = self.lims(0, d) ** self.scalingExp[d] - b = self.lims(1, d) ** self.scalingExp[d] + a, b = limsE(d) c, r = (a + b) / 2., (a - b) / 2. xmat[:, d] = c + r * (xmat2[: n, 2 * d] + 1.j * ax * xmat2[: n, 2 * d + 1]) - xmat[:, d] **= 1. / self.scalingExp[d] - x = checkParameterList(xmat, self.npar)[0] - return x + pts = self.mapParameterList(xmat, "B") + idx = np.arange(n, dtype = int) + len(self.points) + for pj in pts: self.points.append(pj) + return list(idx) diff --git a/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler.py b/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler.py index c50aaf7..ed6b372 100644 --- a/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler.py +++ b/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler.py @@ -1,115 +1,115 @@ # 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 itertools import product import numpy as np -from rrompy.parameter import checkParameterList from rrompy.parameter.parameter_sampling.generic_sampler import GenericSampler from rrompy.utilities.poly_fitting.piecewise_linear import (sparsekinds, sparseMap) -from rrompy.utilities.base.types import List, paramList +from rrompy.utilities.base.types import List, DictAny, paramList from rrompy.utilities.exception_manager import RROMPyException __all__ = ['SparseGridSampler'] class SparseGridSampler(GenericSampler): """Generator of sparse grid sample points.""" def __init__(self, lims:paramList, kind : str = "UNIFORM", - scalingExp : List[float] = None): - super().__init__(lims = lims, scalingExp = scalingExp) + parameterMap : DictAny = 1.): + super().__init__(lims = lims, parameterMap = parameterMap) self.kind = kind self.reset() def __str__(self) -> str: return "{}[{}_{}]_{}".format(self.name(), self.lims[0], self.lims[1], self.kind) @property def npoints(self): """Number of points.""" return len(self.points) @property def kind(self): """Value of kind.""" return self._kind @kind.setter def kind(self, kind): if kind.upper() not in [sk.split("_")[2] + extra for sk, extra in product(sparsekinds, ["", "-NOBOUNDARY"])]: raise RROMPyException("Generator kind not recognized.") self._kind = kind.upper() self._noBoundary = "NOBOUNDARY" in self._kind def reset(self): - centerEff = .5 * (self.lims[0] ** self.scalingExp - + self.lims[1] ** self.scalingExp) - self.points = checkParameterList(centerEff ** (1. / self.scalingExp), - self.npar)[0] + limsE = self.mapParameterList(self.lims) + centerEff = .5 * (limsE[0] + limsE[1]) + self.points = self.mapParameterList(centerEff, "B") self.depth = np.zeros((1, self.npar), dtype = int) self.deltadepth = np.zeros(1, dtype = int) def refine(self, active : List[int] = None) -> List[int]: if active is None: active = np.arange(self.npoints) - limsX = [self.lims(j) ** self.scalingExp[j] for j in range(self.npar)] + limsX = self.mapParameterList(self.lims) newIdxs = [] for act in active: point, dpt = self.points[act], self.depth[act] exhausted = False while not exhausted: ddp = self.deltadepth[act] for jdelta in range(self.npar): for signdelta in [-1., 1.]: Pointj = sparseMap( - point[jdelta] ** self.scalingExp[jdelta], - limsX[jdelta], self.kind, False) + self.mapParameterList(point[jdelta], + idx = [jdelta])(0, 0), + limsX(jdelta), self.kind, False) if not self._noBoundary and dpt[jdelta] == 1: Centerj = sparseMap( - self.points(0, jdelta) ** self.scalingExp[jdelta], - limsX[jdelta], self.kind, False) + self.mapParameterList(self.points[0][jdelta], + idx = [jdelta])(0, 0), + limsX(jdelta), self.kind, False) gradj = Pointj - Centerj if signdelta * gradj > 0: continue pointj = copy(point) Pointj = Pointj + .5 ** (dpt[jdelta] + ddp + self._noBoundary) * signdelta - pointj[jdelta] = sparseMap(Pointj, limsX[jdelta], - self.kind) ** (1. - / self.scalingExp[jdelta]) + pointj[jdelta] = self.mapParameterList( + sparseMap(Pointj, limsX(jdelta), self.kind), + "B", [jdelta])(0, 0) dist = np.sum(np.abs(self.points.data - pointj.reshape(1, -1)), axis = 1) samePt = np.where(np.isclose(dist, 0.))[0] if len(samePt) > 0: if samePt[0] in newIdxs: exhausted = True continue newIdxs += [self.npoints] self.points.append(pointj) self.depth = np.append(self.depth, [dpt], 0) self.depth[-1, jdelta] += 1 + ddp self.deltadepth = np.append(self.deltadepth, [0]) exhausted = True self.deltadepth[act] += 1 return newIdxs def generatePoints(self, n:int, reorder = None) -> paramList: if self.npoints > n: self.reset() idx = np.arange(self.npoints) while self.npoints < n: idx = self.refine(idx) return self.points diff --git a/rrompy/reduction_methods/base/generic_approximant.py b/rrompy/reduction_methods/base/generic_approximant.py index ff19eb5..345fc79 100644 --- a/rrompy/reduction_methods/base/generic_approximant.py +++ b/rrompy/reduction_methods/base/generic_approximant.py @@ -1,914 +1,918 @@ # 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.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.pickle_utilities import pickleDump, pickleLoad from rrompy.parameter import (emptyParameterList, checkParameter, checkParameterList) from rrompy.sampling import sampleList, emptySampleList from rrompy.utilities.parallel import (bcast, masterCore, listGather, listScatter) __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) uV = listScatter(uV)[0].T filesOut = [] if len(uV) > 0: if "name" in kwargs.keys(): nameBase = copy(kwargs["name"]) 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 filesOut = listGather(filesOut) 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) uV = listScatter(uV)[0].T filesOut = [] if len(uV) > 0: if "name" in kwargs.keys(): nameBase = copy(kwargs["name"]) 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 filesOut = listGather(filesOut) 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) uV = listScatter(uV)[0].T filesOut = [] if len(uV) > 0: if "name" in kwargs.keys(): nameBase = copy(kwargs["name"]) 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 filesOut = listGather(filesOut) 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) uV = listScatter(uV)[0].T filesOut = [] if len(uV) > 0: 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 filesOut = listGather(filesOut) 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - '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; - 'scaleFactorDer': scaling factors for derivative computation. 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. scaleFactorDer: Scaling factors for derivative computation. 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", "scaleFactorDer"], [True, "AUTO"], ["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.trained_model_data import TrainedModelData return (TrainedModelData(datadict["mu0"], datadict["mus"], datadict.pop("projMat"), datadict["scaleFactor"], - datadict.pop("rescalingExp")), + datadict.pop("parameterMap")), ["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] + def checkParameterList(self, mu:paramList, + check_if_single : bool = False) -> paramList: + return checkParameterList(mu, self.npar, check_if_single) + @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: fragile = True val = self.parameterDefaultCritical[key] if self._mode == RROMPy_FRAGILE: setattr(self, "_" + key, val) self.approxParameters[key] = val else: getattr(self.__class__, key, None).fset(self, val) 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] if self._mode == RROMPy_FRAGILE: setattr(self, "_" + key, val) self.approxParameters[key] = val else: 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 scaleFactorDer(self): """Value of scaleFactorDer.""" if self._scaleFactorDer == "NONE": return 1. if self._scaleFactorDer == "AUTO": return self.scaleFactor return self._scaleFactorDer @scaleFactorDer.setter def scaleFactorDer(self, scaleFactorDer): if isinstance(scaleFactorDer, (str,)): scaleFactorDer = scaleFactorDer.upper() elif hasattr(scaleFactorDer, "__len__"): scaleFactorDer = list(scaleFactorDer) self._scaleFactorDer = scaleFactorDer self._approxParameters["scaleFactorDer"] = self._scaleFactorDer @property def scaleFactorRel(self): """Value of scaleFactorDer / scaleFactor.""" if self._scaleFactorDer == "AUTO": return None try: return np.divide(self.scaleFactorDer, self.scaleFactor) except: raise RROMPyException(("Error in computation of relative scaling " "factor. Make sure that scaleFactor is " "properly initialized.")) @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.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 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 self.trainedModel = ... self.trainedModel.data = ... self.trainedModel.data.approxParameters = copy( self.approxParameters) Returns > 0 if error was encountered, < 0 if no computation was necessary. """ if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up {}.". format(self.name()), 5) pass vbMng(self, "DEL", "Done setting up approximant.", 5) 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 self._mode == RROMPy_FRAGILE or (self.trainedModel is not None and self.trainedModel.data.approxParameters == self.approxParameters and len(self.mus) == len(self.trainedModel.data.mus)) def _pruneBeforeEval(self, mu:paramList, field:str, append:bool, prune:bool) -> Tuple[paramList, Np1D, Np1D, bool]: - mu = checkParameterList(mu, self.npar)[0] + mu = self.checkParameterList(mu) 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] + newMus = self.checkParameterList(mu) 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] + mu = self.checkParameterList(mu) 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] + mu = self.checkParameterList(mu) 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] + mu = self.checkParameterList(mu) 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) + return np.linalg.norm(self.HFEngine.applyC( + self.getApproxReduced(mu).data), axis = 0) def recompressApprox(self, collapse : bool = False, tol : float = 0.): """Recompress approximant.""" self.setupApprox() vbMng(self, "INIT", "Recompressing approximant.", 20) self.trainedModel.compress(collapse, tol, self.HFEngine, self.approx_state) vbMng(self, "DEL", "Done recompressing approximant.", 20) 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 storeSamples(self, filenameBase : str = "samples", forceNewFile : bool = True) -> str: """Store samples to file.""" filename = filenameBase + "_" + self.name() if forceNewFile: filename = getNewFilename(filename, "pkl")[: - 4] return self.samplingEngine.store(filename, False) def storeTrainedModel(self, filenameBase : str = "trained_model", forceNewFile : bool = True) -> str: """Store trained reduced model to file.""" self.setupApprox() filename = None if masterCore(): 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) filename = bcast(filename) 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"] self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.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]) self.trainedModel.data = data self._mode = RROMPy_FRAGILE vbMng(self, "DEL", "Done loading pre-trained model.", 20) diff --git a/rrompy/reduction_methods/base/trained_model/trained_model.py b/rrompy/reduction_methods/base/trained_model/trained_model.py index 41e7c43..c16f564 100644 --- a/rrompy/reduction_methods/base/trained_model/trained_model.py +++ b/rrompy/reduction_methods/base/trained_model/trained_model.py @@ -1,103 +1,118 @@ # 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 numpy import empty -from rrompy.utilities.base.types import Np1D, paramList, sampList +from numpy import empty, arange +from rrompy.utilities.base.types import Np1D, List, paramList, sampList +from rrompy.utilities.expression import expressionEvaluator from rrompy.parameter import checkParameterList from rrompy.sampling import sampleList __all__ = ['TrainedModel'] class TrainedModel: """ ABSTRACT ROM approximant evaluation. Attributes: Data: dictionary with all that can be pickled. """ 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 reset(self): self.lastSolvedApproxReduced = None self.lastSolvedApprox = None def compress(self, collapse : bool = False, tol : float = 0.): if collapse: self.data.projMat = 1. self.data._collapsed = True if tol > 0.: self.data._compressTol = tol @property def npar(self): """Number of parameters.""" return self.data.mu0.shape[1] + def checkParameterList(self, mu:paramList, + check_if_single : bool = False) -> paramList: + return checkParameterList(mu, self.data.npar, check_if_single) + + def mapParameterList(self, mu:paramList, direct : str = "F", + idx : List[int] = None) -> paramList: + if idx is None: idx = arange(self.npar) + muMapped = checkParameterList(mu, len(idx)) + for j, d in enumerate(idx): + muMapped.data[:, j] = expressionEvaluator( + self.data.parameterMap[direct][d], + muMapped(j)).flatten() + return muMapped + @abstractmethod def getApproxReduced(self, mu : paramList = []) -> sampList: """ Evaluate reduced representation of approximant at arbitrary parameter. (ABSTRACT) Args: mu: Target parameter. """ pass def getApprox(self, mu : paramList = []) -> sampList: """ Evaluate approximant at arbitrary parameter. Args: mu: Target parameter. """ - mu = checkParameterList(mu, self.data.npar)[0] + mu = self.checkParameterList(mu) if (not hasattr(self, "lastSolvedApprox") or self.lastSolvedApprox != mu): uApproxR = self.getApproxReduced(mu) if self.data._collapsed: self.uApprox = uApproxR else: for i, uApR in enumerate(uApproxR): uAp = self.data.projMat[:, : uApR.shape[0]].dot(uApR) if i == 0: uApprox = empty((len(uAp), len(uApproxR)), dtype = uAp.dtype) uApprox[:, i] = uAp self.uApprox = sampleList(uApprox) self.lastSolvedApprox = mu return self.uApprox @abstractmethod def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ pass diff --git a/rrompy/reduction_methods/base/trained_model/trained_model_data.py b/rrompy/reduction_methods/base/trained_model/trained_model_data.py index 3720504..9c64042 100644 --- a/rrompy/reduction_methods/base/trained_model/trained_model_data.py +++ b/rrompy/reduction_methods/base/trained_model/trained_model_data.py @@ -1,37 +1,37 @@ # 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 rrompy.utilities.base.types import Np2D, List, paramVal, paramList -from rrompy.utilities.exception_manager import RROMPyAssert +from rrompy.utilities.base.types import (Np2D, List, DictAny, paramVal, + paramList) +from rrompy.parameter import parameterMap as pMap __all__ = ['TrainedModelData'] class TrainedModelData: """ROM approximant evaluation data (must be pickle-able).""" def __init__(self, mu0:paramVal, mus:paramList, projMat:Np2D, scaleFactor : List[float] = [1.], - rescalingExp : List[float] = [1.]): - self.npar = len(rescalingExp) - RROMPyAssert(mu0.shape[1], self.npar, "Number of parameters") + parameterMap : DictAny = 1.): self.mu0 = mu0 + self.npar = self.mu0.shape[1] self.mus = mus self.projMat = copy(projMat) self.scaleFactor = scaleFactor - self.rescalingExp = rescalingExp + self.parameterMap = pMap(parameterMap, self.npar) self._collapsed = False diff --git a/rrompy/reduction_methods/pivoted/gather_pivoted_approximant.py b/rrompy/reduction_methods/pivoted/gather_pivoted_approximant.py new file mode 100644 index 0000000..31c1f7a --- /dev/null +++ b/rrompy/reduction_methods/pivoted/gather_pivoted_approximant.py @@ -0,0 +1,99 @@ +# 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 Np2D, Tuple, List, interpEng +from rrompy.utilities.poly_fitting.polynomial import PolynomialInterpolator +from rrompy.utilities.parallel.base import (COMM, allowParallelExecution, + forcedSerial) + +def gatherPivotedApproximant(pMat:Np2D, Ps:List[interpEng], Qs:List[interpEng], + mus:Np2D, sizes:List[int], polybasis:str, + send_mus : bool = True) \ + -> Tuple[Np2D, List[interpEng], + List[interpEng], Np2D, List[int]]: + allowParallelExecution(True) + + sizes, sumsizes = np.array(sizes, dtype = int), np.sum(sizes) + npar, samplesize = mus.shape[1], pMat.shape[0] + shapePQ = np.array([[P.deg[0] + 1, P.shape[0], Q.deg[0] + 1] + for P, Q in zip(Ps, Qs)], dtype = int).flatten() + if forcedSerial() or len(sizes) == 1: + return pMat, Ps, Qs, mus, list(shapePQ[1::3]) + # share shapes of local Ps and Qs + shapePQs = np.empty(3 * sumsizes, dtype = int) + COMM.Allgatherv(shapePQ, [shapePQs, 3 * sizes]) + sP = np.hstack((shapePQs[::3].reshape(-1, 1), + shapePQs[1::3].reshape(-1, 1))) + sQ = shapePQs[2::3] + # compute cumulative shapes of all Ps and Qs + sizesP, sizesQ, sizespMat = [], [], [] + if send_mus: sizesmu = [] + sizeOld = 0 + for size in sizes: + sizesP += [np.sum(np.product(sP[sizeOld : sizeOld + size], axis = 1))] + sizesQ += [np.sum(sQ[sizeOld : sizeOld + size])] + sizespMat += [samplesize * np.sum(sP[sizeOld : sizeOld + size, 1])] + if send_mus: + sizesmu += [npar * np.sum(sP[sizeOld : sizeOld + size, 1])] + sizeOld += size + + # initiate nonblocking gathers + pMatflat = pMat.T.flatten() + if len(shapePQ) > 0: + Pflat = np.concatenate([P.coeffs.flatten() for P in Ps]) + Qflat = np.concatenate([np.squeeze(Q.coeffs) for Q in Qs]) + else: + Pflat = np.zeros(0, dtype = pMat.dtype) + Qflat = np.zeros(0, dtype = pMat.dtype) + pMatsflat = np.empty(np.sum(sizespMat), dtype = pMat.dtype) + Psflat = np.empty(np.sum(sizesP), dtype = Pflat.dtype) + Qsflat = np.empty(np.sum(sizesQ), dtype = Qflat.dtype) + pMatreq = COMM.Iallgatherv(pMatflat, [pMatsflat, sizespMat]) + Preq = COMM.Iallgatherv(Pflat, [Psflat, sizesP]) + Qreq = COMM.Iallgatherv(Qflat, [Qsflat, sizesQ]) + if send_mus: + musflat = np.empty(np.sum(sizesmu), dtype = mus.dtype) + mureq = COMM.Iallgatherv(mus.flatten(), [musflat, sizesmu]) + + # post-process data + pMatreq.wait() + pMat = pMatsflat.reshape(-1, samplesize).T + Preq.wait() + Ps, shift = [], 0 + for sPj in sP: + currSize = np.product(sPj) + P = PolynomialInterpolator() + P.coeffs = Psflat[shift : shift + currSize].reshape(sPj) + P.npar = 1 + P.polybasis = polybasis + Ps += [P] + shift += currSize + Qreq.wait() + Qs, shift = [], 0 + for currSize in sQ: + Q = PolynomialInterpolator() + Q.coeffs = Qsflat[shift : shift + currSize] + Q.npar = 1 + Q.polybasis = polybasis + Qs += [Q] + shift += currSize + if send_mus: + mureq.wait() + mus = musflat.reshape(-1, npar) + return pMat, Ps, Qs, mus, list(sP[:, 1]) diff --git a/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py b/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py index 23fbf56..0ca972b 100644 --- a/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py +++ b/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py @@ -1,713 +1,742 @@ # 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 mkdir +from os import mkdir, remove, rmdir import numpy as np from copy import deepcopy as copy from rrompy.reduction_methods.base.generic_approximant import ( GenericApproximant) from rrompy.utilities.base.data_structures import purgeDict, getNewFilename from rrompy.sampling import SamplingEngineStandard, SamplingEngineStandardPOD 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.piecewise_linear import sparsekinds as sk from rrompy.utilities.base.types import Np2D, paramList, List, ListAny from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical import dot from rrompy.utilities.numerical.degree import reduceDegreeN from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) from rrompy.parameter import checkParameterList -from rrompy.utilities.parallel import poolRank, bcastForce +from rrompy.utilities.parallel import poolRank, bcast __all__ = ['GenericPivotedApproximantNoMatch', 'GenericPivotedApproximant'] class GenericPivotedApproximantBase(GenericApproximant): def __init__(self, directionPivot:ListAny, *args, storeAllSamples : bool = False, **kwargs): self._preInit() if len(directionPivot) > 1: raise RROMPyException(("Exactly 1 pivot parameter allowed in pole " "matching.")) from rrompy.parameter.parameter_sampling import (EmptySampler as ES, SparseGridSampler as SG) - self._addParametersToList(["cutOffTolerance", - "radialDirectionalWeightsMarginal"], - [np.inf, [1.]], ["samplerPivot", "SMarginal", - "samplerMarginal"], - [ES(), 1, SG([[-1.], [1.]])]) + self._addParametersToList(["radialDirectionalWeightsMarginal"], [1.], + ["samplerPivot", "SMarginal", + "samplerMarginal"], + [ES(), 1, SG([[-1.], [1.]])], + toBeExcluded = ["sampler"]) self._directionPivot = directionPivot self.storeAllSamples = storeAllSamples super().__init__(*args, **kwargs) self._postInit() 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) def initializeModelData(self, datadict): if "directionPivot" in datadict.keys(): from .trained_model.trained_model_pivoted_data import ( TrainedModelPivotedData) return (TrainedModelPivotedData(datadict["mu0"], datadict["mus"], datadict.pop("projMat"), datadict["scaleFactor"], - datadict.pop("rescalingExp"), + datadict.pop("parameterMap"), 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 + def checkParameterListPivot(self, mu:paramList, + check_if_single : bool = False) -> paramList: + return checkParameterList(mu, self.nparPivot, check_if_single) + + def checkParameterListMarginal(self, mu:paramList, + check_if_single : bool = False) -> paramList: + return checkParameterList(mu, self.nparMarginal, check_if_single) + @property def mus(self): """Value of mus. Its assignment may reset snapshots.""" return self._mus @mus.setter def mus(self, mus): - mus = checkParameterList(mus)[0] + mus = self.checkParameterList(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 musMarginal(self): """Value of musMarginal. Its assignment may reset snapshots.""" return self._musMarginal @musMarginal.setter def musMarginal(self, musMarginal): - musMarginal = checkParameterList(musMarginal)[0] + musMarginal = self.checkParameterListMarginal(musMarginal) if hasattr(self, '_musMarginal'): musMOld = copy(self.musMarginal) else: musMOld = None if (musMOld is None or len(musMarginal) != len(musMOld) or not musMarginal == musMOld): self.resetSamples() self._musMarginal = musMarginal - @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 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 radialDirectionalWeightsMarginal(self): """Value of radialDirectionalWeightsMarginal.""" return self._radialDirectionalWeightsMarginal @radialDirectionalWeightsMarginal.setter - def radialDirectionalWeightsMarginal(self, radialDirWeightsMarginal): - if hasattr(radialDirWeightsMarginal, "__len__"): - radialDirWeightsMarginal = list(radialDirWeightsMarginal) + def radialDirectionalWeightsMarginal(self, radialDirWeightsMarg): + if hasattr(radialDirWeightsMarg, "__len__"): + radialDirWeightsMarg = list(radialDirWeightsMarg) else: - radialDirWeightsMarginal = [radialDirWeightsMarginal] - self._radialDirectionalWeightsMarginal = radialDirWeightsMarginal + radialDirWeightsMarg = [radialDirWeightsMarg] + self._radialDirectionalWeightsMarginal = radialDirWeightsMarg self._approxParameters["radialDirectionalWeightsMarginal"] = ( self.radialDirectionalWeightsMarginal) @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 muBounds(self): """Value of muBounds.""" return self.samplerPivot.lims @property def muBoundsMarginal(self): """Value of muBoundsMarginal.""" return self.samplerMarginal.lims @property def sampler(self): """Proxy of samplerPivot.""" return self._samplerPivot @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 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 if not 'samplerOld' in locals() or samplerOld != self.samplerMarginal: self.resetSamples() def computeScaleFactor(self): """Compute parameter rescaling factor.""" - self.scaleFactorPivot = .5 * np.abs( - self.muBounds[0] ** self.rescalingExpPivot - - self.muBounds[1] ** self.rescalingExpPivot) - self.scaleFactorMarginal = .5 * np.abs( - self.muBoundsMarginal[0] ** self.rescalingExpMarginal - - self.muBoundsMarginal[1] ** self.rescalingExpMarginal) + self.scaleFactorPivot = .5 * np.abs(( + self.HFEngine.mapParameterList(self.muBounds[0], + idx = self.directionPivot) + - self.HFEngine.mapParameterList(self.muBounds[1], + idx = self.directionPivot) + )[0]) + self.scaleFactorMarginal = .5 * np.abs(( + self.HFEngine.mapParameterList(self.muBoundsMarginal[0], + idx = self.directionMarginal) + - self.HFEngine.mapParameterList(self.muBoundsMarginal[1], + idx = self.directionMarginal) + )[0]) self.scaleFactor = np.empty(self.npar) self.scaleFactor[self.directionPivot] = self.scaleFactorPivot self.scaleFactor[self.directionMarginal] = self.scaleFactorMarginal def _setupTrainedModel(self, pMat:Np2D, pMatUpdate : bool = False, forceNew : bool = False): - pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat + pMatEff = self.HFEngine.applyC(pMat) if self.approx_state else pMat if forceNew or self.trainedModel is None: self.trainedModel = self.tModelType() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp datadict = {"mu0": self.mu0, "mus": copy(self.mus), "projMat": pMatEff, "scaleFactor": self.scaleFactor, - "rescalingExp": self.HFEngine.rescalingExp, + "parameterMap": self.HFEngine.parameterMap, "directionPivot": self.directionPivot} self.trainedModel.data = self.initializeModelData(datadict)[0] else: self.trainedModel = self.trainedModel if pMatUpdate: self.trainedModel.data.projMat = np.hstack( (self.trainedModel.data.projMat, pMatEff)) else: self.trainedModel.data.projMat = copy(pMatEff) self.trainedModel.data.mus = copy(self.mus) self.trainedModel.data.musMarginal = copy(self.musMarginal) def normApprox(self, mu:paramList) -> float: _PODOld = self.POD self._POD = False result = super().normApprox(mu) self._POD = _PODOld return result @property def storedSamplesFilenames(self) -> List[str]: if not hasattr(self, "_sampleBaseFilename"): return [] return [self._sampleBaseFilename + "{}_{}.pkl" .format(idx + 1, self.name()) for idx in range(len(self.musMarginal))] + def purgeStoredSamples(self): + if not hasattr(self, "_sampleBaseFilename"): return + try: + for file in self.storedSamplesFilenames: remove(file) + except: + RROMPyWarning(("Could not delete file {}. Aborting purge of " + "stored samples.").format(file)) + return + try: + rmdir(self._sampleBaseFilename[: -8]) + except: + RROMPyWarning(("Could not delete base folder containing stored " + "samples.")) + return + def storeSamples(self, idx : int = None): """Store samples to file.""" if not hasattr(self, "_sampleBaseFilename"): filenameBase = None if poolRank() == 0: foldername = getNewFilename(self.name(), "samples") mkdir(foldername) filenameBase = foldername + "/sample_" - self._sampleBaseFilename = bcastForce(filenameBase) + self._sampleBaseFilename = bcast(filenameBase, force = True) if idx is not None: super().storeSamples(self._sampleBaseFilename + str(idx + 1), False) def loadTrainedModel(self, filename:str): """Load trained reduced model from file.""" super().loadTrainedModel(filename) self._musMarginal = self.trainedModel.data.musMarginal class GenericPivotedApproximantNoMatch(GenericPivotedApproximantBase): """ ROM pivoted approximant (without 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - '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; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; 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. 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; - 'scaleFactorDer': scaling factors for derivative computation; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant. 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. scaleFactorDer: Scaling factors for derivative computation. - cutOffTolerance: Tolerance for ignoring parasitic poles. 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. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. muBounds: 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 tModelType(self): from .trained_model.trained_model_pivoted_rational_nomatch import ( TrainedModelPivotedRationalNoMatch) return TrainedModelPivotedRationalNoMatch def _finalizeMarginalization(self): - vbMng(self, "INIT", "Recompressing by cut off.", 10) - msg = self.trainedModel.recompressByCutOff(self.cutOffTolerance, - self.samplerPivot.normalFoci(), - self.samplerPivot.groundPotential()) - vbMng(self, "DEL", "Done recompressing." + msg, 10) self.trainedModel.setupMarginalInterp( - self.radialDirectionalWeightsMarginal) + [self.radialDirectionalWeightsMarginal]) self.trainedModel.data.approxParameters = copy(self.approxParameters) + def _poleMatching(self): + vbMng(self, "INIT", "Compressing poles.", 10) + self.trainedModel.initializeFromRational() + vbMng(self, "DEL", "Done compressing poles.", 10) + class GenericPivotedApproximant(GenericPivotedApproximantBase): """ 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'matchingMode': mode for pole matching optimization; allowed values include 'NONE' and 'SHIFT'; defaults to 'NONE'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; defaults to 1.; + - 'sharedRatio': required ratio of marginal points to share + resonance; defaults to 1.; - '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_*', 'LEGENDRE_*', 'NEARESTNEIGHBOR', and 'PIECEWISE_LINEAR_*'; defaults to 'MONOMIAL'; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'nNeighborsMarginal': number of marginal nearest neighbors; - defaults to 1; only for 'NEARESTNEIGHBOR' or - 'PIECEWISE_LINEAR_*'; + defaults to 1; only for 'NEARESTNEIGHBOR'; . 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'interpRcondMarginal': tolerance for marginal interpolation; - defaults to None; not for 'NEARESTNEIGHBOR'. + defaults to None; not for 'NEARESTNEIGHBOR'; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights; only for + radial basis. - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; 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. 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; - 'scaleFactorDer': scaling factors for derivative computation; - 'matchingWeight': weight for pole matching optimization; - 'matchingMode': mode for pole matching optimization; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; + - 'sharedRatio': required ratio of marginal points to share + resonance; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; . 'nNeighborsMarginal': number of marginal nearest neighbors; . 'polydegreetypeMarginal': type of polynomial degree for marginal; - . 'interpRcondMarginal': tolerance for marginal interpolation. + . 'interpRcondMarginal': tolerance for marginal interpolation; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights. - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant. 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. scaleFactorDer: Scaling factors for derivative computation. matchingWeight: Weight for pole matching optimization. matchingMode: Mode for pole matching optimization. - cutOffTolerance: Tolerance for ignoring parasitic poles. - cutOffSharedRatio: Required ratio of marginal points to share resonance - in cut off strategy. + sharedRatio: Required ratio of marginal points to share resonance. 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. paramsMarginal: Dictionary of parameters for marginal interpolation. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. muBounds: 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() self._addParametersToList(["matchingWeight", "matchingMode", - "cutOffSharedRatio", "polybasisMarginal", + "sharedRatio", "polybasisMarginal", "paramsMarginal"], [1., "NONE", 1., "MONOMIAL", {}]) self.parameterMarginalList = ["MMarginal", "nNeighborsMarginal", "polydegreetypeMarginal", - "interpRcondMarginal"] + "interpRcondMarginal", + "radialDirectionalWeightsMarginalAdapt"] super().__init__(*args, **kwargs) self._postInit() @property def tModelType(self): from .trained_model.trained_model_pivoted_rational import ( TrainedModelPivotedRational) return TrainedModelPivotedRational @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 matchingMode(self): """Value of matchingMode.""" return self._matchingMode @matchingMode.setter def matchingMode(self, matchingMode): matchingMode = matchingMode.upper().strip().replace(" ", "") if matchingMode != "NONE" and matchingMode[: 5] != "SHIFT": raise RROMPyException("Prescribed matching mode not recognized.") self._matchingMode = matchingMode self._approxParameters["matchingMode"] = self.matchingMode @property - def cutOffSharedRatio(self): - """Value of cutOffSharedRatio.""" - return self._cutOffSharedRatio - @cutOffSharedRatio.setter - def cutOffSharedRatio(self, cutOffSharedRatio): - if cutOffSharedRatio > 1.: - RROMPyWarning("Cut off shared ratio too large. Clipping to 1.") - cutOffSharedRatio = 1. - elif cutOffSharedRatio < 0.: - RROMPyWarning("Cut off shared ratio too small. Clipping to 0.") - cutOffSharedRatio = 0. - self._cutOffSharedRatio = cutOffSharedRatio - self._approxParameters["cutOffSharedRatio"] = self.cutOffSharedRatio + def sharedRatio(self): + """Value of sharedRatio.""" + return self._sharedRatio + @sharedRatio.setter + def sharedRatio(self, sharedRatio): + if sharedRatio > 1.: + RROMPyWarning("Shared ratio too large. Clipping to 1.") + sharedRatio = 1. + elif sharedRatio < 0.: + RROMPyWarning("Shared ratio too small. Clipping to 0.") + sharedRatio = 0. + self._sharedRatio = sharedRatio + self._approxParameters["sharedRatio"] = self.sharedRatio @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 + ["NEARESTNEIGHBOR"] + sk: 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 paramsMarginal(self): """Value of paramsMarginal.""" return self._paramsMarginal @paramsMarginal.setter def paramsMarginal(self, paramsMarginal): paramsMarginal = purgeDict(paramsMarginal, self.parameterMarginalList, dictname = self.name() + ".paramsMarginal", baselevel = 1) keyList = list(paramsMarginal.keys()) if not hasattr(self, "_paramsMarginal"): self._paramsMarginal = {} if "MMarginal" in keyList: MMarg = paramsMarginal["MMarginal"] elif ("MMarginal" in self.paramsMarginal and not hasattr(self, "_MMarginal_isauto")): MMarg = self.paramsMarginal["MMarginal"] else: MMarg = "AUTO" if isinstance(MMarg, str): MMarg = MMarg.strip().replace(" ","") if "-" not in MMarg: MMarg = MMarg + "-0" self._MMarginal_isauto = True self._MMarginal_shift = int(MMarg.split("-")[-1]) MMarg = 0 if MMarg < 0: raise RROMPyException("MMarginal must be non-negative.") self._paramsMarginal["MMarginal"] = MMarg if "nNeighborsMarginal" in keyList: self._paramsMarginal["nNeighborsMarginal"] = max(1, paramsMarginal["nNeighborsMarginal"]) elif "nNeighborsMarginal" not in self.paramsMarginal: self._paramsMarginal["nNeighborsMarginal"] = 1 if "polydegreetypeMarginal" in keyList: try: polydegtypeM = paramsMarginal["polydegreetypeMarginal"]\ .upper().strip().replace(" ","") if polydegtypeM not in ["TOTAL", "FULL"]: raise RROMPyException(("Prescribed polydegreetypeMarginal " "not recognized.")) self._paramsMarginal["polydegreetypeMarginal"] = polydegtypeM except: RROMPyWarning(("Prescribed polydegreetypeMarginal not " "recognized. Overriding to 'TOTAL'.")) self._paramsMarginal["polydegreetypeMarginal"] = "TOTAL" elif "polydegreetypeMarginal" not in self.paramsMarginal: self._paramsMarginal["polydegreetypeMarginal"] = "TOTAL" if "interpRcondMarginal" in keyList: self._paramsMarginal["interpRcondMarginal"] = ( paramsMarginal["interpRcondMarginal"]) elif "interpRcondMarginal" not in self.paramsMarginal: self._paramsMarginal["interpRcondMarginal"] = -1 + + if "radialDirectionalWeightsMarginalAdapt" in keyList: + self._paramsMarginal["radialDirectionalWeightsMarginalAdapt"] = ( + paramsMarginal["radialDirectionalWeightsMarginalAdapt"]) + elif "radialDirectionalWeightsMarginalAdapt" not in self.paramsMarginal: + self._paramsMarginal["radialDirectionalWeightsMarginalAdapt"] = [ + -1., -1.] self._approxParameters["paramsMarginal"] = self.paramsMarginal def _setMMarginalAuto(self): if (self.polybasisMarginal not in ppb + rbpb or "MMarginal" not in self.paramsMarginal or "polydegreetypeMarginal" not in self.paramsMarginal): raise RROMPyException(("Cannot set MMarginal if " "polybasisMarginal does not allow it.")) self.paramsMarginal["MMarginal"] = max(0, reduceDegreeN( len(self.musMarginal), len(self.musMarginal), self.nparMarginal, self.paramsMarginal["polydegreetypeMarginal"]) - self._MMarginal_shift) vbMng(self, "MAIN", ("Automatically setting MMarginal to {}.").format( self.paramsMarginal["MMarginal"]), 25) def purgeparamsMarginal(self): self.paramsMarginal = {} paramsMbadkeys = [] if self.polybasisMarginal in ppb + rbpb + sk: paramsMbadkeys += ["nNeighborsMarginal"] + if self.polybasisMarginal not in rbpb: + paramsMbadkeys += ["radialDirectionalWeightsMarginalAdapt"] if self.polybasisMarginal in ["NEARESTNEIGHBOR"] + sk: paramsMbadkeys += ["MMarginal", "polydegreetypeMarginal"] if hasattr(self, "_MMarginal_isauto"): del self._MMarginal_isauto if hasattr(self, "_MMarginal_shift"): del self._MMarginal_shift if self.polybasisMarginal == "NEARESTNEIGHBOR": paramsMbadkeys += ["interpRcondMarginal"] for key in paramsMbadkeys: if key in self._paramsMarginal: del self._paramsMarginal[key] self._approxParameters["paramsMarginal"] = self.paramsMarginal def _finalizeMarginalization(self): - vbMng(self, "INIT", "Recompressing by cut off.", 10) - msg = self.trainedModel.recompressByCutOff(self.cutOffTolerance, - self.cutOffSharedRatio, - self.samplerPivot.normalFoci(), - self.samplerPivot.groundPotential()) - vbMng(self, "DEL", "Done recompressing." + msg, 10) - if self.polybasisMarginal == "NEARESTNEIGHBOR": - interpPars = [self.paramsMarginal["nNeighborsMarginal"]] - else: - interpPars = [{"rcond":self.paramsMarginal["interpRcondMarginal"]}] - if self.polybasisMarginal in ppb + rbpb: - interpPars = [self.verbosity >= 5, - self.paramsMarginal["polydegreetypeMarginal"] == "TOTAL", - {}] + interpPars - extraPar = hasattr(self, "_reduceDegreeNNoWarn") - if self.polybasisMarginal in ppb: - rDWMEff = None - else: #if self.polybasisMarginal in rbpb + ["NEARESTNEIGHBOR"] + sk: + vbMng(self, "INIT", "Checking shared ratio.", 10) + msg = self.trainedModel.checkSharedRatio(self.sharedRatio) + vbMng(self, "DEL", "Done checking." + msg, 10) + if self.polybasisMarginal in rbpb + ["NEARESTNEIGHBOR"]: self.computeScaleFactor() - rDWMEff = [w * f for w, f in zip( + rDWMEff = np.array([w * f for w, f in zip( self.radialDirectionalWeightsMarginal, - self.scaleFactorMarginal)] - if self.polybasisMarginal in sk: - idxEff = [x for x in range(self.samplerMarginal.npoints) + self.scaleFactorMarginal)]) + if self.polybasisMarginal in ppb + rbpb + sk: + addPars = [] + if self.polybasisMarginal in ppb + rbpb: + if self.polybasisMarginal in rbpb: addPars += [rDWMEff] + addPars += [self.verbosity >= 5, + self.paramsMarginal["polydegreetypeMarginal"] == "TOTAL"] + if self.polybasisMarginal in ppb: + addPars += [{}] + else: # if self.polybasisMarginal in rbpb: + addPars += [{"optimizeScalingBounds":self.paramsMarginal[ + "radialDirectionalWeightsMarginalAdapt"]}] + extraPar = hasattr(self, "_MMarginal_isauto") + else: # if self.polybasisMarginal in sk: + idxEff = [x for x in range(self.samplerMarginal.npoints) if not hasattr(self.trainedModel, "_idxExcl") or x not in self.trainedModel._idxExcl] - extraPar = self.samplerMarginal.depth[idxEff] - self.trainedModel.setupMarginalInterp(self, interpPars, - hasattr(self, "_MMarginal_isauto"), - rDWMEff, extraPar) + extraPar = self.samplerMarginal.depth[idxEff] + interpPars = [self.polybasisMarginal] + addPars + [ + {"rcond":self.paramsMarginal["interpRcondMarginal"]}] + else: # if self.polybasisMarginal == "NEARESTNEIGHBOR": + interpPars = [self.paramsMarginal["nNeighborsMarginal"], rDWMEff] + extraPar = None + self.trainedModel.setupMarginalInterp(self, interpPars, extraPar) self.trainedModel.data.approxParameters = copy(self.approxParameters) + + def _poleMatching(self): + vbMng(self, "INIT", "Compressing and matching poles.", 10) + self.trainedModel.initializeFromRational(self.matchingWeight, + self.matchingMode, + self.HFEngine, False) + vbMng(self, "DEL", "Done compressing and matching poles.", 10) + + def setupApprox(self, *args, **kwargs) -> int: + if self.checkComputedApprox(): return -1 + self.purgeparamsMarginal() + return super().setupApprox(*args, **kwargs) 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 838ecc5..6f1960b 100644 --- a/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py +++ b/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py @@ -1,837 +1,828 @@ # 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 copy import deepcopy as copy import numpy as np from matplotlib import pyplot as plt from rrompy.reduction_methods.pivoted.generic_pivoted_approximant import ( GenericPivotedApproximantBase, GenericPivotedApproximantNoMatch, GenericPivotedApproximant) +from rrompy.reduction_methods.pivoted.gather_pivoted_approximant import ( + gatherPivotedApproximant) from rrompy.utilities.base.types import (Np1D, Np2D, Tuple, List, paramVal, paramList, ListAny) from rrompy.utilities.base import verbosityManager as vbMng 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 -from rrompy.utilities.parallel import (COMM, poolRank, masterCore, - indicesScatter, listGather, - arrayGatherv, matrixGatherv) +from rrompy.parameter import emptyParameterList +from rrompy.utilities.parallel import (masterCore, indicesScatter, + arrayGatherv, isend) __all__ = ['GenericPivotedGreedyApproximantNoMatch', 'GenericPivotedGreedyApproximant'] class GenericPivotedGreedyApproximantBase(GenericPivotedApproximantBase): _allowedEstimatorKindsMarginal = ["LEAVE_ONE_OUT", "LOOK_AHEAD", "LOOK_AHEAD_RECOVER", "NONE"] def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(["matchingWeightError", "cutOffToleranceError", "errorEstimatorKindMarginal", "greedyTolMarginal", "maxIterMarginal"], [0., "AUTO", "NONE", 1e-1, 1e2]) super().__init__(*args, **kwargs) self._postInit() @property def scaleFactorDer(self): """Value of scaleFactorDer.""" if self._scaleFactorDer == "NONE": return 1. if self._scaleFactorDer == "AUTO": return self._scaleFactorOldPivot return self._scaleFactorDer @scaleFactorDer.setter def scaleFactorDer(self, scaleFactorDer): if isinstance(scaleFactorDer, (str,)): scaleFactorDer = scaleFactorDer.upper() elif hasattr(scaleFactorDer, "__len__"): scaleFactorDer = list(scaleFactorDer) self._scaleFactorDer = scaleFactorDer self._approxParameters["scaleFactorDer"] = self._scaleFactorDer @property def samplerMarginal(self): """Value of samplerMarginal.""" return self._samplerMarginal @samplerMarginal.setter def samplerMarginal(self, samplerMarginal): if 'refine' not in dir(samplerMarginal): raise RROMPyException("Marginal sampler type not recognized.") GenericPivotedApproximantBase.samplerMarginal.fset(self, samplerMarginal) @property def errorEstimatorKindMarginal(self): """Value of errorEstimatorKindMarginal.""" return self._errorEstimatorKindMarginal @errorEstimatorKindMarginal.setter def errorEstimatorKindMarginal(self, errorEstimatorKindMarginal): errorEstimatorKindMarginal = errorEstimatorKindMarginal.upper() if errorEstimatorKindMarginal not in ( self._allowedEstimatorKindsMarginal): RROMPyWarning(("Marginal error estimator kind not recognized. " "Overriding to 'NONE'.")) errorEstimatorKindMarginal = "NONE" self._errorEstimatorKindMarginal = errorEstimatorKindMarginal self._approxParameters["errorEstimatorKindMarginal"] = ( self.errorEstimatorKindMarginal) @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, "samplerMarginal"): self.samplerMarginal.reset() if hasattr(self, "samplingEngine") and self.samplingEngine is not None: self.samplingEngine.resetHistory() def _getPolesResExact(self, HITest, foci:Tuple[float, float], ground:float) -> Tuple[Np1D, Np2D]: if self.cutOffToleranceError == "AUTO": cutOffTolErr = self.cutOffTolerance else: cutOffTolErr = self.cutOffToleranceError polesEx = copy(HITest.poles) idxExEff = np.where(potential(polesEx, foci) - ground <= cutOffTolErr * ground)[0] if self.matchingWeightError != 0: resEx = HITest.coeffs[idxExEff] else: resEx = None return polesEx[idxExEff], resEx def _getDistanceApp(self, polesEx:Np1D, resEx:Np2D, muTest:paramVal, foci:Tuple[float, float], ground:float) -> float: if self.cutOffToleranceError == "AUTO": cutOffTolErr = self.cutOffTolerance else: cutOffTolErr = self.cutOffToleranceError 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)[0][ idxApEff, :] resEx = self.trainedModel.data.projMat[:, : resEx.shape[1]].dot(resEx.T) resAp = self.trainedModel.data.projMat[:, : resAp.shape[1]].dot(resAp.T) else: resAp = None dist = chordalMetricAdjusted(polesEx, polesAp, self.matchingWeightError, resEx, resAp, self.HFEngine, False) pmR, pmC = pointMatching(dist) return np.mean(dist[pmR, pmC]) def getErrorEstimatorMarginalLeaveOneOut(self) -> Np1D: nTest = len(self.trainedModel.data.musMarginal) self._musMarginalTestIdxs = np.arange(nTest) if nTest <= 1: err = np.empty(nTest) err[:] = np.inf return err idx, sizes = indicesScatter(nTest, return_sizes = True) err = [] if len(idx) > 0: _tMdataFull = copy(self.trainedModel.data) _musMExcl = None self.verbosity -= 35 self.trainedModel.verbosity -= 35 foci = self.samplerPivot.normalFoci() ground = self.samplerPivot.groundPotential() for i, j in enumerate(idx): jEff = j - (i > 0) muTest = self.trainedModel.data.musMarginal[jEff] polesEx, resEx = self._getPolesResExact( self.trainedModel.data.HIs[jEff], foci, ground) if i > 0: self.musMarginal.insert(_musMExcl, j - 1) _musMExcl = self.musMarginal[j] self.musMarginal.pop(j) if len(polesEx) == 0: err += [0.] continue self._updateTrainedModelMarginalSamples([j]) self._finalizeMarginalization() err += [self._getDistanceApp(polesEx, resEx, muTest, foci, ground)] self._updateTrainedModelMarginalSamples() self.musMarginal.insert(_musMExcl, idx[-1]) self.verbosity += 35 self.trainedModel.verbosity += 35 self.trainedModel.data = _tMdataFull return arrayGatherv(np.array(err), sizes) def getErrorEstimatorMarginalLookAhead(self) -> Np1D: if not hasattr(self.trainedModel, "_musMExcl"): err = np.zeros(0) err[:] = np.inf self._musMarginalTestIdxs = np.zeros(0, dtype = int) return err self._musMarginalTestIdxs = np.array(self.trainedModel._idxExcl, dtype = int) idx, sizes = indicesScatter(len(self.trainedModel._musMExcl), return_sizes = True) err = [] if len(idx) > 0: self.verbosity -= 35 self.trainedModel.verbosity -= 35 foci = self.samplerPivot.normalFoci() ground = self.samplerPivot.groundPotential() for j in idx: muTest = self.trainedModel._musMExcl[j] HITest = self.trainedModel._HIsExcl[j] polesEx, resEx = self._getPolesResExact(HITest, foci, ground) if len(polesEx) == 0: err += [0.] continue err += [self._getDistanceApp(polesEx, resEx, muTest, foci, ground)] self.verbosity += 35 self.trainedModel.verbosity += 35 return arrayGatherv(np.array(err), sizes) def getErrorEstimatorMarginalNone(self) -> Np1D: nErr = len(self.trainedModel.data.musMarginal) self._musMarginalTestIdxs = np.arange(nErr) return (1. + self.greedyTolMarginal) * np.ones(nErr) def errorEstimatorMarginal(self, return_max : bool = False) -> Np1D: vbMng(self.trainedModel, "INIT", "Evaluating error estimator at mu = {}.".format( self.trainedModel.data.musMarginal), 10) if self.errorEstimatorKindMarginal == "LEAVE_ONE_OUT": err = self.getErrorEstimatorMarginalLeaveOneOut() elif self.errorEstimatorKindMarginal[: 10] == "LOOK_AHEAD": err = self.getErrorEstimatorMarginalLookAhead() else:#if self.errorEstimatorKindMarginal == "NONE": err = self.getErrorEstimatorMarginalNone() vbMng(self.trainedModel, "DEL", "Done evaluating error estimator", 10) if not return_max: return err idxMaxEst = np.where(err > self.greedyTolMarginal)[0] maxErr = err[idxMaxEst] if self.errorEstimatorKindMarginal == "NONE": maxErr = None return err, idxMaxEst, maxErr def plotEstimatorMarginal(self, est:Np1D, idxMax:List[int], estMax:List[float]): if self.errorEstimatorKindMarginal == "NONE": return if (not (np.any(np.isnan(est)) or np.any(np.isinf(est))) and masterCore()): fig = plt.figure(figsize = plt.figaspect(1. / self.nparMarginal)) for jpar in range(self.nparMarginal): ax = fig.add_subplot(1, self.nparMarginal, 1 + jpar) if self.errorEstimatorKindMarginal == "LEAVE_ONE_OUT": musre = copy(self.trainedModel.data.musMarginal.re.data) else:#if self.errorEstimatorKindMarginal[: 10] == "LOOK_AHEAD": if not hasattr(self.trainedModel, "_musMExcl"): return musre = np.real(self.trainedModel._musMExcl) if len(idxMax) > 0 and estMax is not None: maxrej = musre[idxMax, jpar] 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(maxrej, estMax, 'xr') + ax.set_xlim(*list(self.samplerMarginal.lims.re(jpar))) ax.grid() plt.tight_layout() plt.show() def _addMarginalSample(self, mus:paramList): - mus = checkParameterList(mus, self.nparMarginal)[0] + mus = self.checkParameterListMarginal(mus) if len(mus) == 0: return self._nmusOld, nmus = len(self.musMarginal), len(mus) if (hasattr(self, "trainedModel") and self.trainedModel is not None and hasattr(self.trainedModel, "_musMExcl")): self._nmusOld += len(self.trainedModel._musMExcl) vbMng(self, "MAIN", ("Adding marginal sample point{} no. {}{} at {} to training " "set.").format("s" * (nmus > 1), self._nmusOld + 1, "--{}".format(self._nmusOld + nmus) * (nmus > 1), mus), 3) self.musMarginal.append(mus) self.setupApproxPivoted(mus) self._poleMatching() del self._nmusOld if (self.errorEstimatorKindMarginal[: 10] == "LOOK_AHEAD" and not self.firstGreedyIterM): ubRange = len(self.trainedModel.data.musMarginal) if hasattr(self.trainedModel, "_idxExcl"): shRange = len(self.trainedModel._musMExcl) else: shRange = 0 testIdxs = list(range(ubRange + shRange - len(mus), ubRange + shRange)) for j in testIdxs[::-1]: self.musMarginal.pop(j - shRange) if hasattr(self.trainedModel, "_idxExcl"): testIdxs = self.trainedModel._idxExcl + testIdxs self._updateTrainedModelMarginalSamples(testIdxs) self._finalizeMarginalization() self._SMarginal = len(self.musMarginal) self._approxParameters["SMarginal"] = self.SMarginal self.trainedModel.data.approxParameters["SMarginal"] = self.SMarginal def greedyNextSampleMarginal(self, muidx:List[int], plotEst : str = "NONE") \ -> Tuple[Np1D, List[int], float, paramVal]: RROMPyAssert(self._mode, message = "Cannot add greedy sample.") + muidx = self._musMarginalTestIdxs[muidx] if (self.errorEstimatorKindMarginal[: 10] == "LOOK_AHEAD" and not self.firstGreedyIterM): if not hasattr(self.trainedModel, "_idxExcl"): raise RROMPyException(("Sample index to be added not present " "in trained model.")) testIdxs = copy(self.trainedModel._idxExcl) skippedIdx = 0 for cj, j in enumerate(self.trainedModel._idxExcl): if j in muidx: testIdxs.pop(skippedIdx) self.musMarginal.insert(self.trainedModel._musMExcl[cj], j - skippedIdx) else: skippedIdx += 1 if len(self.trainedModel._idxExcl) < (len(muidx) + len(testIdxs)): raise RROMPyException(("Sample index to be added not present " "in trained model.")) self._updateTrainedModelMarginalSamples(testIdxs) self._SMarginal = len(self.musMarginal) self._approxParameters["SMarginal"] = self.SMarginal self.trainedModel.data.approxParameters["SMarginal"] = ( self.SMarginal) self.firstGreedyIterM = False idxAdded = self.samplerMarginal.refine(muidx) self._addMarginalSample(self.samplerMarginal.points[idxAdded]) errorEstTest, muidx, maxErrorEst = self.errorEstimatorMarginal(True) if plotEst == "ALL": self.plotEstimatorMarginal(errorEstTest, muidx, maxErrorEst) - return (errorEstTest, self._musMarginalTestIdxs[muidx], maxErrorEst, + return (errorEstTest, muidx, maxErrorEst, self.samplerMarginal.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() self._addMarginalSample(self.samplerMarginal.generatePoints( self.SMarginal)) def _preSetupApproxPivoted(self, mus:paramList) \ -> Tuple[ListAny, ListAny, ListAny]: self.computeScaleFactor() if self.trainedModel is None: self._setupTrainedModel(np.zeros((0, 0))) self.trainedModel.data.Qs, self.trainedModel.data.Ps = [], [] self.trainedModel.data.Psupp = [] self._trainedModelOld = copy(self.trainedModel) self._scaleFactorOldPivot = copy(self.scaleFactor) self.scaleFactor = self.scaleFactorPivot self._temporaryPivot = 1 self._musLoc = copy(self.mus) idx, sizes = indicesScatter(len(mus), return_sizes = True) emptyCores = np.where(np.logical_not(sizes))[0] self.verbosity -= 15 return idx, sizes, emptyCores - def _postSetupApproxPivoted(self, mus:paramList, data:ListAny, pMat:Np2D, - sizes:ListAny): + def _postSetupApproxPivoted(self, mus:Np2D, pMat:Np2D, Ps:ListAny, + Qs:ListAny, sizes:ListAny): self.scaleFactor = self._scaleFactorOldPivot del self._scaleFactorOldPivot, self._temporaryPivot - data = listGather(data) + pMat, Ps, Qs, mus, nsamples = gatherPivotedApproximant(pMat, Ps, Qs, + mus, sizes, + self.polybasis) if len(self._musLoc) > 0: - self._mus = checkParameterList(self._musLoc, self.npar)[0] - self._mus.append(data[0][2]) + self._mus = self.checkParameterList(self._musLoc) + self._mus.append(mus) else: - self._mus = checkParameterList(data[0][2], self.npar)[0] - nsamples, sizesEff, idx = [], [], 0 - for size in sizes: - sizesEff += [0] - for _ in range(size): - _m = data[idx][2] - if idx > 0: self._mus.append(_m) - nsamples += [len(_m)] - sizesEff[-1] += nsamples[-1] - idx += 1 - pMat = matrixGatherv(pMat, sizesEff, False) + self._mus = self.checkParameterList(mus) self.trainedModel = self._trainedModelOld del self._trainedModelOld padLeft = self.trainedModel.data.projMat.shape[1] suppNew = np.append(0, np.cumsum(nsamples)) self._setupTrainedModel(pMat, padLeft > 0) - self.trainedModel.data.Qs += [x[0] for x in data] - self.trainedModel.data.Ps += [x[1] for x in data] + self.trainedModel.data.Qs += Qs + self.trainedModel.data.Ps += Ps self.trainedModel.data.Psupp += list(padLeft + suppNew[: -1]) self.trainedModel.data.approxParameters = copy(self.approxParameters) self.verbosity += 15 - def _localPivotedResult(self, pMat:Np2D, req:ListAny, - emptyCores:ListAny) -> Tuple[Np2D, ListAny, - paramList]: + def _localPivotedResult(self, pMat:Np2D, req:ListAny, emptyCores:ListAny, + mus:Np2D) -> Tuple[Np2D, ListAny, Np2D]: if pMat is None: + mus = copy(self.samplingEngine.mus.data) pMat = copy(self.samplingEngine.projectionMatrix) if masterCore(): for dest in emptyCores: - req += [COMM.isend((len(pMat), pMat.dtype), - dest = dest, tag = dest)] + req += [isend((len(pMat), pMat.dtype, mus.dtype), + dest = dest, tag = dest)] else: + mus = np.vstack((mus, self.samplingEngine.mus.data)) pMat = np.hstack((pMat, self.samplingEngine.projectionMatrix)) - #FIXME - return pMat, req, copy(self.samplingEngine.mus) + return pMat, req, mus @abstractmethod def setupApproxPivoted(self, mus:paramList) -> int: if self.checkComputedApproxPivoted(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up pivoted approximant.", 10) - idx = self._preSetupApproxPivoted() + self._preSetupApproxPivoted() data = [] pass self._postSetupApproxPivoted(mus, data) vbMng(self, "DEL", "Done setting up pivoted approximant.", 10) return 0 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.", 3) max2ErrorEst, self.firstGreedyIterM = np.inf, True self._preliminaryTrainingMarginal() if self.errorEstimatorKindMarginal[: 10] == "LOOK_AHEAD": muidx = np.arange(len(self.trainedModel.data.musMarginal)) else:#if self.errorEstimatorKindMarginal in ["LEAVE_ONE_OUT", "NONE"]: muidx = [] + self._musMarginalTestIdxs = np.array(muidx) while self.firstGreedyIterM or (max2ErrorEst > self.greedyTolMarginal and self.samplerMarginal.npoints < self.maxIterMarginal): errorEstTest, muidx, maxErrorEst, mu = \ self.greedyNextSampleMarginal(muidx, plotEst) if maxErrorEst is None: max2ErrorEst = 1. + self.greedyTolMarginal else: if len(maxErrorEst) > 0: max2ErrorEst = np.max(maxErrorEst) - vbMng(self, "MAIN", ("Uniform testing error estimate " - "{:.4e}.").format(max2ErrorEst), 3) else: - max2ErrorEst = 0. + max2ErrorEst = np.max(errorEstTest) + vbMng(self, "MAIN", ("Uniform testing error estimate " + "{:.4e}.").format(max2ErrorEst), 3) if plotEst == "LAST": self.plotEstimatorMarginal(errorEstTest, muidx, maxErrorEst) vbMng(self, "DEL", ("Done computing snapshots (final snapshot count: " "{}).").format(len(self.mus)), 3) if (self.errorEstimatorKindMarginal == "LOOK_AHEAD_RECOVER" and hasattr(self.trainedModel, "_idxExcl") and len(self.trainedModel._idxExcl) > 0): vbMng(self, "INIT", "Recovering {} test models.".format( len(self.trainedModel._idxExcl)), 7) for j, mu in zip(self.trainedModel._idxExcl, self.trainedModel._musMExcl): self.musMarginal.insert(mu, j) self._updateTrainedModelMarginalSamples() self._finalizeMarginalization() self._SMarginal = len(self.musMarginal) self._approxParameters["SMarginal"] = self.SMarginal self.trainedModel.data.approxParameters["SMarginal"] = ( self.SMarginal) vbMng(self, "DEL", "Done recovering test models.", 7) return 0 def checkComputedApproxPivoted(self) -> bool: return (super().checkComputedApprox() and len(self.musMarginal) == len(self.trainedModel.data.musMarginal)) class GenericPivotedGreedyApproximantNoMatch( GenericPivotedGreedyApproximantBase, GenericPivotedApproximantNoMatch): """ ROM pivoted greedy interpolant computation for parametric problems (without pole matching) (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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - '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; - 'samplerMarginal': marginal sample point generator via sparse grid; - 'errorEstimatorKindMarginal': kind of marginal error estimator; available values include 'LEAVE_ONE_OUT', 'LOOK_AHEAD', 'LOOK_AHEAD_RECOVER', and 'NONE'; defaults to 'NONE'; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; 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. 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; - 'scaleFactorDer': scaling factors for derivative computation; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'errorEstimatorKindMarginal': kind of marginal error estimator; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant. 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 via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. scaleFactorDer: Scaling factors for derivative computation. - cutOffTolerance: Tolerance for ignoring parasitic poles. 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. samplerMarginal: Marginal sample point generator via sparse grid. errorEstimatorKindMarginal: Kind of marginal error estimator. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. muBounds: 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 _poleMatching(self): vbMng(self, "INIT", "Compressing poles.", 10) self.trainedModel.initializeFromRational() vbMng(self, "DEL", "Done compressing poles.", 10) def _updateTrainedModelMarginalSamples(self, idx : ListAny = []): self.trainedModel.updateEffectiveSamples(idx) class GenericPivotedGreedyApproximant(GenericPivotedGreedyApproximantBase, GenericPivotedApproximant): """ ROM pivoted greedy interpolant computation for parametric problems (with pole matching) (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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'matchingMode': mode for pole matching optimization; allowed values include 'NONE' and 'SHIFT'; defaults to 'NONE'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; defaults to 1.; + - 'sharedRatio': required ratio of marginal points to share + resonance; defaults to 1.; - '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; - 'samplerMarginal': marginal sample point generator via sparse grid; - 'errorEstimatorKindMarginal': kind of marginal error estimator; available values include 'LEAVE_ONE_OUT', 'LOOK_AHEAD', 'LOOK_AHEAD_RECOVER', and 'NONE'; defaults to 'NONE'; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL_*', 'CHEBYSHEV_*', 'LEGENDRE_*', 'NEARESTNEIGHBOR', and 'PIECEWISE_LINEAR_*'; defaults to 'MONOMIAL'; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'nNeighborsMarginal': number of marginal nearest neighbors; - defaults to 1; only for 'NEARESTNEIGHBOR' or - 'PIECEWISE_LINEAR_*'; + defaults to 1; only for 'NEARESTNEIGHBOR'; . 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'interpRcondMarginal': tolerance for marginal interpolation; - defaults to None; not for 'NEARESTNEIGHBOR'. + defaults to None; not for 'NEARESTNEIGHBOR'; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights; only for + radial basis. - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; 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. 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; - 'scaleFactorDer': scaling factors for derivative computation; - 'matchingWeight': weight for pole matching optimization; - 'matchingMode': mode for pole matching optimization; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; + - 'sharedRatio': required ratio of marginal points to share + resonance; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'errorEstimatorKindMarginal': kind of marginal error estimator; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; . 'nNeighborsMarginal': number of marginal nearest neighbors; . 'polydegreetypeMarginal': type of polynomial degree for marginal; - . 'interpRcondMarginal': tolerance for marginal interpolation. + . 'interpRcondMarginal': tolerance for marginal interpolation; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights. - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant. 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 via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. scaleFactorDer: Scaling factors for derivative computation. matchingWeight: Weight for pole matching optimization. matchingMode: Mode for pole matching optimization. - cutOffTolerance: Tolerance for ignoring parasitic poles. - cutOffSharedRatio: Required ratio of marginal points to share resonance - in cut off strategy. + sharedRatio: Required ratio of marginal points to share resonance. 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. samplerMarginal: Marginal sample point generator via sparse grid. errorEstimatorKindMarginal: Kind of marginal error estimator. polybasisMarginal: Type of polynomial basis for marginal interpolation. paramsMarginal: Dictionary of parameters for marginal interpolation. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. muBounds: 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 _poleMatching(self): vbMng(self, "INIT", "Compressing and matching poles.", 10) self.trainedModel.initializeFromRational(self.matchingWeight, self.matchingMode, self.HFEngine, False) vbMng(self, "DEL", "Done compressing and matching poles.", 10) def _updateTrainedModelMarginalSamples(self, idx : ListAny = []): self.trainedModel.updateEffectiveSamples(idx, self.matchingWeight, self.matchingMode, self.HFEngine, False) def getErrorEstimatorMarginalLeaveOneOut(self) -> Np1D: if self.polybasisMarginal != "NEARESTNEIGHBOR": if not hasattr(self, "_MMarginal_isauto"): if not hasattr(self, "_MMarginalOriginal"): self._MMarginalOriginal = self.paramsMarginal["MMarginal"] self.paramsMarginal["MMarginal"] = self._MMarginalOriginal self._reduceDegreeNNoWarn = 1 err = super().getErrorEstimatorMarginalLeaveOneOut() if self.polybasisMarginal != "NEARESTNEIGHBOR": del self._reduceDegreeNNoWarn return err def setupApprox(self, *args, **kwargs) -> int: if self.checkComputedApprox(): return -1 self.purgeparamsMarginal() return super().setupApprox(*args, **kwargs) 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 6e19636..b6a27cc 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,526 +1,519 @@ #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 ( GenericPivotedGreedyApproximantBase, GenericPivotedGreedyApproximantNoMatch, GenericPivotedGreedyApproximant) 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 ( RationalInterpolantGreedyPivotedNoMatch, RationalInterpolantGreedyPivoted) 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 -from rrompy.utilities.parallel import COMM, poolRank +from rrompy.utilities.parallel import poolRank, recv __all__ = ['RationalInterpolantGreedyPivotedGreedyNoMatch', 'RationalInterpolantGreedyPivotedGreedy'] class RationalInterpolantGreedyPivotedGreedyBase( GenericPivotedGreedyApproximantBase): @property def sampleBatchSize(self): """Value of sampleBatchSize.""" return 1 @property def sampleBatchIdx(self): """Value of sampleBatchIdx.""" return self.S 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), 3) 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)])): + or not np.allclose(mu, self.samplingEngine.mus[j - len(mus)])): self.samplingEngine.nextSample(mu) if self._isLastSampleCollinear(): vbMng(self, "MAIN", ("Collinearity above tolerance detected. Starting " "preemptive greedy loop termination."), 3) 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 _setSampleBatch(self, maxS:int): return self.S 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() self.samplingEngine.scaleFactor = self.scaleFactorDer 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]) + idxPop = pruneSamples(self.HFEngine.mapParameterList(muTestBasePivot, + idx = self.directionPivot), + self.HFEngine.mapParameterList(musPivot, + idx = self.directionPivot), + 1e-10 * self.scaleFactorPivot[0]) muTestBasePivot.pop(idxPop) - self.mus = emptyParameterList() + 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.muMargLoc + muk = np.empty_like(self.mus[0]) + muk[self.directionPivot] = musPivot[k] + muk[self.directionMarginal] = self.muMargLoc + self.mus[k] = muk for k in range(len(muTestBasePivot)): - self.muTest.data[k, self.directionPivot] = muTestBasePivot[k].data - self.muTest.data[k, self.directionMarginal] = self.muMargLoc - self.muTest.data[-1, self.directionPivot] = musPivot[-1].data - self.muTest.data[-1, self.directionMarginal] = self.muMargLoc + muk = np.empty_like(self.muTest[0]) + muk[self.directionPivot] = muTestBasePivot[k] + muk[self.directionMarginal] = self.muMargLoc + self.muTest[k] = muk + muk = np.empty_like(self.mus[0]) + muk[self.directionPivot] = musPivot[-1] + muk[self.directionMarginal] = self.muMargLoc + self.muTest[-1] = muk 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), 3) self.samplingEngine.iterSample(self.mus) self._S = len(self.mus) self._approxParameters["S"] = self.S self.M, self.N = ("AUTO",) * 2 def setupApproxPivoted(self, mus:paramList) -> int: if self.checkComputedApproxPivoted(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up pivoted approximant.", 10) if not hasattr(self, "_plotEstPivot"): self._plotEstPivot = "NONE" idx, sizes, emptyCores = self._preSetupApproxPivoted(mus) S0 = copy(self.S) - data, pMat, req = [], None, [] + pMat, Ps, Qs, req, musA = None, [], [], [], None if len(idx) == 0: vbMng(self, "MAIN", "Idling.", 45) if self.storeAllSamples: self.storeSamples() - pL, pT = COMM.recv(source = 0, tag = poolRank()) + pL, pT, mT = recv(source = 0, tag = poolRank()) pMat = np.empty((pL, 0), dtype = pT) + musA = np.empty((0, self.mu0.shape[1]), dtype = mT) else: for i in idx: self.muMargLoc = mus[i] vbMng(self, "MAIN", "Building marginal model no. {} at " "{}.".format(i + 1, self.muMargLoc), 25) self.samplingEngine.resetHistory() self.trainedModel = None self.verbosity -= 5 self.samplingEngine.verbosity -= 5 RationalInterpolantGreedy.setupApprox(self, self._plotEstPivot) self.verbosity += 5 self.samplingEngine.verbosity += 5 if self.storeAllSamples: self.storeSamples(i + self._nmusOld) - pMat, req, _m = self._localPivotedResult(pMat, req, emptyCores) - data += [(copy(self.trainedModel.data.Q), - copy(self.trainedModel.data.P), _m)] + pMat, req, musA = self._localPivotedResult(pMat, req, + emptyCores, musA) + Ps += [copy(self.trainedModel.data.P)] + Qs += [copy(self.trainedModel.data.Q)] self._S = S0 del self.muMargLoc for r in req: r.wait() - self._postSetupApproxPivoted(mus, data, pMat, sizes) + self._postSetupApproxPivoted(musA, pMat, Ps, Qs, sizes) vbMng(self, "DEL", "Done setting up pivoted 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 class RationalInterpolantGreedyPivotedGreedyNoMatch( RationalInterpolantGreedyPivotedGreedyBase, GenericPivotedGreedyApproximantNoMatch, RationalInterpolantGreedyPivotedNoMatch): """ ROM greedy pivoted greedy rational interpolant computation for parametric problems (without pole matching). 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - '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; - 'samplerMarginal': marginal sample point generator via sparse grid; - 'errorEstimatorKindMarginal': kind of marginal error estimator; available values include 'LEAVE_ONE_OUT', 'LOOK_AHEAD', and 'LOOK_AHEAD_RECOVER'; defaults to 'LEAVE_ONE_OUT'; - 'polybasis': type of polynomial basis for pivot interpolation; 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', 'LOOK_AHEAD', 'LOOK_AHEAD_RES', and 'NONE'; defaults to 'NONE'; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'errorEstimatorKindMarginal': kind of marginal error estimator; - 'polybasis': type of polynomial basis for pivot 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; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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 via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. scaleFactorDer: Scaling factors for derivative computation. - cutOffTolerance: Tolerance for ignoring parasitic poles. 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. samplerMarginal: Marginal sample point generator via sparse grid. errorEstimatorKindMarginal: Kind of marginal error estimator. polybasis: Type of polynomial basis for pivot 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. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. interpRcond: Tolerance for pivot 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. muBounds: 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. """ class RationalInterpolantGreedyPivotedGreedy( RationalInterpolantGreedyPivotedGreedyBase, GenericPivotedGreedyApproximant, RationalInterpolantGreedyPivoted): """ ROM greedy pivoted greedy rational interpolant computation for parametric problems (with pole matching). 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'matchingMode': mode for pole matching optimization; allowed values include 'NONE' and 'SHIFT'; defaults to 'NONE'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; defaults to 1.; + - 'sharedRatio': required ratio of marginal points to share + resonance; defaults to 1.; - '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; - 'samplerMarginal': marginal sample point generator via sparse grid; - 'errorEstimatorKindMarginal': kind of marginal error estimator; available values include 'LEAVE_ONE_OUT', 'LOOK_AHEAD', and 'LOOK_AHEAD_RECOVER'; defaults to 'LEAVE_ONE_OUT'; - 'polybasis': type of polynomial basis for pivot interpolation; defaults to 'MONOMIAL'; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL_*', 'CHEBYSHEV_*', 'LEGENDRE_*', 'NEARESTNEIGHBOR', and 'PIECEWISE_LINEAR_*'; defaults to 'MONOMIAL'; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'nNeighborsMarginal': number of marginal nearest neighbors; - defaults to 1; only for 'NEARESTNEIGHBOR' or - 'PIECEWISE_LINEAR_*'; + defaults to 1; only for 'NEARESTNEIGHBOR'; . 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'interpRcondMarginal': tolerance for marginal interpolation; - defaults to None; not for 'NEARESTNEIGHBOR'. + defaults to None; not for 'NEARESTNEIGHBOR'; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights; only for + radial basis. - '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', 'LOOK_AHEAD', 'LOOK_AHEAD_RES', and 'NONE'; defaults to 'NONE'; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - 'matchingWeight': weight for pole matching optimization; - 'matchingMode': mode for pole matching optimization; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; + - 'sharedRatio': required ratio of marginal points to share + resonance; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'errorEstimatorKindMarginal': kind of marginal error estimator; - 'polybasis': type of polynomial basis for pivot interpolation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; . 'nNeighborsMarginal': number of marginal nearest neighbors; . 'polydegreetypeMarginal': type of polynomial degree for marginal; - . 'interpRcondMarginal': tolerance for marginal interpolation. + . 'interpRcondMarginal': tolerance for marginal interpolation; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights. - '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; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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 via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. scaleFactorDer: Scaling factors for derivative computation. matchingWeight: Weight for pole matching optimization. matchingMode: Mode for pole matching optimization. - cutOffTolerance: Tolerance for ignoring parasitic poles. - cutOffSharedRatio: Required ratio of marginal points to share resonance - in cut off strategy. + sharedRatio: Required ratio of marginal points to share resonance. 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. samplerMarginal: Marginal sample point generator via sparse grid. errorEstimatorKindMarginal: Kind of marginal error estimator. polybasis: Type of polynomial basis for pivot interpolation. polybasisMarginal: Type of polynomial basis for marginal interpolation. paramsMarginal: Dictionary of parameters 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. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. interpRcond: Tolerance for pivot 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. muBounds: 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. """ 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 ecb8fde..26ad0ea 100644 --- a/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py +++ b/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py @@ -1,444 +1,446 @@ # 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 numpy import empty +from numpy import empty, empty_like from .generic_pivoted_greedy_approximant import ( GenericPivotedGreedyApproximantBase, GenericPivotedGreedyApproximantNoMatch, GenericPivotedGreedyApproximant) from rrompy.reduction_methods.standard import RationalInterpolant from rrompy.reduction_methods.pivoted import ( RationalInterpolantPivotedNoMatch, RationalInterpolantPivoted) 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 -from rrompy.utilities.parallel import COMM, poolRank +from rrompy.parameter import emptyParameterList +from rrompy.utilities.parallel import poolRank, recv __all__ = ['RationalInterpolantPivotedGreedyNoMatch', 'RationalInterpolantPivotedGreedy'] class RationalInterpolantPivotedGreedyBase( GenericPivotedGreedyApproximantBase): 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.samplingEngine.scaleFactor = self.scaleFactorDer if not hasattr(self, "musPivot") or len(self.musPivot) != self.S: self.musPivot = self.samplerPivot.generatePoints(self.S) while len(self.musPivot) > self.S: self.musPivot.pop() musLoc = emptyParameterList() musLoc.reset((self.S, self.HFEngine.npar)) self.samplingEngine.resetHistory() for k in range(self.S): - musLoc.data[k, self.directionPivot] = self.musPivot[k].data - musLoc.data[k, self.directionMarginal] = self.muMargLoc + muk = empty_like(musLoc[0]) + muk[self.directionPivot] = self.musPivot[k] + muk[self.directionMarginal] = self.muMargLoc + musLoc[k] = muk self.samplingEngine.iterSample(musLoc) vbMng(self, "DEL", "Done computing snapshots.", 5) self._m_selfmus = copy(musLoc) 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]]] + self._m_HFEparameterMap = copy(self.HFEngine.parameterMap) + self._mu0 = self.checkParameterListPivot(self.mu0(self.directionPivot)) + self.HFEngine.parameterMap = { + "F": [self.HFEngine.parameterMap["F"][self.directionPivot[0]]], + "B": [self.HFEngine.parameterMap["B"][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 pivoted approximant.", 10) idx, sizes, emptyCores = self._preSetupApproxPivoted(mus) - data, pMat, req = [], None, [] + pMat, Ps, Qs, req, musA = None, [], [], [], None if len(idx) == 0: vbMng(self, "MAIN", "Idling.", 45) if self.storeAllSamples: self.storeSamples() - pL, pT = COMM.recv(source = 0, tag = poolRank()) + pL, pT, mT = recv(source = 0, tag = poolRank()) pMat = empty((pL, 0), dtype = pT) + musA = empty((0, self.mu0.shape[1]), dtype = mT) else: for i in idx: self.muMargLoc = mus[i] vbMng(self, "MAIN", "Building marginal model no. {} at " "{}.".format(i + 1, self.muMargLoc), 25) self.samplingEngine.resetHistory() self.trainedModel = None self.verbosity -= 5 self.samplingEngine.verbosity -= 5 RationalInterpolant.setupApprox(self) self.verbosity += 5 self.samplingEngine.verbosity += 5 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.HFEngine.parameterMap = self._m_HFEparameterMap + del self._m_mu0, self._m_selfmus, self._m_HFEparameterMap if self.storeAllSamples: self.storeSamples(i + self._nmusOld) - pMat, req, _m = self._localPivotedResult(pMat, req, emptyCores) - data += [(copy(self.trainedModel.data.Q), - copy(self.trainedModel.data.P), _m)] + pMat, req, musA = self._localPivotedResult(pMat, req, + emptyCores, musA) + Ps += [copy(self.trainedModel.data.P)] + Qs += [copy(self.trainedModel.data.Q)] del self.muMargLoc for r in req: r.wait() - self._postSetupApproxPivoted(mus, data, pMat, sizes) + self._postSetupApproxPivoted(musA, pMat, Ps, Qs, sizes) vbMng(self, "DEL", "Done setting up pivoted approximant.", 10) return 0 class RationalInterpolantPivotedGreedyNoMatch( RationalInterpolantPivotedGreedyBase, GenericPivotedGreedyApproximantNoMatch, RationalInterpolantPivotedNoMatch): """ ROM pivoted greedy rational interpolant computation for parametric problems (without pole matching). 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - '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; - 'samplerMarginal': marginal sample point generator via sparse grid; - 'errorEstimatorKindMarginal': kind of marginal error estimator; available values include 'LEAVE_ONE_OUT', 'LOOK_AHEAD', and 'LOOK_AHEAD_RECOVER'; defaults to 'LEAVE_ONE_OUT'; - 'polybasis': type of polynomial basis for pivot 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; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'radialDirectionalWeights': radial basis weights for pivot numerator; defaults to 1; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; defaults to [-1, -1]; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'errorEstimatorKindMarginal': kind of marginal error estimator; - 'polybasis': type of polynomial basis for pivot interpolation; - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'radialDirectionalWeights': radial basis weights for pivot numerator; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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 via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. scaleFactorDer: Scaling factors for derivative computation. - cutOffTolerance: Tolerance for ignoring parasitic poles. 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. samplerMarginal: Marginal sample point generator via sparse grid. errorEstimatorKindMarginal: Kind of marginal error estimator. polybasis: Type of polynomial basis for pivot interpolation. M: Degree of rational interpolant numerator. N: Degree of rational interpolant denominator. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. radialDirectionalWeights: Radial basis weights for pivot numerator. + radialDirectionalWeightsAdapt: Bounds for adaptive rescaling of radial + basis weights. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. interpRcond: Tolerance for pivot 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. muBounds: 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. """ class RationalInterpolantPivotedGreedy(RationalInterpolantPivotedGreedyBase, GenericPivotedGreedyApproximant, RationalInterpolantPivoted): """ ROM pivoted greedy rational interpolant computation for parametric problems (with pole matching). 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'matchingMode': mode for pole matching optimization; allowed values include 'NONE' and 'SHIFT'; defaults to 'NONE'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; defaults to 1.; + - 'sharedRatio': required ratio of marginal points to share + resonance; defaults to 1.; - '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; - 'samplerMarginal': marginal sample point generator via sparse grid; - 'errorEstimatorKindMarginal': kind of marginal error estimator; available values include 'LEAVE_ONE_OUT', 'LOOK_AHEAD', and 'LOOK_AHEAD_RECOVER'; defaults to 'LEAVE_ONE_OUT'; - 'polybasis': type of polynomial basis for pivot interpolation; defaults to 'MONOMIAL'; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; allowed values include 'MONOMIAL_*', 'CHEBYSHEV_*', 'LEGENDRE_*', 'NEARESTNEIGHBOR', and 'PIECEWISE_LINEAR_*'; defaults to 'MONOMIAL'; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'nNeighborsMarginal': number of marginal nearest neighbors; - defaults to 1; only for 'NEARESTNEIGHBOR' or - 'PIECEWISE_LINEAR_*'; + defaults to 1; only for 'NEARESTNEIGHBOR'; . 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'interpRcondMarginal': tolerance for marginal interpolation; - defaults to None; not for 'NEARESTNEIGHBOR'. + defaults to None; not for 'NEARESTNEIGHBOR'; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights; only for + radial basis. - '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; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; defaults to 1e-1; - 'maxIterMarginal': maximum number of marginal greedy steps; defaults to 1e2; - 'radialDirectionalWeights': radial basis weights for pivot numerator; defaults to 1; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; defaults to [-1, -1]; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - 'matchingWeight': weight for pole matching optimization; - 'matchingMode': mode for pole matching optimization; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; + - 'sharedRatio': required ratio of marginal points to share + resonance; - 'matchingWeightError': weight for pole matching optimization in error estimation; - 'cutOffToleranceError': tolerance for ignoring parasitic poles in error estimation; - 'errorEstimatorKindMarginal': kind of marginal error estimator; - 'polybasis': type of polynomial basis for pivot interpolation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; . 'nNeighborsMarginal': number of marginal nearest neighbors; . 'polydegreetypeMarginal': type of polynomial degree for marginal; - . 'interpRcondMarginal': tolerance for marginal interpolation. + . 'interpRcondMarginal': tolerance for marginal interpolation; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights. - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'greedyTolMarginal': uniform error tolerance for marginal greedy algorithm; - 'maxIterMarginal': maximum number of marginal greedy steps; - 'radialDirectionalWeights': radial basis weights for pivot numerator; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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 via sparse grid. approx_state: Whether to approximate state. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. scaleFactorDer: Scaling factors for derivative computation. matchingWeight: Weight for pole matching optimization. matchingMode: Mode for pole matching optimization. - cutOffTolerance: Tolerance for ignoring parasitic poles. - cutOffSharedRatio: Required ratio of marginal points to share resonance - in cut off strategy. + sharedRatio: Required ratio of marginal points to share resonance. 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. samplerMarginal: Marginal sample point generator via sparse grid. errorEstimatorKindMarginal: Kind of marginal error estimator. polybasis: Type of polynomial basis for pivot interpolation. polybasisMarginal: Type of polynomial basis for marginal interpolation. paramsMarginal: Dictionary of parameters for marginal interpolation. M: Degree of rational interpolant numerator. N: Degree of rational interpolant denominator. greedyTolMarginal: Uniform error tolerance for marginal greedy algorithm. maxIterMarginal: Maximum number of marginal greedy steps. radialDirectionalWeights: Radial basis weights for pivot numerator. + radialDirectionalWeightsAdapt: Bounds for adaptive rescaling of radial + basis weights. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. interpRcond: Tolerance for pivot 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. muBounds: 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. """ diff --git a/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py b/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py index a0e16ea..fb9f77e 100644 --- a/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py +++ b/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py @@ -1,629 +1,567 @@ # 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 (GenericPivotedApproximantBase, GenericPivotedApproximantNoMatch, GenericPivotedApproximant) +from .gather_pivoted_approximant import gatherPivotedApproximant 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.poly_fitting.polynomial import polyvander as pv from rrompy.utilities.exception_manager import RROMPyAssert, RROMPyWarning -from rrompy.parameter import emptyParameterList, checkParameterList -from rrompy.utilities.parallel import (COMM, poolRank, indicesScatter, - listGather, matrixGatherv) +from rrompy.parameter import emptyParameterList, parameterList +from rrompy.utilities.parallel import poolRank, indicesScatter, isend, recv __all__ = ['RationalInterpolantGreedyPivotedNoMatch', 'RationalInterpolantGreedyPivoted'] class RationalInterpolantGreedyPivotedBase(GenericPivotedApproximantBase, RationalInterpolantGreedy): - 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 - + @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 + def residueTol(self): + """Value of residueTol.""" + return self._residueTol + @residueTol.setter + def residueTol(self, residueTol): + if residueTol < 0. or (residueTol > 0. and self.nparPivot > 1): + RROMPyWarning("Overriding prescribed residue tolerance to 0.") + residueTol = 0. + self._residueTol = residueTol + self._approxParameters["residueTol"] = self.residueTol - @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 _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]]] + self._m_HFEparameterMap = copy(self.HFEngine.parameterMap) + self._mu0 = self.checkParameterListPivot( + self.mu0(self.directionPivot)) + self._mus = self.checkParameterListPivot( + self.mus(self.directionPivot)) + self.HFEngine.parameterMap = { + "F": [self.HFEngine.parameterMap["F"][self.directionPivot[0]]], + "B": [self.HFEngine.parameterMap["B"][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 + self.HFEngine.parameterMap = self._m_HFEparameterMap + del self._m_mu0, self._m_selfmus, self._m_HFEparameterMap 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 + self.trainedModel.data.parameterMap = self.HFEngine.parameterMap Qc = np.zeros((1,) * self.directionPivot[0] + (len(self.trainedModel.data.Q.coeffs),) + (1,) * (self.npar - self.directionPivot[0] - 1), dtype = self.trainedModel.data.Q.coeffs.dtype) Pc = np.zeros((1,) * self.directionPivot[0] + (len(self.trainedModel.data.P.coeffs),) + (1,) * (self.npar - self.directionPivot[0] - 1) + (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._musUniqueCN = self.checkParameterList(musUniqueCNAux) 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.mu0 = self.checkParameterListPivot( + self.mu0(self.directionPivot)) self.trainedModel.data.scaleFactor = self.scaleFactor - self.trainedModel.data.rescalingExp = self.HFEngine.rescalingExp[ - self.directionPivot[0]] + self.trainedModel.data.parameterMap = { + "F": [self.HFEngine.parameterMap["F"][self.directionPivot[0]]], + "B": [self.HFEngine.parameterMap["B"][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.") self._S = self._setSampleBatch(self.S) self.resetSamples() self.samplingEngine.scaleFactor = self.scaleFactorDer 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() + idxPop = pruneSamples(self.HFEngine.mapParameterList(muTestPivot, + idx = self.directionPivot), + self.HFEngine.mapParameterList(musPivot, + idx = self.directionPivot), + 1e-10 * self.scaleFactorPivot[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 + muk = np.empty_like(self.mus[0]) + muk[self.directionPivot] = musPivot[k] + muk[self.directionMarginal] = self.musMargLoc + self.mus[k] = muk for k in range(len(muTestPivot)): - muTestBase.data[k, self.directionPivot] = muTestPivot[k].data - muTestBase.data[k, self.directionMarginal] = self.musMargLoc.data + muk = np.empty_like(muTestBase[0]) + muk[self.directionPivot] = muTestPivot[k] + muk[self.directionMarginal] = self.musMargLoc + muTestBase[k] = muk 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), 3) 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 + self.muTest = parameterList(muTestBase) + self.muTest.append(muLast) self.M, self.N = ("AUTO",) * 2 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.computeScaleFactor() self._musMarginal = self.samplerMarginal.generatePoints(self.SMarginal) while len(self.musMarginal) > self.SMarginal: self.musMarginal.pop() S0 = copy(self.S) idx, sizes = indicesScatter(len(self.musMarginal), return_sizes = True) - data, pMat = [], None + pMat, Ps, Qs, mus = None, [], [], None req, emptyCores = [], np.where(np.logical_not(sizes))[0] if len(idx) == 0: vbMng(self, "MAIN", "Idling.", 25) if self.storeAllSamples: self.storeSamples() - pL, pT = COMM.recv(source = 0, tag = poolRank()) + pL, pT, mT = recv(source = 0, tag = poolRank()) pMat = np.empty((pL, 0), dtype = pT) + mus = np.empty((0, self.mu0.shape[1]), dtype = mT) else: _scaleFactorOldPivot = copy(self.scaleFactor) self.scaleFactor = self.scaleFactorPivot self._temporaryPivot = 1 for i in idx: self.musMargLoc = self.musMarginal[i] vbMng(self, "MAIN", "Building marginal model no. {} at {}.".format(i + 1, self.musMargLoc), 5) self.samplingEngine.resetHistory() self.trainedModel = None self.verbosity -= 5 self.samplingEngine.verbosity -= 5 super().setupApprox(*args, **kwargs) self.verbosity += 5 self.samplingEngine.verbosity += 5 if self.storeAllSamples: self.storeSamples(i) if pMat is None: + mus = copy(self.samplingEngine.mus.data) pMat = copy(self.samplingEngine.projectionMatrix) if i == 0: for dest in emptyCores: - req += [COMM.isend((len(pMat), pMat.dtype), - dest = dest, tag = dest)] + req += [isend((len(pMat), pMat.dtype, mus.dtype), + dest = dest, tag = dest)] else: + mus = np.vstack((mus, self.samplingEngine.mus.data)) pMat = np.hstack((pMat, self.samplingEngine.projectionMatrix)) - data += [(copy(self.trainedModel.data.Q), - copy(self.trainedModel.data.P), - copy(self.samplingEngine.mus.data))] - #FIXME + Ps += [copy(self.trainedModel.data.P)] + Qs += [copy(self.trainedModel.data.Q)] self._S = S0 del self._temporaryPivot, self.musMargLoc self.scaleFactor = _scaleFactorOldPivot for r in req: r.wait() - data = listGather(data) - self._mus = checkParameterList(data[0][2], self.npar)[0] - nsamples, sizesEff, idx = [], [], 0 - for size in sizes: - sizesEff += [0] - for _ in range(size): - _m = data[idx][2] - if idx > 0: self._mus.append(_m) - nsamples += [len(_m)] - sizesEff[-1] += nsamples[-1] - idx += 1 - pMat = matrixGatherv(pMat, sizesEff, False) + pMat, Ps, Qs, mus, nsamples = gatherPivotedApproximant(pMat, Ps, Qs, + mus, sizes, + self.polybasis) + self._mus = self.checkParameterList(mus) Psupp = np.append(0, np.cumsum(nsamples)) self._setupTrainedModel(pMat, forceNew = True) - self.trainedModel.data.Qs = [x[0] for x in data] - self.trainedModel.data.Ps = [x[1] for x in data] + self.trainedModel.data.Qs, self.trainedModel.data.Ps = Qs, Ps self.trainedModel.data.Psupp = list(Psupp[: -1]) self._poleMatching() self._finalizeMarginalization() vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 class RationalInterpolantGreedyPivotedNoMatch( RationalInterpolantGreedyPivotedBase, GenericPivotedApproximantNoMatch): """ ROM pivoted rational interpolant (without 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - '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'; - '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', 'LOOK_AHEAD', 'LOOK_AHEAD_RES', and 'NONE'; defaults to 'NONE'; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'polybasis': type of polynomial basis for pivot 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; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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. scaleFactorDer: Scaling factors for derivative computation. - cutOffTolerance: Tolerance for ignoring parasitic poles. 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. 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. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. interpRcond: Tolerance for pivot 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. muBounds: 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 _poleMatching(self): - vbMng(self, "INIT", "Compressing poles.", 10) - self.trainedModel.initializeFromRational() - vbMng(self, "DEL", "Done compressing poles.", 10) - class RationalInterpolantGreedyPivoted(RationalInterpolantGreedyPivotedBase, GenericPivotedApproximant): """ 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'matchingMode': mode for pole matching optimization; allowed values include 'NONE' and 'SHIFT'; defaults to 'NONE'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; defaults to 1.; + - 'sharedRatio': required ratio of marginal points to share + resonance; defaults to 1.; - '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_*', 'LEGENDRE_*', 'NEARESTNEIGHBOR', and 'PIECEWISE_LINEAR_*'; defaults to 'MONOMIAL'; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'nNeighborsMarginal': number of marginal nearest neighbors; - defaults to 1; only for 'NEARESTNEIGHBOR' or - 'PIECEWISE_LINEAR_*'; + defaults to 1; only for 'NEARESTNEIGHBOR'; . 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'interpRcondMarginal': tolerance for marginal interpolation; - defaults to None; not for 'NEARESTNEIGHBOR'. + defaults to None; not for 'NEARESTNEIGHBOR'; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights; only for + radial basis. - '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', 'LOOK_AHEAD', 'LOOK_AHEAD_RES', and 'NONE'; defaults to 'NONE'; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - 'matchingWeight': weight for pole matching optimization; - 'matchingMode': mode for pole matching optimization; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; + - 'sharedRatio': required ratio of marginal points to share + resonance; - 'polybasis': type of polynomial basis for pivot interpolation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; . 'nNeighborsMarginal': number of marginal nearest neighbors; . 'polydegreetypeMarginal': type of polynomial degree for marginal; - . 'interpRcondMarginal': tolerance for marginal interpolation. + . 'interpRcondMarginal': tolerance for marginal interpolation; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights. - '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; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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. scaleFactorDer: Scaling factors for derivative computation. matchingWeight: Weight for pole matching optimization. matchingMode: Mode for pole matching optimization. - cutOffTolerance: Tolerance for ignoring parasitic poles. - cutOffSharedRatio: Required ratio of marginal points to share resonance - in cut off strategy. + sharedRatio: Required ratio of marginal points to share resonance. 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. paramsMarginal: Dictionary of parameters 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. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. interpRcond: Tolerance for pivot 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. muBounds: 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 _poleMatching(self): - vbMng(self, "INIT", "Compressing and matching poles.", 10) - self.trainedModel.initializeFromRational(self.matchingWeight, - self.matchingMode, - self.HFEngine, False) - vbMng(self, "DEL", "Done compressing and matching poles.", 10) - - def setupApprox(self, *args, **kwargs) -> int: - if self.checkComputedApprox(): return -1 - self.purgeparamsMarginal() - return super().setupApprox(*args, **kwargs) diff --git a/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py b/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py index fbbb77e..71ba112 100644 --- a/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py +++ b/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py @@ -1,510 +1,469 @@ # 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 (GenericPivotedApproximantBase, GenericPivotedApproximantNoMatch, GenericPivotedApproximant) +from .gather_pivoted_approximant import gatherPivotedApproximant from rrompy.reduction_methods.standard.rational_interpolant import ( RationalInterpolant) from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.numerical.hash_derivative import nextDerivativeIndices from rrompy.utilities.exception_manager import RROMPyAssert, RROMPyWarning from rrompy.parameter import emptyParameterList -from rrompy.utilities.parallel import (COMM, poolRank, indicesScatter, - listGather, matrixGatherv) +from rrompy.utilities.parallel import poolRank, indicesScatter, isend, recv __all__ = ['RationalInterpolantPivotedNoMatch', 'RationalInterpolantPivoted'] class RationalInterpolantPivotedBase(GenericPivotedApproximantBase, RationalInterpolant): def __init__(self, *args, **kwargs): self._preInit() - self._addParametersToList(toBeExcluded = ["polydegreetype", "sampler"]) + self._addParametersToList(toBeExcluded = ["polydegreetype"]) super().__init__(*args, **kwargs) self._postInit() @property def scaleFactorDer(self): """Value of scaleFactorDer.""" if self._scaleFactorDer == "NONE": return 1. if self._scaleFactorDer == "AUTO": return self.scaleFactorPivot return self._scaleFactorDer @scaleFactorDer.setter def scaleFactorDer(self, scaleFactorDer): if isinstance(scaleFactorDer, (str,)): scaleFactorDer = scaleFactorDer.upper() elif hasattr(scaleFactorDer, "__len__"): scaleFactorDer = list(scaleFactorDer) self._scaleFactorDer = scaleFactorDer self._approxParameters["scaleFactorDer"] = self._scaleFactorDer @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 residueTol(self): + """Value of residueTol.""" + return self._residueTol + @residueTol.setter + def residueTol(self, residueTol): + if residueTol < 0. or (residueTol > 0. and self.nparPivot > 1): + RROMPyWarning("Overriding prescribed residue tolerance to 0.") + residueTol = 0. + self._residueTol = residueTol + self._approxParameters["residueTol"] = self.residueTol 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 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.computeScaleFactor() self.resetSamples() self.samplingEngine.scaleFactor = self.scaleFactorDer 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 = emptyParameterList() self.mus.reset((self.S * self.SMarginal, self.HFEngine.npar)) 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 + muk = np.empty_like(self.mus[0]) + muk[self.directionPivot] = self.musPivot[k - j * self.S] + muk[self.directionMarginal] = muMarg + self.mus[k] = muk N0 = copy(self.N) self._setupTrainedModel(np.zeros((0, 0)), forceNew = True) idx, sizes = indicesScatter(len(self.musMarginal), return_sizes = True) - data, pMat = [], None + pMat, Ps, Qs = None, [], [] req, emptyCores = [], np.where(np.logical_not(sizes))[0] if len(idx) == 0: vbMng(self, "MAIN", "Idling.", 30) if self.storeAllSamples: self.storeSamples() - pL, pT = COMM.recv(source = 0, tag = poolRank()) + pL, pT = recv(source = 0, tag = poolRank()) pMat = np.empty((pL, 0), dtype = pT) else: _scaleFactorOldPivot = copy(self.scaleFactor) self.scaleFactor = self.scaleFactorPivot self._temporaryPivot = 1 for i in idx: vbMng(self, "MAIN", "Building marginal model no. {} at {}.".format(i + 1, self.musMarginal[i]), 5) vbMng(self, "INIT", "Starting computation of snapshots.", 10) self.samplingEngine.resetHistory() self.samplingEngine.iterSample( - self.mus.data[self.S * i : self.S * (i + 1)]) + self.mus[self.S * i : self.S * (i + 1)]) vbMng(self, "DEL", "Done computing snapshots.", 10) self.verbosity -= 5 self.samplingEngine.verbosity -= 5 - self._iterCorrector() + self._setupRational(self._setupDenominator()[0]) self.verbosity += 5 self.samplingEngine.verbosity += 5 if self.storeAllSamples: self.storeSamples(i) if pMat is None: pMat = copy(self.samplingEngine.projectionMatrix) if i == 0: for dest in emptyCores: - req += [COMM.isend((len(pMat), pMat.dtype), - dest = dest, tag = dest)] + req += [isend((len(pMat), pMat.dtype), dest = dest, + tag = dest)] else: pMat = np.hstack((pMat, self.samplingEngine.projectionMatrix)) - data += [(copy(self.trainedModel.data.Q), - copy(self.trainedModel.data.P))] + Ps += [copy(self.trainedModel.data.P)] + Qs += [copy(self.trainedModel.data.Q)] del self.trainedModel.data.Q, self.trainedModel.data.P - #FIXME self.N = N0 del self._temporaryPivot self.scaleFactor = _scaleFactorOldPivot for r in req: r.wait() - data = listGather(data) - pMat = matrixGatherv(pMat, [self.S * s for s in sizes], False) + pMat, Ps, Qs, _, _ = gatherPivotedApproximant(pMat, Ps, Qs, + self.mus.data, sizes, + self.polybasis, False) self._setupTrainedModel(pMat) - self.trainedModel.data.Qs = [x[0] for x in data] - self.trainedModel.data.Ps = [x[1] for x in data] + self.trainedModel.data.Qs, self.trainedModel.data.Ps = Qs, Ps Psupp = np.arange(0, len(self.musMarginal) * self.S, self.S) self.trainedModel.data.Psupp = list(Psupp) self._poleMatching() self._finalizeMarginalization() vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 class RationalInterpolantPivotedNoMatch(RationalInterpolantPivotedBase, GenericPivotedApproximantNoMatch): """ ROM pivoted rational interpolant (without 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - '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'; - '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; - 'radialDirectionalWeights': radial basis weights for pivot numerator; defaults to 1; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; defaults to [-1, -1]; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - 'polybasis': type of polynomial basis for pivot interpolation; - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'radialDirectionalWeights': radial basis weights for pivot numerator; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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. scaleFactorDer: Scaling factors for derivative computation. - cutOffTolerance: Tolerance for ignoring parasitic poles. 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. M: Numerator degree of approximant. N: Denominator degree of approximant. radialDirectionalWeights: Radial basis weights for pivot numerator. + radialDirectionalWeightsAdapt: Bounds for adaptive rescaling of radial + basis weights. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. interpRcond: Tolerance for pivot 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. muBounds: 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 _poleMatching(self): - vbMng(self, "INIT", "Compressing poles.", 10) - self.trainedModel.initializeFromRational() - vbMng(self, "DEL", "Done compressing poles.", 10) - class RationalInterpolantPivoted(RationalInterpolantPivotedBase, GenericPivotedApproximant): """ 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - 'matchingWeight': weight for pole matching optimization; defaults to 1; - 'matchingMode': mode for pole matching optimization; allowed values include 'NONE' and 'SHIFT'; defaults to 'NONE'; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - defaults to np.inf; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; defaults to 1.; + - 'sharedRatio': required ratio of marginal points to share + resonance; defaults to 1.; - '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_*', 'LEGENDRE_*', 'NEARESTNEIGHBOR', and 'PIECEWISE_LINEAR_*'; defaults to 'MONOMIAL'; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; defaults to 'AUTO', i.e. maximum allowed; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'nNeighborsMarginal': number of marginal nearest neighbors; - defaults to 1; only for 'NEARESTNEIGHBOR' or - 'PIECEWISE_LINEAR_*'; + defaults to 1; only for 'NEARESTNEIGHBOR'; . 'polydegreetypeMarginal': type of polynomial degree for marginal; defaults to 'TOTAL'; not for 'NEARESTNEIGHBOR' or 'PIECEWISE_LINEAR_*'; . 'interpRcondMarginal': tolerance for marginal interpolation; - defaults to None; not for 'NEARESTNEIGHBOR'. + defaults to None; not for 'NEARESTNEIGHBOR'; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights; only for + radial basis. - '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; - 'radialDirectionalWeights': radial basis weights for pivot numerator; defaults to 1; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; defaults to [-1, -1]; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; defaults to 1; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - 'matchingWeight': weight for pole matching optimization; - 'matchingMode': mode for pole matching optimization; - - 'cutOffTolerance': tolerance for ignoring parasitic poles; - - 'cutOffSharedRatio': required ratio of marginal points to share - resonance in cut off strategy; + - 'sharedRatio': required ratio of marginal points to share + resonance; - 'polybasis': type of polynomial basis for pivot interpolation; - 'polybasisMarginal': type of polynomial basis for marginal interpolation; - 'paramsMarginal': dictionary of parameters for marginal interpolation; include: . 'MMarginal': degree of marginal interpolant; . 'nNeighborsMarginal': number of marginal nearest neighbors; . 'polydegreetypeMarginal': type of polynomial degree for marginal; - . 'interpRcondMarginal': tolerance for marginal interpolation. + . 'interpRcondMarginal': tolerance for marginal interpolation; + . 'radialDirectionalWeightsMarginalAdapt': bounds for adaptive + rescaling of marginal radial basis weights. - 'M': degree of rational interpolant numerator; - 'N': degree of rational interpolant denominator; - 'radialDirectionalWeights': radial basis weights for pivot numerator; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; - 'radialDirectionalWeightsMarginal': radial basis weights for marginal interpolant; - 'interpRcond': tolerance for pivot 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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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. scaleFactorDer: Scaling factors for derivative computation. matchingWeight: Weight for pole matching optimization. matchingMode: Mode for pole matching optimization. - cutOffTolerance: Tolerance for ignoring parasitic poles. - cutOffSharedRatio: Required ratio of marginal points to share resonance - in cut off strategy. + sharedRatio: Required ratio of marginal points to share resonance. 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. paramsMarginal: Dictionary of parameters for marginal interpolation. M: Numerator degree of approximant. N: Denominator degree of approximant. radialDirectionalWeights: Radial basis weights for pivot numerator. + radialDirectionalWeightsAdapt: Bounds for adaptive rescaling of radial + basis weights. radialDirectionalWeightsMarginal: Radial basis weights for marginal interpolant. interpRcond: Tolerance for pivot 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. muBounds: 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 _poleMatching(self): - vbMng(self, "INIT", "Compressing and matching poles.", 10) - self.trainedModel.initializeFromRational(self.matchingWeight, - self.matchingMode, - self.HFEngine, False) - vbMng(self, "DEL", "Done compressing and matching poles.", 10) - - def setupApprox(self, *args, **kwargs) -> int: - if self.checkComputedApprox(): return -1 - self.purgeparamsMarginal() - return super().setupApprox(*args, **kwargs) 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 a4f80e6..b5355a2 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,73 +1,45 @@ # 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.trained_model_data import ( TrainedModelData) -from rrompy.utilities.base.types import Np2D, List, ListAny, paramVal -from rrompy.parameter import checkParameterList +from rrompy.utilities.base.types import Np2D, List, DictAny, paramVal __all__ = ['TrainedModelPivotedData'] class TrainedModelPivotedData(TrainedModelData): """ROM approximant evaluation data (must be pickle-able).""" def __init__(self, mu0:paramVal, mus:paramVal, projMat:Np2D, - scaleFactor : ListAny = [1.], - rescalingExp : List[float] = [1.], - directionPivot : ListAny = [0]): - super().__init__(mu0, mus, projMat, scaleFactor, rescalingExp) + scaleFactor : List[float] = [1.], parameterMap : DictAny = 1., + directionPivot : List[int] = [0]): + super().__init__(mu0, mus, projMat, scaleFactor, parameterMap) 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/pivoted/trained_model/trained_model_pivoted_rational.py b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational.py index e64974f..a8414b5 100644 --- a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational.py +++ b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational.py @@ -1,308 +1,295 @@ # 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 warnings import numpy as np from scipy.sparse import csr_matrix, hstack, SparseEfficiencyWarning from copy import deepcopy as copy from .trained_model_pivoted_rational_nomatch import ( TrainedModelPivotedRationalNoMatch) -from rrompy.utilities.base.types import (Np1D, Np2D, List, ListAny, paramVal, - paramList, HFEng) +from rrompy.utilities.base.types import (Np2D, ListAny, paramVal, paramList, + HFEng) from rrompy.utilities.base import verbosityManager as vbMng -from rrompy.utilities.numerical.point_matching import (potential, - rationalFunctionMatching) +from rrompy.utilities.numerical.point_matching import rationalFunctionMatching 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.heaviside import (heavisideUniformShape, HeavisideInterpolator as HI) from rrompy.utilities.poly_fitting.nearest_neighbor import ( NearestNeighborInterpolator as NNI) -from rrompy.utilities.poly_fitting.piecewise_linear import ( +from rrompy.utilities.poly_fitting.piecewise_linear import (sparsekinds, PiecewiseLinearInterpolator as PLI) -from rrompy.utilities.exception_manager import RROMPyException, RROMPyWarning -from rrompy.parameter import checkParameterList +from rrompy.utilities.exception_manager import RROMPyException __all__ = ['TrainedModelPivotedRational'] class TrainedModelPivotedRational(TrainedModelPivotedRationalNoMatch): """ ROM approximant evaluation for pivoted approximants based on interpolation of rational approximants (with pole matching). Attributes: Data: dictionary with all that can be pickled. """ 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 + mu = self.checkParameterListMarginal(mu) + if mu0 is None: + mu0 = self.checkParameterListMarginal( + self.data.mu0(0, self.data.directionMarginal)) + return (self.mapParameterList(mu, idx = self.data.directionMarginal) + - self.mapParameterList(mu0, idx = self.data.directionMarginal) + ) / [self.data.scaleFactor[x] + for x in self.data.directionMarginal] - def setupMarginalInterp(self, approx, interpPars:ListAny, - MMAuto:bool, rDWM : Np1D = None, - extraPar = True): + def setupMarginalInterp(self, approx, interpPars:ListAny, extraPar = None): vbMng(self, "INIT", "Starting computation of marginal interpolator.", 12) musMCN = self.centerNormalizeMarginal(self.data.musMarginal) - nM = len(musMCN) - pbM = approx.polybasisMarginal + nM, pbM = len(musMCN), approx.polybasisMarginal + if pbM in ppb + rbpb: + if extraPar: approx._setMMarginalAuto() + _MMarginalEff = approx.paramsMarginal["MMarginal"] + if pbM in ppb: + p = PI() + elif pbM in rbpb: + p = RBI() + else: # if pbM in sparsekinds + ["NEARESTNEIGHBOR"]: + if pbM == "NEARESTNEIGHBOR": + p = NNI() + else: # if pbM in sparsekinds: + pllims = [[-1.] * self.data.nparMarginal, + [1.] * self.data.nparMarginal] + p = PLI() for ipts, pts in enumerate(self.data.suppEffPts): - if pbM in ppb: - p = PI() - elif pbM in rbpb: - p = RBI() - else: # #if pbM in sparsekinds + ["NEARESTNEIGHBOR"]: - if ipts > 0 or pbM == "NEARESTNEIGHBOR": - p = NNI() - else: #if ipts == 0 or pbM in sparsekinds: - pllims = [[-1.] * self.data.nparMarginal, - [1.] * self.data.nparMarginal] - p = PLI() if len(pts) == 0: raise RROMPyException("Empty list of support points.") musMCNEff, valsEff = musMCN[pts], np.eye(len(pts)) if pbM in ppb + rbpb: - if MMAuto: + if extraPar: if ipts > 0: verb = approx.verbosity approx.verbosity = 0 _musM = approx.musMarginal approx.musMarginal = musMCNEff - approx._setMMarginalAuto() - if ipts > 0: + approx._setMMarginalAuto() approx.musMarginal = _musM approx.verbosity = verb - if ipts == 0: - _MMarginalEff = approx.paramsMarginal["MMarginal"] - if not MMAuto: - approx.paramsMarginal["MMarginal"] = _MMarginalEff - MM = reduceDegreeN(approx.paramsMarginal["MMarginal"], - len(musMCNEff), self.data.nparMarginal, - approx.paramsMarginal["polydegreetypeMarginal"]) - if MM < approx.paramsMarginal["MMarginal"]: - if ipts == 0 and not extraPar: - RROMPyWarning( - ("MMarginal too large compared to SMarginal. Reducing " - "MMarginal by {}").format( - approx.paramsMarginal["MMarginal"] - MM)) - approx.paramsMarginal["MMarginal"] = MM + else: + approx.paramsMarginal["MMarginal"] = reduceDegreeN( + _MMarginalEff, len(musMCNEff), self.data.nparMarginal, + approx.paramsMarginal["polydegreetypeMarginal"]) MMEff = approx.paramsMarginal["MMarginal"] while MMEff >= 0: - pParRest = copy(interpPars) - if pbM in rbpb: - pParRest = [rDWM] + pParRest - wellCond, msg = p.setupByInterpolation(musMCNEff, - valsEff, MMEff, - pbM, *pParRest) + wellCond, msg = p.setupByInterpolation(musMCNEff, valsEff, + MMEff, *interpPars) vbMng(self, "MAIN", msg, 30) if wellCond: break vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing " "MMarginal by 1."), 35) MMEff -= 1 if MMEff < 0: raise RROMPyException(("Instability in computation of " "interpolant. Aborting.")) - elif ipts > 0 or pbM == "NEARESTNEIGHBOR": - nNeigh = interpPars[0] if ipts == 0 else 1 - p.setupByInterpolation(musMCNEff, valsEff, nNeigh, rDWM) - else: #if ipts == 0 and pbM in sparsekinds: + if (pbM in rbpb and len(interpPars) > 4 + and "optimizeScalingBounds" in interpPars[4].keys()): + interpPars[4]["optimizeScalingBounds"] = [-1., -1.] + elif pbM == "NEARESTNEIGHBOR": + if ipts > 0: interpPars[0] = 1 + p.setupByInterpolation(musMCNEff, valsEff, *interpPars) + elif ipts == 0: # and pbM in sparsekinds: wellCond, msg = p.setupByInterpolation(musMCNEff, valsEff, pllims, extraPar[pts], - pbM, *interpPars) + *interpPars) vbMng(self, "MAIN", msg, 30) if not wellCond: vbMng(self, "MAIN", "Warning: polyfit is poorly conditioned.", 35) if ipts == 0: self.data.marginalInterp = copy(p) - self.data.polesEff = [copy(hi.poles) for hi in self.data.HIs] - self.data.coeffsEff = [] + self.data.coeffsEff, self.data.polesEff = [], [] for hi, sup in zip(self.data.HIs, self.data.Psupp): cEff = hi.coeffs if (self.data._collapsed or self.data.projMat.shape[1] == cEff.shape[1]): cEff = copy(cEff) else: + supC = self.data.projMat.shape[1] - sup - cEff.shape[1] cEff = hstack((csr_matrix((len(cEff), sup)), csr_matrix(cEff), - csr_matrix((len(cEff), - self.data.projMat.shape[1] - - sup - cEff.shape[1]))), - "csr") + csr_matrix((len(cEff), supC))), "csr") self.data.coeffsEff += [cEff] + self.data.polesEff += [copy(hi.poles)] else: ptsBad = [i for i in range(nM) if i not in pts] - musMCNBad = musMCN[ptsBad] idxBad = np.where(self.data.suppEffIdx == ipts)[0] - valMuMBad = p(musMCNBad) - pls = np.empty((len(ptsBad), len(idxBad)), - dtype = self.data.polesEff[0].dtype) warnings.simplefilter('ignore', SparseEfficiencyWarning) - if (self.data._collapsed - or self.data.projMat.shape[1] == cEff.shape[1]): - cfs = [0.] * len(ptsBad) + if pbM in sparsekinds: + for ij, j in enumerate(ptsBad): + nearest = pts[np.argmin(np.sum(np.abs(musMCNEff.data + - np.tile(musMCN[j], [len(pts), 1]) + ), axis = 1).flatten())] + self.data.coeffsEff[j][idxBad] = copy( + self.data.coeffsEff[nearest][idxBad]) + self.data.polesEff[j][idxBad] = copy( + self.data.polesEff[nearest][idxBad]) else: - cfs = [None] * len(ptsBad) - for jj in range(len(ptsBad)): - cfs[jj] = csr_matrix( - (len(idxBad), self.data.projMat.shape[1]), - dtype = self.data.coeffsEff[0].dtype) - for ij, j in enumerate(pts): - pls += (np.expand_dims(valMuMBad[ij], -1) - * self.data.polesEff[j][idxBad]) - for jj, val in enumerate(valMuMBad[ij]): - cfs[jj] = (cfs[jj] - + val * self.data.coeffsEff[j][idxBad]) - for ij, j in enumerate(ptsBad): - self.data.polesEff[j][idxBad] = pls[ij] - self.data.coeffsEff[j][idxBad] = cfs[ij] + if (self.data._collapsed + or self.data.projMat.shape[1] == cEff.shape[1]): + cfBase = np.zeros((len(idxBad), cEff.shape[1]), + dtype = cEff.dtype) + else: + cfBase = csr_matrix((len(idxBad), + self.data.projMat.shape[1]), + dtype = cEff.dtype) + valMuMBad = p(musMCN[ptsBad]) + for ijb, jb in enumerate(ptsBad): + self.data.coeffsEff[jb][idxBad] = copy(cfBase) + self.data.polesEff[jb][idxBad] = 0. + for ij, j in enumerate(pts): + val = valMuMBad[ij][ijb] + if not np.isclose(val, 0.): + self.data.coeffsEff[jb][idxBad] += (val + * self.data.coeffsEff[j][idxBad]) + self.data.polesEff[jb][idxBad] += (val + * self.data.polesEff[j][idxBad]) + warnings.filters.pop(0) if pbM in ppb + rbpb: approx.paramsMarginal["MMarginal"] = _MMarginalEff vbMng(self, "DEL", "Done computing marginal interpolator.", 12) def initializeFromLists(self, poles:ListAny, coeffs:ListAny, supps:ListAny, basis:str, matchingWeight:float, matchingMode:str, HFEngine:HFEng, is_state:bool): """Initialize Heaviside representation.""" poles, coeffs = rationalFunctionMatching( *heavisideUniformShape(poles, coeffs), self.data.musMarginal.data, matchingWeight, matchingMode, supps, self.data.projMat, HFEngine, is_state) super().initializeFromLists(poles, coeffs, supps, basis) self.data.suppEffPts = [np.arange(len(self.data.HIs))] self.data.suppEffIdx = np.zeros(len(poles[0]), dtype = int) - def recompressByCutOff(self, tol:float, shared:float, - foci:List[np.complex], ground:float) -> str: + def checkSharedRatio(self, shared:float) -> str: N = len(self.data.HIs[0].poles) M = len(self.data.HIs) - mu0 = np.mean(foci) - goodLocPoles = np.array([potential(hi.poles, foci) - ground - <= tol * ground for hi in self.data.HIs]) + goodLocPoles = np.array([np.logical_not(np.isinf(hi.poles) + ) 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(np.all(goodLocPoles)): return " No poles erased." goodGlobPoles = np.sum(goodLocPoles, axis = 0) goodEnoughPoles = goodGlobPoles >= max(1., 1. * shared * M) keepPole = np.where(goodEnoughPoles)[0] halfPole = np.where(np.logical_and(goodEnoughPoles, goodGlobPoles < M))[0] removePole = np.where(np.logical_not(goodEnoughPoles))[0] if len(removePole) > 0: - keepCoeff = np.append(keepPole, np.append([N], - np.arange(N + 1, len(self.data.HIs[0].coeffs)))) + keepCoeff = np.append(keepPole, + np.arange(N, 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.coeffs[N, :] -= hi.coeffs[j, :] / hi.poles[j] 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 interpolateMarginalInterpolator(self, mu : paramList = []) -> ListAny: """Obtain interpolated approximant interpolator.""" - mu = checkParameterList(mu, self.data.nparMarginal)[0] + mu = self.checkParameterListMarginal(mu) vbMng(self, "INIT", "Interpolating marginal models at mu = {}.".format(mu), 95) his = [] muC = self.centerNormalizeMarginal(mu) mIvals = self.data.marginalInterp(muC) verb, self.verbosity = self.verbosity, 0 poless = self.interpolateMarginalPoles(mu, mIvals) coeffss = self.interpolateMarginalCoeffs(mu, mIvals) self.verbosity = verb for j in range(len(mu)): his += [HI()] his[-1].poles = poless[j] his[-1].coeffs = coeffss[j] his[-1].npar = 1 his[-1].polybasis = self.data.HIs[0].polybasis vbMng(self, "DEL", "Done interpolating marginal models.", 95) return his def interpolateMarginalPoles(self, mu : paramList = [], mIvals : Np2D = None) -> ListAny: """Obtain interpolated approximant poles.""" - mu = checkParameterList(mu, self.data.nparMarginal)[0] + mu = self.checkParameterListMarginal(mu) vbMng(self, "INIT", "Interpolating marginal poles at mu = {}.".format(mu), 95) intMPoles = np.zeros((len(mu),) + self.data.polesEff[0].shape, dtype = self.data.polesEff[0].dtype) if mIvals is None: muC = self.centerNormalizeMarginal(mu) mIvals = self.data.marginalInterp(muC) for pEff, mI in zip(self.data.polesEff, mIvals): intMPoles += np.expand_dims(mI, - 1) * pEff vbMng(self, "DEL", "Done interpolating marginal poles.", 95) return intMPoles def interpolateMarginalCoeffs(self, mu : paramList = [], mIvals : Np2D = None) -> ListAny: """Obtain interpolated approximant coefficients.""" - mu = checkParameterList(mu, self.data.nparMarginal)[0] + mu = self.checkParameterListMarginal(mu) vbMng(self, "INIT", "Interpolating marginal coefficients at mu = {}.".format(mu), 95) intMCoeffs = np.zeros((len(mu),) + self.data.coeffsEff[0].shape, dtype = self.data.coeffsEff[0].dtype) if mIvals is None: muC = self.centerNormalizeMarginal(mu) mIvals = self.data.marginalInterp(muC) for cEff, mI in zip(self.data.coeffsEff, mIvals): for j, m in enumerate(mI): intMCoeffs[j] += m * cEff vbMng(self, "DEL", "Done interpolating marginal coefficients.", 95) return intMCoeffs diff --git a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational_nomatch.py b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational_nomatch.py index fca087a..07e6e74 100644 --- a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational_nomatch.py +++ b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational_nomatch.py @@ -1,343 +1,330 @@ # 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 scipy.special import factorial as fact from itertools import combinations 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) from rrompy.utilities.base import verbosityManager as vbMng, freepar as fp from rrompy.utilities.numerical import dot from rrompy.utilities.numerical.compress_matrix import compressMatrix -from rrompy.utilities.numerical.point_matching import potential from rrompy.utilities.poly_fitting.heaviside import (rational2heaviside, HeavisideInterpolator as HI) from rrompy.utilities.poly_fitting.nearest_neighbor import ( NearestNeighborInterpolator as NNI) from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) from rrompy.parameter import checkParameterList -from rrompy.sampling import sampleList +from rrompy.sampling import sampleList, emptySampleList __all__ = ['TrainedModelPivotedRationalNoMatch'] class TrainedModelPivotedRationalNoMatch(TrainedModelRational): """ ROM approximant evaluation for pivoted approximants based on interpolation of rational approximants (without pole matching). Attributes: Data: dictionary with all that can be pickled. """ + def checkParameterListPivot(self, mu:paramList, + check_if_single : bool = False) -> paramList: + return checkParameterList(mu, self.data.nparPivot, check_if_single) + + def checkParameterListMarginal(self, mu:paramList, + check_if_single : bool = False) -> paramList: + return checkParameterList(mu, self.data.nparMarginal, check_if_single) + def compress(self, collapse : bool = False, tol : float = 0., *args, **kwargs): if not collapse and tol <= 0.: return RMat = self.data.projMat if not collapse: if hasattr(self.data, "_compressTol"): RROMPyWarning(("Recompressing already compressed model is " "ineffective. Aborting.")) return self.data.projMat, RMat, _ = compressMatrix(RMat, tol, *args, **kwargs) for obj, suppj in zip(self.data.HIs, self.data.Psupp): obj.postmultiplyTensorize(RMat.T[suppj : suppj + obj.shape[0]]) if hasattr(self, "_HIsExcl"): for obj, suppj in zip(self._HIsExcl, self.data.Psupp): obj.postmultiplyTensorize(RMat.T[suppj : suppj + obj.shape[0]]) if hasattr(self.data, "Ps"): for obj, suppj in zip(self.data.Ps, self.data.Psupp): obj.postmultiplyTensorize(RMat.T[suppj : suppj + obj.shape[0]]) if hasattr(self, "_PsExcl"): for obj, suppj in zip(self._PsExcl, self._PsuppExcl): obj.postmultiplyTensorize(RMat.T[suppj : suppj + obj.shape[0]]) if hasattr(self.data, "coeffsEff"): for j in range(len(self.data.coeffsEff)): self.data.coeffsEff[j] = dot(self.data.coeffsEff[j], RMat.T) if hasattr(self, "_HIsExcl") or hasattr(self, "_PsExcl"): self._PsuppExcl = [0] * len(self._PsuppExcl) self.data.Psupp = [0] * len(self.data.Psupp) super(TrainedModelRational, self).compress(collapse, tol) 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 + mu = self.checkParameterListPivot(mu) + if mu0 is None: + mu0 = self.checkParameterListPivot( + self.data.mu0(0, self.data.directionPivot)) + return (self.mapParameterList(mu, idx = self.data.directionPivot) + - self.mapParameterList(mu0, idx = self.data.directionPivot) + ) / [self.data.scaleFactor[x] for x in self.data.directionPivot] - def setupMarginalInterp(self, rDWM : Np1D = None): - self.data.NN = NNI() - self.data.NN.setupByInterpolation(self.data.musMarginal, + def setupMarginalInterp(self, interpPars:ListAny): + self.data.marginalInterp = NNI() + self.data.marginalInterp.setupByInterpolation(self.data.musMarginal, np.arange(len(self.data.musMarginal)), - 1, rDWM) + 1, *interpPars) def updateEffectiveSamples(self, exclude:List[int], *args, **kwargs): 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]) self.data.Psupp.insert(excl, self._PsuppExcl[j]) self._idxExcl, self._musMExcl = list(np.sort(exclude)), [] self._HIsExcl, self._PsExcl, self._QsExcl = [], [], [] self._PsuppExcl = [] 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 self._PsuppExcl = [self.data.Psupp.pop(excl)] + self._PsuppExcl poles = [hi.poles for hi in self.data.HIs] coeffs = [hi.coeffs for hi in self.data.HIs] self.initializeFromLists(poles, coeffs, self.data.Psupp, self.data.HIs[0].polybasis, *args, **kwargs) def initializeFromLists(self, poles:ListAny, coeffs:ListAny, supps:ListAny, basis:str, *args, **kwargs): """Initialize Heaviside representation.""" self.data.HIs = [] for pls, cfs in zip(poles, coeffs): hsi = HI() hsi.poles = pls + if len(cfs) == len(pls): + cfs = np.pad(cfs, ((0, 1), (0, 0)), "constant") hsi.coeffs = cfs hsi.npar = 1 hsi.polybasis = basis self.data.HIs += [hsi] def initializeFromRational(self, *args, **kwargs): """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, self.data.Psupp, basis, *args, **kwargs) - def recompressByCutOff(self, tol:float, foci:List[np.complex], - ground:float) -> str: - mu0 = np.mean(foci) - gLocPoles = [potential(hi.poles, foci) - ground <= tol * ground - for hi in self.data.HIs] - nRemPole = np.sum([np.sum(np.logical_not(gLPi)) for gLPi in gLocPoles]) - if nRemPole == 0: return " No poles erased." - for hi, gLocPolesi in zip(self.data.HIs, gLocPoles): - N = len(hi.poles) - polyCorrection = np.zeros_like(hi.coeffs[0, :]) - for j, goodj in enumerate(gLocPolesi): - if not goodj and 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[gLocPolesi] - gLocCoeffi = np.append(gLocPolesi, - np.ones(len(hi.coeffs) - N, dtype = bool)) - hi.coeffs = hi.coeffs[gLocCoeffi, :] - return " Erased {} pole{}.".format(nRemPole, "s" * (nRemPole != 1)) - def interpolateMarginalInterpolator(self, mu : paramList = []) -> ListAny: """Obtain interpolated approximant interpolator.""" - mu = checkParameterList(mu, self.data.nparMarginal)[0] + mu = self.checkParameterListMarginal(mu) vbMng(self, "INIT", "Finding nearest neighbor to mu = {}.".format(mu), 95) his = [] - intM = np.array(self.data.NN(mu), dtype = int) + intM = np.array(self.data.marginalInterp(mu), dtype = int) for j in range(len(mu)): i = intM[j] his += [HI()] his[-1].poles = copy(self.data.HIs[i].poles) his[-1].coeffs = copy(self.data.HIs[i].coeffs) his[-1].npar = 1 his[-1].polybasis = self.data.HIs[0].polybasis if not self.data._collapsed: his[-1].pad(self.data.Psupp[i], self.data.projMat.shape[1] - self.data.Psupp[i] - his[-1].shape[0]) vbMng(self, "DEL", "Done finding nearest neighbor.", 95) return his def interpolateMarginalPoles(self, mu : paramList = []) -> ListAny: """Obtain interpolated approximant poles.""" interps = self.interpolateMarginalInterpolator(mu) return [interp.poles for interp in interps] def interpolateMarginalCoeffs(self, mu : paramList = []) -> ListAny: """Obtain interpolated approximant poles.""" interps = self.interpolateMarginalInterpolator(mu) return [interp.coeffs for interp in interps] 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] + mu = self.checkParameterList(mu) if (not hasattr(self, "lastSolvedApproxReduced") or self.lastSolvedApproxReduced != mu): vbMng(self, "INIT", "Evaluating approximant at mu = {}.".format(mu), 12) - muP = self.centerNormalizePivot(mu.data[:, - self.data.directionPivot]) - muM = mu.data[:, self.data.directionMarginal] + muP = self.centerNormalizePivot(mu(self.data.directionPivot)) + muM = mu(self.data.directionMarginal) his = self.interpolateMarginalInterpolator(muM) for i, (mP, hi) in enumerate(zip(muP, his)): uAppR = hi(mP)[:, 0] if i == 0: uApproxR = np.empty((len(uAppR), len(mu)), dtype = uAppR.dtype) uApproxR[:, i] = uAppR self.uApproxReduced = sampleList(uApproxR) 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] + mu = self.checkParameterList(mu) p = emptySampleList() - muP = self.centerNormalizePivot(mu.data[:, self.data.directionPivot]) - muM = mu.data[:, self.data.directionMarginal] + muP = self.centerNormalizePivot(mu(self.data.directionPivot)) + muM = mu(self.data.directionMarginal) his = self.interpolateMarginalInterpolator(muM) for i, (mP, hi) in enumerate(zip(muP, his)): Pval = hi(mP) * np.prod(mP[0] - hi.poles) if i == 0: p.reset((len(Pval), len(mu)), dtype = Pval.dtype) p[i] = Pval 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(mu.data[:, self.data.directionPivot]) - muM = mu.data[:, self.data.directionMarginal] + mu = self.checkParameterList(mu) + muP = self.centerNormalizePivot(mu(self.data.directionPivot)) + muM = mu(self.data.directionMarginal) 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) pls = self.interpolateMarginalPoles(muM) for i, (mP, pl) in enumerate(zip(muP, pls)): N = len(pl) if derP == N: derVal[i] = 1. elif derP >= 0 and derP < N: plDist = muP[0] - pl for terms in combinations(np.arange(N), N - derP): derVal[i] += np.prod(plDist[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) + mVals[rDim] = self.data.mu0(rDim)[0] 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]) + roots = (self.data.scaleFactor[rDim] + * self.interpolateMarginalPoles(mMarg)[0]) + return self.mapParameterList(self.mapParameterList(self.data.mu0(rDim), + idx = [rDim])(0, 0) + + roots, "B", [rDim])(0) def getResidues(self, *args, **kwargs) -> Np2D: """ 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] res = self.interpolateMarginalCoeffs(mMarg)[0][: len(pls), :] if not self.data._collapsed: res = self.data.projMat.dot(res.T).T return pls, res diff --git a/rrompy/reduction_methods/standard/generic_standard_approximant.py b/rrompy/reduction_methods/standard/generic_standard_approximant.py index b1564b1..8c666ad 100644 --- a/rrompy/reduction_methods/standard/generic_standard_approximant.py +++ b/rrompy/reduction_methods/standard/generic_standard_approximant.py @@ -1,191 +1,189 @@ # 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 numpy as np from copy import deepcopy as copy from rrompy.reduction_methods.base.generic_approximant import ( GenericApproximant) -from rrompy.utilities.numerical import dot from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.base.types import Np2D from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) -from rrompy.parameter import checkParameterList __all__ = ['GenericStandardApproximant'] class GenericStandardApproximant(GenericApproximant): """ ROM 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - '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 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; - 'scaleFactorDer': scaling factors for derivative computation. 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. scaleFactorDer: Scaling factors for derivative computation. 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. """ def __init__(self, *args, **kwargs): self._preInit() from rrompy.parameter.parameter_sampling import EmptySampler as ES self._addParametersToList([], [], ["sampler"], [ES()]) super().__init__(*args, **kwargs) self._postInit() @property def mus(self): """Value of mus. Its assignment may reset snapshots.""" return self._mus @mus.setter def mus(self, mus): - mus = checkParameterList(mus, self.npar)[0] + mus = self.checkParameterList(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 muBounds(self): """Value of muBounds.""" return self.sampler.lims @property def sampler(self): """Value of sampler.""" return self._sampler @sampler.setter def sampler(self, sampler): if 'generatePoints' not in dir(sampler): raise RROMPyException("Sampler type not recognized.") if hasattr(self, '_sampler') and self._sampler is not None: samplerOld = self.sampler self._sampler = sampler self._approxParameters["sampler"] = self.sampler if not 'samplerOld' in locals() or samplerOld != self.sampler: self.resetSamples() def setSamples(self, samplingEngine, merge : bool = False): """Copy samplingEngine and samples.""" vbMng(self, "INIT", "Transfering samples.", 15) if isinstance(samplingEngine, (str, list, tuple,)): self.setupSampling() self.samplingEngine.load(samplingEngine, merge) elif merge: try: selfkeys = self.samplingEngine.feature_keys for key in samplingEngine.feature_keys: if key in selfkeys: self.samplingEngine._mergeFeature(key, samplingEngine.feature_vals[key]) except: RROMPyWarning(("Sample merge failed. Falling back to complete " "sampling engine replacement.")) self.samplingEngine = copy(samplingEngine) else: self.samplingEngine = copy(samplingEngine) if self.POD and (self.samplingEngine.nsamples != len(self.samplingEngine.samples_ortho)): RROMPyWarning(("Assigning non-POD sampling engine to POD " "approximant is unstable. Declassing local " "POD to False.")) self._POD = False self._mus = copy(self.samplingEngine.mus) + self.scaleFactor = self.samplingEngine.scaleFactor vbMng(self, "DEL", "Done transfering samples.", 15) def computeSnapshots(self): """Compute snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start snapshot computation.") - self.computeScaleFactor() - self.samplingEngine.scaleFactor = self.scaleFactorDer if self.samplingEngine.nsamples != self.S: + self.computeScaleFactor() + self.samplingEngine.scaleFactor = self.scaleFactorDer vbMng(self, "INIT", "Starting computation of snapshots.", 5) self.mus = self.sampler.generatePoints(self.S) while len(self.mus) > self.S: self.mus.pop() self.samplingEngine.iterSample(self.mus) vbMng(self, "DEL", "Done computing snapshots.", 5) def computeScaleFactor(self): """Compute parameter rescaling factor.""" - self.scaleFactor = .5 * np.abs( - self.muBounds[0] ** self.HFEngine.rescalingExp - - self.muBounds[1] ** self.HFEngine.rescalingExp) + self.scaleFactor = .5 * np.abs(( + self.HFEngine.mapParameterList(self.muBounds[0]) + - self.HFEngine.mapParameterList(self.muBounds[1]))[0]) def _setupTrainedModel(self, pMat:Np2D, pMatUpdate : bool = False): - pMatEff = dot(self.HFEngine.C, pMat) if self.approx_state else pMat + pMatEff = self.HFEngine.applyC(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, "mus": copy(self.mus), "projMat": pMatEff, "scaleFactor": self.scaleFactor, - "rescalingExp": self.HFEngine.rescalingExp} + "parameterMap": self.HFEngine.parameterMap} self.trainedModel.data = self.initializeModelData(datadict)[0] else: self.trainedModel = self.trainedModel if pMatUpdate: self.trainedModel.data.projMat = np.hstack( (self.trainedModel.data.projMat, pMatEff)) else: self.trainedModel.data.projMat = copy(pMatEff) self.trainedModel.data.mus = copy(self.mus) diff --git a/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py b/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py index 2391bfb..30b923a 100644 --- a/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py +++ b/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py @@ -1,646 +1,644 @@ # 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 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.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 +from rrompy.sampling.sample_list import sampleList +from rrompy.parameter import emptyParameterList, parameterList from rrompy.utilities.parallel import masterCore __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] + if isinstance(mus, (parameterList, sampleList)): mus = mus.data + if isinstance(badmus, (parameterList, sampleList)): badmus = badmus.data + if len(badmus) == 0: return np.arange(len(mus)) + proximity = np.min(localL2Distance(mus, badmus), axis = 1) + return np.where(proximity <= tol)[0] 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - '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; - 'scaleFactorDer': scaling factors for derivative computation; - '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. scaleFactorDer: Scaling factors for derivative computation. 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] + mus = self.checkParameterList(mus) tMverb, self.trainedModel.verbosity = self.trainedModel.verbosity, 0 - uApproxRs = self.getApproxReduced(mus) + uApproxRs = self.getApproxReduced(mus).data self.trainedModel.verbosity = tMverb - muTestEff = mus ** self.HFEngine.rescalingExp + muTestEff = self.HFEngine.mapParameterList(mus) 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 + radiusA = np.expand_dims(uApproxRs, 1) * radiusA ff, Lf, LL = self._affineResidualMatricesContraction(radiusb, radiusA) err = np.abs((LL - 2. * np.real(Lf) + ff) / ff) ** .5 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] + mus = self.checkParameterList(mus) 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 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), 3) 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))) and masterCore()): 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) + musre = np.array(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.set_xlim(*list(self.sampler.lims.re(jpar))) 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), 3) 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)])): + or not np.allclose(mu, self.samplingEngine.mus[j - len(mus)])): self.samplingEngine.nextSample(mu) if self._isLastSampleCollinear(): vbMng(self, "MAIN", ("Collinearity above tolerance detected. Starting " "preemptive greedy loop termination."), 3) 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.") - self.computeScaleFactor() if self.samplingEngine.nsamples > 0: return self.resetSamples() + self.computeScaleFactor() self.samplingEngine.scaleFactor = self.scaleFactorDer 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, + idxPop = pruneSamples(self.HFEngine.mapParameterList(muTestBase), + self.HFEngine.mapParameterList(self.mus), 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), 3) 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 @abstractmethod def setupApproxLocal(self) -> int: if self.checkComputedApprox(): return -1 RROMPyAssert(self._mode, message = "Cannot setup approximant.") vbMng(self, "INIT", "Setting up local approximant.", 5) pass vbMng(self, "DEL", "Done setting up local approximant.", 5) return 0 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.", 3) self._collinearityFlag = 0 self._preliminaryTraining() muidx, self.firstGreedyIter = [len(self.muTest) - 1], True errorEstTest, maxErrorEst = [np.inf], np.inf max2ErrorEst, trainedModelOld = np.inf, None while self.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) + 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 self.firstGreedyIter: RROMPyWarning(("Instability in a posteriori " "estimator. Starting preemptive greedy " "loop termination.")) self.muTest, errorEstTest = muTestOld, errorEstTestOld - if self.firstGreedyIter: - self.mus.pop(-1) + if self.firstGreedyIter and muidx[0] < 0: + self.trainedModel = None + raise RROMPyException(("Instability in approximant " + "computation. Aborting greedy " + "iterations.")) + self._S = trainedModelOld.data.approxParameters["S"] + self._approxParameters["S"] = self.S + while self.samplingEngine.nsamples > self.S: self.samplingEngine.popSample() - if muidx[0] < 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) + while len(self.mus) > self.S: self.mus.pop(-1) muidx, maxErrorEst = muidxOld, maxErrorEstOld break if maxErrorEst is not None: max2ErrorEst = np.max(maxErrorEst) vbMng(self, "MAIN", ("Uniform testing error estimate " "{:.4e}.").format(max2ErrorEst), 3) if self.firstGreedyIter: trainedModelOld = copy(self.trainedModel) else: trainedModelOld.data = copy(self.trainedModel.data) self.firstGreedyIter = False + vbMng(self, "DEL", ("Done computing snapshots (final snapshot count: " + "{}).").format(self.S), 3) 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), 3) return 0 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 + if isinstance(pMat, (parameterList, sampleList)): 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 + if isinstance(pMat, (parameterList, sampleList)): + 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 + if isinstance(pMat, (parameterList, sampleList)): 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 + if isinstance(pMat, (parameterList, sampleList)): + 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.projectionMatrix 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 f641b4c..232335e 100644 --- a/rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py +++ b/rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py @@ -1,542 +1,538 @@ # 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, polyfitname, PolynomialInterpolator as PI, - polyvanderTotal as pvT) + polyvander) 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.base.verbosity_depth import (verbosityManager as vbMng, + getVerbosityDepth, setVerbosityDepth) 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - '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', 'LOOK_AHEAD', 'LOOK_AHEAD_RES', '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; - - '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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - '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; - - 'correctorForce': whether corrector should forcefully delete bad - poles; - - 'correctorTol': tolerance for corrector step; - - 'correctorMaxIter': maximum number of corrector iterations. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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. scaleFactorDer: Scaling factors for derivative computation. 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. - correctorForce: Whether corrector should forcefully delete bad poles. - correctorTol: Tolerance for corrector step. - correctorMaxIter: Maximum number of corrector iterations. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. 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", "LOOK_AHEAD", "LOOK_AHEAD_RES", "LOOK_AHEAD_OUTPUT", "NONE"] def __init__(self, *args, **kwargs): self._preInit() self._addParametersToList(["errorEstimatorKind"], ["DISCREPANCY"], toBeExcluded = ["M", "N", "polydegreetype", "radialDirectionalWeights"]) 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._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) + return polyvander(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] + mus = self.checkParameterList(mus) muCTest = self.trainedModel.centerNormalize(mus) tMverb, 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 + muTrainEff = self.HFEngine.mapParameterList(self.mus) + muTestEff = self.HFEngine.mapParameterList(mus) 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 self.trainedModel.verbosity = tMverb 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 return err def getErrorEstimatorLookAhead(self, mus:Np1D, what : str = "") -> Tuple[Np1D, List[int]]: """Residual estimator based on look-ahead idea.""" errTest, QTest, idxMaxEst = self._EIMStep(mus) _approx_state_old = self.approx_state if what == "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: if what == "RES" and not self.HFEngine.isCEye: raise RROMPyException(("Cannot compute LOOK_AHEAD_RES " "estimator in fragile mode for " "non-scalar C.")) app_muTestSample = dot(self.trainedModel.data.projMat[:, : app_muTestSample.shape[0]], - app_muTestSample.data) + app_muTestSample) else: app_muTestSample = dot(self.samplingEngine.projectionMatrix, - app_muTestSample.data) + app_muTestSample) if what == "RES": errmu = self.HFEngine.residual(mu_muTestSample, app_muTestSample, post_c = False) solmu = self.HFEngine.residual(mu_muTestSample, None, post_c = False) else: for j, mu in enumerate(mu_muTestSample): uEx = self.samplingEngine.nextSample(mu) if j == 0: solmu = emptySampleList() solmu.reset((len(uEx), len(mu_muTestSample)), dtype = uEx.dtype) solmu[j] = uEx if what == "OUTPUT" and self.approx_state: - solmu = sampleList(self.HFEngine.applyC(solmu.data)) + solmu = sampleList(self.HFEngine.applyC(solmu)) app_muTestSample = sampleList(self.HFEngine.applyC( - app_muTestSample.data)) + app_muTestSample)) 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), len(mu_muTestSample)), dtype = np.complex) errT[np.arange(len(self.mus), len(musT)), np.arange(len(mu_muTestSample))] = errsamples * QTest[idxMaxEst] vanT = self._polyvanderAuxiliary(musT, self.E + 1, self.polybasis) fitOut = customFit(vanT, errT, full = True, rcond = self.interpRcond) vbMng(self, "MAIN", ("Fitting {} samples with degree {} through {}... Conditioning " "of LS system: {:.4e}.").format(len(vanT), self.E + 1, polyfitname(self.polybasis), fitOut[1][2][0] / fitOut[1][2][-1]), 15) vanC = self._polyvanderAuxiliary(musC, self.E + 1, self.polybasis) err = np.sum(np.abs(vanC.dot(fitOut[0])), axis = -1) / QTest 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] + mus = self.checkParameterList(mus) tMverb, 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) self.trainedModel.verbosity = tMverb 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: 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]) 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] + mus = self.checkParameterList(mus) 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[: 10] == "LOOK_AHEAD": err, idxMaxEst = self.getErrorEstimatorLookAhead(mus, self.errorEstimatorKind[11 :]) 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."), 15) 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 _setSampleBatch(self, maxS:int): self.sampleBatchIdx, self.sampleBatchSize, S = -1, 0, 0 nextBatchSize = 1 while S + nextBatchSize <= maxS: self.sampleBatchIdx += 1 self.sampleBatchSize = nextBatchSize S += self.sampleBatchSize nextBatchSize = totalDegreeN(self.npar - 1, self.sampleBatchIdx + 1) return S def _preliminaryTraining(self): """Initialize starting snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") if self.samplingEngine.nsamples > 0: return self._S = self._setSampleBatch(self.S) super()._preliminaryTraining() self.M, self.N = ("AUTO",) * 2 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.projectionMatrix if self.trainedModel is not None: pMat = pMat[:, len(self.trainedModel.data.mus) :] self._setupTrainedModel(pMat, self.trainedModel is not None) self.catchInstability = 2 + vbDepth = getVerbosityDepth() unstable = False if self.E > 0: try: Q = self._setupDenominator()[0] except RROMPyException as RE: + setVerbosityDepth(vbDepth) 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: + setVerbosityDepth(vbDepth) 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 setupApprox(self, plotEst : str = "NONE") -> int: val = super().setupApprox(plotEst) if val == 0: - self._iterCorrector() + self._setupRational(self.trainedModel.data.Q, + self.trainedModel.data.P) self.trainedModel.data.approxParameters = copy( self.approxParameters) return val def loadTrainedModel(self, filename:str): """Load trained reduced model from file.""" super().loadTrainedModel(filename) self._setSampleBatch(self.S + 1) - diff --git a/rrompy/reduction_methods/standard/nearest_neighbor.py b/rrompy/reduction_methods/standard/nearest_neighbor.py index 4af1bd8..932856c 100644 --- a/rrompy/reduction_methods/standard/nearest_neighbor.py +++ b/rrompy/reduction_methods/standard/nearest_neighbor.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 . # from copy import deepcopy as copy import numpy as np from .generic_standard_approximant import GenericStandardApproximant from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.poly_fitting.nearest_neighbor import ( NearestNeighborInterpolator as NNI) 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator; - 'nNeighbors': number of nearest neighbors; defaults to 1; - 'radialDirectionalWeights': directional weights for computation of parameter distance; 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; - 'scaleFactorDer': scaling factors for derivative computation; - 'nNeighbors': number of nearest neighbors; - 'radialDirectionalWeights': directional weights for computation of parameter distance. 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. scaleFactorDer: Scaling factors for derivative computation. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. nNeighbors: Number of nearest neighbors. radialDirectionalWeights: Directional weights for computation of parameter distance. 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(["nNeighbors", "radialDirectionalWeights"], - [1, [1.]]) + [1, 1.]) super().__init__(*args, **kwargs) self._postInit() @property def tModelType(self): from .trained_model.trained_model_nearest_neighbor import ( TrainedModelNearestNeighbor) return TrainedModelNearestNeighbor @property def nNeighbors(self): """Value of nNeighbors.""" return self._nNeighbors @nNeighbors.setter def nNeighbors(self, nNeighbors): self._nNeighbors = max(1, nNeighbors) self._approxParameters["nNeighbors"] = self.nNeighbors @property def radialDirectionalWeights(self): """Value of radialDirectionalWeights.""" return self._radialDirectionalWeights @radialDirectionalWeights.setter def radialDirectionalWeights(self, radialDirectionalWeights): if hasattr(radialDirectionalWeights, "__len__"): radialDirectionalWeights = list(radialDirectionalWeights) else: radialDirectionalWeights = [radialDirectionalWeights] self._radialDirectionalWeights = radialDirectionalWeights self._approxParameters["radialDirectionalWeights"] = ( self.radialDirectionalWeights) 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() setData = self.trainedModel is None self._setupTrainedModel(self.samplingEngine.projectionMatrix) if setData: self.trainedModel.data.NN = NNI() if self.POD: R = self.samplingEngine.RPOD if isinstance(R, (np.ndarray,)): vals, supp = list(R.T), [0] * R.shape[1] else: vals, supp = [], [] for j in range(R.shape[1]): idx = R.indices[R.indptr[j] : R.indptr[j + 1]] if len(idx) == 0: supp += [0] val = np.empty(0, dtype = R.dtype) else: supp += [idx[0]] idx = idx - idx[0] val = np.zeros(idx[-1] + 1, dtype = R.dtype) val[idx] = R.data[R.indptr[j] : R.indptr[j + 1]] vals += [val] else: vals = [np.ones(1)] * len(self.mus) supp = list(range(len(self.mus))) self.trainedModel.data.NN.setupByInterpolation(self.mus, np.arange(len(self.mus)), self.nNeighbors, self.radialDirectionalWeights) self.trainedModel.data.vals, self.trainedModel.data.supp = vals, supp 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 57a8aee..6cfedf6 100644 --- a/rrompy/reduction_methods/standard/rational_interpolant.py +++ b/rrompy/reduction_methods/standard/rational_interpolant.py @@ -1,670 +1,631 @@ # 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) + polybases as ppb, polyfitname, + polyvander as pvP, 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.base.types import Np1D, Np2D, Tuple, List, sampList +from rrompy.utilities.base.types import (Np1D, Np2D, Tuple, List, sampList, + interpEng) from rrompy.utilities.base import verbosityManager as vbMng 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - '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; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; defaults to [-1, -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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - '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; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; - '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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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. scaleFactorDer: Scaling factors for derivative computation. 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. + radialDirectionalWeightsAdapt: Bounds for adaptive rescaling of radial + basis weights. 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. 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", "interpRcond", - "robustTol", "correctorForce", - "correctorTol", "correctorMaxIter"], - ["MONOMIAL", "AUTO", "AUTO", "TOTAL", [1.], - -1, 0., False, 0., 1]) + "radialDirectionalWeights", + "radialDirectionalWeightsAdapt", + "interpRcond", "robustTol", + "cutOffTolerance", "residueTol"], + ["MONOMIAL", "AUTO", "AUTO", "TOTAL", 1., + [-1., -1.], -1, 0., np.inf, 0.]) super().__init__(*args, **kwargs) self.catchInstability = 0 self._postInit() @property def tModelType(self): 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: 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 hasattr(radialDirectionalWeights, "__len__"): radialDirectionalWeights = list(radialDirectionalWeights) else: radialDirectionalWeights = [radialDirectionalWeights] self._radialDirectionalWeights = radialDirectionalWeights self._approxParameters["radialDirectionalWeights"] = ( self.radialDirectionalWeights) + @property + def radialDirectionalWeightsAdapt(self): + """Value of radialDirectionalWeightsAdapt.""" + return self._radialDirectionalWeightsAdapt + @radialDirectionalWeightsAdapt.setter + def radialDirectionalWeightsAdapt(self, radialDirectionalWeightsAdapt): + self._radialDirectionalWeightsAdapt = radialDirectionalWeightsAdapt + self._approxParameters["radialDirectionalWeightsAdapt"] = ( + self.radialDirectionalWeightsAdapt) + @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 + 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 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 residueTol(self): + """Value of residueTol.""" + return self._residueTol + @residueTol.setter + def residueTol(self, residueTol): + if residueTol < 0. or (residueTol > 0. and self.npar > 1): + RROMPyWarning("Overriding prescribed residue tolerance to 0.") + residueTol = 0. + self._residueTol = residueTol + self._approxParameters["residueTol"] = self.residueTol + 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, self.scaleFactorRel) if self.POD: Qevaldiag = Qevaldiag.dot(self.samplingEngine.RPOD.T) 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: - pParRest = [self.verbosity >= 5, self.polydegreetype == "TOTAL", + pParRest = [self.M, self.polybasis, self.verbosity >= 5, + self.polydegreetype == "TOTAL", {"derIdxs": self._derIdxs, "reorder": self._reorder, "scl": self.scaleFactorRel}] if self.polybasis in ppb: p = PI() else: self.computeScaleFactor() - rDWEff = [w * f for w, f in zip(self.radialDirectionalWeights, - self.scaleFactor)] - pParRest = [rDWEff] + pParRest + rDWEff = np.array([w * f for w, f in zip( + self.radialDirectionalWeights, + self.scaleFactor)]) + pParRest = pParRest[: 2] + [rDWEff] + pParRest[2 :] + pParRest[-1]["optimizeScalingBounds"] = ( + self.radialDirectionalWeightsAdapt) p = RBI() if self.polybasis in ppb + rbpb: pParRest += [{"rcond": self.interpRcond}] wellCond, msg = p.setupByInterpolation(self._musUniqueCN, - Qevaldiag, self.M, - self.polybasis, *pParRest) + Qevaldiag, *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) vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing M " "by 1."), 10) self.M = self.M - 1 if self.M < 0: raise RROMPyException(("Instability in computation of numerator. " "Aborting.")) self.M = M 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() self._setupTrainedModel(self.samplingEngine.projectionMatrix) - self._iterCorrector() + self._setupRational(self._setupDenominator()[0]) self.trainedModel.data.approxParameters = copy(self.approxParameters) vbMng(self, "DEL", "Done setting up approximant.", 5) return 0 - def _iterCorrector(self): - noCorrector = self.correctorTol <= 0. or (self.correctorMaxIter <= 1 - and not self.correctorForce) - if noCorrector: - vbMng(self, "INIT", "Starting approximant finalization.", 5) - else: - vbMng(self, "INIT", "Starting correction iterations.", 5) - 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._samplesOld = copy(self.samplingEngine.RPOD) - else: - self._samplesOld = copy(self.samplingEngine.samples) - Nauto = hasattr(self, "_N_isauto") - for j in range(self.correctorMaxIter): - if self.N > 0 or (Nauto and self.S > self.npar): - Q = self._setupDenominator()[0] - if hasattr(self, "_N_isauto"): del self._N_isauto - 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() - if noCorrector or (j >= self.correctorMaxIter - 1 - and not self.correctorForce): - self.N = 0 - else: - self._applyCorrector(j) - if self.N <= 0: break - if Nauto: self._N_isauto = True - self.N = _N0 - if noCorrector: - vbMng(self, "DEL", "Terminated approximant finalization.", 5) - return - if self.POD: - self.samplingEngine.RPOD = self._samplesOld - 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.", 5) - - def _applyCorrector(self, j:int): - 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( + def _setupRational(self, Q:interpEng, P : interpEng = None): + vbMng(self, "INIT", "Starting approximant finalization.", 5) + computeNum = P is None + if self.N > 0 and self.npar == 1: + foci = self.sampler.normalFoci() + ground = self.sampler.groundPotential() + if not np.isinf(self.cutOffTolerance): + pls = Q.roots() + ground = self.sampler.groundPotential() + idKeep = np.logical_and(np.logical_not(np.isinf(pls)), + potential(pls, foci) / ground - 1. + <= self.cutOffTolerance) + if np.sum(idKeep) < self.N: + vbMng(self, "MAIN", + ("Removing {} poles out of {} due to cut " + "off.").format(np.sum(idKeep), self.N), 10) + Q = PI() + Q.npar = self.npar + Q.polybasis = self.polybasis0 + Q.coeffs = np.ones(1, dtype = np.complex) + for pl in pls[idKeep]: + Q.coeffs = polyTimes(Q.coeffs, [- pl, 1.], + Pbasis = Q.polybasis, Rbasis = Q.polybasis) + Q.coeffs /= np.linalg.norm(Q.coeffs) + self.N = np.sum(idKeep) + computeNum = True + if self.residueTol > 0: + if computeNum: P = self._setupNumerator() + cfs, pls, _ = rational2heaviside(P, Q) + cfs = cfs[: self.N] + if self.POD: + resEff = np.linalg.norm(cfs, axis = 1) + else: + resEff = self.HFEngine.norm( self.samplingEngine.projectionMatrix.dot(cfs.T), is_state = self.approx_state) - potentialEff = (potential(pls, self.sampler.normalFoci()) - / self.sampler.groundPotential()) - potentialEff[np.logical_or(potentialEff < 1., np.isinf(pls))] = 1. - resEff[np.isinf(pls)] = 0. - resEff /= potentialEff - idxKeep = np.where(resEff >= self.correctorTol * np.max(resEff))[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 or j < self.correctorMaxIter - 1) - 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 * np.max(resEff)]), - 65) - if j < self.correctorMaxIter - 1: - That = polyTimesTable(self._Qhat, self._musUniqueCN, - self._reorder, self._derIdxs, - self.scaleFactorRel).T - if self.POD: - self.samplingEngine.RPOD = self._samplesOld.dot(That) - else: - self.samplingEngine.samples = self._samplesOld.dot(That) + potEff = potential(pls, foci) / ground + potEff[np.logical_or(potEff < 1., np.isinf(pls))] = 1. + resEff[np.isinf(pls)] = 0. + resEff /= potEff + idKeep = resEff >= self.residueTol * np.max(resEff) + if np.sum(idKeep) < self.N: + vbMng(self, "MAIN", + ("Removing {} poles out of {} due to residue " + "magnitude.").format(np.sum(idKeep), self.N), 10) + Q = PI() + Q.npar = self.npar + Q.polybasis = self.polybasis0 + Q.coeffs = np.ones(1, dtype = np.complex) + for pl in pls[idKeep]: + Q.coeffs = polyTimes(Q.coeffs, [- pl, 1.], + Pbasis = Q.polybasis, Rbasis = Q.polybasis) + Q.coeffs /= np.linalg.norm(Q.coeffs) + self.N = np.sum(idKeep) + else: + computeNum = False + self.trainedModel.data.Q = Q + if computeNum: P = self._setupNumerator() + self.trainedModel.data.P = P + vbMng(self, "DEL", "Terminated approximant finalization.", 5) 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, - self.scaleFactorRel] + pvPPar = [self.polybasis0, self._derIdxs, self._reorder, + self.scaleFactorRel] if hasattr(self, "_M_isauto"): self._setMAuto() E = max(self.M, self.N) while E >= 0: if self.polydegreetype == "TOTAL": Eeff = E idxsB = totalDegreeMaxMask(E, self.npar) else: #if self.polydegreetype == "FULL": Eeff = [E] * self.npar idxsB = fullDegreeMaxMask(E, self.npar) - TE = TEGen(self._musUniqueCN, Eeff, *TEGenPar) + TE = pvP(self._musUniqueCN, Eeff, *pvPPar) 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], E, 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) EeqN = E == self.N vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing E {}" "by 1.").format("and N " * EeqN), 10) if EeqN: self.N = self.N - 1 E -= 1 if self.N < 0: raise RROMPyException(("Instability in computation of " "denominator. Aborting.")) invD = vanderInvTable(fitinv, idxsB, self._reorder, self._derIdxs) if self.N == E: TN = TE else: 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) - TN = TEGen(self._musUniqueCN, Neff, *TEGenPar) + TN = pvP(self._musUniqueCN, Neff, *pvPPar) for k in range(len(invD)): invD[k] = dot(invD[k], TN) 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) -> Np2D: """ Obtain approximant residues. Returns: Matrix with residues as columns. """ return self.trainedModel.getResidues(*args, **kwargs) diff --git a/rrompy/reduction_methods/standard/rational_pade.py b/rrompy/reduction_methods/standard/rational_pade.py index 24d1f71..6852778 100644 --- a/rrompy/reduction_methods/standard/rational_pade.py +++ b/rrompy/reduction_methods/standard/rational_pade.py @@ -1,310 +1,313 @@ # 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 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.polynomial import (polybases as ppb, + polyfitname, polyvander as pvP, + polyTimesTable, vanderInvTable, + PolynomialInterpolator as PI) from rrompy.utilities.poly_fitting.radial_basis import (polybases as rbpb, RadialBasisInterpolator as RBI) from rrompy.utilities.base.types import Np2D, Tuple, List from rrompy.utilities.base import verbosityManager as vbMng 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; - 'scaleFactorDer': scaling factors for derivative computation; defaults to 'AUTO'; - '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; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; defaults to [-1, -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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + defaults to np.inf; + - 'residueTol': tolerance for residue elimination; defaults to 0., + i.e. no bad residues. 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; - 'scaleFactorDer': scaling factors for derivative computation; - '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; + - 'radialDirectionalWeightsAdapt': bounds for adaptive rescaling of + radial basis weights; - '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. + - 'cutOffTolerance': tolerance for ignoring parasitic poles; + - 'residueTol': tolerance for residue elimination. 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. scaleFactorDer: Scaling factors for derivative computation. 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. + radialDirectionalWeightsAdapt: Bounds for adaptive rescaling of radial + basis weights. 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. + cutOffTolerance: Tolerance for ignoring parasitic poles. + residueTol: Tolerance for residue elimination. 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, self.scaleFactorRel) if self.POD: Qevaldiag = Qevaldiag.dot(self.samplingEngine.RPOD.T) 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: Seff = cfun(self.M, self.npar) - pParRest = [self.verbosity >= 5, self.polydegreetype == "TOTAL", + pParRest = [self.M, self.polybasis, self.verbosity >= 5, + self.polydegreetype == "TOTAL", {"derIdxs": [self._derIdxs[0][: Seff]], "reorder": self._reorder[: Seff], "scl": self.scaleFactorRel}] if self.polybasis in ppb: p = PI() else: self.computeScaleFactor() - rDWEff = [w * f for w, f in zip(self.radialDirectionalWeights, - self.scaleFactor)] - pParRest = [rDWEff] + pParRest + rDWEff = np.array([w * f for w, f in zip( + self.radialDirectionalWeights, + self.scaleFactor)]) + pParRest = pParRest[: 2] + [rDWEff] + pParRest[2 :] + pParRest[-1]["optimizeScalingBounds"] = ( + self.radialDirectionalWeightsAdapt) p = RBI() 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) vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing M " "by 1."), 10) self.M = self.M - 1 if self.M < 0: raise RROMPyException(("Instability in computation of numerator. " "Aborting.")) self.M = M 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 + cfun = totalDegreeN else: - cfun, TEGen = fullDegreeN, pvP + cfun = fullDegreeN E = max(self.M, self.N) while E >= 0: Seff = cfun(E, self.npar) - TEGenPar = [self.polybasis0, [self._derIdxs[0][: Seff]], + pvPPar = [self.polybasis0, [self._derIdxs[0][: Seff]], self._reorder[: Seff], self.scaleFactorRel] if self.polydegreetype == "TOTAL": Eeff = E idxsB = totalDegreeMaxMask(E, self.npar) else: #if self.polydegreetype == "FULL": Eeff = [E] * self.npar idxsB = fullDegreeMaxMask(E, self.npar) - TE = TEGen(self._musUniqueCN, Eeff, *TEGenPar) + TE = pvP(self._musUniqueCN, Eeff, *pvPPar) 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], E, 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) EeqN = E == self.N vbMng(self, "MAIN", ("Polyfit is poorly conditioned. Reducing E {}" "by 1.").format("and N " * EeqN), 10) if EeqN: self.N = self.N - 1 E -= 1 if self.N < 0: raise RROMPyException(("Instability in computation of " "denominator. Aborting.")) invD = vanderInvTable(fitinv, idxsB, self._reorder[: Seff], [self._derIdxs[0][: Seff]]) if self.N == E: TN = TE else: 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) - TN = TEGen(self._musUniqueCN, Neff, *TEGenPar) + TN = pvP(self._musUniqueCN, Neff, *pvPPar) for k in range(len(invD)): invD[k] = dot(invD[k], TN) return invD, fitinv 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 9c1b127..d99eeca 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,95 +1,94 @@ # 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.trained_model import ( TrainedModel) from rrompy.utilities.numerical.compress_matrix import compressMatrix from rrompy.utilities.base.types import Np1D, paramList, sampList from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.exception_manager import RROMPyWarning -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 compress(self, collapse : bool = False, tol : float = 0., *args, **kwargs): if not collapse and tol <= 0.: return RMat = self.data.projMat if not collapse: if hasattr(self.data, "_compressTol"): RROMPyWarning(("Recompressing already compressed model is " "ineffective. Aborting.")) return self.data.projMat, RMat, _ = compressMatrix(RMat, tol, *args, **kwargs) for j, suppj in enumerate(self.data.supp): self.data.vals[j] = RMat[:, suppj : suppj + len(self.data.vals[j]) ].dot(self.data.vals[j]) self.data.supp[j] = [0] super().compress(collapse, tol) def getNearestNeighbor(self, mu : paramList = []) -> Np1D: """ Find nearest neighbor to arbitrary parameter. Args: mu: Target parameter. """ - mu = checkParameterList(mu, self.data.npar)[0] + mu = self.checkParameterList(mu) vbMng(self, "INIT", "Finding nearest neighbor to mu = {}.".format(mu), 22) nn = [] intM = np.array(self.data.NN(mu), dtype = int) for iM in intM: val, supp = self.data.vals[iM], self.data.supp[iM] nn += [np.pad(val, (supp, self.data.projMat.shape[1] - supp - len(val)), 'constant')] nn = sampleList(nn) vbMng(self, "DEL", "Done finding nearest neighbor.", 22) return nn 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] + mu = self.checkParameterList(mu) if (not hasattr(self, "lastSolvedApproxReduced") or self.lastSolvedApproxReduced != mu): vbMng(self, "INIT", "Evaluating approximant at mu = {}.".format(mu), 12) self.uApproxReduced = self.getNearestNeighbor(mu) 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 95ed030..a14f10e 100644 --- a/rrompy/reduction_methods/standard/trained_model/trained_model_rational.py +++ b/rrompy/reduction_methods/standard/trained_model/trained_model_rational.py @@ -1,191 +1,190 @@ # 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.trained_model import ( TrainedModel) +from rrompy.utilities.numerical import dot from rrompy.utilities.numerical.compress_matrix import compressMatrix from rrompy.utilities.base.types import (Np1D, Np2D, 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.parameter import 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 compress(self, collapse : bool = False, tol : float = 0., *args, **kwargs): if not collapse and tol <= 0.: return RMat = self.data.projMat if not collapse: if hasattr(self.data, "_compressTol"): RROMPyWarning(("Recompressing already compressed model is " "ineffective. Aborting.")) return self.data.projMat, RMat, _ = compressMatrix(RMat, tol, *args, **kwargs) self.data.P.postmultiplyTensorize(RMat.T) super().compress(collapse, tol) 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] + mu = self.checkParameterList(mu) if mu0 is None: mu0 = self.data.mu0 - rad = ((mu ** self.data.rescalingExp - mu0 ** self.data.rescalingExp) - / self.data.scaleFactor) - return rad + return (self.mapParameterList(mu) + - self.mapParameterList(mu0)) / self.data.scaleFactor def getPVal(self, mu : paramList = []) -> sampList: """ Evaluate rational numerator at arbitrary parameter. Args: mu: Target parameter. """ - mu = checkParameterList(mu, self.data.npar)[0] + mu = self.checkParameterList(mu) vbMng(self, "INIT", "Evaluating numerator at mu = {}.".format(mu), 17) p = sampleList(self.data.P(self.centerNormalize(mu))) 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] + mu = self.checkParameterList(mu) vbMng(self, "INIT", "Evaluating denominator at mu = {}.".format(mu), 17) q = self.data.Q(self.centerNormalize(mu), 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] + mu = self.checkParameterList(mu) 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: QV[QVzero] = np.finfo(np.complex).eps / (1. + self.data.Q.deg[0]) - uApproxR = self.getPVal(mu).data / QV - self.uApproxReduced = sampleList(uApproxR) + 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 = list(self.centerNormalize(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]) + roots = self.data.scaleFactor[rDim] * self.data.Q.roots(mVals) + return self.mapParameterList(self.mapParameterList(self.data.mu0(rDim), + idx = [rDim])(0, 0) + + roots, "B", [rDim])(0) def getResidues(self, *args, **kwargs) -> Np2D: """ 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 + mValsLoc = list(mVals) + mValsLoc[rDim] = pl + poles[k] = mValsLoc 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.getPVal(poles).data + Res = self.getPVal(poles) if not self.data._collapsed: - Res = self.data.projMat[:, : Res.shape[0]].dot(Res) + Res = sampleList(dot(self.data.projMat[:, : Res.shape[0]], Res)) res = Res / QV return pls, res.T 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 e01888f..b0c43e7 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,158 +1,158 @@ # 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.trained_model import ( TrainedModel) from rrompy.reduction_methods.base.reduced_basis_utils import ( projectAffineDecomposition) 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.compress_matrix import compressMatrix 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.parameter import checkParameter from rrompy.sampling import sampleList -from rrompy.utilities.parallel import (COMM, poolRank, masterCore, listScatter, - matrixGatherv) +from rrompy.utilities.parallel import (poolRank, masterCore, listScatter, + matrixGatherv, isend, recv) __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 compress(self, collapse : bool = False, tol : float = 0., *args, **kwargs): if collapse: raise RROMPyException("Cannot collapse implicit surrogates.") if tol <= 0.: return if hasattr(self.data, "_compressTol"): RROMPyWarning(("Recompressing already compressed model is " "ineffective. Aborting.")) return self.data.projMat, RMat, _ = compressMatrix(self.data.projMat, tol, *args, **kwargs) self.data.ARBs, self.data.bRBs = projectAffineDecomposition( self.data.ARBs, self.data.bRBs, RMat) super().compress(collapse, tol) 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 + muEff = self.mapParameterList(mu) 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] + mu = self.checkParameterList(mu) if (not hasattr(self, "lastSolvedApproxReduced") or self.lastSolvedApproxReduced != mu): vbMng(self, "INIT", "Computing RB solution at mu = {}.".format(mu), 12) mu, _, sizes = listScatter(mu, return_sizes = True) - mu = checkParameterList(mu)[0] + mu = self.checkParameterList(mu) req, emptyCores = [], np.where(np.logical_not(sizes))[0] if len(mu) == 0: vbMng(self, "MAIN", "Idling.", 37) - uL, uT = COMM.recv(source = 0, tag = poolRank()) + uL, uT = recv(source = 0, tag = poolRank()) uApproxR = np.empty((uL, 0), dtype = uT) else: for j, mj in enumerate(mu): self.assembleReducedModel(mj) try: uAppR = np.linalg.solve(self.data.ARBmu, self.data.bRBmu) except np.linalg.LinAlgError as e: raise RROMPyException(e) if j == 0: uApproxR = np.empty((len(uAppR), len(mu)), dtype = uAppR.dtype) if masterCore(): for dest in emptyCores: - req += [COMM.isend((len(uAppR), uAppR.dtype), - dest = dest, tag = dest)] + req += [isend((len(uAppR), uAppR.dtype), + dest = dest, tag = dest)] uApproxR[:, j] = uAppR for r in req: r.wait() uApproxR = matrixGatherv(uApproxR, sizes) self.uApproxReduced = sampleList(uApproxR) 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 = checkParameter(mVals, return_data = True).flatten() mVals[rDim] = fp ARBs = marginalizePolyList(ARBs, mVals, "auto") ev = eigvalsNonlinearDense(ARBs, jSupp = jSupp, **kwargs) - return np.power(ev, 1. / self.data.rescalingExp[rDim]) + return self.mapParameterList(ev, "B", [rDim])(0) diff --git a/rrompy/sampling/engines/pod_engine.py b/rrompy/sampling/engines/pod_engine.py index c9ebe8f..1eb6e66 100644 --- a/rrompy/sampling/engines/pod_engine.py +++ b/rrompy/sampling/engines/pod_engine.py @@ -1,144 +1,137 @@ # 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 warnings import catch_warnings from rrompy.utilities.base.types import Np1D, Np2D, Tuple, HFEng, sampList -from rrompy.utilities.numerical import dot from rrompy.sampling import sampleList __all__ = ['PODEngine'] class PODEngine: """ POD engine for general matrix orthogonalization. """ def __init__(self, HFEngine:HFEng): self.HFEngine = HFEngine 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 GS(self, a:Np1D, Q:sampList, n : int = -1, is_state : bool = True) -> Tuple[Np1D, Np1D, bool]: """ Compute 1 Gram-Schmidt step with given projector. Args: a: vector to be projected; Q: orthogonal projection matrix; n: number of columns of Q to be considered; is_state: whether state-norm should be used. Returns: Resulting normalized vector, coefficients of a wrt the updated basis, whether computation is ill-conditioned. """ if n == -1: n = Q.shape[1] r = np.zeros((n + 1,), dtype = Q.dtype) if n > 0: + from rrompy.utilities.numerical import dot Q = Q[: n] for j in range(2): # twice is enough! nu = self.HFEngine.innerProduct(a, Q, is_state = is_state) a = a - dot(Q, nu) r[:-1] = r[:-1] + nu.flatten() r[-1] = self.HFEngine.norm(a, is_state = is_state) ill_cond = False with catch_warnings(record = True) as w: snr = np.abs(r[-1]) / np.linalg.norm(r) - if len(w) > 0 or np.isclose(snr, 0.): + if len(w) > 0 or snr < np.finfo(np.complex).eps * len(r): ill_cond = True r[-1] = 1. a = a / r[-1] return a, r, ill_cond def generalizedQR(self, A:sampList, Q0 : sampList = None, only_R : bool = False, genTrials : int = 10, is_state : bool = True) -> Tuple[sampList, Np2D]: """ Compute generalized QR decomposition of a matrix through Householder - method. + method; see Trefethen, IMA J.N.A., 2010. Args: A: matrix to be decomposed; Q0(optional): initial orthogonal guess for Q; defaults to random; only_R(optional): whether to skip reconstruction of Q; defaults to False. genTrials(optional): number of trials of generation of linearly independent vector; defaults to 10. is_state(optional): whether state-norm should be used; defaults to True. Returns: Resulting (orthogonal and )upper-triangular factor(s). """ Nh, N = A.shape B = copy(A) - V = copy(A) + V = sampleList(np.zeros(A.shape, dtype = A.dtype)) R = np.zeros((N, N), dtype = A.dtype) - if Q0 is None: - Q = sampleList(np.zeros(A.shape, dtype = A.dtype) - + np.random.randn(*(A.shape))) - else: - Q = copy(Q0) + Q = copy(V) if Q0 is None else sampleList(Q0) for k in range(N): a = B[k] R[k, k] = self.HFEngine.norm(a, is_state = is_state) if Q0 is None and k < Nh: for _ in range(genTrials): Q[k], _, illC = self.GS(np.random.randn(Nh), Q, k, is_state) if not illC: break else: - illC = False - if illC or k >= Nh: - Q[k] = np.zeros(Nh, dtype = Q.dtype) - alpha = 0. + illC = k >= Nh + if illC: + if Q0 is not None or k < Nh: Q[k] = 0. else: alpha = self.HFEngine.innerProduct(a, Q[k], is_state = is_state) if np.isclose(np.abs(alpha), 0.): s = 1. - else: s = alpha / np.abs(alpha) + else: s = - alpha / np.abs(alpha) Q[k] = s * Q[k] - V[k], _, _ = self.GS(R[k, k] * Q[k] - a, Q, k, is_state) + V[k], _, _ = self.GS(R[k, k] * Q[k] - a, Q, k, is_state) J = np.arange(k + 1, N) vtB = self.HFEngine.innerProduct(B[J], V[k], is_state = is_state) - B.data[:, J] -= 2 * np.outer(V[k], vtB) - if illC: - R[k, J] = 0. - else: + B[J] = (B[J] - 2 * np.outer(V[k], vtB)).T + if not illC: R[k, J] = self.HFEngine.innerProduct(B[J], Q[k], is_state = is_state) - B.data[:, J] -= np.outer(Q[k], R[k, J]) + B[J] = (B[J] - np.outer(Q[k], R[k, J])).T if only_R: return R - for k in range(N - 1, -1, -1): - J = list(range(k, N)) + for k in range(min(Nh, N) - 1, -1, -1): + J = np.arange(k, min(Nh, N)) vtQ = self.HFEngine.innerProduct(Q[J], V[k], is_state = is_state) - Q.data[:, J] -= 2 * np.outer(V[k], vtQ) + Q[J] = (Q[J] - 2 * np.outer(V[k], vtQ)).T return Q, R diff --git a/rrompy/sampling/engines/sampling_engine_standard.py b/rrompy/sampling/engines/sampling_engine_standard.py index 6dd28a9..b663ba4 100644 --- a/rrompy/sampling/engines/sampling_engine_standard.py +++ b/rrompy/sampling/engines/sampling_engine_standard.py @@ -1,357 +1,358 @@ # 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 numbers import Number from copy import deepcopy as copy import numpy as np from warnings import catch_warnings from rrompy.utilities.base.types import (Np1D, Np2D, HFEng, List, paramVal, Any, paramList, sampList, Tuple, TupleAny, DictAny, FigHandle) from rrompy.utilities.base.data_structures import getNewFilename from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.exception_manager import (RROMPyWarning, RROMPyException, RROMPyAssert, RROMPy_READY, RROMPy_FRAGILE) -from rrompy.utilities.numerical import dot from rrompy.utilities.numerical.hash_derivative import nextDerivativeIndices from rrompy.utilities.base.pickle_utilities import pickleDump, pickleLoad from rrompy.parameter import (emptyParameterList, checkParameter, checkParameterList) from rrompy.sampling import sampleList, emptySampleList from rrompy.utilities.parallel import bcast, masterCore __all__ = ['SamplingEngineStandard'] class SamplingEngineStandard: def __init__(self, HFEngine:HFEng, sample_state : bool = False, verbosity : int = 10, timestamp : bool = True, scaleFactor : Np1D = None): 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) self.scaleFactor = scaleFactor 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() @property def scaleFactor(self): """Value of scaleFactor.""" return self._scaleFactor @scaleFactor.setter def scaleFactor(self, scaleFactor): if scaleFactor is None: scaleFactor = 1. if not hasattr(scaleFactor, "__len__"): scaleFactor = [scaleFactor] self._scaleFactor = scaleFactor def scaleDer(self, derIdx:Np1D): if not isinstance(self.scaleFactor, Number): RROMPyAssert(len(derIdx), len(self.scaleFactor), "Number of derivative indices") with catch_warnings(record = True) as w: res = np.prod(np.power(self.scaleFactor, derIdx)) if len(w) == 0: return res raise RROMPyException(("Error in computing derivative scaling " "factor: {}".format(str(w[-1].message)))) @property def feature_keys(self) -> TupleAny: return ["mus", "samples", "nsamples", "_derIdxs"] @property def feature_vals(self) -> DictAny: return {"mus":self.mus, "samples":self.samples, - "nsamples":self.nsamples, "_derIdxs":self._derIdxs} + "nsamples":self.nsamples, "_derIdxs":self._derIdxs, + "_scaleFactor":self.scaleFactor} def _mergeFeature(self, name:str, value:Any): if name in ["mus", "samples"]: getattr(self, name).append(value) elif name == "nsamples": self.nsamples += value elif name == "_derIdxs": self._derIdxs += value else: raise RROMPyException(("Invalid key {} in sampling engine " - "merge.".format(key))) + "merge.".format(name))) def store(self, filenameBase : str = "sampling_engine", forceNewFile : bool = True, local : bool = False) -> str: """Store sampling engine to file.""" filename = None if masterCore(): vbMng(self, "INIT", "Storing sampling engine to file.", 20) if forceNewFile: filename = getNewFilename(filenameBase, "pkl") else: filename = "{}.pkl".format(filenameBase) pickleDump(self.feature_vals, filename) vbMng(self, "DEL", "Done storing engine.", 20) if local: return filename = bcast(filename) return filename def load(self, filename:str, merge : bool = False): """Load sampling engine from file.""" if isinstance(filename, (list, tuple,)): self.load(filename[0], merge) for filen in filename[1 :]: self.load(filen, True) return vbMng(self, "INIT", "Loading stored sampling engine from file.", 20) datadict = pickleLoad(filename) for key in datadict: if key in self.feature_keys: - if merge: + if merge and key != "_scaleFactor": self._mergeFeature(key, datadict[key]) else: setattr(self, key, datadict[key]) self._mode = RROMPy_FRAGILE vbMng(self, "DEL", "Done loading stored engine.", 20) @property def projectionMatrix(self) -> Np2D: return self.samples.data def resetHistory(self): self._mode = RROMPy_READY 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._mode = RROMPy_READY 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 postprocessu(self, u:sampList, overwrite : bool = False): self.setsample(u, overwrite) def postprocessuBulk(self): pass 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] + mu = checkParameterList(mu, self.HFEngine.npar) vbMng(self, "INIT", "Solving HF model for mu = {}.".format(mu), 15) u = self.HFEngine.solve(mu, RHS, return_state = self.sample_state) vbMng(self, "DEL", "Done solving HF model.", 15) return u def _getSampleConcurrence(self, mu:paramVal, previous:Np1D) -> sampList: RROMPyAssert(self._mode, message = "Cannot add samples.") if not (self.sample_state or self.HFEngine.isCEye): raise RROMPyException(("Derivatives of solution with non-scalar " "C not computable.")) + from rrompy.utilities.numerical import dot if len(previous) >= len(self._derIdxs): self._derIdxs += nextDerivativeIndices(self._derIdxs, len(self.scaleFactor), len(previous) + 1 - len(self._derIdxs)) derIdx = self._derIdxs[len(previous)] mu = checkParameter(mu, self.HFEngine.npar) samplesOld = self.samples(previous) RHS = self.scaleDer(derIdx) * 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 -= self.scaleDer(diffP) * dot(self.HFEngine.A(mu, diffP), samplesOld[j]) return self.solveLS(mu, RHS = RHS) def nextSample(self, mu:paramVal, overwrite : bool = False, postprocess : bool = True) -> Np1D: RROMPyAssert(self._mode, message = "Cannot add samples.") 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] + mus = checkParameterList(mus, self.HFEngine.npar) 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 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/engines/sampling_engine_standard_pod.py b/rrompy/sampling/engines/sampling_engine_standard_pod.py index f398e6e..8c21326 100644 --- a/rrompy/sampling/engines/sampling_engine_standard_pod.py +++ b/rrompy/sampling/engines/sampling_engine_standard_pod.py @@ -1,104 +1,103 @@ # 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 scipy.sparse import block_diag from .pod_engine import PODEngine from .sampling_engine_standard import SamplingEngineStandard from rrompy.utilities.base.types import (Np1D, Np2D, TupleAny, DictAny, Any, paramVal, sampList) from rrompy.utilities.base import verbosityManager as vbMng from rrompy.sampling import sampleList, emptySampleList __all__ = ['SamplingEngineStandardPOD'] class SamplingEngineStandardPOD(SamplingEngineStandard): @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) @property def feature_keys(self) -> TupleAny: return super().feature_keys + ["samples_ortho", "RPOD"] @property def feature_vals(self) -> DictAny: vals = super().feature_vals vals["samples_ortho"] = self.samples_ortho vals["RPOD"] = self.RPOD return vals def _mergeFeature(self, name:str, value:Any): if name == "samples_ortho": self.samples_ortho.append(value) elif name == "RPOD": self.RPOD = block_diag((self.RPOD, value), "csc") else: super()._mergeFeature(name, value) @property def projectionMatrix(self) -> Np2D: return self.samples_ortho.data def resetHistory(self): super().resetHistory() self.samples_ortho = emptySampleList() self.RPOD = np.zeros((0, 0), dtype = np.complex) def setsample_ortho(self, u:sampList, overwrite : bool = False): if overwrite: self.samples_ortho[self.nsamples] = u else: if self.nsamples == 0: self.samples_ortho = sampleList(u) else: self.samples_ortho.append(u) def popSample(self): if hasattr(self, "nsamples") and self.nsamples > 1: self.RPOD = self.RPOD[: -1, : -1] self.samples_ortho.pop() super().popSample() def preallocateSamples(self, u:Np1D, mu:paramVal, n:int): super().preallocateSamples(u, mu, n) self.samples_ortho.reset((u.shape[0], n), u.dtype) def postprocessu(self, u:sampList, overwrite : bool = False): self.setsample(u, overwrite) vbMng(self, "INIT", "Starting orthogonalization.", 20) u, r, _ = self.PODEngine.GS(u, self.samples_ortho, 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) self.setsample_ortho(u, overwrite) def postprocessuBulk(self): vbMng(self, "INIT", "Starting orthogonalization.", 10) samples_ortho, self.RPOD = self.PODEngine.generalizedQR(self.samples, is_state = self.sample_state) vbMng(self, "DEL", "Done orthogonalizing.", 10) nsamples, self.nsamples = self.nsamples, 0 self.setsample_ortho(samples_ortho) self.nsamples = nsamples diff --git a/rrompy/sampling/sample_list.py b/rrompy/sampling/sample_list.py index 3366fea..ffd35fd 100644 --- a/rrompy/sampling/sample_list.py +++ b/rrompy/sampling/sample_list.py @@ -1,224 +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: 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,)): + if isinstance(key, (tuple, list, np.ndarray)): 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): + from rrompy.utilities.numerical import dot 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/solver/fenics/__init__.py b/rrompy/solver/fenics/__init__.py index 5043f0b..417fc2b 100644 --- a/rrompy/solver/fenics/__init__.py +++ b/rrompy/solver/fenics/__init__.py @@ -1,51 +1,57 @@ # 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 .fenics_constants import (fenZERO, fenZEROS, fenONE, fenONES, bdrTrue, bdrFalse) from .fenics_la import fenics2Sparse, fenics2Vector from .fenics_serial import serializeMesh, serializeFunctionSpace from .fenics_norms import (L2NormMatrix, L2InverseNormMatrix, H1NormMatrix, - Hminus1NormMatrix, elasticNormMatrix, - elasticDualNormMatrix) + Hminus1NormMatrix, augmentedH1NormMatrix, + augmentedHminus1NormMatrix, elasticNormMatrix, + elasticDualNormMatrix, augmentedElasticNormMatrix, + augmentedElasticDualNormMatrix) from .fenics_plotting import fenplot, affine_warping from .fenics_projecting import interp_project __all__ = [ 'fenZERO', 'fenZEROS', 'fenONE', 'fenONES', 'bdrTrue', 'bdrFalse', 'fenics2Sparse', 'fenics2Vector', 'serializeMesh', 'serializeFunctionSpace', 'L2NormMatrix', 'L2InverseNormMatrix', 'H1NormMatrix', 'Hminus1NormMatrix', + 'augmentedH1NormMatrix', + 'augmentedHminus1NormMatrix', 'elasticNormMatrix', 'elasticDualNormMatrix', + 'augmentedElasticNormMatrix', + 'augmentedElasticDualNormMatrix', 'fenplot', 'affine_warping', 'interp_project' ] diff --git a/rrompy/solver/fenics/fenics_norms.py b/rrompy/solver/fenics/fenics_norms.py index 56b4939..b8364e8 100644 --- a/rrompy/solver/fenics/fenics_norms.py +++ b/rrompy/solver/fenics/fenics_norms.py @@ -1,82 +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 . # +from scipy.sparse import block_diag import fenics as fen from rrompy.utilities.base.types import Np2D, FenFunc, DictAny, FenFuncSpace from rrompy.solver.norm_utilities import (Np2DLikeInv, Np2DLikeInvLowRank) from .fenics_la import fenics2Sparse __all__ = ['L2NormMatrix', 'L2InverseNormMatrix', 'H1NormMatrix', - 'Hminus1NormMatrix', 'elasticNormMatrix', 'elasticDualNormMatrix'] + 'Hminus1NormMatrix', 'augmentedH1NormMatrix', + 'augmentedHminus1NormMatrix', 'elasticNormMatrix', + 'elasticDualNormMatrix', 'augmentedElasticNormMatrix', + 'augmentedElasticDualNormMatrix'] def L2NormMatrix(V:FenFuncSpace, r_ : FenFunc = 1.) -> Np2D: u = fen.TrialFunction(V) v = fen.TestFunction(V) return fenics2Sparse(r_ * fen.dot(u, v) * fen.dx) def L2InverseNormMatrix(V:FenFuncSpace, r_ : FenFunc = 1., solverType : str = "SPSOLVE", solverArgs : DictAny = {}, compressRank : int = None, compressOversampling : int = 10, compressSeed : int = 420) -> Np2D: L2Mat = L2NormMatrix(V, r_) if compressRank is None: return Np2DLikeInv(L2Mat, 1., solverType, solverArgs) return Np2DLikeInvLowRank(L2Mat, 1., solverType, solverArgs, compressRank, compressOversampling, compressSeed) def H1NormMatrix(V:FenFuncSpace, w : float = 0., r_ : FenFunc = 1., a_ : FenFunc = 1.) -> Np2D: u = fen.TrialFunction(V) v = fen.TestFunction(V) return fenics2Sparse((w * r_ * fen.dot(u, v) + fen.dot(a_ * fen.grad(u), fen.grad(v))) * fen.dx) def Hminus1NormMatrix(V:FenFuncSpace, w : float = 0., r_ : FenFunc = 1., a_ : FenFunc = 1., solverType : str = "SPSOLVE", solverArgs : DictAny = {}, compressRank : int = None, compressOversampling : int = 10, compressSeed : int = 420) -> Np2D: H1Mat = H1NormMatrix(V, w, r_, a_) if compressRank is None: return Np2DLikeInv(H1Mat, 1., solverType, solverArgs) return Np2DLikeInvLowRank(H1Mat, 1., solverType, solverArgs, compressRank, compressOversampling, compressSeed) +def augmentedH1NormMatrix(V:FenFuncSpace, r_ : FenFunc = 1., + a_ : FenFunc = 1.) -> Np2D: + return block_diag((H1NormMatrix(V, a_ = a_), L2NormMatrix(V, r_)), + format = "csr") + +def augmentedHminus1NormMatrix(V:FenFuncSpace, r_ : FenFunc = 1., + a_ : FenFunc = 1., solverType : str = "SPSOLVE", + solverArgs : DictAny = {}, compressRank : int = None, + compressOversampling : int = 10, + compressSeed : int = 420) -> Np2D: + H1Mat = augmentedH1NormMatrix(V, r_, a_) + if compressRank is None: + return Np2DLikeInv(H1Mat, 1., solverType, solverArgs) + return Np2DLikeInvLowRank(H1Mat, 1., solverType, solverArgs, compressRank, + compressOversampling, compressSeed) + def elasticNormMatrix(V:FenFuncSpace, l_:FenFunc, m_:FenFunc, w : float = 0., r_ : FenFunc = 1.) -> Np2D: u = fen.TrialFunction(V) v = fen.TestFunction(V) epsilon = lambda f: 0.5 * (fen.grad(f) + fen.nabla_grad(f)) sigma = (l_ * fen.div(u) * fen.Identity(u.geometric_dimension()) + 2. * m_ * epsilon(u)) return fenics2Sparse((w * r_ * fen.dot(u, v) + fen.inner(sigma, epsilon(v))) * fen.dx) def elasticDualNormMatrix(V:FenFuncSpace, l_:FenFunc, m_:FenFunc, - w : float = 0., solverType : str = "SPSOLVE", - solverArgs : DictAny = {}, r_ : FenFunc = 1., - compressRank : int = None, - compressOversampling : int = 10, - compressSeed : int = 420) -> Np2D: + w : float = 0., r_ : FenFunc = 1., + solverType : str = "SPSOLVE", solverArgs : DictAny = {}, + compressRank : int = None, compressOversampling : int = 10, + compressSeed : int = 420) -> Np2D: elMat = elasticNormMatrix(V, l_, m_, w, r_) if compressRank is None: return Np2DLikeInv(elMat, 1., solverType, solverArgs) return Np2DLikeInvLowRank(elMat, 1., solverType, solverArgs, compressRank, compressOversampling, compressSeed) +def augmentedElasticNormMatrix(V:FenFuncSpace, l_:FenFunc, m_:FenFunc, + r_ : FenFunc = 1.) -> Np2D: + return block_diag((elasticNormMatrix(V, l_, m_), L2NormMatrix(V, r_)), + format = "csr") + +def augmentedElasticDualNormMatrix(V:FenFuncSpace, l_:FenFunc, m_:FenFunc, + r_ : FenFunc = 1., solverType : str = "SPSOLVE", + solverArgs : DictAny = {}, compressRank : int = None, + compressOversampling : int = 10, + compressSeed : int = 420) -> Np2D: + elMat = augmentedElasticNormMatrix(V, l_, m_, r_) + if compressRank is None: + return Np2DLikeInv(elMat, 1., solverType, solverArgs) + return Np2DLikeInvLowRank(elMat, 1., solverType, solverArgs, compressRank, + compressOversampling, compressSeed) diff --git a/rrompy/solver/norm_utilities.py b/rrompy/solver/norm_utilities.py index 358d53c..eccef9d 100644 --- a/rrompy/solver/norm_utilities.py +++ b/rrompy/solver/norm_utilities.py @@ -1,91 +1,93 @@ # 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.sampling.sample_list import sampleList +from rrompy.parameter.parameter_list import parameterList from .linear_solver import setupSolver from rrompy.utilities.exception_manager import RROMPyException __all__ = ['Np2DLike', 'Np2DLikeInv', 'Np2DLikeInvLowRank', 'normEngine'] 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 isinstance(u, (parameterList, sampleList)): u = u.data + if isinstance(v, (parameterList, sampleList)): 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/decorators.py b/rrompy/utilities/base/decorators.py index 6199200..89bea4d 100644 --- a/rrompy/utilities/base/decorators.py +++ b/rrompy/utilities/base/decorators.py @@ -1,27 +1,86 @@ # 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 . # -__all__ = ['affine_construct', 'nonaffine_construct'] +from numpy import inf, random +from functools import wraps + +__all__ = ['affine_construct', 'nonaffine_construct', 'threshold', + 'addWhiteNoise'] def affine_construct(method): method.is_affine = True return method def nonaffine_construct(method): method.is_affine = False return method + +def threshold(thresh = inf): + def method_with_thresh(method): + method.threshold = thresh + return method + return method_with_thresh + +def addWhiteNoise(amplitude : float = 1., seed : int = None, + to_solve : bool = True): + def add_noise(engine): + try: + is_engine = "solve" in dir(engine) + except: + is_engine = False + if is_engine: # apply from outside class + if to_solve: + class engine_with_noise(engine): + def solve(self, *args, **kwargs): + if not hasattr(self, "rng"): + self.rng = random.default_rng(seed) + self.__class__.__name__ = (engine.__name__ + + "+WhiteNoise") + sol = super().solve(*args, **kwargs) + noise = self.rng.standard_normal(sol.shape) + if sol.dtype == complex: + noise = noise + 1.j * self.rng.standard_normal( + noise.shape) + return sol + amplitude * noise + else: + class engine_with_noise(engine): + def b(self, *args, **kwargs): + if not hasattr(self, "rng"): + self.rng = random.default_rng(seed) + self.__class__.__name__ = (engine.__name__ + + "+WhiteNoise") + bb = super().b(*args, **kwargs) + noise = self.rng.standard_normal(bb.shape) + if bb.dtype == complex: + noise = noise + 1.j * self.rng.standard_normal( + noise.shape) + return bb + amplitude * noise + return engine_with_noise + # apply from within class + @wraps(engine) + def solve_with_noise(self, *args, **kwargs): + if not hasattr(self, "rng"): + self.rng = random.default_rng(seed) + self.__class__.__name__ += "+WhiteNoise" + sol = engine(self, *args, **kwargs) + noise = self.rng.standard_normal(sol.shape) + if sol.dtype == complex: + noise = noise + 1.j * self.rng.standard_normal(noise.shape) + return sol + amplitude * noise + return solve_with_noise + return add_noise diff --git a/rrompy/utilities/base/verbosity_depth.py b/rrompy/utilities/base/verbosity_depth.py index a3c674e..db51c7d 100644 --- a/rrompy/utilities/base/verbosity_depth.py +++ b/rrompy/utilities/base/verbosity_depth.py @@ -1,87 +1,97 @@ # 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 datetime import datetime from rrompy.utilities.exception_manager import RROMPyException __all__ = ["verbosityDepth", "verbosityManager"] -from datetime import datetime - def getTimestamp() -> str: time = datetime.now().strftime("%H:%M:%S.%f") return "\x1b[42m{}\x1b[0m".format(time) def updateVerbosityCheckpoint(vctype:int) -> str: global RROMPy_verbosity_checkpoint, RROMPy_verbosity_buffer if "RROMPy_verbosity_checkpoint" not in globals(): RROMPy_verbosity_checkpoint = 0 RROMPy_verbosity_checkpoint += vctype if "RROMPy_verbosity_buffer" not in globals(): RROMPy_verbosity_buffer = "" if RROMPy_verbosity_checkpoint <= 0: buffer = copy(RROMPy_verbosity_buffer) del RROMPy_verbosity_buffer return buffer return None +def getVerbosityDepth() -> int: + global RROMPy_verbosity_depth + if "RROMPy_verbosity_depth" not in globals(): return 0 + return RROMPy_verbosity_depth + +def setVerbosityDepth(depth): + global RROMPy_verbosity_depth + if depth <= 0: + if "RROMPy_verbosity_depth" in globals(): + del RROMPy_verbosity_depth + else: + RROMPy_verbosity_depth = depth + def verbosityDepth(vdtype:str, message:str, end : str = "\n", timestamp : bool = True): global RROMPy_verbosity_depth, RROMPy_verbosity_checkpoint, \ RROMPy_verbosity_buffer assert isinstance(vdtype, str) vdtype = vdtype.upper() if vdtype not in ["INIT", "MAIN", "DEL"]: raise RROMPyException("Verbosity depth type not recognized.") if "RROMPy_verbosity_checkpoint" not in globals(): RROMPy_verbosity_checkpoint = 0 - out = "{} ".format(getTimestamp()) if timestamp else "" if vdtype == "INIT": if "RROMPy_verbosity_depth" not in globals(): - RROMPy_verbosity_depth = 0 - RROMPy_verbosity_depth += 1 - out += "│" * (RROMPy_verbosity_depth - 1) + setVerbosityDepth(1) + else: + setVerbosityDepth(RROMPy_verbosity_depth + 1) + assert "RROMPy_verbosity_depth" in globals() + out = "{} ".format(getTimestamp()) if timestamp else "" + out += "│" * (RROMPy_verbosity_depth - 1) + if vdtype == "INIT": out += "┌" - else: - assert "RROMPy_verbosity_depth" in globals() - if vdtype == "MAIN": - out += "│" * (RROMPy_verbosity_depth - 1) - out += "├" - elif vdtype == "DEL": - RROMPy_verbosity_depth -= 1 - out += "│" * RROMPy_verbosity_depth - out += "└" - if RROMPy_verbosity_depth <= 0: del RROMPy_verbosity_depth + elif vdtype == "MAIN": + out += "├" + else: #if vdtype == "DEL": + setVerbosityDepth(RROMPy_verbosity_depth - 1) + out += "└" from rrompy.utilities.parallel import poolRank, poolSize, masterCore if message != "" and masterCore(): if RROMPy_verbosity_checkpoint and poolSize() > 1: poolrk = "{{\x1b[34m{}\x1b[0m}}".format(poolRank()) else: poolrk = "" msg = "{}{}{}{}".format(out, poolrk, message, end) if RROMPy_verbosity_checkpoint: assert "RROMPy_verbosity_buffer" in globals() RROMPy_verbosity_buffer += msg else: print(msg, end = "", flush = True) return def verbosityManager(object, vdtype:str, message:str, vlvl : int = 0, end : str = "\n"): if object.verbosity >= vlvl: return verbosityDepth(vdtype, message, end, object.timestamp) diff --git a/rrompy/utilities/exception_manager/exception_manager.py b/rrompy/utilities/exception_manager/exception_manager.py index 4fffb61..61a00c5 100644 --- a/rrompy/utilities/exception_manager/exception_manager.py +++ b/rrompy/utilities/exception_manager/exception_manager.py @@ -1,32 +1,26 @@ # 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 . # __all__ = ["RROMPyException"] -def purgeVerbosityDepth(): - from rrompy.utilities.base.verbosity_depth import verbosityDepth - while True: - try: - verbosityDepth("DEL", "", "", False) - except: - break - class RROMPyException(Exception): def __init__(self, message, purge : bool = True): - if purge: purgeVerbosityDepth() + if purge: + from rrompy.utilities.base.verbosity_depth import setVerbosityDepth + setVerbosityDepth(0) super().__init__(message) diff --git a/rrompy/utilities/exception_manager/warning_manager.py b/rrompy/utilities/exception_manager/warning_manager.py index 4504b2e..8be18f3 100644 --- a/rrompy/utilities/exception_manager/warning_manager.py +++ b/rrompy/utilities/exception_manager/warning_manager.py @@ -1,36 +1,32 @@ # 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 traceback as tb -from datetime import datetime __all__ = ['RROMPyWarning'] def RROMPyWarning(msg : str = "", stacklevel : int = 0): - from rrompy.utilities.base.verbosity_depth import getTimestamp + from rrompy.utilities.base.verbosity_depth import verbosityDepth frameSummary = tb.extract_stack()[- 3 - stacklevel] - timestamp = getTimestamp() if frameSummary.name == "": name = "" else: name = ", within {}".format(frameSummary.name) - print("{} \x1b[3mWarning at {}:{}{}:\x1b[0m".format(timestamp, - frameSummary.filename, - frameSummary.lineno, - name)) - print("> \x1b[31m{}\x1b[0m".format(frameSummary.line)) - if len(msg) > 0: - print(" \x1b[3m{}\x1b[0m\n".format(msg)) + verbosityDepth("INIT", " \x1b[3mWarning at {}:{}{}:\x1b[0m".format( + frameSummary.filename, frameSummary.lineno, name)) + verbosityDepth("DEL" * (len(msg) == 0) + "MAIN" * (len(msg) > 0), + " > \x1b[31m{}\x1b[0m".format(frameSummary.line)) + if len(msg) > 0: verbosityDepth("DEL", " \x1b[3m{}\x1b[0m".format(msg)) diff --git a/rrompy/utilities/expression/expression_evaluator.py b/rrompy/utilities/expression/expression_evaluator.py index d95d85e..69128fc 100644 --- a/rrompy/utilities/expression/expression_evaluator.py +++ b/rrompy/utilities/expression/expression_evaluator.py @@ -1,123 +1,125 @@ # 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 numbers import Number import numpy as np from copy import deepcopy as copy from .keys import (expressionKeysUnary, expressionKeysUnaryParam, expressionKeysBinary, expressionKeysBinaryParam) from rrompy.utilities.base.types import Tuple, TupleAny, paramList from rrompy.utilities.exception_manager import RROMPyException -from rrompy.parameter import parameterList, checkParameterList +from rrompy.sampling.sample_list import sampleList +from rrompy.parameter.parameter_list import parameterList, checkParameterList __all__ = ["expressionEvaluator"] def manageNotExpression(expr): if isinstance(expr, (str,)) and expr == "x": return None - elif isinstance(expr, (Number, parameterList,)): return expr + elif isinstance(expr, (Number,)): return expr + elif isinstance(expr, (parameterList, sampleList)): return expr.data else: try: return np.array(expr) except: raise RROMPyException(("Expression '{}' not " "recognized.").format(expr)) def expressionEvaluator(expr:TupleAny, x:paramList, force_shape : Tuple[int] = None): - if not isinstance(x, (parameterList,)): x = checkParameterList(x)[0] + if not isinstance(x, (parameterList,)): x = checkParameterList(x) exprSimp = [None] * len(expr) for j, ex in enumerate(expr): if isinstance(ex, (tuple,)): exprSimp[j] = expressionEvaluator(ex, x) else: exprSimp[j] = ex z, zc = None, None pile, pilePars = [], [] j = -1 while j + 1 < len(exprSimp): j += 1 ex = exprSimp[j] if not isinstance(ex, (np.ndarray, parameterList, list, tuple,)): if ex in expressionKeysUnary.keys(): pile = pile + [ex] pilePars = pilePars + [None] continue if ex in expressionKeysUnaryParam.keys(): pile = pile + [ex] j += 1 if j >= len(exprSimp) or not isinstance(exprSimp[j], (dict,)): raise RROMPyException(("Parameters missing for unary " "operand '{}'.").format(ex)) pilePars = pilePars + [exprSimp[j]] continue if ex in expressionKeysBinary.keys(): if len(pile) > 0 or z is None or zc is not None: raise RROMPyException(("Binary operand '{}' must follow " "numerical expression.").format(ex)) zc = copy(z) pile = pile + [ex] pilePars = pilePars + [None] continue if ex in expressionKeysBinaryParam.keys(): if len(pile) > 0 or z is None or zc is not None: raise RROMPyException(("Binary operand '{}' must follow " "numerical expression.").format(ex)) zc = copy(z) pile = pile + [ex] j += 1 if j >= len(exprSimp) or not isinstance(exprSimp[j], (dict,)): raise RROMPyException(("Parameters missing for binary " "operand '{}'.").format(ex)) pilePars = pilePars + [exprSimp[j]] continue z = manageNotExpression(ex) - if z is None: z = copy(x) + if z is None: z = checkParameterList(x, return_data = True) if len(pile) > 0: for pl, plp in zip(pile[::-1], pilePars[::-1]): if zc is None: if plp is None: z = expressionKeysUnary[pl](z) else: z = expressionKeysUnaryParam[pl](z, plp) else: if plp is None: z = expressionKeysBinary[pl](zc, z) else: z = expressionKeysBinaryParam[pl](zc, z, plp) zc, pile, pilePars = None, [], [] if len(pile) > 0: raise RROMPyException(("Missing numerical expression feeding into " "'{}'.").format(pile[-1])) if force_shape is not None: if hasattr(z, "__len__") and len(z) > 1: - if isinstance(z, (parameterList,)): z = z.data + if isinstance(z, (parameterList, sampleList)): z = z.data if isinstance(z, (list, tuple,)): z = np.array(z) if z.size == np.prod(force_shape): z = np.reshape(z, force_shape) else: zdim = len(z.shape) if z.shape != force_shape[: zdim]: raise RROMPyException(("Error in reshaping result: shapes " "{} and {} not compatible.").format( z.shape, force_shape)) else: z = np.tile(z, [1] * zdim + force_shape[zdim :]) else: if hasattr(z, "__len__"): z = z[0] z = z * np.ones(force_shape) return z diff --git a/rrompy/utilities/expression/keys.py b/rrompy/utilities/expression/keys.py index 7b1bd54..e4442e5 100644 --- a/rrompy/utilities/expression/keys.py +++ b/rrompy/utilities/expression/keys.py @@ -1,57 +1,60 @@ # 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 expressionKeysUnary = {"sin" : lambda x: np.sin(x), "cos" : lambda x: np.cos(x), "tan" : lambda x: np.tan(x), "arcsin" : lambda x: np.arcsin(x), "arccos" : lambda x: np.arccos(x), "arctan" : lambda x: np.arctan(x), "floor" : lambda x: np.floor(x), "ceil" : lambda x: np.ceil(x), "round" : lambda x: np.round(x), "exp" : lambda x: np.exp(x), "expm1" : lambda x: np.expm1(x), + "exp2" : lambda x: np.exp2(x), "log" : lambda x: np.log(x), + "log2" : lambda x: np.log2(x), + "log10" : lambda x: np.log10(x), + "log1p" : lambda x: np.log1p(x), "reciprocal" : lambda x: np.reciprocal(x), "negative" : lambda x: np.negative(x), "abs" : lambda x: np.abs(x), "angle" : lambda x: np.angle(x), "real" : lambda x: np.real(x), "imag" : lambda x: np.imag(x), - "conj" : lambda x: np.negative(x), - "data" : lambda x: x.data} + "conj" : lambda x: np.negative(x)} expressionKeysBinary = {"+" : lambda x, y: x + y, "-" : lambda x, y: x - y, "*" : lambda x, y: x * y, "/" : lambda x, y: x / y, "//" : lambda x, y: x // y, "**" : lambda x, y: x ** y, "[]" : lambda x, y: x[y], - "()" : lambda x, y: x(y)} + "()" : lambda x, y: x[:, y]} expressionKeysUnaryParam = { "sum" : lambda x, pars: np.sum(x, **pars), "mean" : lambda x, pars: np.mean(x, **pars), "prod" : lambda x, pars: np.prod(x, **pars)} expressionKeysBinaryParam = {} diff --git a/rrompy/utilities/expression/monomial_creator.py b/rrompy/utilities/expression/monomial_creator.py index a90335c..8803efb 100644 --- a/rrompy/utilities/expression/monomial_creator.py +++ b/rrompy/utilities/expression/monomial_creator.py @@ -1,58 +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.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)) + exprLoc = ("prod", {"axis" : 1}, ("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/halton.py b/rrompy/utilities/numerical/halton.py index 509de0c..ef5449c 100644 --- a/rrompy/utilities/numerical/halton.py +++ b/rrompy/utilities/numerical/halton.py @@ -1,45 +1,51 @@ # 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 __all__ = ['haltonGenerate'] -def haltonGenerate(dim, size, seed = 0, step = True): - seq = np.empty((size, dim), dtype = float) - primes = [2, 3] - for j in range(2, dim * (1 + 1 * step) + 1): - primeBase = primes[j - 1] + 2 +haltonPrimes = [2, 3] + +def haltonAddPrimes(n): + global haltonPrimes + for j in range(len(haltonPrimes), n): + primeBase = haltonPrimes[- 1] + 2 while True: for i in range(1, j): - if primeBase % primes[i] == 0: break + if primeBase % haltonPrimes[i] == 0: break else: break primeBase += 2 - primes += [primeBase] - base = np.array(primes[: dim]) - stepSize = primes[dim * 2 - 1] if step else 1 + haltonPrimes += [primeBase] + return haltonPrimes + +def haltonGenerate(dim, size, seed = 0, step = True, return_seed = False): + haltonPrimes = haltonAddPrimes(dim * (1 + 1 * step) + 1) + base = np.array(haltonPrimes[: dim]) + stepSize = haltonPrimes[dim * 2 - 1] if step else 1 seq = np.zeros((size, dim), dtype = float) seqBase = np.tile((np.arange(seed, stepSize * size + seed, stepSize) + 1).reshape(-1, 1), [1, dim]) powerB = 1 while seqBase[-1, 0] > 0: seqBase, remainder = np.divmod(seqBase, base) seq += remainder / np.power(base, powerB) powerB += 1 + if return_seed: return seq, stepSize * size + seed return seq diff --git a/rrompy/utilities/numerical/marginalize_poly_list.py b/rrompy/utilities/numerical/marginalize_poly_list.py index d36262c..48fc089 100644 --- a/rrompy/utilities/numerical/marginalize_poly_list.py +++ b/rrompy/utilities/numerical/marginalize_poly_list.py @@ -1,79 +1,79 @@ # 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.sparse import csr from rrompy.utilities.base.types import Np1D, Np2D, ListAny from rrompy.utilities.base import freepar as fp from .hash_derivative import (hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) from rrompy.parameter import checkParameter __all__ = ['marginalizePolyList'] def marginalizePolyList(objs:ListAny, marginalVals : Np1D = [fp], zeroObj : Np2D = 0., recompress : bool = True) -> ListAny: res = [] freeLocations = [] fixedLocations = [] muFixed = [] if not hasattr(marginalVals, "__len__"): marginalVals = [marginalVals] for i, m in enumerate(marginalVals): if m == fp: freeLocations += [i] else: fixedLocations += [i] muFixed += [m] - muFixed = checkParameter(muFixed, len(fixedLocations)) + muFixed = checkParameter(muFixed, len(fixedLocations), return_data = True) if zeroObj == "auto": if isinstance(objs[0], np.ndarray): zeroObj = np.zeros_like(objs[0]) elif isinstance(objs[0], csr.csr_matrix): d = objs[0].shape[0] zeroObj = csr.csr_matrix(([], [], np.zeros(d + 1)), shape = objs[0].shape, dtype = objs[0].dtype) else: zeroObj = 0. for j, obj in enumerate(objs): derjBase = hashI(j, len(marginalVals)) jNew = hashD([derjBase[i] for i in freeLocations]) derjFixed = [derjBase[i] for i in fixedLocations] - obj = np.prod((muFixed ** derjFixed).data) * obj + obj = np.prod(muFixed ** derjFixed) * obj if jNew >= len(res): for _ in range(len(res), jNew): res += [zeroObj] res += [obj] else: res[jNew] = res[jNew] + obj if recompress: for re in res[::-1]: try: if isinstance(re, np.ndarray): iszero = np.allclose(re, zeroObj, atol = 2 * np.finfo(re.dtype).eps) elif isinstance(re, csr.csr_matrix): iszero = re.nnz == 0 else: break if not iszero: break except: break res.pop() return res diff --git a/rrompy/utilities/numerical/point_matching.py b/rrompy/utilities/numerical/point_matching.py index 9f1e7e8..2562ceb 100644 --- a/rrompy/utilities/numerical/point_matching.py +++ b/rrompy/utilities/numerical/point_matching.py @@ -1,143 +1,150 @@ # 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 warnings import numpy as np from scipy.optimize import linear_sum_assignment as LSA from rrompy.utilities.base.types import Tuple, List, ListAny, Np1D, Np2D, HFEng from rrompy.utilities.exception_manager import (RROMPyException, RROMPyWarning, RROMPyAssert) __all__ = ['pointMatching', 'rationalFunctionMatching', 'potential', 'angleTable', 'chordalMetricTable', 'chordalMetricAdjusted'] def pointMatching(distanceMatrix:Np2D) -> Tuple[Np1D, Np1D]: return LSA(distanceMatrix) def rationalFunctionMatching(poles:List[Np1D], coeffs:List[Np2D], featPts:Np2D, matchingWeight:float, matchingMode:str, supps:ListAny, projMat:Np2D, HFEngine : HFEng = None, is_state : bool = True) \ -> Tuple[List[Np1D], List[Np2D]]: M, N = len(featPts), len(poles[0]) RROMPyAssert(len(poles), M, "Number of rational functions to be matched") RROMPyAssert(len(coeffs), M, "Number of rational functions to be matched") if M <= 1: return poles, coeffs matchingMode = matchingMode.upper().strip().replace(" ", "") if matchingMode != "NONE": if matchingMode[: 5] != "SHIFT": raise RROMPyException("Prescribed matching mode not recognized.") if "-" in matchingMode: shiftdeg = int(matchingMode.split("-")[-1]) else: shiftdeg = 1 if matchingMode == "SHIFT": avg = [np.mean(pls[np.logical_not(np.isinf(pls))]) for pls in poles] with warnings.catch_warnings(): from rrompy.utilities.poly_fitting.polynomial import ( PolynomialInterpolator as PI) for deg in range(shiftdeg, 0, -1): try: shift = PI() shift.setupByInterpolation(featPts, np.array(avg), deg, verbose = False) break except: pass else: shift = lambda _: np.mean(avg) else: #if matchingMode == "NONE": shift = lambda _: 0. featDist = np.sum(np.abs(np.repeat(featPts, M, 0) - np.tile(featPts, [M, 1])), axis = 1) free = list(range(M)) fixed = [free.pop(np.argpartition(featDist, M)[M] % M)] featDist = featDist.reshape(M, M) for j in range(M - 1, 0, -1): idx = np.argmin(featDist[np.ix_(fixed, free)].flatten()) Ifix = fixed[idx // j] fixed += [free.pop(idx % j)] Ifree = fixed[-1] plsfix = poles[Ifix] plsfree = (poles[Ifree] + shift([featPts[Ifix]]) - shift([featPts[Ifree]])) resfix, resfree = None, None if matchingWeight != 0: resfix, resfree = coeffs[Ifix][: N].T, coeffs[Ifree][: N].T if isinstance(projMat, (np.ndarray,)): suppfix, suppfree = supps[Ifix], supps[Ifree] resfix = projMat[:, suppfix : suppfix + len(resfix)].dot( resfix) resfree = projMat[:, suppfree : suppfree + len(resfree)].dot( resfree) distj = chordalMetricAdjusted(plsfix, plsfree, matchingWeight, resfix, resfree, HFEngine, is_state) reordering = pointMatching(distj)[1] poles[Ifree] = poles[Ifree][reordering] coeffs[Ifree][: N] = coeffs[Ifree][reordering] return poles, coeffs def potential(x:Np1D, foci : Tuple[float, float] = [- 1., 1.]) -> Np1D: mu0 = np.mean(foci) musig = foci[0] - mu0 isInf = np.isinf(x) dist = np.empty(len(x)) dist[isInf] = np.inf xEffR = x[np.logical_not(isInf)] - mu0 if np.isclose(musig, 0.): if foci[0] != foci[1]: RROMPyWarning("Collapsing different but numerically equal foci.") dist[np.logical_not(isInf)] = np.abs(xEffR) else: xEffR /= musig bernEff = (xEffR ** 2. - 1) ** .5 dist[np.logical_not(isInf)] = np.max(np.vstack(( np.abs(xEffR + bernEff), np.abs(xEffR - bernEff) )), axis = 0) return dist def angleTable(X:Np2D, Y:Np2D, HFEngine : HFEng = None, - is_state : bool = True) -> Np2D: + is_state : bool = True, radius : float = None) -> Np2D: if HFEngine is None: - innerT = Y.dot(X.T.conj()) - normX = np.linalg.norm(X, axis = 1) - normY = np.linalg.norm(Y, axis = 1) + innerT = np.real(Y.T.conj().dot(X)) + norm2X = np.sum(np.abs(X) ** 2., axis = 0) + norm2Y = np.sum(np.abs(Y) ** 2., axis = 0) else: - innerT = HFEngine.innerProduct(X, Y, is_state = is_state) - normX = HFEngine.norm(X, is_state = is_state) - normY = HFEngine.norm(Y, is_state = is_state) - xInf = np.where(np.isclose(normX, 0.))[0] - normX[xInf] = 1. - return normY - np.abs(innerT / normX).T + innerT = np.real(HFEngine.innerProduct(X, Y, is_state = is_state)) + norm2X = HFEngine.norm(X, is_state = is_state) ** 2. + norm2Y = HFEngine.norm(Y, is_state = is_state) ** 2. + xInf = np.where(np.isclose(norm2X, 0.))[0] + yInf = np.where(np.isclose(norm2Y, 0.))[0] + if radius is None: radius = np.mean(norm2Y) ** .5 + dist2T = (np.tile(norm2Y.reshape(-1, 1), len(norm2X)) + + norm2X.reshape(1, -1) - 2 * innerT) + dist2T[:, xInf], dist2T[yInf, :] = 1., 1. + dist2T[np.ix_(yInf, xInf)] = 0. + dist2T[dist2T < 0.] = 0. + return radius * ((dist2T / (norm2X + radius ** 2.)).T + / (norm2Y + radius ** 2.)) ** .5 def chordalMetricTable(x:Np1D, y:Np1D, radius : float = 1.) -> Np2D: x, y = np.array(x), np.array(y) xInf, yInf = np.where(np.isinf(x))[0], np.where(np.isinf(y))[0] x[xInf], y[yInf] = 0., 0. distT = np.abs(np.tile(y.reshape(-1, 1), len(x)) - x.reshape(1, -1)) distT[:, xInf], distT[yInf, :] = 1., 1. distT[np.ix_(yInf, xInf)] = 0. return radius * ((distT / (np.abs(x) ** 2. + radius ** 2.) ** .5).T / (np.abs(y) ** 2. + radius ** 2.) ** .5) def chordalMetricAdjusted(x:Np1D, y:Np1D, w : float = 0, X : Np2D = None, Y : Np2D = None, HFEngine : HFEng = None, is_state : bool = True) -> Np2D: dist = chordalMetricTable(x, y) if w == 0: return dist distAdj = angleTable(X, Y, HFEngine, is_state) return (dist + w * distAdj) / (1. + w) diff --git a/rrompy/utilities/numerical/sobol.py b/rrompy/utilities/numerical/sobol.py index 593b840..c1e747a 100644 --- a/rrompy/utilities/numerical/sobol.py +++ b/rrompy/utilities/numerical/sobol.py @@ -1,80 +1,80 @@ # 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.exception_manager import RROMPyException __all__ = ['sobolGenerate'] def low2bit(n): if n < 0: raise RROMPyException("Only positive integers allowed.") j = 2 while (n % j) // (j / 2) == 1: j = j * 2 return int(np.log2(j)) - 1 -def sobolGenerate(dim_num, n, seed = 0): +def sobolGenerate(dim_num, n, seed = 0, return_seed = False): SEEDBITS = 30 v = np.zeros((dim_num, SEEDBITS), dtype = int) v[:, 0] = 1 v[2:, 1] = np.array([1,3,1,3,1,3,3,1,3,1,3,1,3,1,1,3,1,3,1,3,1,3,3,1,3,1,3, 1,3,1,1,3,1,3,1,3,1,3])[:max(0,dim_num-2)] v[3:, 2] = np.array([7,5,1,3,3,7,5,5,7,7,1,3,3,7,5,1,1,5,3,3,1,7,5,1,3,3,7, 5,1,1,5,7,7,5,1,3,3])[:max(0,dim_num-3)] v[5:, 3] = np.array([1,7,9,13,11,1,3,7,9,5,13,13,11,3,15,5,3,15,7,9,13,9,1, 11,7,5,15,1,15,11,5,3,1,7,9])[:max(0,dim_num-5)] v[7:, 4] = np.array([9,3,27,15,29,21,23,19,11,25,7,13,17,1,25,29,3,31,11,5, 23,27,19,21,5,1,17,13,7,15,9,31,9])[:max(0,dim_num-7)] v[13:, 5] = np.array([37,33,7,5,11,39,63,27,17,15,23,29,3,21,13,31,25,9,49, 33,19,29,11,19,27,15,25])[:max(0,dim_num-13)] v[19:, 6] = np.array([13,33,115,41,79,17,29,119,75,73,105,7,59,65,21,3,113, 61,89,45,107])[:max(0,dim_num-19)] v[37:, 7] = np.array([7,23,39])[:max(0,dim_num-37)] poly = [1,3,7,11,13,19,25,37,59,47,61,55,41,67,97,91,109,103,115,131,193, 137,145,143,241,157,185,167,229,171,213,191,253,203,211,239,247, 285,369,299] v[0, :] = 1 for i in range(1, dim_num): j = poly[i] m = int(1 + np.floor(np.log2(j))) includ = np.array([int(x) for x in "{0:b}".format(j)], dtype = int) for j in range(m - 1, SEEDBITS): newv = v[i, j - m + 1] l = 1 for k in range(1, m): l *= 2 if includ[k]: newv = np.bitwise_xor(newv, l * v[i, j - k]) v[i, j] = newv v = np.multiply(v, np.power(2, np.arange(SEEDBITS)[::-1])) recipd = np.power(2., - SEEDBITS) lastq = np.zeros(dim_num, dtype = int) #### if seed < 0: seed = 0 for seed_temp in range(seed): l = low2bit(seed_temp) lastq = np.bitwise_xor(lastq, v[:, l]) r = np.empty((n, dim_num)) for seed_temp in range(seed, seed + n): l = low2bit(seed_temp) r[seed_temp - seed, :] = lastq * recipd lastq = np.bitwise_xor(lastq, v[:, l]) + if return_seed: return r, seed + n return r - diff --git a/rrompy/utilities/numerical/tensor_la.py b/rrompy/utilities/numerical/tensor_la.py index 5901bf6..188768b 100644 --- a/rrompy/utilities/numerical/tensor_la.py +++ b/rrompy/utilities/numerical/tensor_la.py @@ -1,47 +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 . # import numpy as np from numbers import Number +from rrompy.sampling.sample_list import sampleList +from rrompy.parameter.parameter_list import parameterList from rrompy.utilities.exception_manager import RROMPyException __all__ = ['dot', 'solve'] def dot(u, v): if isinstance(u, Number) or isinstance(v, Number): return u * v + if isinstance(u, (parameterList, sampleList)): u = u.data + if isinstance(v, (parameterList, sampleList)): v = v.data if u.shape[-1] == v.shape[0]: if isinstance(u, np.ndarray): return np.tensordot(u, v, 1) else: return u.dot(v) M = u.shape[-1] N = v.shape[0] // M rshape = u.shape[: -2] + (N * u.shape[-2],) + v.shape[1 :] return u.dot(v.reshape(M, -1)).reshape(rshape) def solve(A, b, solver, kwargs): try: if isinstance(A, Number): return b / A + if isinstance(A, (parameterList, sampleList)): A = A.data + if isinstance(b, (parameterList, sampleList)): b = b.data if A.shape[-1] == b.shape[0]: return solver(A, b, kwargs) M = A.shape[-1] N = b.shape[0] // M rshape = A.shape[: -2] + (N * A.shape[-2],) + b.shape[1 :] return solver(A, b.reshape(M, -1), kwargs).reshape(rshape) except np.linalg.LinAlgError as e: raise RROMPyException(e) diff --git a/rrompy/utilities/parallel/__init__.py b/rrompy/utilities/parallel/__init__.py index 5e47a86..5a11a6e 100644 --- a/rrompy/utilities/parallel/__init__.py +++ b/rrompy/utilities/parallel/__init__.py @@ -1,48 +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 .base import (_MPI_IS_LOADED, COMM, SELF, poolRank, poolSize, masterCore, updateSerialIndex, forceSerialExecution, allowParallelExecution, forcedSerial) -from .partition import partitionIndices -from .broadcast import bcastForce, bcast +from .broadcast import bcast, ibcast +from .sendrecv import send, isend, recv, irecv +from .request import fakeRequest from .scatter import indicesScatter, listScatter from .gather import listGather, arrayGatherv, matrixGatherv __all__ = [ '_MPI_IS_LOADED', 'COMM', 'SELF', 'poolRank', 'poolSize', 'masterCore', 'updateSerialIndex', 'forceSerialExecution', 'allowParallelExecution', 'forcedSerial', - 'partitionIndices', - 'bcastForce', 'bcast', + 'ibcast', + 'send', + 'isend', + 'recv', + 'irecv', + 'fakeRequest', 'indicesScatter', 'listScatter', 'listGather', 'arrayGatherv', 'matrixGatherv' ] diff --git a/rrompy/utilities/parallel/base.py b/rrompy/utilities/parallel/base.py index d8b2e4c..476ed2d 100644 --- a/rrompy/utilities/parallel/base.py +++ b/rrompy/utilities/parallel/base.py @@ -1,67 +1,67 @@ # 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.verbosity_depth import updateVerbosityCheckpoint try: from mpi4py import MPI _MPI_IS_LOADED = True COMM, SELF = MPI.COMM_WORLD, MPI.COMM_SELF except ImportError: _MPI_IS_LOADED = False COMM, SELF = None, None __all__ = ['_MPI_IS_LOADED', 'COMM', 'SELF', 'poolRank', 'poolSize', 'masterCore', 'updateSerialIndex', 'forceSerialExecution', 'allowParallelExecution', 'forcedSerial'] def poolRank() -> int: if _MPI_IS_LOADED: return COMM.Get_rank() return 0 def poolSize() -> int: if _MPI_IS_LOADED: return COMM.Get_size() return 1 def masterCore() -> bool: return forcedSerial() or poolRank() == 0 def updateSerialIndex(n : int = 0, delta : bool = True): global RROMPy_force_serial if "RROMPy_force_serial" not in globals(): RROMPy_force_serial = 0 if delta: RROMPy_force_serial += n else: RROMPy_force_serial = n if RROMPy_force_serial <= 0: del RROMPy_force_serial def forceSerialExecution(verbosity : bool = False): updateSerialIndex(1) - if verbosity: updateVerbosityCheckpoint(1) + if verbosity and poolSize() > 1: updateVerbosityCheckpoint(1) def allowParallelExecution(verbosity : bool = False): updateSerialIndex(-1) - if verbosity: + if verbosity and poolSize() > 1: buffer = updateVerbosityCheckpoint(-1) if buffer is not None: msg = COMM.gather(buffer, root = 0) if masterCore(): out = "" for ms in msg: out += ms print(out, end = "", flush = True) def forcedSerial(): global RROMPy_force_serial return "RROMPy_force_serial" in globals() and RROMPy_force_serial > 0 diff --git a/rrompy/utilities/parallel/broadcast.py b/rrompy/utilities/parallel/broadcast.py index 3f5ecdd..3ed7faa 100644 --- a/rrompy/utilities/parallel/broadcast.py +++ b/rrompy/utilities/parallel/broadcast.py @@ -1,30 +1,32 @@ # 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 .base import COMM, poolSize, forcedSerial +from .request import fakeRequest -__all__ = ['bcastForce', 'bcast'] +__all__ = ['bcast', 'ibcast'] -def bcastForce(obj, root : int = 0): - return COMM.bcast(obj, root = root) - -def bcast(obj, root : int = 0): - if not forcedSerial() and poolSize() > 1: - return bcastForce(obj, root) +def bcast(obj, root : int = 0, force : bool = False): + if force or (not forcedSerial() and poolSize() > 1): + return COMM.bcast(obj, root = root) return obj +def ibcast(obj, root : int = 0, force : bool = False): + if force or (not forcedSerial() and poolSize() > 1): + return COMM.Ibcast(obj, root = root) + return fakeRequest(obj) diff --git a/rrompy/utilities/parallel/gather.py b/rrompy/utilities/parallel/gather.py index 11dda8c..ba11675 100755 --- a/rrompy/utilities/parallel/gather.py +++ b/rrompy/utilities/parallel/gather.py @@ -1,57 +1,57 @@ # 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, List, ListAny from .base import COMM, poolSize, forcedSerial, allowParallelExecution -__all__ = ['listGather', 'arrayGatherv', 'matrixGather'] +__all__ = ['listGather', 'arrayGatherv', 'matrixGatherv'] def listGather(obj:ListAny, allow : bool = True) -> ListAny: """Gather list from parallel cores.""" if allow: allowParallelExecution(True) if not forcedSerial() and poolSize() > 1: objList = COMM.allgather(obj) res = [] for dat in objList: res += dat return res return obj def arrayGatherv(obj:Np1D, sizes:List[int], allow : bool = True) -> Np1D: """Gather vector from parallel cores.""" if allow: allowParallelExecution(True) if not forcedSerial() and len(sizes) > 1: out = np.empty(np.sum(sizes), dtype = obj.dtype) COMM.Allgatherv(obj, [out, sizes]) return out return obj def matrixGatherv(obj:Np2D, sizes:List[int], allow : bool = True, vertical : bool = False) -> Np2D: """Gather matrix from parallel cores.""" if allow: allowParallelExecution(True) if not forcedSerial() and len(sizes) > 1: if vertical: length = obj.shape[1] else: length, obj = obj.shape[0], obj.T out = arrayGatherv(obj.flatten(), [s * length for s in sizes], False).reshape(-1, length) if vertical: return out return out.T return obj diff --git a/rrompy/utilities/parallel/partition.py b/rrompy/utilities/parallel/partition.py deleted file mode 100644 index c84c5fd..0000000 --- a/rrompy/utilities/parallel/partition.py +++ /dev/null @@ -1,30 +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 numpy as np -from rrompy.utilities.base.types import ListAny - -__all__ = ['partitionIndices'] - -def partitionIndices(a:int, n:int, j:int, - return_sizes : bool = False) -> ListAny: - lb = (a // n) * j + min(j, a % n) - idx = list(range(lb, lb + (a // n) + (j < a % n))) - if return_sizes: - return idx, [a // n + 1] * (a % n) + [a // n] * (n - a % n) - return idx diff --git a/tests/1_utilities/fenics_const.py b/rrompy/utilities/parallel/request.py similarity index 86% rename from tests/1_utilities/fenics_const.py rename to rrompy/utilities/parallel/request.py index 96d611e..091833e 100644 --- a/tests/1_utilities/fenics_const.py +++ b/rrompy/utilities/parallel/request.py @@ -1,20 +1,23 @@ # 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 . # -def test_fenics_loads(): - from rrompy.solver.fenics import fenZERO, fenZEROS, fenONE, fenONES +__all__ = ['fakeRequest'] + +def fakeRequest(obj = None): + class request: wait = lambda: obj + return request diff --git a/rrompy/utilities/parallel/scatter.py b/rrompy/utilities/parallel/scatter.py index 6d87b4c..16b9be5 100644 --- a/rrompy/utilities/parallel/scatter.py +++ b/rrompy/utilities/parallel/scatter.py @@ -1,41 +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 . # from rrompy.utilities.base.types import Tuple, ListAny -from .base import (COMM, poolSize, poolRank, masterCore, forcedSerial, - forceSerialExecution) -from .partition import partitionIndices +from .base import poolSize, poolRank, forcedSerial, forceSerialExecution __all__ = ['indicesScatter', 'listScatter'] +def partitionIndices(a:int, n:int, j:int, + return_sizes : bool = False) -> ListAny: + aeach, nextra = a // n, a % n + lb = aeach * j + min(j, nextra) + idx = list(range(lb, lb + aeach + (j < nextra))) + if return_sizes: return idx, [aeach + 1] * nextra + [aeach] * (n - nextra) + return idx + def indicesScatter(a:int, force : bool = True, return_sizes : bool = False) -> ListAny: """Split indices among parallel cores.""" size, rank = (1, 0) if forcedSerial() else (poolSize(), poolRank()) if force: forceSerialExecution(True) return partitionIndices(a, size, rank, return_sizes) def listScatter(obj:ListAny, force : bool = True, return_sizes : bool = False) -> Tuple[ListAny, ListAny]: """Split list among parallel cores.""" idx = indicesScatter(len(obj), force, return_sizes) if not return_sizes: idx = (idx,) try: return (obj[idx[0]],) + idx except: return ([obj[i] for i in idx[0]],) + idx diff --git a/rrompy/utilities/parallel/broadcast.py b/rrompy/utilities/parallel/sendrecv.py similarity index 68% copy from rrompy/utilities/parallel/broadcast.py copy to rrompy/utilities/parallel/sendrecv.py index 3f5ecdd..ae6d736 100644 --- a/rrompy/utilities/parallel/broadcast.py +++ b/rrompy/utilities/parallel/sendrecv.py @@ -1,30 +1,34 @@ # 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 .base import COMM, poolSize, forcedSerial +from .base import COMM -__all__ = ['bcastForce', 'bcast'] +__all__ = ['send', 'isend', 'recv', 'irecv'] -def bcastForce(obj, root : int = 0): - return COMM.bcast(obj, root = root) +def send(*args, **kwargs): + COMM.send(*args, **kwargs) -def bcast(obj, root : int = 0): - if not forcedSerial() and poolSize() > 1: - return bcastForce(obj, root) - return obj +def isend(*args, **kwargs): + return COMM.isend(*args, **kwargs) + +def recv(*args, **kwargs): + return COMM.recv(*args, **kwargs) + +def irecv(*args, **kwargs): + return COMM.irecv(*args, **kwargs) diff --git a/rrompy/utilities/poly_fitting/heaviside/__init__.py b/rrompy/utilities/poly_fitting/heaviside/__init__.py index 4896f36..c4c01da 100644 --- a/rrompy/utilities/poly_fitting/heaviside/__init__.py +++ b/rrompy/utilities/poly_fitting/heaviside/__init__.py @@ -1,42 +1,41 @@ # 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 .base import polybases, polyfitname, polydomcoeff from .val import polyval -from .vander import polyvander, polyvanderTotal +from .vander import polyvander from .heaviside_to_from_rational import heaviside2rational, rational2heaviside from .heaviside_to_from_affine import heaviside2affine, affine2heaviside from .heaviside_interpolator import HeavisideInterpolator from .heaviside_manipulation import heavisideUniformShape __all__ = [ 'polybases', 'polyfitname', 'polydomcoeff', 'polyval', 'polyvander', 'heaviside2rational', 'rational2heaviside', 'heaviside2affine', 'affine2heaviside', - 'polyvanderTotal', 'HeavisideInterpolator', 'heavisideUniformShape' ] diff --git a/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py b/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py index ac3b776..51c429c 100644 --- a/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py +++ b/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py @@ -1,72 +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.types import (List, ListAny, DictAny, 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): 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): + scl : Np1D = None, parameterMap : DictAny = 1.): self.coeffs, self.poles, self.polybasis = rational2heaviside(num, den, murange, scl, - scalingExp) + parameterMap) def roots(self, marginalVals : ListAny = [fp], murange : Np1D = None, - scalingExp : List[float] = None): + parameterMap : DictAny = 1.): 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] + parameterMap = parameterMap)[0] return polyroots(coeffsN, basisN) diff --git a/rrompy/utilities/poly_fitting/heaviside/heaviside_to_from_rational.py b/rrompy/utilities/poly_fitting/heaviside/heaviside_to_from_rational.py index cb4f260..91fdd2d 100644 --- a/rrompy/utilities/poly_fitting/heaviside/heaviside_to_from_rational.py +++ b/rrompy/utilities/poly_fitting/heaviside/heaviside_to_from_rational.py @@ -1,102 +1,102 @@ # 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.polynomial.polynomial_interpolator import \ PolynomialInterpolator from rrompy.utilities.poly_fitting.polynomial.vander import polyvander from rrompy.utilities.poly_fitting.custom_fit import customFit -from rrompy.utilities.base.types import Np1D, Np2D, List, Tuple, interpEng +from rrompy.utilities.base.types import Np1D, Np2D, Tuple, DictAny, interpEng from rrompy.parameter.parameter_sampling import (RandomSampler as RS, QuadratureSampler as QS) from .val import polyval from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert __all__ = ['heaviside2rational', 'rational2heaviside'] def heaviside2rational(c:Np2D, poles:Np1D, murange : Np1D = None, basis : str = "MONOMIAL_HEAVISIDE", basisD : str = None, - scalingExp : List[float] = None) \ + parameterMap : DictAny = 1.) \ -> Tuple[interpEng, interpEng]: num = PolynomialInterpolator() den = PolynomialInterpolator() basisN = basis.split("_")[0] if basisD is None: basisD = basisN if murange is None: multiplier = [1., 1.j] avgs = [np.mean(np.real(poles)), np.mean(np.imag(poles))] ranges = np.array([[np.min(np.real(poles)), np.max(np.real(poles))], [np.min(np.imag(poles)), np.max(np.imag(poles))]]) domIdx = np.argmax(ranges[:, 1] - ranges[:, 0]) murange = [multiplier[domIdx] * x + multiplier[1 - domIdx] * avgs[1 - domIdx] for x in ranges[domIdx, :]] extraPt = None while extraPt is None or np.any(np.isclose(extraPt, poles)): extraPt = murange[0] + (murange[1] - murange[0]) * np.random.rand(1) denAuxPts = np.concatenate((poles, extraPt)) denAuxVals = np.concatenate((np.zeros(len(poles)), [1.])) den.setupByInterpolation(denAuxPts, denAuxVals, len(poles), basisD) den.coeffs /= np.linalg.norm(den.coeffs) if basis == "CHEBYSHEV": - sampler = QS(murange, "CHEBYSHEV", scalingExp) + sampler = QS(murange, "CHEBYSHEV", parameterMap) elif basis == "LEGENDRE": - sampler = QS(murange, "GAUSSLEGENDRE", scalingExp) + sampler = QS(murange, "GAUSSLEGENDRE", parameterMap) else: - sampler = RS(murange, "HALTON", scalingExp) + sampler = RS(murange, "HALTON", parameterMap) xAux = sampler.generatePoints(len(c)) valsAux = den(xAux) * polyval(xAux, c, poles, basis) num.setupByInterpolation(xAux, valsAux, len(c) - 1, basisN) return num, den def rational2heaviside(num:interpEng, den:interpEng, murange : Np1D = np.array([-1., 1.]), scl : Np1D = None, - scalingExp : List[float] = None) \ + parameterMap : DictAny = 1.) \ -> Tuple[Np2D, Np1D, str]: if (not isinstance(num, PolynomialInterpolator) or not isinstance(den, PolynomialInterpolator)): raise RROMPyException(("Rational numerator and denominator must be " "polynomial interpolators.")) RROMPyAssert(num.npar, 1, "Number of parameters") RROMPyAssert(den.npar, 1, "Number of parameters") basis = num.polybasis + "_HEAVISIDE" c = np.zeros_like(num.coeffs) poles = den.roots() Psp = num(poles) Qsp = den(poles) Qspder = den(poles, 1, scl) polesBad = np.abs(Qsp) >= 1e-5 Psp[..., polesBad] = 0. Qspder[polesBad] = 1. c[: len(poles)] = (Psp / Qspder).T if len(c) > len(poles): from rrompy.parameter.parameter_sampling import (RandomSampler as RS, QuadratureSampler as QS) if num.polybasis == "CHEBYSHEV": - sampler = QS(murange, "CHEBYSHEV", scalingExp) + sampler = QS(murange, "CHEBYSHEV", parameterMap) elif num.polybasis == "LEGENDRE": - sampler = QS(murange, "GAUSSLEGENDRE", scalingExp) + sampler = QS(murange, "GAUSSLEGENDRE", parameterMap) else: - sampler = RS(murange, "HALTON", scalingExp) + sampler = RS(murange, "HALTON", parameterMap) xAux = sampler.generatePoints(len(c)) valsAux = (num(xAux) / den(xAux) - polyval(xAux, c, poles, basis)).T VanAux = polyvander(xAux, [len(c) - len(poles) - 1], num.polybasis) c[len(poles) :] = customFit(VanAux, valsAux) poles[polesBad] = np.inf return c, poles, basis diff --git a/rrompy/utilities/poly_fitting/heaviside/val.py b/rrompy/utilities/poly_fitting/heaviside/val.py index 39707c2..4f178ef 100644 --- a/rrompy/utilities/poly_fitting/heaviside/val.py +++ b/rrompy/utilities/poly_fitting/heaviside/val.py @@ -1,46 +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 copy import deepcopy as copy from rrompy.utilities.base.types import Np1D, Np2D, paramList from rrompy.parameter import checkParameterList from rrompy.utilities.poly_fitting.polynomial.val import polyval as pvP __all__ = ['polyval'] def polyval(x:paramList, c:Np2D, poles:Np1D, basis : str = "MONOMIAL_HEAVISIDE") -> Np2D: - x = checkParameterList(x, 1)[0] + x = checkParameterList(x, 1) poles = poles.flatten() if len(c) > len(poles): basisp = basis.split("_")[0] c0 = pvP(x, c[len(poles) :], basisp) else: c0 = np.zeros(c.shape[1:] + (len(x),), dtype = c.dtype) csh = copy(c0.shape) if len(csh) == 1: c0 = c0.reshape(1, -1) for j in range(len(x)): muDiff = 1. / (x[j] - poles) + muDiff[np.isinf(poles)] = 0. val = np.tensordot(muDiff, c[: len(poles)], 1) try: c0[..., j] += val except: c0[..., j] += val.flatten() if len(csh) == 1: c0 = c0.flatten() return c0 diff --git a/rrompy/utilities/poly_fitting/heaviside/vander.py b/rrompy/utilities/poly_fitting/heaviside/vander.py index d762104..6c9f331 100644 --- a/rrompy/utilities/poly_fitting/heaviside/vander.py +++ b/rrompy/utilities/poly_fitting/heaviside/vander.py @@ -1,86 +1,63 @@ # 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.polynomial.vander import (polyvander as pvP, - polyvanderTotal as pvTP) +from rrompy.utilities.poly_fitting.polynomial.vander import polyvander as pvP from rrompy.utilities.base.types import Np1D, Np2D, List, paramList from rrompy.parameter import checkParameterList from rrompy.utilities.exception_manager import RROMPyException -__all__ = ['heavisidevander', 'polyvander', 'polyvanderTotal'] +__all__ = ['heavisidevander', 'polyvander'] def heavisidevander(x:paramList, poles:Np1D, reorder : List[int] = None) -> Np2D: """Compute Heaviside-Vandermonde matrix.""" - x = checkParameterList(x, 1)[0] + x = checkParameterList(x, 1) x_un, idx_un = x.unique(return_inverse = True) nx = len(x) if len(x_un) < nx: raise RROMPyException("Sample points must be distinct.") del x_un x = x.data.flatten() if reorder is not None: x = x[reorder] poles = poles.flatten() Van = np.empty((len(x), len(poles)), dtype = np.complex) for j in range(len(x)): Van[j, :] = 1. / (x[j] - poles) return Van def polyvander(x:paramList, poles:Np1D, degs : List[int] = None, basis : str = "MONOMIAL_HEAVISIDE", derIdxs : List[List[List[int]]] = None, - reorder : List[int] = None, scl : Np1D = None) -> Np2D: + reorder : List[int] = None, scl : Np1D = None, + forceTotalDegree : bool = False) -> Np2D: """ Compute full Hermite-Vandermonde matrix with specified derivative directions. """ if derIdxs is not None and np.sum(np.sum(derIdxs)) > 0: raise RROMPyException(("Cannot take derivatives of heaviside " "function.")) basisp = basis.split("_")[0] VanH = heavisidevander(x, poles, reorder = reorder) if degs is None or np.sum(degs) < 0: VanP = np.empty((len(x), 0)) else: VanP = pvP(x, degs, basisp, derIdxs = derIdxs, reorder = reorder, - scl = scl) + scl = scl, forceTotalDegree = forceTotalDegree) return np.block([[VanH, VanP]]) - -def polyvanderTotal(x:paramList, poles:Np1D, deg : int = None, - basis : str = "MONOMIAL_HEAVISIDE", - 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. - """ - if derIdxs is not None and np.sum(np.sum(derIdxs)) > 0: - raise RROMPyException(("Cannot take derivatives of radial basis " - "function.")) - basisp = basis.split("_")[0] - VanR = heavisidevander(x, poles, reorder = reorder) - if deg is None or deg < 0: - VanP = np.empty((len(x), 0)) - derIdxs, _ = np.zeros(0, dtype = int), np.zeros(0, dtype = int) - else: - VanP = pvTP(x, deg, basisp, derIdxs = derIdxs, - reorder = reorder, scl = scl) - return np.block([[VanR, VanP], - [VanP.T.conj(), np.zeros(tuple([VanP.shape[1]] * 2))]]) - diff --git a/rrompy/utilities/poly_fitting/nearest_neighbor/nearest_neighbor_interpolator.py b/rrompy/utilities/poly_fitting/nearest_neighbor/nearest_neighbor_interpolator.py index 078a5c4..984450e 100644 --- a/rrompy/utilities/poly_fitting/nearest_neighbor/nearest_neighbor_interpolator.py +++ b/rrompy/utilities/poly_fitting/nearest_neighbor/nearest_neighbor_interpolator.py @@ -1,91 +1,90 @@ # 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, Np2D, paramList from rrompy.utilities.poly_fitting.interpolator import GenericInterpolator from .val import polyval from rrompy.utilities.numerical import dot from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.parameter import checkParameterList __all__ = ['NearestNeighborInterpolator'] class NearestNeighborInterpolator(GenericInterpolator): def __init__(self, other = None): if other is None: return self.support = other.support self.coeffsLocal = other.coeffsLocal self.nNeighbors = other.nNeighbors self.directionalWeights = other.directionalWeights self.npar = other.npar @property def shape(self): sh = self.coeffsLocal.shape[1 :] if self.coeffsLocal.ndim > 1 else 1 return sh def __call__(self, mu:paramList, der : List[int] = None, scl : Np1D = None): if der is not None and np.sum(der) > 0: return np.zeros(self.coeffsLocal.shape[1 :] + (len(mu),)) return polyval(mu, self.coeffsLocal, self.support, self.nNeighbors, self.directionalWeights) def __copy__(self): return NearestNeighborInterpolator(self) def __deepcopy__(self, memo): other = NearestNeighborInterpolator() (other.support, other.coeffsLocal, other.nNeighbors, other.directionalWeights, other.npar) = copy((self.support, self.coeffsLocal, self.nNeighbors, self.directionalWeights, self.npar), memo) return other def postmultiplyTensorize(self, A:Np2D): RROMPyAssert(A.shape[0], self.shape[-1], "Shape of output") self.coeffsLocal = dot(self.coeffsLocal, 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.)) def setupByInterpolation(self, support:paramList, values:ListAny, nNeighbors : int = 1, directionalWeights : Np1D = None): - support = checkParameterList(support)[0] + support = checkParameterList(support) RROMPyAssert(len(support), len(values), "Number of support values") self.support = copy(support) self.npar = support.shape[1] self.coeffsLocal = values self.nNeighbors = max(1, nNeighbors) - if directionalWeights is None: - directionalWeights = np.ones(self.npar) - self.directionalWeights = directionalWeights + if directionalWeights is None: directionalWeights = [1.] * self.npar + self.directionalWeights = np.array(directionalWeights) RROMPyAssert(len(support), len(values), "Number of support points") return True, None diff --git a/rrompy/utilities/poly_fitting/nearest_neighbor/val.py b/rrompy/utilities/poly_fitting/nearest_neighbor/val.py index ccc02ad..d23f3ff 100644 --- a/rrompy/utilities/poly_fitting/nearest_neighbor/val.py +++ b/rrompy/utilities/poly_fitting/nearest_neighbor/val.py @@ -1,41 +1,41 @@ # 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, paramList from rrompy.parameter import checkParameterList __all__ = ['polyval'] def polyval(x:paramList, cL:Np2D, supportPoints:paramList, nNeighbors : int = 1, directionalWeights : Np1D = None) -> Np2D: - supportPoints = checkParameterList(supportPoints)[0] + supportPoints = checkParameterList(supportPoints, return_data = True) if directionalWeights is None: directionalWeights = np.ones(supportPoints.shape[1]) npar = supportPoints.shape[1] - x = checkParameterList(x, npar)[0] - muDiff = (np.repeat(supportPoints.data.reshape((len(supportPoints), 1, - npar)), len(x), axis = 1) - - x.data) * directionalWeights + x = checkParameterList(x, npar, return_data = True) + muDiff = (np.repeat(supportPoints.reshape((len(supportPoints), 1, + npar)), len(x), axis = 1) - x + ) * directionalWeights dist = (np.sum(np.abs(muDiff) ** 2., axis = 2) + np.finfo(float).eps ** 2.) ** -.5 if len(dist) > nNeighbors: iOut = np.argpartition(dist, - nNeighbors, axis = 0)[: - nNeighbors] np.put_along_axis(dist, iOut, 0., 0) dist /= np.linalg.norm(dist, axis = 0, ord = 1) return np.moveaxis(np.tensordot(dist.T, cL, 1), 0, -1) diff --git a/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py b/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py index 8bc6a42..7741a82 100644 --- a/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py +++ b/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py @@ -1,65 +1,64 @@ # 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 .base import centerNormalize, sparseMap from rrompy.utilities.base.types import Np1D, Np2D, paramList -from rrompy.utilities.exception_manager import RROMPyException from rrompy.parameter import checkParameterList __all__ = ['hatFunction', 'val', 'vander'] def hatFunctionRef(x:Np1D, supp:float, depth:int, kind:str) -> Np1D: if depth < 0: y = np.zeros_like(x) elif depth == 0: y = np.ones_like(x) else: suppEff = sparseMap(supp, [-1., 1.], kind, False) exp = depth if "NOBOUNDARY" in kind.upper() else depth - 1 suppLREff = suppEff + .5 ** exp * np.array([-1., 1.]) widthL, widthR = sparseMap(suppLREff, [-1., 1.], kind) - supp xC = np.array(x - supp) if np.isclose(widthL, 0.) or supp + widthL < - 1. - .1 * widthL: isleft, isright = 0, 1 elif np.isclose(widthR, 0.) or supp + widthR > 1. - .1 * widthR: isleft, isright = 1, 0 else: isleft, isright = xC < 0., xC >= 0. y = 1. - xC / (widthL * isleft + widthR * isright) return np.clip(y, 0., 1., y) def hatFunction(x:paramList, supportPoints:paramList, depths:Np2D, kind:str, lims:paramList) -> Np2D: - x = checkParameterList(x)[0] - supportPoints = checkParameterList(supportPoints, x.shape[1])[0] - lims = checkParameterList(lims, x.shape[1])[0] + x = checkParameterList(x) + supportPoints = checkParameterList(supportPoints, x.shape[1]) + lims = checkParameterList(lims, x.shape[1]) res = np.ones((len(supportPoints), len(x))) for d in range(x.shape[1]): x0 = centerNormalize(x(d), lims(d), False) for j in range(len(supportPoints)): supp = centerNormalize(supportPoints(j, d), lims(d), False) res[j] *= hatFunctionRef(x0, supp, depths[j, d], kind) return res.T def vander(supportPoints:paramList, depths:Np2D, kind:str, lims:paramList) -> Np2D: return hatFunction(supportPoints, supportPoints, depths, kind, lims) def val(x:paramList, c:Np2D, supportPoints:paramList, depths:Np2D, kind:str, lims:paramList) -> Np2D: van = hatFunction(x, supportPoints, depths, kind, lims) return np.tensordot(c, van, (0, -1)) diff --git a/rrompy/utilities/poly_fitting/piecewise_linear/piecewise_linear_interpolator.py b/rrompy/utilities/poly_fitting/piecewise_linear/piecewise_linear_interpolator.py index c7bbe74..4d14b1d 100644 --- a/rrompy/utilities/poly_fitting/piecewise_linear/piecewise_linear_interpolator.py +++ b/rrompy/utilities/poly_fitting/piecewise_linear/piecewise_linear_interpolator.py @@ -1,103 +1,103 @@ # 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 .kernel import vander, val from rrompy.utilities.numerical import dot from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert from rrompy.parameter import checkParameterList __all__ = ['PiecewiseLinearInterpolator'] class PiecewiseLinearInterpolator(GenericInterpolator): def __init__(self, other = None): if other is None: return self.support = other.support self.lims = other.lims self.coeffs = other.coeffs self.depths = other.depths self.npar = other.npar self.kind = other.kind @property def shape(self): sh = self.coeffs.shape[1 :] if self.coeffs.ndim > 1 else 1 return sh 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 piecewise " "linear function.")) return val(mu, self.coeffs, self.support, self.depths, self.kind, self.lims) def __copy__(self): return PiecewiseLinearInterpolator(self) def __deepcopy__(self, memo): other = PiecewiseLinearInterpolator() (other.support, other.lims, other.coeffs, other.depths, other.npar, other.kind) = copy((self.support, self.lims, self.coeffs, self.depths, self.npar, self.kind), memo) return other def postmultiplyTensorize(self, A:Np2D): RROMPyAssert(A.shape[0], self.shape[-1], "Shape of output") self.coeffs = dot(self.coeffs, 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.coeffs = np.pad(self.coeffs, padwidth, "constant", constant_values = (0., 0.)) padwidth = [(0, 0)] * (self.npar - 1) + padwidth def setupByInterpolation(self, support:paramList, values:ListAny, lims:paramList, depths:Np2D, kind : str = "PIECEWISE_LINEAR_UNIFORM", verbose : bool = True, fitCoeffs : DictAny = {}): - support = checkParameterList(support)[0] + support = checkParameterList(support) RROMPyAssert(len(support), len(values), "Number of support values") self.support = copy(support) self.npar = support.shape[1] - lims = checkParameterList(lims, self.npar)[0] + lims = checkParameterList(lims, self.npar) self.lims = copy(lims) self.depths = copy(depths) self.kind = kind van = vander(support, depths, kind, lims) outDim = values.shape[1:] values = values.reshape(values.shape[0], -1) fitOut = customFit(van, values, full = True, **fitCoeffs) if verbose: msg = ("Fitting {} samples through piecewise linear " "interpolator... Conditioning of system: {:.4e}.").format( len(support), fitOut[1][2][0] / fitOut[1][2][-1]) else: msg = None self.coeffs = fitOut[0].reshape((len(support),) + outDim) return fitOut[1][1] == van.shape[1], msg diff --git a/rrompy/utilities/poly_fitting/polynomial/__init__.py b/rrompy/utilities/poly_fitting/polynomial/__init__.py index 1f598b2..8bd3678 100644 --- a/rrompy/utilities/poly_fitting/polynomial/__init__.py +++ b/rrompy/utilities/poly_fitting/polynomial/__init__.py @@ -1,48 +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 . # from .base import (polybases, polyfitname, polydomcoeff) from .der import polyder from .val import polyval from .marginalize import polymarginalize -from .vander import polyvander, polyvanderTotal +from .vander import polyvander from .roots import polyroots from .polynomial_algebra import (changePolyBasis, polyTimes, polyDivide, polyTimesTable, vanderInvTable, blockDiagDer) from .polynomial_interpolator import PolynomialInterpolator __all__ = [ 'polybases', 'polyfitname', 'polydomcoeff', 'polyder', 'polyval', 'polymarginalize', 'polyvander', - 'polyvanderTotal', 'polyroots', 'changePolyBasis', 'polyTimes', 'polyDivide', 'polyTimesTable', 'vanderInvTable', 'blockDiagDer', 'PolynomialInterpolator' ] diff --git a/rrompy/utilities/poly_fitting/polynomial/base.py b/rrompy/utilities/poly_fitting/polynomial/base.py index 02562a6..ba549c9 100644 --- a/rrompy/utilities/poly_fitting/polynomial/base.py +++ b/rrompy/utilities/poly_fitting/polynomial/base.py @@ -1,61 +1,59 @@ # 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 from rrompy.utilities.exception_manager import RROMPyException __all__ = ['polybases', 'polyfitname', 'polydomcoeff'] polybases = ["CHEBYSHEV", "LEGENDRE", "MONOMIAL"] def polyfitname(basis:str) -> str: - fitnames = {"CHEBYSHEV" : "chebfit", "LEGENDRE" : "legfit", - "MONOMIAL" : "polyfit"} - try: - return fitnames[basis.upper()] - except: + if basis.upper() not in polybases: raise RROMPyException("Polynomial basis not recognized.") + return {"CHEBYSHEV" : "chebfit", "LEGENDRE" : "legfit", + "MONOMIAL" : "polyfit"}[basis.upper()] def polydomcoeff(n:int, basis:str) -> float: basis = basis.upper() if isinstance(n, (list, tuple, np.ndarray,)): nv = np.array(n) else: nv = np.array([n]) if basis == "CHEBYSHEV": x = np.ones_like(nv, dtype = float) x[nv > 0] = np.power(2., nv[nv > 0] - 1) elif basis == "LEGENDRE": x = np.ones_like(nv, dtype = float) x[nv > 10] = (np.power(2., nv[nv > 10]) * np.power(np.pi * nv[nv > 10], -.5)) x[nv <= 10] = (np.power(.5, nv[nv <= 10]) * binom(2 * nv[nv <= 10], nv[nv <= 10])) elif basis == "MONOMIAL": x = np.ones_like(nv, dtype = float) else: raise RROMPyException("Polynomial basis not recognized.") if isinstance(n, (list,)): return list(x) if isinstance(n, (tuple,)): return tuple(x) if isinstance(n, (np.ndarray,)): return x return x[0] diff --git a/rrompy/utilities/poly_fitting/polynomial/der.py b/rrompy/utilities/poly_fitting/polynomial/der.py index 092d0ff..7f9a4de 100644 --- a/rrompy/utilities/poly_fitting/polynomial/der.py +++ b/rrompy/utilities/poly_fitting/polynomial/der.py @@ -1,43 +1,43 @@ # 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 .base import polybases from rrompy.utilities.base.types import Np1D, Np2D, List from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert __all__ = ['polyder'] def polyder(c:Np2D, basis:str, m : List[int] = None, scl : Np1D = None) -> Np2D: c = np.array(c, ndmin = 1, copy = 1) if m is not None: if scl is None: scl = [1.] * c.ndim if not isinstance(m, (list,tuple,np.ndarray,)): m = [m] if not isinstance(scl, (list,tuple,np.ndarray,)): scl = [scl] RROMPyAssert(c.ndim, len(m), "Number of parameters") RROMPyAssert(c.ndim, len(scl), "Number of parameters") - try: - derbase = {"CHEBYSHEV" : np.polynomial.chebyshev.chebder, - "LEGENDRE" : np.polynomial.legendre.legder, - "MONOMIAL" : np.polynomial.polynomial.polyder - }[basis.upper()] - except: + if basis.upper() not in polybases: raise RROMPyException("Polynomial basis not recognized.") + derbase = {"CHEBYSHEV" : np.polynomial.chebyshev.chebder, + "LEGENDRE" : np.polynomial.legendre.legder, + "MONOMIAL" : np.polynomial.polynomial.polyder + }[basis.upper()] for j, (mj, sj) in enumerate(zip(m, scl)): c = derbase(c, m = mj, scl = sj, axis = j) return c diff --git a/rrompy/utilities/poly_fitting/polynomial/marginalize.py b/rrompy/utilities/poly_fitting/polynomial/marginalize.py index 67ce917..66da859 100644 --- a/rrompy/utilities/poly_fitting/polynomial/marginalize.py +++ b/rrompy/utilities/poly_fitting/polynomial/marginalize.py @@ -1,58 +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 . # from numpy import array, polynomial as po from copy import deepcopy as copy +from .base import polybases from rrompy.utilities.base.types import Np1D, Np2D from rrompy.utilities.base import freepar as fp from rrompy.utilities.exception_manager import RROMPyAssert, RROMPyException __all__ = ['polymarginalize'] def polymarginalize(c:Np2D, basis:str, marginalVals : Np1D = [fp], nMarginal : int = None) -> Np1D: if not hasattr(c, "ndim"): c = array(c) ndim = c.ndim if not hasattr(marginalVals, "__len__"): marginalVals = [marginalVals] marginalVals = list(marginalVals) - try: - polyvalbase = {"CHEBYSHEV" : po.chebyshev.chebval, - "LEGENDRE" : po.legendre.legval, - "MONOMIAL" : po.polynomial.polyval}[basis.upper()] - except: + if basis.upper() not in polybases: raise RROMPyException("Polynomial basis not recognized.") + polyvalbase = {"CHEBYSHEV" : po.chebyshev.chebval, + "LEGENDRE" : po.legendre.legval, + "MONOMIAL" : po.polynomial.polyval}[basis.upper()] RROMPyAssert(ndim, len(marginalVals), "Marginalized variables") marginalDims = [] for j in range(len(marginalVals)): if marginalVals[j] == fp: marginalDims += [c.shape[j]] if nMarginal is not None and len(marginalDims) != nMarginal: raise RROMPyException(("Exactly {} 'freepar' entries in marginalVals " "must be provided.").format(nMarginal)) cEff = [copy(c)] for d in range(ndim): if marginalVals[d] != fp: for dj in range(len(cEff)): cEff[dj] = polyvalbase(marginalVals[d], cEff[dj], tensor = False) else: cEff2 = [] for dj in range(len(cEff)): cEff2 += list(cEff[dj]) cEff = copy(cEff2) return array(cEff).reshape(tuple(marginalDims)) diff --git a/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py b/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py index 8467c5a..5b80325 100644 --- a/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py +++ b/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py @@ -1,129 +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 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 .vander import polyvander as pv 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): 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] + support = checkParameterList(support) 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) + if not totalDegree and not hasattr(deg, "__len__"): + deg = [deg] * self.npar + vander = pv(support, deg, basis = polybasis, **vanderCoeffs) RROMPyAssert(len(vander), len(values), "Number of support values") 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]) + 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/roots.py b/rrompy/utilities/poly_fitting/polynomial/roots.py index f392eb7..dbd1e9a 100644 --- a/rrompy/utilities/poly_fitting/polynomial/roots.py +++ b/rrompy/utilities/poly_fitting/polynomial/roots.py @@ -1,34 +1,34 @@ # 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 numpy import polynomial as po +from .base import polybases +from .marginalize import polymarginalize from rrompy.utilities.base.types import Np1D, Np2D from rrompy.utilities.base import freepar as fp -from .marginalize import polymarginalize from rrompy.utilities.exception_manager import RROMPyException __all__ = ['polyroots'] def polyroots(c:Np2D, basis:str, marginalVals : Np1D = [fp]) -> Np1D: - try: - rootsbase = {"CHEBYSHEV" : po.chebyshev.chebroots, - "LEGENDRE" : po.legendre.legroots, - "MONOMIAL" : po.polynomial.polyroots}[basis.upper()] - except: + if basis.upper() not in polybases: raise RROMPyException("Polynomial basis not recognized.") + rootsbase = {"CHEBYSHEV" : po.chebyshev.chebroots, + "LEGENDRE" : po.legendre.legroots, + "MONOMIAL" : po.polynomial.polyroots}[basis.upper()] return rootsbase(polymarginalize(c, basis, marginalVals, 1)) diff --git a/rrompy/utilities/poly_fitting/polynomial/val.py b/rrompy/utilities/poly_fitting/polynomial/val.py index 0727be3..6bd85c6 100644 --- a/rrompy/utilities/poly_fitting/polynomial/val.py +++ b/rrompy/utilities/poly_fitting/polynomial/val.py @@ -1,43 +1,43 @@ # 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 .base import polybases from .der import polyder from rrompy.utilities.base.types import Np1D, Np2D, List, paramList from rrompy.parameter import checkParameterList from rrompy.utilities.exception_manager import RROMPyException __all__ = ['polyval'] def polyval(x:paramList, c:Np2D, basis:str, m : List[int] = None, scl : Np1D = None) -> Np2D: c = polyder(c, basis, m = m, scl = scl) - x = checkParameterList(x)[0] + x = checkParameterList(x) if x.shape[1] > c.ndim: raise RROMPyException("Incompatible parameter number.") - try: - polyvalbase = {"CHEBYSHEV" : np.polynomial.chebyshev.chebval, - "LEGENDRE" : np.polynomial.legendre.legval, - "MONOMIAL" : np.polynomial.polynomial.polyval - }[basis.upper()] - except: + if basis.upper() not in polybases: raise RROMPyException("Polynomial basis not recognized.") + polyvalbase = {"CHEBYSHEV" : np.polynomial.chebyshev.chebval, + "LEGENDRE" : np.polynomial.legendre.legval, + "MONOMIAL" : np.polynomial.polynomial.polyval + }[basis.upper()] c = polyvalbase(x(0), c, tensor = True) for d in range(1, x.shape[1]): c = polyvalbase(x(d), c, tensor = False) return c diff --git a/rrompy/utilities/poly_fitting/polynomial/vander.py b/rrompy/utilities/poly_fitting/polynomial/vander.py index 4b26d46..20aa591 100644 --- a/rrompy/utilities/poly_fitting/polynomial/vander.py +++ b/rrompy/utilities/poly_fitting/polynomial/vander.py @@ -1,138 +1,129 @@ # 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 .base import polybases from .der import polyder from rrompy.utilities.base.types import Np1D, Np2D, List, paramList from rrompy.utilities.numerical.degree import totalDegreeSet from rrompy.parameter import checkParameterList from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert -__all__ = ['polyvander', 'polyvanderTotal'] +__all__ = ['polyvander'] 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: + reorder : List[int] = None, scl : Np1D = None, + forceTotalDegree : bool = False) -> 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 = checkParameterList(x) + dim = x.shape[1] + totalDeg = (forceTotalDegree + or not isinstance(degs, (list, tuple, np.ndarray,))) + if forceTotalDegree and isinstance(degs, (list, tuple, np.ndarray,)): + if np.any(np.array(degs) != degs[0]): + raise RROMPyException(("Cannot force total degree if prescribed " + "degrees are different")) + degs = degs[0] + if not isinstance(degs, (list, tuple, np.ndarray,)): degs = [degs] * dim + RROMPyAssert(len(degs), dim, "Number of parameters") 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: + if basis.upper() not in polybases: raise RROMPyException("Polynomial basis not recognized.") + vanderbase = {"CHEBYSHEV" : np.polynomial.chebyshev.chebvander, + "LEGENDRE" : np.polynomial.legendre.legvander, + "MONOMIAL" : np.polynomial.polynomial.polyvander + }[basis.upper()] 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) + if not totalDeg: return Van + derIdxs, mask = totalDegreeSet(degs[0], dim, 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): + for j in range(degs[0] + 1): idxLocal = rangeAux[derTotal == j][::-1] idxPrev += len(idxLocal) ordIdxs[idxPrev - len(idxLocal) : idxPrev] = idxLocal - return VanBase[:, mask][:, ordIdxs] + return Van[:, mask][:, ordIdxs] diff --git a/rrompy/utilities/poly_fitting/radial_basis/__init__.py b/rrompy/utilities/poly_fitting/radial_basis/__init__.py index 98a445b..a275cfc 100644 --- a/rrompy/utilities/poly_fitting/radial_basis/__init__.py +++ b/rrompy/utilities/poly_fitting/radial_basis/__init__.py @@ -1,42 +1,37 @@ # 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 .kernel import (radialGaussian, thinPlateSpline, multiQuadric, - localWendland) +from .kernel import kernels from .base import rbbases, polybases, polyfitname, polydomcoeff from .val import polyval -from .vander import rbvander, polyvander, polyvanderTotal +from .vander import rbvander, polyvander from .radial_basis_interpolator import RadialBasisInterpolator __all__ = [ - 'radialGaussian', - 'thinPlateSpline', - 'multiQuadric', - 'localWendland', + 'kernels', 'rbbases', 'polybases', 'polyfitname', 'polydomcoeff', 'polyval', 'rbvander', 'polyvander', - 'polyvanderTotal', 'RadialBasisInterpolator' ] diff --git a/rrompy/utilities/poly_fitting/radial_basis/base.py b/rrompy/utilities/poly_fitting/radial_basis/base.py index 2423bf0..d43f306 100644 --- a/rrompy/utilities/poly_fitting/radial_basis/base.py +++ b/rrompy/utilities/poly_fitting/radial_basis/base.py @@ -1,43 +1,44 @@ # 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 itertools import product +from .kernel import kernels from rrompy.utilities.exception_manager import RROMPyException from rrompy.utilities.poly_fitting.polynomial.base import (polybases as pbP, polyfitname as pfnP, polydomcoeff as polydomcoeffB) __all__ = ['rbbases', 'polybases', 'polyfitname', 'polydomcoeff'] -rbbases = ["GAUSSIAN", "THINPLATE", "MULTIQUADRIC", "WENDLAND"] +rbbases = list(kernels.keys()) polybases = [x + "_" + y for x, y in product(pbP, rbbases)] def polyfitname(basis:str) -> str: - fitrnames = {"GAUSSIAN" : "gaussian", "THINPLATE" : "thinplate", - "MULTIQUADRIC" : "multiquadric", "WENDLAND" : "wendland"} - basisp, basisr = basis.split("_") try: - return pfnP(basisp) + "_" + fitrnames[basisr] + basisp, basisr = basis.split("_") + if basisr.upper() in rbbases: basisr = basisr.lower() + else: raise + return pfnP(basisp) + "_" + basisr except: raise RROMPyException("Polynomial-radial basis combination not " "recognized.") def polydomcoeff(n:int, basis:str) -> float: return polydomcoeffB(n, basis.split("_")[0]) diff --git a/rrompy/utilities/poly_fitting/radial_basis/kernel.py b/rrompy/utilities/poly_fitting/radial_basis/kernel.py index e349e58..2ebd45c 100644 --- a/rrompy/utilities/poly_fitting/radial_basis/kernel.py +++ b/rrompy/utilities/poly_fitting/radial_basis/kernel.py @@ -1,42 +1,51 @@ # 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.decorators import threshold from rrompy.utilities.base.types import Np1D from rrompy.utilities.exception_manager import RROMPyAssert -__all__ = ['radialGaussian', 'thinPlateSpline', 'multiQuadric', - 'localWendland'] +__all__ = ['kernels'] -def radialGaussian(r2:Np1D, der : int = 0) -> Np1D: +thresholdGaussian = -2. * np.log(np.finfo(float).eps) +@threshold(thresholdGaussian) +def radialGaussian(r2:Np1D, der : int = 0, + apply_threshold : bool = True) -> Np1D: RROMPyAssert(der, 0, "Radial basis derivative") - return np.exp(- .5 * r2) + if apply_threshold: r2[r2 > thresholdGaussian] = thresholdGaussian + return np.exp(-.5 * r2) -def thinPlateSpline(r2:Np1D, der : int = 0) -> Np1D: +thresholdMultiQuadric = np.finfo(float).eps ** -2. +@threshold(thresholdMultiQuadric) +def multiQuadric(r2:Np1D, der : int = 0, + apply_threshold : bool = True) -> Np1D: RROMPyAssert(der, 0, "Radial basis derivative") - return .5 * r2 * np.log(np.finfo(float).eps + r2) + if apply_threshold: r2[r2 > thresholdMultiQuadric] = thresholdMultiQuadric + return (r2 + 1.) ** -.5 -def multiQuadric(r2:Np1D, der : int = 0) -> Np1D: - RROMPyAssert(der, 0, "Radial basis derivative") - return np.power(r2 + 1., -.5) - -def localWendland(r2:Np1D, der : int = 0) -> Np1D: +@threshold(1.) +def localWendland(r2:Np1D, der : int = 0, + apply_threshold : bool = True) -> Np1D: RROMPyAssert(der, 0, "Radial basis derivative") + if apply_threshold: r2[r2 > 1.] = 1. rm1 = 1. - r2 ** .5 - rm1[rm1 <= 0.] = 0. return rm1 ** 4. * (5. - 4. * rm1) + +kernels = {"GAUSSIAN" : radialGaussian, "MULTIQUADRIC" : multiQuadric, + "WENDLAND" : localWendland} 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 3224b81..1fce87c 100644 --- a/rrompy/utilities/poly_fitting/radial_basis/radial_basis_interpolator.py +++ b/rrompy/utilities/poly_fitting/radial_basis/radial_basis_interpolator.py @@ -1,139 +1,133 @@ # 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 .vander import polyvander as pv 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 @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) def __copy__(self): return RadialBasisInterpolator(self) def __deepcopy__(self, memo): other = RadialBasisInterpolator() (other.support, other.coeffsGlobal, other.coeffsLocal, other.directionalWeights, other.npar, other.polybasis) = copy( (self.support, self.coeffsGlobal, self.coeffsLocal, self.directionalWeights, self.npar, self.polybasis), 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] + support = checkParameterList(support) RROMPyAssert(len(support), len(values), "Number of support values") self.support = copy(support) if "reorder" in vanderCoeffs.keys(): self.support = self.support[vanderCoeffs["reorder"]] self.npar = support.shape[1] - if directionalWeights is None: - directionalWeights = np.ones(self.npar) - self.directionalWeights = directionalWeights + if directionalWeights is None: directionalWeights = [1.] * self.npar + directionalWeights = np.array(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) + if not totalDegree and not hasattr(deg, "__len__"): + deg = [deg] * self.npar + vander, self.directionalWeights = pv(support, deg, basis = polybasis, + directionalWeights = 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)].reshape((len(support),) + outDim) 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/rrompy/utilities/poly_fitting/radial_basis/val.py b/rrompy/utilities/poly_fitting/radial_basis/val.py index f5c8405..5a913a9 100644 --- a/rrompy/utilities/poly_fitting/radial_basis/val.py +++ b/rrompy/utilities/poly_fitting/radial_basis/val.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 copy import deepcopy as copy import numpy as np -from .kernel import (radialGaussian, thinPlateSpline, multiQuadric, - localWendland) +from .kernel import kernels from rrompy.utilities.poly_fitting.polynomial.val import polyval as pvP from rrompy.utilities.base.types import Np1D, Np2D, paramList from rrompy.parameter import checkParameterList from rrompy.utilities.exception_manager import RROMPyException __all__ = ['polyval'] def polyval(x:paramList, cG:Np2D, cL:Np2D, supportPoints:paramList, directionalWeights:Np1D, basis:str) -> Np2D: - x = checkParameterList(x)[0] + x = checkParameterList(x, return_data = True) basisp, basisr = basis.split("_") c = pvP(x, cG, basisp) try: - radialvalbase = {"GAUSSIAN" : radialGaussian, - "THINPLATE" : thinPlateSpline, - "MULTIQUADRIC" : multiQuadric, - "WENDLAND" : localWendland}[basisr.upper()] + radialker = kernels[basisr.upper()] except: raise RROMPyException("Radial basis not recognized.") - supportPoints = checkParameterList(supportPoints)[0] + supportPoints = checkParameterList(supportPoints) csh = copy(c.shape) if len(csh) == 1: c = c.reshape(1, -1) - for j in range(len(x)): - muDiff = (supportPoints.data - x[j]) * directionalWeights - r2j = np.sum(np.abs(muDiff) ** 2., axis = 1).reshape(1, -1) - val = np.tensordot(radialvalbase(r2j), cL, 1) - try: - c[..., j] += val - except: - c[..., j] += val.flatten() + radialVal = np.zeros((len(supportPoints), len(x))) + xDiff2V, xDiff2I, xDiff2J = np.zeros(0), [], [] + for i in range(len(supportPoints)): + xiD2Loc = np.sum(np.abs((x - supportPoints[i]) + * directionalWeights) ** 2., axis = 1) + xiD2Good = np.where(xiD2Loc <= radialker.threshold)[0] + xDiff2V = np.append(xDiff2V, xiD2Loc[xiD2Good]) + xDiff2I += [i] * len(xiD2Good) + xDiff2J += list(xiD2Good) + radialVal[xDiff2I, xDiff2J] = radialker(xDiff2V, apply_threshold = False) + c += np.tensordot(cL, radialVal, (0, 0)) if len(csh) == 1: c = c.flatten() return c diff --git a/rrompy/utilities/poly_fitting/radial_basis/vander.py b/rrompy/utilities/poly_fitting/radial_basis/vander.py index 773a5b3..f4c6c2f 100644 --- a/rrompy/utilities/poly_fitting/radial_basis/vander.py +++ b/rrompy/utilities/poly_fitting/radial_basis/vander.py @@ -1,97 +1,95 @@ # 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 .kernel import (radialGaussian, thinPlateSpline, multiQuadric, - localWendland) -from rrompy.utilities.poly_fitting.polynomial.vander import (polyvander as pvP, - polyvanderTotal as pvTP) -from rrompy.utilities.base.types import Np1D, Np2D, List, paramList +from .kernel import kernels +from rrompy.utilities.poly_fitting.polynomial.vander import polyvander as pvP +from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List, paramList from rrompy.parameter import checkParameterList from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert -__all__ = ['rbvander', 'polyvander', 'polyvanderTotal'] +__all__ = ['rbvander', 'polyvander'] def rbvander(x:paramList, basis:str, reorder : List[int] = None, directionalWeights : Np1D = None) -> Np2D: """Compute radial-basis-Vandermonde matrix.""" - x = checkParameterList(x)[0] + x = checkParameterList(x) x_un = x.unique() nx = len(x) if len(x_un) < nx: raise RROMPyException("Sample points must be distinct.") del x_un x = x.data if directionalWeights is None: directionalWeights = np.ones(x.shape[1]) elif not hasattr(directionalWeights, "__len__"): directionalWeights = directionalWeights * np.ones(x.shape[1]) RROMPyAssert(len(directionalWeights), x.shape[1], "Number of directional weights") try: - radialkernel = {"GAUSSIAN" : radialGaussian, - "THINPLATE" : thinPlateSpline, - "MULTIQUADRIC" : multiQuadric, - "WENDLAND" : localWendland}[basis.upper()] + radialker = kernels[basis.upper()] except: raise RROMPyException("Radial basis not recognized.") Van = np.zeros((nx, nx)) if reorder is not None: x = x[reorder] - for j in range(nx): - muDiff = (x - x[j]) * directionalWeights - r2j = np.sum(np.abs(muDiff) ** 2., axis = 1).reshape(1, -1) - Van[j] = radialkernel(r2j) + xDiff2V, xDiff2I, xDiff2J = np.zeros(0), [], [] + for i in range(nx - 1): + xiD2Loc = np.sum(np.abs((x[i + 1 :] - x[i]) + * directionalWeights) ** 2., axis = 1) + xiD2Good = np.where(xiD2Loc <= radialker.threshold)[0] + xDiff2V = np.append(xDiff2V, xiD2Loc[xiD2Good]) + xDiff2I += [i] * len(xiD2Good) + xDiff2J += list(i + 1 + xiD2Good) + kernelV = radialker(xDiff2V, apply_threshold = False) + Van = np.eye(nx) + Van[xDiff2I, xDiff2J] = kernelV + Van[xDiff2J, xDiff2I] = kernelV return Van def polyvander(x:paramList, degs:List[int], basis:str, derIdxs : List[List[List[int]]] = None, reorder : List[int] = None, directionalWeights : Np1D = None, - scl : Np1D = None) -> Np2D: + scl : Np1D = None, forceTotalDegree : bool = False, + optimizeScalingBounds : List[float] = [-1., -1.], + maxOptimizeIter : int = 10) -> Tuple[Np2D, Np1D]: """ Compute full Hermite-Vandermonde matrix with specified derivative directions. """ if derIdxs is not None and np.sum(np.sum(derIdxs)) > 0: raise RROMPyException(("Cannot take derivatives of radial basis " "function.")) basisp, basisr = basis.split("_") - VanR = rbvander(x, basisr, reorder = reorder, - directionalWeights = directionalWeights) VanP = pvP(x, degs, basisp, derIdxs = derIdxs, reorder = reorder, - scl = scl) - return np.block([[VanR, VanP], - [VanP.T.conj(), np.zeros(tuple([VanP.shape[1]] * 2))]]) - -def polyvanderTotal(x:paramList, deg:int, basis:str, - derIdxs : List[List[List[int]]] = None, - reorder : List[int] = None, - directionalWeights : Np1D = None, - scl : Np1D = None) -> Np2D: - """ - Compute full total degree Hermite-Vandermonde matrix with specified - derivative directions. - """ - if derIdxs is not None and np.sum(np.sum(derIdxs)) > 0: - raise RROMPyException(("Cannot take derivatives of radial basis " - "function.")) - basisp, basisr = basis.split("_") - VanR = rbvander(x, basisr, reorder = reorder, - directionalWeights = directionalWeights) - VanP = pvTP(x, deg, basisp, derIdxs = derIdxs, reorder = reorder, scl = scl) - return np.block([[VanR, VanP], - [VanP.T.conj(), np.zeros(tuple([VanP.shape[1]] * 2))]]) + scl = scl, forceTotalDegree = forceTotalDegree) + VanZ = np.zeros([VanP.shape[1]] * 2) + optDir, optFact = 0, 100. + for it in range(maxOptimizeIter): + VanR = rbvander(x, basisr, reorder = reorder, + directionalWeights = directionalWeights) + Van = np.block([[VanR, VanP], [VanP.T.conj(), VanZ]]) + if optimizeScalingBounds[0] < 0. or it == maxOptimizeIter - 1: break + ncond = np.linalg.cond(Van) + if ncond < optimizeScalingBounds[0]: + if optDir != -1: optFact **= .5 + optDir, directionalWeights = -1, directionalWeights / optFact + elif ncond > optimizeScalingBounds[1]: + if optDir != 1: optFact **= .5 + optDir, directionalWeights = 1, directionalWeights * optFact + else: break + return Van, directionalWeights diff --git a/setup.cfg b/setup.cfg index a95dc41..9078760 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,10 @@ [aliases] test=pytest [tool:pytest] -addopts = --tb=short +addopts = --tb=short --runxfail python_files = tests/*/*.py filterwarnings = error ignore::DeprecationWarning ignore::FutureWarning diff --git a/setup.py b/setup.py index 968484a..cf88ffe 100644 --- a/setup.py +++ b/setup.py @@ -1,48 +1,48 @@ # 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 from setuptools import find_packages, setup rrompy_directory = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) setup(name="RROMPy", description="Rational reduced order modelling in Python", long_description="Rational reduced order modelling in Python", author="Davide Pradovera", author_email="davide.pradovera@epfl.ch", - version="2.6", + version="2.7", license="GNU Library or Lesser General Public License (LGPL)", classifiers=[ "Development Status :: 3 - Alpha" "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Software Development :: Libraries :: Python Modules", ], packages=find_packages(rrompy_directory), setup_requires=[ "pytest-runner" ], tests_require=[ "pytest" ], zip_safe=False ) diff --git a/tests/1_utilities/fenics_norms.py b/tests/1_utilities/fenics_norms.py index 61896d1..6eb91a2 100644 --- a/tests/1_utilities/fenics_norms.py +++ b/tests/1_utilities/fenics_norms.py @@ -1,92 +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 import fenics as fen from rrompy.solver.fenics import (L2NormMatrix, H1NormMatrix, Hminus1NormMatrix, elasticNormMatrix, elasticDualNormMatrix) +def test_fenics_loads(): + from rrompy.solver.fenics import fenZERO, fenZEROS, fenONE, fenONES + print(fenZERO, fenZEROS, fenONE, fenONES) + def test_fenics_L2(): V = fen.FunctionSpace(fen.UnitSquareMesh(3, 3), "P", 3) u = fen.interpolate(fen.Constant(3.), V) v = fen.interpolate(fen.Expression("x[0]*x[0]+x[1]", degree = 2), V) uv = u.vector()[:] vv = v.vector()[:] mass = L2NormMatrix(V) inner = fen.assemble(fen.dot(u, v) * fen.dx) assert np.isclose(uv.T.dot(mass.dot(vv)), 2.5, rtol = 1e-8) assert np.isclose(inner, 2.5, rtol = 1e-8) def test_fenics_H1(): V = fen.FunctionSpace(fen.UnitSquareMesh(3, 3), "P", 2) u = fen.interpolate(fen.Expression("x[0]+exp(x[1])", degree = 4), V) v = fen.interpolate(fen.Expression("x[0]*x[0]+x[1]", degree = 2), V) uv = u.vector()[:] vv = v.vector()[:] stiffness = H1NormMatrix(V) helmholtz = H1NormMatrix(V, 12) inners = fen.assemble(fen.dot(fen.grad(u), fen.grad(v)) * fen.dx) innerh = 12 * fen.assemble(fen.dot(u, v) * fen.dx) assert np.isclose(uv.T.dot(stiffness.dot(vv)), np.exp(1), rtol = 1e-6) assert np.isclose(inners, np.exp(1), rtol = 1e-6) assert np.isclose(uv.T.dot(helmholtz.dot(vv)), 5 * np.exp(1) + 14, rtol = 1e-3) assert np.isclose(inners + innerh, 5 * np.exp(1) + 14, rtol = 1e-3) def test_fenics_Hminus1(): V = fen.FunctionSpace(fen.UnitSquareMesh(3, 3), "P", 2) u = fen.interpolate(fen.Expression("x[0]+exp(x[1])", degree = 4), V) v = fen.interpolate(fen.Expression("x[0]*x[0]+x[1]", degree = 2), V) uv = u.vector()[:] vv = v.vector()[:] energyFull = Hminus1NormMatrix(V, 12) energyLR = Hminus1NormMatrix(V, 12, compressRank = 20) assert np.isclose(uv.T.dot(energyFull.dot(vv)), uv.T.dot(energyLR.dot(vv)), rtol = 1e-2) assert np.isclose(uv.T.dot(energyFull.dot(vv)), 457.9913548, rtol = 1e-2) def test_fenics_elastic(): V = fen.VectorFunctionSpace(fen.UnitCubeMesh(5, 5, 5), "P", 1) l_ = 1. m_ = fen.Expression(".5*x[0]+1.", degree = 1) u = fen.interpolate(fen.Expression(("exp(x[1])", "x[0]-x[2]", "3."), degree = 4), V) v = fen.interpolate(fen.Expression(("x[0]*x[0]+x[2]", "1.", "-1. * x[1]"), degree = 2), V) uv = u.vector()[:] vv = v.vector()[:] energyMat = elasticNormMatrix(V, l_, m_, 10) epsilon = lambda f: 0.5 * (fen.grad(f) + fen.nabla_grad(f)) sigma = (l_ * fen.div(u) * fen.Identity(3) + 2. * m_ * epsilon(u)) energy = fen.assemble((10 * fen.dot(u, v) + fen.inner(sigma, epsilon(v))) * fen.dx) assert np.isclose(uv.T.dot(energyMat.dot(vv)), energy, rtol = 1e-8) def test_fenics_elastic_dual(): V = fen.VectorFunctionSpace(fen.UnitCubeMesh(5, 5, 5), "P", 1) l_ = 1. m_ = fen.Expression(".5*x[0]+1.", degree = 1) u = fen.interpolate(fen.Expression(("exp(x[1])", "x[0]-x[2]", "3."), degree = 4), V) v = fen.interpolate(fen.Expression(("x[0]*x[0]+x[2]", "1.", "-1. * x[1]"), degree = 2), V) uv = u.vector()[:] vv = v.vector()[:] energyFull = elasticDualNormMatrix(V, l_, m_, 10) assert np.isclose(uv.T.dot(energyFull.dot(vv)), -134.6099529, rtol = 1e-3) diff --git a/tests/1_utilities/nearest_neighbor_fitting.py b/tests/1_utilities/nearest_neighbor_fitting.py index 695e953..2c74fa9 100644 --- a/tests/1_utilities/nearest_neighbor_fitting.py +++ b/tests/1_utilities/nearest_neighbor_fitting.py @@ -1,63 +1,63 @@ # 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.nearest_neighbor import polyval from rrompy.parameter import checkParameterList def test_nearest_neighbor_1d(): directionalWeights = [1.] nNeighbors = 1 - xSupp = checkParameterList(np.arange(-1, 3), 1)[0] + xSupp = checkParameterList(np.arange(-1, 3), 1) ySupp = np.array([-1., 3., -3., 1.]) xx = np.linspace(-2., 3., 100) - yy = polyval(checkParameterList(xx, 1)[0], ySupp, xSupp, nNeighbors, + yy = polyval(checkParameterList(xx, 1), ySupp, xSupp, nNeighbors, directionalWeights) nn = [np.where(xx < -.5), np.where(np.logical_and(xx > -.5, xx < .5)), np.where(np.logical_and(xx > .5, xx < 1.5)), np.where(xx > 1.5)] for j in range(4): assert np.allclose(yy[nn[j]], ySupp[j], atol = 1e-5) def test_nearest_neighbor_exact_1d(): directionalWeights = [1.] nNeighbors = 2 - xSupp = checkParameterList(np.arange(3), 1)[0] + xSupp = checkParameterList(np.arange(3), 1) ySupp = np.array([-1., 3., -3.]) xx = np.linspace(0., 1., 100) - yy = polyval(checkParameterList(xx, 1)[0], ySupp, xSupp, nNeighbors, + yy = polyval(checkParameterList(xx, 1), ySupp, xSupp, nNeighbors, directionalWeights) assert np.allclose(yy, -1. + 4 * xx, atol = 1e-5) xxL = np.linspace(-10., -.5, 100) - yyL = polyval(checkParameterList(xxL, 1)[0], ySupp, xSupp, nNeighbors, + yyL = polyval(checkParameterList(xxL, 1), ySupp, xSupp, nNeighbors, directionalWeights) xxR = np.linspace(1.5, 9., 100) - yyR = polyval(checkParameterList(xxR, 1)[0], ySupp, xSupp, nNeighbors, + yyR = polyval(checkParameterList(xxR, 1), ySupp, xSupp, nNeighbors, directionalWeights) assert np.all(yyL > -1. + 4 * xxL) and np.all(yyR < -1. + 4 * xxR) def test_nearest_neighbors_bounded_2d(): directionalWeights = [5., 1.] nNeighbors = 3 - xSupp = checkParameterList([[0., 0.], [1., 0.], [0., 1.], [0., 10.]], 2)[0] + xSupp = checkParameterList([[0., 0.], [1., 0.], [0., 1.], [0., 10.]], 2) ySupp = np.array([1., 2., -1., 1e5]) x1 = np.tile(np.linspace(0., 1., 100), 100) x2 = np.repeat(np.linspace(0., 1., 100), 100) xx = np.hstack((x1.reshape(-1, 1), x2.reshape(-1, 1))) - yy = polyval(checkParameterList(xx, 2)[0], ySupp, xSupp, nNeighbors, + yy = polyval(checkParameterList(xx, 2), ySupp, xSupp, nNeighbors, directionalWeights) assert np.all(yy <= 2.) and np.all(yy >= -1.) diff --git a/tests/1_utilities/parameter_sampling.py b/tests/1_utilities/parameter_sampling.py index cdc3009..75b7e55 100644 --- a/tests/1_utilities/parameter_sampling.py +++ b/tests/1_utilities/parameter_sampling.py @@ -1,59 +1,59 @@ # 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.parameter.parameter_sampling import (ManualSampler, QuadratureSampler, RandomSampler, FFTSampler) -from rrompy.parameter import checkParameter +from rrompy.parameter import checkParameter, parameterMap as pMap def test_manual(): sampler = ManualSampler(lims = [0., 3.], points = np.linspace(0, 3, 101), - scalingExp = 2.) + parameterMap = pMap(2.)) assert sampler.name() == "ManualSampler" x = sampler.generatePoints(10) assert np.allclose(x(0), np.linspace(0, 3, 101)[:10], rtol = 1e-5) def test_quadrature(): sampler = QuadratureSampler(lims = [0., 3.], kind = "CHEBYSHEV") x = sampler.generatePoints(9, reorder = False) assert np.isclose(x(0)[4], 1.5, rtol = 1e-5) def test_random(): sampler = RandomSampler(lims = [0., 3.], kind = "SOBOL", seed = 13432) x = sampler.generatePoints(100) assert np.isclose(x(0)[47], 0.55609130859375, rtol = 1e-5) def test_fft(): sampler = FFTSampler(lims = [-1., 1.]) x = sampler.generatePoints(100) assert np.allclose(np.power(x(0), 100), 1., rtol = 1e-5) def test_2D(): sampler = QuadratureSampler(lims = [(0., 0.), (3., 1.)], kind = "GAUSSLEGENDRE") x = sampler.generatePoints(81) assert sum(np.isclose(x(0), 1.5)) == 9 assert sum(np.isclose(x(1), .5)) == 9 def test_4D(): sampler = RandomSampler(lims = [(0.,) * 4, (1.,) * 4], kind = "UNIFORM", seed = 1234) x = sampler.generatePoints(10) assert x.shape[1] == 4 assert checkParameter([x[0]]) == checkParameter([(0.191519450378892, 0.622108771039832, 0.437727739007115, 0.785358583713769)]) diff --git a/tests/1_utilities/poly_fitting.py b/tests/1_utilities/poly_fitting.py index 9dfb196..faefeaa 100644 --- a/tests/1_utilities/poly_fitting.py +++ b/tests/1_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.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]]) + 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/1_utilities/radial_fitting.py b/tests/1_utilities/radial_fitting.py index 4dedfaf..5e8f0bf 100644 --- a/tests/1_utilities/radial_fitting.py +++ b/tests/1_utilities/radial_fitting.py @@ -1,165 +1,129 @@ # 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, +from rrompy.utilities.poly_fitting.radial_basis import (kernels, polybases, polyfitname, polydomcoeff, - polyval, polyvander, - polyvanderTotal) + polyval, polyvander) 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) + radialGaussian = kernels["GAUSSIAN"] directionalWeights = np.array([5.]) - xSupp = checkParameterList(np.arange(-1, 3), 1)[0] + xSupp = checkParameterList(np.arange(-1, 3), 1) 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, + yy = polyval(checkParameterList(xx, 1), 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) + directionalWeights = directionalWeights)[0] 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) + multiQuadric = kernels["MULTIQUADRIC"] directionalWeights = np.array([1.]) - xSupp = checkParameterList(np.arange(-1, 3), 1)[0] + xSupp = checkParameterList(np.arange(-1, 3), 1) 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, + yy = polyval(checkParameterList(xx, 1), 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) + directionalWeights = directionalWeights)[0] 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) + VanT = polyvander(xySupp, deg, polyrbname, + directionalWeights = directionalWeights)[0] 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 diff --git a/tests/1_utilities/basic_routines.py b/tests/1_utilities/routines_base.py similarity index 99% rename from tests/1_utilities/basic_routines.py rename to tests/1_utilities/routines_base.py index 86ab5f5..48047b1 100644 --- a/tests/1_utilities/basic_routines.py +++ b/tests/1_utilities/routines_base.py @@ -1,68 +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.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/1_utilities/sampling.py b/tests/1_utilities/sampling.py index c9ce11b..b63774c 100644 --- a/tests/1_utilities/sampling.py +++ b/tests/1_utilities/sampling.py @@ -1,65 +1,60 @@ # 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 import scipy.sparse as sp -from rrompy.hfengines.base import LinearAffineEngine, NumpyEngineBase +from rrompy.hfengines.scipy_engines import EigenproblemEngine from rrompy.sampling import SamplingEngineStandard, SamplingEngineStandardPOD from rrompy.parameter import parameterList -class matrixEngine(LinearAffineEngine, NumpyEngineBase): +class matrixEngine(EigenproblemEngine): def __init__(self): - super().__init__(verbosity = 0) - self._affinePoly = True N = 100 - self.npar = 1 - self.nAs, self.nbs = 2, 1 - self.setAs([sp.spdiags([np.arange(1, 1 + N)], [0], N, N), - - sp.eye(N)]) - self.setbs([np.exp(1.j * np.linspace(0, -np.pi, N))]) - self.setthAs(self.getMonomialWeights(self.nAs)) - self.setthbs(self.getMonomialWeights(self.nbs)) + A = sp.spdiags([np.arange(1, 1 + N)], [0], N, N) + B = - sp.eye(N) + f = np.exp(1.j * np.linspace(0, -np.pi, N)) + super().__init__([A, B], f, verbosity = 0) def test_krylov(): mu = 10. + .5j solver = matrixEngine() samplingEngine = SamplingEngineStandard(solver, verbosity = 0) samples = samplingEngine.iterSample([mu] * 5).data assert samples.shape == (100, 5) assert np.isclose(np.linalg.norm(samples), 37.02294804524299, rtol = 1e-5) def test_distributed(): mus = parameterList(np.linspace(5, 15, 11) + .5j) solver = matrixEngine() samplingEngine = SamplingEngineStandard(solver, verbosity = 0) samples = samplingEngine.iterSample(mus).data assert samples.shape == (100, 11) assert np.isclose(np.linalg.norm(samples), 8.59778606421386, rtol = 1e-5) def test_distributed_pod(): mus = np.linspace(5, 15, 11) + .5j solver = matrixEngine() samplingEngine = SamplingEngineStandardPOD(solver, verbosity = 0) - samples_nonortho = samplingEngine.iterSample(mus).data + samplingEngine.iterSample(mus).data samples = samplingEngine.projectionMatrix assert samples.shape == (100, 11) assert np.isclose(np.linalg.norm(samples), 3.3166247903553994, rtol = 1e-5) assert np.isclose(np.linalg.cond(samples.conj().T.dot(samples)), 1., rtol = 1e-5) diff --git a/tests/2_hfengines/helmholtz_box_scattering_problem_engine.py b/tests/2_hfengines/helmholtz_box_scattering_problem_engine.py index db36141..0365f84 100644 --- a/tests/2_hfengines/helmholtz_box_scattering_problem_engine.py +++ b/tests/2_hfengines/helmholtz_box_scattering_problem_engine.py @@ -1,53 +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 . # import numpy as np import fenics as fen -from rrompy.hfengines.linear_problem import ScatteringProblemEngine +from rrompy.hfengines.fenics_engines import ScatteringProblemEngine class HelmholtzBoxScatteringProblemEngine(ScatteringProblemEngine): """ Solver for scattering problem outside a box with parametric wavenumber. - \Delta u - omega^2 * n^2 * u = 0 in \Omega u = 0 on \Gamma_D \partial_nu - i k u = 0 on \Gamma_R with exact solution a transmitted plane wave. """ def __init__(self, R:float, kappa:float, theta:float, n:int, *args, **kwargs): super().__init__(kappa, *args, **kwargs) import mshr scatterer = mshr.Polygon([fen.Point(-1, -.5), fen.Point(1, -.5), fen.Point(1, .5), fen.Point(.8, .5), fen.Point(.8, -.3), fen.Point(-.8, -.3), fen.Point(-.8, .5), fen.Point(-1, .5),]) mesh = mshr.generate_mesh(mshr.Circle(fen.Point(0, 0), R) - scatterer, 3 * n) self.V = fen.FunctionSpace(mesh, "P", 1) self.DirichletBoundary = (lambda x, on_boundary: on_boundary and (x[0]**2+x[1]**2)**.5 < .95 * R) self.RobinBoundary = "REST" c, s = np.cos(theta), np.sin(theta) x, y = fen.SpatialCoordinate(mesh)[:] u0R = - fen.cos(kappa * (c * x + s * y)) u0I = - fen.sin(kappa * (c * x + s * y)) self.DirichletDatum = [u0R, u0I] diff --git a/tests/2_hfengines/helmholtz_cavity_scattering_problem_engine.py b/tests/2_hfengines/helmholtz_cavity_scattering_problem_engine.py index 8e35bda..f648fc4 100644 --- a/tests/2_hfengines/helmholtz_cavity_scattering_problem_engine.py +++ b/tests/2_hfengines/helmholtz_cavity_scattering_problem_engine.py @@ -1,53 +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 . # import numpy as np import fenics as fen -from rrompy.hfengines.linear_problem import ScatteringProblemEngine +from rrompy.hfengines.fenics_engines import ScatteringProblemEngine class HelmholtzCavityScatteringProblemEngine(ScatteringProblemEngine): """ Solver for scattering problem inside a cavity with parametric wavenumber. - \Delta u - omega^2 * n^2 * u = 0 in \Omega u = 0 on \Gamma_D \partial_nu - i k u = 0 on \Gamma_R with exact solution a transmitted plane wave. """ def __init__(self, kappa:float, n:int, gamma : float = 0., signR : int = -1, *args, **kwargs): super().__init__(kappa, *args, **kwargs) self.signR = signR pi = np.pi mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), 3 * n, 3 * n) self.V = fen.FunctionSpace(mesh, "P", 1) self.RobinBoundary = (lambda x, on_boundary: on_boundary and np.isclose(x[1], np.pi)) self.DirichletBoundary = "REST" x, y = fen.SpatialCoordinate(mesh)[:] C = 4. / pi ** 4. bR = C * (2 * (x * (pi - x) + y * (2 * pi - y)) + (kappa * gamma) ** 2. * x * (pi - x) * y * (2 * pi - y)) bI = C * signR * 2 * kappa * (gamma * (pi - 2 * x) * y * (pi - y) + 2 * x * (pi - x) * (pi - y)) wR = fen.cos(kappa * signR * (gamma * x + y)) wI = fen.sin(kappa * signR * (gamma * x + y)) self.forcingTerm = [bR * wR + bI * wI, bI * wR - bR * wI] diff --git a/tests/2_hfengines/helmholtz_elasticity.py b/tests/2_hfengines/helmholtz_elasticity.py index 8d1a819..8fd4f76 100644 --- a/tests/2_hfengines/helmholtz_elasticity.py +++ b/tests/2_hfengines/helmholtz_elasticity.py @@ -1,55 +1,55 @@ # 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.hfengines.vector_linear_problem import ( +from rrompy.hfengines.fenics_engines import ( LinearElasticityHelmholtzProblemEngine, LinearElasticityHelmholtzProblemEngineDamped) from rod_3d import rod3Dsolver def test_helmholtz_elastic_rod(): solverBase = rod3Dsolver() solver = LinearElasticityHelmholtzProblemEngine() solver.V = solverBase.V solver.lambda_ = solverBase.lambda_ solver.mu_ = solverBase.mu_ solver.forcingTerm = solverBase.forcingTerm solver.DirichletBoundary = solverBase.DirichletBoundary solver.NeumannBoundary = solverBase.NeumannBoundary mu = 10 uh = solver.solve(mu)[0] assert np.isclose(solver.norm(uh), 0.17836028624665373, rtol = 1e-5) assert np.isclose(solver.norm(solver.residual(mu, uh)[0], dual = True), 8.070977e-07, rtol = 1e-1) def test_helmholtz_elastic_rod_damped(): solverBase = rod3Dsolver() solver = LinearElasticityHelmholtzProblemEngineDamped() solver.V = solverBase.V solver.lambda_ = solverBase.lambda_ solver.mu_ = solverBase.mu_ solver.forcingTerm = solverBase.forcingTerm solver.DirichletBoundary = solverBase.DirichletBoundary solver.NeumannBoundary = solverBase.NeumannBoundary solver.eta = 10 mu = 10 uh = solver.solve(mu)[0] assert np.isclose(solver.norm(uh), 0.17646530119044376, rtol = 1e-2) assert np.isclose(solver.norm(solver.residual(10, uh)[0], dual = True), 6.7057338e-07, rtol = 1e-1) diff --git a/tests/2_hfengines/helmholtz_square_bubble_domain_problem_engine.py b/tests/2_hfengines/helmholtz_square_bubble_domain_problem_engine.py index c5a0c9f..a732882 100644 --- a/tests/2_hfengines/helmholtz_square_bubble_domain_problem_engine.py +++ b/tests/2_hfengines/helmholtz_square_bubble_domain_problem_engine.py @@ -1,129 +1,130 @@ # 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 numpy.polynomial.polynomial import polyfit as fit import fenics as fen from rrompy.utilities.base.types import paramVal from rrompy.solver.fenics import fenZERO -from rrompy.hfengines.linear_problem import HelmholtzProblemEngine +from rrompy.hfengines.fenics_engines import HelmholtzProblemEngine from rrompy.utilities.base import verbosityManager as vbMng from rrompy.solver.fenics import fenics2Sparse, fenics2Vector +from rrompy.parameter import parameterMap as pMap class HelmholtzSquareBubbleDomainProblemEngine(HelmholtzProblemEngine): """ Solver for square bubble Helmholtz problems with parametric domain heigth. - \Delta u - kappa^2 * u = f in \Omega_mu = [0,\pi] x [0,\mu\pi] u = 0 on \Gamma_mu = \partial\Omega_mu with exact solution square bubble times plane wave. """ def __init__(self, kappa:float, theta:float, n:int, mu0 : paramVal = [1.], *args, **kwargs): super().__init__(mu0, *args, **kwargs) self._affinePoly = False self.nAs, self.nbs = 2, 15 self.kappa = kappa self.theta = theta mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(np.pi, np.pi), 3 * n, 3 * n) self.V = fen.FunctionSpace(mesh, "P", 1) - self.rescalingExp = [1.] + self.parameterMap = pMap(1.) def buildA(self): """Build terms of operator of linear system.""" if self.As[0] is None: vbMng(self, "INIT", "Assembling operator term A0.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a0Re = fen.dot(self.u.dx(1), self.v.dx(1)) * fen.dx self.As[0] = fenics2Sparse(a0Re, {}, DirichletBC0, 1) self.thAs[0] = self.getMonomialSingleWeight([0]) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[1] is None: vbMng(self, "INIT", "Assembling operator term A2.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) nRe, nIm = self.refractionIndex n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm k2Re, k2Im = np.real(self.omega ** 2), np.imag(self.omega ** 2) k2n2Re = k2Re * n2Re - k2Im * n2Im k2n2Im = k2Re * n2Im + k2Im * n2Re parsRe = self.iterReduceQuadratureDegree(zip([k2n2Re], ["kappaSquaredRefractionIndexSquaredReal"])) parsIm = self.iterReduceQuadratureDegree(zip([k2n2Im], ["kappaSquaredRefractionIndexSquaredImag"])) a2Re = (fen.dot(self.u.dx(0), self.v.dx(0)) - k2n2Re * fen.dot(self.u, self.v)) * fen.dx a2Im = - k2n2Im * fen.dot(self.u, self.v) * fen.dx self.As[1] = (fenics2Sparse(a2Re, parsRe, DirichletBC0, 0) + 1.j * fenics2Sparse(a2Im, parsIm, DirichletBC0, 0)) self.thAs[1] = self.getMonomialSingleWeight([2]) vbMng(self, "DEL", "Done assembling operator term.", 20) def buildb(self): """Build terms of operator of linear system.""" if self.thbs[0] is None: self.thbs = self.getMonomialWeights(self.nbs) bDEIMCoeffs = None for j in range(self.nbs): if self.bs[j] is None: vbMng(self, "INIT", "Assembling forcing term b{}.".format(j), 20) if bDEIMCoeffs is None: muDEIM = np.linspace(.5, 4., self.nbs) for jj, muD in enumerate(muDEIM): pi = np.pi c, s = np.cos(self.theta), np.sin(self.theta) x, y = fen.SpatialCoordinate(self.V.mesh())[:] muR, muI = np.real(muD), np.imag(muD) mu2R, mu2I = np.real(muD ** 2.), np.imag(muD ** 2.) C = 16. / pi ** 4. bR = C * (2 * (x * (pi - x) + y * (pi - y)) + (self.kappa * s) ** 2. * (mu2R - 1.) * x * (pi - x) * y * (pi - y)) bI = C * (2 * self.kappa * (c * (pi - 2 * x) * y * (pi - y) + s * x * (pi - x) * (pi - 2 * y)) + (self.kappa * s) ** 2. * mu2I * x * (pi - x) * y * (pi - y)) wR = (fen.cos(self.kappa * (c * x + s * muR * y)) * fen.exp(self.kappa * s * muI * y)) wI = (fen.sin(self.kappa * (c * x + s * muR * y)) * fen.exp(self.kappa * s * muI * y)) fRe, fIm = bR * wR + bI * wI, bI * wR - bR * wI fRe = mu2R * fRe - mu2I * fIm + fenZERO fIm = mu2R * fIm + mu2I * fRe + fenZERO parsRe = self.iterReduceQuadratureDegree(zip([fRe], ["forcingTerm{}Real".format(jj)])) parsIm = self.iterReduceQuadratureDegree(zip([fIm], ["forcingTerm{}Imag".format(jj)])) LR = fen.dot(fRe, self.v) * fen.dx LI = fen.dot(fIm, self.v) * fen.dx DBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) bjj = (fenics2Vector(LR, parsRe, DBC0, 1) + 1.j * fenics2Vector(LI, parsIm, DBC0, 1)) if jj == 0: bDEIM = np.empty((self.nbs, len(bjj)), dtype = np.complex) bDEIM[jj] = bjj bDEIMCoeffs = fit(muDEIM, bDEIM, self.nbs - 1) self.bs[j] = bDEIMCoeffs[j] vbMng(self, "DEL", "Done assembling forcing term.", 20) diff --git a/tests/2_hfengines/helmholtz_square_bubble_problem_engine.py b/tests/2_hfengines/helmholtz_square_bubble_problem_engine.py index 361e213..c4cefbe 100644 --- a/tests/2_hfengines/helmholtz_square_bubble_problem_engine.py +++ b/tests/2_hfengines/helmholtz_square_bubble_problem_engine.py @@ -1,46 +1,46 @@ # 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 import fenics as fen -from rrompy.hfengines.linear_problem import HelmholtzProblemEngine +from rrompy.hfengines.fenics_engines import HelmholtzProblemEngine class HelmholtzSquareBubbleProblemEngine(HelmholtzProblemEngine): """ Solver for square bubble Helmholtz problems with parametric wavenumber. - \Delta u - omega^2 * u = f in \Omega u = 0 on \Gamma_D with exact solution square bubble times plane wave. """ def __init__(self, kappa:float, theta:float, n:int, *args, **kwargs): super().__init__(kappa, *args, **kwargs) pi = np.pi mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), 3 * n, 3 * n) self.V = fen.FunctionSpace(mesh, "P", 1) c, s = np.cos(theta), np.sin(theta) x, y = fen.SpatialCoordinate(mesh)[:] C = 16. / pi ** 4. bR = C * 2 * (x * (pi - x) + y * (pi - y)) bI = C * 2 * kappa * (c * (pi - 2 * x) * y * (pi - y) + s * x * (pi - x) * (pi - 2 * y)) wR = fen.cos(kappa * (c * x + s * y)) wI = fen.sin(kappa * (c * x + s * y)) self.forcingTerm = [bR * wR + bI * wI, bI * wR - bR * wI] diff --git a/tests/2_hfengines/helmholtz_square_transmission_problem_engine.py b/tests/2_hfengines/helmholtz_square_transmission_problem_engine.py index 8b821a8..b473bb3 100644 --- a/tests/2_hfengines/helmholtz_square_transmission_problem_engine.py +++ b/tests/2_hfengines/helmholtz_square_transmission_problem_engine.py @@ -1,68 +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 numpy as np import fenics as fen import ufl -from rrompy.hfengines.linear_problem import HelmholtzProblemEngine +from rrompy.hfengines.fenics_engines import HelmholtzProblemEngine class HelmholtzSquareTransmissionProblemEngine(HelmholtzProblemEngine): """ Solver for square transmission Helmholtz problems with parametric wavenumber. - \Delta u - omega^2 * n^2 * u = 0 in \Omega u = 0 on \Gamma_D with exact solution a transmitted plane wave. """ def __init__(self, nT:float, nB:float, kappa:float, theta:float, n:int, *args, **kwargs): super().__init__(kappa, *args, **kwargs) mesh = fen.RectangleMesh(fen.Point(-np.pi/2, -np.pi/2), fen.Point(np.pi/2, np.pi/2), 3 * n, 3 * n) self.V = fen.FunctionSpace(mesh, "P", 1) dx, dy = np.cos(theta), np.sin(theta) Kx = kappa * nB * dx Ky = kappa * (nT**2. - (nB * dx)**2. + 0.j)**.5 T = 2 * kappa * nB * dy / (Ky + kappa * nB * dy) x, y = fen.SpatialCoordinate(mesh)[:] TR, TI = np.real(T), np.imag(T) if np.isclose(np.imag(Ky), 0.): u0RT = (TR * fen.cos(Kx * x + np.real(Ky) * y) - TI * fen.sin(Kx * x + np.real(Ky) * y)) u0IT = (TR * fen.sin(Kx * x + np.real(Ky) * y) + TI * fen.cos(Kx * x + np.real(Ky) * y)) else: u0RT = fen.exp(- np.imag(Ky) * y) * (TR * fen.cos(Kx * x) - TI * fen.sin(Kx * x)) u0IT = fen.exp(- np.imag(Ky) * y) * (TR * fen.sin(Kx * x) + TI * fen.cos(Kx * x)) u0RB = (fen.cos(kappa * nB * (dx * x + dy * y)) + (TR - 1) * fen.cos(kappa * nB * (dx*x - dy*y)) - TI * fen.sin(kappa * nB * (dx*x - dy*y))) u0IB = (fen.sin(kappa * nB * (dx * x + dy * y)) + (TR - 1) * fen.sin(kappa * nB * (dx*x - dy*y)) + TI * fen.cos(kappa * nB * (dx*x - dy*y))) u0R = ufl.conditional(ufl.ge(y, 0.), u0RT, u0RB) u0I = ufl.conditional(ufl.ge(y, 0.), u0IT, u0IB) self.refractionIndex = ufl.conditional(ufl.ge(y, 0.), fen.Constant(nT), fen.Constant(nB)) self.DirichletDatum = [u0R, u0I] diff --git a/tests/2_hfengines/laplace_disk_gaussian.py b/tests/2_hfengines/laplace_disk_gaussian.py index 95c9055..7c7a6f1 100644 --- a/tests/2_hfengines/laplace_disk_gaussian.py +++ b/tests/2_hfengines/laplace_disk_gaussian.py @@ -1,150 +1,150 @@ # 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 numpy.polynomial.polynomial import polyfit as fit import fenics as fen from rrompy.utilities.base.types import paramVal -from rrompy.hfengines.linear_problem import LaplaceBaseProblemEngine +from rrompy.hfengines.fenics_engines import LaplaceBaseProblemEngine from rrompy.solver.fenics import fenZERO from rrompy.utilities.base import verbosityManager as vbMng from rrompy.utilities.poly_fitting.polynomial import ( PolynomialInterpolator as PI) from rrompy.solver.fenics import fenics2Vector class LaplaceDiskGaussian(LaplaceBaseProblemEngine): """ Solver for disk Laplace problems with parametric forcing term center. - \Delta u = C exp(-.5 * ||\cdot - (mu, 0)||^2) in \Omega = B(0, 5) u = 0 on \partial\Omega. """ def __init__(self, n:int, mu0 : paramVal = [0.], *args, **kwargs): super().__init__(mu0, *args, **kwargs) self.nbs = 19 import mshr mesh = mshr.generate_mesh(mshr.Circle(fen.Point(0., 0.), 5.), 3 * n) self.V = fen.FunctionSpace(mesh, "P", 1) def buildb(self): """Build terms of operator of linear system.""" if self.thbs[0] is None: self.thbs = self.getMonomialWeights(self.nbs) bDEIMCoeffs = None for j in range(self.nbs): if self.bs[j] is None: vbMng(self, "INIT", "Assembling forcing term b{}.".format(j), 20) if bDEIMCoeffs is None: muDEIM = 3. * np.linspace(0., 1., self.nbs // 2 + 1) ** 2. muDEIM = np.concatenate((-muDEIM[:0:-1], muDEIM)) for jj, muD in enumerate(muDEIM): x, y = fen.SpatialCoordinate(self.V.mesh())[:] C = np.exp(-.5 * muD ** 2.) CR, CI = np.real(C), np.imag(C) f0 = ((2 * np.pi) ** -.5 * fen.exp(-.5 * (x ** 2. + y ** 2.))) muR, muI = np.real(muD), np.imag(muD) f1R = fen.exp(muR * x) * fen.cos(muI * x) f1I = fen.exp(muR * x) * fen.sin(muI * x) fRe = f0 * (CR * f1R - CI * f1I) + fenZERO fIm = f0 * (CR * f1I + CI * f1R) + fenZERO parsRe = self.iterReduceQuadratureDegree(zip([fRe], ["forcingTerm{}Real".format(jj)])) parsIm = self.iterReduceQuadratureDegree(zip([fIm], ["forcingTerm{}Imag".format(jj)])) LR = fen.dot(fRe, self.v) * fen.dx LI = fen.dot(fIm, self.v) * fen.dx DBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) bjj = (fenics2Vector(LR, parsRe, DBC0, 1) + 1.j * fenics2Vector(LI, parsIm, DBC0, 1)) if jj == 0: bDEIM = np.empty((self.nbs, len(bjj)), dtype = np.complex) bDEIM[jj] = bjj bDEIMCoeffs = (fit(muDEIM / 3., bDEIM, self.nbs - 1).T * np.power(3., - np.arange(self.nbs))).T self.bs[j] = bDEIMCoeffs[j] vbMng(self, "DEL", "Done assembling forcing term.", 20) class LaplaceDiskGaussian2(LaplaceDiskGaussian): """ Solver for disk Laplace problems with parametric forcing term center. - \Delta u = C exp(-.5 * ||\cdot - (mu1, mu2)||^2) in \Omega = B(0, 5) u = 0 on \partial\Omega. """ def __init__(self, n:int, mu0 : paramVal = [0., 0.], *args, **kwargs): super().__init__(n = n, mu0 = mu0, *args, **kwargs) self.nbs = 16 self.npar = 2 def buildb(self): """Build terms of operator of linear system.""" if self.thbs[0] is None: self.thbs = [None] * self.nbs bDEIMCoeffs = None for j in range(self.nbs): j1, j2 = j % int(self.nbs ** .5), j // int(self.nbs ** .5) if self.bs[j] is None: vbMng(self, "INIT", "Assembling forcing term b{}.".format(j), 20) if bDEIMCoeffs is None: muD1 = np.linspace(-2., 2., int(self.nbs ** .5)) muDEIM = np.empty((self.nbs, 2)) muDEIM[:, 0] = np.repeat(muD1, int(self.nbs ** .5)) muDEIM[:, 1] = np.tile(muD1, int(self.nbs ** .5)) for jj, muD in enumerate(muDEIM): x, y = fen.SpatialCoordinate(self.V.mesh())[:] C = np.exp(-.5 * (muD[0] ** 2. + muD[1] ** 2.)) CR, CI = np.real(C), np.imag(C) f0 = ((2 * np.pi) ** -.5 * fen.exp(-.5 * (x ** 2. + y ** 2.))) muxR, muxI = np.real(muD[0]), np.imag(muD[0]) muyR, muyI = np.real(muD[1]), np.imag(muD[1]) f1R = (fen.exp(muxR * x + muyR * y) * fen.cos(muxI * x + muyI * y)) f1I = (fen.exp(muxR * x + muyR * y) * fen.sin(muxI * x + muyI * y)) fRe = f0 * (CR * f1R - CI * f1I) + fenZERO fIm = f0 * (CR * f1I + CI * f1R) + fenZERO parsRe = self.iterReduceQuadratureDegree(zip([fRe], ["forcingTerm{}Real".format(jj)])) parsIm = self.iterReduceQuadratureDegree(zip([fIm], ["forcingTerm{}Imag".format(jj)])) LR = fen.dot(fRe, self.v) * fen.dx LI = fen.dot(fIm, self.v) * fen.dx DBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) bjj = (fenics2Vector(LR, parsRe, DBC0, 1) + 1.j * fenics2Vector(LI, parsIm, DBC0, 1)) if jj == 0: bDEIM = np.empty((self.nbs, len(bjj)), dtype = np.complex) bDEIM[jj] = bjj p = PI() wellCond, _ = p.setupByInterpolation(muDEIM, bDEIM, int(self.nbs ** .5) - 1, "MONOMIAL", False, False) bDEIMCoeffs = p.coeffs self.bs[j1 + int(self.nbs ** .5) * j2] = bDEIMCoeffs[j1, j2] self.thbs[j1 + int(self.nbs ** .5) * j2] = ( self.getMonomialSingleWeight([j1, j2])) vbMng(self, "DEL", "Done assembling forcing term.", 20) diff --git a/tests/2_hfengines/linear_elasticity_beam_poisson_ratio_engine.py b/tests/2_hfengines/linear_elasticity_beam_poisson_ratio_engine.py index 3201a15..9adf3cd 100644 --- a/tests/2_hfengines/linear_elasticity_beam_poisson_ratio_engine.py +++ b/tests/2_hfengines/linear_elasticity_beam_poisson_ratio_engine.py @@ -1,112 +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 fenics as fen -from rrompy.hfengines.vector_linear_problem import \ - LinearElasticityProblemEngine +from rrompy.hfengines.fenics_engines import LinearElasticityProblemEngine from rrompy.solver.fenics import fenZERO, fenZEROS from rrompy.utilities.base import verbosityManager as vbMng from rrompy.solver.fenics import fenics2Sparse, fenics2Vector class LinearElasticityBeamPoissonRatioEngine(LinearElasticityProblemEngine): """ Solver for linear elasticity problem of a beam subject to its own weight, with parametric Poisson's ratio. - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) = rho_ * g in \Omega u = 0 on \Gamma_D \partial_nu = 0 on \Gamma_N """ def __init__(self, n:int, rho_:float, g:float, E:float, nu0:float, length:float, *args, **kwargs): super().__init__(nu0, *args, **kwargs) self._affinePoly = False self.nAs, self.nbs = 3, 2 self.E_ = E mesh = fen.RectangleMesh(fen.Point(0., 0.), fen.Point(length, 1), n, max(int(n / length), 1)) self.V = fen.VectorFunctionSpace(mesh, "P", 1) self.forcingTerm = [fen.Constant((0., - rho_ * g / E)), fenZEROS(2)] self.DirichletBoundary = lambda x, on_b: on_b and fen.near(x[0], 0.) self.NeumannBoundary = "REST" def buildA(self): """Build terms of operator of linear system.""" if self.As[0] is None: vbMng(self, "INIT", "Assembling operator term A0.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(2), self.DirichletBoundary) a0Re = fenZERO * fen.inner(self.u, self.v) * fen.dx self.As[0] = fenics2Sparse(a0Re, {}, DirichletBC0, 1) self.thAs[0] = self.getMonomialSingleWeight([0]) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[1] is None: vbMng(self, "INIT", "Assembling operator term A1.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(2), self.DirichletBoundary) epsilon = lambda u: .5 * (fen.grad(u) + fen.nabla_grad(u)) a1Re = self.E_ * fen.inner(epsilon(self.u), epsilon(self.v)) * fen.dx self.As[1] = fenics2Sparse(a1Re, {}, DirichletBC0, 0) self.thAs[1] = [('x', '()', 0, '*', -2., '+', 1.), (-2.0,), None] epsilon = lambda u: .5 * (fen.grad(u) + fen.nabla_grad(u)) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[2] is None: vbMng(self, "INIT", "Assembling operator term A2.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(2), self.DirichletBoundary) a2Re = self.E_ * fen.div(self.u) * fen.div(self.v) * fen.dx self.As[2] = fenics2Sparse(a2Re, {}, DirichletBC0, 0) self.thAs[2] = self.getMonomialSingleWeight([1]) vbMng(self, "DEL", "Done assembling operator term.", 20) def buildb(self): """Build terms of operator of linear system.""" if self.thbs[0] is None: self.thbs = [None] * self.nbs if self.bs[0] is None: vbMng(self, "INIT", "Assembling forcing term b0.", 20) L0Re = fen.inner(fenZEROS(2), self.v) * fen.dx DBCR = fen.DirichletBC(self.V, self.DirichletDatum[0], self.DirichletBoundary) DBCI = fen.DirichletBC(self.V, self.DirichletDatum[1], self.DirichletBoundary) self.bs[0] = (fenics2Vector(L0Re, {}, DBCR, 1) + 1.j * fenics2Vector(L0Re, {}, DBCI, 1)) self.thbs[0] = self.getMonomialSingleWeight([0]) if self.bs[1] is None: vbMng(self, "INIT", "Assembling forcing term b1.", 20) fRe, fIm = self.forcingTerm parsRe = self.iterReduceQuadratureDegree(zip([fRe], ["forcingTermReal"])) parsIm = self.iterReduceQuadratureDegree(zip([fIm], ["forcingTermImag"])) L1Re = fen.inner(fRe, self.v) * fen.dx L1Im = fen.inner(fIm, self.v) * fen.dx DBC0 = fen.DirichletBC(self.V, fenZEROS(2), self.DirichletBoundary) self.bs[1] = (fenics2Vector(L1Re, parsRe, DBC0, 1) + 1.j * fenics2Vector(L1Im, parsIm, DBC0, 1)) self.thbs[1] = [('x', '()', 0, '**', 2., '*', -2., '-', ('x', '()', 0), '+', 1.), ('x', '()', 0, '*', -4., '-', 1.), (-2.0,), None] vbMng(self, "DEL", "Done assembling forcing term.", 20) diff --git a/tests/2_hfengines/matrix.py b/tests/2_hfengines/matrix.py index 9b376dd..6be0590 100644 --- a/tests/2_hfengines/matrix.py +++ b/tests/2_hfengines/matrix.py @@ -1,58 +1,45 @@ # 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 import scipy.sparse as sp -from rrompy.hfengines.base import LinearAffineEngine, NumpyEngineBase - -class MatrixEngineBase(LinearAffineEngine, NumpyEngineBase): - pass +from rrompy.hfengines.scipy_engines import EigenproblemEngine def test_deterministic(): - solver = MatrixEngineBase(verbosity = 0) N = 100 - solver._affinePoly = True - solver.npar = 1 - solver.nAs, solver.nbs = 2, 1 - solver.setAs([sp.spdiags([np.arange(1, 1 + N)], [0], N, N), - - sp.eye(N)]) - solver.setbs([np.exp(1.j * np.linspace(0, -np.pi, N))]) - solver.setthAs(solver.getMonomialWeights(solver.nAs)) - solver.setthbs(solver.getMonomialWeights(solver.nbs)) + A = sp.spdiags([np.arange(1, 1 + N)], [0], N, N) + B = - sp.eye(N) + f = np.exp(1.j * np.linspace(0, -np.pi, N)) + solver = EigenproblemEngine([A, B], f, verbosity = 0) mu = 10. + .5j uh = solver.solve(mu)[0] assert np.isclose(solver.norm(solver.residual(mu, uh)[0], dual = True), 1.088e-15, rtol = 1e-1) def test_random(): - solver = MatrixEngineBase(verbosity = 0) N = 100 - solver.setSolver("SOLVE") - solver.npar = 1 - solver.nAs, solver.nbs = 2, 1 np.random.seed(420) fftB = np.fft.fft(np.eye(N)) * N ** -.5 - solver.setAs([fftB.dot(np.multiply(np.arange(1, 1 + N), fftB.conj()).T), - - np.eye(N)]) - solver.setbs([np.random.randn(N) + 1.j * np.random.randn(N)]) - solver.setthAs(solver.getMonomialWeights(solver.nAs)) - solver.setthbs(solver.getMonomialWeights(solver.nbs)) + A = fftB.dot(np.multiply(np.arange(1, 1 + N), fftB.conj()).T) + B = - np.eye(N) + f = np.random.randn(N) + 1.j * np.random.randn(N) + solver = EigenproblemEngine([A, B], f, verbosity = 0) mu = 1. + .5j uh = solver.solve(mu)[0] assert np.isclose(solver.norm(solver.residual(mu, uh)[0], dual = True), 7.18658e-14, rtol = 1e-1) diff --git a/tests/2_hfengines/membrane_fracture_engine.py b/tests/2_hfengines/membrane_fracture_engine.py index 11de128..e0c2b23 100644 --- a/tests/2_hfengines/membrane_fracture_engine.py +++ b/tests/2_hfengines/membrane_fracture_engine.py @@ -1,139 +1,140 @@ # 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 mshr, ufl from rrompy.utilities.base.types import paramVal from rrompy.solver.fenics import fenZERO, fenONE -from rrompy.hfengines.linear_problem import HelmholtzProblemEngine +from rrompy.hfengines.fenics_engines import HelmholtzProblemEngine from rrompy.utilities.base import verbosityManager as vbMng from rrompy.solver.fenics import fenics2Sparse +from rrompy.parameter import parameterMap as pMap class MembraneFractureEngine(HelmholtzProblemEngine): def __init__(self, mu0 : paramVal = [20. ** .5, .6], H : float = 1., L : float = .75, delta : float = .05, n : int = 50, *args, **kwargs): super().__init__(mu0, *args, **kwargs) self._affinePoly = False self.nAs = 5 self.npar = 2 self.H = H - self.rescalingExp = [2., 1.] + self.parameterMap = pMap([2., 1.]) domain = (mshr.Rectangle(fen.Point(0., - H / 2.), fen.Point(2. * L + delta, H / 2.)) - mshr.Rectangle(fen.Point(L, 0.), fen.Point(L + delta, H / 2.))) mesh = mshr.generate_mesh(domain, n) self.V = fen.FunctionSpace(mesh, "P", 1) self.NeumannBoundary = lambda x, on_b: (on_b and x[1] >= - H / 4. and x[0] >= L and x[0] <= L + delta) self.DirichletBoundary = "REST" x, y = fen.SpatialCoordinate(mesh)[:] self._belowIndicator = ufl.conditional(ufl.le(y, 0.), fenONE, fenZERO) self._aboveIndicator = fenONE - self._belowIndicator self.DirichletDatum = [fen.exp(- 10. * (H / 2. + y) / H - .5 * ((x - .6 * L) / (.1 * L)) ** 2. ) * self._belowIndicator, fenZERO] def buildA(self): """Build terms of operator of linear system.""" if self.As[0] is None: vbMng(self, "INIT", "Assembling operator term A0.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a0Re = fenZERO * fen.dot(self.u, self.v) * fen.dx self.As[0] = fenics2Sparse(a0Re, {}, DirichletBC0, 1) self.thAs[0] = self.getMonomialSingleWeight([0, 0]) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[1] is None: vbMng(self, "INIT", "Assembling operator term A1.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a1Re = (self.H ** 3 / 4. * self._aboveIndicator * fen.dot(self.u.dx(1), self.v.dx(1)) * fen.dx) self.As[1] = fenics2Sparse(a1Re, {}, DirichletBC0, 0) self.thAs[1] = [('x', '()', 1, '*', -2., '+', self.H), (0.,), (-2.,), None] vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[2] is None: vbMng(self, "INIT", "Assembling operator term A2.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a2Re = self.H ** 2 * fen.dot(self.u.dx(0), self.v.dx(0)) * fen.dx self.As[2] = fenics2Sparse(a2Re, {}, DirichletBC0, 0) self.thAs[2] = [('x', '()', 1, '*', -1., '+', self.H, '*', ('x', '()', 1), '**', 2.), (0.,), ('x', '()', 1, '**', 2., '*', 4., '-', ('x', '()', 1, '*', 6. * self.H), '+', 2. * self.H ** 2., '*', ('x', '()', 1)), (0.,), (0.,), ('x', '()', 1, '**', 2., '*', 6., '-', ('x', '()', 1, '*', 6. * self.H), '+', self.H ** 2.), (0.,), (0.,), (0.,), ('x', '()', 1, '*', 4., '-', 2. * self.H), (0.,), (0.,), (0.,), (0.,), (1.,), None] vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[3] is None: vbMng(self, "INIT", "Assembling operator term A3.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) nRe, nIm = self.refractionIndex n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm parsRe = self.iterReduceQuadratureDegree(zip([n2Re], ["refractionIndexSquaredReal"])) parsIm = self.iterReduceQuadratureDegree(zip([n2Im], ["refractionIndexSquaredImag"])) a3Re = - n2Re * fen.dot(self.u, self.v) * fen.dx a3Im = - n2Im * fen.dot(self.u, self.v) * fen.dx self.As[3] = (fenics2Sparse(a3Re, parsRe, DirichletBC0, 0) + 1.j * fenics2Sparse(a3Im, parsIm, DirichletBC0, 0)) self.thAs[3] = [('x', '()', 1, '*', -1., '+', self.H, '*', ('x', '()', 1), '**', 2., '*', ('x', '()', 0)), ('x', '()', 1, '*', -1., '+', self.H, '*', ('x', '()', 1), '**', 2.), (2. * self.H ** 2., '-', ('x', '()', 1, '*', 6. * self.H), '+', ('x', '()', 1, '**', 2., '*', 4.), '*', ('x', '()', 1), '*', ('x', '()', 0)), (0.,), ('x', '()', 1, '**', 2., '*', 4., '-', ('x', '()', 1, '*', 6. * self.H), '+', 2. * self.H ** 2., '*', ('x', '()', 1)), ('x', '()', 1, '**', 2., '*', 6., '-', ('x', '()', 1, '*', 6. * self.H), '+', self.H ** 2., '*', ('x', '()', 0)), (0.,), (0.,), ('x', '()', 1, '**', 2., '*', 6., '-', ('x', '()', 1, '*', 6. * self.H), '+', self.H ** 2.), ('x', '()', 1, '*', 4., '-', 2. * self.H, '*', ('x', '()', 0)), (0.,), (0.,), (0.,), ('x', '()', 1, '*', 4., '-', 2. * self.H), ('x', '()', 0), (0.,), (0.,), (0.,), (0.,), (1.,), None] vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[4] is None: vbMng(self, "INIT", "Assembling operator term A4.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a4Re = .25 * self.H ** 2 * fen.dot(self.u.dx(1), self.v.dx(1)) * fen.dx self.As[4] = fenics2Sparse(a4Re, {}, DirichletBC0, 0) self.thAs[4] = self.getMonomialSingleWeight([0, 2]) vbMng(self, "DEL", "Done assembling operator term.", 20) diff --git a/tests/2_hfengines/membrane_fracture_nodomain_engine.py b/tests/2_hfengines/membrane_fracture_nodomain_engine.py index 642330c..fedb1a8 100644 --- a/tests/2_hfengines/membrane_fracture_nodomain_engine.py +++ b/tests/2_hfengines/membrane_fracture_nodomain_engine.py @@ -1,86 +1,86 @@ # 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 mshr, ufl from rrompy.utilities.base.types import paramVal from rrompy.solver.fenics import fenZERO, fenONE -from rrompy.hfengines.linear_problem.helmholtz_problem_engine import ( - HelmholtzProblemEngine) +from rrompy.hfengines.fenics_engines import HelmholtzProblemEngine from rrompy.utilities.base import verbosityManager as vbMng from rrompy.solver.fenics import fenics2Sparse +from rrompy.parameter import parameterMap as pMap class MembraneFractureNoDomainEngine(HelmholtzProblemEngine): def __init__(self, mu0 : paramVal = [20. ** .5, .6], H : float = 1., L : float = .75, delta : float = .05, n : int = 50, *args, **kwargs): super().__init__(mu0[0], *args, **kwargs) self._affinePoly = True self.npar = 1 self.lFrac = mu0[1] self.H = H - self.rescalingExp = [2.] + self.parameterMap = pMap(2.) domain = (mshr.Rectangle(fen.Point(0., - H / 2.), fen.Point(2. * L + delta, H / 2.)) - mshr.Rectangle(fen.Point(L, 0.), fen.Point(L + delta, H / 2.))) mesh = mshr.generate_mesh(domain, n) self.V = fen.FunctionSpace(mesh, "P", 1) self.NeumannBoundary = lambda x, on_b: (on_b and x[1] >= - H / 4. and x[0] >= L and x[0] <= L + delta) self.DirichletBoundary = "REST" x, y = fen.SpatialCoordinate(mesh)[:] self._belowIndicator = ufl.conditional(ufl.le(y, 0.), fenONE, fenZERO) self._aboveIndicator = fenONE - self._belowIndicator self.DirichletDatum = [fen.exp(- 10. * (H / 2. + y) / H - .5 * ((x - .6 * L) / (.1 * L)) ** 2. ) * self._belowIndicator, fenZERO] 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.autoSetDS() vbMng(self, "INIT", "Assembling operator term A0.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a0Re = (fen.dot(self.u.dx(0), self.v.dx(0)) + self.H ** 4 / 4. * (self.lFrac ** -2. * self._aboveIndicator + (self.H - self.lFrac) ** -2. * self._belowIndicator) * fen.dot(self.u.dx(1), self.v.dx(1)) ) * fen.dx self.As[0] = fenics2Sparse(a0Re, {}, DirichletBC0, 1) vbMng(self, "DEL", "Done assembling operator term.", 20) if self.As[1] is None: vbMng(self, "INIT", "Assembling operator term A1.", 20) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) nRe, nIm = self.refractionIndex n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm parsRe = self.iterReduceQuadratureDegree(zip([n2Re], ["refractionIndexSquaredReal"])) parsIm = self.iterReduceQuadratureDegree(zip([n2Im], ["refractionIndexSquaredImag"])) a1Re = - n2Re * fen.dot(self.u, self.v) * fen.dx a1Im = - n2Im * fen.dot(self.u, self.v) * fen.dx self.As[1] = (fenics2Sparse(a1Re, parsRe, DirichletBC0, 0) + 1.j * fenics2Sparse(a1Im, parsIm, DirichletBC0, 0)) vbMng(self, "DEL", "Done assembling operator term.", 20) diff --git a/tests/2_hfengines/rod_3d.py b/tests/2_hfengines/rod_3d.py index e5da838..3f81f2b 100644 --- a/tests/2_hfengines/rod_3d.py +++ b/tests/2_hfengines/rod_3d.py @@ -1,37 +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 . # import fenics as fen -from rrompy.hfengines.vector_linear_problem import ( - LinearElasticityProblemEngine) +from rrompy.hfengines.fenics_engines import LinearElasticityProblemEngine def rod3Dsolver(): mesh = fen.Mesh("./tests/2_hfengines/rod.xml") rho_ = 1e3 nu = .45 E = 1e6 g = 1e3 solver = LinearElasticityProblemEngine(verbosity = 0) solver.V = fen.VectorFunctionSpace(mesh, "P", 1) solver.lambda_ = E * nu / (1. + nu) / (1. - 2 * nu) solver.mu_ = E / (1. + nu) solver.forcingTerm = [fen.Constant((0., - rho_ * g / E, 0.)), fen.Constant((0., 0., 0.))] solver.DirichletBoundary = lambda x, on_b: on_b and fen.near(x[2], 0.) solver.NeumannBoundary = "REST" return solver diff --git a/tests/3_reduction_methods_1D/matrix_fft.py b/tests/3_reduction_methods_1D/matrix_fft.py index 9370e37..9369edb 100644 --- a/tests/3_reduction_methods_1D/matrix_fft.py +++ b/tests/3_reduction_methods_1D/matrix_fft.py @@ -1,38 +1,31 @@ # 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.hfengines.base import LinearAffineEngine, NumpyEngineBase +from rrompy.hfengines.scipy_engines import EigenproblemEngine -class matrixFFT(LinearAffineEngine, NumpyEngineBase): +class matrixFFT(EigenproblemEngine): def __init__(self): - super().__init__(verbosity = 0) - self._affinePoly = True N = 100 np.random.seed(420) - self.setSolver("SOLVE") - self.npar = 1 - self.nAs, self.nbs = 2, 1 - self.mu0 = 0. fftB = np.fft.fft(np.eye(N)) * N**-.5 - self.setAs([fftB.dot(np.multiply(np.arange(1, 1 + N), fftB.conj()).T), - - np.eye(N)]) - self.setbs([np.random.randn(N) + 1.j * np.random.randn(N)]) - self.setthAs(self.getMonomialWeights(self.nAs)) - self.setthbs(self.getMonomialWeights(self.nbs)) + A = fftB.dot(np.multiply(np.arange(1, 1 + N), fftB.conj()).T) + B = - np.eye(N) + f = np.random.randn(N) + 1.j * np.random.randn(N) + super().__init__([A, B], f, verbosity = 0) diff --git a/tests/3_reduction_methods_1D/rational_interpolant_1d.py b/tests/3_reduction_methods_1D/rational_interpolant_1d.py index dc6420b..9f73335 100644 --- a/tests/3_reduction_methods_1D/rational_interpolant_1d.py +++ b/tests/3_reduction_methods_1D/rational_interpolant_1d.py @@ -1,69 +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 matrix_fft import matrixFFT from rrompy.reduction_methods import RationalInterpolant as RI from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, ManualSampler as MS) from rrompy.parameter import checkParameterList def test_monomials(capsys): mu = 1.5 solver = matrixFFT() params = {"POD": False, "S": 10, "robustTol": 1e-6, "interpRcond": 1e-3, "polybasis": "MONOMIAL", "sampler": QS([1.5, 6.5], "UNIFORM")} approx = RI(solver, 4., approx_state = True, approxParameters = params, verbosity = 10) approx.setupApprox() out, err = capsys.readouterr() assert "poorly conditioned. Reducing E " in out assert len(err) == 0 assert np.isclose(approx.normErr(mu)[0], 1.4746e-05, atol = 1e-4) def test_well_cond(): mu = 1.5 solver = matrixFFT() params = {"POD": True, "S": 10, "robustTol": 1e-14, "interpRcond": 1e-10, "polybasis": "CHEBYSHEV", "sampler": QS([1., 7.], "CHEBYSHEV")} approx = RI(solver, 4., approx_state = True, approxParameters = params, verbosity = 0) approx.setupApprox() poles = approx.getPoles() for lambda_ in np.arange(1, 8): assert np.isclose(np.min(np.abs(poles - lambda_)), 0., atol = 1e-4) for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0], 0., atol = 1e-8) def test_hermite(): mu = 1.5 solver = matrixFFT() sampler0 = QS([1., 7.], "CHEBYSHEV") - points, _ = checkParameterList(np.tile(sampler0.generatePoints(4)(0), 3)) + points = checkParameterList(np.tile(sampler0.generatePoints(4)(0), 3)) params = {"POD": True, "S": 12, "polybasis": "CHEBYSHEV", "sampler": MS([1., 7.], points = points)} approx = RI(solver, 4., approx_state = True, approxParameters = params, verbosity = 0) approx.setupApprox() poles = approx.getPoles() for lambda_ in np.arange(1, 8): assert np.isclose(np.min(np.abs(poles - lambda_)), 0., atol = 1e-4) for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0], 0., atol = 1e-8) diff --git a/tests/3_reduction_methods_1D/reduced_basis_1d.py b/tests/3_reduction_methods_1D/reduced_basis_1d.py index 5b77882..788b37b 100644 --- a/tests/3_reduction_methods_1D/reduced_basis_1d.py +++ b/tests/3_reduction_methods_1D/reduced_basis_1d.py @@ -1,60 +1,60 @@ # 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 matrix_fft import matrixFFT from rrompy.reduction_methods import ReducedBasis as RB from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, ManualSampler as MS) from rrompy.parameter import checkParameterList def test_LS(): solver = matrixFFT() params = {"POD": True, "R": 5, "S": 10, "sampler": QS([1., 7.], "CHEBYSHEV")} approx = RB(solver, 4., approx_state = True, approxParameters = params, verbosity = 0) approx.setupApprox() for mu in approx.mus: assert not np.isclose(approx.normErr(mu)[0], 0., atol = 1e-7) approx.POD = False approx.setupApprox() for mu in approx.mus[approx.R :]: assert not np.isclose(approx.normErr(mu)[0], 0., atol = 1e-3) def test_interp(): solver = matrixFFT() params = {"POD": False, "S": 10, "sampler": QS([1., 7.], "CHEBYSHEV")} approx = RB(solver, 4., approx_state = True, approxParameters = params, verbosity = 0) approx.setupApprox() for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0], 0., atol = 1e-7) def test_hermite(): mu = 1.5 solver = matrixFFT() sampler0 = QS([1., 7.], "CHEBYSHEV") - points, _ = checkParameterList(np.tile(sampler0.generatePoints(4)(0), 3)) + points = checkParameterList(np.tile(sampler0.generatePoints(4)(0), 3)) params = {"POD": True, "S": 12, "sampler": MS([1., 7.], points = points)} approx = RB(solver, 4., approx_state = True, approxParameters = params, verbosity = 0) approx.setupApprox() for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0], 0., atol = 1e-8) diff --git a/tests/4_reduction_methods_multiD/matrix_random.py b/tests/4_reduction_methods_multiD/matrix_random.py index 1bac706..e11c44b 100644 --- a/tests/4_reduction_methods_multiD/matrix_random.py +++ b/tests/4_reduction_methods_multiD/matrix_random.py @@ -1,41 +1,35 @@ # 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.hfengines.base import LinearAffineEngine, NumpyEngineBase +from rrompy.hfengines.scipy_engines import EigenproblemEngine -class matrixRandom(LinearAffineEngine, NumpyEngineBase): +class matrixRandom(EigenproblemEngine): def __init__(self): - super().__init__(verbosity = 0) - self._affinePoly = True N = 100 np.random.seed(420) - self.setSolver("SOLVE") - self.npar = 2 - self.nAs, self.nbs = 3, 1 - self.mu0 = [0., 0.] d1 = np.random.randn(N) Q1, _ = np.linalg.qr(np.random.randn(N, N)) d2 = np.random.randn(N) Q2, _ = np.linalg.qr(np.random.randn(N, N)) - self.setAs([np.eye(N), Q1.dot(np.multiply(d1, Q1.conj()).T), - Q2.dot(np.multiply(d2, Q2.conj()).T)]) - self.setbs([np.random.randn(N) + 1.j * np.random.randn(N)]) - self.setthAs(self.getMonomialWeights(self.nAs)) - self.setthbs(self.getMonomialWeights(self.nbs)) + A = np.eye(N) + B = Q1.dot(np.multiply(d1, Q1.conj()).T) + C = Q2.dot(np.multiply(d2, Q2.conj()).T) + f = np.random.randn(N) + 1.j * np.random.randn(N) + super().__init__([A, B, C], f, verbosity = 0) diff --git a/tests/4_reduction_methods_multiD/reduced_basis_2d.py b/tests/4_reduction_methods_multiD/reduced_basis_2d.py index 979efa5..26f864e 100644 --- a/tests/4_reduction_methods_multiD/reduced_basis_2d.py +++ b/tests/4_reduction_methods_multiD/reduced_basis_2d.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 matrix_random import matrixRandom from rrompy.reduction_methods import ReducedBasis as RB from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, ManualSampler as MS) from rrompy.parameter import checkParameterList def test_LS(): mu0 = [2, 3] solver = matrixRandom() params = {"POD": True, "R": 5, "S": 9, "sampler": QS([[0., 4.], [1., 5.]], "CHEBYSHEV")} approx = RB(solver, mu0, approx_state = True, approxParameters = params, verbosity = 0) approx.setupApprox() for mu in approx.mus: assert not np.isclose(approx.normErr(mu)[0], 0., atol = 1e-7) approx.POD = False approx.setupApprox() for mu in approx.mus[approx.R :]: assert not np.isclose(approx.normErr(mu)[0], 0., atol = 1e-3) def test_interp(): mu0 = [2, 3] solver = matrixRandom() params = {"POD": False, "S": 9, "sampler": QS([[0., 4.], [1., 5.]], "CHEBYSHEV")} approx = RB(solver, mu0, approx_state = True, approxParameters = params, verbosity = 0) approx.setupApprox() for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0], 0., atol = 1e-7) def test_hermite(): mu0 = [2, 3] solver = matrixRandom() sampler0 = QS([[0., 4.], [1., 5.]], "CHEBYSHEV") - points, _ = checkParameterList(np.tile( - sampler0.generatePoints(4).data, [3, 1])) + points = checkParameterList(np.tile(sampler0.generatePoints(4).data, + [3, 1])) params = {"POD": True, "S": 12, "sampler": MS([[0., 4.], [1., 5.]], points = points)} approx = RB(solver, mu0, approx_state = True, approxParameters = params, verbosity = 0) approx.setupApprox() for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0], 0., atol = 1e-8)