diff --git a/VERSION b/VERSION
index e6b7b62..af65e02 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.7
\ No newline at end of file
+2.8
\ No newline at end of file
diff --git a/examples/1_symmetric_disk/symmetric_disk_engine.py b/examples/1_symmetric_disk/symmetric_disk_engine.py
index be459e4..6b2ac19 100644
--- a/examples/1_symmetric_disk/symmetric_disk_engine.py
+++ b/examples/1_symmetric_disk/symmetric_disk_engine.py
@@ -1,12 +1,13 @@
import fenics as fen
import mshr
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.)]
+ self.cutOffPolesIMin, self.cutOffPolesIMax = -1e-2, 1e-2
diff --git a/examples/2_double_slit/double_slit_engine.py b/examples/2_double_slit/double_slit_engine.py
index 4c4b5ab..3e61806 100644
--- a/examples/2_double_slit/double_slit_engine.py
+++ b/examples/2_double_slit/double_slit_engine.py
@@ -1,57 +1,58 @@
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.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"
+ self.cutOffPolesIMax = 0.
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 7d8f2c4..3df9737 100644
--- a/examples/3_sector_angle/sector_angle.py
+++ b/examples/3_sector_angle/sector_angle.py
@@ -1,110 +1,108 @@
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.),
+ 'matchingWeight':1., '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.),
+ 'matchingWeight':1., '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 cd767dc..5172549 100644
--- a/examples/3_sector_angle/sector_angle_engine.py
+++ b/examples/3_sector_angle/sector_angle_engine.py
@@ -1,44 +1,46 @@
import numpy as np
import fenics as fen
import mshr
from rrompy.utilities.base.decorators import nonaffine_construct
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.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
+ self.cutOffPolesRMinRel, self.cutOffPolesRMaxRel = -2., 2.
+ self.cutOffPolesIMin, self.cutOffPolesIMax = -1e-2, 1e-2
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.py b/examples/4_funnel_output/funnel_output.py
index d004124..b4ffd18 100644
--- a/examples/4_funnel_output/funnel_output.py
+++ b/examples/4_funnel_output/funnel_output.py
@@ -1,58 +1,59 @@
import numpy as np
from funnel_output_engine import FunnelOutputEngine as engine
from rrompy.reduction_methods import (NearestNeighbor as NN,
RationalInterpolant as RI,
RationalInterpolantGreedy as RIG)
from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS,
EmptySampler as ES)
ks = [5., 10.]
k0, n = np.mean(ks), 50
solver = engine(k0, n)
k = 6.5
for method in ["RI", "RI_OUTPUT", "RI_GREEDY", "RI_GREEDY_OUTPUT"]:
print("Testing {} method".format(method))
if "GREEDY" not in method:
params = {'S':20, 'POD':True, 'polybasis':"CHEBYSHEV",
'sampler':QS(ks, "CHEBYSHEV")}
algo = RI
if "GREEDY" in method:
params = {'S':2, 'POD':True, 'polybasis':"LEGENDRE", 'greedyTol':1e-1,
'maxIter':25, 'sampler':QS(ks, "UNIFORM"),
'errorEstimatorKind':"LOOK_AHEAD_OUTPUT"}
algo = RIG
approx = algo(solver, mu0 = k0, approx_state = method[-7 :] != "_OUTPUT",
approxParameters = params, verbosity = 5)
if "GREEDY" not in method:
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')
normErr = approx.normErr(k)[0]
normSol = approx.normHF(k)[0]
print("SolNorm:\t{:.5e}\nErr_app: \t{:.5e}\nErrRel_app:\t{:.5e}".format(
normSol, normErr, normErr / normSol))
print("--- Closest snapshot ---")
approxNN = NN(solver, mu0 = k0, approx_state = method[-7 :] != "_OUTPUT",
- approxParameters = {'S':approx.S, 'POD':True,
- 'sampler':ES()}, verbosity = 0)
+ approxParameters = {'S':approx.samplingEngine.nsamples,
+ 'POD':True, 'sampler':ES()},
+ verbosity = 0)
approxNN.setSamples(approx.samplingEngine)
approxNN.plotApprox(k, name = 'u_close')
approxNN.plotHF(k, name = 'u_HF')
approxNN.plotErr(k, name = 'err_close')
normErr = approxNN.normErr(k)[0]
normSol = approxNN.normHF(k)[0]
print("SolNorm:\t{:.5e}\nErr_close:\t{:.5e}\nErrRel_close:\t{:.5e}".format(
normSol, normErr, normErr / normSol))
print("Poles:\n{}".format(approx.getPoles()))
print("\n")
diff --git a/examples/4_funnel_output/funnel_output_engine.py b/examples/4_funnel_output/funnel_output_engine.py
index a9b5e89..e32bc08 100644
--- a/examples/4_funnel_output/funnel_output_engine.py
+++ b/examples/4_funnel_output/funnel_output_engine.py
@@ -1,35 +1,36 @@
import numpy as np
import fenics as fen
import mshr
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]
+ self.cutOffPolesIMax = 0.
diff --git a/examples/5_anisotropic_square/anisotropic_square.py b/examples/5_anisotropic_square/anisotropic_square.py
index 2738c6c..1961d60 100644
--- a/examples/5_anisotropic_square/anisotropic_square.py
+++ b/examples/5_anisotropic_square/anisotropic_square.py
@@ -1,77 +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 (
RationalInterpolantGreedyPivotedGreedy as RIGPG)
from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS,
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",
'errorEstimatorKindMarginal':"LOOK_AHEAD_RECOVER",
"SMarginal": 3, "paramsMarginal": {"MMarginal": 2,
"radialDirectionalWeightsMarginalAdapt": [1e9, 1e12]},
"greedyTolMarginal": 1e-2, "samplerMarginal":SGS(Ls),
"radialDirectionalWeightsMarginal": [4.], "matchingWeight": 1.}
for shared, tol in product([1., 0.], [1., 3.]):
print("Testing cutoff tolerance {} with shared ratio {}.".format(tol,
shared))
- params['cutOffTolerance'] = tol
+ solver.cutOffPolesRMinRel = - 1. - tol
+ solver.cutOffPolesRMaxRel = 1. + tol
params['sharedRatio'] = shared
approx = RIGPG([0], solver, mu0 = [z0, L0], approx_state = True,
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 dfee5f9..1ed0399 100644
--- a/examples/5_anisotropic_square/anisotropic_square_engine.py
+++ b/examples/5_anisotropic_square/anisotropic_square_engine.py
@@ -1,64 +1,65 @@
import numpy as np
import fenics as fen
import ufl
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.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))
+ self.cutOffPolesIMin, self.cutOffPolesIMax = -1e-2, 1e-2
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_test_cutoff.py b/examples/5_anisotropic_square/anisotropic_square_test_cutoff.py
deleted file mode 100755
index 1786c06..0000000
--- a/examples/5_anisotropic_square/anisotropic_square_test_cutoff.py
+++ /dev/null
@@ -1,78 +0,0 @@
-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 (
- RationalInterpolantGreedyPivotedGreedy as RIGPG)
-from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS,
- 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,
- "polybasis": "LEGENDRE", 'samplerPivot':QS(zs, "UNIFORM"),
- 'sharedRatio': 0., "maxIterMarginal":20,
- 'cutOffToleranceError': 1., 'trainSetGenerator':QS(zs, "UNIFORM"),
- 'errorEstimatorKind':"LOOK_AHEAD_RES",
- 'errorEstimatorKindMarginal':"LOOK_AHEAD_RECOVER",
- "SMarginal": 3, "paramsMarginal": {"MMarginal": 2,
- "radialDirectionalWeightsMarginalAdapt": [1e9, 1e12]},
- "greedyTolMarginal": 1e-2, "samplerMarginal":SGS(Ls),
- "radialDirectionalWeightsMarginal": [4.], "matchingWeight": 1.}
-
-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)
- 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/7_MHD/mhd.py b/examples/7_MHD/mhd.py
index bb8482c..a750e41 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), 'residueTol':1e-5}
+ 'sampler':FFTS(ks)}
algo = RI
if method == "BOX":
params = {'S':64, 'POD':True, 'polybasis':"MONOMIAL",
- 'sampler':QBS(ks), 'residueTol':1e-5}
+ 'sampler':QBS(ks)}
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), 'residueTol':1e-5}
+ 'trainSetGenerator':FFTS(ks)}
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 aa8988f..da8755c 100644
--- a/examples/7_MHD/mhd_engine.py
+++ b/examples/7_MHD/mhd_engine.py
@@ -1,17 +1,19 @@
import numpy as np
import scipy.io as scio
import scipy.sparse as sp
from rrompy.hfengines.scipy_engines import TensorizedEigenproblemEngine
class MHDEngine(TensorizedEigenproblemEngine):
"""
From Matrix Market: //math.nist.gov/MatrixMarket/data/NEP/mhd/mhd.html
"""
def __init__(self, ncol : int = 1, seed : int = 31415):
A = sp.csr_matrix(scio.mmread("mhd4800a.mtx"), dtype = np.complex)
B = - scio.mmread("mhd4800b.mtx").tocsr()
super().__init__([A, B], seed, ncol)
+ self.cutOffPolesRMax = 0.
+ self.cutOffResNormMin = 1e-5
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
index 30f1f30..69785c4 100644
--- a/examples/8_damped_mass_chain/damped_mass_chain.py
+++ b/examples/8_damped_mass_chain/damped_mass_chain.py
@@ -1,186 +1,185 @@
### 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/rational_interpolation_method.pdf b/rational_interpolation_method.pdf
new file mode 100644
index 0000000..b6ea8b0
Binary files /dev/null and b/rational_interpolation_method.pdf differ
diff --git a/rational_interpolation_method.tex b/rational_interpolation_method.tex
new file mode 100644
index 0000000..7a97969
--- /dev/null
+++ b/rational_interpolation_method.tex
@@ -0,0 +1,219 @@
+\documentclass[10pt,a4paper]{article}
+\usepackage[left=1in,right=1in,top=1in,bottom=1in]{geometry}
+\usepackage{amsmath}
+\usepackage{amsfonts}
+\usepackage{amssymb}
+\usepackage{hyperref}
+\usepackage{xcolor}
+
+\setlength{\parindent}{0pt}
+\newcommand{\code}[1]{{\color{blue}\texttt{#1}}}
+\newcommand{\footpath}[1]{\footnote{\path{#1}}}
+\newcommand{\N}{\mathbb{N}}
+\newcommand{\R}{\mathbb{R}}
+\newcommand{\C}{\mathbb{C}}
+\newcommand{\I}{\mathcal{I}}
+\DeclareMathOperator*{\argmin}{arg\,min}
+\newcommand{\inner}[2]{\left\langle#1,#2\right\rangle_V}
+\newcommand{\norm}[1]{\left\|#1\right\|_V}
+
+\title{\bf The RROMPy rational interpolation method}
+\author{D. Pradovera, CSQI, EPF Lausanne -- \texttt{davide.pradovera@epfl.ch}}
+\date{}
+\begin{document}
+\maketitle
+
+\section*{Introduction}
+This document provides an explanation for the numerical method provided by the class \code{Rational Interpolant}\footpath{./rrompy/reduction_methods/standard/rational_interpolant.py} and daughters, e.g. \code{Rational Interpolant Greedy}\footpath{./rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py}, as well as most of the pivoted approximants\footpath{./rrompy/reduction_methods/pivoted/{,greedy/}rational_interpolant_*.py}.
+
+We restrict the discussion to the single-parameter case, and most of the focus will be dedicated to the impact of the \code{functionalSolve} parameter, whose allowed values are
+\begin{itemize}
+\item \code{NORM} (default): see \ref{sec:norm}; allows for derivative information, i.e. repeated sample points.
+\item \code{DOMINANT}: see \ref{sec:dominant}; allows for derivative information, i.e. repeated sample points.
+\item \code{BARYCENTRIC\_NORM}: see \ref{sec:barycentricnorm}; does not allow for a Least Squares (LS) approach.
+\item \code{BARYCENTRIC\_AVERAGE}: see \ref{sec:barycentricaverage}; does not allow for a Least Squares (LS) approach.
+\item \code{NODAL}: see \ref{sec:nodal}; iterative method.
+\end{itemize}
+
+The main reference throughout the present document is \cite{Pradovera}.
+
+\section{Aim of approximation}
+We seek an approximation of $u:\C\to V$, with $(V,\inner{\cdot}{\cdot})$ a complex\footnote{The inner product is linear (resp. conjugate linear) in the first (resp. second) argument: $\inner{\alpha v}{\beta w}=\alpha\overline{\beta}\inner{v}{w}$.} Hilbert space (with endowed norm $\norm{\cdot}$), of the form $\widehat{p}/\widehat{q}$, where $\widehat{p}:\C\to V$ and $\widehat{q}:\C\to\C$. For a given denominator $\widehat{q}$, the numerator $\widehat{p}$ is found by interpolation (possibly, LS or based on radial basis functions) of $\widehat{q}u$. Hence, here we focus on the computation of the denominator $\widehat{q}$.
+
+Other than the choice of target function $u$, the parameters which affect the computation of $\widehat{q}$ are:
+\begin{itemize}
+\item $\code{mus}\subset\C$ ($\{\mu_j\}_{j=1}^S$ below); for all \code{functionalSolve} values but \code{NORM} and \code{DOMINANT}, the $S$ points must be distinct.
+\item $\code{N}\in\N$ ($N$ below); for \code{BARYCENTRIC}, $N$ must equal $S-1$.
+\item $\code{polybasis0}\in\{\code{"CHEBYSHEV"}, \code{"LEGENDRE"}, \code{"MONOMIAL"}\}$; only for \code{NORM} and \code{DOMINANT}.
+\end{itemize}
+For simplicity, we will consider only the case of $S$ distinct sample points. One can deal with the case of confluent sample points by extending the standard (Lagrange) interpolation steps to Hermite-Lagrange ones.
+
+The main motivation behind the method involves the modified approximation problem
+\[u\approx\I^N\left(\Big(\big(\mu_j,\widehat{q}(\mu_j)u(\mu_j)\big)\Big)_{j=1}^S\right)\Big/\widehat{q},\]
+where $\widehat{q}:\C\to\C$ is a polynomial of degree $\leq N$, and $\I^N:(\C\times V)^S\to\mathbb{P}^N(\C;V)$ is a (LS) polynomial interpolation operator, which maps $S$ samples of a function (which lie in $V$) to a polynomial of degree $N.
#
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 .scipy_engine_base import ScipyEngineBase, ScipyEngineBaseTensorized
from .vector_fenics_engine_base import VectorFenicsEngineBase, VectorFenicsEngineBaseTensorized
__all__ = [
'BoundaryConditions',
'FenicsEngineBase',
'FenicsEngineBaseTensorized',
'HFEngineBase',
'LinearAffineEngine',
'checkIfAffine',
- 'MarginalProxyEngine',
'ScipyEngineBase',
'ScipyEngineBaseTensorized',
'VectorFenicsEngineBase',
'VectorFenicsEngineBaseTensorized'
]
diff --git a/rrompy/hfengines/base/hfengine_base.py b/rrompy/hfengines/base/hfengine_base.py
index 93976ae..deb6e37 100644
--- a/rrompy/hfengines/base/hfengine_base.py
+++ b/rrompy/hfengines/base/hfengine_base.py
@@ -1,272 +1,317 @@
# 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 collections.abc import Iterable
from copy import copy as softcopy
from rrompy.utilities.base.decorators import nonaffine_construct
from rrompy.utilities.base.types import (Np1D, Np2D, List, DictAny, paramVal,
paramList, sampList)
-from rrompy.utilities.numerical import solve as tsolve, dot, customPInv
+from rrompy.utilities.numerical import solve as tsolve, dot, pseudoInverse
from rrompy.utilities.expression import expressionEvaluator
from rrompy.utilities.exception_manager import RROMPyAssert
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 (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.parameterMap = pMap(1., npar)
self._npar = npar
@property
def spacedim(self):
return 1
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,
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 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__")
+ if (hasattr(self, "As") and isinstance(self.As, Iterable)
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)
+ return dot(pseudoInverse(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)
if len(mu) == 0: return emptySampleList()
mu, idx, sizes = listScatter(mu, return_sizes = True)
mu = self.checkParameterList(mu)
req, emptyCores = [], np.where(np.logical_not(sizes))[0]
if len(mu) == 0:
uL, uT = recv(source = 0, tag = poolRank())
sol = np.empty((uL, 0), dtype = uT)
else:
- if RHS is None:
+ if RHS is None: # build RHSs
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 += [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)
if len(mu) == 0: return emptySampleList()
mu, idx, sizes = listScatter(mu, return_sizes = True)
mu = self.checkParameterList(mu)
req, emptyCores = [], np.where(np.logical_not(sizes))[0]
if len(mu) == 0:
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 += [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))
+
+ cutOffPolesRMax,cutOffPolesRMin = np.inf, - np.inf
+ cutOffPolesRMaxRel, cutOffPolesRMinRel = np.inf, - np.inf
+ cutOffPolesIMax, cutOffPolesIMin = np.inf, - np.inf
+ cutOffPolesIMaxRel, cutOffPolesIMinRel = np.inf, - np.inf
+ cutOffResNormMin = -1
+ def flagBadPolesResidues(self, poles:Np1D, residues : Np1D = None,
+ relative : bool = False) -> Np1D:
+ """
+ Flag (numerical) poles/residues which are impossible.
+
+ Args:
+ poles: poles to be judged.
+ residues: residues to be judged.
+ relative: whether relative values should be used for poles.
+ """
+ poles = np.array(poles).flatten()
+ flag = np.zeros(len(poles), dtype = bool)
+ if residues is None:
+ self._ignoreResidues = self.cutOffResNormMin <= 0.
+ if relative:
+ RMax, RMin = self.cutOffPolesRMaxRel, self.cutOffPolesRMinRel
+ IMax, IMin = self.cutOffPolesIMaxRel, self.cutOffPolesIMinRel
+ else:
+ RMax, RMin = self.cutOffPolesRMax, self.cutOffPolesRMin
+ IMax, IMin = self.cutOffPolesIMax, self.cutOffPolesIMin
+ if not np.isinf(RMax):
+ flag = np.logical_or(flag, np.real(poles) > RMax)
+ if not np.isinf(RMin):
+ flag = np.logical_or(flag, np.real(poles) < RMin)
+ if not np.isinf(IMax):
+ flag = np.logical_or(flag, np.imag(poles) > IMax)
+ if not np.isinf(IMin):
+ flag = np.logical_or(flag, np.imag(poles) < IMin)
+ else:
+ residues = np.array(residues).reshape(len(poles), -1)
+ if self.cutOffResNormMin > 0.:
+ if residues.shape[1] == self.spacedim:
+ resEff = self.norm(residues.T)
+ else:
+ resEff = np.linalg.norm(residues, axis = 1)
+ resEff /= np.max(resEff)
+ flag = np.logical_or(flag, resEff < self.cutOffResNormMin)
+ return flag
diff --git a/rrompy/hfengines/base/linear_affine_engine.py b/rrompy/hfengines/base/linear_affine_engine.py
index 3bacc52..e8edfab 100644
--- a/rrompy/hfengines/base/linear_affine_engine.py
+++ b/rrompy/hfengines/base/linear_affine_engine.py
@@ -1,197 +1,198 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
from abc import abstractmethod
import numpy as np
import scipy.sparse as scsp
+from collections.abc import Iterable
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__")
+ if (hasattr(self, "bs") and isinstance(self.bs, Iterable)
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."""
+ """Assemble (derivative of) affine object from list of affine terms."""
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 isinstance(expr, Iterable):
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
+ derI = hashD(der) if isinstance(der, Iterable) 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
+ derI = hashD(der) if isinstance(der, Iterable) 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)
+ "using EIM 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
deleted file mode 100644
index 4b4d90c..0000000
--- a/rrompy/hfengines/base/marginal_proxy_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 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):
- mu = checkParameter(mu, self.nparFree, return_data = True)
- return np.insert(mu, 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/fenics_engines/helmholtz_problem_engine_augmented.py b/rrompy/hfengines/fenics_engines/helmholtz_problem_engine_augmented.py
index b682999..222c0e2 100755
--- a/rrompy/hfengines/fenics_engines/helmholtz_problem_engine_augmented.py
+++ b/rrompy/hfengines/fenics_engines/helmholtz_problem_engine_augmented.py
@@ -1,267 +1,268 @@
# 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 collections.abc import Iterable
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__")
+ if (hasattr(self, "bs") and isinstance(self.bs, Iterable)
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__")
+ if (hasattr(self, "bs") and isinstance(self.bs, Iterable)
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/fenics_engines/linear_elasticity_helmholtz_problem_engine_augmented.py b/rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine_augmented.py
index 75ae923..cff2ee1 100755
--- a/rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine_augmented.py
+++ b/rrompy/hfengines/fenics_engines/linear_elasticity_helmholtz_problem_engine_augmented.py
@@ -1,280 +1,281 @@
# 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 collections.abc import Iterable
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__")
+ if (hasattr(self, "bs") and isinstance(self.bs, Iterable)
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__")
+ if (hasattr(self, "bs") and isinstance(self.bs, Iterable)
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/scipy_engines/eigenproblem_engine.py b/rrompy/hfengines/scipy_engines/eigenproblem_engine.py
index a3980c2..2f6a124 100644
--- a/rrompy/hfengines/scipy_engines/eigenproblem_engine.py
+++ b/rrompy/hfengines/scipy_engines/eigenproblem_engine.py
@@ -1,70 +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.
+ Solver for generic eigenvalue-like problems with multiple RHSs.
(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/parameter/parameter_list.py b/rrompy/parameter/parameter_list.py
index ec0ccb6..28da974 100644
--- a/rrompy/parameter/parameter_list.py
+++ b/rrompy/parameter/parameter_list.py
@@ -1,234 +1,245 @@
# 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 collections.abc import Iterable
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, check_if_single : bool = False,
return_data : bool = False):
+ """Constructor of parameterList with parameter dimension check."""
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)
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, return_data : bool = False):
+ """
+ Constructor of parameterList with check on parameter dimension and
+ parameter number.
+ """
muL, wasPar = checkParameterList(mu, npar, True, return_data)
if not wasPar:
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:
+ """
+ List of (multi-D) parameters with many properties overloaded from Numpy
+ arrays.
+ """
+
__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]
+ if not isinstance(data, Iterable): 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, 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
index 2452b88..9c840e3 100644
--- a/rrompy/parameter/parameter_map.py
+++ b/rrompy/parameter/parameter_map.py
@@ -1,54 +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 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:
+ """
+ Constructor of dictionary with keys "F" and "B" for evaluation of forward
+ and backward (inverse) map.
+ """
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/__init__.py b/rrompy/parameter/parameter_sampling/__init__.py
index 2ed053b..b44b6da 100644
--- a/rrompy/parameter/parameter_sampling/__init__.py
+++ b/rrompy/parameter/parameter_sampling/__init__.py
@@ -1,40 +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 .empty_sampler import EmptySampler
from .manual_sampler import ManualSampler
from .segment import QuadratureSampler, QuadratureSamplerTotal, RandomSampler
from .shape import (FFTSampler, QuadratureBoxSampler, QuadratureCircleSampler,
RandomBoxSampler, RandomCircleSampler)
-from .sparse_grid import SparseGridSampler
+from .sparse_grid import SparseGridSampler, SparseGridSamplerTwoWay
__all__ = [
'EmptySampler',
'ManualSampler',
'QuadratureSampler',
'QuadratureSamplerTotal',
'RandomSampler',
'FFTSampler',
'QuadratureBoxSampler',
'QuadratureCircleSampler',
'RandomBoxSampler',
'RandomCircleSampler',
- 'SparseGridSampler'
+ 'SparseGridSampler',
+ 'SparseGridSamplerTwoWay'
]
diff --git a/rrompy/parameter/parameter_sampling/generic_random_sampler.py b/rrompy/parameter/parameter_sampling/generic_random_sampler.py
index 8341c46..c1cdbe1 100644
--- a/rrompy/parameter/parameter_sampling/generic_random_sampler.py
+++ b/rrompy/parameter/parameter_sampling/generic_random_sampler.py
@@ -1,78 +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, DictAny, paramList
+from rrompy.utilities.base.types import Tuple, 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",
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)
@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]:
+ def refine(self, active : List[int] = None) -> Tuple[List[int], List[int]]:
pass
diff --git a/rrompy/parameter/parameter_sampling/manual_sampler.py b/rrompy/parameter/parameter_sampling/manual_sampler.py
index a00c0f5..3c9daf8 100644
--- a/rrompy/parameter/parameter_sampling/manual_sampler.py
+++ b/rrompy/parameter/parameter_sampling/manual_sampler.py
@@ -1,61 +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 copy import deepcopy as copy
from .generic_sampler import GenericSampler
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,
parameterMap : DictAny = 1.,
normalFoci : List[np.complex] = [-1., 1.]):
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)
self._points = points
def __str__(self) -> str:
return "{}[{}]".format(self.name(), "_".join(map(str, self.points)))
def generatePoints(self, n:int, reorder : bool = True) -> paramList:
"""Array of sample points."""
if n > len(self.points):
pts = copy(self.points)
+ # repeat points if necessary
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)
return x
diff --git a/rrompy/parameter/parameter_sampling/segment/random_sampler.py b/rrompy/parameter/parameter_sampling/segment/random_sampler.py
index c1515f5..a79cc26 100644
--- a/rrompy/parameter/parameter_sampling/segment/random_sampler.py
+++ b/rrompy/parameter/parameter_sampling/segment/random_sampler.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 .
#
from numbers import Number
import numpy as np
from rrompy.parameter.parameter_sampling.generic_random_sampler import (
GenericRandomSampler)
from rrompy.utilities.numerical.halton import haltonGenerate
from rrompy.utilities.numerical.sobol import sobolGenerate
-from rrompy.utilities.base.types import List
+from rrompy.utilities.base.types import Tuple, List
__all__ = ['RandomSampler']
class RandomSampler(GenericRandomSampler):
"""Generator of (quasi-)random sample points."""
- def refine(self, active : List[int] = None) -> List[int]:
+ def refine(self, active : List[int] = None) -> Tuple[List[int], 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":
xmat = np.random.uniform(size = (n, self.npar))
elif self.kind == "HALTON":
xmat, self.seedLoc = haltonGenerate(self.npar, n, self.seedLoc,
return_seed = True)
else:
xmat, self.seedLoc = sobolGenerate(self.npar, n, self.seedLoc,
return_seed = True)
limsE = self.mapParameterList(self.lims)
for d in range(self.npar):
a, b = limsE(d)
xmat[:, d] = a + (b - a) * xmat[:, d]
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)
+ return list(idx), []
diff --git a/rrompy/parameter/parameter_sampling/shape/generic_shape_sampler.py b/rrompy/parameter/parameter_sampling/shape/generic_shape_sampler.py
index c46ed25..8feebb3 100644
--- a/rrompy/parameter/parameter_sampling/shape/generic_shape_sampler.py
+++ b/rrompy/parameter/parameter_sampling/shape/generic_shape_sampler.py
@@ -1,48 +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 .
#
import numpy as np
+from collections.abc import Iterable
from rrompy.parameter.parameter_sampling.generic_sampler import GenericSampler
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,
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]
+ if not isinstance(axisRatios, Iterable): 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/random_box_sampler.py b/rrompy/parameter/parameter_sampling/shape/random_box_sampler.py
index f1e872f..4142563 100644
--- a/rrompy/parameter/parameter_sampling/shape/random_box_sampler.py
+++ b/rrompy/parameter/parameter_sampling/shape/random_box_sampler.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 .
#
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 List
+from rrompy.utilities.base.types import Tuple, List
__all__ = ['RandomBoxSampler']
class RandomBoxSampler(GenericShapeRandomSampler):
"""Generator of (quasi-)random sample points on boxes."""
- def refine(self, active : List[int] = None) -> List[int]:
+ def refine(self, active : List[int] = None) -> Tuple[List[int], 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":
xmat2 = np.random.uniform(size = (nEff, 2 * self.npar))
elif self.kind == "HALTON":
xmat2, self.seedLoc = haltonGenerate(2 * self.npar, nEff,
self.seedLoc,
return_seed = True)
else:
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, b = limsE(d)
xmat[:, d] = a + (b - a) * (xmat2[: n, 2 * d]
+ 1.j * self.axisRatios[d] * (xmat2[: n, 2 * d + 1] - .5))
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)
+ 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 5a1de1f..e484597 100644
--- a/rrompy/parameter/parameter_sampling/shape/random_circle_sampler.py
+++ b/rrompy/parameter/parameter_sampling/shape/random_circle_sampler.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 .
#
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 List
+from rrompy.utilities.base.types import Tuple, List
__all__ = ['RandomCircleSampler']
class RandomCircleSampler(GenericShapeRandomSampler):
"""Generator of (quasi-)random sample points on ellipses."""
- def refine(self, active : List[int] = None) -> List[int]:
+ def refine(self, active : List[int] = None) -> Tuple[List[int], 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":
xmat2 = np.random.uniform(size = (nEff, 2 * self.npar))
elif self.kind == "HALTON":
xmat2, self.seedLoc = haltonGenerate(2 * self.npar, nEff,
self.seedLoc,
return_seed = True)
else:
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, 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])
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)
+ return list(idx), []
diff --git a/rrompy/parameter/parameter_sampling/sparse_grid/__init__.py b/rrompy/parameter/parameter_sampling/sparse_grid/__init__.py
index 64373b4..f69816e 100644
--- a/rrompy/parameter/parameter_sampling/sparse_grid/__init__.py
+++ b/rrompy/parameter/parameter_sampling/sparse_grid/__init__.py
@@ -1,25 +1,27 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
from .sparse_grid_sampler import SparseGridSampler
+from .sparse_grid_sampler_two_way import SparseGridSamplerTwoWay
__all__ = [
- 'SparseGridSampler'
+ 'SparseGridSampler',
+ 'SparseGridSamplerTwoWay'
]
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 ed6b372..e695e06 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,108 @@
# 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.parameter_sampling.generic_sampler import GenericSampler
from rrompy.utilities.poly_fitting.piecewise_linear import (sparsekinds,
sparseMap)
-from rrompy.utilities.base.types import List, DictAny, paramList
+from rrompy.utilities.base.types import Tuple, List, Np1D, 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",
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"])]:
+ product(sparsekinds, ["", "-HAAR"])]:
raise RROMPyException("Generator kind not recognized.")
self._kind = kind.upper()
- self._noBoundary = "NOBOUNDARY" in self._kind
+ self._noBoundary = "HAAR" in self._kind
def reset(self):
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)
+ self.depth = np.array([[self._noBoundary] * self.npar], dtype = int)
- def refine(self, active : List[int] = None) -> List[int]:
+ def refine(self, active : List[int] = None) -> Tuple[List[int], List[int]]:
if active is None: active = np.arange(self.npoints)
- limsX = self.mapParameterList(self.lims)
- newIdxs = []
+ active = np.array(active)
+ if np.any(active < 0) or np.any(active >= self.npoints):
+ raise RROMPyException(("Active indices must be between 0 "
+ "(included) and npoints (excluded)."))
+ newIdxs, oldIdxs = [], []
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(
- self.mapParameterList(point[jdelta],
- idx = [jdelta])(0, 0),
- limsX(jdelta), self.kind, False)
- if not self._noBoundary and dpt[jdelta] == 1:
- Centerj = sparseMap(
- 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] = 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
+ for jdelta, signdelta in product(range(self.npar), [-1., 1.]):
+ idx = self.addForwardPoint(point, dpt, jdelta, signdelta)
+ if idx is not None:
+ if idx > 0: newIdxs += [idx]
+ else: oldIdxs += [- idx]
+ return newIdxs, oldIdxs
+
+ def addForwardPoint(self, basepoint:Np1D, basedepth:Np1D, index:int,
+ sign:float) -> int:
+ if basedepth[index] < self._noBoundary:
+ return None #makeshift skip for wrong boundary points at lvl 1
+ limd = self.mapParameterList(self.lims(index), idx = [index])(0)
+ xd0 = sparseMap(self.mapParameterList(basepoint[index],
+ idx = [index])(0, 0),
+ limd, self.kind, False) + .5 ** basedepth[index] * sign
+ if np.abs(xd0) >= 1. + 1e-15 * (1 - 2 * self._noBoundary):
+ return None #point out of bounds
+ pt = copy(basepoint)
+ pt[index] = self.mapParameterList(sparseMap(xd0, limd, self.kind),
+ "B", [index])(0, 0)
+ dist = np.sum(np.abs(self.points.data - pt.reshape(1, -1)), axis = 1)
+ samePt = np.where(np.isclose(dist, 0.))[0]
+ if len(samePt) > 0: #point already exists
+ return - samePt[0]
+ self.points.append(pt)
+ self.depth = np.append(self.depth, [basedepth], 0)
+ self.depth[-1, index] += 1
+ return self.npoints - 1
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)
+ while self.npoints < n: idx = self.refine(idx)[0]
return self.points
diff --git a/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler_two_way.py b/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler_two_way.py
new file mode 100644
index 0000000..12a379b
--- /dev/null
+++ b/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler_two_way.py
@@ -0,0 +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 itertools import product
+import numpy as np
+from .sparse_grid_sampler import SparseGridSampler
+from rrompy.utilities.base.types import Tuple, List
+from rrompy.utilities.exception_manager import RROMPyException
+
+__all__ = ['SparseGridSamplerTwoWay']
+
+class SparseGridSamplerTwoWay(SparseGridSampler):
+ """Generator of sparse grid sample points with two-way refinement."""
+
+ def refine(self, active : List[int] = None) -> Tuple[List[int], List[int]]:
+ if active is None: active = np.arange(self.npoints)
+ active = np.array(active)
+ if np.any(active < 0) or np.any(active >= self.npoints):
+ raise RROMPyException(("Active indices must be between 0 "
+ "(included) and npoints (excluded)."))
+ newIdxs, oldIdxs = [], []
+ for act in active:
+ point, dpt = self.points[act], self.depth[act]
+ for jdelta, signdelta, backwards in product(range(self.npar),
+ [-1., 1.], [0, 1]):
+ if backwards: dpt[jdelta] -= 1
+ idx = self.addForwardPoint(point, dpt, jdelta, signdelta)
+ if idx is not None:
+ if idx > 0: newIdxs += [idx]
+ else: oldIdxs += [- idx]
+ if backwards: dpt[jdelta] += 1
+ return newIdxs, oldIdxs
diff --git a/rrompy/reduction_methods/__init__.py b/rrompy/reduction_methods/__init__.py
index 9e3d83f..92f8737 100644
--- a/rrompy/reduction_methods/__init__.py
+++ b/rrompy/reduction_methods/__init__.py
@@ -1,48 +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 .standard import (NearestNeighbor, RationalInterpolant, RationalPade,
- ReducedBasis)
+from .standard import NearestNeighbor, RationalInterpolant, ReducedBasis
from .standard.greedy import RationalInterpolantGreedy, ReducedBasisGreedy
from .pivoted import (RationalInterpolantPivotedNoMatch,
RationalInterpolantPivoted,
RationalInterpolantGreedyPivotedNoMatch,
RationalInterpolantGreedyPivoted)
from .pivoted.greedy import (RationalInterpolantPivotedGreedyNoMatch,
RationalInterpolantPivotedGreedy,
RationalInterpolantGreedyPivotedGreedyNoMatch,
RationalInterpolantGreedyPivotedGreedy)
__all__ = [
'NearestNeighbor',
'RationalInterpolant',
- 'RationalPade',
'ReducedBasis',
'RationalInterpolantGreedy',
'ReducedBasisGreedy',
'RationalInterpolantPivotedNoMatch',
'RationalInterpolantPivoted',
'RationalInterpolantGreedyPivotedNoMatch',
'RationalInterpolantGreedyPivoted',
'RationalInterpolantPivotedGreedyNoMatch',
'RationalInterpolantPivotedGreedy',
'RationalInterpolantGreedyPivotedGreedyNoMatch',
'RationalInterpolantGreedyPivotedGreedy'
]
diff --git a/rrompy/reduction_methods/base/__init__.py b/rrompy/reduction_methods/base/__init__.py
index 3a18513..8eec21a 100644
--- a/rrompy/reduction_methods/base/__init__.py
+++ b/rrompy/reduction_methods/base/__init__.py
@@ -1,27 +1,17 @@
# 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 .rational_interpolant_utils import checkRobustTolerance
-from .reduced_basis_utils import projectAffineDecomposition
-
-__all__ = [
- 'checkRobustTolerance',
- 'projectAffineDecomposition'
- ]
-
-
diff --git a/rrompy/reduction_methods/base/generic_approximant.py b/rrompy/reduction_methods/base/generic_approximant.py
index 345fc79..fd56144 100644
--- a/rrompy/reduction_methods/base/generic_approximant.py
+++ b/rrompy/reduction_methods/base/generic_approximant.py
@@ -1,918 +1,928 @@
# 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 collections.abc import Iterable
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.sampling import (SamplingEngine, SamplingEngineNormalize,
+ SamplingEnginePOD)
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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. full POD;
- '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;
+ - 'POD': kind of snapshots orthogonalization;
- '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.
+ POD: Kind of snapshots orthogonalization.
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"],
+ self._addParametersToList(["POD", "scaleFactorDer"], [1, "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("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
+ if self.POD == 1:
+ sEng = SamplingEnginePOD
+ elif self.POD == 1/2:
+ sEng = SamplingEngineNormalize
else:
- SamplingEngine = SamplingEngineStandard
- self.samplingEngine = SamplingEngine(self.HFEngine,
- sample_state = self.approx_state,
- verbosity = self.verbosity)
+ sEng = SamplingEngine
+ self.samplingEngine = sEng(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)
+ def mapParameterList(self, *args, **kwargs):
+ return self.HFEngine.mapParameterList(*args, **kwargs)
+
@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
+ if POD not in [0, 1/2, 1]:
+ raise RROMPyException("POD must be either 0, 1/2, or 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__"):
+ elif isinstance(scaleFactorDer, Iterable):
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."))
+ "properly initialized.")) from None
@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."))
+ "data.__dict__ properties.")) from None
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 = 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 = 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 = 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 = 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 = 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):
+ if not (self.POD == 1 and self.HFEngine.isCEye):
return self.HFEngine.norm(self.getApprox(mu), is_state = False)
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/rational_interpolant_utils.py b/rrompy/reduction_methods/base/rational_interpolant_utils.py
deleted file mode 100644
index 72b1f56..0000000
--- a/rrompy/reduction_methods/base/rational_interpolant_utils.py
+++ /dev/null
@@ -1,32 +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 Np1D
-
-__all__ = ['checkRobustTolerance']
-
-def checkRobustTolerance(ev:Np1D, tol:float) -> dict:
- """
- Perform robustness check on eigen-/singular values and return reduced
- parameters with warning.
- """
- ev /= np.max(ev)
- ts = tol * np.linalg.norm(ev)
- return len(ev) - np.sum(np.abs(ev) >= ts)
-
diff --git a/rrompy/reduction_methods/pivoted/gather_pivoted_approximant.py b/rrompy/reduction_methods/pivoted/gather_pivoted_approximant.py
index 31c1f7a..b8a9062 100644
--- a/rrompy/reduction_methods/pivoted/gather_pivoted_approximant.py
+++ b/rrompy/reduction_methods/pivoted/gather_pivoted_approximant.py
@@ -1,99 +1,108 @@
# 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.poly_fitting.polynomial import (PolynomialInterpolator,
+ PolynomialInterpolatorNodal)
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)
+ Qnodal = isinstance(Qs[0], PolynomialInterpolatorNodal)
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]
+ shapePQ = np.array([[P.deg[0] + 1, P.shape[0], Q.deg[0] + 1 - Qnodal]
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])
+ if Qnodal:
+ Qflat = np.concatenate([Q.poles for Q in Qs])
+ else:
+ 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)
+ Qflat = np.zeros(0, dtype = np.complex)
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
+ if Qnodal:
+ Q = PolynomialInterpolatorNodal()
+ Q.poles = Qsflat[shift : shift + currSize]
+ else:
+ 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 0ca972b..1a67e27 100644
--- a/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py
+++ b/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py
@@ -1,742 +1,744 @@
# 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, remove, rmdir
import numpy as np
+from collections.abc import Iterable
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.sampling import (SamplingEngine, SamplingEngineNormalize,
+ SamplingEnginePOD)
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.degree import reduceDegreeN
from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert,
RROMPyWarning)
from rrompy.parameter import checkParameterList
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(["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
+ if self.POD == 1:
+ sEng = SamplingEnginePOD
+ elif self.POD == 1/2:
+ sEng = SamplingEngineNormalize
else:
- SamplingEngine = SamplingEngineStandard
- self.samplingEngine = SamplingEngine(self.HFEngine,
- sample_state = self.approx_state,
- verbosity = self.verbosity)
+ sEng = SamplingEngine
+ self.samplingEngine = sEng(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("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)
+ def mapParameterList(self, *args, **kwargs):
+ if hasattr(self, "_temporaryPivot"):
+ return self.mapParameterListPivot(*args, **kwargs)
+ return super().mapParameterList(*args, **kwargs)
+
+ def mapParameterListPivot(self, mu:paramList, direct : str = "F",
+ idx : List[int] = None):
+ if idx is None:
+ idx = self.directionPivot
+ else:
+ idx = [self.directionPivot[j] for j in idx]
+ return super().mapParameterList(mu, direct, idx)
+
+ def mapParameterListMarginal(self, mu:paramList, direct : str = "F",
+ idx : List[int] = None):
+ if idx is None:
+ idx = self.directionMarginal
+ else:
+ idx = [self.directionMarginal[j] for j in idx]
+ return super().mapParameterList(mu, direct, idx)
+
+ @property
+ def mu0(self):
+ """Value of mu0."""
+ if hasattr(self, "_temporaryPivot"):
+ return self.checkParameterListPivot(self._mu0(self.directionPivot))
+ return self._mu0
+ @mu0.setter
+ def mu0(self, mu0):
+ GenericApproximant.mu0.fset(self, mu0)
+
@property
def mus(self):
"""Value of mus. Its assignment may reset snapshots."""
return self._mus
@mus.setter
def mus(self, mus):
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 = 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 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, radialDirWeightsMarg):
- if hasattr(radialDirWeightsMarg, "__len__"):
+ if isinstance(radialDirWeightsMarg, Iterable):
radialDirWeightsMarg = list(radialDirWeightsMarg)
else:
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 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.HFEngine.mapParameterList(self.muBounds[0],
- idx = self.directionPivot)
- - self.HFEngine.mapParameterList(self.muBounds[1],
- idx = self.directionPivot)
- )[0])
+ self.mapParameterListPivot(self.muBounds[0])
+ - self.mapParameterListPivot(self.muBounds[1]))[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.mapParameterListMarginal(self.muBoundsMarginal[0])
+ - self.mapParameterListMarginal(self.muBoundsMarginal[1]))[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 = 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,
"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
+ _PODOld, self._POD = self.POD, 0
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
+ for file in self.storedSamplesFilenames: remove(file)
+ rmdir(self._sampleBaseFilename[: -8])
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 = 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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- 'scaleFactorDer': scaling factors for derivative computation;
defaults to 'AUTO';
- '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;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- '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.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
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):
self.trainedModel.setupMarginalInterp(
[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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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';
- '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';
. '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' or
+ 'PIECEWISE_LINEAR_*';
. '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;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchingWeight': weight for pole matching optimization;
- - 'matchingMode': mode for pole matching optimization;
- '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;
. '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.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchingWeight: Weight for pole matching optimization.
- matchingMode: Mode for pole matching optimization.
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",
- "sharedRatio", "polybasisMarginal",
- "paramsMarginal"],
- [1., "NONE", 1., "MONOMIAL", {}])
+ self._addParametersToList(["matchingWeight", "sharedRatio",
+ "polybasisMarginal", "paramsMarginal"],
+ [1., 1., "MONOMIAL", {}])
self.parameterMarginalList = ["MMarginal", "nNeighborsMarginal",
"polydegreetypeMarginal",
"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 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"]
+ paramsMbadkeys += ["MMarginal", "polydegreetypeMarginal",
+ "interpRcondMarginal"]
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", "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 = np.array([w * f for w, f in zip(
self.radialDirectionalWeightsMarginal,
self.scaleFactorMarginal)])
if self.polybasisMarginal in ppb + rbpb + sk:
- addPars = []
+ interpPars = [self.polybasisMarginal]
if self.polybasisMarginal in ppb + rbpb:
- if self.polybasisMarginal in rbpb: addPars += [rDWMEff]
- addPars += [self.verbosity >= 5,
+ if self.polybasisMarginal in rbpb: interpPars += [rDWMEff]
+ interpPars += [self.verbosity >= 5,
self.paramsMarginal["polydegreetypeMarginal"] == "TOTAL"]
if self.polybasisMarginal in ppb:
- addPars += [{}]
+ interpPars += [{}]
else: # if self.polybasisMarginal in rbpb:
- addPars += [{"optimizeScalingBounds":self.paramsMarginal[
+ interpPars += [{"optimizeScalingBounds":self.paramsMarginal[
"radialDirectionalWeightsMarginalAdapt"]}]
+ interpPars += [
+ {"rcond":self.paramsMarginal["interpRcondMarginal"]}]
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]
- 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 6f1960b..423b63d 100644
--- a/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py
+++ b/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py
@@ -1,828 +1,732 @@
# 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 collections.abc import Iterable
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.numerical import pointMatching
+from rrompy.utilities.numerical.point_distances import chordalMetricAngleMatrix
from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert,
RROMPyWarning)
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"]
+ _allowedEstimatorKindsMarginal = ["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])
+ [0., "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__"):
+ elif isinstance(scaleFactorDer, Iterable):
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, :]
+ : len(polesAp), :]
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)
+ dist = chordalMetricAngleMatrix(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)
+ polesEx = HITest.poles
+ idxGood = np.logical_not(np.logical_or(np.isinf(polesEx),
+ np.isnan(polesEx)))
+ polesEx = polesEx[idxGood]
+ if self.matchingWeightError != 0:
+ resEx = HITest.coeffs[np.where(idxGood)[0]]
+ else:
+ resEx = None
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":
+ if self.errorEstimatorKindMarginal == "NONE":
+ nErr = len(self.trainedModel.data.musMarginal)
+ self._musMarginalTestIdxs = np.arange(nErr)
+ err = (1. + self.greedyTolMarginal) * np.ones(nErr)
+ else:#if 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()):
+ and masterCore() and hasattr(self.trainedModel, "_musMExcl")):
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)
+ 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 = 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)
+ idxAdded = self.samplerMarginal.refine(muidx)[0]
self._addMarginalSample(self.samplerMarginal.points[idxAdded])
errorEstTest, muidx, maxErrorEst = self.errorEstimatorMarginal(True)
if plotEst == "ALL":
self.plotEstimatorMarginal(errorEstTest, 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:Np2D, pMat:Np2D, Ps:ListAny,
Qs:ListAny, sizes:ListAny):
self.scaleFactor = self._scaleFactorOldPivot
del self._scaleFactorOldPivot, self._temporaryPivot
pMat, Ps, Qs, mus, nsamples = gatherPivotedApproximant(pMat, Ps, Qs,
mus, sizes,
self.polybasis)
if len(self._musLoc) > 0:
self._mus = self.checkParameterList(self._musLoc)
self._mus.append(mus)
else:
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 += 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,
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 += [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))
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)
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"]:
+ if self.errorEstimatorKindMarginal == "NONE":
muidx = []
+ else:#if self.errorEstimatorKindMarginal[: 10] == "LOOK_AHEAD":
+ muidx = np.arange(len(self.trainedModel.data.musMarginal))
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)
else:
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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- 'scaleFactorDer': scaling factors for derivative computation;
defaults to 'AUTO';
- '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';
+ available values include '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;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- '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.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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';
- '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';
+ available values include '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';
. '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' or
+ 'PIECEWISE_LINEAR_*';
. '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;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchingWeight': weight for pole matching optimization;
- - 'matchingMode': mode for pole matching optimization;
- '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;
. '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.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchingWeight: Weight for pole matching optimization.
- matchingMode: Mode for pole matching optimization.
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)
+ _polybasisMarginal = self.polybasisMarginal
+ self._polybasisMarginal = ("PIECEWISE_LINEAR_"
+ + self.samplerMarginal.kind)
+ setupOK = super().setupApprox(*args, **kwargs)
+ self._polybasisMarginal = _polybasisMarginal
+ self._finalizeMarginalization()
+ return setupOK
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 b6a27cc..8649fdb 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,519 +1,502 @@
#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 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[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(self.HFEngine.mapParameterList(muTestBasePivot,
- idx = self.directionPivot),
- self.HFEngine.mapParameterList(musPivot,
- idx = self.directionPivot),
+ idxPop = pruneSamples(self.mapParameterListPivot(muTestBasePivot),
+ self.mapParameterListPivot(musPivot),
1e-10 * self.scaleFactorPivot[0])
muTestBasePivot.pop(idxPop)
self._mus = emptyParameterList()
self.mus.reset((self.S - 1, self.HFEngine.npar))
self.muTest = emptyParameterList()
self.muTest.reset((len(muTestBasePivot) + 1, self.HFEngine.npar))
for k in range(self.S - 1):
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)):
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)
pMat, Ps, Qs, req, musA = None, [], [], [], None
if len(idx) == 0:
vbMng(self, "MAIN", "Idling.", 45)
if self.storeAllSamples: self.storeSamples()
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, 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(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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- 'scaleFactorDer': scaling factors for derivative computation;
defaults to 'AUTO';
- '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';
+ available values include 'LOOK_AHEAD' and 'LOOK_AHEAD_RECOVER';
+ defaults to 'NONE';
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for pivot interpolation; defaults to
None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
directionPivot: Pivot components.
mus: Array of snapshot parameters.
musMarginal: Array of marginal snapshot parameters.
approxParameters: Dictionary containing values for main parameters of
approximant. Recognized keys are in parameterList.
parameterListSoft: Recognized keys of soft approximant parameters:
- - 'POD': whether to compute POD of snapshots;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for pivot interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator via sparse
grid.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for pivot interpolation.
robustTol: Tolerance for robust rational denominator management.
- 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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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';
- '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';
+ available values include 'LOOK_AHEAD' and 'LOOK_AHEAD_RECOVER';
+ defaults to 'NONE';
- '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';
. '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' or
+ 'PIECEWISE_LINEAR_*';
. '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for pivot interpolation; defaults to
None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
directionPivot: Pivot components.
mus: Array of snapshot parameters.
musMarginal: Array of marginal snapshot parameters.
approxParameters: Dictionary containing values for main parameters of
approximant. Recognized keys are in parameterList.
parameterListSoft: Recognized keys of soft approximant parameters:
- - 'POD': whether to compute POD of snapshots;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchingWeight': weight for pole matching optimization;
- - 'matchingMode': mode for pole matching optimization;
- '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;
. '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for pivot interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator via sparse
grid.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchingWeight: Weight for pole matching optimization.
- matchingMode: Mode for pole matching optimization.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for pivot interpolation.
robustTol: Tolerance for robust rational denominator management.
- 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 26ad0ea..724a683 100644
--- a/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py
+++ b/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py
@@ -1,446 +1,428 @@
# 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, 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 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):
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_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)
pMat, Ps, Qs, req, musA = None, [], [], [], None
if len(idx) == 0:
vbMng(self, "MAIN", "Idling.", 45)
if self.storeAllSamples: self.storeSamples()
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.parameterMap = self._m_HFEparameterMap
- del self._m_mu0, self._m_selfmus, self._m_HFEparameterMap
+ del self._m_selfmus, self._m_HFEparameterMap
if self.storeAllSamples: self.storeSamples(i + self._nmusOld)
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(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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- 'scaleFactorDer': scaling factors for derivative computation;
defaults to 'AUTO';
- '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';
+ available values include 'LOOK_AHEAD' and 'LOOK_AHEAD_RECOVER';
+ defaults to 'NONE';
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for pivot interpolation; defaults to
None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
directionPivot: Pivot components.
mus: Array of snapshot parameters.
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;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for pivot interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator via sparse
grid.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for pivot interpolation.
robustTol: Tolerance for robust rational denominator management.
- 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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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';
- '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';
+ available values include 'LOOK_AHEAD' and 'LOOK_AHEAD_RECOVER';
+ defaults to 'NONE';
- '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';
. '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' or
+ 'PIECEWISE_LINEAR_*';
. '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for pivot interpolation; defaults to
None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
directionPivot: Pivot components.
mus: Array of snapshot parameters.
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;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchingWeight': weight for pole matching optimization;
- - 'matchingMode': mode for pole matching optimization;
- '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;
. '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for pivot interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator via sparse
grid.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchingWeight: Weight for pole matching optimization.
- matchingMode: Mode for pole matching optimization.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for pivot interpolation.
robustTol: Tolerance for robust rational denominator management.
- 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 fb9f77e..d196135 100644
--- a/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py
+++ b/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py
@@ -1,567 +1,527 @@
# 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.utilities.exception_manager import RROMPyAssert
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()
+ super().__init__(*args, **kwargs)
+ self._ignoreResidues = self.nparPivot > 1
+ self._postInit()
+
@property
def tModelType(self):
if hasattr(self, "_temporaryPivot"):
return RationalInterpolantGreedy.tModelType.fget(self)
return super().tModelType
-
- @property
- 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 _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_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.parameterMap = self._m_HFEparameterMap
- del self._m_mu0, self._m_selfmus, self._m_HFEparameterMap
+ del 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.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 = 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
+ self.trainedModel.data.Q._dirPivot = self.directionPivot[0]
+ self.trainedModel.data.P._dirPivot = self.directionPivot[0]
else:
self._temporaryPivot = 1
self.trainedModel.data.mu0 = self.checkParameterListPivot(
self.mu0(self.directionPivot))
self.trainedModel.data.scaleFactor = self.scaleFactor
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
+ del self.trainedModel.data.Q._dirPivot
+ del self.trainedModel.data.P._dirPivot
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(self.HFEngine.mapParameterList(muTestPivot,
- idx = self.directionPivot),
- self.HFEngine.mapParameterList(musPivot,
- idx = self.directionPivot),
+ idxPop = pruneSamples(self.mapParameterListPivot(muTestPivot),
+ self.mapParameterListPivot(musPivot),
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):
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)):
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 = parameterList(muTestBase)
self.muTest.append(muLast)
self.M, self.N = ("AUTO",) * 2
+ def setupApproxLocal(self) -> int:
+ """Compute rational interpolant."""
+ self._marginalizeMiscellanea(True)
+ setupOK = super().setupApproxLocal()
+ self._marginalizeMiscellanea(False)
+ return setupOK
+
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)
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, 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 += [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))
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()
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, 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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- 'scaleFactorDer': scaling factors for derivative computation;
defaults to 'AUTO';
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for pivot interpolation; defaults to
None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
directionPivot: Pivot components.
mus: Array of snapshot parameters.
musMarginal: Array of marginal snapshot parameters.
approxParameters: Dictionary containing values for main parameters of
approximant. Recognized keys are in parameterList.
parameterListSoft: Recognized keys of soft approximant parameters:
- - 'POD': whether to compute POD of snapshots;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for pivot interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for pivot interpolation.
robustTol: Tolerance for robust rational denominator management.
- 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.
"""
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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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';
- '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';
. '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' or
+ 'PIECEWISE_LINEAR_*';
. '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for pivot interpolation; defaults to
None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
directionPivot: Pivot components.
mus: Array of snapshot parameters.
musMarginal: Array of marginal snapshot parameters.
approxParameters: Dictionary containing values for main parameters of
approximant. Recognized keys are in parameterList.
parameterListSoft: Recognized keys of soft approximant parameters:
- - 'POD': whether to compute POD of snapshots;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchingWeight': weight for pole matching optimization;
- - 'matchingMode': mode for pole matching optimization;
- '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;
. '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for pivot interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchingWeight: Weight for pole matching optimization.
- matchingMode: Mode for pole matching optimization.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for pivot interpolation.
robustTol: Tolerance for robust rational denominator management.
- 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.
"""
diff --git a/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py b/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py
index 71ba112..4af819a 100644
--- a/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py
+++ b/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py
@@ -1,469 +1,456 @@
# 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 collections.abc import Iterable
+from copy import deepcopy as copy
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 poolRank, indicesScatter, isend, recv
__all__ = ['RationalInterpolantPivotedNoMatch', 'RationalInterpolantPivoted']
class RationalInterpolantPivotedBase(GenericPivotedApproximantBase,
RationalInterpolant):
def __init__(self, *args, **kwargs):
self._preInit()
self._addParametersToList(toBeExcluded = ["polydegreetype"])
super().__init__(*args, **kwargs)
+ self._ignoreResidues = self.nparPivot > 1
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__"):
+ elif isinstance(scaleFactorDer, Iterable):
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 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.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):
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)
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 = 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[self.S * i : self.S * (i + 1)])
vbMng(self, "DEL", "Done computing snapshots.", 10)
self.verbosity -= 5
self.samplingEngine.verbosity -= 5
- self._setupRational(self._setupDenominator()[0])
+ self._setupRational(self._setupDenominator())
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 += [isend((len(pMat), pMat.dtype), dest = dest,
tag = dest)]
else:
pMat = np.hstack((pMat,
self.samplingEngine.projectionMatrix))
Ps += [copy(self.trainedModel.data.P)]
Qs += [copy(self.trainedModel.data.Q)]
del self.trainedModel.data.Q, self.trainedModel.data.P
self.N = N0
del self._temporaryPivot
self.scaleFactor = _scaleFactorOldPivot
for r in req: r.wait()
pMat, Ps, Qs, _, _ = gatherPivotedApproximant(pMat, Ps, Qs,
self.mus.data, sizes,
self.polybasis, False)
self._setupTrainedModel(pMat)
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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- 'scaleFactorDer': scaling factors for derivative computation;
defaults to 'AUTO';
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for pivot interpolation; defaults to
None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
directionPivot: Pivot components.
mus: Array of snapshot parameters.
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;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for pivot interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for pivot interpolation.
robustTol: Tolerance for robust rational denominator management.
- 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.
"""
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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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';
- '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';
. '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' or
+ 'PIECEWISE_LINEAR_*';
. '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for pivot interpolation; defaults to
None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
directionPivot: Pivot components.
mus: Array of snapshot parameters.
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;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchingWeight': weight for pole matching optimization;
- - 'matchingMode': mode for pole matching optimization;
- '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;
. '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for pivot interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchingWeight: Weight for pole matching optimization.
- matchingMode: Mode for pole matching optimization.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for pivot interpolation.
robustTol: Tolerance for robust rational denominator management.
- 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.
"""
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 a8414b5..4f0f036 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,295 +1,293 @@
# 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 (Np2D, ListAny, paramVal, paramList,
HFEng)
from rrompy.utilities.base import verbosityManager as vbMng
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 (sparsekinds,
PiecewiseLinearInterpolator as PLI)
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 = 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, extraPar = None):
vbMng(self, "INIT", "Starting computation of marginal interpolator.",
12)
musMCN = self.centerNormalizeMarginal(self.data.musMarginal)
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 len(pts) == 0:
raise RROMPyException("Empty list of support points.")
musMCNEff, valsEff = musMCN[pts], np.eye(len(pts))
if pbM in ppb + rbpb:
if extraPar:
if ipts > 0:
verb = approx.verbosity
approx.verbosity = 0
_musM = approx.musMarginal
approx.musMarginal = musMCNEff
approx._setMMarginalAuto()
approx.musMarginal = _musM
approx.verbosity = verb
else:
approx.paramsMarginal["MMarginal"] = reduceDegreeN(
_MMarginalEff, len(musMCNEff), self.data.nparMarginal,
approx.paramsMarginal["polydegreetypeMarginal"])
MMEff = approx.paramsMarginal["MMarginal"]
while MMEff >= 0:
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."))
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],
- *interpPars)
- vbMng(self, "MAIN", msg, 30)
- if not wellCond:
- vbMng(self, "MAIN",
- "Warning: polyfit is poorly conditioned.", 35)
+ p.setupByInterpolation(musMCNEff, valsEff, pllims,
+ extraPar[pts], *interpPars)
if ipts == 0:
self.data.marginalInterp = copy(p)
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), 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]
idxBad = np.where(self.data.suppEffIdx == ipts)[0]
warnings.simplefilter('ignore', SparseEfficiencyWarning)
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:
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):
+ basis:str, matchingWeight:float, 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)
+ Ns = [len(pls) for pls in poles]
+ poles, coeffs = heavisideUniformShape(poles, coeffs)
+ root = Ns.index(len(poles[0]))
+ poles, coeffs = rationalFunctionMatching(poles, coeffs,
+ self.data.musMarginal.data,
+ matchingWeight, supps,
+ self.data.projMat, HFEngine,
+ is_state, root)
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 checkSharedRatio(self, shared:float) -> str:
N = len(self.data.HIs[0].poles)
M = len(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.arange(N, len(self.data.HIs[0].coeffs)))
for hi in self.data.HIs:
for j in removePole:
if not np.isinf(hi.poles[j]):
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 = 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 = 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 = 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 07e6e74..62b45be 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,330 +1,328 @@
# 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 collections.abc import Iterable
+from copy import deepcopy as copy
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.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, 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 = 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, interpPars:ListAny):
self.data.marginalInterp = NNI()
self.data.marginalInterp.setupByInterpolation(self.data.musMarginal,
np.arange(len(self.data.musMarginal)),
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 interpolateMarginalInterpolator(self, mu : paramList = []) -> ListAny:
"""Obtain interpolated approximant interpolator."""
mu = self.checkParameterListMarginal(mu)
vbMng(self, "INIT", "Finding nearest neighbor to mu = {}.".format(mu),
95)
his = []
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 = 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(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 = self.checkParameterList(mu)
p = emptySampleList()
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 = 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]
+ if not isinstance(mVals, Iterable): 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:
+ rDim = mVals.index(fp)
+ if rDim < len(mVals) - 1 and fp in mVals[rDim + 1 :]:
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)[0]
mMarg = [mVals[j] for j in range(len(mVals)) if j != 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]
+ if not isinstance(mVals, Iterable): 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/__init__.py b/rrompy/reduction_methods/standard/__init__.py
index eb87cda..3f444ad 100644
--- a/rrompy/reduction_methods/standard/__init__.py
+++ b/rrompy/reduction_methods/standard/__init__.py
@@ -1,31 +1,29 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
from .nearest_neighbor import NearestNeighbor
from .rational_interpolant import RationalInterpolant
-from .rational_pade import RationalPade
from .reduced_basis import ReducedBasis
__all__ = [
'NearestNeighbor',
'RationalInterpolant',
- 'RationalPade',
'ReducedBasis'
]
diff --git a/rrompy/reduction_methods/standard/generic_standard_approximant.py b/rrompy/reduction_methods/standard/generic_standard_approximant.py
index 8c666ad..2ae7b7a 100644
--- a/rrompy/reduction_methods/standard/generic_standard_approximant.py
+++ b/rrompy/reduction_methods/standard/generic_standard_approximant.py
@@ -1,189 +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 copy import deepcopy as copy
from rrompy.reduction_methods.base.generic_approximant import (
GenericApproximant)
from rrompy.utilities.base import verbosityManager as vbMng
from rrompy.utilities.base.types import Np2D
from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert,
RROMPyWarning)
__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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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;
+ - 'POD': kind of snapshots orthogonalization;
- '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.
+ POD: Kind of snapshots orthogonalization.
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 = 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)):
+ if self.POD != 0 and (self.samplingEngine.nsamples
+ != len(self.samplingEngine.samples_normal)):
RROMPyWarning(("Assigning non-POD sampling engine to POD "
"approximant is unstable. Declassing local "
- "POD to False."))
- self._POD = False
+ "POD to 0."))
+ self._POD = 0
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.")
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.HFEngine.mapParameterList(self.muBounds[0])
- - self.HFEngine.mapParameterList(self.muBounds[1]))[0])
+ self.mapParameterList(self.muBounds[0])
+ - self.mapParameterList(self.muBounds[1]))[0])
def _setupTrainedModel(self, pMat:Np2D, pMatUpdate : bool = False):
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,
"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 30b923a..24a3468 100644
--- a/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py
+++ b/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py
@@ -1,644 +1,645 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
from 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.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 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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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;
+ - 'POD': kind of snapshots orthogonalization;
- '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.
+ POD: Kind of snapshots orthogonalization.
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"])
+ "nTestPoints", "trainSetGenerator"],
+ [1e-2, 0., 1e2, 5e2, "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 = self.checkParameterList(mus)
tMverb, self.trainedModel.verbosity = self.trainedModel.verbosity, 0
uApproxRs = self.getApproxReduced(mus).data
self.trainedModel.verbosity = tMverb
- muTestEff = self.HFEngine.mapParameterList(mus)
+ muTestEff = self.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, 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 = 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]
+ if self.POD == 1:
+ reff = self.samplingEngine.Rscale[:, -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 = 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[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.")
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(self.HFEngine.mapParameterList(muTestBase),
- self.HFEngine.mapParameterList(self.mus),
+ idxPop = pruneSamples(self.mapParameterList(muTestBase),
+ self.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)
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 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()
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:
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)
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 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 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 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 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 232335e..d1a0787 100644
--- a/rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py
+++ b/rrompy/reduction_methods/standard/greedy/rational_interpolant_greedy.py
@@ -1,538 +1,537 @@
# 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,
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.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.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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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';
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for meaning); defaults to 'NORM';
- 'interpRcond': tolerance for interpolation; defaults to None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults and must
be True.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
mus: Array of snapshot parameters.
approxParameters: Dictionary containing values for main parameters of
approximant. Recognized keys are in parameterList.
parameterListSoft: Recognized keys of soft approximant parameters:
- - 'POD': whether to compute POD of snapshots;
+ - 'POD': kind of snapshots orthogonalization;
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for interpolation;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of samples current approximant relies upon;
- 'sampler': sample point generator.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
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.
- cutOffTolerance: Tolerance for ignoring parasitic poles.
- residueTol: Tolerance for residue elimination.
errorEstimatorKind: kind of error estimator.
+ functionalSolve: Strategy for minimization of denominator functional.
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 polyvander(mus, deg, *args)
def getErrorEstimatorDiscrepancy(self, mus:Np1D) -> Np1D:
"""Discrepancy-based residual estimator."""
checkIfAffine(self.HFEngine, "apply discrepancy-based error estimator")
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.HFEngine.mapParameterList(self.mus)
- muTestEff = self.HFEngine.mapParameterList(mus)
+ muTrainEff = self.mapParameterList(self.mus)
+ muTestEff = self.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)
else:
app_muTestSample = dot(self.samplingEngine.projectionMatrix,
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))
app_muTestSample = sampleList(self.HFEngine.applyC(
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 = 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)
+ coeffTest = np.linalg.solve(vanTrial, valuesTrial)
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 = 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
+ unstable = 0
if self.E > 0:
try:
- Q = self._setupDenominator()[0]
+ Q = self._setupDenominator()
except RROMPyException as RE:
+ if RE.critical: raise RE from None
setVerbosityDepth(vbDepth)
RROMPyWarning("Downgraded {}: {}".format(RE.__class__.__name__,
RE))
- unstable = True
+ unstable = 1
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:
+ if RE.critical: raise RE from None
setVerbosityDepth(vbDepth)
RROMPyWarning("Downgraded {}: {}".format(RE.__class__.__name__,
RE))
- unstable = True
+ unstable = 1
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
+ return unstable
def setupApprox(self, plotEst : str = "NONE") -> int:
val = super().setupApprox(plotEst)
if val == 0:
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/greedy/reduced_basis_greedy.py b/rrompy/reduction_methods/standard/greedy/reduced_basis_greedy.py
index 57313d4..6747697 100644
--- a/rrompy/reduction_methods/standard/greedy/reduced_basis_greedy.py
+++ b/rrompy/reduction_methods/standard/greedy/reduced_basis_greedy.py
@@ -1,148 +1,149 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
from copy import deepcopy as copy
from .generic_greedy_approximant import GenericGreedyApproximant
from rrompy.reduction_methods.standard import ReducedBasis
from rrompy.utilities.base import verbosityManager as vbMng
from rrompy.utilities.exception_manager import RROMPyWarning, RROMPyAssert
__all__ = ['ReducedBasisGreedy']
class ReducedBasisGreedy(GenericGreedyApproximant, ReducedBasis):
"""
ROM greedy RB approximant computation for parametric problems.
Args:
HFEngine: HF problem solver.
mu0(optional): Default parameter. Defaults to 0.
approxParameters(optional): Dictionary containing values for main
parameters of approximant. Recognized keys are:
- - 'POD': whether to compute POD of snapshots; defaults to True;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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 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;
+ - 'POD': kind of snapshots orthogonalization;
- '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.
+ POD: Kind of snapshots orthogonalization.
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.
As: List of sparse matrices (in CSC format) representing coefficients
of linear system matrix.
bs: List of numpy vectors representing coefficients of linear system
RHS.
ARBs: List of sparse matrices (in CSC format) representing coefficients
of compressed linear system matrix.
bRBs: List of numpy vectors representing coefficients of compressed
linear system RHS.
"""
def __init__(self, *args, **kwargs):
self._preInit()
self._addParametersToList(toBeExcluded = ["R", "PODTolerance"])
super().__init__(*args, **kwargs)
self._postInit()
@property
def PODTolerance(self):
"""Value of PODTolerance."""
self._PODTolerance = -1
return self._PODTolerance
@PODTolerance.setter
def PODTolerance(self, PODTolerance):
RROMPyWarning(("PODTolerance is used just to simplify inheritance, "
"and its value cannot be changed from -1."))
def setupApproxLocal(self) -> int:
"""Compute RB projection matrix."""
if self.checkComputedApprox(): return -1
RROMPyAssert(self._mode, message = "Cannot setup approximant.")
self.verbosity -= 10
vbMng(self, "INIT", "Setting up local approximant.", 5)
- vbMng(self, "INIT", "Computing projection matrix.", 7)
+ vbMng(self, "INIT", "Computing projection matrix.", 15)
pMatOld, pMat = None, self.samplingEngine.projectionMatrix
if self.trainedModel is not None:
Sold = len(self.trainedModel.data.mus)
pMatOld, pMat = pMat[:, : Sold], pMat[:, Sold :]
- vbMng(self, "DEL", "Done computing projection matrix.", 7)
+ vbMng(self, "DEL", "Done computing projection matrix.", 15)
setData = self.trainedModel is None
self._setupTrainedModel(pMat, not setData)
if setData:
self.trainedModel.data.affinePoly = self.HFEngine.affinePoly
self.trainedModel.data.thAs = self.HFEngine.thAs
self.trainedModel.data.thbs = self.HFEngine.thbs
ARBs, bRBs = self.assembleReducedSystem(pMat, pMatOld)
self.trainedModel.data.ARBs = ARBs
self.trainedModel.data.bRBs = bRBs
self.trainedModel.data.approxParameters = copy(self.approxParameters)
vbMng(self, "DEL", "Done setting up local approximant.", 5)
self.verbosity += 10
return 0
diff --git a/rrompy/reduction_methods/standard/nearest_neighbor.py b/rrompy/reduction_methods/standard/nearest_neighbor.py
index 932856c..c2fe8d8 100644
--- a/rrompy/reduction_methods/standard/nearest_neighbor.py
+++ b/rrompy/reduction_methods/standard/nearest_neighbor.py
@@ -1,165 +1,170 @@
# 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 collections.abc import Iterable
+from copy import deepcopy as copy
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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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;
+ - 'POD': kind of snapshots orthogonalization;
- '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.
+ POD: Kind of snapshots orthogonalization.
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.])
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__"):
+ if isinstance(radialDirectionalWeights, Iterable):
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 self.POD == 1:
+ R = self.samplingEngine.Rscale
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)
+ if self.POD == 0:
+ vals = [np.ones(1)] * len(self.mus)
+ else:
+ vals = list(self.samplingEngine.Rscale.reshape(-1, 1))
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 6cfedf6..e62a496 100644
--- a/rrompy/reduction_methods/standard/rational_interpolant.py
+++ b/rrompy/reduction_methods/standard/rational_interpolant.py
@@ -1,631 +1,840 @@
# 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 scipy.linalg import eigvals
+from collections.abc import Iterable
from .generic_standard_approximant import GenericStandardApproximant
from rrompy.utilities.poly_fitting.polynomial import (
- polybases as ppb, polyfitname,
- polyvander as pvP, polyTimes,
- polyTimesTable, vanderInvTable,
- PolynomialInterpolator as PI)
+ polybases as ppb, polyfitname,
+ polyvander as pvP, polyTimes,
+ PolynomialInterpolator as PI,
+ PolynomialInterpolatorNodal as PIN)
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,
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 import (pseudoInverse, dot, potential,
+ distanceMatrix)
+from rrompy.utilities.numerical.factorials import multifactorial
+from rrompy.utilities.numerical.hash_derivative import (nextDerivativeIndices,
+ hashDerivativeToIdx as hashD,
+ hashIdxToDerivative as hashI)
from rrompy.utilities.numerical.degree import (reduceDegreeN,
degreeTotalToFull, fullDegreeMaxMask,
totalDegreeMaxMask)
+from rrompy.solver import Np2DLikeGramian
from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert,
RROMPyWarning)
__all__ = ['RationalInterpolant']
+def polyTimesTable(P:interpEng, mus:Np1D, reorder:List[int],
+ derIdxs:List[List[List[int]]], scl : Np1D = None) -> Np2D:
+ """Table of polynomial products."""
+ if not isinstance(P, PI):
+ raise RROMPyException(("Polynomial to evaluate must be a polynomial "
+ "interpolator."))
+ Pvals = [[0.] * len(derIdx) for derIdx in derIdxs]
+ for j, derIdx in enumerate(derIdxs):
+ nder = len(derIdx)
+ for der in range(nder):
+ derI = hashI(der, P.npar)
+ Pvals[j][der] = P([mus[j]], derI, scl) / multifactorial(derI)
+ return blockDiagDer(Pvals, reorder, derIdxs)
+
+def vanderInvTable(vanInv:Np2D, idxs:List[int], reorder:List[int],
+ derIdxs:List[List[List[int]]]) -> Np2D:
+ """Table of Vandermonde pseudo-inverse."""
+ S = len(reorder)
+ Ts = [None] * len(idxs)
+ for k in range(len(idxs)):
+ invLocs = [None] * len(derIdxs)
+ idxGlob = 0
+ for j, derIdx in enumerate(derIdxs):
+ nder = len(derIdx)
+ idxGlob += nder
+ idxLoc = np.arange(S)[np.logical_and(reorder >= idxGlob - nder,
+ reorder < idxGlob)]
+ invLocs[j] = vanInv[k, idxLoc]
+ Ts[k] = blockDiagDer(invLocs, reorder, derIdxs, [2, 1, 0])
+ return Ts
+
+def blockDiagDer(vals:List[Np1D], reorder:List[int],
+ derIdxs:List[List[List[int]]],
+ permute : List[int] = None) -> Np2D:
+ """Table of derivative values for point confluence."""
+ S = len(reorder)
+ T = np.zeros((S, S), dtype = np.complex)
+ if permute is None: permute = [0, 1, 2]
+ idxGlob = 0
+ for j, derIdx in enumerate(derIdxs):
+ nder = len(derIdx)
+ idxGlob += nder
+ idxLoc = np.arange(S)[np.logical_and(reorder >= idxGlob - nder,
+ reorder < idxGlob)]
+ val = vals[j]
+ for derI, derIdxI in enumerate(derIdx):
+ for derJ, derIdxJ in enumerate(derIdx):
+ diffIdx = [x - y for (x, y) in zip(derIdxI, derIdxJ)]
+ if all([x >= 0 for x in diffIdx]):
+ diffj = hashD(diffIdx)
+ i1, i2, i3 = np.array([derI, derJ, diffj])[permute]
+ T[idxLoc[i1], idxLoc[i2]] = val[i3]
+ return T
+
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;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- '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];
+ - 'functionalSolve': strategy for minimization of denominator
+ functional; allowed values include 'NORM', 'DOMINANT', 'NODAL',
+ 'BARYCENTRIC_NORM', and 'BARYCENTRIC[_AVERAGE]' (check pdf in
+ main folder for explanation); defaults to 'NORM';
- 'interpRcond': tolerance for interpolation; defaults to None;
- 'robustTol': tolerance for robust rational denominator
- management; defaults to 0;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- defaults to np.inf;
- - 'residueTol': tolerance for residue elimination; defaults to 0.,
- i.e. no bad residues.
+ management; defaults to 0.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults to
False.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
mus: Array of snapshot parameters.
approxParameters: Dictionary containing values for main parameters of
approximant. Recognized keys are in parameterList.
parameterListSoft: Recognized keys of soft approximant parameters:
- - 'POD': whether to compute POD of snapshots;
+ - 'POD': kind of snapshots orthogonalization;
- '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;
+ - 'functionalSolve': strategy for minimization of denominator
+ functional;
- 'interpRcond': tolerance for interpolation via numpy.polyfit;
- 'robustTol': tolerance for robust rational denominator
- management;
- - 'cutOffTolerance': tolerance for ignoring parasitic poles;
- - 'residueTol': tolerance for residue elimination.
+ management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of samples current approximant relies upon;
- 'sampler': sample point generator.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
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.
+ functionalSolve: Strategy for minimization of denominator functional.
interpRcond: Tolerance for interpolation via numpy.polyfit.
robustTol: Tolerance for robust rational denominator management.
- 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.
"""
+
+ _allowedFunctionalSolveKinds = ["NORM", "DOMINANT", "NODAL",
+ "BARYCENTRIC_NORM", "BARYCENTRIC_AVERAGE"]
def __init__(self, *args, **kwargs):
self._preInit()
self._addParametersToList(["polybasis", "M", "N", "polydegreetype",
"radialDirectionalWeights",
"radialDirectionalWeightsAdapt",
- "interpRcond", "robustTol",
- "cutOffTolerance", "residueTol"],
+ "functionalSolve", "interpRcond",
+ "robustTol"],
["MONOMIAL", "AUTO", "AUTO", "TOTAL", 1.,
- [-1., -1.], -1, 0., np.inf, 0.])
+ [-1., -1.], "NORM", -1, 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 functionalSolve(self):
+ """Value of functionalSolve."""
+ return self._functionalSolve
+ @functionalSolve.setter
+ def functionalSolve(self, functionalSolve):
+ try:
+ functionalSolve = functionalSolve.upper().strip().replace(" ","")
+ if functionalSolve == "BARYCENTRIC": functionalSolve += "_AVERAGE"
+ if functionalSolve not in self._allowedFunctionalSolveKinds:
+ raise RROMPyException(("Prescribed functionalSolve not "
+ "recognized."))
+ self._functionalSolve = functionalSolve
+ except:
+ RROMPyWarning(("Prescribed functionalSolve not recognized. "
+ "Overriding to 'NORM'."))
+ self._functionalSolve = "NORM"
+ self._approxParameters["functionalSolve"] = self.functionalSolve
+
@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__"):
+ if isinstance(radialDirectionalWeights, Iterable):
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 cutOffTolerance(self):
- """Value of cutOffTolerance."""
- return self._cutOffTolerance
- @cutOffTolerance.setter
- def cutOffTolerance(self, cutOffTolerance):
- self._cutOffTolerance = cutOffTolerance
- self._approxParameters["cutOffTolerance"] = self.cutOffTolerance
-
- @property
- 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()
+ if self.functionalSolve != "NORM" and self.npar > 1:
+ RROMPyWarning(("Strategy for functional optimization must be "
+ "'NORM' for more than one parameter. "
+ "Overriding to 'NORM'."))
+ self.functionalSolve = "NORM"
+ if (self.functionalSolve[:11] == "BARYCENTRIC"
+ and self.N + 1 < self.S):
+ RROMPyWarning(("Barycentric strategy cannot be applied with "
+ "Least Squares. Overriding to 'NORM'."))
+ self.functionalSolve = "NORM"
+ if self.functionalSolve[:11] == "BARYCENTRIC":
+ invD, TN = None, None
+ self._setupInterpolationIndices()
+ else:
+ invD, TN = self._computeInterpolantInverseBlocks()
+ if (self.functionalSolve in ["NODAL", "BARYCENTRIC_NORM",
+ "BARYCENTRIC_AVERAGE"]
+ and len(self._musUnique) != len(self.mus)):
+ if self.functionalSolve[:11] == "BARYCENTRIC":
+ warnflag = "Barycentric"
+ invD, TN = self._computeInterpolantInverseBlocks()
+ else:
+ warnflag = "Iterative"
+ RROMPyWarning(("{} functional optimization cannot be applied "
+ "to repeated samples. Overriding to "
+ "'NORM'.").format(warnflag))
+ self.functionalSolve = "NORM"
idxSamplesEff = list(range(self.S))
- if self.POD:
+ if self.POD == 1:
ev, eV = self.findeveVGQR(
- self.samplingEngine.RPOD[:, idxSamplesEff], invD)
+ self.samplingEngine.Rscale[:, idxSamplesEff], invD, TN)
+ elif self.POD == 1/2:
+ ev, eV = self.findeveVGExplicit(
+ self.samplingEngine.samples_normal(idxSamplesEff),
+ invD, TN, self.samplingEngine.Rscale)
else:
ev, eV = self.findeveVGExplicit(
- self.samplingEngine.samples(idxSamplesEff), invD)
- nevBad = checkRobustTolerance(ev, self.robustTol)
- if nevBad <= 1: break
- if self.catchInstability > 0:
+ self.samplingEngine.samples(idxSamplesEff), invD, TN)
+ if self.functionalSolve == "NODAL": break
+ evR = ev / np.max(ev)
+ ts = self.robustTol * np.linalg.norm(evR)
+ nevBad = len(ev) - np.sum(np.abs(evR) >= ts)
+ if nevBad <= (self.functionalSolve == "NORM"): break
+ if self.catchInstability:
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])
+ if self.N > 0 and self.functionalSolve in ["NODAL", "BARYCENTRIC_NORM",
+ "BARYCENTRIC_AVERAGE"]:
+ q = PIN()
+ q.polybasis, q.nodes = self.polybasis0, eV
else:
- q.coeffs = eV[:, 0].reshape([self.N + 1] * self.npar)
+ 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)
+ else:
+ q.coeffs = eV.reshape([self.N + 1] * self.npar)
vbMng(self, "DEL", "Done computing denominator.", 7)
- return q, fitinv
+ return q
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 self.POD == 1:
+ Qevaldiag = Qevaldiag.dot(self.samplingEngine.Rscale.T)
+ elif self.POD == 1/2:
+ Qevaldiag = Qevaldiag * self.samplingEngine.Rscale
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.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 = 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, *pParRest)
vbMng(self, "MAIN", msg, 5)
if wellCond: break
- if self.catchInstability > 0:
+ if self.catchInstability:
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._setupRational(self._setupDenominator()[0])
+ self._setupRational(self._setupDenominator())
self.trainedModel.data.approxParameters = copy(self.approxParameters)
vbMng(self, "DEL", "Done setting up approximant.", 5)
return 0
def _setupRational(self, Q:interpEng, P : interpEng = None):
vbMng(self, "INIT", "Starting approximant finalization.", 5)
- computeNum = P is None
+ self.trainedModel.data.Q = Q
+ if P is None: P = self._setupNumerator()
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)
+ #check for bad poles
+ pls = Q.roots()
+ idxBad = self.HFEngine.flagBadPolesResidues(pls, relative = True)
+ plsN = self.mapParameterList(self.mapParameterList(self.mu0)(0, 0)
+ + self.scaleFactor * pls, "B")(0)
+ idxBad = np.logical_or(self.HFEngine.flagBadPolesResidues(pls,
+ relative = True),
+ self.HFEngine.flagBadPolesResidues(plsN))
+ if np.any(idxBad):
+ vbMng(self, "MAIN",
+ "Removing {} spurious poles out of {} due to poles."\
+ .format(np.sum(idxBad), self.N), 10)
+ if isinstance(Q, PIN):
+ Q.nodes = Q.nodes[np.logical_not(idxBad)]
+ else:
Q = PI()
Q.npar = self.npar
Q.polybasis = self.polybasis0
Q.coeffs = np.ones(1, dtype = np.complex)
- for pl in pls[idKeep]:
+ for pl in pls[np.logical_not(idxBad)]:
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()
+ self.trainedModel.data.Q = Q
+ self.N = Q.deg[0]
+ P = self._setupNumerator()
+ if (not hasattr(self.HFEngine, "_ignoreResidues")
+ or not self.HFEngine._ignoreResidues):
+ #check for bad residues
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)
+ cfs = cfs[: self.N].T
+ if self.POD != 1:
+ cfs = self.samplingEngine.projectionMatrix.dot(cfs)
+ foci = self.sampler.normalFoci()
+ ground = self.sampler.groundPotential()
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:
+ cfs[:, np.isinf(pls)] = 0.
+ cfs /= potEff # rescale by potential
+ idxBad = self.HFEngine.flagBadPolesResidues(pls, cfs)
+ if np.any(idxBad):
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.],
+ ("Removing {} spurious poles out of {} due to "
+ "residues.").format(np.sum(idxBad), self.N), 10)
+ if isinstance(Q, PIN):
+ Q.nodes = Q.nodes[np.logical_not(idxBad)]
+ else:
+ Q = PI()
+ Q.npar = self.npar
+ Q.polybasis = self.polybasis0
+ Q.coeffs = np.ones(1, dtype = np.complex)
+ for pl in pls[np.logical_not(idxBad)]:
+ 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()
+ Q.coeffs /= np.linalg.norm(Q.coeffs)
+ self.trainedModel.data.Q = Q
+ self.N = Q.deg[0]
+ 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()
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 = pvP(self._musUniqueCN, Eeff, *pvPPar)
- fitOut = customPInv(TE, rcond = self.interpRcond, full = True)
+ fullE = E + 1 == len(self._reorder) == len(self._musUniqueCN)
+ if fullE:
+ mus = self._musUniqueCN[self._reorder]
+ dist = distanceMatrix(mus, magnitude = False)[..., 0]
+ dist[np.arange(E + 1), np.arange(E + 1)] = multifactorial([E])
+ fitinvE = np.prod(dist, axis = 1) ** -1
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
+ ("Evaluating quasi-Lagrangian basis of degree {} at {} "
+ "sample points.").format(E, E + 1), 5)
+ invD = [np.diag(fitinvE)]
else:
+ 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 = pvP(self._musUniqueCN, Eeff, *pvPPar)
+ fitOut = pseudoInverse(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:
+ 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 and not fullE:
+ TN = TE
+ else: #build TN from scratch
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 = pvP(self._musUniqueCN, Neff, *pvPPar)
- for k in range(len(invD)): invD[k] = dot(invD[k], TN)
- return invD, fitinv
+ return invD, TN
- def findeveVGExplicit(self, sampleE:sampList,
- invD:List[Np2D]) -> Tuple[Np1D, Np2D]:
+ def findeveVGExplicit(self, sampleE:sampList, invD:List[Np2D], TN:Np2D,
+ Rscaling : Np1D = None) -> 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
+ if Rscaling is not None:
+ gramian = (gramian.T * Rscaling.conj()).T * Rscaling
+ if self.functionalSolve == "NODAL":
+ SEnd = invD[0].shape[1]
+ G0 = np.zeros((SEnd,) * 2, dtype = np.complex)
+ if self.functionalSolve[:11] == "BARYCENTRIC":
+ G = gramian
+ nEnd = len(gramian) - 1
+ else:
+ nEnd = TN.shape[1]
+ G = np.zeros((nEnd, nEnd), dtype = np.complex)
+ for k in range(len(invD)):
+ iDkN = dot(invD[k], TN)
+ G += dot(dot(gramian, iDkN).T, iDkN.conj()).T
+ if self.functionalSolve == "NODAL":
+ G0 += 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:
+ if self.functionalSolve in ["NORM", "BARYCENTRIC_NORM"]:
ev, eV = np.linalg.eigh(G)
- except np.linalg.LinAlgError as e:
- raise RROMPyException(e)
+ eV = eV[:, 0]
+ if self.functionalSolve == "BARYCENTRIC_NORM":
+ eV = self.findeVBarycentric(eV)
+ problem = "eigenproblem"
+ else:
+ if self.functionalSolve == "BARYCENTRIC_AVERAGE":
+ fitOut = pseudoInverse(G, rcond = self.interpRcond,
+ full = True)
+ eV = self.findeVBarycentric(np.sum(fitOut[0], axis = 1))
+ else:
+ fitOut = pseudoInverse(G[:-1, :-1], rcond = self.interpRcond,
+ full = True)
+ eV = np.append(fitOut[0].dot(G[:-1, -1]), -1.)
+ ev = fitOut[1][1][::-1]
+ problem = "linear problem"
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)
+ ("Solved {} of size {} with condition number "
+ "{:.4e}.").format(problem, nEnd, ev[-1] / ev[0]), 5)
+ if self.functionalSolve == "NODAL": eV = self.findeVNodal(eV, G0)
return ev, eV
- def findeveVGQR(self, RPODE:Np2D, invD:List[Np2D]) -> Tuple[Np1D, Np2D]:
+ def findeveVGQR(self, RPODE:Np2D, invD:List[Np2D],
+ TN: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])
+ if self.functionalSolve == "NODAL":
+ gramian = Np2DLikeGramian(None, dot(RPODE, invD[0]))
+ if self.functionalSolve[:11] == "BARYCENTRIC":
+ Rstack = RPODE
+ nEnd = RPODE.shape[1] - 1
+ else:
+ S, nEnd, eWidth = RPODE.shape[0], TN.shape[1], len(invD)
+ Rstack = np.zeros((S * eWidth, nEnd), dtype = np.complex)
+ for k in range(eWidth):
+ Rstack[k * S : (k + 1) * S, :] = dot(RPODE, dot(invD[k], TN))
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)
+ if self.functionalSolve in ["NORM", "BARYCENTRIC_NORM",
+ "BARYCENTRIC_AVERAGE"]:
+ _, ev, Vh = np.linalg.svd(Rstack, full_matrices = False)
+ if self.functionalSolve in ["NORM", "BARYCENTRIC_NORM"]:
+ eV = Vh[-1, :].conj()
+ ev = ev[::-1]
+ else: #if self.functionalSolve == "BARYCENTRIC_AVERAGE":
+ ev[np.logical_not(np.isclose(ev, 0.))] **= -2.
+ eV = Vh.T.conj().dot(ev * np.sum(Vh, axis = 1))
+ if self.functionalSolve[:11] == "BARYCENTRIC":
+ eV = self.findeVBarycentric(eV)
+ problem = "svd problem"
+ else:
+ fitOut = pseudoInverse(Rstack[:, :-1], rcond = self.interpRcond,
+ full = True)
+ ev = fitOut[1][1][::-1]
+ eV = np.append(fitOut[0].dot(Rstack[:, -1]), -1.)
+ problem = "linear problem"
+ vbMng(self, "MAIN",
+ ("Solved {} of size {} with condition number "
+ "{:.4e}.").format(problem, nEnd, ev[-1] / ev[0]), 5)
+ if self.functionalSolve == "NODAL": eV = self.findeVNodal(eV, gramian)
return ev, eV
+ def findeVBarycentric(self, baryWeights:Np1D) -> Np1D:
+ RROMPyAssert(self._mode,
+ message = "Cannot solve optimization problem.")
+ arrow = np.pad(np.diag(self._musUniqueCN[self._reorder].flatten()),
+ (1, 0), "constant", constant_values = 1.) + 0.j
+ arrow[0, 0] = 0.
+ arrow[0, 1:] = baryWeights
+ active = np.pad(np.eye(len(baryWeights)), (1, 0), "constant")
+ eV = eigvals(arrow, active)
+ return eV[np.logical_not(np.isinf(eV))]
+
+ def findeVNodal(self, q0coeffs:Np1D, gram:Np2D, maxiter : int = 25,
+ tolNewton : float = 1e-10) -> Np1D:
+ RROMPyAssert(self._mode,
+ message = "Cannot solve optimization problem.")
+ q = PI()
+ q.npar, q.polybasis, q.coeffs = self.npar, self.polybasis0, q0coeffs
+ nodes = q.roots()
+ N = len(nodes)
+ grad = np.zeros(N, dtype = np.complex)
+ hess = np.zeros((N, N), dtype = np.complex)
+ for niter in range(maxiter):
+ plDist = distanceMatrix(self._musUniqueCN[self._reorder], nodes,
+ magnitude = False)[:, :, 0]
+ q0, q = np.prod(plDist, axis = 1), []
+ for jS in range(N):
+ mask = np.arange(N) != jS
+ q += [np.prod(plDist[:, mask], axis = 1)]
+ grad[jS] = q[-1].conj().dot(gram.dot(q0))
+ for iS in range(jS + 1):
+ if iS == jS:
+ hij = 0.
+ sij = q[-1].conj().dot(gram.dot(q[-1]))
+ else:
+ mask = np.logical_and(np.arange(N) != iS,
+ np.arange(N) != jS)
+ qij = np.prod(plDist[:, mask], axis = 1)
+ hij = qij.conj().dot(gram.dot(q0))
+ sij = q[-1].conj().dot(gram.dot(q[iS]))
+ hess[jS, iS] = hij + sij
+ if iS < jS: hess[iS, jS] = hij + np.conj(sij)
+ dnodes = np.linalg.solve(hess, grad)
+ nodes += dnodes
+ tol = np.linalg.norm(dnodes) / np.linalg.norm(nodes)
+ if tol < tolNewton: break
+ if niter < maxiter:
+ vbMng(self, "MAIN",
+ ("Newton's method for problem of size {} converged "
+ "(err = {:.4e}) in {} iteration{}.").format(
+ N + 1, tol, niter + 1, "s" * (niter > 0)), 5)
+ else:
+ RROMPyWarning(("Newton's method for problem of size {} did "
+ "not converge (err = {:.4e}) after {} "
+ "iterations.").format(N + 1, tol, niter + 1))
+ return nodes
+
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
deleted file mode 100644
index 6852778..0000000
--- a/rrompy/reduction_methods/standard/rational_pade.py
+++ /dev/null
@@ -1,313 +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.reduction_methods.base import checkRobustTolerance
-from .rational_interpolant import RationalInterpolant
-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;
- - '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;
- - '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.
- 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.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 = 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],
- *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 = totalDegreeN
- else:
- cfun = fullDegreeN
- E = max(self.M, self.N)
- while E >= 0:
- Seff = cfun(E, self.npar)
- 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 = 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 = 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/reduced_basis.py b/rrompy/reduction_methods/standard/reduced_basis.py
index c45c109..849958a 100644
--- a/rrompy/reduction_methods/standard/reduced_basis.py
+++ b/rrompy/reduction_methods/standard/reduced_basis.py
@@ -1,208 +1,201 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
from copy import deepcopy as copy
import numpy as np
from .generic_standard_approximant import GenericStandardApproximant
from rrompy.hfengines.base.linear_affine_engine import checkIfAffine
-from rrompy.reduction_methods.base.reduced_basis_utils import \
- projectAffineDecomposition
+from .reduced_basis_utils import projectAffineDecomposition
from rrompy.utilities.base.types import Np1D, Np2D, List, Tuple, sampList
from rrompy.utilities.base import verbosityManager as vbMng
from rrompy.utilities.exception_manager import (RROMPyWarning, RROMPyException,
RROMPyAssert)
__all__ = ['ReducedBasis']
class ReducedBasis(GenericStandardApproximant):
"""
ROM RB approximant computation for parametric problems.
Args:
HFEngine: HF problem solver.
mu0(optional): Default parameter. Defaults to 0.
approxParameters(optional): Dictionary containing values for main
parameters of approximant. Recognized keys are:
- - 'POD': whether to compute POD of snapshots; defaults to True;
+ - 'POD': kind of snapshots orthogonalization; allowed values
+ include 0, 1/2, and 1; defaults to 1, i.e. POD;
- 'scaleFactorDer': scaling factors for derivative computation;
defaults to 'AUTO';
- 'S': total number of samples current approximant relies upon;
- 'sampler': sample point generator;
- 'R': rank for Galerkin projection; defaults to 'AUTO', i.e.
maximum allowed;
- 'PODTolerance': tolerance for snapshots POD; defaults to -1.
Defaults to empty dict.
approx_state(optional): Whether to approximate state. Defaults and must
be True.
verbosity(optional): Verbosity level. Defaults to 10.
Attributes:
HFEngine: HF problem solver.
mu0: Default parameter.
mus: Array of snapshot parameters.
approxParameters: Dictionary containing values for main parameters of
approximant. Recognized keys are in parameterList.
parameterListSoft: Recognized keys of soft approximant parameters:
- - 'POD': whether to compute POD of snapshots;
+ - 'POD': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'R': rank for Galerkin projection;
- 'PODTolerance': tolerance for snapshots POD.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of samples current approximant relies upon;
- 'sampler': sample point generator.
approx_state: Whether to approximate state.
verbosity: Verbosity level.
- POD: Whether to compute POD of snapshots.
+ POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
S: Number of solution snapshots over which current approximant is
based upon.
sampler: Sample point generator.
R: Rank for Galerkin projection.
+ PODTolerance: Tolerance for snapshots POD.
muBounds: list of bounds for parameter values.
samplingEngine: Sampling engine.
uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as
sampleList.
lastSolvedHF: Parameter(s) corresponding to last computed high fidelity
solution(s) as parameterList.
uApproxReduced: Reduced approximate solution(s) with parameter(s)
lastSolvedApprox as sampleList.
lastSolvedApproxReduced: Parameter(s) corresponding to last computed
reduced approximate solution(s) as parameterList.
uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as
sampleList.
lastSolvedApprox: Parameter(s) corresponding to last computed
approximate solution(s) as parameterList.
"""
def __init__(self, *args, **kwargs):
self._preInit()
self._addParametersToList(["R", "PODTolerance"], ["AUTO", -1])
super().__init__(*args, **kwargs)
checkIfAffine(self.HFEngine, "apply RB method")
if not self.approx_state:
raise RROMPyException("Must compute RB approximation of state.")
self._postInit()
@property
def tModelType(self):
from .trained_model.trained_model_reduced_basis import (
TrainedModelReducedBasis)
return TrainedModelReducedBasis
@property
def R(self):
"""Value of R. Its assignment may change S."""
return self._R
@R.setter
def R(self, R):
if isinstance(R, str):
R = R.strip().replace(" ","")
if "-" not in R: R = R + "-0"
self._R_isauto, self._R_shift = True, int(R.split("-")[-1])
R = 0
if R < 0: raise RROMPyException("R must be non-negative.")
self._R = R
self._approxParameters["R"] = self.R
def _setRAuto(self):
self.R = max(0, self.S - self._R_shift)
vbMng(self, "MAIN", "Automatically setting R to {}.".format(self.R),
25)
@property
def PODTolerance(self):
"""Value of PODTolerance."""
return self._PODTolerance
@PODTolerance.setter
def PODTolerance(self, PODTolerance):
self._PODTolerance = PODTolerance
self._approxParameters["PODTolerance"] = self.PODTolerance
def _setupProjectionMatrix(self):
"""Compute projection matrix."""
RROMPyAssert(self._mode, message = "Cannot setup numerator.")
vbMng(self, "INIT", "Starting computation of projection matrix.", 7)
if hasattr(self, "_R_isauto"):
self._setRAuto()
else:
if self.S < self.R:
RROMPyWarning(("R too large compared to S. Reducing R by "
"{}").format(self.R - self.S))
self.S = self.S
- try:
- if self.POD:
- U, s, _ = np.linalg.svd(self.samplingEngine.RPOD)
- s = s ** 2.
- else:
- Gramian = self.HFEngine.innerProduct(
- self.samplingEngine.projectionMatrix,
- self.samplingEngine.projectionMatrix,
- is_state = True)
- U, s, _ = np.linalg.svd(Gramian)
- except np.linalg.LinAlgError as e:
- raise RROMPyException(e)
- snorm = np.cumsum(s[::-1]) / np.sum(s)
- nPODTrunc = min(self.S - np.argmax(snorm > self.PODTolerance),
- self.R)
- pMat = self.samplingEngine.projectionMatrix.dot(U[:, : nPODTrunc])
+ if self.POD == 1:
+ U, s, _ = np.linalg.svd(self.samplingEngine.Rscale)
+ cs = np.cumsum(np.abs(s[::-1]) ** 2.)
+ nTolTrunc = np.argmax(cs > self.PODTolerance * cs[-1])
+ nPODTrunc = min(self.S - nTolTrunc, self.R)
+ pMat = self.samplingEngine.projectionMatrix.dot(U[:, : nPODTrunc])
+ else:
+ pMat = self.samplingEngine.projectionMatrix[:, : self.R]
vbMng(self, "MAIN",
- ("Assembling {}x{} projection matrix from {} "
+ ("Assembled {}x{} projection matrix from {} "
"samples.").format(*(pMat.shape), self.S), 5)
vbMng(self, "DEL", "Done computing projection matrix.", 7)
return pMat
def setupApprox(self) -> int:
"""Compute RB projection matrix."""
if self.checkComputedApprox(): return -1
RROMPyAssert(self._mode, message = "Cannot setup approximant.")
vbMng(self, "INIT", "Setting up {}.". format(self.name()), 5)
self.computeSnapshots()
setData = self.trainedModel is None
pMat = self._setupProjectionMatrix()
self._setupTrainedModel(pMat)
if setData:
self.trainedModel.data.affinePoly = self.HFEngine.affinePoly
self.trainedModel.data.thAs = self.HFEngine.thAs
self.trainedModel.data.thbs = self.HFEngine.thbs
ARBs, bRBs = self.assembleReducedSystem(pMat)
self.trainedModel.data.ARBs = ARBs
self.trainedModel.data.bRBs = bRBs
self.trainedModel.data.approxParameters = copy(self.approxParameters)
vbMng(self, "DEL", "Done setting up approximant.", 5)
return 0
def assembleReducedSystem(self, pMat : sampList = None,
pMatOld : sampList = None)\
-> Tuple[List[Np2D], List[Np1D]]:
"""Build affine blocks of RB linear system through projections."""
if pMat is None:
self.setupApprox()
ARBs = self.trainedModel.data.ARBs
bRBs = self.trainedModel.data.bRBs
else:
self.HFEngine.buildA()
self.HFEngine.buildb()
vbMng(self, "INIT", "Projecting affine terms of HF model.", 10)
ARBsOld = None if pMatOld is None else self.trainedModel.data.ARBs
bRBsOld = None if pMatOld is None else self.trainedModel.data.bRBs
ARBs, bRBs = projectAffineDecomposition(self.HFEngine.As,
self.HFEngine.bs, pMat,
ARBsOld, bRBsOld, pMatOld)
vbMng(self, "DEL", "Done projecting affine terms.", 10)
return ARBs, bRBs
diff --git a/rrompy/reduction_methods/base/reduced_basis_utils.py b/rrompy/reduction_methods/standard/reduced_basis_utils.py
similarity index 100%
rename from rrompy/reduction_methods/base/reduced_basis_utils.py
rename to rrompy/reduction_methods/standard/reduced_basis_utils.py
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 a14f10e..a68fd29 100644
--- a/rrompy/reduction_methods/standard/trained_model/trained_model_rational.py
+++ b/rrompy/reduction_methods/standard/trained_model/trained_model_rational.py
@@ -1,190 +1,188 @@
# 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 collections.abc import Iterable
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 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 = self.checkParameterList(mu)
if mu0 is None: mu0 = self.data.mu0
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 = 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 = 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 = 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])
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]
+ if not isinstance(mVals, Iterable): 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:
+ rDim = mVals.index(fp)
+ if rDim < len(mVals) - 1 and fp in mVals[rDim + 1 :]:
raise RROMPyException(("Exactly 1 'freepar' entry in "
"marginalVals must be provided."))
mVals[rDim] = self.data.mu0(rDim)
mVals = list(self.centerNormalize(mVals).data.flatten())
mVals[rDim] = fp
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]
+ if not isinstance(mVals, Iterable): 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):
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)
if not self.data._collapsed:
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 b0c43e7..d48c91c 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,153 @@
# 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 collections.abc import Iterable
from rrompy.reduction_methods.base.trained_model.trained_model import (
TrainedModel)
-from rrompy.reduction_methods.base.reduced_basis_utils import (
+from rrompy.reduction_methods.standard.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
from rrompy.sampling import sampleList
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 = 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 = 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 = self.checkParameterList(mu)
req, emptyCores = [], np.where(np.logical_not(sizes))[0]
if len(mu) == 0:
vbMng(self, "MAIN", "Idling.", 37)
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)
+ uAppR = np.linalg.solve(self.data.ARBmu, self.data.bRBmu)
if j == 0:
uApproxR = np.empty((len(uAppR), len(mu)),
dtype = uAppR.dtype)
if masterCore():
for dest in emptyCores:
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]
+ if not isinstance(marginalVals, Iterable):
+ marginalVals = [marginalVals]
mVals = list(marginalVals)
- try:
- rDim = mVals.index(fp)
- if rDim < len(mVals) - 1 and fp in mVals[rDim + 1 :]:
- raise
- except:
+ rDim = mVals.index(fp)
+ if rDim < len(mVals) - 1 and fp in mVals[rDim + 1 :]:
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, return_data = True).flatten()
mVals[rDim] = fp
ARBs = marginalizePolyList(ARBs, mVals, "auto")
ev = eigvalsNonlinearDense(ARBs, jSupp = jSupp, **kwargs)
return self.mapParameterList(ev, "B", [rDim])(0)
diff --git a/rrompy/sampling/__init__.py b/rrompy/sampling/__init__.py
index 772695e..f9d16df 100644
--- a/rrompy/sampling/__init__.py
+++ b/rrompy/sampling/__init__.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 .sample_list import emptySampleList, sampleList
-from .engines import PODEngine, SamplingEngineStandard, SamplingEngineStandardPOD
+from .engines import (PODEngine, SamplingEngine, SamplingEngineNormalize,
+ SamplingEnginePOD)
__all__ = [
'emptySampleList',
'sampleList',
'PODEngine',
- 'SamplingEngineStandard',
- 'SamplingEngineStandardPOD'
+ 'SamplingEngine',
+ 'SamplingEngineNormalize',
+ 'SamplingEnginePOD'
]
diff --git a/rrompy/sampling/engines/__init__.py b/rrompy/sampling/engines/__init__.py
index f21dbca..684c215 100644
--- a/rrompy/sampling/engines/__init__.py
+++ b/rrompy/sampling/engines/__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 .pod_engine import PODEngine
-from .sampling_engine_standard import SamplingEngineStandard
-from .sampling_engine_standard_pod import SamplingEngineStandardPOD
+from .sampling_engine import SamplingEngine
+from .sampling_engine_normalize import SamplingEngineNormalize
+from .sampling_engine_pod import SamplingEnginePOD
__all__ = [
'PODEngine',
- 'SamplingEngineStandard',
- 'SamplingEngineStandardPOD'
+ 'SamplingEngine',
+ 'SamplingEngineNormalize',
+ 'SamplingEnginePOD'
]
diff --git a/rrompy/sampling/engines/pod_engine.py b/rrompy/sampling/engines/pod_engine.py
index 1eb6e66..7964dff 100644
--- a/rrompy/sampling/engines/pod_engine.py
+++ b/rrompy/sampling/engines/pod_engine.py
@@ -1,137 +1,151 @@
# 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.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 normalize(self, A:Np2D, is_state : bool = True) -> Tuple[Np1D, Np1D]:
+ """
+ Normalize column-wise by norm.
+
+ Args:
+ A: matrix to be normalized;
+ is_state: whether state-norm should be used.
+
+ Returns:
+ Resulting normalized matrix, column-wise norm.
+ """
+ r = self.HFEngine.norm(A, is_state = is_state)
+ return A / r, r
+
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 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; 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 = sampleList(np.zeros(A.shape, dtype = A.dtype))
R = np.zeros((N, N), dtype = A.dtype)
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 = 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)
Q[k] = s * Q[k]
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[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[J] = (B[J] - np.outer(Q[k], R[k, J])).T
if only_R:
return R
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[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.py
similarity index 94%
rename from rrompy/sampling/engines/sampling_engine_standard.py
rename to rrompy/sampling/engines/sampling_engine.py
index b663ba4..ad0a750 100644
--- a/rrompy/sampling/engines/sampling_engine_standard.py
+++ b/rrompy/sampling/engines/sampling_engine.py
@@ -1,358 +1,391 @@
# 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 collections.abc import Iterable
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.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']
+__all__ = ['SamplingEngine']
-class SamplingEngineStandard:
+class SamplingEngine:
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]
+ if not isinstance(scaleFactor, Iterable): 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,
"_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(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 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)
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:
+ """
+ Compute sample after checking if it is a derivative.
+
+ Args:
+ mu: Parameter value.
+ previous: Indices of previous unique samples.
+
+ Returns:
+ Snapshot.
+ """
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:
+ """
+ Compute one sample.
+
+ Args:
+ mu: Parameter value.
+ overwrite(optional): Whether to overwrite sample in self.samples.
+ Defaults to False.
+ postprocess(optional): Whether to perform post-processing step.
+ Defaults to True.
+
+ Returns:
+ Snapshot.
+ """
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:
+ """
+ Compute set of samples.
+
+ Args:
+ mus: Parameter values.
+
+ Returns:
+ Snapshots.
+ """
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_normalize.py
similarity index 57%
rename from rrompy/sampling/engines/sampling_engine_standard_pod.py
rename to rrompy/sampling/engines/sampling_engine_normalize.py
index 8c21326..4a6d678 100644
--- a/rrompy/sampling/engines/sampling_engine_standard_pod.py
+++ b/rrompy/sampling/engines/sampling_engine_normalize.py
@@ -1,103 +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 scipy.sparse import block_diag
from .pod_engine import PODEngine
-from .sampling_engine_standard import SamplingEngineStandard
+from .sampling_engine import SamplingEngine
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']
+__all__ = ['SamplingEngineNormalize']
-class SamplingEngineStandardPOD(SamplingEngineStandard):
+class SamplingEngineNormalize(SamplingEngine):
@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)
+ SamplingEngine.HFEngine.fset(self, HFEngine)
self.PODEngine = PODEngine(self._HFEngine)
@property
def feature_keys(self) -> TupleAny:
- return super().feature_keys + ["samples_ortho", "RPOD"]
+ return super().feature_keys + ["samples_normal", "Rscale"]
@property
def feature_vals(self) -> DictAny:
vals = super().feature_vals
- vals["samples_ortho"] = self.samples_ortho
- vals["RPOD"] = self.RPOD
+ vals["samples_normal"] = self.samples_normal
+ vals["Rscale"] = self.Rscale
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")
+ if name == "samples_normal":
+ self.samples_normal.append(value)
+ elif name == "Rscale":
+ self.Rscale = np.append(self.Rscale, value)
else:
super()._mergeFeature(name, value)
@property
def projectionMatrix(self) -> Np2D:
- return self.samples_ortho.data
+ return self.samples_normal.data
def resetHistory(self):
super().resetHistory()
- self.samples_ortho = emptySampleList()
- self.RPOD = np.zeros((0, 0), dtype = np.complex)
+ self.samples_normal = emptySampleList()
+ self.Rscale = np.zeros(0, dtype = np.complex)
- def setsample_ortho(self, u:sampList, overwrite : bool = False):
+ def setsample_normal(self, u:sampList, overwrite : bool = False):
if overwrite:
- self.samples_ortho[self.nsamples] = u
+ self.samples_normal[self.nsamples] = u
else:
if self.nsamples == 0:
- self.samples_ortho = sampleList(u)
+ self.samples_normal = sampleList(u)
else:
- self.samples_ortho.append(u)
+ self.samples_normal.append(u)
def popSample(self):
if hasattr(self, "nsamples") and self.nsamples > 1:
- self.RPOD = self.RPOD[: -1, : -1]
- self.samples_ortho.pop()
+ self.Rscale = self.Rscale[: -1]
+ self.samples_normal.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)
+ self.samples_normal.reset((u.shape[0], n), u.dtype)
def postprocessu(self, u:sampList, overwrite : bool = False):
+ """Postprocess by normalizing snapshot."""
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)
+ vbMng(self, "INIT", "Starting normalization.", 20)
+ u, r = self.PODEngine.normalize(u, is_state = self.sample_state)
+ self.Rscale = np.append(self.Rscale, r)
+ vbMng(self, "DEL", "Done normalizing.", 20)
+ self.setsample_normal(u, overwrite)
def postprocessuBulk(self):
- vbMng(self, "INIT", "Starting orthogonalization.", 10)
- samples_ortho, self.RPOD = self.PODEngine.generalizedQR(self.samples,
+ """Postprocess by normalizing snapshots in bulk."""
+ vbMng(self, "INIT", "Starting normalization.", 10)
+ samples_normal, self.Rscale = self.PODEngine.normalize(self.samples,
is_state = self.sample_state)
- vbMng(self, "DEL", "Done orthogonalizing.", 10)
+ vbMng(self, "DEL", "Done normalizing.", 10)
nsamples, self.nsamples = self.nsamples, 0
- self.setsample_ortho(samples_ortho)
+ self.setsample_normal(samples_normal)
self.nsamples = nsamples
diff --git a/rrompy/sampling/engines/sampling_engine_pod.py b/rrompy/sampling/engines/sampling_engine_pod.py
new file mode 100644
index 0000000..ffd08ca
--- /dev/null
+++ b/rrompy/sampling/engines/sampling_engine_pod.py
@@ -0,0 +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.sparse import block_diag
+from .sampling_engine_normalize import SamplingEngineNormalize
+from rrompy.utilities.base.types import Any, sampList
+from rrompy.utilities.base import verbosityManager as vbMng
+
+__all__ = ['SamplingEnginePOD']
+
+class SamplingEnginePOD(SamplingEngineNormalize):
+ def _mergeFeature(self, name:str, value:Any):
+ if name == "Rscale":
+ self.Rscale = block_diag((self.Rscale, value), "csc")
+ else:
+ super()._mergeFeature(name, value)
+
+ def resetHistory(self):
+ super().resetHistory()
+ self.Rscale = np.zeros((0, 0), dtype = np.complex)
+
+ def popSample(self):
+ if hasattr(self, "nsamples") and self.nsamples > 1:
+ self.Rscale = self.Rscale[:, : -1]
+ super().popSample()
+
+ def postprocessu(self, u:sampList, overwrite : bool = False):
+ """Postprocess by orthogonalizing snapshot."""
+ self.setsample(u, overwrite)
+ vbMng(self, "INIT", "Starting orthogonalization.", 20)
+ u, r, _ = self.PODEngine.GS(u, self.samples_normal,
+ is_state = self.sample_state)
+ self.Rscale = np.pad(self.Rscale, ((0, 1), (0, 1)), 'constant')
+ self.Rscale[:, -1] = r
+ vbMng(self, "DEL", "Done orthogonalizing.", 20)
+ self.setsample_normal(u, overwrite)
+
+ def postprocessuBulk(self):
+ """Postprocess by orthogonalizing snapshots in bulk."""
+ vbMng(self, "INIT", "Starting orthogonalization.", 10)
+ samples_normal, self.Rscale = self.PODEngine.generalizedQR(
+ self.samples, is_state = self.sample_state)
+ vbMng(self, "DEL", "Done orthogonalizing.", 10)
+ nsamples, self.nsamples = self.nsamples, 0
+ self.setsample_normal(samples_normal)
+ self.nsamples = nsamples
diff --git a/rrompy/sampling/sample_list.py b/rrompy/sampling/sample_list.py
index ffd35fd..28fc894 100644
--- a/rrompy/sampling/sample_list.py
+++ b/rrompy/sampling/sample_list.py
@@ -1,224 +1,226 @@
# 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
__all__ = ['emptySampleList', 'sampleList']
def emptySampleList():
return sampleList(np.empty((0, 0)))
class sampleList:
+ """List of snapshots with many properties overloaded from Numpy arrays."""
+
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, 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/__init__.py b/rrompy/solver/__init__.py
index efe2441..35c0abe 100644
--- a/rrompy/solver/__init__.py
+++ b/rrompy/solver/__init__.py
@@ -1,33 +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 .linear_solver import RROMPyLinearSolvers
-from .norm_utilities import (Np2DLike, Np2DLikeInv, Np2DLikeInvLowRank,
- normEngine)
+from .norm_utilities import (Np2DLike, Np2DLikeGramian, Np2DLikeInv,
+ Np2DLikeInvLowRank, normEngine)
__all__ = [
'RROMPyLinearSolvers',
'Np2DLike',
+ 'Np2DLikeGramian',
'Np2DLikeInv',
'Np2DLikeInvLowRank',
'normEngine'
]
diff --git a/rrompy/solver/fenics/fenics_plotting.py b/rrompy/solver/fenics/fenics_plotting.py
index 8d05990..10674aa 100644
--- a/rrompy/solver/fenics/fenics_plotting.py
+++ b/rrompy/solver/fenics/fenics_plotting.py
@@ -1,85 +1,81 @@
# 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 dolfin.cpp as cpp
import ufl
import fenics as fen
from rrompy.utilities.base.types import Np1D, Np2D
from .fenics_projecting import interp_project
-from rrompy.utilities.exception_manager import RROMPyException
_all_plottable_types = (cpp.function.Function, cpp.function.Expression,
cpp.mesh.Mesh, cpp.fem.DirichletBC,
cpp.mesh.MeshFunctionBool, cpp.mesh.MeshFunctionInt,
cpp.mesh.MeshFunctionDouble,
cpp.mesh.MeshFunctionSizet)
__all__ = ['fenplot', 'affine_warping']
def fenplot(object, *args, warping = None, **kwargs):
"See dolfin.common.plot for more details."
mesh = kwargs.pop('mesh', None)
if isinstance(object, cpp.mesh.Mesh):
if mesh is not None and mesh.id() != object.id():
raise RuntimeError("Got different mesh in plot object and keyword "
"argument")
mesh = object
if mesh is None:
if isinstance(object, cpp.function.Function):
mesh = object.function_space().mesh()
elif hasattr(object, "mesh"):
mesh = object.mesh()
if not isinstance(object, _all_plottable_types):
from dolfin.fem.projection import project
try:
#cpp.log.info("Object cannot be plotted directly, projecting to "
# "piecewise linears.")
object = project(object, mesh = mesh)
mesh = object.function_space().mesh()
object = object._cpp_object
except Exception as e:
msg = "Don't know how to plot given object:\n %s\n" \
"and projection failed:\n %s" % (str(object), str(e))
raise RuntimeError(msg)
if warping is not None:
fen.ALE.move(mesh, interp_project(warping[0], mesh))
out = fen.plot(object, *args, mesh = mesh, **kwargs)
if warping is not None:
fen.ALE.move(mesh, interp_project(warping[1], mesh))
return out
def affine_warping(mesh, A:Np2D, b : Np1D = None):
coords = fen.SpatialCoordinate(mesh)[:]
ndim = mesh.topology().dim()
if b is None: b = [0.] * ndim
assert A.shape[0] == ndim and A.shape[1] == ndim and len(b) == ndim
- try:
- Ainv = np.linalg.inv(A)
- except np.linalg.LinAlgError as e:
- raise RROMPyException(e)
+ Ainv = np.linalg.inv(A)
warp = [- 1. * c for c in coords]
warpInv = [- 1. * c for c in coords]
for i in range(ndim):
warp[i] = warp[i] + b[i]
for j in range(ndim):
warp[i] = warp[i] + A[i, j] * coords[j]
warpInv[i] = warpInv[i] + Ainv[i, j] * (coords[j] - b[j])
return tuple([ufl.as_vector(tuple(w)) for w in [warp, warpInv]])
diff --git a/rrompy/solver/norm_utilities.py b/rrompy/solver/norm_utilities.py
index eccef9d..30bf579 100644
--- a/rrompy/solver/norm_utilities.py
+++ b/rrompy/solver/norm_utilities.py
@@ -1,93 +1,101 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
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']
+__all__ = ['Np2DLike', 'Np2DLikeGramian', 'Np2DLikeInv', 'Np2DLikeInvLowRank',
+ 'normEngine']
class Np2DLike:
@abstractmethod
def dot(self, u:Np2D) -> Np2D:
pass
+class Np2DLikeGramian(Np2DLike):
+ def __init__(self, L : Np2D = None, R : Np2D = None):
+ if L is None and R is None:
+ raise RROMPyException(("Must specify at least one of low-rank "
+ "factors."))
+ self.L = R.T.conj() if L is None else L
+ self.R = L.T.conj() if R is None else R
+ def dot(self, u:Np2D) -> Np2D:
+ return tdot(self.L, tdot(self.R, u)).reshape(u.shape)
+
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)
+ 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, :]
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 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/data_structures.py b/rrompy/utilities/base/data_structures.py
index 75eca6b..344c04d 100644
--- a/rrompy/utilities/base/data_structures.py
+++ b/rrompy/utilities/base/data_structures.py
@@ -1,77 +1,81 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
import os
import time
from rrompy.utilities.base.types import Any, DictAny, ListAny
from rrompy.utilities.exception_manager import RROMPyWarning
__all__ = ['findDictStrKey', 'purgeDict', 'purgeList']
def findDictStrKey(key:Any, keyList:ListAny):
+ """Find key in dictionary."""
for akey in keyList:
if isinstance(key, str) and key.lower() == akey.lower():
return akey
return None
def purgeDict(dct:DictAny, allowedKeys : ListAny = [], silent : bool = False,
complement : bool = False, dictname : str = "",
baselevel : int = 0) -> DictAny:
+ """Purge unwanted keys from dictionary."""
if dictname != "":
dictname = " in " + dictname
dctcp = {}
for key in dct.keys():
akey = findDictStrKey(key, allowedKeys)
if (akey is None) != complement:
if not silent:
RROMPyWarning(("Ignoring key {0}{2} with value "
"{1}.").format(key, dct[key], dictname),
baselevel)
else:
if akey is None:
akey = key
dctcp[akey] = dct[key]
return dctcp
def purgeList(lst:ListAny, allowedEntries : ListAny = [],
silent : bool = False, complement : bool = False,
listname : str = "", baselevel : int = 0) -> ListAny:
+ """Purge unwanted keys from list."""
if listname != "":
listname = " in " + listname
lstcp = []
for x in lst:
ax = findDictStrKey(x, allowedEntries)
if (ax is None) != complement:
if not silent:
RROMPyWarning("Ignoring entry {0}{1}.".format(x, listname),
baselevel)
else:
lstcp = lstcp + [ax]
return lstcp
def getNewFilename(prefix : str = "", extension : str = "dat",
timestamp : bool = True) -> str:
+ """Get currently unused filename for file storage."""
extra = ""
if timestamp: extra = time.strftime("_%y-%m-%d_%H:%M:%S", time.localtime())
filenameBase = "{}{}".format(prefix, extra)
idx = 0
filename = filenameBase + ".{}".format(extension)
while os.path.exists(filename):
idx += 1
filename = filenameBase + "_{}.{}".format(idx, extension)
return filename
diff --git a/rrompy/utilities/base/verbosity_depth.py b/rrompy/utilities/base/verbosity_depth.py
index db51c7d..18eaa8a 100644
--- a/rrompy/utilities/base/verbosity_depth.py
+++ b/rrompy/utilities/base/verbosity_depth.py
@@ -1,97 +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 .
#
from copy import deepcopy as copy
from datetime import datetime
from rrompy.utilities.exception_manager import RROMPyException
__all__ = ["verbosityDepth", "verbosityManager"]
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):
+ """Manage console logging."""
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
if vdtype == "INIT":
if "RROMPy_verbosity_depth" not in globals():
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 += "┌"
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"):
+ """Manage console logging based on object verbosity level."""
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 61a00c5..ec97e43 100644
--- a/rrompy/utilities/exception_manager/exception_manager.py
+++ b/rrompy/utilities/exception_manager/exception_manager.py
@@ -1,26 +1,27 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
__all__ = ["RROMPyException"]
class RROMPyException(Exception):
- def __init__(self, message, purge : bool = True):
- if purge:
+ def __init__(self, message, critical : bool = True):
+ self.critical = critical
+ if critical:
from rrompy.utilities.base.verbosity_depth import setVerbosityDepth
setVerbosityDepth(0)
super().__init__(message)
diff --git a/rrompy/utilities/exception_manager/generic_assert.py b/rrompy/utilities/exception_manager/generic_assert.py
index 34b68f5..777db68 100644
--- a/rrompy/utilities/exception_manager/generic_assert.py
+++ b/rrompy/utilities/exception_manager/generic_assert.py
@@ -1,35 +1,33 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
+from collections.abc import Iterable
from rrompy.utilities.exception_manager import RROMPyException
__all__ = ['RROMPy_READY', 'RROMPy_FRAGILE', 'RROMPyAssert']
RROMPy_READY = "ready"
RROMPy_FRAGILE = "fragile"
def RROMPyAssert(obj, checkVal = RROMPy_READY, what = "Current mode",
message = ""):
- if obj != checkVal:
- try:
- if obj in checkVal: return
- except:
- pass
+ if obj != checkVal and (not isinstance(checkVal, Iterable)
+ or obj not in checkVal):
raise RROMPyException("{} {} not compatible with {}. {}".format(
what, obj, checkVal, message))
diff --git a/rrompy/utilities/expression/expression_evaluator.py b/rrompy/utilities/expression/expression_evaluator.py
index 69128fc..0457c9b 100644
--- a/rrompy/utilities/expression/expression_evaluator.py
+++ b/rrompy/utilities/expression/expression_evaluator.py
@@ -1,125 +1,136 @@
# 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.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,)): return expr
elif isinstance(expr, (parameterList, sampleList)): return expr.data
else:
try:
return np.array(expr)
except:
raise RROMPyException(("Expression '{}' not "
- "recognized.").format(expr))
+ "recognized.").format(expr)) from None
def expressionEvaluator(expr:TupleAny, x:paramList,
force_shape : Tuple[int] = None):
+ """
+ Evaluate expression object by plugging in values of x. An expression object
+ is a tuple containing numbers and/or keywords representing variables
+ and operands.
+ Examples:
+ * exp(-.5*x_0) -> ('exp', ('x', '()', 0, '*', -.5))
+ * -x_1+x_1^2*x_0 -> ('x', '()', 1, '*', -1., '+', ('x', '()', 1),
+ '**', 2., '*', ('x', '()', 0))
+ * 10^(prod(x^2)) -> (10., "**", ("prod", {"axis" : 1},
+ ("data", "x", "**", 2)))
+ """
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 = 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, 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/monomial_creator.py b/rrompy/utilities/expression/monomial_creator.py
index 8803efb..8286b60 100644
--- a/rrompy/utilities/expression/monomial_creator.py
+++ b/rrompy/utilities/expression/monomial_creator.py
@@ -1,58 +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 collections.abc import Iterable
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]
+ if not isinstance(deg, Iterable): 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}, ("x", "**", derdiff))
if not np.isclose(mult, 1):
exprLoc = exprLoc + ("*", mult,)
expr += [exprLoc]
else:
expr += [(0.,)]
if return_derivatives: expr += [None]
return expr
def createMonomialList(n:int, dim:int,
return_derivatives : bool = False) -> List[List[TupleAny]]:
derIdxs = nextDerivativeIndices([], dim, n)
idxList = []
for j, der in enumerate(derIdxs):
idxList += [createMonomial(der, return_derivatives)]
return idxList
diff --git a/rrompy/utilities/numerical/__init__.py b/rrompy/utilities/numerical/__init__.py
index 401ef1a..e362722 100644
--- a/rrompy/utilities/numerical/__init__.py
+++ b/rrompy/utilities/numerical/__init__.py
@@ -1,42 +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 .compress_matrix import compressMatrix
-from .custom_pinv import customPInv
from .halton import haltonGenerate
from .kroneckerer import kroneckerer
from .low_discrepancy import lowDiscrepancy
-from .point_matching import pointMatching, rationalFunctionMatching, potential
+from .point_distances import distanceMatrix
+from .point_matching import pointMatching, rationalFunctionMatching
+from .potential import potential
+from .pseudo_inverse import pseudoInverse
from .quadrature_points import quadraturePointsGenerate
from .sobol import sobolGenerate
from .tensor_la import dot, solve
__all__ = [
'compressMatrix',
- 'customPInv',
'haltonGenerate',
'kroneckerer',
'lowDiscrepancy',
+ 'distanceMatrix',
'pointMatching',
'rationalFunctionMatching',
'potential',
+ 'pseudoInverse',
'quadraturePointsGenerate',
'sobolGenerate',
'dot',
'solve'
]
diff --git a/rrompy/utilities/numerical/compress_matrix.py b/rrompy/utilities/numerical/compress_matrix.py
index 76fe175..09ff210 100644
--- a/rrompy/utilities/numerical/compress_matrix.py
+++ b/rrompy/utilities/numerical/compress_matrix.py
@@ -1,38 +1,39 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
import numpy as np
from rrompy.utilities.numerical.tensor_la import dot
from rrompy.utilities.base.types import Np2D, Tuple, HFEng
__all__ = ["compressMatrix"]
def compressMatrix(A:Np2D, tol : float = 0., HFEngine : HFEng = None,
is_state : bool = True) -> Tuple[Np2D, Np2D, float]:
+ """Compress matrix by SVD."""
if HFEngine is None or not is_state:
U, s, _ = np.linalg.svd(A.T.conj().dot(A))
else:
U, s, _ = np.linalg.svd(HFEngine.innerProduct(A, A,
is_state = is_state))
remove = np.where(s < tol * s[0])[0]
ncut = len(s) if len(remove) == 0 else remove[0]
sums = np.sum(s)
s = s[: ncut] ** .5
R = (U[:, : ncut].conj() * s).T
U = dot(A, U[:, : ncut] * s ** -1.)
return U, R, 1. - np.linalg.norm(s) / sums
diff --git a/rrompy/utilities/numerical/factorials.py b/rrompy/utilities/numerical/factorials.py
index 19b4b07..2b6870f 100644
--- a/rrompy/utilities/numerical/factorials.py
+++ b/rrompy/utilities/numerical/factorials.py
@@ -1,33 +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 prod
from scipy.special import binom, factorial
+from collections.abc import Iterable
from rrompy.utilities.base.types import List
__all__ = ['multibinom', 'multifactorial']
def multibinom(x:List[int], y:List[int]) -> int:
- if not hasattr(x, "__len__"): x = [x]
- if not hasattr(y, "__len__"): y = [y]
+ if not isinstance(x, Iterable): x = [x]
+ if not isinstance(y, Iterable): y = [y]
return int(prod([binom(a, b) for (a, b) in zip(x, y)]))
def multifactorial(x:List[int]) -> int:
- if not hasattr(x, "__len__"): x = [x]
+ if not isinstance(x, Iterable): x = [x]
return int(prod([factorial(a) for a in x]))
diff --git a/rrompy/utilities/numerical/marginalize_poly_list.py b/rrompy/utilities/numerical/marginalize_poly_list.py
index 48fc089..27979cf 100644
--- a/rrompy/utilities/numerical/marginalize_poly_list.py
+++ b/rrompy/utilities/numerical/marginalize_poly_list.py
@@ -1,79 +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 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:
+ """Marginalize out variable in list of polynomials."""
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), 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) * 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/nonlinear_eigenproblem.py b/rrompy/utilities/numerical/nonlinear_eigenproblem.py
index d6375aa..ef1b562 100644
--- a/rrompy/utilities/numerical/nonlinear_eigenproblem.py
+++ b/rrompy/utilities/numerical/nonlinear_eigenproblem.py
@@ -1,77 +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 scipy.linalg as scla
from rrompy.utilities.base.types import Tuple, List, Np1D, Np2D
-from .custom_pinv import customPInv
-from rrompy.utilities.exception_manager import RROMPyException
+from .pseudo_inverse import pseudoInverse
__all__ = ['linearizeDense', 'eigNonlinearDense', 'eigvalsNonlinearDense']
def linearizeDense(As:List[Np2D], jSupp : int = 1) -> Tuple[Np2D, Np2D]:
N = len(As)
n = As[0].shape[0]
stiff = np.zeros(((N - 1) * n, (N - 1) * n), dtype = As[0].dtype)
mass = np.zeros(((N - 1) * n, (N - 1) * n), dtype = As[0].dtype)
if N > 1:
if isinstance(jSupp, str) and jSupp.upper() == "COMPANION":
II = np.arange(n, (N - 1) * n)
stiff = np.pad(- np.hstack(As[-2 :: -1]),
[[0, (N - 2) * n], [0, 0]], "constant")
stiff[II, II - n] = 1.
mass = np.pad(As[-1], [0, (N - 2) * n], "constant")
mass[II, II] = 1.
else:
for j in range(jSupp):
for k in range(jSupp - j - 1, jSupp):
mass[n * j : n * (j + 1), k * n : (k + 1) * n] = \
As[N - 2 + jSupp - k - j]
for j in range(jSupp - 1, N - 1):
for k in range(jSupp, N - 1 + jSupp - j):
stiff[n * j : n * (j + 1), (k - 1) * n : k * n] = \
- As[jSupp - k + N - 2 - j]
stiff[: n * (jSupp - 1), : n * (jSupp - 1)] = \
mass[: n * (jSupp - 1), n : n * jSupp]
mass[n * jSupp :, n * jSupp :] = stiff[n * (jSupp - 1) : - n,
n * jSupp :]
return stiff, mass
def eigNonlinearDense(As:List[Np2D], jSupp : int = 1,
return_inverse : bool = False,
**kwargs_eig) -> Tuple[Np1D, Np2D]:
stiff, mass = linearizeDense(As, jSupp)
if stiff.shape[0] == 0: return stiff, stiff
- try:
- ev, P = scla.eig(stiff, mass, overwrite_a = True, overwrite_b = True,
- **kwargs_eig)
- except np.linalg.LinAlgError as e:
- raise RROMPyException(e)
+ ev, P = scla.eig(stiff, mass, overwrite_a = True, overwrite_b = True,
+ **kwargs_eig)
if not return_inverse: return ev, P
- Pinv = customPInv(P)
- return ev, P, Pinv
+ return ev, P, pseudoInverse(P)
def eigvalsNonlinearDense(As:List[Np2D], jSupp : int = 1,
**kwargs_eigvals) -> Np1D:
stiff, mass = linearizeDense(As, jSupp)
if stiff.shape[0] == 0: return stiff
- try:
- return scla.eigvals(stiff, mass, overwrite_a = True, **kwargs_eigvals)
- except np.linalg.LinAlgError as e:
- raise RROMPyException(e)
-
+ return scla.eigvals(stiff, mass, overwrite_a = True, **kwargs_eigvals)
diff --git a/rrompy/utilities/numerical/number_theory.py b/rrompy/utilities/numerical/number_theory.py
deleted file mode 100644
index cc728ab..0000000
--- a/rrompy/utilities/numerical/number_theory.py
+++ /dev/null
@@ -1,70 +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
-
-__all__ = ['squareResonances']
-
-prime_v = []
-
-def squareResonances(a:int, b:int, zero_terms : bool = True):
- spectrum = []
- a = max(int(np.floor(a)), 0)
- b = max(int(np.ceil(b)), 0)
- global prime_v
- if len(prime_v) == 0:
- prime_v = [2, 3]
- if a > prime_v[-1]:
- for i in range(prime_v[-1], a, 2):
- getLowestPrimeFactor(i)
- for i in range(a, b + 1):
- spectrum = spectrum + [i] * countSquareSums(i, zero_terms)
- return np.array(spectrum)
-
-def getLowestPrimeFactor(n:int):
- global prime_v
- for x in prime_v:
- if n % x == 0:
- return x
- if prime_v[-1] < n:
- prime_v = prime_v + [n]
- return n
-
-def primeFactorize(n:int):
- factors = []
- number = n
- while number > 1:
- factor = getLowestPrimeFactor(number)
- factors.append(factor)
- number = int(number / factor)
- if n < -1:
- factors[0] = -factors[0]
- return list(factors)
-
-def countSquareSums(n:int, zero_terms : bool = True):
- if n < 2: return (n + 1) * zero_terms
- factors = primeFactorize(n)
- funique, fcounts = np.unique(factors, return_counts = True)
- count = 1
- for fac, con in zip(funique, fcounts): #using number theory magic
- if fac % 4 == 1:
- count = count * (con + 1)
- elif fac % 4 == 3 and con % 2 == 1:
- return 0
- return count + (2 * zero_terms - 1) * (int(n ** .5) ** 2 == n)
-
diff --git a/rrompy/utilities/numerical/point_distances.py b/rrompy/utilities/numerical/point_distances.py
new file mode 100644
index 0000000..ac31169
--- /dev/null
+++ b/rrompy/utilities/numerical/point_distances.py
@@ -0,0 +1,77 @@
+# Copyright (C) 2018 by the RROMPy authors
+#
+# This file is part of RROMPy.
+#
+# RROMPy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# RROMPy is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with RROMPy. If not, see .
+#
+
+import numpy as np
+from rrompy.utilities.base.types import Np1D, Np2D, HFEng
+
+__all__ = ['distanceMatrix', 'vectorAngleMatrix', 'chordalMetricMatrix',
+ 'chordalMetricAngleMatrix']
+
+def distanceMatrix(x:Np2D, y : Np2D = None, npar : int = None,
+ magnitude : bool = True, weights : Np1D = None) -> Np2D:
+ if npar is None: npar = x.shape[1] if x.ndim > 1 else 1
+ if y is None: y = x
+ if x.ndim != 3 or x.shape[1] != npar: x = x.reshape(-1, 1, npar)
+ if y.ndim != 2 or y.shape[1] != npar: y = y.reshape(-1, npar)
+ dist = np.repeat(x, len(y), axis = 1) - y
+ if weights is not None: dist *= np.array(weights).flatten()
+ if magnitude:
+ if dist.shape[2] == 1:
+ dist = np.abs(dist)[..., 0]
+ else:
+ dist = np.sum(np.abs(dist) ** 2., axis = 2) ** .5
+ return dist
+
+def vectorAngleMatrix(X:Np2D, Y:Np2D, HFEngine : HFEng = None,
+ is_state : bool = True, radius : float = None) -> Np2D:
+ if HFEngine is None:
+ 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 = 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 chordalMetricMatrix(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 = distanceMatrix(x, y)
+ 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 chordalMetricAngleMatrix(x:Np1D, y:Np1D, w : float = 0, X : Np2D = None,
+ Y : Np2D = None, HFEngine : HFEng = None,
+ is_state : bool = True) -> Np2D:
+ dist = chordalMetricMatrix(x, y)
+ if w == 0: return dist
+ distAdj = vectorAngleMatrix(X, Y, HFEngine, is_state)
+ return (dist + w * distAdj) / (1. + w)
diff --git a/rrompy/utilities/numerical/point_matching.py b/rrompy/utilities/numerical/point_matching.py
index 2562ceb..74082c0 100644
--- a/rrompy/utilities/numerical/point_matching.py
+++ b/rrompy/utilities/numerical/point_matching.py
@@ -1,150 +1,87 @@
# 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 .point_distances import distanceMatrix, chordalMetricAngleMatrix
from rrompy.utilities.base.types import Tuple, List, ListAny, Np1D, Np2D, HFEng
-from rrompy.utilities.exception_manager import (RROMPyException, RROMPyWarning,
- RROMPyAssert)
+from rrompy.utilities.exception_manager import RROMPyAssert
-__all__ = ['pointMatching', 'rationalFunctionMatching', 'potential',
- 'angleTable', 'chordalMetricTable', 'chordalMetricAdjusted']
+__all__ = ['pointMatching', 'rationalFunctionMatching']
-def pointMatching(distanceMatrix:Np2D) -> Tuple[Np1D, Np1D]:
- return LSA(distanceMatrix)
+def pointMatching(distMatrix:Np2D) -> Tuple[Np1D, Np1D]:
+ return LSA(distMatrix)
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) \
+ featPts:Np2D, matchingWeight:float, supps:ListAny,
+ projMat:Np2D, HFEngine : HFEng = None,
+ is_state : bool = True, root : int = None) \
-> Tuple[List[Np1D], List[Np2D]]:
+ """
+ Match poles and residues of a set of rational functions.
+
+ Args:
+ poles: List of (lists of) poles.
+ coeffs: List of (lists of) residues.
+ featPts: Marginal parameters corresponding to rational models.
+ matchingWeight: Matching weight in distance computation.
+ supps: Support indices for projection matrix.
+ projMat: Projection matrix for residues.
+ HFEngine(optional): Engine for distance evaluation. Defaults to None,
+ i.e. Euclidean metric.
+ is_state(optional): Whether residues are of system state. Defaults to
+ True.
+ root(optional): Root of search tree. Defaults to None, i.e.
+ automatically chosen.
+
+ Returns:
+ Matched list of (lists of) poles and list of (lists of) residues.
+ """
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)
+ featDist = distanceMatrix(featPts).flatten()
free = list(range(M))
- fixed = [free.pop(np.argpartition(featDist, M)[M] % M)]
+ if root is None: #start from sample points closest to each other
+ root = np.argpartition(featDist, M)[M] % M
+ fixed = [free.pop(root)]
featDist = featDist.reshape(M, M)
for j in range(M - 1, 0, -1):
+ #find closest point
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]]))
+ plsfix, plsfree = poles[Ifix], poles[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)
+ #build assignment distance matrix
+ distj = chordalMetricAngleMatrix(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, radius : float = None) -> Np2D:
- if HFEngine is None:
- 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 = 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/potential.py b/rrompy/utilities/numerical/potential.py
new file mode 100644
index 0000000..c96f0bf
--- /dev/null
+++ b/rrompy/utilities/numerical/potential.py
@@ -0,0 +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 .
+#
+
+import numpy as np
+from rrompy.utilities.base.types import Tuple, Np1D
+from rrompy.utilities.exception_manager import RROMPyWarning
+
+__all__ = ['potential']
+
+def potential(x:Np1D, foci : Tuple[float, float] = [- 1., 1.]) -> Np1D:
+ """Evaluation of complex potential for ellipses or line segments."""
+ 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
+
diff --git a/rrompy/utilities/numerical/custom_pinv.py b/rrompy/utilities/numerical/pseudo_inverse.py
similarity index 84%
rename from rrompy/utilities/numerical/custom_pinv.py
rename to rrompy/utilities/numerical/pseudo_inverse.py
index 86c1ad3..a4d3af2 100644
--- a/rrompy/utilities/numerical/custom_pinv.py
+++ b/rrompy/utilities/numerical/pseudo_inverse.py
@@ -1,56 +1,52 @@
# 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 numbers import Number
-from rrompy.utilities.exception_manager import RROMPyException
-__all__ = ["customPInv"]
+__all__ = ["pseudoInverse"]
-def customPInv(A, rcond=-1, full=False):
+def pseudoInverse(A, rcond=-1, full=False):
"""
Compute the (Moore-Penrose) pseudo-inverse of a matrix.
Calculate the generalized inverse of a matrix using its
singular-value decomposition (SVD) and including all
*large* singular values.
"""
if isinstance(A, Number):
if np.isclose(A, 0.): return np.inf
return 1. / A
A = A.conjugate()
- try:
- u, s, vt = np.linalg.svd(A, full_matrices=False)
- except np.linalg.LinAlgError as e:
- raise RROMPyException(e)
+ u, s, vt = np.linalg.svd(A, full_matrices=False)
if rcond < 0: rcond = len(A) * np.finfo(A.dtype).eps
cutoff = rcond * np.amax(s)
large = s > cutoff
sinv = copy(s)
sinv = np.divide(1, s, where = large, out = sinv)
sinv[~large] = 0
res = (vt.T * sinv) @ u.T
if full:
return res, [np.sum(large), s, rcond]
else:
return res
diff --git a/rrompy/utilities/numerical/rayleigh_quotient_iteration.py b/rrompy/utilities/numerical/rayleigh_quotient_iteration.py
deleted file mode 100644
index e395918..0000000
--- a/rrompy/utilities/numerical/rayleigh_quotient_iteration.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2018 by the RROMPy authors
-#
-# This file is part of RROMPy.
-#
-# RROMPy is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# RROMPy is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with RROMPy. If not, see .
-#
-
-import numpy as np
-from rrompy.utilities.base.types import Np1D, Np2D, DictAny
-from .tensor_la import dot, solve
-
-__all__ = ['rayleighQuotientIteration']
-
-def rayleighQuotientIteration(A:Np2D, v0:Np1D, M:Np2D, solver:callable,
- solverArgs:DictAny, sigma : float = 0.,
- nIterP : int = 10, nIterR : int = 10) -> float:
- nIterP = min(nIterP, len(v0) // 2)
- nIterR = min(nIterR, (len(v0) + 1) // 2)
- v0 /= dot(dot(M, v0).T, v0.conj()) ** .5
- for j in range(nIterP):
- v0 = solve(A - sigma * M, dot(M, v0), solver, solverArgs)
- v0 /= dot(dot(M, v0).T, v0.conj()) ** .5
- l0 = dot(A.dot(v0).T, v0.conj())
- for j in range(nIterR):
- v0 = solve(A - l0 * M, dot(M, v0), solver, solverArgs)
- v0 /= dot(dot(M, v0).T, v0.conj()) ** .5
- l0 = dot(A.dot(v0).T, v0.conj())
- if np.isnan(l0): l0 = np.finfo(float).eps
- return np.abs(l0)
diff --git a/rrompy/utilities/numerical/tensor_la.py b/rrompy/utilities/numerical/tensor_la.py
index 188768b..efb57e3 100644
--- a/rrompy/utilities/numerical/tensor_la.py
+++ b/rrompy/utilities/numerical/tensor_la.py
@@ -1,53 +1,50 @@
# 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):
+ """A * b."""
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)
-
+ """A \ b."""
+ 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)
diff --git a/rrompy/utilities/poly_fitting/custom_fit.py b/rrompy/utilities/poly_fitting/custom_fit.py
index ccdd06a..ed79768 100644
--- a/rrompy/utilities/poly_fitting/custom_fit.py
+++ b/rrompy/utilities/poly_fitting/custom_fit.py
@@ -1,136 +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
import numpy.linalg as la
from rrompy.utilities.exception_manager import RROMPyException, RROMPyWarning
__all__ = ["customFit"]
def customFit(van, y, rcond=-1, full=False, w=None):
"""
Least-squares fit of a polynomial to data. Copied from
numpy.polynomial.polynomial.
Parameters
----------
va : array_like, shape (`M`,`deg` + 1)
Vandermonde-like matrix.
y : array_like, shape (`M`,) or (`M`, `K`)
y-coordinates of the sample points. Several sets of sample points
sharing the same x-coordinates can be (independently) fit with one
call to `polyfit` by passing in for `y` a 2-D array that contains
one data set per column.
rcond : float, optional
Relative condition number of the fit. Singular values smaller
than `rcond`, relative to the largest singular value, will be
ignored. The default value is ``len(van)*eps``, where `eps` is the
relative precision of the platform's float type, about 2e-16 in
most cases.
full : bool, optional
Switch determining the nature of the return value. When ``False``
(the default) just the coefficients are returned; when ``True``,
diagnostic information from the singular value decomposition (used
to solve the fit's matrix equation) is also returned.
w : array_like, shape (`M`,), optional
Weights. If not None, the contribution of each point
``(x[i],y[i])`` to the fit is weighted by `w[i]`. Ideally the
weights are chosen so that the errors of the products ``w[i]*y[i]``
all have the same variance. The default value is None.
Returns
-------
coef : ndarray, shape (`deg` + 1,) or (`deg` + 1, `K`)
Polynomial coefficients ordered from low to high. If `y` was 2-D,
the coefficients in column `k` of `coef` represent the polynomial
fit to the data in `y`'s `k`-th column.
[residuals, rank, singular_values, rcond] : list
These values are only returned if `full` = True
resid -- sum of squared residuals of the least squares fit
rank -- the numerical rank of the scaled Vandermonde matrix
sv -- singular values of the scaled Vandermonde matrix
rcond -- value of `rcond`.
For more details, see `linalg.lstsq`.
"""
van = np.asarray(van) + 0.0
y = np.asarray(y) + 0.0
# check arguments.
if van.ndim != 2:
raise RROMPyException("expected 2D vector for van")
if van.size == 0:
raise RROMPyException("expected non-empty vector for van")
if y.ndim < 1 or y.ndim > 2:
raise RROMPyException("expected 1D or 2D array for y")
if len(van) != len(y):
raise RROMPyException("expected van and y to have same length")
order = van.shape[1]
# set up the least squares matrices in transposed form
lhs = van.T
rhs = y.T
if isinstance(w, (str, )) and w.upper() == "AUTO":
# Determine the norms of the design matrix rows.
if issubclass(van.dtype.type, np.complexfloating):
w = np.sqrt((np.square(van.real) + np.square(van.imag)).sum(1))
else:
w = np.sqrt(np.square(van).sum(1))
w[w == 0] = 1
w = np.power(w, -1.)
if w is not None:
w = np.asarray(w) + 0.0
if w.ndim != 1:
raise RROMPyException("expected 1D vector for w")
if len(van) != len(w):
raise RROMPyException("expected van and w to have same length")
# apply weights. Don't use inplace operations as they
# can cause problems with NA.
lhs = lhs * w
rhs = rhs * w
# set rcond
if rcond < 0:
rcond = len(van)*np.finfo(van.dtype).eps
# Determine the norms of the design matrix columns.
if issubclass(lhs.dtype.type, np.complexfloating):
scl = np.sqrt((np.square(lhs.real) + np.square(lhs.imag)).sum(1))
else:
scl = np.sqrt(np.square(lhs).sum(1))
scl[scl == 0] = 1
# Solve the least squares problem.
- try:
- c, resids, rank, s = la.lstsq(lhs.T/scl, rhs.T, rcond)
- except np.linalg.LinAlgError as e:
- raise RROMPyException(e)
+ c, resids, rank, s = la.lstsq(lhs.T/scl, rhs.T, rcond)
c = (c.T/scl).T
# warn on rank reduction
if rank != order and not full:
RROMPyWarning("The fit may be poorly conditioned", stacklevel = 2)
if full:
return c, [resids, rank, s, rcond]
else:
return c
diff --git a/rrompy/utilities/poly_fitting/heaviside/base.py b/rrompy/utilities/poly_fitting/heaviside/base.py
index cb19d09..b322988 100644
--- a/rrompy/utilities/poly_fitting/heaviside/base.py
+++ b/rrompy/utilities/poly_fitting/heaviside/base.py
@@ -1,36 +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 rrompy.utilities.exception_manager import RROMPyException
from rrompy.utilities.poly_fitting.polynomial.base import (polybases as pbP,
polyfitname as pfnP,
polydomcoeff as polydomcoeffB)
__all__ = ['polybases', 'polyfitname', 'polydomcoeff']
polybases = [x + "_HEAVISIDE" for x in pbP]
def polyfitname(basis:str) -> str:
- basisp = basis.split("_")[0]
- try:
- return pfnP(basisp) + "_heaviside"
- except:
- raise RROMPyException("Polynomial-Heaviside basis not recognized.")
+ return pfnP(basis.split("_")[0]) + "_heaviside"
def polydomcoeff(n:int, basis:str) -> float:
return polydomcoeffB(n, basis.split("_")[0])
diff --git a/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py b/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py
index 51c429c..a9e4d5c 100644
--- a/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py
+++ b/rrompy/utilities/poly_fitting/heaviside/heaviside_interpolator.py
@@ -1,72 +1,73 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
import numpy as np
from copy import deepcopy as copy
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):
+ """Rational function class in Heaviside form. Only in 1D."""
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, parameterMap : DictAny = 1.):
self.coeffs, self.poles, self.polybasis = rational2heaviside(num, den,
murange, scl,
parameterMap)
def roots(self, marginalVals : ListAny = [fp], murange : Np1D = 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,
parameterMap = parameterMap)[0]
return polyroots(coeffsN, basisN)
diff --git a/rrompy/utilities/poly_fitting/heaviside/heaviside_manipulation.py b/rrompy/utilities/poly_fitting/heaviside/heaviside_manipulation.py
index bfdd2de..75201d4 100644
--- a/rrompy/utilities/poly_fitting/heaviside/heaviside_manipulation.py
+++ b/rrompy/utilities/poly_fitting/heaviside/heaviside_manipulation.py
@@ -1,40 +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, List, Tuple
__all__ = ['heavisideUniformShape']
def heavisideUniformShape(poles:List[Np1D], residues:List[Np2D]) \
-> Tuple[List[Np1D], List[Np2D]]:
+ """Add fictitious poles at inf to make rational functions of same size."""
NEff = max([len(pls) for pls in poles])
for j in range(len(poles)):
dN = NEff - len(poles[j])
if dN > 0:
residues[j] = np.vstack((residues[j][: len(poles[j])],
np.zeros((dN, residues[j].shape[1])),
residues[j][len(poles[j]) :]))
poles[j] = np.append(poles[j], [np.inf] * dN)
cEff = max([len(cfs) for cfs in residues])
for j in range(len(residues)):
dc = cEff - len(residues[j])
if dc > 0:
residues[j] = np.vstack((residues[j],
np.zeros((dc, residues[j].shape[1]))))
return poles, residues
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 91fdd2d..123c4e2 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,98 @@
# 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.polynomial_interpolator import (
+ PolynomialInterpolator, PolynomialInterpolatorNodal)
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, 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,
parameterMap : DictAny = 1.) \
-> Tuple[interpEng, interpEng]:
- num = PolynomialInterpolator()
- den = PolynomialInterpolator()
basisN = basis.split("_")[0]
if basisD is None: basisD = basisN
+ num = PolynomialInterpolator()
+ den = PolynomialInterpolatorNodal()
+ den.polybasis, den.nodes = basisD, poles
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", parameterMap)
elif basis == "LEGENDRE":
sampler = QS(murange, "GAUSSLEGENDRE", parameterMap)
else:
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,
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(poles) > 0:
+ 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", parameterMap)
elif num.polybasis == "LEGENDRE":
sampler = QS(murange, "GAUSSLEGENDRE", parameterMap)
else:
sampler = RS(murange, "HALTON", parameterMap)
xAux = sampler.generatePoints(len(c))
- valsAux = (num(xAux) / den(xAux)
- - polyval(xAux, c, poles, basis)).T
+ valsAux = num(xAux) / den(xAux)
+ if len(poles) > 0:
+ valsAux -= polyval(xAux, c, poles, basis)
VanAux = polyvander(xAux, [len(c) - len(poles) - 1], num.polybasis)
- c[len(poles) :] = customFit(VanAux, valsAux)
- poles[polesBad] = np.inf
+ c[len(poles) :] = customFit(VanAux, valsAux.T)
+ if len(poles) > 0: poles[polesBad] = np.inf
return c, poles, basis
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 984450e..35b57c7 100644
--- a/rrompy/utilities/poly_fitting/nearest_neighbor/nearest_neighbor_interpolator.py
+++ b/rrompy/utilities/poly_fitting/nearest_neighbor/nearest_neighbor_interpolator.py
@@ -1,90 +1,92 @@
# 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
+import numpy as np
+from collections.abc import Iterable
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):
+ """Function class with setup by nearest neighbor interpolation."""
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]
+ if not isinstance(nleft, Iterable): nleft = [nleft]
+ if not isinstance(nright, Iterable): 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)
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 = [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 d23f3ff..4bd19ec 100644
--- a/rrompy/utilities/poly_fitting/nearest_neighbor/val.py
+++ b/rrompy/utilities/poly_fitting/nearest_neighbor/val.py
@@ -1,41 +1,39 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
import numpy as np
+from rrompy.utilities.numerical import distanceMatrix
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, return_data = True)
if directionalWeights is None:
directionalWeights = np.ones(supportPoints.shape[1])
npar = supportPoints.shape[1]
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
+ muDiff = distanceMatrix(supportPoints, x, weights = directionalWeights)
+ dist = (muDiff ** 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/base.py b/rrompy/utilities/poly_fitting/piecewise_linear/base.py
index eb9dd52..ae5b6cd 100644
--- a/rrompy/utilities/poly_fitting/piecewise_linear/base.py
+++ b/rrompy/utilities/poly_fitting/piecewise_linear/base.py
@@ -1,47 +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 rrompy.utilities.base.types import Np1D, Tuple
from rrompy.utilities.exception_manager import RROMPyException
__all__ = ['sparsekinds', 'sparseMap']
sparsekinds = ["PIECEWISE_LINEAR_" + k for k in ["UNIFORM", "CLENSHAWCURTIS"]]
def centerNormalize(x:Np1D, lims:Tuple[np.complex, np.complex],
forward : bool = True) -> Np1D:
- """forward: X([-1, 1]) -> X(lims)"""
+ """If forward, x in [-1, 1] -> y in lims. Otherwise, the opposite."""
center, width = .5 * (lims[0] + lims[-1]), .5 * (lims[-1] - lims[0])
if forward: return width * x + center
return np.real((x - center) / width)
def sparseMap(x:Np1D, lims:Tuple[np.complex, np.complex], kind:str,
forward : bool = True) -> Np1D:
- """forward: U([-1, 1]) -> lims"""
+ """If forward, x in [-1, 1] -> y in lims. Otherwise, the opposite."""
kind = kind.upper().strip().replace(" ", "").split("_")[-1].split("-")[0]
if kind == "UNIFORM":
return centerNormalize(x, lims, forward)
elif kind == "CLENSHAWCURTIS":
if forward:
x0 = np.cos(.5 * np.pi * (1. - x))
return centerNormalize(x0, lims, forward)
x0 = centerNormalize(x, lims, forward)
return 1. - 2. / np.pi * np.arccos(np.clip(x0, -1., 1.))
else:
raise RROMPyException("Sparse map kind not recognized.")
diff --git a/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py b/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py
index 7741a82..60c5f69 100644
--- a/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py
+++ b/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py
@@ -1,64 +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 .base import centerNormalize, sparseMap
from rrompy.utilities.base.types import Np1D, Np2D, paramList
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)
+ noBdr = "HAAR" in kind.upper()
+ if depth < noBdr: return np.zeros_like(x)
+ if depth == noBdr: return np.ones_like(x)
+ suppEff = sparseMap(supp, [-1., 1.], kind, False)
+ suppLREff = suppEff + .5 ** (depth - 1) * np.array([-1., 1.])
+ widthL, widthR = sparseMap(suppLREff, [-1., 1.], kind) - supp
+ xC = np.array(x - supp)
+ if np.isclose(widthL, 0.) or supp + (.95 + .1 * noBdr) * widthL < - 1.:
+ isleft, isright = 0, 1
+ elif np.isclose(widthR, 0.) or supp + (.95 + .1 * noBdr) * widthR > 1.:
+ isleft, isright = 1, 0
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)
+ 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)
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 4d14b1d..01000df 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,101 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
-import numpy as np
from copy import deepcopy as copy
-from rrompy.utilities.base.types import (List, ListAny, DictAny, Np1D, Np2D,
- paramList)
+import numpy as np
+from scipy.linalg import solve_triangular
+from collections.abc import Iterable
+from rrompy.utilities.base.types import List, ListAny, 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):
+ """
+ Function class with setup by piecewise linear interpolation. Only on sparse
+ grids.
+ """
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]
+ if not isinstance(nleft, Iterable): nleft = [nleft]
+ if not isinstance(nright, Iterable): 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 = {}):
+ kind : str = "PIECEWISE_LINEAR_UNIFORM"):
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)
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
+ self.coeffs = solve_triangular(van, values, unit_diagonal = True,
+ lower = True).reshape((len(support),)
+ + outDim)
diff --git a/rrompy/utilities/poly_fitting/polynomial/__init__.py b/rrompy/utilities/poly_fitting/polynomial/__init__.py
index 8bd3678..8b7b46c 100644
--- a/rrompy/utilities/poly_fitting/polynomial/__init__.py
+++ b/rrompy/utilities/poly_fitting/polynomial/__init__.py
@@ -1,47 +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 .base import (polybases, polyfitname, polydomcoeff)
from .der import polyder
from .val import polyval
from .marginalize import polymarginalize
from .vander import polyvander
from .roots import polyroots
-from .polynomial_algebra import (changePolyBasis, polyTimes, polyDivide,
- polyTimesTable, vanderInvTable, blockDiagDer)
-from .polynomial_interpolator import PolynomialInterpolator
+from .polynomial_algebra import changePolyBasis, polyTimes, polyDivide
+from .polynomial_interpolator import (PolynomialInterpolator,
+ PolynomialInterpolatorNodal)
__all__ = [
'polybases',
'polyfitname',
'polydomcoeff',
'polyder',
'polyval',
'polymarginalize',
'polyvander',
'polyroots',
'changePolyBasis',
'polyTimes',
'polyDivide',
- 'polyTimesTable',
- 'vanderInvTable',
- 'blockDiagDer',
- 'PolynomialInterpolator'
+ 'PolynomialInterpolator',
+ 'PolynomialInterpolatorNodal'
]
diff --git a/rrompy/utilities/poly_fitting/polynomial/base.py b/rrompy/utilities/poly_fitting/polynomial/base.py
index ba549c9..594f5e0 100644
--- a/rrompy/utilities/poly_fitting/polynomial/base.py
+++ b/rrompy/utilities/poly_fitting/polynomial/base.py
@@ -1,59 +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 scipy.special import binom
from rrompy.utilities.exception_manager import RROMPyException
__all__ = ['polybases', 'polyfitname', 'polydomcoeff']
polybases = ["CHEBYSHEV", "LEGENDRE", "MONOMIAL"]
def polyfitname(basis:str) -> str:
- if basis.upper() not in polybases:
+ basis = basis.upper()
+ if basis not in polybases:
raise RROMPyException("Polynomial basis not recognized.")
return {"CHEBYSHEV" : "chebfit", "LEGENDRE" : "legfit",
- "MONOMIAL" : "polyfit"}[basis.upper()]
+ "MONOMIAL" : "polyfit"}[basis]
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/marginalize.py b/rrompy/utilities/poly_fitting/polynomial/marginalize.py
index 66da859..0a45239 100644
--- a/rrompy/utilities/poly_fitting/polynomial/marginalize.py
+++ b/rrompy/utilities/poly_fitting/polynomial/marginalize.py
@@ -1,58 +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 .
#
-from numpy import array, polynomial as po
from copy import deepcopy as copy
+from numpy import array, polynomial as po
+from collections.abc import Iterable
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:
+ """Marginalize out variable in polynomial."""
if not hasattr(c, "ndim"): c = array(c)
ndim = c.ndim
- if not hasattr(marginalVals, "__len__"): marginalVals = [marginalVals]
+ if not isinstance(marginalVals, Iterable): marginalVals = [marginalVals]
marginalVals = list(marginalVals)
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_algebra.py b/rrompy/utilities/poly_fitting/polynomial/polynomial_algebra.py
index d50df84..fca3dad 100644
--- a/rrompy/utilities/poly_fitting/polynomial/polynomial_algebra.py
+++ b/rrompy/utilities/poly_fitting/polynomial/polynomial_algebra.py
@@ -1,146 +1,85 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
import numpy as np
from copy import deepcopy as copy
-from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List, interpEng
+from rrompy.utilities.base.types import Np2D, Tuple
from .vander import polyvander
-from .polynomial_interpolator import PolynomialInterpolator
-from rrompy.utilities.numerical import customPInv
-from rrompy.utilities.numerical.factorials import multifactorial
-from rrompy.utilities.numerical.hash_derivative import (
- hashDerivativeToIdx as hashD,
- hashIdxToDerivative as hashI)
+from rrompy.utilities.numerical import pseudoInverse
from rrompy.utilities.exception_manager import RROMPyException
-__all__ = ['changePolyBasis', 'polyTimes', 'polyDivide', 'polyTimesTable',
- 'vanderInvTable', 'blockDiagDer']
+__all__ = ['changePolyBasis', 'polyTimes', 'polyDivide']
def changePolyBasis(P:Np2D, dim : int = None, basis0 : str = "MONOMIAL",
basisF : str = "MONOMIAL") -> Np2D:
if basis0 == basisF: return P
if dim is None: dim = P.ndim
if basis0 != "MONOMIAL" and basisF != "MONOMIAL":
return changePolyBasis(changePolyBasis(P, dim, basis0, "MONOMIAL"),
dim, "MONOMIAL", basisF)
basisD = basisF if basis0 == "MONOMIAL" else basis0
R = copy(P)
N = np.max(P.shape[: dim]) - 1
vander = polyvander([0], N, basisD, [list(range(N + 1))])
- if basis0 == "MONOMIAL": vander = customPInv(vander)
+ if basis0 == "MONOMIAL": vander = pseudoInverse(vander)
for j in range(dim):
R = np.tensordot(vander, R, (-1, j))
return R
def polyTimes(P:Np2D, Q:Np2D, dim : int = None, Pbasis : str = "MONOMIAL",
Qbasis : str = "MONOMIAL", Rbasis : str = "MONOMIAL") -> Np2D:
if not isinstance(P, (np.ndarray,)): P = np.array(P)
if not isinstance(Q, (np.ndarray,)): Q = np.array(Q)
P = changePolyBasis(P, dim, Pbasis, "MONOMIAL")
Q = changePolyBasis(Q, dim, Qbasis, "MONOMIAL")
if dim is None: dim = P.ndim
if dim <= 0: return
R = np.zeros([x + y - 1 for (x, y) in zip(P.shape[: dim], Q.shape[: dim])],
dtype = P.dtype)
if dim == 1:
for j, Qj in enumerate(Q):
R[j : j + len(P)] = R[j : j + len(P)] + Qj * P
else:
for j, Qj in enumerate(Q):
for l, Pl in enumerate(P):
R[j + l] = R[j + l] + polyTimes(Pl, Qj, dim - 1)
return changePolyBasis(R, dim, "MONOMIAL", Rbasis)
def polyDivide(P:Np2D, Q:Np2D, dim : int = None, Pbasis : str = "MONOMIAL",
Qbasis : str = "MONOMIAL",
Rbasis : str = "MONOMIAL") -> Tuple[Np2D, Np2D]:
if not isinstance(P, (np.ndarray,)): P = np.array(P)
if not isinstance(Q, (np.ndarray,)): Q = np.array(Q)
P = changePolyBasis(P, dim, Pbasis, "MONOMIAL")
Pc = copy(P)
Q = changePolyBasis(Q, dim, Qbasis, "MONOMIAL")
if dim is None: dim = P.ndim
if dim <= 0: return
R = np.zeros([x - y + 1 for (x, y) in zip(P.shape[: dim], Q.shape[: dim])],
dtype = P.dtype)
if dim == 1:
for i in range(len(R) - 1, -1, -1):
- try:
- R[i] = Pc[-1] / Q[-1]
- except:
- raise RROMPyException(("Numerical instability in polynomial "
- "quotient."))
+ R[i] = Pc[-1] / Q[-1]
Pc = Pc[: -1]
for j, Qj in enumerate(Q[::-1]):
if j > 0: Pc[-j] = Pc[-j] - R[i] * Qj
else:
raise RROMPyException(("Quotient of multivariate polynomials not "
"supported."))
return (changePolyBasis(R, dim, "MONOMIAL", Rbasis),
changePolyBasis(Pc, dim, "MONOMIAL", Rbasis))
-
-def polyTimesTable(P:interpEng, mus:Np1D, reorder:List[int],
- derIdxs:List[List[List[int]]], scl : Np1D = None) -> Np2D:
- if not isinstance(P, PolynomialInterpolator):
- raise RROMPyException(("Polynomial to evaluate must be a polynomial "
- "interpolator."))
- Pvals = [[0.] * len(derIdx) for derIdx in derIdxs]
- for j, derIdx in enumerate(derIdxs):
- nder = len(derIdx)
- for der in range(nder):
- derI = hashI(der, P.npar)
- Pvals[j][der] = P([mus[j]], derI, scl) / multifactorial(derI)
- return blockDiagDer(Pvals, reorder, derIdxs)
-
-def vanderInvTable(vanInv:Np2D, idxs:List[int], reorder:List[int],
- derIdxs:List[List[List[int]]]) -> Np2D:
- S = len(reorder)
- Ts = [None] * len(idxs)
- for k in range(len(idxs)):
- invLocs = [None] * len(derIdxs)
- idxGlob = 0
- for j, derIdx in enumerate(derIdxs):
- nder = len(derIdx)
- idxGlob += nder
- idxLoc = np.arange(S)[np.logical_and(reorder >= idxGlob - nder,
- reorder < idxGlob)]
- invLocs[j] = vanInv[k, idxLoc]
- Ts[k] = blockDiagDer(invLocs, reorder, derIdxs, [2, 1, 0])
- return Ts
-
-def blockDiagDer(vals:List[Np1D], reorder:List[int],
- derIdxs:List[List[List[int]]],
- permute : List[int] = None) -> Np2D:
- S = len(reorder)
- T = np.zeros((S, S), dtype = np.complex)
- if permute is None: permute = [0, 1, 2]
- idxGlob = 0
- for j, derIdx in enumerate(derIdxs):
- nder = len(derIdx)
- idxGlob += nder
- idxLoc = np.arange(S)[np.logical_and(reorder >= idxGlob - nder,
- reorder < idxGlob)]
- val = vals[j]
- for derI, derIdxI in enumerate(derIdx):
- for derJ, derIdxJ in enumerate(derIdx):
- diffIdx = [x - y for (x, y) in zip(derIdxI, derIdxJ)]
- if all([x >= 0 for x in diffIdx]):
- diffj = hashD(diffIdx)
- i1, i2, i3 = np.array([derI, derJ, diffj])[permute]
- T[idxLoc[i1], idxLoc[i2]] = val[i3]
- return T
diff --git a/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py b/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py
index 5b80325..d434817 100644
--- a/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py
+++ b/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py
@@ -1,126 +1,226 @@
# 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
+import numpy as np
+from scipy.special import factorial as fact
+from collections.abc import Iterable
+from itertools import combinations
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
-from rrompy.utilities.numerical import dot
+from .polynomial_algebra import changePolyBasis, polyTimes
+from rrompy.utilities.numerical import dot, distanceMatrix
from rrompy.utilities.numerical.degree import degreeTotalToFull
from rrompy.utilities.exception_manager import RROMPyAssert, RROMPyException
from rrompy.parameter import checkParameterList
-__all__ = ['PolynomialInterpolator']
+__all__ = ['PolynomialInterpolator', 'PolynomialInterpolatorNodal']
class PolynomialInterpolator(GenericInterpolator):
+ """Function class with setup by polynomial interpolation."""
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):
+ if hasattr(self, "_dirPivot"):
+ mu = checkParameterList(mu)(self._dirPivot)
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]
+ if not isinstance(nleft, Iterable): nleft = [nleft]
+ if not isinstance(nright, Iterable): 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)
self.npar = support.shape[1]
self.polybasis = polybasis
- if not totalDegree and not hasattr(deg, "__len__"):
+ if not totalDegree and not isinstance(deg, Iterable):
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])
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:
+ rDim = marginalVals.index(fp)
+ if rDim < len(marginalVals) - 1 and fp in marginalVals[rDim + 1 :]:
raise RROMPyException(("Exactly 1 'freepar' entry in "
"marginalVals must be provided."))
return polyroots(self.coeffs, self.polybasis, marginalVals)
+
+class PolynomialInterpolatorNodal(PolynomialInterpolator):
+ """
+ Function class with setup by polynomial interpolation. Stores roots of
+ monomial polynomial instead of coefficients. Only for 1D.
+ """
+ def __init__(self, other = None):
+ self.npar = 1
+ if other is None: return
+ self.nodes = other.nodes
+ self.polybasis = other.polybasis
+
+ @property
+ def nodes(self):
+ return self._nodes
+ @nodes.setter
+ def nodes(self, nodes):
+ self.coeffs = None
+ self._nodes = nodes
+
+ @property
+ def coeffs(self):
+ if self._coeffs is None: self.buildCoeffs()
+ return self._coeffs
+ @coeffs.setter
+ def coeffs(self, coeffs):
+ self._coeffs = coeffs
+
+ @property
+ def shape(self):
+ return (1,)
+
+ @property
+ def deg(self):
+ return [len(self.nodes)]
+
+ def __getitem__(self, key):
+ return self.coeffs[key]
+
+ def __call__(self, mu:paramList, der : List[int] = None,
+ scl : Np1D = None):
+ dirPivot = self._dirPivot if hasattr(self, "_dirPivot") else 0
+ if der is None: der = 0
+ elif isinstance(der, (list,tuple,np.ndarray,)): der = der[dirPivot]
+ if scl is None: scl = 1.
+ elif isinstance(scl, (list,tuple,np.ndarray,)): scl = scl[dirPivot]
+ mu = checkParameterList(mu)(dirPivot)
+ val = np.zeros(len(mu), dtype = np.complex)
+ if der == self.deg[0]:
+ val[:] = 1.
+ elif der >= 0 and der < self.deg[0]:
+ plDist = distanceMatrix(mu, self.nodes, magnitude = False)[:, :, 0]
+ for terms in combinations(np.arange(self.deg[0]),
+ self.deg[0] - der):
+ val += np.prod(plDist[:, list(terms)], axis = 1)
+ return scl ** der * fact(der) * val
+
+ def __copy__(self):
+ return PolynomialInterpolatorNodal(self)
+
+ def __deepcopy__(self, memo):
+ other = PolynomialInterpolatorNodal()
+ other.nodes, other.polybasis = copy((self.nodes, self.polybasis), memo)
+ return other
+
+ def buildCoeffs(self):
+ local = [np.array([- pl, 1.], dtype = np.complex) for pl in self.nodes]
+ N = len(local)
+ while N > 1:
+ for j in range(N // 2):
+ local[j] = polyTimes(local[j], local[- 1 - j])
+ local = local[(N - 1) // 2 :: -1]
+ N = len(local)
+ self._coeffs = changePolyBasis(local[0], None, "MONOMIAL",
+ self.polybasis)
+
+ def pad(self, *args, **kwargs):
+ raise RROMPyException(("Padding not allowed for polynomials in nodal "
+ "form"))
+
+ def postmultiplyTensorize(self, *args, **kwargs):
+ raise RROMPyException(("Post-multiply not allowed for polynomials in "
+ "nodal form"))
+
+ def setupByInterpolation(self, support:paramList, *args, **kwargs):
+ support = checkParameterList(support)
+ self.npar = support.shape[1]
+ if self.npar > 1:
+ raise RROMPyException(("Polynomial in nodal form must have "
+ "scalar output"))
+ output = super().setupByInterpolation(support, *args, **kwargs)
+ self._nodes = super().roots()
+ return output
+
+ def roots(self, marginalVals : ListAny = [fp]):
+ return self.nodes
diff --git a/rrompy/utilities/poly_fitting/polynomial/vander.py b/rrompy/utilities/poly_fitting/polynomial/vander.py
index 20aa591..9862de8 100644
--- a/rrompy/utilities/poly_fitting/polynomial/vander.py
+++ b/rrompy/utilities/poly_fitting/polynomial/vander.py
@@ -1,129 +1,132 @@
# 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 collections.abc import Iterable
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']
def firstDerTransition(dim:int, TDirac:List[Np2D], basis:str,
scl : Np1D = None) -> Np2D:
+ """Manage step from function samples to function derivatives."""
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 polyvander(x:paramList, degs:List[int], basis:str,
derIdxs : List[List[List[int]]] = None,
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
)
"""
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
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__"):
+ if not isinstance(derFlat[j], Iterable):
derFlat[j] = [derFlat[j]]
RROMPyAssert(len(derFlat[j]), dim, "Number of dimensions")
+ #manage mixed derivatives
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, :]
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(degs[0] + 1):
idxLocal = rangeAux[derTotal == j][::-1]
idxPrev += len(idxLocal)
ordIdxs[idxPrev - len(idxLocal) : idxPrev] = idxLocal
return Van[:, mask][:, ordIdxs]
diff --git a/rrompy/utilities/poly_fitting/radial_basis/base.py b/rrompy/utilities/poly_fitting/radial_basis/base.py
index d43f306..b9981e5 100644
--- a/rrompy/utilities/poly_fitting/radial_basis/base.py
+++ b/rrompy/utilities/poly_fitting/radial_basis/base.py
@@ -1,44 +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 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 = list(kernels.keys())
polybases = [x + "_" + y for x, y in product(pbP, rbbases)]
def polyfitname(basis:str) -> str:
- try:
- basisp, basisr = basis.split("_")
- if basisr.upper() in rbbases: basisr = basisr.lower()
- else: raise
- return pfnP(basisp) + "_" + basisr
- except:
+ basissp = basis.split("_")
+ if len(basissp) != 2 or basissp[1].upper() not in rbbases:
raise RROMPyException("Polynomial-radial basis combination not "
"recognized.")
+ return pfnP(basissp[0]) + "_" + basissp[1].lower()
def polydomcoeff(n:int, basis:str) -> float:
return polydomcoeffB(n, basis.split("_")[0])
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 1fce87c..7d7c458 100644
--- a/rrompy/utilities/poly_fitting/radial_basis/radial_basis_interpolator.py
+++ b/rrompy/utilities/poly_fitting/radial_basis/radial_basis_interpolator.py
@@ -1,133 +1,135 @@
# 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
+import numpy as np
+from collections.abc import Iterable
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
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):
+ """Function class with setup by radial basis interpolation."""
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]
+ if not isinstance(nleft, Iterable): nleft = [nleft]
+ if not isinstance(nright, Iterable): 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)
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 = [1.] * self.npar
directionalWeights = np.array(directionalWeights)
self.polybasis = polybasis
- if not totalDegree and not hasattr(deg, "__len__"):
+ if not totalDegree and not isinstance(deg, Iterable):
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 5a913a9..320cfd4 100644
--- a/rrompy/utilities/poly_fitting/radial_basis/val.py
+++ b/rrompy/utilities/poly_fitting/radial_basis/val.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 .
#
from copy import deepcopy as copy
import numpy as np
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, return_data = True)
basisp, basisr = basis.split("_")
c = pvP(x, cG, basisp)
- try:
- radialker = kernels[basisr.upper()]
- except:
+ basisr = basisr.upper()
+ if basisr not in kernels.keys():
raise RROMPyException("Radial basis not recognized.")
+ radialker = kernels[basisr]
supportPoints = checkParameterList(supportPoints)
csh = copy(c.shape)
if len(csh) == 1: c = c.reshape(1, -1)
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 f4c6c2f..101e5d6 100644
--- a/rrompy/utilities/poly_fitting/radial_basis/vander.py
+++ b/rrompy/utilities/poly_fitting/radial_basis/vander.py
@@ -1,95 +1,96 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
import numpy as np
+from collections.abc import Iterable
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']
def rbvander(x:paramList, basis:str, reorder : List[int] = None,
directionalWeights : Np1D = None) -> Np2D:
"""Compute radial-basis-Vandermonde matrix."""
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__"):
+ elif not isinstance(directionalWeights, Iterable):
directionalWeights = directionalWeights * np.ones(x.shape[1])
RROMPyAssert(len(directionalWeights), x.shape[1],
"Number of directional weights")
- try:
- radialker = kernels[basis.upper()]
- except:
+ basis = basis.upper()
+ if basis not in kernels.keys():
raise RROMPyException("Radial basis not recognized.")
+ radialker = kernels[basis]
Van = np.zeros((nx, nx))
if reorder is not None: x = x[reorder]
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, 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("_")
VanP = pvP(x, degs, basisp, derIdxs = derIdxs, reorder = reorder,
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.py b/setup.py
index cf88ffe..b159886 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.7",
+ version="2.8",
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/heaviside_fitting.py b/tests/1_utilities/heaviside_fitting.py
index 5f625c4..dc8fc6f 100644
--- a/tests/1_utilities/heaviside_fitting.py
+++ b/tests/1_utilities/heaviside_fitting.py
@@ -1,72 +1,73 @@
# Copyright (C) 2018 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
import numpy as np
from rrompy.utilities.poly_fitting.heaviside import (polybases, polyfitname,
polydomcoeff,
heaviside2rational,
rational2heaviside)
-from rrompy.utilities.poly_fitting.polynomial.polynomial_interpolator import \
- PolynomialInterpolator
+from rrompy.utilities.poly_fitting.polynomial.polynomial_interpolator import (
+ PolynomialInterpolator, PolynomialInterpolatorNodal)
def test_monomial_heaviside():
polyhsname = "MONOMIAL_HEAVISIDE"
assert polyhsname in polybases
fitname = polyfitname(polyhsname)
domcoeff = polydomcoeff(5, polyhsname)
assert fitname == "polyfit_heaviside"
assert np.isclose(domcoeff, 1., rtol = 1e-5)
pls = np.array([-5., 0., 2.])
cHS = np.array([4., 1., -10., -25., 4., .25])
cNum = np.array([-10., 195., -120., -15.5, 4.75, .25])
cDen = np.array([0., -10., 3., 1.])
cNum /= np.linalg.norm(cDen)
cDen /= np.linalg.norm(cDen)
numI = PolynomialInterpolator()
numI.coeffs, numI.npar, numI.polybasis = cNum, 1, "MONOMIAL"
denI = PolynomialInterpolator()
denI.coeffs, denI.npar, denI.polybasis = cDen, 1, "MONOMIAL"
numA, denA = heaviside2rational(cHS, pls, [-10., 10.])
+ assert isinstance(denA, PolynomialInterpolatorNodal)
cA, plsA, _ = rational2heaviside(numI, denI, [-10., 10.])
numA.coeffs /= (denA.coeffs[1] / cDen[1])
denA.coeffs /= (denA.coeffs[1] / cDen[1])
assert np.allclose(numA.coeffs, numI.coeffs, atol = 1e-5)
assert np.allclose(denA.coeffs, denI.coeffs, atol = 1e-5)
assert np.allclose(cA, cHS, atol = 1e-5)
assert np.allclose(plsA, pls, atol = 1e-5)
def test_chebyshev_heaviside():
polyhsname = "CHEBYSHEV_HEAVISIDE"
assert polyhsname in polybases
fitname = polyfitname(polyhsname)
domcoeff = polydomcoeff(5, polyhsname)
assert fitname == "chebfit_heaviside"
assert np.isclose(domcoeff, 16, rtol = 1e-5)
pls = np.array([-5., 0., 2. + 1.j, 7])
cHS = np.array([4., 1., 25., -10., -25., 4., .25], dtype = np.complex)
numA, denA = heaviside2rational(cHS, pls, [-10., 10.])
cA, plsA, _ = rational2heaviside(numA, denA, [-10., 10.])
assert np.allclose(cA, cHS, atol = 1e-5)
assert np.allclose(plsA, pls, atol = 1e-5)
diff --git a/tests/1_utilities/sampling.py b/tests/1_utilities/sampling.py
index b63774c..bab6905 100644
--- a/tests/1_utilities/sampling.py
+++ b/tests/1_utilities/sampling.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
import scipy.sparse as sp
from rrompy.hfengines.scipy_engines import EigenproblemEngine
-from rrompy.sampling import SamplingEngineStandard, SamplingEngineStandardPOD
+from rrompy.sampling import SamplingEngine, SamplingEnginePOD
from rrompy.parameter import parameterList
class matrixEngine(EigenproblemEngine):
def __init__(self):
N = 100
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
+ sEng = SamplingEngine(solver, verbosity = 0)
+ samples = sEng.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
+ sEng = SamplingEngine(solver, verbosity = 0)
+ samples = sEng.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)
+ sEng = SamplingEnginePOD(solver, verbosity = 0)
- samplingEngine.iterSample(mus).data
- samples = samplingEngine.projectionMatrix
+ sEng.iterSample(mus).data
+ samples = sEng.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/3_reduction_methods_1D/rational_interpolant_1d.py b/tests/3_reduction_methods_1D/rational_interpolant_1d.py
index 9f73335..50c9889 100644
--- a/tests/3_reduction_methods_1D/rational_interpolant_1d.py
+++ b/tests/3_reduction_methods_1D/rational_interpolant_1d.py
@@ -1,69 +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 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 "below tolerance. Reducing N " in out
+ assert "poorly conditioned. Reducing M " 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))
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/4_reduction_methods_multiD/greedy_pivoted_rational_2d.py b/tests/4_reduction_methods_multiD/greedy_pivoted_rational_2d.py
index 25e239f..448a5d4 100644
--- a/tests/4_reduction_methods_multiD/greedy_pivoted_rational_2d.py
+++ b/tests/4_reduction_methods_multiD/greedy_pivoted_rational_2d.py
@@ -1,87 +1,87 @@
# 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 (
RationalInterpolantPivotedGreedy as RIPG,
RationalInterpolantGreedyPivotedGreedy as RIGPG)
from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS,
SparseGridSampler as SGS)
def test_pivoted_greedy():
mu = [5.05, 7.1]
mu0 = [5., 7.]
solver = matrixRandom()
uh = solver.solve(mu)[0]
params = {"POD": True, "S": 5, "polybasis": "CHEBYSHEV",
"samplerPivot": QS([4.75, 5.25], "CHEBYSHEV"),
"SMarginal": 3, "greedyTolMarginal": 1e-2,
"radialDirectionalWeightsMarginal": 2.,
"polybasisMarginal": "MONOMIAL_GAUSSIAN",
"paramsMarginal":{"MMarginal": 1},
- "errorEstimatorKindMarginal": "LEAVE_ONE_OUT",
+ "errorEstimatorKindMarginal": "LOOK_AHEAD_RECOVER",
"matchingWeight": 1., "samplerMarginal":SGS([6.75, 7.25])}
approx = RIPG([0], solver, mu0, approx_state = True,
approxParameters = params, verbosity = 0)
approx.setupApprox()
uhP1 = approx.getApprox(mu)[0]
errP = approx.getErr(mu)[0]
errNP = approx.normErr(mu)[0]
myerrP = uhP1 - uh
assert np.allclose(np.abs(errP - myerrP), 0., rtol = 1e-3)
assert np.isclose(solver.norm(errP), errNP, rtol = 1e-3)
resP = approx.getRes(mu)[0]
resNP = approx.normRes(mu)
assert np.isclose(solver.norm(resP), resNP, rtol = 1e-3)
assert np.allclose(np.abs(resP - (solver.b(mu) - solver.A(mu).dot(uhP1))),
0., rtol = 1e-3)
assert np.isclose(errNP / solver.norm(uh), 6.0631706e-04, rtol = 1)
def test_greedy_pivoted_greedy():
mu = [5.05, 7.1]
mu0 = [5., 7.]
solver = matrixRandom()
uh = solver.solve(mu)[0]
params = {"POD": True, "nTestPoints": 100, "greedyTol": 1e-3, "S": 2,
"polybasis": "CHEBYSHEV",
"samplerPivot": QS([4.75, 5.25], "CHEBYSHEV"),
"trainSetGenerator": QS([4.75, 5.25], "CHEBYSHEV"),
"SMarginal": 3, "greedyTolMarginal": 1e-2,
"radialDirectionalWeightsMarginal": 2.,
"polybasisMarginal": "MONOMIAL_GAUSSIAN",
"paramsMarginal":{"MMarginal": 1},
- "errorEstimatorKindMarginal": "LEAVE_ONE_OUT",
+ "errorEstimatorKindMarginal": "LOOK_AHEAD_RECOVER",
"matchingWeight": 1., "samplerMarginal":SGS([6.75, 7.25])}
approx = RIGPG([0], solver, mu0, approx_state = True,
approxParameters = params, verbosity = 0)
approx.setupApprox()
uhP1 = approx.getApprox(mu)[0]
errP = approx.getErr(mu)[0]
errNP = approx.normErr(mu)[0]
myerrP = uhP1 - uh
assert np.allclose(np.abs(errP - myerrP), 0., rtol = 1e-3)
assert np.isclose(solver.norm(errP), errNP, rtol = 1e-3)
resP = approx.getRes(mu)[0]
resNP = approx.normRes(mu)
assert np.isclose(solver.norm(resP), resNP, rtol = 1e-3)
assert np.allclose(np.abs(resP - (solver.b(mu) - solver.A(mu).dot(uhP1))),
0., rtol = 1e-3)
assert np.isclose(errNP / solver.norm(uh), 6.0631706e-04, rtol = 1)
diff --git a/tests/4_reduction_methods_multiD/pivoted_rational_2d.py b/tests/4_reduction_methods_multiD/pivoted_rational_2d.py
index d73c223..87215c9 100644
--- a/tests/4_reduction_methods_multiD/pivoted_rational_2d.py
+++ b/tests/4_reduction_methods_multiD/pivoted_rational_2d.py
@@ -1,112 +1,114 @@
# 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 (RationalInterpolantPivoted as RIP,
RationalInterpolantGreedyPivoted as RIGP)
from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS,
ManualSampler as MS)
def test_pivoted_uniform():
mu = [5.05, 7.1]
mu0 = [5., 7.]
solver = matrixRandom()
uh = solver.solve(mu)[0]
params = {"POD": True, "S": 5, "polybasis": "CHEBYSHEV",
"samplerPivot": QS([4.75, 5.25], "CHEBYSHEV"), "SMarginal": 5,
"polybasisMarginal": "MONOMIAL", "matchingWeight": 1.,
"samplerMarginal": QS([6.75, 7.25], "UNIFORM")}
approx = RIP([0], solver, mu0, approx_state = True,
approxParameters = params, verbosity = 0)
approx.setupApprox()
uhP1 = approx.getApprox(mu)[0]
errP = approx.getErr(mu)[0]
errNP = approx.normErr(mu)[0]
myerrP = uhP1 - uh
assert np.allclose(np.abs(errP - myerrP), 0., rtol = 1e-3)
assert np.isclose(solver.norm(errP), errNP, rtol = 1e-3)
resP = approx.getRes(mu)[0]
resNP = approx.normRes(mu)
assert np.isclose(solver.norm(resP), resNP, rtol = 1e-3)
assert np.allclose(np.abs(resP - (solver.b(mu) - solver.A(mu).dot(uhP1))),
0., rtol = 1e-3)
assert np.isclose(errNP / solver.norm(uh), 6.0631706e-04, rtol = 1)
def test_pivoted_manual_grid(capsys):
mu = [5.05, 7.1]
mu0 = [5., 7.]
solver = matrixRandom()
uh = solver.solve(mu)[0]
params = {"POD": False, "S": 5, "polybasis": "MONOMIAL",
"samplerPivot": MS([4.75, 5.25], np.array([5.]),
normalFoci = [0., 0.]), "SMarginal": 5,
"polybasisMarginal": "MONOMIAL", "matchingWeight": 1.,
"samplerMarginal": MS([6.75, 7.25], np.linspace(6.75, 7.25, 5)),
- "robustTol": 1e-6, "interpRcond": 1e-3, "cutOffTolerance": 1.}
+ "robustTol": 1e-6, "interpRcond": 1e-3}
approx = RIP([0], solver, mu0, approx_state = True,
approxParameters = params, verbosity = 0)
approx.setupApprox()
uhP1 = approx.getApprox(mu)[0]
errP = approx.getErr(mu)[0]
errNP = approx.normErr(mu)[0]
myerrP = uhP1 - uh
assert np.allclose(np.abs(errP - myerrP), 0., rtol = 1e-3)
assert np.isclose(solver.norm(errP), errNP, rtol = 1e-3)
resP = approx.getRes(mu)[0]
resNP = approx.normRes(mu)
assert np.isclose(solver.norm(resP), resNP, rtol = 1e-3)
assert np.allclose(np.abs(resP - (solver.b(mu) - solver.A(mu).dot(uhP1))),
0., rtol = 1e-3)
assert np.isclose(errNP / solver.norm(uh), .4763489, rtol = 1)
out, err = capsys.readouterr()
assert ("poorly conditioned" not in out)
assert len(err) == 0
def test_pivoted_greedy():
mu = [5.05, 7.1]
mu0 = [5., 7.]
solver = matrixRandom()
uh = solver.solve(mu)[0]
params = {"POD": True, "nTestPoints": 100, "greedyTol": 1e-4,
"collinearityTol": 1e8, "errorEstimatorKind": "DISCREPANCY",
"S": 5, "polybasis": "CHEBYSHEV",
"samplerPivot": QS([4.75, 5.25], "UNIFORM"),
"trainSetGenerator": QS([4.75, 5.25], "CHEBYSHEV"),
"SMarginal": 5, "polybasisMarginal": "MONOMIAL",
"samplerMarginal": QS([6.75, 7.25], "UNIFORM"),
- "matchingWeight": 1., "cutOffTolerance": 1.5}
+ "matchingWeight": 1.}
+ solver.cutOffPolesRMinRel, solver.cutOffPolesRMaxRel = -3., 3.
+ solver.cutOffPolesIMinRel, solver.cutOffPolesIMaxRel = -1.5, 1.5
approx = RIGP([0], solver, mu0, approx_state = True,
approxParameters = params, verbosity = 0)
approx.setupApprox()
uhP1 = approx.getApprox(mu)[0]
errP = approx.getErr(mu)[0]
errNP = approx.normErr(mu)[0]
myerrP = uhP1 - uh
assert np.allclose(np.abs(errP - myerrP), 0., rtol = 1e-3)
assert np.isclose(solver.norm(errP), errNP, rtol = 1e-3)
resP = approx.getRes(mu)[0]
resNP = approx.normRes(mu)
assert np.isclose(solver.norm(resP), resNP, rtol = 1e-3)
assert np.allclose(np.abs(resP - (solver.b(mu) - solver.A(mu).dot(uhP1))),
0., rtol = 1e-3)
assert np.isclose(errNP / solver.norm(uh), 1.181958e-02, rtol = 1)