diff --git a/examples/5_anisotropic_square/anisotropic_square.py b/examples/5_anisotropic_square/anisotropic_square.py
index 2dd8934..f7ef858 100644
--- a/examples/5_anisotropic_square/anisotropic_square.py
+++ b/examples/5_anisotropic_square/anisotropic_square.py
@@ -1,80 +1,81 @@
### example from Smetana, Zahm, Patera. Randomized residual-based error
### estimators for parametrized equations.
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 (
RationalInterpolantGreedyPivotedGreedyPoleMatch 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",
+ "polybasisMarginal": "PIECEWISE_LINEAR_UNIFORM",
+ "polybasis": "LEGENDRE", "samplerPivot":QS(zs, "UNIFORM"),
+ "trainSetGenerator":QS(zs, "UNIFORM"),
+ "errorEstimatorKind":"LOOK_AHEAD_RES",
+ "errorEstimatorKindMarginal":"LOOK_AHEAD_RECOVER",
+ "matchingChordalRadius": [1., "AUTO"],
"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))
solver.cutOffPolesRMinRel = - 1. - tol
solver.cutOffPolesRMaxRel = 1. + tol
- params['matchingShared'] = shared
+ params["matchingShared"] = shared
approx = RIGPG([0], solver, mu0 = [z0, L0], 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.set_xlabel("mu_1")
+ ax1.set_ylabel("mu_2")
ax1.grid()
- ax2.plot(polesE, tspace, 'k-.', linewidth = 1)
+ 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.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.set_xlabel("mu_1")
+ ax2.set_ylabel("mu_2")
ax2.grid()
plt.show()
print("\n")
diff --git a/rrompy/hfengines/base/hfengine_base.py b/rrompy/hfengines/base/hfengine_base.py
index ae452c1..1250741 100644
--- a/rrompy/hfengines/base/hfengine_base.py
+++ b/rrompy/hfengines/base/hfengine_base.py
@@ -1,427 +1,427 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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,
mu_independent)
from rrompy.utilities.base.types import (Np1D, Np2D, List, DictAny, paramVal,
paramList, sampList)
from rrompy.utilities.numerical import solve as tsolve, dot
from rrompy.utilities.expression import expressionEvaluator
from rrompy.utilities.exception_manager import RROMPyException, 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 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
@mu_independent
def C(self, mu:paramVal):
"""
Value of C. Should be overridden (with something like
return self._C(mu)
) if a mu-dependent C is needed.
"""
if self._C is None: self._C = 1.
return self._C
def setCQuadratic(self, quad : bool = 1):
if isinstance(quad, (str,)):
quad = quad.upper().strip().replace(" ","")
if quad == "SELFADJOINT":
quad = 2
elif quad in ["STANDARD", "GENERAL"]:
quad = 1
else:
raise RROMPyException(("String keyword for output symmetry "
"not recognized."))
if quad:
self._is_C_quadratic = int(quad)
elif hasattr(self, "_is_C_quadratic"):
del self._is_C_quadratic
@property
def isCEye(self):
"""
Whether the action of C can be seen as a scalar multiplication. Should
be overridden (with
return True
) if a mu-dependent scalar C is used.
"""
return isinstance(self._C, Number)
def applyC(self, u:sampList, mu:paramVal):
"""Apply LHS of linear system."""
if not (hasattr(self, "_is_C_quadratic") and self._is_C_quadratic):
return dot(self.C(mu), u)
return self._applyCQuadratic(u, mu)
def _applyCQuadratic(self, u:sampList, mu:paramVal, v : sampList = None,
onlyDiag : bool = False):
"""Apply quadratic LHS of linear system."""
if not (hasattr(self, "_is_C_quadratic") and self._is_C_quadratic):
raise RROMPyException(("Cannot call quadratic output routine if "
"output is not quadratic."))
C = self.C(mu)
is_C_list = isinstance(C, list)
if ((is_C_list and np.any([Ci.ndim != 2 for Ci in C]))
or not (is_C_list or C.ndim in [2, 3])):
raise RROMPyException(("C array for quadratic output must have 2 "
"or 3 dimensions or be list of "
"2-dimensional arrays."))
symmetry = v is None # computing u^H * C * u
selfadjoint = self._is_C_quadratic == 2 and symmetry
if isinstance(u, sampleList): u = u.data
if isinstance(v, sampleList): v = v.data
while u.ndim < 2: u = np.expand_dims(u, -1)
if v is None: v = u
while v.ndim < 2: v = np.expand_dims(v, -1)
N, M = u.shape[1], v.shape[1]
if onlyDiag:
if N != M:
raise RROMPyException(("Cannot extract diagonal of "
"rectangular output."))
Ncol = (N,)
elif symmetry:
Ncol = (N * (N + 1) // 2,) if selfadjoint else (N ** 2,)
else:
Ncol = (N, M)
for j in range(N):
Rv = v[:, j] if onlyDiag else v[:, j :] if selfadjoint else v
if is_C_list:
cj = np.array([[dot(dot(Ci, u[:, j]), Rv.conj())] for Ci in C])
else:
cj = dot(dot(C, u[:, j]), Rv.conj())
if C.ndim == 2: cj = np.expand_dims(cj, 0)
if selfadjoint and (onlyDiag or N == 1): cj = np.real(cj)
if j == 0: res = np.empty((len(cj),) + Ncol, dtype = cj[0].dtype)
if not onlyDiag and symmetry: # map 2D idx to hierarchical 1D idx
if self._is_C_quadratic == 2:
res[:, j * (j + 1) // 2] = cj[:, 0]
iD = np.arange(j + 1, N) * np.arange(j + 4, N + 3) // 2 - j
res[:, iD] = 2 * cj[:, 1 :] # exploit symmetry
else:
res[:, j ** 2 : j * (j + 1)] = cj[:, : j]
iD = np.arange(j, N) * np.arange(j + 2, N + 2) - j
res[:, iD] = cj[:, j :]
else:
res[:, j] = cj
return res
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 = self.checkParameterList(mu)
mu_loc, idx, sizes = listScatter(mu, return_sizes = True)
mu_loc = self.checkParameterList(mu_loc)
req, emptyCores = [], np.where(np.logical_not(sizes))[0]
if len(mu_loc) == 0:
uL, uT = recv(source = 0, tag = poolRank())
sol = np.empty((uL, 0), dtype = uT)
else:
applyCglob = (hasattr(self, "_is_C_quadratic")
and self._is_C_quadratic)
if RHS is None: # build RHSs
RHS = sampleList([self.b(m) for m in mu_loc])
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_loc) - 1) + 1, len(RHS), "Sample size")
for j, mj in enumerate(mu_loc):
u = tsolve(self.A(mj), RHS[mult * j], self._solver,
self._solverArgs)
if not (return_state or applyCglob): u = self.applyC(u, mj)
if j == 0:
sol = np.empty((len(u), len(mu_loc)), dtype = u.dtype)
if masterCore():
for dest in emptyCores:
req += [isend((len(u), u.dtype), dest = dest,
tag = dest)]
sol[:, j] = u
for r in req: r.wait()
sol = matrixGatherv(sol, sizes)
if not return_state and applyCglob: sol = self.applyC(sol, mu)
return sampleList(sol)
def residual(self, mu : paramList = [], u : sampList = None,
post_c : bool = True) -> sampList:
"""
Find residual of linear system for given approximate solution.
Args:
mu: parameter value.
u: numpy complex array with function dofs. If None, set to 0.
post_c: whether to post-process using c. Defaults to True.
"""
from rrompy.sampling import sampleList, emptySampleList
if mu == []: mu = self.mu0
mu = self.checkParameterList(mu)
if len(mu) == 0: return emptySampleList()
mu_loc, idx, sizes = listScatter(mu, return_sizes = True)
mu_loc = self.checkParameterList(mu_loc)
req, emptyCores = [], np.where(np.logical_not(sizes))[0]
if len(mu_loc) == 0:
uL, uT = recv(source = 0, tag = poolRank())
res = np.empty((uL, 0), dtype = uT)
else:
applyCglob = (hasattr(self, "_is_C_quadratic")
and self._is_C_quadratic)
v = sampleList(np.zeros((self.spacedim, len(mu_loc))))
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_loc, v)):
r = self.b(mj) - dot(self.A(mj), vj)
if post_c and not applyCglob: r = self.applyC(r, mj)
if j == 0:
res = np.empty((len(r), len(mu_loc)), dtype = r.dtype)
if masterCore():
for dest in emptyCores:
req += [isend((len(r), r.dtype), dest = dest,
tag = dest)]
res[:, j] = r
for r in req: r.wait()
res = matrixGatherv(res, sizes)
if post_c and applyCglob: res = self.applyC(res, mu)
return sampleList(res)
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
cutOffResAngleMin, cutOffResAngleMax = -1, np.pi + 1
@property
def _ignoreResidues(self):
return (self.cutOffResNormMin <= 0. and self.cutOffResAngleMin <= 0.
and self.cutOffResAngleMax >= np.pi)
def flagBadPolesResidues(self, poles:Np1D, residues : Np1D = None,
relative : bool = False,
projMat : Np2D = None) -> Np1D:
"""
Flag (numerical) poles/residues which are impossible.
Args:
poles: poles to be judged.
residues: residues norms to be judged.
relative: whether relative values should be used for poles.
projMat: matrix for projection of residues.
"""
poles = np.array(poles).flatten()
flag = np.zeros(len(poles), dtype = bool)
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)
if residues is not None and not self._ignoreResidues:
residues = np.array(residues).reshape(-1, len(poles))
if projMat is None:
resNorm = np.linalg.norm(residues, axis = 0)
else:
residues = projMat.dot(residues)
resNorm = self.norm(residues)
if self.cutOffResNormMin > 0.:
resNormEff = resNorm / np.max(resNorm)
flag = np.logical_or(flag, resNormEff < self.cutOffResNormMin)
if self.cutOffResAngleMin > 0. or self.cutOffResAngleMax < np.pi:
if projMat is None:
angles = np.real(residues.T.conj().dot(residues))
else:
angles = np.real(self.innerProduct(residues, residues))
resNormEff = resNorm
- resNormEff[np.isclose(resNormEff, 0.)] = 1.
+ resNormEff[np.isclose(resNormEff, 0., atol = 1e-15)] = 1.
angles = np.clip((angles / resNormEff).T / resNormEff, -1., 1.)
angles = np.arccos(angles)
badangles = np.logical_or(angles < self.cutOffResAngleMin,
angles > self.cutOffResAngleMax)
badangles[np.arange(len(angles)), np.arange(len(angles))] = 0
idx = np.zeros(len(angles), dtype = bool)
while np.sum(badangles) > 0:
idxn = np.argmax(np.sum(badangles, axis = 1))
badangles[idxn], badangles[:, idxn] = 0, 0
idx[idxn] = 1
flag = np.logical_or(flag, idx)
return flag
diff --git a/rrompy/parameter/parameter_map.py b/rrompy/parameter/parameter_map.py
index b2270b6..eb41030 100644
--- a/rrompy/parameter/parameter_map.py
+++ b/rrompy/parameter/parameter_map.py
@@ -1,58 +1,58 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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.):
+ if np.isclose(e, 1., atol = 1e-10):
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/generic_sampler.py b/rrompy/parameter/parameter_sampling/generic_sampler.py
index bec6d8e..a03ce28 100644
--- a/rrompy/parameter/parameter_sampling/generic_sampler.py
+++ b/rrompy/parameter/parameter_sampling/generic_sampler.py
@@ -1,94 +1,96 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
import numpy as np
from abc import abstractmethod
from rrompy.utilities.base.types import List, DictAny, paramList
from rrompy.utilities.expression import expressionEvaluator
from rrompy.utilities.exception_manager import RROMPyException
from rrompy.parameter import checkParameterList, parameterMap as pMap
__all__ = ['GenericSampler']
class GenericSampler:
"""ABSTRACT. Generic generator of sample points."""
def __init__(self, lims:paramList, parameterMap : DictAny = 1.):
self.lims = lims
self.parameterMap = pMap(parameterMap, self.npar)
def name(self) -> str:
return self.__class__.__name__
def __str__(self) -> str:
return "{}[{}_{}]".format(self.name(), self.lims[0], self.lims[1])
def __repr__(self) -> str:
return self.__str__() + " at " + hex(id(self))
def __eq__(self, other) -> bool:
if (not hasattr(other, "__dict__")
or self.__dict__.keys() != other.__dict__.keys()):
return False
for key in self.__dict__:
val = self.__dict__[key]
if isinstance(val, (np.ndarray,)):
if not np.allclose(val, other.__dict__[key]): return False
else:
if val != other.__dict__[key]: return False
return True
@property
def npar(self):
"""Number of parameters."""
return self._lims.shape[1]
def normalFoci(self, d : int = 0):
return [-1., 1.]
def groundPotential(self, d : int = 0):
fp = self.normalFoci(d)[1]
fpa = np.abs(fp)
- if np.isclose(fpa, 0.) or np.isclose(fpa, 1.): return 1.
+ if (np.isclose(fpa, 0., atol = 1e-15)
+ or np.isclose(fpa, 1., atol = 1e-10)):
+ return 1.
return (1. + np.abs(1. - fp ** 2.) ** .5) / fpa
@property
def lims(self):
"""Value of lims."""
return self._lims
@lims.setter
def lims(self, lims):
lims = checkParameterList(lims)
if len(lims) != 2:
raise RROMPyException("2 limits must be specified.")
self._lims = lims
def mapParameterList(self, mu:paramList, direct : str = "F",
idx : List[int] = None) -> paramList:
if idx is None: idx = np.arange(self.npar)
muMapped = checkParameterList(mu, len(idx))
for j, d in enumerate(idx):
muMapped.data[:, j] = expressionEvaluator(
self.parameterMap[direct][d],
muMapped(j)).flatten()
return muMapped
@abstractmethod
def generatePoints(self, n:int, reorder : bool = True) -> paramList:
"""Array of points."""
pass
diff --git a/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler.py b/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler.py
index 6ce0ec5..9580348 100644
--- a/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler.py
+++ b/rrompy/parameter/parameter_sampling/sparse_grid/sparse_grid_sampler.py
@@ -1,108 +1,108 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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 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, ["", "-HAAR"])]:
raise RROMPyException("Generator kind not recognized.")
self._kind = kind.upper()
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.array([[self._noBoundary] * self.npar], dtype = int)
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 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]
+ samePt = np.where(np.isclose(dist, 0., atol = 1e-15))[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)[0]
return self.points
diff --git a/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py b/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py
index 41a88bb..438a6f8 100644
--- a/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py
+++ b/rrompy/reduction_methods/pivoted/generic_pivoted_approximant.py
@@ -1,800 +1,818 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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 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 .trained_model.convert_trained_model_pivoted import (
convertTrainedModelPivoted)
from rrompy.utilities.base.data_structures import purgeDict, getNewFilename
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, RROMPyWarning
from rrompy.parameter import checkParameterList
from rrompy.utilities.parallel import poolRank, bcast
__all__ = ['GenericPivotedApproximantNoMatch',
'GenericPivotedApproximantPoleMatch']
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
if not hasattr(self, "_output_lvl"): self._output_lvl = []
self._output_lvl += [1 / 2]
super().__init__(*args, **kwargs)
self._postInit()
def setupSampling(self): super().setupSampling(False)
def initializeModelData(self, datadict):
if "directionPivot" in datadict.keys():
from .trained_model.trained_model_pivoted_data import (
TrainedModelPivotedData)
data = TrainedModelPivotedData(datadict["mu0"], datadict["mus"],
datadict.pop("projMat"),
datadict["scaleFactor"],
datadict.pop("parameterMap"),
datadict["directionPivot"])
if hasattr(self.HFEngine, "_is_C_quadratic") and not (
hasattr(self, "matchState") and self.matchState):
data._is_C_quadratic = self.HFEngine._is_C_quadratic
return (data, ["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 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.mapParameterListPivot(self.muBounds[0])
- self.mapParameterListPivot(self.muBounds[1]))[0])
self.scaleFactorMarginal = .5 * np.abs((
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,
pMatOld : Np2D = None, forceNew : bool = False):
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": pMat, "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, pMat))
else:
self.trainedModel.data.projMat = copy(pMat)
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, 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
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
def setTrainedModel(self, model):
"""Deepcopy approximation from trained model."""
super().setTrainedModel(model)
self.trainedModel = convertTrainedModelPivoted(self.trainedModel,
self.tModelType, self,
True)
self._finalizeMarginalization()
self.trainedModel.data.approxParameters = self.approxParameters
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': 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.
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': 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.
verbosity: Verbosity level.
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 _preliminaryMarginalFinalization(self):
pass
class GenericPivotedApproximantPoleMatch(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': 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';
- 'matchState': whether to match the system state rather than the
system output; defaults to False;
- 'matchingWeight': weight for pole matching optimization; defaults
to 1;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues; if <= 0, Euclidean metric is used; if
+ 'AUTO', automatically selected; defaults to -1;
- 'matchingShared': 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_*';
. 'interpTolMarginal': tolerance for marginal interpolation;
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.
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': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchState': whether to match the system state rather than the
system output;
- 'matchingWeight': weight for pole matching optimization;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues;
- 'matchingShared': 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;
. 'interpTolMarginal': 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.
verbosity: Verbosity level.
POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchState: Whether to match the system state rather than the system
output.
matchingWeight: Weight for pole matching optimization.
+ matchingChordalRadius: Radius to be used in chordal metric for poles
+ and residues.
matchingShared: 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(["matchState", "matchingWeight",
- "matchingShared", "polybasisMarginal",
- "paramsMarginal"],
- [False, 1., 1., "MONOMIAL", {}])
+ "matchingChordalRadius", "matchingShared",
+ "polybasisMarginal", "paramsMarginal"],
+ [False, 1., [-1, -1], 1., "MONOMIAL", {}])
self.parameterMarginalList = ["MMarginal", "nNeighborsMarginal",
"polydegreetypeMarginal",
"interpTolMarginal",
"radialDirectionalWeightsMarginalAdapt"]
super().__init__(*args, **kwargs)
self._postInit()
@property
def tModelType(self):
from .trained_model.trained_model_pivoted_rational_polematch import (
TrainedModelPivotedRationalPoleMatch)
return TrainedModelPivotedRationalPoleMatch
@property
def matchState(self):
"""Value of matchState."""
return self._matchState
@matchState.setter
def matchState(self, matchState):
self._matchState = matchState
self._approxParameters["matchState"] = self.matchState
@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 matchingChordalRadius(self):
+ """Value of matchingChordalRadius."""
+ return self._matchingChordalRadius
+ @matchingChordalRadius.setter
+ def matchingChordalRadius(self, matchingChordalRadius):
+ self._matchingChordalRadius = matchingChordalRadius
+ self._approxParameters["matchingChordalRadius"] = (
+ self.matchingChordalRadius)
+
@property
def matchingShared(self):
"""Value of matchingShared."""
return self._matchingShared
@matchingShared.setter
def matchingShared(self, matchingShared):
if matchingShared > 1.:
RROMPyWarning("Shared ratio too large. Clipping to 1.")
matchingShared = 1.
elif matchingShared < 0.:
RROMPyWarning("Shared ratio too small. Clipping to 0.")
matchingShared = 0.
self._matchingShared = matchingShared
self._approxParameters["matchingShared"] = self.matchingShared
@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 "interpTolMarginal" in keyList:
self._paramsMarginal["interpTolMarginal"] = (
paramsMarginal["interpTolMarginal"])
elif "interpTolMarginal" not in self.paramsMarginal:
self._paramsMarginal["interpTolMarginal"] = -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",
"interpTolMarginal"]
if hasattr(self, "_MMarginal_isauto"): del self._MMarginal_isauto
if hasattr(self, "_MMarginal_shift"): del self._MMarginal_shift
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.checkShared(self.matchingShared)
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:
interpPars = [self.polybasisMarginal]
if self.polybasisMarginal in ppb + rbpb:
if self.polybasisMarginal in rbpb: interpPars += [rDWMEff]
interpPars += [self.verbosity >= 5,
self.paramsMarginal["polydegreetypeMarginal"] == "TOTAL"]
if self.polybasisMarginal in ppb:
interpPars += [{}]
else: # if self.polybasisMarginal in rbpb:
interpPars += [{"optimizeScalingBounds":self.paramsMarginal[
"radialDirectionalWeightsMarginalAdapt"]}]
interpPars += [
{"rcond":self.paramsMarginal["interpTolMarginal"]}]
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]
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 _preliminaryMarginalFinalization(self):
vbMng(self, "INIT", "Compressing and matching poles.", 10)
self.trainedModel.initializeFromRational(self.matchingWeight,
self.HFEngine,
- self.matchState)
+ self.matchState,
+ self.matchingChordalRadius)
vbMng(self, "DEL", "Done compressing and matching poles.", 10)
def _postApplyC(self):
if self.POD == 1 and not (
hasattr(self.HFEngine.C, "is_mu_independent")
and self.HFEngine.C.is_mu_independent in self._output_lvl):
raise RROMPyException(("Cannot apply mu-dependent C to "
"orthonormalized samples."))
applyCglob = (hasattr(self.HFEngine, "_is_C_quadratic")
and self.HFEngine._is_C_quadratic)
vbMng(self, "INIT", "Extracting system output from state.", 35)
if applyCglob:
pMat, dirM = None, self.trainedModel.data.directionMarginal
for muM in self.trainedModel.data.musMarginal:
idx = np.where([np.allclose(mu(dirM)[0], muM[0])
for mu in self.trainedModel.data.mus])[0]
pMati = self.trainedModel.data.projMat[:, idx]
musi = self.trainedModel.data.mus[idx]
pMati = self.HFEngine.applyC(pMati, musi)
if pMat is None:
pMat = np.array(pMati)
else:
pMat = np.append(pMat, pMati, axis = 1)
else:
pMat = None
for j, mu in enumerate(musi):
pMij = self.trainedModel.data.projMat[:, j]
pMij = np.expand_dims(self.HFEngine.applyC(pMij, mu), -1)
if pMati is None:
pMat = np.array(pMij)
else:
pMat = np.append(pMat, pMij, axis = 1)
vbMng(self, "DEL", "Done extracting system output.", 35)
self.trainedModel.data.projMat = pMat
if hasattr(self.HFEngine, "_is_C_quadratic"):
self.trainedModel.data._is_C_quadratic = (
self.HFEngine._is_C_quadratic)
def setTrainedModel(self, model):
"""Deepcopy approximation from trained model."""
super().setTrainedModel(model)
self._preliminaryMarginalFinalization()
self.trainedModel.data.approxParameters = self.approxParameters
@abstractmethod
def setupApprox(self, *args, **kwargs) -> int:
if self.checkComputedApprox(): return -1
self.purgeparamsMarginal()
setupOK = super().setupApprox(*args, **kwargs)
if self.matchState: self._postApplyC()
return setupOK
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 7f00194..b732007 100644
--- a/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py
+++ b/rrompy/reduction_methods/pivoted/greedy/generic_pivoted_greedy_approximant.py
@@ -1,654 +1,663 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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,
GenericPivotedApproximantPoleMatch)
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,
buildResiduesForDistance)
-from rrompy.utilities.numerical.point_distances import chordalMetricAngleMatrix
+from rrompy.utilities.numerical.point_distances import doubleDistanceMatrix
from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert,
RROMPyWarning)
from rrompy.parameter import emptyParameterList
from rrompy.utilities.parallel import (masterCore, indicesScatter,
arrayGatherv, isend)
__all__ = ['GenericPivotedGreedyApproximantPoleMatch']
class GenericPivotedGreedyApproximantBase(GenericPivotedApproximantBase):
_allowedEstimatorKindsMarginal = ["LOOK_AHEAD", "LOOK_AHEAD_RECOVER",
"NONE"]
def __init__(self, *args, **kwargs):
self._preInit()
self._addParametersToList(["matchingWeightError",
"errorEstimatorKindMarginal",
"greedyTolMarginal", "maxIterMarginal"],
[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 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 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 _getDistanceApp(self, polesEx:Np1D, resEx:Np2D, muTest:paramVal,
foci:Tuple[float, float], ground:float) -> float:
polesAp = self.trainedModel.interpolateMarginalPoles(muTest)[0]
if self.matchingWeightError != 0:
resAp = self.trainedModel.interpolateMarginalCoeffs(muTest)[0][
: len(polesAp), :]
if (hasattr(self.trainedModel.data, "_is_C_quadratic")
and self.trainedModel.data._is_C_quadratic):
self.trainedModel._setupQuadMapping()
projMapping = self.quad_mapping
projMappingReal = self.data._is_C_quadratic == 2
else:
projMapping, projMappingReal = None, False
resEx = buildResiduesForDistance(resEx,
self.trainedModel.data.projMat,
0, projMapping, projMappingReal)
resAp = buildResiduesForDistance(resAp,
self.trainedModel.data.projMat,
0, projMapping, projMappingReal)
else:
resAp = None
- dist = chordalMetricAngleMatrix(polesEx, polesAp,
- self.matchingWeightError, resEx, resAp,
- self.HFEngine, False)
+ dist = doubleDistanceMatrix(polesEx, polesAp, self.matchingWeightError,
+ resEx, resAp, self.HFEngine, False,
+ self.trainedModel.data.chordalRadius)
pmR, pmC = pointMatching(dist)
return np.mean(dist[pmR, pmC])
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 -= 25
self.trainedModel.verbosity -= 25
foci = self.samplerPivot.normalFoci()
ground = self.samplerPivot.groundPotential()
for j in idx:
muTest = self.trainedModel._musMExcl[j]
HITest = self.trainedModel._HIsExcl[j]
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 += 25
self.trainedModel.verbosity += 25
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 == "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()
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 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)
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]
+ np.abs(musre[:, idx] - musre[0, idx]), 1),
+ 0., atol = 1e-15))[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._preliminaryMarginalFinalization()
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)[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 -= 10
self.samplingEngine.verbosity -= 10
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 += 10
self.samplingEngine.verbosity += 10
def _localPivotedResult(self, pMat:Np2D, req:ListAny, emptyCores:ListAny,
mus:Np2D) -> Tuple[Np2D, ListAny, Np2D]:
pMati = self.samplingEngine.projectionMatrix
musi = self.samplingEngine.mus
if not hasattr(self, "matchState") or not self.matchState:
if self.POD == 1 and not (
hasattr(self.HFEngine.C, "is_mu_independent")
and self.HFEngine.C.is_mu_independent in self._output_lvl):
raise RROMPyException(("Cannot apply mu-dependent C "
"to orthonormalized samples."))
vbMng(self, "INIT", "Extracting system output from state.", 35)
if (hasattr(self.HFEngine, "_is_C_quadratic")
and self.HFEngine._is_C_quadratic):
pMati = self.HFEngine.applyC(pMati, musi)
else:
pMatiEff = None
for j, mu in enumerate(musi):
pMij = np.expand_dims(self.HFEngine.applyC(pMati[:, j],
mu), -1)
if pMatiEff is None:
pMatiEff = np.array(pMij)
else:
pMatiEff = np.append(pMatiEff, pMij, axis = 1)
pMati = pMatiEff
vbMng(self, "DEL", "Done extracting system output.", 35)
if pMat is None:
mus = copy(musi.data)
pMat = copy(pMati)
if masterCore():
for dest in emptyCores:
req += [isend((len(pMat), pMat.dtype, mus.dtype),
dest = dest, tag = dest)]
else:
mus = np.vstack((mus, musi.data))
pMat = np.hstack((pMat, pMati))
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 == "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 GenericPivotedGreedyApproximantPoleMatch(
GenericPivotedGreedyApproximantBase,
GenericPivotedApproximantPoleMatch):
"""
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': 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';
- 'matchState': whether to match the system state rather than the
system output; defaults to False;
- 'matchingWeight': weight for pole matching optimization; defaults
to 1;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues; if <= 0, Euclidean metric is used; if
+ 'AUTO', automatically selected; defaults to -1;
- 'matchingShared': required ratio of marginal points to share
resonance; defaults to 1.;
- 'matchingWeightError': weight for pole matching optimization in
error estimation; defaults to 0;
- '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 '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_*';
. 'interpTolMarginal': tolerance for marginal interpolation;
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.
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': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchState': whether to match the system state rather than the
system output;
- 'matchingWeight': weight for pole matching optimization;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues;
- 'matchingShared': required ratio of marginal points to share
resonance;
- 'matchingWeightError': weight for pole matching optimization 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;
. 'interpTolMarginal': 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.
verbosity: Verbosity level.
POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchState: Whether to match the system state rather than the system
output.
matchingWeight: Weight for pole matching optimization.
+ matchingChordalRadius: Radius to be used in chordal metric for poles
+ and residues.
matchingShared: Required ratio of marginal points to share resonance.
matchingWeightError: Weight for pole matching optimization 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 _updateTrainedModelMarginalSamples(self, idx : ListAny = []):
self.trainedModel.updateEffectiveSamples(idx, self.matchingWeight,
- self.HFEngine, False)
+ self.HFEngine, False,
+ self.matchingChordalRadius)
def setupApprox(self, *args, **kwargs) -> int:
if self.checkComputedApprox(): return -1
self.purgeparamsMarginal()
_polybasisMarginal = self.polybasisMarginal
self._polybasisMarginal = ("PIECEWISE_LINEAR_"
+ self.samplerMarginal.kind)
setupOK = super().setupApprox(*args, **kwargs)
self._polybasisMarginal = _polybasisMarginal
self._finalizeMarginalization()
if self.matchState: self._postApplyC()
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 0dc1bc2..a668611 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,355 +1,362 @@
#Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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,
GenericPivotedGreedyApproximantPoleMatch)
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 (
RationalInterpolantGreedyPivotedPoleMatch)
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__ = ['RationalInterpolantGreedyPivotedGreedyPoleMatch']
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.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 -= 10
RationalInterpolantGreedy.setupApprox(self, self._plotEstPivot)
self.verbosity += 5
self.samplingEngine.verbosity += 10
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 RationalInterpolantGreedyPivotedGreedyPoleMatch(
RationalInterpolantGreedyPivotedGreedyBase,
GenericPivotedGreedyApproximantPoleMatch,
RationalInterpolantGreedyPivotedPoleMatch):
"""
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': 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';
- 'matchState': whether to match the system state rather than the
system output; defaults to False;
- 'matchingWeight': weight for pole matching optimization; defaults
to 1;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues; if <= 0, Euclidean metric is used; if
+ 'AUTO', automatically selected; defaults to -1;
- 'matchingShared': required ratio of marginal points to share
resonance; defaults to 1.;
- 'matchingWeightError': weight for pole matching optimization in
error estimation; defaults to 0;
- '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 '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_*';
. 'interpTolMarginal': tolerance for marginal interpolation;
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';
- 'interpTol': tolerance for pivot interpolation; defaults to None;
- 'QTol': tolerance for robust rational denominator management;
defaults to 0.
Defaults to empty dict.
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': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchState': whether to match the system state rather than the
system output;
- 'matchingWeight': weight for pole matching optimization;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues;
- 'matchingShared': required ratio of marginal points to share
resonance;
- 'matchingWeightError': weight for pole matching optimization 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;
. 'interpTolMarginal': 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;
- 'interpTol': tolerance for pivot interpolation;
- 'QTol': tolerance for robust rational denominator management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator via sparse
grid.
verbosity: Verbosity level.
POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchState: Whether to match the system state rather than the system
output.
matchingWeight: Weight for pole matching optimization.
+ matchingChordalRadius: Radius to be used in chordal metric for poles
+ and residues.
matchingShared: Required ratio of marginal points to share resonance.
matchingWeightError: Weight for pole matching optimization 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.
interpTol: Tolerance for pivot interpolation.
QTol: Tolerance for robust rational denominator management.
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 a6887a6..d47fc84 100644
--- a/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py
+++ b/rrompy/reduction_methods/pivoted/greedy/rational_interpolant_pivoted_greedy.py
@@ -1,285 +1,292 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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,
GenericPivotedGreedyApproximantPoleMatch)
from rrompy.reduction_methods.standard import RationalInterpolant
from rrompy.reduction_methods.pivoted import (
RationalInterpolantPivotedPoleMatch)
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__ = ['RationalInterpolantPivotedGreedyPoleMatch']
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_HFEparameterMap = copy(self.HFEngine.parameterMap)
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._mus = self._m_selfmus
self.HFEngine.parameterMap = 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 RationalInterpolantPivotedGreedyPoleMatch(
RationalInterpolantPivotedGreedyBase,
GenericPivotedGreedyApproximantPoleMatch,
RationalInterpolantPivotedPoleMatch):
"""
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': 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';
- 'matchState': whether to match the system state rather than the
system output; defaults to False;
- 'matchingWeight': weight for pole matching optimization; defaults
to 1;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues; if <= 0, Euclidean metric is used; if
+ 'AUTO', automatically selected; defaults to -1;
- 'matchingShared': required ratio of marginal points to share
resonance; defaults to 1.;
- 'matchingWeightError': weight for pole matching optimization in
error estimation; defaults to 0;
- '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 '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_*';
. 'interpTolMarginal': tolerance for marginal interpolation;
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';
- 'interpTol': tolerance for pivot interpolation; defaults to None;
- 'QTol': tolerance for robust rational denominator management;
defaults to 0.
Defaults to empty dict.
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': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchState': whether to match the system state rather than the
system output;
- 'matchingWeight': weight for pole matching optimization;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues;
- 'matchingShared': required ratio of marginal points to share
resonance;
- 'matchingWeightError': weight for pole matching optimization 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;
. 'interpTolMarginal': 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;
- 'interpTol': tolerance for pivot interpolation;
- 'QTol': tolerance for robust rational denominator management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator via sparse
grid.
verbosity: Verbosity level.
POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchState: Whether to match the system state rather than the system
output.
matchingWeight: Weight for pole matching optimization.
+ matchingChordalRadius: Radius to be used in chordal metric for poles
+ and residues.
matchingShared: Required ratio of marginal points to share resonance.
matchingWeightError: Weight for pole matching optimization 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.
interpTol: Tolerance for pivot interpolation.
QTol: Tolerance for robust rational denominator management.
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 64611cc..b4a2568 100644
--- a/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py
+++ b/rrompy/reduction_methods/pivoted/rational_interpolant_greedy_pivoted.py
@@ -1,569 +1,576 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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,
GenericPivotedApproximantPoleMatch)
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 RROMPyException, RROMPyAssert
from rrompy.parameter import emptyParameterList, parameterList
from rrompy.utilities.parallel import poolRank, indicesScatter, isend, recv
__all__ = ['RationalInterpolantGreedyPivotedNoMatch',
'RationalInterpolantGreedyPivotedPoleMatch']
class RationalInterpolantGreedyPivotedBase(GenericPivotedApproximantBase,
RationalInterpolantGreedy):
def __init__(self, *args, **kwargs):
self._preInit()
super().__init__(*args, **kwargs)
if self.nparPivot > 1: self.HFEngine._ignoreResidues = 1
self._postInit()
@property
def tModelType(self):
if hasattr(self, "_temporaryPivot"):
return RationalInterpolantGreedy.tModelType.fget(self)
return super().tModelType
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_selfmus = copy(self.mus)
self._m_HFEparameterMap = copy(self.HFEngine.parameterMap)
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._mus = self._m_selfmus
self.HFEngine.parameterMap = 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
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]
# tell greedy error estimator that operator / RHS is pivot-affine
if hasattr(self.HFEngine.A, "is_affine"):
self._A_is_affine = self.HFEngine.A.is_affine
else:
self._A_is_affine = 0
if hasattr(self.HFEngine.b, "is_affine"):
self._b_is_affine = self.HFEngine.b.is_affine
else:
self._b_is_affine = 0
if self._A_is_affine >= 1 / 2 and self._b_is_affine >= 1 / 2:
self._affine_lvl += [1 / 2]
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._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
if self._A_is_affine >= 1 / 2 and self._b_is_affine >= 1 / 2:
self._affine_lvl.pop()
del self._A_is_affine, self._b_is_affine
self.trainedModel.data.npar = self.npar
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
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.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 -= 10
RationalInterpolantGreedy.setupApprox(self, *args, **kwargs)
self.verbosity += 5
self.samplingEngine.verbosity += 10
if self.storeAllSamples: self.storeSamples(i)
musi = self.samplingEngine.mus
pMati = self.samplingEngine.projectionMatrix
if not hasattr(self, "matchState") or not self.matchState:
if self.POD == 1 and not (
hasattr(self.HFEngine.C, "is_mu_independent")
and self.HFEngine.C.is_mu_independent in self._output_lvl):
raise RROMPyException(("Cannot apply mu-dependent C "
"to orthonormalized samples."))
vbMng(self, "INIT", "Extracting system output from state.",
35)
if (hasattr(self.HFEngine, "_is_C_quadratic")
and self.HFEngine._is_C_quadratic):
pMati = self.HFEngine.applyC(pMati, musi)
else:
pMatiEff = None
for j, mu in enumerate(musi):
pMij = np.expand_dims(self.HFEngine.applyC(
pMati[:, j], mu), -1)
if pMatiEff is None:
pMatiEff = np.array(pMij)
else:
pMatiEff = np.append(pMatiEff, pMij, axis = 1)
pMati = pMatiEff
vbMng(self, "DEL", "Done extracting system output.", 35)
if pMat is None:
mus = copy(musi.data)
pMat = copy(pMati)
if i == 0:
for dest in emptyCores:
req += [isend((len(pMat), pMat.dtype, mus.dtype),
dest = dest, tag = dest)]
else:
mus = np.vstack((mus, musi.data))
pMat = np.hstack((pMat, pMati))
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._preliminaryMarginalFinalization()
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': 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';
- 'interpTol': tolerance for pivot interpolation; defaults to
None;
- 'QTol': tolerance for robust rational denominator management;
defaults to 0.
Defaults to empty dict.
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': 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;
- 'interpTol': tolerance for pivot interpolation;
- 'QTol': tolerance for robust rational denominator management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator.
verbosity: Verbosity level.
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.
interpTol: Tolerance for pivot interpolation.
QTol: Tolerance for robust rational denominator management.
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 RationalInterpolantGreedyPivotedPoleMatch(
RationalInterpolantGreedyPivotedBase,
GenericPivotedApproximantPoleMatch):
"""
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': 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';
- 'matchState': whether to match the system state rather than the
system output; defaults to False;
- 'matchingWeight': weight for pole matching optimization; defaults
to 1;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues; if <= 0, Euclidean metric is used; if
+ 'AUTO', automatically selected; defaults to -1;
- 'matchingShared': 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_*';
. 'interpTolMarginal': tolerance for marginal interpolation;
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';
- 'interpTol': tolerance for pivot interpolation; defaults to None;
- 'QTol': tolerance for robust rational denominator management;
defaults to 0.
Defaults to empty dict.
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': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchState': whether to match the system state rather than the
system output;
- 'matchingWeight': weight for pole matching optimization;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues;
- 'matchingShared': 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;
. 'interpTolMarginal': 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;
- 'interpTol': tolerance for pivot interpolation;
- 'QTol': tolerance for robust rational denominator management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator.
verbosity: Verbosity level.
POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchState: Whether to match the system state rather than the system
output.
matchingWeight: Weight for pole matching optimization.
+ matchingChordalRadius: Radius to be used in chordal metric for poles
+ and residues.
matchingShared: 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.
interpTol: Tolerance for pivot interpolation.
QTol: Tolerance for robust rational denominator management.
muBounds: list of bounds for pivot parameter values.
muBoundsMarginal: list of bounds for marginal parameter values.
samplingEngine: Sampling engine.
uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as
sampleList.
lastSolvedHF: Parameter(s) corresponding to last computed high fidelity
solution(s) as parameterList.
uApproxReduced: Reduced approximate solution(s) with parameter(s)
lastSolvedApprox as sampleList.
lastSolvedApproxReduced: Parameter(s) corresponding to last computed
reduced approximate solution(s) as parameterList.
uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as
sampleList.
lastSolvedApprox: Parameter(s) corresponding to last computed
approximate solution(s) as parameterList.
Q: Numpy 1D vector containing complex coefficients of approximant
denominator.
P: Numpy 2D vector whose columns are FE dofs of coefficients of
approximant numerator.
"""
def setupApprox(self, *args, **kwargs) -> int:
if self.checkComputedApprox(): return -1
self.purgeparamsMarginal()
setupOK = super().setupApprox(*args, **kwargs)
if self.matchState: self._postApplyC()
return setupOK
diff --git a/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py b/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py
index 3142c71..38b8170 100644
--- a/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py
+++ b/rrompy/reduction_methods/pivoted/rational_interpolant_pivoted.py
@@ -1,484 +1,491 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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 copy import deepcopy as copy
from .generic_pivoted_approximant import (GenericPivotedApproximantBase,
GenericPivotedApproximantNoMatch,
GenericPivotedApproximantPoleMatch)
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 (RROMPyException, RROMPyAssert,
RROMPyWarning)
from rrompy.parameter import emptyParameterList
from rrompy.utilities.parallel import poolRank, indicesScatter, isend, recv
__all__ = ['RationalInterpolantPivotedNoMatch',
'RationalInterpolantPivotedPoleMatch']
class RationalInterpolantPivotedBase(GenericPivotedApproximantBase,
RationalInterpolant):
def __init__(self, *args, **kwargs):
self._preInit()
self._addParametersToList(toBeExcluded = ["polydegreetype"])
super().__init__(*args, **kwargs)
if self.nparPivot > 1: self.HFEngine._ignoreResidues = 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 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'."))
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:
musi = self.mus[self.S * i : self.S * (i + 1)]
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(musi)
vbMng(self, "DEL", "Done computing snapshots.", 10)
self.verbosity -= 5
self.samplingEngine.verbosity -= 10
self._setupRational(self._setupDenominator())
self.verbosity += 5
self.samplingEngine.verbosity += 10
if self.storeAllSamples: self.storeSamples(i)
pMati = self.samplingEngine.projectionMatrix
if not hasattr(self, "matchState") or not self.matchState:
if self.POD == 1 and not (
hasattr(self.HFEngine.C, "is_mu_independent")
and self.HFEngine.C.is_mu_independent in self._output_lvl):
raise RROMPyException(("Cannot apply mu-dependent C "
"to orthonormalized samples."))
vbMng(self, "INIT", "Extracting system output from state.",
35)
if (hasattr(self.HFEngine, "_is_C_quadratic")
and self.HFEngine._is_C_quadratic):
pMati = self.HFEngine.applyC(pMati, musi)
else:
pMatiEff = None
for j, mu in enumerate(musi):
pMij = np.expand_dims(self.HFEngine.applyC(
pMati[:, j], mu), -1)
if pMatiEff is None:
pMatiEff = np.array(pMij)
else:
pMatiEff = np.append(pMatiEff, pMij, axis = 1)
pMati = pMatiEff
vbMng(self, "DEL", "Done extracting system output.", 35)
if pMat is None:
pMat = copy(pMati)
if i == 0:
for dest in emptyCores:
req += [isend((len(pMat), pMat.dtype), dest = dest,
tag = dest)]
else:
pMat = np.hstack((pMat, pMati))
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._preliminaryMarginalFinalization()
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': 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';
- 'interpTol': tolerance for pivot interpolation; defaults to
None;
- 'QTol': tolerance for robust rational denominator management;
defaults to 0.
Defaults to empty dict.
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': 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;
- 'interpTol': tolerance for pivot interpolation;
- 'QTol': tolerance for robust rational denominator management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator.
verbosity: Verbosity level.
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.
interpTol: Tolerance for pivot interpolation.
QTol: Tolerance for robust rational denominator management.
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 RationalInterpolantPivotedPoleMatch(RationalInterpolantPivotedBase,
GenericPivotedApproximantPoleMatch):
"""
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': 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';
- 'matchState': whether to match the system state rather than the
system output; defaults to False;
- 'matchingWeight': weight for pole matching optimization; defaults
to 1;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues; if <= 0, Euclidean metric is used; if
+ 'AUTO', automatically selected; defaults to -1;
- 'matchingShared': 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_*';
. 'interpTolMarginal': tolerance for marginal interpolation;
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';
- 'interpTol': tolerance for pivot interpolation; defaults to None;
- 'QTol': tolerance for robust rational denominator management;
defaults to 0.
Defaults to empty dict.
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': kind of snapshots orthogonalization;
- 'scaleFactorDer': scaling factors for derivative computation;
- 'matchState': whether to match the system state rather than the
system output;
- 'matchingWeight': weight for pole matching optimization;
+ - 'matchingChordalRadius': radius to be used in chordal metric for
+ poles and residues;
- 'matchingShared': 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;
. 'interpTolMarginal': 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;
- 'interpTol': tolerance for pivot interpolation;
- 'QTol': tolerance for robust rational denominator management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of pivot samples current approximant relies
upon;
- 'samplerPivot': pivot sample point generator;
- 'SMarginal': total number of marginal samples current approximant
relies upon;
- 'samplerMarginal': marginal sample point generator.
verbosity: Verbosity level.
POD: Kind of snapshots orthogonalization.
scaleFactorDer: Scaling factors for derivative computation.
matchState: Whether to match the system state rather than the system
output.
matchingWeight: Weight for pole matching optimization.
+ matchingChordalRadius: Radius to be used in chordal metric for poles
+ and residues.
matchingShared: 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.
interpTol: Tolerance for pivot interpolation.
QTol: Tolerance for robust rational denominator management.
muBounds: list of bounds for pivot parameter values.
muBoundsMarginal: list of bounds for marginal parameter values.
samplingEngine: Sampling engine.
uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as
sampleList.
lastSolvedHF: Parameter(s) corresponding to last computed high fidelity
solution(s) as parameterList.
uApproxReduced: Reduced approximate solution(s) with parameter(s)
lastSolvedApprox as sampleList.
lastSolvedApproxReduced: Parameter(s) corresponding to last computed
reduced approximate solution(s) as parameterList.
uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as
sampleList.
lastSolvedApprox: Parameter(s) corresponding to last computed
approximate solution(s) as parameterList.
Q: Numpy 1D vector containing complex coefficients of approximant
denominator.
P: Numpy 2D vector whose columns are FE dofs of coefficients of
approximant numerator.
"""
def setupApprox(self, *args, **kwargs) -> int:
if self.checkComputedApprox(): return -1
self.purgeparamsMarginal()
setupOK = super().setupApprox(*args, **kwargs)
if self.matchState: self._postApplyC()
return setupOK
diff --git a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational_polematch.py b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational_polematch.py
index 4a4f77a..69f9653 100644
--- a/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational_polematch.py
+++ b/rrompy/reduction_methods/pivoted/trained_model/trained_model_pivoted_rational_polematch.py
@@ -1,503 +1,557 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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.special import factorial as fact
-from scipy.sparse import csr_matrix, hstack, SparseEfficiencyWarning
+from scipy.sparse import spmatrix, csr_matrix, hstack, SparseEfficiencyWarning
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 .trained_model_pivoted_rational_nomatch import (
TrainedModelPivotedRationalNoMatch)
-from rrompy.utilities.base.types import (Np1D, Np2D, List, ListAny, paramVal,
- paramList, sampList, HFEng)
+from rrompy.utilities.base.types import (Tuple, Np1D, Np2D, List, ListAny,
+ paramVal, paramList, sampList, HFEng)
from rrompy.utilities.base import verbosityManager as vbMng, freepar as fp
from rrompy.utilities.numerical import dot
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 (rational2heaviside,
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, RROMPyAssert
from rrompy.sampling import sampleList, emptySampleList
__all__ = ['TrainedModelPivotedRationalPoleMatch']
+def getChordalScaling(x:Np2D, r:float) -> Tuple[Np2D, Np2D]:
+ goodX = np.where(np.logical_not(np.isinf(x[:, 0])))[0]
+ normX = np.empty(len(x))
+ normX[goodX] = np.linalg.norm(x[goodX], axis = 1) / r
+ scale = np.ones((len(normX), 1))
+ scale[goodX, 0] = 1. / (normX[goodX] ** 2. + 1.)
+ xscaled = np.zeros_like(x)
+ for j in goodX: xscaled[j] = x[j] * scale[j]
+ return xscaled, r * (1 - scale)
+
+def normalizeChordal(x:Np2D, r:float) -> Np2D:
+ for j in range(x.shape[0]):
+ x[j, -1] -= .5 * r
+ if isinstance(x, (spmatrix,)):
+ normxj = np.linalg.norm(x[j].todense())
+ else:
+ normxj = np.linalg.norm(x[j])
+ if normxj < 1e-15: normxj += np.finfo(float).eps
+ x[j] *= .5 * r / normxj
+ x[j, -1] += .5 * r
+ return x
+
+def pullbackChordal(x:Np2D, r:float) -> Np2D:
+ y = copy(x[:, : -1])
+ for j, p in enumerate(x[:, -1]):
+ scalexj = 1. - p / r
+ y[j] = np.inf if scalexj < 1e-15 else y[j] / scalexj
+ return y
+
class TrainedModelPivotedRationalPoleMatch(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 compress(self, collapse : bool = False, tol : float = 0.,
returnRMat : bool = False, **compressMatrixkwargs):
Psupp = copy(self.data.Psupp)
RMat = super().compress(collapse, tol, True, **compressMatrixkwargs)
if RMat is None: return
for obj, suppj in zip(self.data.HIs, Psupp):
obj.postmultiplyTensorize(RMat.T[suppj : suppj + obj.shape[0]])
if hasattr(self, "_HIsExcl"):
for obj, suppj in zip(self._HIsExcl, Psupp):
obj.postmultiplyTensorize(RMat.T[suppj : suppj + obj.shape[0]])
if not hasattr(self, "_PsExcl"):
self._PsuppExcl = [0] * len(self._PsuppExcl)
if returnRMat: return RMat
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:
p.setupByInterpolation(musMCNEff, valsEff, pllims,
extraPar[pts], *interpPars)
if ipts == 0:
self.data.marginalInterp = copy(p)
self.data.coeffsEff, self.data.polesEff = [], []
+ N = len(self.data.suppEffIdx)
+ goodIdx = np.where(self.data.suppEffIdx != -1)[0]
for hi, sup in zip(self.data.HIs, self.data.Psupp):
- cEff = hi.coeffs
+ pEff, cEff = hi.poles.reshape(-1, 1), hi.coeffs
+ if self.data.chordalRadius[0] > 0.:
+ pEff = np.hstack(getChordalScaling(pEff,
+ self.data.chordalRadius[0]))
+ if self.data.chordalRadius[1] > 0.:
+ cEff, cEffH = getChordalScaling(cEff,
+ self.data.chordalRadius[1])
+ else:
+ cEffH = np.empty((cEff.shape[0], 0))
if (self.data._collapsed
or self.supportEnd == cEff.shape[1]):
- cEff = copy(cEff)
+ cEff = np.hstack([cEff, cEffH])
else:
supC = self.supportEnd - 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)]
+ csr_matrix((len(cEff), supC)),
+ cEffH), "csr")
+ goodIdxC = np.append(goodIdx, np.arange(N, cEff.shape[0]))
+ self.data.coeffsEff += [cEff[goodIdxC, :]]
+ self.data.polesEff += [pEff[goodIdx]]
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.supportEnd == cEff.shape[1]):
cfBase = np.zeros((len(idxBad), cEff.shape[1]),
dtype = cEff.dtype)
else:
- cfBase = csr_matrix((len(idxBad), self.supportEnd),
+ cfBase = csr_matrix((len(idxBad),
+ self.data.coeffsEff[0].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.):
+ if not np.isclose(val, 0., atol = 1e-15):
self.data.coeffsEff[jb][idxBad] += (val
* self.data.coeffsEff[j][idxBad])
self.data.polesEff[jb][idxBad] += (val
* self.data.polesEff[j][idxBad])
+ if self.data.chordalRadius[0] > 0:
+ self.data.polesEff[jb][idxBad] = normalizeChordal(
+ self.data.polesEff[jb][idxBad],
+ self.data.chordalRadius[0])
+ if self.data.chordalRadius[1] > 0:
+ self.data.coeffsEff[jb][idxBad] = normalizeChordal(
+ self.data.coeffsEff[jb][idxBad],
+ self.data.chordalRadius[1])
warnings.filters.pop(0)
if pbM in ppb + rbpb:
approx.paramsMarginal["MMarginal"] = _MMarginalEff
vbMng(self, "DEL", "Done computing marginal interpolator.", 12)
def updateEffectiveSamples(self, exclude:List[int], *args, **kwargs):
if hasattr(self, "_idxExcl"):
for j, excl in enumerate(self._idxExcl):
self.data.HIs.insert(excl, self._HIsExcl[j])
super().updateEffectiveSamples(exclude)
self._HIsExcl = []
for excl in self._idxExcl[::-1]:
self._HIsExcl = [self.data.HIs.pop(excl)] + self._HIsExcl
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 initializeFromRational(self, matchingWeight:float, HFEngine:HFEng,
- is_state:bool):
+ 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,
- matchingWeight, HFEngine, is_state)
+ self.initializeFromLists(poles, coeffs, self.data.Psupp, basis, *args,
+ **kwargs)
def initializeFromLists(self, poles:ListAny, coeffs:ListAny, supps:ListAny,
basis:str, matchingWeight:float, HFEngine:HFEng,
- is_state:bool):
+ is_state:bool, chordalRadius:Tuple[float, float]):
"""Initialize Heaviside representation."""
- Ns = [len(pls) for pls in poles]
poles, coeffs = heavisideUniformShape(poles, coeffs)
- root = Ns.index(len(poles[0]))
+ N = len(poles[0])
+ if chordalRadius[0] == "AUTO": chordalRadius[0] = 1.
+ if chordalRadius[1] == "AUTO":
+ chordalRadius[1] = np.mean([np.linalg.norm(c[: N], axis = 1)
+ for c in coeffs])
+ self.data.chordalRadius = chordalRadius
if hasattr(self.data, "_is_C_quadratic") and self.data._is_C_quadratic:
csizemax = np.max([len(c) for c in coeffs])
#csizemax = np.max([c.shape[1] for c in coeffs])
if self.data._is_C_quadratic == 1:
csizemax = csizemax ** 2
else: # if self.data._is_C_quadratic == 2:
csizemax = csizemax * (csizemax + 1) // 2
TrainedModelRational._setupQuadMapping(self, csizemax)
projMapping = self.quad_mapping
projMappingReal = self.data._is_C_quadratic == 2
else:
projMapping, projMappingReal = None, False
poles, coeffs = rationalFunctionMatching(poles, coeffs,
self.data.musMarginal.data,
matchingWeight, supps,
self.data.projMat, HFEngine,
- is_state, root, projMapping,
- projMappingReal)
+ is_state, None, chordalRadius,
+ projMapping, projMappingReal)
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]
self.data.suppEffPts = [np.arange(len(self.data.HIs))]
self.data.suppEffIdx = np.zeros(len(poles[0]), dtype = int)
def checkShared(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."
+ self.data.suppEffIdx = - np.ones(N, dtype = int)
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, :]
+ self.data.suppEffIdx[keepPole] = 0
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)
+ return (" Hard-erased {} pole".format(N - len(keepPole))
+ + "s" * (N - len(keepPole) != 1)
+ " and soft-erased {} pole".format(len(halfPole))
+ "s" * (len(halfPole) != 1) + ".")
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 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
+ for j, m in enumerate(mI): intMPoles[j] += m * pEff
+ rCP = self.data.chordalRadius[0]
+ if rCP > 0:
+ for j in range(len(mu)):
+ intMPoles[j, ..., 0] = pullbackChordal(
+ normalizeChordal(intMPoles[j], rCP),
+ rCP)[..., 0]
vbMng(self, "DEL", "Done interpolating marginal poles.", 95)
- return intMPoles
+ return intMPoles[..., 0]
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
+ rCC = self.data.chordalRadius[1]
+ if rCC > 0:
+ for j in range(len(mu)):
+ intMCoeffs[j, ..., : -1] = pullbackChordal(normalizeChordal(
+ intMCoeffs[j], rCC), rCC)
+ intMCoeffs = intMCoeffs[..., : -1]
vbMng(self, "DEL", "Done interpolating marginal coefficients.", 95)
return intMCoeffs
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 = mP[0] - pl
for terms in combinations(np.arange(N), N - derP):
derVal[i] += np.prod(plDist[list(terms)])
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 isinstance(mVals, Iterable): mVals = [mVals]
mVals = list(mVals)
else:
mVals = [fp]
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 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), :].T
if hasattr(self.data, "_is_C_quadratic") and self.data._is_C_quadratic:
self._setupQuadMapping()
res = res[self.quad_mapping[0]] * res[self.quad_mapping[1]].conj()
if not self.data._collapsed: res = dot(self.data.projMat, res).T
if (hasattr(self.data, "_is_C_quadratic")
and self.data._is_C_quadratic == 2):
res = np.real(res)
return pls, res
diff --git a/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py b/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py
index e50cd40..e14e366 100644
--- a/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py
+++ b/rrompy/reduction_methods/standard/greedy/generic_greedy_approximant.py
@@ -1,620 +1,622 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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, 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.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': 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.
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': 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.
verbosity: Verbosity level.
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.
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()
if not hasattr(self, "_affine_lvl"): self._affine_lvl = []
self._affine_lvl += [1]
self._addParametersToList(["greedyTol", "collinearityTol", "maxIter",
"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 _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",
False, self._affine_lvl)
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.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 == 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.
+ cLevel = np.inf if np.isclose(cLevel, 0., atol = 1e-15) else 1 / cLevel
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]
+ np.abs(musre[:, idx] - musre[0, idx]), 1),
+ 0., atol = 1e-15))[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.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(len(self.mus)), 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.trainedModel = None
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.
"""
if (not hasattr(self.trainedModel.data, "gramian")
or self.trainedModel.data.gramian is None):
gramian = self.HFEngine.innerProduct(pMat, pMat, dual = True)
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.HFEngine.innerProduct(
pMat(idxNew), pMat(idxOld), dual = True)
gramian[Sold :, : Sold] = gramian[: Sold, Sold :].T.conj()
gramian[Sold :, Sold :] = self.HFEngine.innerProduct(
pMat(idxNew), pMat(idxNew), dual = True)
self.trainedModel.data.gramian = gramian
def assembleReducedResidualBlocksbb(self, bs:List[Np1D]):
"""
Build blocks (of type bb) of reduced linear system through projections.
"""
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.HFEngine.innerProduct(Mbi, Mbi, dual = True)
for j in range(i):
Mbj = bs[j]
resbb[i, j] = self.HFEngine.innerProduct(Mbj, Mbi,
dual = True)
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.
"""
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.HFEngine.innerProduct(MAj, Mbi,
dual = True)
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.HFEngine.innerProduct(
MAj, Mbi, dual = True)
self.trainedModel.data.resAb = resAb
def assembleReducedResidualBlocksAA(self, As:List[Np2D], pMat:sampList):
"""
Build blocks (of type AA) of reduced linear system through projections.
"""
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.HFEngine.innerProduct(MAi, MAi,
dual = True)
for j in range(i):
MAj = dot(As[j], pMat)
resAA[:, i, :, j] = self.HFEngine.innerProduct(MAj, MAi,
dual = True)
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.HFEngine.innerProduct(
MAi[:, Sold :], MAi[:, : Sold], dual = True)
resAA[Sold :, i, : Sold, i] = resAA[: Sold, i,
Sold :, i].T.conj()
resAA[Sold :, i, Sold :, i] = self.HFEngine.innerProduct(
MAi[:, Sold :], MAi[:, Sold :], dual = True)
for j in range(i):
MAj = dot(As[j], pMat)
resAA[: Sold, i, Sold :, j] = (
self.HFEngine.innerProduct(MAj[:, Sold :],
MAi[:, : Sold],
dual = True))
resAA[Sold :, i, : Sold, j] = (
self.HFEngine.innerProduct(MAj[:, : Sold],
MAi[:, Sold :],
dual = True))
resAA[Sold :, i, Sold :, j] = (
self.HFEngine.innerProduct(MAj[:, Sold :],
MAi[:, Sold :],
dual = True))
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",
False, self._affine_lvl)
else:
checkIfAffine(self.HFEngine, "assemble reduced RHS blocks", True,
self._affine_lvl)
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/rational_interpolant.py b/rrompy/reduction_methods/standard/rational_interpolant.py
index ca32c08..d0795b2 100644
--- a/rrompy/reduction_methods/standard/rational_interpolant.py
+++ b/rrompy/reduction_methods/standard/rational_interpolant.py
@@ -1,816 +1,816 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
from copy import deepcopy as copy
import numpy as np
from scipy.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,
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 (pseudoInverse, dot, potential,
- distanceMatrix)
+ baseDistanceMatrix)
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': 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';
- 'interpTol': tolerance for interpolation; defaults to None;
- 'QTol': tolerance for robust rational denominator management;
defaults to 0.
Defaults to empty dict.
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': 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;
- 'interpTol': tolerance for interpolation via numpy.polyfit;
- 'QTol': tolerance for robust rational denominator management.
parameterListCritical: Recognized keys of critical approximant
parameters:
- 'S': total number of samples current approximant relies upon;
- 'sampler': sample point generator.
verbosity: Verbosity level.
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.
interpTol: Tolerance for interpolation via numpy.polyfit.
QTol: Tolerance for robust rational denominator management.
muBounds: list of bounds for parameter values.
samplingEngine: Sampling engine.
uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as
sampleList.
lastSolvedHF: Parameter(s) corresponding to last computed high fidelity
solution(s) as parameterList.
uApproxReduced: Reduced approximate solution(s) with parameter(s)
lastSolvedApprox as sampleList.
lastSolvedApproxReduced: Parameter(s) corresponding to last computed
reduced approximate solution(s) as parameterList.
uApprox: Approximate solution(s) with parameter(s) lastSolvedApprox as
sampleList.
lastSolvedApprox: Parameter(s) corresponding to last computed
approximate solution(s) as parameterList.
Q: Numpy 1D vector containing complex coefficients of approximant
denominator.
P: Numpy 2D vector whose columns are FE dofs of coefficients of
approximant numerator.
"""
_allowedFunctionalSolveKinds = ["NORM", "DOMINANT", "NODAL",
"BARYCENTRIC_NORM", "BARYCENTRIC_AVERAGE"]
def __init__(self, *args, **kwargs):
self._preInit()
self._addParametersToList(["polybasis", "M", "N", "polydegreetype",
"radialDirectionalWeights",
"radialDirectionalWeightsAdapt",
"functionalSolve", "interpTol", "QTol"],
["MONOMIAL", "AUTO", "AUTO", "TOTAL", 1.,
[-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 interpTol(self):
"""Value of interpTol."""
return self._interpTol
@interpTol.setter
def interpTol(self, interpTol):
self._interpTol = interpTol
self._approxParameters["interpTol"] = self.interpTol
@property
def radialDirectionalWeights(self):
"""Value of radialDirectionalWeights."""
return self._radialDirectionalWeights
@radialDirectionalWeights.setter
def radialDirectionalWeights(self, radialDirectionalWeights):
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 QTol(self):
"""Value of tolerance for robust rational denominator management."""
return self._QTol
@QTol.setter
def QTol(self, QTol):
if QTol < 0.:
RROMPyWarning(("Overriding prescribed negative robustness "
"tolerance to 0."))
QTol = 0.
self._QTol = QTol
self._approxParameters["QTol"] = self.QTol
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:
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) != self.S):
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 == 1:
ev, eV = self.findeveVGQR(
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, TN)
if self.functionalSolve == "NODAL": break
evR = ev / np.max(ev)
ts = self.QTol * 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))
if self.N > 0 and self.functionalSolve in ["NODAL", "BARYCENTRIC_NORM",
"BARYCENTRIC_AVERAGE"]:
q = PIN()
q.polybasis, q.nodes = self.polybasis0, eV
else:
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
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 == 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.interpTol}]
wellCond, msg = p.setupByInterpolation(self._musUniqueCN,
Qevaldiag, *pParRest)
vbMng(self, "MAIN", msg, 5)
if wellCond: break
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())
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)
self.trainedModel.data.Q = Q
if P is None: P = self._setupNumerator()
while self.N > 0 and self.npar == 1:
#check for bad poles (absolute)
pls = Q.roots()
plsA = self.mapParameterList(self.mapParameterList(self.mu0)(0, 0)
+ self.scaleFactor * pls, "B")(0)
idxBadA = self.HFEngine.flagBadPolesResidues(plsA)
if self.HFEngine._ignoreResidues:
#check for bad poles (relative)
cfs, projMat = None, None
else:
#check for bad poles (relative) and residues
cfs = rational2heaviside(P, Q)[0][: self.N].T
if self.POD != 1:
projMat = self.samplingEngine.projectionMatrix
else:
projMat = None
foci = self.sampler.normalFoci()
ground = self.sampler.groundPotential()
potEff = potential(pls, foci) / ground
potEff[np.logical_or(potEff < 1., np.isinf(pls))] = 1.
cfs[:, np.isinf(pls)] = 0.
cfs /= potEff # rescale by potential
idxBadR = self.HFEngine.flagBadPolesResidues(pls, cfs, True,
projMat)
idxBad = np.logical_or(idxBadA, idxBadR)
if not np.any(idxBad): break
vbMng(self, "MAIN",
"Removing {} spurious poles out of {}.".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.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)
fullE = E + 1 == len(self._reorder) == len(self._musUniqueCN)
if fullE:
mus = self._musUniqueCN[self._reorder]
- dist = distanceMatrix(mus, magnitude = False)[..., 0]
+ dist = baseDistanceMatrix(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",
("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.interpTol, 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)
return invD, TN
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.")
vbMng(self, "INIT", "Building gramian matrix.", 10)
gramian = self.HFEngine.innerProduct(sampleE, sampleE, is_state = True)
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)
if self.functionalSolve in ["NORM", "BARYCENTRIC_NORM"]:
ev, eV = np.linalg.eigh(G)
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.interpTol, full = True)
eV = self.findeVBarycentric(np.sum(fitOut[0], axis = 1))
else:
fitOut = pseudoInverse(G[:-1, :-1], rcond = self.interpTol,
full = True)
eV = np.append(fitOut[0].dot(G[:-1, -1]), -1.)
ev = fitOut[1][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, G0)
return ev, eV
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.")
vbMng(self, "INIT", "Building half-gramian matrix stack.", 10)
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)
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[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.interpTol,
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]
+ plDist = baseDistanceMatrix(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/sampling/engines/pod_engine.py b/rrompy/sampling/engines/pod_engine.py
index 2c1eb1b..d929205 100644
--- a/rrompy/sampling/engines/pod_engine.py
+++ b/rrompy/sampling/engines/pod_engine.py
@@ -1,151 +1,151 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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.
+ if np.isclose(np.abs(alpha), 0., atol = 1e-15): 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/utilities/expression/monomial_creator.py b/rrompy/utilities/expression/monomial_creator.py
index 9857347..08bb1c8 100644
--- a/rrompy/utilities/expression/monomial_creator.py
+++ b/rrompy/utilities/expression/monomial_creator.py
@@ -1,64 +1,64 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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 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)
activex = np.where(derdiff)[0]
if len(activex) == 0:
exprLoc = (mult,)
else:
if len(activex) == 1:
activex = activex[0]
exprLoc = ("x", "()", activex, "**", derdiff[activex])
else:
exprLoc = ("prod", {"axis" : 1}, ("x", "**", derdiff))
- if not np.isclose(mult, 1):
+ if not np.isclose(mult, 1, atol = 1e-10):
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 7b45abc..6785cfc 100644
--- a/rrompy/utilities/numerical/__init__.py
+++ b/rrompy/utilities/numerical/__init__.py
@@ -1,45 +1,45 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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 .halton import haltonGenerate
from .kroneckerer import kroneckerer
from .low_discrepancy import lowDiscrepancy
-from .point_distances import distanceMatrix
+from .point_distances import baseDistanceMatrix
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',
'haltonGenerate',
'kroneckerer',
'lowDiscrepancy',
- 'distanceMatrix',
+ 'baseDistanceMatrix',
'pointMatching',
'rationalFunctionMatching',
'potential',
'pseudoInverse',
'quadraturePointsGenerate',
'sobolGenerate',
'dot',
'solve'
]
diff --git a/rrompy/utilities/numerical/point_distances.py b/rrompy/utilities/numerical/point_distances.py
index bead358..cd0726a 100644
--- a/rrompy/utilities/numerical/point_distances.py
+++ b/rrompy/utilities/numerical/point_distances.py
@@ -1,77 +1,80 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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
+from rrompy.utilities.base.types import Tuple, Np1D, Np2D, HFEng
-__all__ = ['distanceMatrix', 'vectorAngleMatrix', 'chordalMetricMatrix',
- 'chordalMetricAngleMatrix']
+__all__ = ['baseDistanceMatrix', 'vectorDistanceMatrix',
+ 'doubleDistanceMatrix']
-def distanceMatrix(x:Np2D, y : Np2D = None, npar : int = None,
- magnitude : bool = True, weights : Np1D = None) -> Np2D:
+def baseDistanceMatrix(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:
+def vectorDistanceMatrix(X:Np2D, Y:Np2D, HFEngine : HFEng = None,
+ is_state : bool = True, chordalRadius : float = -1,
+ badValue : float = 0.) -> 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.
+ if np.isinf(badValue):
+ xbad = np.where(np.isinf(norm2X))[0]
+ ybad = np.where(np.isinf(norm2Y))[0]
+ else:
+ xbad = np.where(np.isclose(norm2X, badValue, atol = 1e-15))[0]
+ ybad = np.where(np.isclose(norm2Y, badValue, atol = 1e-15))[0]
+ if chordalRadius <= 0:
+ dist2T[:, xbad], dist2T[ybad, :] = np.inf, np.inf
+ else:
+ dist2T[:, xbad], dist2T[ybad, :] = 1., 1.
+ dist2T[np.ix_(ybad, xbad)] = 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(y, x)
- 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)
+ if chordalRadius <= 0: return dist2T.T ** .5
+ norm2X[xbad], norm2Y[ybad] = 0., 0.
+ norm2X, norm2Y = norm2X / chordalRadius ** 2., norm2Y / chordalRadius ** 2.
+ return ((dist2T / (norm2X + 1.)).T / (norm2Y + 1.)) ** .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)
+def doubleDistanceMatrix(x:Np1D, y:Np1D, w : float = 0, X : Np2D = None,
+ Y : Np2D = None, HFEngine : HFEng = None,
+ is_state : bool = True,
+ chordalRadius : Tuple[float, float] = [-1] * 2) \
+ -> Np2D:
+ dist = vectorDistanceMatrix(np.reshape(x, [1, -1]), np.reshape(y, [1, -1]),
+ chordalRadius = chordalRadius[0],
+ badValue = np.inf)
if w == 0: return dist
- distAdj = vectorAngleMatrix(X, Y, HFEngine, is_state)
+ distAdj = vectorDistanceMatrix(X, Y, HFEngine, is_state, chordalRadius[1])
return (dist + w * distAdj) / (1. + w)
diff --git a/rrompy/utilities/numerical/point_matching.py b/rrompy/utilities/numerical/point_matching.py
index af0e821..7288cf6 100644
--- a/rrompy/utilities/numerical/point_matching.py
+++ b/rrompy/utilities/numerical/point_matching.py
@@ -1,105 +1,132 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
+from copy import deepcopy as copy
import numpy as np
from scipy.optimize import linear_sum_assignment as LSA
-from .point_distances import distanceMatrix, chordalMetricAngleMatrix
+from .point_distances import baseDistanceMatrix, doubleDistanceMatrix
from rrompy.utilities.base.types import Tuple, List, ListAny, Np1D, Np2D, HFEng
from rrompy.utilities.exception_manager import RROMPyAssert
__all__ = ['pointMatching', 'rationalFunctionMatching']
def pointMatching(distMatrix:Np2D) -> Tuple[Np1D, Np1D]:
return LSA(distMatrix)
def buildResiduesForDistance(res:Np2D, projMat:Np2D, supp:int,
projMapping : Np2D = None,
projMappingReal : bool = False) -> Np2D:
if projMapping is not None:
badidx = np.where(projMapping >= len(res))
if len(badidx[1]) > 0: projMapping = projMapping[:, : badidx[1][0]]
res = res[projMapping[0]] * res[projMapping[1]].conj()
if isinstance(projMat, (np.ndarray,)):
res = projMat[:, supp : supp + len(res)].dot(res)
if projMapping is not None and projMappingReal: res = np.real(res)
return res
def rationalFunctionMatching(poles:List[Np1D], coeffs:List[Np2D],
featPts:Np2D, matchingWeight:float, supps:ListAny,
projMat:Np2D, HFEngine : HFEng = None,
is_state : bool = True, root : int = None,
+ chordalRadius : Tuple[float, float] = [-1] * 2,
projMapping : Np2D = None,
projMappingReal : bool = False) \
-> 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.
+ chordalRadius(optional): Radius to be used in chordal metric. If <= 0,
+ Euclidean metric is used. Defaults to [-1, -1].
projMapping(optional): Mapping for projection based on projMap. Should
be assigned for nonlinear outputs. Defaults to None.
projMappingReal(optional): Whether projection based on projMap is
followed by collapse onto real part. Defaults to False.
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
- featDist = distanceMatrix(featPts).flatten()
+ featDist = baseDistanceMatrix(featPts)
free = list(range(M))
- if root is None: #start from sample points closest to each other
- root = np.argpartition(featDist, M)[M] % M
+ if root is None:
+ #start from sample point with closest neighbor,
+ #among those with no inf pole
+ notInfPls = np.where([np.logical_not(np.any(np.isinf(p)))
+ for p in poles])[0]
+ MEff = len(notInfPls)
+ if MEff == 1:
+ root = notInfPls[0]
+ else:
+ featDistEff = featDist[notInfPls][:, notInfPls]
+ root = notInfPls[np.argpartition(featDistEff.flatten(),
+ MEff)[MEff] % MEff]
+ polesC = copy(poles)
+ if matchingWeight != 0:
+ resC = [buildResiduesForDistance(coeffs[j][: N].T, projMat, supps[j],
+ projMapping, projMappingReal)
+ for j in range(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, plsfree = poles[Ifix], poles[Ifree]
+ plsfix, plsfree = polesC[Ifix], polesC[Ifree]
+ freeInf = np.where(np.isinf(plsfree))[0]
+ freeNotInf = np.where(np.logical_not(np.isinf(plsfree)))[0]
+ plsfree = plsfree[freeNotInf]
if matchingWeight == 0:
resfix, resfree = None, None
else:
- resfix = buildResiduesForDistance(coeffs[Ifix][: N].T, projMat,
- supps[Ifix], projMapping,
- projMappingReal)
- resfree = buildResiduesForDistance(coeffs[Ifree][: N].T, projMat,
- supps[Ifree], projMapping,
- projMappingReal)
+ resfix, resfree = resC[Ifix], resC[Ifree][:, freeNotInf]
#build assignment distance matrix
- distj = chordalMetricAngleMatrix(plsfix, plsfree, matchingWeight,
- resfix, resfree, HFEngine, is_state)
+ distj = doubleDistanceMatrix(plsfree, plsfix, matchingWeight, resfree,
+ resfix, HFEngine, is_state,
+ chordalRadius)
reordering = pointMatching(distj)[1]
- poles[Ifree] = poles[Ifree][reordering]
- coeffs[Ifree][: N] = coeffs[Ifree][reordering]
+ reorderingInf = [x for x in range(N) if x not in reordering]
+ #reorder good poles
+ poles[Ifree][reordering], poles[Ifree][reorderingInf] = (
+ poles[Ifree][freeNotInf], poles[Ifree][freeInf])
+ coeffs[Ifree][reordering], coeffs[Ifree][reorderingInf] = (
+ coeffs[Ifree][freeNotInf], coeffs[Ifree][freeInf])
+ #transfer missing poles over
+ polesC[Ifree][reordering], polesC[Ifree][reorderingInf] = (
+ polesC[Ifree][freeNotInf], polesC[Ifix][reorderingInf])
+ if matchingWeight != 0:
+ resC[Ifree][:, reordering], resC[Ifree][:, reorderingInf] = (
+ resC[Ifree][:, freeNotInf], resC[Ifix][:, reorderingInf])
return poles, coeffs
diff --git a/rrompy/utilities/numerical/potential.py b/rrompy/utilities/numerical/potential.py
index 457f7e8..57e65ae 100644
--- a/rrompy/utilities/numerical/potential.py
+++ b/rrompy/utilities/numerical/potential.py
@@ -1,44 +1,44 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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 np.isclose(musig, 0., atol = 1e-15):
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/poly_fitting/heaviside/heaviside_manipulation.py b/rrompy/utilities/poly_fitting/heaviside/heaviside_manipulation.py
index 4937fac..ee99588 100644
--- a/rrompy/utilities/poly_fitting/heaviside/heaviside_manipulation.py
+++ b/rrompy/utilities/poly_fitting/heaviside/heaviside_manipulation.py
@@ -1,41 +1,49 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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])
+ NEff = -1
+ for j in range(len(poles)):
+ N = len(poles[j])
+ if N <= NEff: continue
+ keepPole = np.where(np.logical_not(np.isinf(poles[j])))[0]
+ NEff = max(NEff, len(keepPole))
+ poles[j] = poles[j][keepPole]
+ keepRes = np.append(keepPole, np.arange(N, residues[j].shape[0]))
+ residues[j] = residues[j][keepRes]
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/nearest_neighbor/val.py b/rrompy/utilities/poly_fitting/nearest_neighbor/val.py
index b6489e7..c574c92 100644
--- a/rrompy/utilities/poly_fitting/nearest_neighbor/val.py
+++ b/rrompy/utilities/poly_fitting/nearest_neighbor/val.py
@@ -1,39 +1,39 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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.numerical import baseDistanceMatrix
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 = distanceMatrix(supportPoints, x, weights = directionalWeights)
+ muDiff = baseDistanceMatrix(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/kernel.py b/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py
index bf1a0fa..527ec74 100644
--- a/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py
+++ b/rrompy/utilities/poly_fitting/piecewise_linear/kernel.py
@@ -1,63 +1,65 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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:
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.:
+ if (np.isclose(widthL, 0., atol = 1e-12)
+ or supp + (.95 + .1 * noBdr) * widthL < - 1.):
isleft, isright = 0, 1
- elif np.isclose(widthR, 0.) or supp + (.95 + .1 * noBdr) * widthR > 1.:
+ elif (np.isclose(widthR, 0., atol = 1e-12)
+ or supp + (.95 + .1 * noBdr) * widthR > 1.):
isleft, isright = 1, 0
else:
isleft, isright = xC < 0., xC >= 0.
y = 1. - xC / (widthL * isleft + widthR * isright)
return np.clip(y, 0., 1., y)
def hatFunction(x:paramList, supportPoints:paramList, depths:Np2D,
kind:str, lims:paramList) -> Np2D:
x = checkParameterList(x)
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/polynomial/polynomial_interpolator.py b/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py
index 3c0bc85..26d84d0 100644
--- a/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py
+++ b/rrompy/utilities/poly_fitting/polynomial/polynomial_interpolator.py
@@ -1,226 +1,227 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with RROMPy. If not, see .
#
from copy import deepcopy as copy
import numpy as np
from scipy.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 .polynomial_algebra import changePolyBasis, polyTimes
-from rrompy.utilities.numerical import dot, distanceMatrix
+from rrompy.utilities.numerical import dot, baseDistanceMatrix
from rrompy.utilities.numerical.degree import degreeTotalToFull
from rrompy.utilities.exception_manager import RROMPyAssert, RROMPyException
from rrompy.parameter import checkParameterList
__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 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 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")
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]
+ plDist = baseDistanceMatrix(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/tests/4_reduction_methods_multiD/greedy_pivoted_rational_2d.py b/tests/4_reduction_methods_multiD/greedy_pivoted_rational_2d.py
index 8c250e8..086c13e 100644
--- a/tests/4_reduction_methods_multiD/greedy_pivoted_rational_2d.py
+++ b/tests/4_reduction_methods_multiD/greedy_pivoted_rational_2d.py
@@ -1,85 +1,87 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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 (
RationalInterpolantPivotedGreedyPoleMatch as RIPG,
RationalInterpolantGreedyPivotedGreedyPoleMatch 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},
+ "paramsMarginal":{"MMarginal": 1,
+ "radialDirectionalWeightsMarginalAdapt": [1e9, 1e12]},
"errorEstimatorKindMarginal": "LOOK_AHEAD_RECOVER",
"matchingWeight": 1., "samplerMarginal":SGS([6.75, 7.25])}
approx = RIPG([0], solver, mu0, 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": "LOOK_AHEAD_RECOVER",
- "matchingWeight": 1., "samplerMarginal":SGS([6.75, 7.25])}
+ "matchingWeight": 1., "matchingChordalRadius": [1., "AUTO"],
+ "samplerMarginal":SGS([6.75, 7.25])}
approx = RIGPG([0], solver, mu0, 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)
+ assert np.isclose(errNP / solver.norm(uh), .106066, 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 2a65e0a..c1bcb56 100644
--- a/tests/4_reduction_methods_multiD/pivoted_rational_2d.py
+++ b/tests/4_reduction_methods_multiD/pivoted_rational_2d.py
@@ -1,112 +1,113 @@
# Copyright (C) 2018-2020 by the RROMPy authors
#
# This file is part of RROMPy.
#
# RROMPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RROMPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General 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 (
RationalInterpolantPivotedPoleMatch as RIP,
RationalInterpolantGreedyPivotedPoleMatch 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, 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.,
+ "matchingChordalRadius": [1., "AUTO"],
"samplerMarginal": MS([6.75, 7.25], np.linspace(6.75, 7.25, 5)),
"QTol": 1e-6, "interpTol": 1e-3}
approx = RIP([0], solver, mu0, 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.}
+ "matchingWeight": 1.,
+ "samplerMarginal": QS([6.75, 7.25], "UNIFORM")}
solver.cutOffPolesRMinRel, solver.cutOffPolesRMaxRel = -3., 3.
solver.cutOffPolesIMinRel, solver.cutOffPolesIMaxRel = -1.5, 1.5
approx = RIGP([0], solver, mu0, 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)
+ assert np.isclose(errNP / solver.norm(uh), 7.8581e-2, rtol = 1)