diff --git a/examples/scipy/LagrangeSweep.py b/examples/scipy/LagrangeSweep.py
index 7194251..d1d09c9 100644
--- a/examples/scipy/LagrangeSweep.py
+++ b/examples/scipy/LagrangeSweep.py
@@ -1,85 +1,102 @@
 from copy import copy
 import numpy as np
-from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE
-from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE
+#from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE
+from rrompy.hfengines.scipy import HelmholtzCavityScatteringProblemEngine as HBSPE
 from rrompy.reduction_methods.lagrange import ApproximantLagrangePade as Pade
 from rrompy.reduction_methods.lagrange import ApproximantLagrangeRB as RB
 from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper
 from rrompy.utilities.parameter_sampling import QuadratureSampler as QS
+from rrompy.utilities.parameter_sampling import WarpingFunction as WF
+from rrompy.utilities.parameter_sampling import WarpingSampler as WS
 
 verb = 0
-testNo = 1
 npoints = 100
 homog = True
 homog = False
+LSratio = 2. / 3.
+sampling = "Uniform"
+#sampling = "Cheby"
+#sampling = "SinC"
 
-if testNo == 1:
-    ks = np.power([24 + 5.j, 34 + 5.j], .5)
-    solver = HSBPE(kappa = 32 ** .5, theta = np.pi / 3, n = 15)
-    solver.omega = np.real(np.mean(ks))
-    mutars = np.linspace(21**.5, 42**.5, npoints)
-    filenamebase = '../data/output/HelmholtzBubbleSHIFTLSweep'
-    homog = False
-elif testNo == 2:
-    ks = [10 + 0.j, 14 + 0.j]
-    solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, n = 10)
-    solver.omega = np.real(np.mean(ks))
-    mutars = np.linspace(9, 15, npoints)
-    homogMSG = ""
-    if not homog: homogMSG = "Non"
-    filenamebase = '../data/output/HelmholtzBoxLSweep' + homogMSG + "Homog"
+assert LSratio <= 1. + np.finfo(float).eps
+
+ks = [10 + 0.j, 14 + 0.j]
+solver = HBSPE(kappa = 3, n = 15)
+solver.omega = np.real(np.mean(ks))
+mutars = np.linspace(9, 15, npoints)
+homogMSG = "Homog"
+if not homog: homogMSG = "Non" + homogMSG
+#filenamebase = '../data/output/HelmholtzBoxLSweep'
+filenamebase = '../data/plots/LagrangeScatteringSquare1p5'
+filenamebase = filenamebase + sampling + "/HelmholtzBoxLSweep" + homogMSG
 
 k0 = np.mean(ks)
-shift = 6
-nsets = 5
-stride = 3
+shift = 7
+shift = np.int(8 / LSratio - 1)
+nsets = 3
+stride = np.int(8 / LSratio)
 Smax = stride * (nsets - 1) + shift + 2
 
 rescaling = lambda x: np.power(x, 2.)
 rescalingInv = lambda x: np.power(x, .5)
-paramsPade = {'S':Smax, 'POD':True,
-              'sampler':QS(ks, "CHEBYSHEV", rescaling, rescalingInv)}    
+if sampling == "Uniform":
+    sampler = QS(ks, "UNIFORM", rescaling, rescalingInv)
+if sampling == "Cheby":
+    sampler = QS(ks, "CHEBYSHEV", rescaling, rescalingInv)
+if sampling == "SinC":
+    warping = WF(call = lambda x: x - 2. * (1. - LSratio) / np.pi * np.sin(np.pi * x),
+                 repr = "x-{}*sin(pi*x)".format(2. * (1. - LSratio) / np.pi))
+    sampler = WS(ks, warping, rescaling, rescalingInv)
+
+paramsPade = {'S':Smax, 'POD':True, 'sampler':sampler}
 paramsRB = copy(paramsPade)
 paramsPoly = copy(paramsPade)
 paramsSetsPade = [None] * nsets
 paramsSetsRB = [None] * nsets
 paramsSetsPoly = [None] * nsets
 for i in range(nsets):
-    paramsSetsPade[i] = {'N': stride * i + shift + 1,
-                         'M': stride * i + shift + 1,
+    paramsSetsPade[i] = {'N': np.int(LSratio * (stride * i + shift + 1)),
+                         'M': np.int(LSratio * (stride * i + shift + 1)),
                          'S': stride * i + shift + 2}
-    paramsSetsRB[i] = {'R': stride * i + shift + 2,
+    paramsSetsRB[i] = {'R': np.int(LSratio * (stride * i + shift + 1)),
                        'S': stride * i + shift + 2}
     paramsSetsPoly[i] = {'N': 0,
-                         'M': stride * i + shift + 1,
+                         'M': np.int(LSratio * (stride * i + shift + 1)),
                          'S': stride * i + shift + 2}
 
 appPade = Pade(solver, mu0 = k0, approxParameters = paramsPade,
                verbosity = verb, homogeneize = homog)
 appRB = RB(solver, mu0 = k0, approxParameters = paramsRB, 
            verbosity = verb, homogeneize = homog)
 appPoly = Pade(solver, mu0 = k0, approxParameters = paramsPoly,
                verbosity = verb, homogeneize = homog)
 
 sweeper = Sweeper(mutars = mutars, mostExpensive = 'Approx')
 sweeper.ROMEngine = appPade
 sweeper.params = paramsSetsPade
 filenamePade = sweeper.sweep(filenamebase + 'Pade.dat')
 sweeper.ROMEngine = appRB
 sweeper.params = paramsSetsRB
 filenameRB = sweeper.sweep(filenamebase + 'RB.dat')
 sweeper.ROMEngine = appPoly
 sweeper.params = paramsSetsPoly
 filenamePoly = sweeper.sweep(filenamebase + 'Poly.dat')
 
 sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'],
                     ['normHF', 'normApp'], ['S'], onePlot = True,
-                    save = filenamebase + 'Norm',
-                    saveFormat = "png", labels = ["Pade'", "RB", "Poly"])
+                    save = filenamebase + 'Norm', ylims = {'top' : 1e1},
+                    saveFormat = "png", labels = ["Pade'", "RB", "Poly"],
+#                    figsize = (5, 3.75))
+                    figsize = (10, 7.5))
 sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'],
                     ['normResRel'], ['S'], save = filenamebase + 'Res',
-                    saveFormat = "png", labels = ["Pade'", "RB", "Poly"])
+                    ylims = {'top' : 1e1}, saveFormat = "png",
+                    labels = ["Pade'", "RB", "Poly"],
+#                    figsize = (5, 3.75))
+                    figsize = (10, 7.5))
 sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'],
                     ['normErrRel'], ['S'], save = filenamebase + 'Err',
-                    saveFormat = "png", labels = ["Pade'", "RB", "Poly"])
-
+                    ylims = {'top' : 1e1}, saveFormat = "png",
+                    labels = ["Pade'", "RB", "Poly"],
+#                    figsize = (5, 3.75))
+                    figsize = (10, 7.5))
diff --git a/examples/scipy/PadeLagrange.py b/examples/scipy/PadeLagrange.py
index 6fdcec3..e3f330f 100644
--- a/examples/scipy/PadeLagrange.py
+++ b/examples/scipy/PadeLagrange.py
@@ -1,107 +1,107 @@
 import numpy as np
 from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE
 from rrompy.hfengines.scipy import HelmholtzSquareTransmissionProblemEngine as HSTPE
 from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE
 from rrompy.reduction_methods.lagrange import ApproximantLagrangePade as Pade
 from rrompy.utilities.parameter_sampling import QuadratureSampler as QS
 
 testNo = 2
 verb = 0
 homog = True
 homog = False
 
 if testNo == 1:
     k0s = np.power([10 + 0.j, 14 + 0.j], .5)
     k0 = np.mean(k0s)
     ktar = (11 + .5j) ** .5
 
     rescaling = lambda x: np.power(x, 2.)
     rescalingInv = lambda x: np.power(x, .5)
     params = {'N':4, 'M':3, 'S':5, 'POD':True,
               'sampler':QS(k0s, "CHEBYSHEV", rescaling, rescalingInv)}    
 
     solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40,
                    verbosity = verb)
     solver.omega = np.real(k0)
     approx = Pade(solver, mu0 = k0, approxParameters = params,
                   verbosity = verb)
     
     approx.setupApprox()
 #    approx.plotSamples()
     approx.plotApp(ktar, name = 'u_Pade''')
     approx.plotHF(ktar, name = 'u_HF')
     approx.plotErr(ktar, name = 'err')
     approx.plotRes(ktar, name = 'res')
     
     appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar)
     resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar)
     print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr,
                                                    np.divide(appErr, solNorm)))
     print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm,
                                                   np.divide(resNorm, RHSNorm)))
     print('\nPoles Pade'':')
     print(approx.getPoles())
 
 ############
 elif testNo == 2:
     k0s = [3.85 + 0.j, 4.15 + 0.j]
     k0 = np.mean(k0s)
     ktar = 4 + .15j
 
     rescaling = lambda x: np.power(x, 2.)
     rescalingInv = lambda x: np.power(x, .5)
     params = {'N':8, 'M':9, 'S':10, 'POD':True,
               'sampler':QS(k0s, "CHEBYSHEV", rescaling, rescalingInv)}    
 
     solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50,
                    verbosity = verb)
     solver.omega = np.real(k0)
     approx = Pade(solver, mu0 = k0, approxParameters = params,
                   verbosity = verb, homogeneize = homog)
     
     approx.setupApprox()
-    approx.plotSamples()
+#    approx.plotSamples()
     approx.plotApp(ktar, name = 'u_Pade''')
     approx.plotHF(ktar, name = 'u_HF')
     approx.plotErr(ktar, name = 'err')
     approx.plotRes(ktar, name = 'res')
     
     appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar)
     resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar)
     print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr,
                                                    np.divide(appErr, solNorm)))
     print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm,
                                                   np.divide(resNorm, RHSNorm)))
     print('\nPoles Pade'':')
     print(approx.getPoles())
 
 ############
 elif testNo == 3:
     k0s = [2, 5]
     k0 = np.mean(k0s)
     ktar = 4.5 - 0.j
     params = {'N':10, 'M':11, 'S':15, 'POD':True,
               'sampler':QS(k0s, "CHEBYSHEV")}
     
     solver = HBSPE(R = 7, kappa = 3, theta = - np.pi * 75 / 180, n = 40,
                    verbosity = verb)
     solver.omega = np.real(k0)
     approx = Pade(solver, mu0 = k0, approxParameters = params,
                   verbosity = verb, homogeneize = homog)
     
     approx.setupApprox()
     approx.plotSamples()
     approx.plotApp(ktar, name = 'u_Pade''')
     approx.plotHF(ktar, name = 'u_HF')
     approx.plotErr(ktar, name = 'err')
     approx.plotRes(ktar, name = 'res')
     
     appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar)
     resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar)
     print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr,
                                                    np.divide(appErr, solNorm)))
     print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm,
                                                   np.divide(resNorm, RHSNorm)))
     print('\nPoles Pade'':')
     print(approx.getPoles())
     
diff --git a/examples/scipy/PadeTaylor.py b/examples/scipy/PadeTaylor.py
index 7bae70c..4fe5a30 100644
--- a/examples/scipy/PadeTaylor.py
+++ b/examples/scipy/PadeTaylor.py
@@ -1,97 +1,97 @@
 import numpy as np
 from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE
 from rrompy.hfengines.scipy import (
                              HelmholtzSquareTransmissionProblemEngine as HSTPE)
 from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE
 from rrompy.reduction_methods.taylor import ApproximantTaylorPade as Pade
 
-testNo = 4
+testNo = 2
 verb = 0
 homog = True
 #homog = False
 
 if testNo == 1:
     params = {'N':4, 'M':3, 'E':4, 'sampleType':'Arnoldi', 'POD':True}
     k0 = 12 ** .5
     ktar = 10.5 ** .5
     
     solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40,
                    verbosity = verb)
     solver.omega = np.real(k0)
     approx = Pade(solver, mu0 = k0, approxParameters = params,
                   verbosity = verb)
     
     approx.setupApprox()
 #    approx.plotSamples()
     approx.plotApp(ktar, name = 'u_Pade''')
     approx.plotHF(ktar, name = 'u_HF')
     approx.plotErr(ktar, name = 'err')
     approx.plotRes(ktar, name = 'res')
     
     appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar)
     resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar)
     print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr,
                                                    np.divide(appErr, solNorm)))
     print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm,
                                                   np.divide(resNorm, RHSNorm)))
     print('\nPoles Pade'':')
     print(approx.getPoles())
 
 ############
 elif testNo == 2:
     params = {'N':6, 'M':7, 'E':7, 'sampleType':'Arnoldi', 'POD':True}
     k0 = 16 ** .5
     ktar = 15 ** .5
     
     solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50,
                    verbosity = verb)
     solver.omega = np.real(k0)
     approx = Pade(solver, mu0 = k0, approxParameters = params,
                   verbosity = verb, homogeneize = homog)
     
     approx.setupApprox()
 #    approx.plotSamples()
     approx.plotApp(ktar, name = 'u_Pade''')
     approx.plotHF(ktar, name = 'u_HF')
     approx.plotErr(ktar, name = 'err')
     approx.plotRes(ktar, name = 'res')
     
     appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar)
     resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar)
     print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr,
                                                    np.divide(appErr, solNorm)))
     print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm,
                                                   np.divide(resNorm, RHSNorm)))
     print('\nPoles Pade'':')
     print(approx.getPoles())
 
 ############
 elif testNo in [3, 4]:
     if testNo == 3:
         params = {'N':7, 'M':8, 'E':8, 'sampleType':'Krylov', 'POD':True}
     else:
         params = {'N':7, 'M':8, 'E':8, 'sampleType':'Arnoldi', 'POD':True}
     k0 = 3
     ktar = 4.+0.j
 
     solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, n = 30,
                    verbosity = verb)
     solver.omega = np.real(k0)
     approx = Pade(solver, mu0 = k0, approxParameters = params,
                   verbosity = verb, homogeneize = homog)
 
     approx.setupApprox()
     approx.plotSamples()
     approx.plotApp(ktar, name = 'u_Pade''')
     approx.plotHF(ktar, name = 'u_HF')
     approx.plotErr(ktar, name = 'err')
     approx.plotRes(ktar, name = 'res')
     
     appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar)
     resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar)
     print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr,
                                                    np.divide(appErr, solNorm)))
     print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm,
                                                   np.divide(resNorm, RHSNorm)))
     print('\nPoles Pade'':')
     print(approx.getPoles())
diff --git a/rrompy/hfengines/base/boundary_conditions.py b/rrompy/hfengines/base/boundary_conditions.py
index 48bebb9..f92ae87 100644
--- a/rrompy/hfengines/base/boundary_conditions.py
+++ b/rrompy/hfengines/base/boundary_conditions.py
@@ -1,110 +1,113 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from rrompy.utilities.base.types import GenExpr
 from rrompy.utilities.base.fenics import bdrTrue, bdrFalse
 
 __all__ = ['BoundaryConditions']
 
 class BoundaryConditions:
     """Boundary conditions manager."""
 
     allowedKinds = ["Dirichlet", "Neumann", "Robin"]
 
     def __init__(self, kind : str = None):
         if kind is None: return
         kind = kind[0].upper() + kind[1:].lower()
         if kind in self.allowedKinds:
             getattr(self.__class__, kind + "Boundary", None).fset(self, "ALL")
         else:
             raise Exception("BC kind not recognized.")
 
     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 _generalManagement(self, kind:str, value:GenExpr):
         if isinstance(value, (str,)):
             value = value.upper()
             if value.upper() == "ALL":
                 self._complementaryManagementAll(kind)
             elif value.upper() == "REST":
                 self._complementaryManagementRest(kind)
             else:
                 raise Exception("Wildcard not recognized.")
         elif callable(value):
             self._standardManagement(kind, value)
         else:
             raise Exception(kind + "Boundary type not recognized.")
 
     def _complementaryManagementAll(self, kind:str):
         if kind not in self.allowedKinds:
             raise Exception("BC kind not recognized.")
         for k in self.allowedKinds:
             if k != kind:
                 getattr(self.__class__, k + "Boundary").fset(self, bdrFalse)
         super().__setattr__("_" + kind + "Boundary", bdrTrue)
         if hasattr(self, "_" + kind + "Rest"):
             super().__delattr__("_" + kind + "Rest")
 
     def _complementaryManagementRest(self, kind:str):
         if kind not in self.allowedKinds:
             raise Exception("BC kind not recognized.")
         otherBCs = []
         for k in self.allowedKinds:
             if k != kind:
                 if hasattr(self, "_" + k + "Rest"):
                     raise Exception("Only 1 'REST' wildcard can be specified.")
                 otherBCs += [getattr(self, k + "Boundary")]
         def restCall(x, on_boundary):
             return (on_boundary
                 and not any([bc(x, on_boundary) for bc in otherBCs]))
         super().__setattr__("_" + kind + "Boundary", restCall)
         super().__setattr__("_" + kind + "Rest", 1)
 
     def _standardManagement(self, kind:str, bc:callable):
         super().__setattr__("_" + kind + "Boundary", bc)
         if hasattr(self, "_" + kind + "Rest"):
             super().__delattr__("_" + kind + "Rest")
 
     @property
     def DirichletBoundary(self):
         """Function handle to DirichletBoundary."""
         return self._DirichletBoundary
     @DirichletBoundary.setter
     def DirichletBoundary(self, DirichletBoundary):
         self._generalManagement("Dirichlet", DirichletBoundary)
         
     @property
     def NeumannBoundary(self):
         """Function handle to NeumannBoundary."""
         return self._NeumannBoundary
     @NeumannBoundary.setter
     def NeumannBoundary(self, NeumannBoundary):
         self._generalManagement("Neumann", NeumannBoundary)
 
     @property
     def RobinBoundary(self):
         """Function handle to RobinBoundary."""
         return self._RobinBoundary
     @RobinBoundary.setter
     def RobinBoundary(self, RobinBoundary):
         self._generalManagement("Robin", RobinBoundary)
 
diff --git a/rrompy/hfengines/base/problem_engine_base.py b/rrompy/hfengines/base/problem_engine_base.py
index fd909ab..3767bcd 100644
--- a/rrompy/hfengines/base/problem_engine_base.py
+++ b/rrompy/hfengines/base/problem_engine_base.py
@@ -1,333 +1,336 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from abc import abstractmethod
 import fenics as fen
 import numpy as np
 from scipy.sparse import csr_matrix
 import scipy.sparse as scsp
 import scipy.sparse.linalg as scspla
 from matplotlib import pyplot as plt
 from rrompy.utilities.base.types import (Np1D, ScOp, strLst, FenFunc,
                                          Tuple, List)
 from rrompy.utilities.base import purgeList, getNewFilename, verbosityDepth
 from .boundary_conditions import BoundaryConditions
 
 __all__ = ['ProblemEngineBase']
 
 class ProblemEngineBase:
     """
     Generic solver for parametric problems.
     """
 
     nAs, nbs = 1, 1
     functional = lambda self, u: 0.
 
     def __init__(self, degree_threshold : int = np.inf, verbosity : int = 10):
         self.BCManager = BoundaryConditions("Dirichlet")
         self.V = fen.FunctionSpace(fen.UnitSquareMesh(10, 10), "P", 1)
         self.verbosity = verbosity
         self.resetAs()
         self.resetbs()
         self.bsmu = np.nan
         self.liftDirichletDatamu = np.nan
         self.mu0BC = np.nan
         self.degree_threshold = degree_threshold
     
     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 innerProduct(self, u:Np1D, v:Np1D) -> float:
         """Hilbert space scalar product."""
         if not hasattr(self, "energyNormMatrix"):
             self.buildEnergyNormForm()
         return v.conj().T.dot(self.energyNormMatrix.dot(u))
 
     def buildEnergyNormForm(self):
         """
         Build sparse matrix (in CSR format) representative of scalar product.
         """
         if self.verbosity >= 20:
             verbosityDepth("INIT", "Assembling energy matrix.", end = "")
         normMat = fen.assemble(fen.dot(self.u, self.v) * fen.dx)
         normMatFen = fen.as_backend_type(normMat).mat()
         self.energyNormMatrix = csr_matrix(normMatFen.getValuesCSR()[::-1],
                                            shape = normMat.size)
         if self.verbosity >= 20:
             verbosityDepth("DEL", " Done.", inline = True)
 
     def norm(self, u:Np1D) -> float:
         return np.abs(self.innerProduct(u, u)) ** .5
 
     def rescaling(self, x:Np1D) -> Np1D:
         """Rescaling in parameter dependence."""
         return x
 
     def rescalingInv(self, x:Np1D) -> Np1D:
         """Inverse rescaling in parameter dependence."""
         return x
 
     def checkAInBounds(self, der : int = 0):
         """Check if derivative index is oob for operator of linear system."""
         if der < 0 or der >= self.nAs:
             d = self.V.dim()
             return scsp.csr_matrix((np.zeros(0), np.zeros(0), np.zeros(d + 1)),
                                    shape = (d, d), dtype = np.complex)
 
     def checkbInBounds(self, der : int = 0, homogeneized : bool = False):
         """Check if derivative index is oob for RHS of linear system."""
         if der < 0 or der >= max(self.nbs, self.nAs * homogeneized):
             return np.zeros(self.V.dim(), dtype = np.complex)
 
     def setDirichletDatum(self, mu:complex):
         """Set Dirichlet datum if parametric."""
         if hasattr(self, "liftedDirichletDatum"):
             self.liftDirichletDatamu = mu
 
     def liftDirichletData(self, mu:complex) -> Np1D:
         """Lift Dirichlet datum."""
         self.setDirichletDatum(mu)
         if not np.isclose(self.liftDirichletDatamu, mu):
             try:
                 liftRe = fen.interpolate(self.DirichletDatum[0], self.V)
             except:
                 liftRe = fen.project(self.DirichletDatum[0], self.V)
             try:
                 liftIm = fen.interpolate(self.DirichletDatum[1], self.V)
             except:
                 liftIm = fen.project(self.DirichletDatum[1], self.V)
             self.liftedDirichletDatum = (np.array(liftRe.vector())
                                  + 1.j * np.array(liftIm.vector()))
         return self.liftedDirichletDatum
 
     def resetAs(self):
         """Reset (derivatives of) operator of linear system."""
         self.As = [None] * self.nAs
 
     def resetbs(self):
         """Reset (derivatives of) RHS of linear system."""
         self.bs = {True: [None] * max(self.nbs, self.nAs),
                    False: [None] * self.nbs}
 
     def reduceQuadratureDegree(self, fun:FenFunc, name:str):
         """Check whether to reduce compiler parameters to degree threshold."""
         if not np.isinf(self.degree_threshold):
             from ufl.algorithms.estimate_degrees import (
                                       estimate_total_polynomial_degree as ETPD)
             try:
                 deg = ETPD(fun)
             except:
                 return False
             if deg > self.degree_threshold:
                 if self.verbosity >= 15:
                     verbosityDepth("MAIN", ("Reducing quadrature degree from "
                                             "{} to {} for {}.").format(
                                                          deg,
                                                          self.degree_threshold,
                                                          name))
                 return True
         return False
 
     def iterReduceQuadratureDegree(self, funsNames:List[Tuple[FenFunc, str]]):
         """
         Iterate reduceQuadratureDegree over list and define reduce compiler
             parameters.
         """
         if funsNames is not None:
             for fun, name in funsNames:
                 if self.reduceQuadratureDegree(fun, name):
                     return {"quadrature_degree" : self.degree_threshold}
         return {}
 
     @abstractmethod
     def A(self, mu:complex, der : int = 0) -> ScOp:
         """Assemble (derivative of) operator of linear system."""
         Anull = self.checkAInBounds(der)
         if Anull is not None: return Anull
         if self.As[der] is None:
             self.As[der] = 0.
         return self.As[der]
 
     @abstractmethod
     def b(self, mu:complex, der : int = 0,
           homogeneized : bool = False) -> Np1D:
         """Assemble (derivative of) RHS of linear system."""
         bnull = self.checkbInBounds(der, homogeneized)
         if bnull is not None: return bnull
         if self.bs[homogeneized][der] is None:
             self.bs[homogeneized][der] = 0.
         return self.bs[homogeneized][der]
 
     def affineBlocksA(self, mu : complex = 0.) -> Tuple[List[Np1D], callable]:
         """Assemble affine blocks of operator of linear system."""
         def lambdas(x, j):
             if j == 0: return np.ones(np.size(x))
             if j in range(1, self.nAs): return np.power(self.rescaling(x)
                                                       - self.rescaling(mu), j)
             raise Exception("Wrong j value.")
         As = [None] * self.nAs
         for j in range(self.nAs):
             As[j] = self.A(mu, j)
         return As, lambdas
 
     def affineBlocksb(self, mu : complex = 0., homogeneized : bool = False)\
                                                 -> Tuple[List[Np1D], callable]:
         """Assemble affine blocks of RHS of linear system."""
         def lambdas(x, j):
             if j == 0: return np.ones(np.size(x))
             if j in range(1, self.nbsEff): return np.power(self.rescaling(x)
                                                          - self.rescaling(mu),
                                                            j)
             raise Exception("Wrong j value.")
         if homogeneized:
             self.nbsEff = max(self.nAs, self.nbs)
         else:
             self.nbsEff = self.nbs
         bs = [None] * self.nbsEff
         for j in range(self.nbsEff):
             bs[j] = self.b(mu, j, homogeneized)
         return bs, lambdas
 
     def solve(self, mu:complex, RHS : Np1D = None,
               homogeneized : bool = False) -> Np1D:
         """
         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.
         """
         A = self.A(mu)
         if RHS is None: RHS = self.b(mu, 0, homogeneized)
         return scspla.spsolve(A, RHS)
 
     def residual(self, u:Np1D, mu:complex,
                  homogeneized : bool = False) -> Np1D:
         """
         Find residual of linear system for given approximate solution.
 
         Args:
             u: numpy complex array with function dofs. If None, set to 0.
             mu: parameter value.
         """
         A = self.A(mu)
         RHS = self.b(mu, 0, homogeneized)
         if u is None: return RHS
         return RHS - A.dot(u)
 
     def plot(self, u:Np1D, name : str = "u", save : str = None,
              what : strLst = 'all', saveFormat : str = "eps",
              saveDPI : int = 100, **figspecs):
         """
         Do some nice plots of the complex-valued function with given dofs.
 
         Args:
             u: numpy complex array with function dofs.
             name(optional): Name to be shown as title of the plots. Defaults to
                 'u'.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             what(optional): Which plots to do. If list, can contain 'ABS',
                 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'.
                 Defaults to 'ALL'.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         if isinstance(what, (str,)):
             if what.upper() == 'ALL':
                 what = ['ABS', 'PHASE', 'REAL', 'IMAG']
             else:
                 what = [what]
         what = purgeList(what, ['ABS', 'PHASE', 'REAL', 'IMAG'],
                          listname = self.name() + ".what", baselevel = 1)
         if len(what) == 0: return
         if 'figsize' not in figspecs.keys():
             figspecs['figsize'] = (13. * len(what) / 4, 3)
         subplotcode = 100 + len(what) * 10
 
         plt.figure(**figspecs)
         plt.jet()
         if 'ABS' in what:
             uAb = fen.Function(self.V)
             uAb.vector()[:] = np.array(np.abs(u), dtype = float)
             subplotcode = subplotcode + 1
             plt.subplot(subplotcode)
             p = fen.plot(uAb, title = "|{0}|".format(name))
             plt.colorbar(p)
         if 'PHASE' in what:
             uPh = fen.Function(self.V)
             uPh.vector()[:] = np.array(np.angle(u), dtype = float)
             subplotcode = subplotcode + 1
             plt.subplot(subplotcode)
             p = fen.plot(uPh, title = "phase({0})".format(name))
             plt.colorbar(p)
         if 'REAL' in what:
             uRe = fen.Function(self.V)
             uRe.vector()[:] = np.array(np.real(u), dtype = float)
             subplotcode = subplotcode + 1
             plt.subplot(subplotcode)
             p = fen.plot(uRe, title = "Re({0})".format(name))
             plt.colorbar(p)
         if 'IMAG' in what:
             uIm = fen.Function(self.V)
             uIm.vector()[:] = np.array(np.imag(u), dtype = float)
             subplotcode = subplotcode + 1
             plt.subplot(subplotcode)
             p = fen.plot(uIm, title = "Im({0})".format(name))
             plt.colorbar(p)
         if save is not None:
             save = save.strip()
             plt.savefig(getNewFilename("{}_fig_".format(save), saveFormat),
                         format = saveFormat, dpi = saveDPI)
         plt.show()
         plt.close()
 
     def plotmesh(self, name : str = "Mesh", save : str = None,
                  saveFormat : str = "eps", saveDPI : int = 100, **figspecs):
         """
         Do a nice plot of the mesh.
 
         Args:
             u: numpy complex array with function dofs.
             name(optional): Name to be shown as title of the plots. Defaults to
                 'u'.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         plt.figure(**figspecs)
         fen.plot(self.V.mesh())
         if save is not None:
             save = save.strip()
             plt.savefig(getNewFilename("{}_msh_".format(save), saveFormat),
                         format = saveFormat, dpi = saveDPI)
         plt.show()
         plt.close()
 
diff --git a/rrompy/reduction_methods/base/generic_approximant.py b/rrompy/reduction_methods/base/generic_approximant.py
index 924764e..1dbdbb1 100644
--- a/rrompy/reduction_methods/base/generic_approximant.py
+++ b/rrompy/reduction_methods/base/generic_approximant.py
@@ -1,499 +1,502 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from abc import abstractmethod
 import numpy as np
 from copy import copy
 from rrompy.sampling.base.sampling_engine_base import SamplingEngineBase
 from rrompy.utilities.base.types import Np1D, DictAny, HFEng, sampleEng, strLst
 from rrompy.utilities.base import purgeDict, verbosityDepth
 
 __all__ = ['GenericApproximant']
 
 class GenericApproximant:
     """
     ABSTRACT
     ROM approximant computation for parametric problems.
     
     Args:
         HFEngine: HF problem solver.
         mu0(optional): Default parameter. Defaults to 0.
         approxParameters(optional): Dictionary containing values for main
             parameters of approximant. Recognized keys are:
             - 'POD': whether to compute POD of snapshots; defaults to True.
             Defaults to empty dict.
         verbosity(optional): Verbosity level. Defaults to 10.
 
     Attributes:
         HFEngine: HF problem solver.
         mu0: Default parameter.
         w: Weight for norm computation.
         approxParameters: Dictionary containing values for main parameters of
             approximant. Recognized keys are in parameterList.
         parameterList: Recognized keys of approximant parameters:
             - 'POD': whether to compute POD of snapshots.
         verbosity: Verbosity level.
         POD: Whether to compute POD of snapshots.
         samplingEngine: Sampling engine.
         uHF: High fidelity solution with wavenumber lastSolvedHF as numpy
             complex vector.
         lastSolvedHF: Wavenumber corresponding to last computed high fidelity
             solution.
         uApp: Last evaluated approximant as numpy complex vector.
         lastApproxParameters: List of parameters corresponding to last
             computed approximant.
     """
 
     def __init__(self, HFEngine:HFEng, mu0 : complex = 0,
                  approxParameters : DictAny = {}, homogeneize : bool = False,
                  verbosity : int = 10):
         self._preInit()
         self.verbosity = verbosity
         if self.verbosity >= 10:
             verbosityDepth("INIT", ("Initializing approximant engine of "
                                     "type {}.").format(self.name()))
         self.HFEngine = HFEngine
         self._HFEngine0 = copy(HFEngine)
         if not hasattr(self, "parameterList"):
             self.parameterList = []
         self.parameterList += ["POD"]
 
         self.mu0 = mu0
         self.homogeneize = homogeneize
         self.approxParameters = approxParameters
         self._postInit()
 
     def _preInit(self):
         if not hasattr(self, "depth"): self.depth = 0
         else: self.depth += 1
 
     def _postInit(self):
         if self.depth == 0:
             if self.verbosity >= 10:
                 verbosityDepth("DEL", "Done initializing.\n")
             del self.depth
         else: self.depth -= 1
 
     def name(self) -> str:
         return self.__class__.__name__
         
     def __str__(self) -> str:
         return self.name()
 
+    def __repr__(self) -> str:
+        return self.__str__() + " at " + hex(id(self))
+
     def setupSampling(self, SamplingEngine : sampleEng = SamplingEngineBase):
         """Setup sampling engine."""
         self.samplingEngine = SamplingEngine(self.HFEngine,
                                              verbosity = self.verbosity)
 
     @property
     def approxParameters(self):
         """Value of approximant parameters."""
         return self._approxParameters
     @approxParameters.setter
     def approxParameters(self, approxParams):
         if not hasattr(self, "approxParameters"):
             self._approxParameters = {}
         approxParameters = purgeDict(approxParams, self.parameterList,
                                   dictname = self.name() + ".approxParameters",
                                   baselevel = 1)
         keyList = list(approxParameters.keys())
         if "POD" in keyList:
             self.POD = approxParameters["POD"]
         elif hasattr(self, "POD"):
             self.POD = self.POD
         else:
             self.POD = True
 
     @property
     def POD(self):
         """Value of POD."""
         return self._POD
     @POD.setter
     def POD(self, POD):
         if hasattr(self, "POD"): PODold = self.POD
         else: PODold = -1
         self._POD = POD
         self._approxParameters["POD"] = self.POD
         if PODold != self.POD:
             self.setupSampling()
             self.resetSamples()
 
     @property
     def homogeneize(self):
         """Value of homogeneize."""
         return self._homogeneize
     @homogeneize.setter
     def homogeneize(self, homogeneize):
         if not hasattr(self, "_homogeneize"):
             self._homogeneize = None
         if homogeneize != self.homogeneize:
             self._homogeneize = homogeneize
             self.resetSamples()
 
     def solveHF(self, mu : complex = None):
         """
         Find high fidelity solution with original parameters and arbitrary
             parameter.
 
         Args:
             mu: Target parameter.
         """
         if mu is None: mu = self.mu0
         if (not hasattr(self, "lastSolvedHF")
          or not np.isclose(self.lastSolvedHF, mu)):
             self.uHF = self.samplingEngine.solveLS(mu,
                                                homogeneized = self.homogeneize)
             self.lastSolvedHF = mu
 
     def resetSamples(self):
         """Reset samples."""
         if hasattr(self, "samplingEngine"):
             self.samplingEngine.resetHistory()
         else:
             self.setupSampling()
 
     def plotSamples(self, name : str = "u", save : str = None,
                     what : strLst = 'all', saveFormat : str = "eps",
                     saveDPI : int = 100, **figspecs):
         """
         Do some nice plots of the samples.
 
         Args:
             name(optional): Name to be shown as title of the plots. Defaults to
                 'u'.
             what(optional): Which plots to do. If list, can contain 'ABS',
                 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'.
                 Defaults to 'ALL'.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         self.samplingEngine.plotSamples(name = name, save = save, what = what,
                                         saveFormat = saveFormat,
                                         saveDPI = saveDPI,
                                         **figspecs)
         
     @abstractmethod
     def setupApprox(self):
         """
         Setup approximant. (ABSTRACT)
 
         Any specialization should include something like
             self.computeDerivatives()
             if not self.checkComputedApprox():
                 ...
                 self.lastApproxParameters = copy(self.approxParameters)
         """
         pass
 
     def checkComputedApprox(self) -> bool:
         """
         Check if setup of new approximant is not needed.
 
         Returns:
             True if new setup is not needed. False otherwise.
         """
         return (hasattr(self, "lastApproxParameters")
             and self.approxParameters == self.lastApproxParameters)
 
     @abstractmethod
     def evalApprox(self, mu:complex):
         """
         Evaluate approximant at arbitrary parameter. (ABSTRACT)
 
         Any specialization should include something like
             self.setupApprox()
             self.uApp = ...
 
         Args:
             mu: Target parameter.
         """
         pass
 
     def getHF(self, mu:complex, homogeneized : bool = False) -> Np1D:
         """
         Get HF solution at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             HFsolution.
         """
         self.solveHF(mu)
         if self.homogeneize and not homogeneized:
             return self.uHF + self.HFEngine.liftDirichletData(mu)
         if not self.homogeneize and homogeneized:
             return self.uHF - self.HFEngine.liftDirichletData(mu)
         return self.uHF
 
     def getRHS(self, mu:complex, homogeneized : bool = False) -> Np1D:
         """
         Get linear system RHS at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Linear system RHS.
         """
         return self.HFEngine.residual(None, mu, homogeneized = homogeneized)
 
     def getApp(self, mu:complex, homogeneized : bool = False) -> Np1D:
         """
         Get approximant at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Approximant.
         """
         self.evalApprox(mu)
         if self.homogeneize and not homogeneized:
             return self.uApp + self.HFEngine.liftDirichletData(mu)
         if not self.homogeneize and homogeneized:
             return self.uApp - self.HFEngine.liftDirichletData(mu)
         return self.uApp
 
     def getRes(self, mu:complex, homogeneized : bool = False) -> Np1D:
         """
         Get residual at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Approximant residual.
         """
         return self.HFEngine.residual(self.getApp(mu, homogeneized), mu,
                                       homogeneized = homogeneized)
 
     def getErr(self, mu:complex, homogeneized : bool = False) -> Np1D:
         """
         Get error at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Approximant error.
         """
         return self.getApp(mu, homogeneized) - self.getHF(mu, homogeneized)
 
     @abstractmethod
     def getPoles(self) -> Np1D:
         """
         Obtain approximant poles.
 
         Returns:
             Numpy complex vector of poles.
         """
         pass
 
     def normHF(self, mu:complex, homogeneized : bool = False) -> float:
         """
         Compute norm of HF solution at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Target norm of HFsolution.
         """
         return self.HFEngine.norm(self.getHF(mu, homogeneized))
 
     def normRHS(self, mu:complex, homogeneized : bool = False) -> Np1D:
         """
         Compute norm of linear system RHS at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Norm of linear system RHS.
         """
         return self.HFEngine.norm(self.getRHS(mu, homogeneized))
 
     def normApp(self, mu:complex, homogeneized : bool = False) -> float:
         """
         Compute norm of (translated) approximant at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Target norm of approximant.
         """
         return self.HFEngine.norm(self.getApp(mu, homogeneized))
 
     def normRes(self, mu:complex, homogeneized : bool = False) -> float:
         """
         Compute norm of approximant residual at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Target norm of (A(mu)app(mu) - f(mu)).
         """
         return self.HFEngine.norm(self.getRes(mu, homogeneized))
 
     def normErr(self, mu:complex, homogeneized : bool = False) -> float:
         """
         Compute norm of approximant error at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
 
         Returns:
             Target norm of (approximant - HFsolution).
         """
         return self.HFEngine.norm(self.getErr(mu, homogeneized))
 
     def plotHF(self, mu:complex, name : str = "uHF", save : str = None,
                what : strLst = 'all', saveFormat : str = "eps",
                saveDPI : int = 100, homogeneized : bool = False, **figspecs):
         """
         Do some nice plots of the HF solution at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             name(optional): Name to be shown as title of the plots. Defaults to
                 'u'.
             what(optional): Which plots to do. If list, can contain 'ABS',
                 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'.
                 Defaults to 'ALL'.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         uHF = self.getHF(mu, homogeneized)
         self.HFEngine.plot(uHF, name = name, save = save, what = what,
                            saveFormat = saveFormat, saveDPI = saveDPI,
                            **figspecs)
 
     def plotApp(self, mu:complex, name : str = "uApp", save : str = None,
                 what : strLst = 'all', saveFormat : str = "eps",
                 saveDPI : int = 100, homogeneized : bool = False, **figspecs):
         """
         Do some nice plots of approximant at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             name(optional): Name to be shown as title of the plots. Defaults to
                 'u'.
             what(optional): Which plots to do. If list, can contain 'ABS',
                 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'.
                 Defaults to 'ALL'.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         uApp = self.getApp(mu, homogeneized)
         self.HFEngine.plot(uApp, name = name, save = save, what = what,
                            saveFormat = saveFormat, saveDPI = saveDPI,
                            **figspecs)
 
     def plotRes(self, mu:complex, name : str = "res", save : str = None,
                 what : strLst = 'all', saveFormat : str = "eps",
                 saveDPI : int = 100, homogeneized : bool = False, **figspecs):
         """
         Do some nice plots of approximation residual at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             name(optional): Name to be shown as title of the plots. Defaults to
                 'u'.
             what(optional): Which plots to do. If list, can contain 'ABS',
                 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'.
                 Defaults to 'ALL'.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         uRes = self.getRes(mu, homogeneized)
         self.HFEngine.plot(uRes, name = name, save = save, what = what,
                            saveFormat = saveFormat, saveDPI = saveDPI,
                            **figspecs)
 
     def plotErr(self, mu:complex, name : str = "err", save : str = None,
                 what : strLst = 'all', saveFormat : str = "eps",
                 saveDPI : int = 100, homogeneized : bool = False, **figspecs):
         """
         Do some nice plots of approximation error at arbitrary parameter.
 
         Args:
             mu: Target parameter.
             name(optional): Name to be shown as title of the plots. Defaults to
                 'u'.
             what(optional): Which plots to do. If list, can contain 'ABS',
                 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'.
                 Defaults to 'ALL'.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             homogeneized(optional): Whether to remove Dirichlet BC. Defaults to
                 False.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         uErr = self.getErr(mu, homogeneized)
         self.HFEngine.plot(uErr, name = name, save = save, what = what,
                            saveFormat = saveFormat, saveDPI = saveDPI,
                            **figspecs)
 
diff --git a/rrompy/reduction_methods/lagrange/approximant_lagrange_pade.py b/rrompy/reduction_methods/lagrange/approximant_lagrange_pade.py
index 438501f..870bf68 100644
--- a/rrompy/reduction_methods/lagrange/approximant_lagrange_pade.py
+++ b/rrompy/reduction_methods/lagrange/approximant_lagrange_pade.py
@@ -1,358 +1,420 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from copy import copy
 import numpy as np
 from .generic_approximant_lagrange import GenericApproximantLagrange
-from rrompy.utilities.base.types import Np1D, DictAny, HFEng
+from rrompy.utilities.base.types import Np1D, DictAny, List, HFEng
 from rrompy.utilities.base import purgeDict, verbosityDepth
 from rrompy.utilities.warning_manager import warn
 
 __all__ = ['ApproximantLagrangePade']
 
 class ApproximantLagrangePade(GenericApproximantLagrange):
     """
     ROM Lagrange Pade' interpolant computation for parametric problems.
 
     Args:
         HFEngine: HF problem solver.
         mu0(optional): Default parameter. Defaults to 0.
         approxParameters(optional): Dictionary containing values for main
             parameters of approximant. Recognized keys are:
             - 'POD': whether to compute POD of snapshots; defaults to True;
             - 'S': total number of samples current approximant relies upon;
                 defaults to 2;
             - 'sampler': sample point generator; defaults to uniform sampler on
                 [0, 1];
+            - 'E': coefficient of interpolant to be minimized; defaults to 
+                min(S, M + 1);
             - 'M': degree of Pade' interpolant numerator; defaults to 0;
             - 'N': degree of Pade' interpolant denominator; defaults to 0.
             Defaults to empty dict.
             
     Attributes:
         HFEngine: HF problem solver.
         mu0: Default parameter.
         mus: Array of snapshot parameters.
         ws: Array of snapshot weigths.
         approxParameters: Dictionary containing values for main parameters of
             approximant. Recognized keys are in parameterList.
         parameterList: Recognized keys of approximant parameters:
             - 'POD': whether to compute POD of snapshots; 
             - 'S': total number of samples current approximant relies upon;
             - 'sampler': sample point generator;
+            - 'E': coefficient of interpolant to be minimized;
             - 'M': degree of Pade' interpolant numerator;
             - 'N': degree of Pade' interpolant denominator;
             - 'robustTol': tolerance for robust Pade' denominator management.
         extraApproxParameters: List of approxParameters keys in addition to
             mother class's.
         S: Number of solution snapshots over which current approximant is
             based upon.
         sampler: Sample point generator.
         M: Numerator degree of approximant.
         N: Denominator degree of approximant.
         POD: Whether to compute POD of snapshots.
         robustTol: Tolerance for robust Pade' denominator management.
         samplingEngine: Sampling engine.
         uHF: High fidelity solution with wavenumber lastSolvedHF as numpy
             complex vector.
         lastSolvedHF: Wavenumber corresponding to last computed high fidelity
             solution.
         Q: Numpy 1D vector containing complex coefficients of approximant
             denominator.
         P: Numpy 2D vector whose columns are FE dofs of coefficients of
             approximant numerator.
         uApp: Last evaluated approximant as numpy complex vector.
         lastApproxParameters: List of parameters corresponding to last
             computed approximant.
     """
     
     def __init__(self, HFEngine:HFEng, mu0 : complex = 0.,
                  approxParameters : DictAny = {}, homogeneize : bool = False,
                  verbosity : int = 10):
         self._preInit()
         if not hasattr(self, "parameterList"):
             self.parameterList = []
-        self.parameterList += ["M", "N", "robustTol"]
+        self.parameterList += ["E", "M", "N", "robustTol"]
         super().__init__(HFEngine = HFEngine, mu0 = mu0,
                          approxParameters = approxParameters,
                          homogeneize = homogeneize,
                          verbosity = verbosity)
         self._postInit()
 
     @property
     def approxParameters(self):
         """
-        Value of approximant parameters. Its assignment may change M, N and S.
+        Value of approximant parameters. Its assignment may change E, M, N and
+            S.
         """
         return self._approxParameters
     @approxParameters.setter
     def approxParameters(self, approxParams):
         approxParameters = purgeDict(approxParams, self.parameterList,
                                   dictname = self.name() + ".approxParameters",
                                   baselevel = 1)
-        approxParametersCopy = purgeDict(approxParameters, ["M", "N"],
+        approxParametersCopy = purgeDict(approxParameters, ["E", "M", "N"],
                                          True, True, baselevel = 1)
+        if hasattr(self, "M"):
+            Mold = self.M
+            self._M = 0
+        if hasattr(self, "N"):
+            Nold = self.N
+            self._N = 0
+        if hasattr(self, "E"):
+            self._E = 0
         GenericApproximantLagrange.approxParameters.fset(self,
                                                          approxParametersCopy)
         keyList = list(approxParameters.keys())
         if "robustTol" in keyList:
             self.robustTol = approxParameters["robustTol"]
         elif hasattr(self, "robustTol"):
             self.robustTol = self.robustTol
         else:
             self.robustTol = 0
         if "M" in keyList:
             self.M = approxParameters["M"]
         elif hasattr(self, "M"):
-            self.M = self.M
+            self.M = Mold
         else:
             self.M = 0
         if "N" in keyList:
             self.N = approxParameters["N"]
         elif hasattr(self, "N"):
-            self.N = self.N
+            self.N = Nold
         else:
             self.N = 0
+        if "E" in keyList:
+            self.E = approxParameters["E"]
+        else:
+            self.E = min(self.S - 1, self.M + 1)
 
     @property
     def M(self):
         """Value of M. Its assignment may change S."""
         return self._M
     @M.setter
     def M(self, M):
         if M < 0: raise ArithmeticError("M must be non-negative.")
         self._M = M
         self._approxParameters["M"] = self.M
         if hasattr(self, "S") and self.S < self.M + 1:
             warn("Prescribed S is too small. Updating S to M + 1.")
             self.S = self.M + 1
 
     @property
     def N(self):
         """Value of N. Its assignment may change S."""
         return self._N
     @N.setter
     def N(self, N):
         if N < 0: raise ArithmeticError("N must be non-negative.")
         self._N = N
         self._approxParameters["N"] = self.N
         if hasattr(self, "S") and self.S < self.N + 1:
             warn("Prescribed S is too small. Updating S to N + 1.")
             self.S = self.N + 1
 
+    @property
+    def E(self):
+        """Value of E. Its assignment may change S."""
+        return self._E
+    @E.setter
+    def E(self, E):
+        if E < 0: raise ArithmeticError("E must be non-negative.")
+        self._E = E
+        self._approxParameters["E"] = self.E
+        if hasattr(self, "S") and self.S < self.E + 1:
+            warn("Prescribed S is too small. Updating S to E + 1.")
+            self.S = self.E + 1
+
     @property
     def robustTol(self):
         """Value of tolerance for robust Pade' denominator management."""
         return self._robustTol
     @robustTol.setter
     def robustTol(self, robustTol):
         if robustTol < 0.:
             warn("Overriding prescribed negative robustness tolerance to 0.")
             robustTol = 0.
         self._robustTol = robustTol
         self._approxParameters["robustTol"] = self.robustTol
 
     @property
     def S(self):
         """Value of S."""
         return self._S
     @S.setter
     def S(self, S):
         if S <= 0: raise ArithmeticError("S must be positive.")
         if hasattr(self, "S"): Sold = self.S
         else: Sold = -1
-        vals, label = [0] * 2, {0:"M", 1:"N"}
+        vals, label = [0] * 3, {0:"M", 1:"N", 2:"E"}
         if hasattr(self, "M"): vals[0] = self.M
         if hasattr(self, "N"): vals[1] = self.N
+        if hasattr(self, "E"): vals[2] = self.E
         idxmax = np.argmax(vals)
         if vals[idxmax] + 1 > S:
             warn("Prescribed S is too small. Updating S to {} + 1."\
                                                         .format(label[idxmax]))
             self.S = vals[idxmax] + 1
         else:
             self._S = S
             self._approxParameters["S"] = self.S
             if Sold != self.S:
                 self.resetSamples()
 
+    def getTargetFunctionalOptimum(self):
+        """Compute optimum of target LS functional."""
+        self.setupApprox()
+        if self._funcOptimumWarn:
+            warn("Target functional optimum numerically zero.")
+        return np.abs(self._funcOptimum)
+
     def setupApprox(self):
         """
         Compute Pade' interpolant.
         SVD-based robust eigenvalue management.
         """
         if not self.checkComputedApprox():
             if self.verbosity >= 5:
                 verbosityDepth("INIT", "Setting up {}.". format(self.name()))
             self.computeSnapshots()
-            if self.verbosity >= 7:
-                verbosityDepth("INIT", "Starting computation of denominator.")
-            while True:
-                self.computeSnapshots()
-                TN = np.vander(self.radiusPade(self.mus), N = self.N + 1,
-                               increasing = True)
+            if self.N > 0:
+                if self.verbosity >= 7:
+                    verbosityDepth("INIT", ("Starting computation of "
+                                            "denominator."))
+                while self.N > 0:
+                    TN = np.vander(self.radiusPade(self.mus), N = self.N + 1)
+                    if self.POD:
+                        data = self.samplingEngine.RPOD.T
+                    else:
+                        data = self.samplingEngine.samples.T
+                    RHSFull = np.empty((self.S, data.shape[1] * (self.N + 1)),
+                                       dtype = np.complex)
+                    for j in range(self.S):
+                        RHSFull[j, :] = np.kron(TN[j, :], data[j, :])
+                    G = np.polyfit(self.radiusPade(self.mus), RHSFull,
+                                   deg = self.E, w = self.ws)
+                    G = G[0, :].reshape((self.N + 1, data.shape[1])).T
+                    if self.POD:
+                        if self.verbosity >= 7:
+                            verbosityDepth("INIT", ("Solving svd for square "
+                                                    "root of gramian matrix."),
+                                           end = "")
+                        _, ev, eV = np.linalg.svd(G, full_matrices = False)
+                        ev = ev[::-1]
+                        eV = eV[::-1, :].conj().T
+                        self._funcOptimum = ev[0]
+                    else:
+                        if self.verbosity >= 10:
+                            verbosityDepth("INIT", "Building gramian matrix.",
+                                           end = "")
+                        G2 = self.HFEngine.innerProduct(G, G)
+                        if self.verbosity >= 10:
+                            verbosityDepth("DEL", "Done building gramian.",
+                                           inline = True)
+                        if self.verbosity >= 7:
+                            verbosityDepth("INIT", ("Solving eigenvalue "
+                                                    "problem for gramian "
+                                                    "matrix."), end = "")
+                        ev, eV = np.linalg.eigh(G2)
+                        self._funcOptimum = ev[0] ** .5
+                    if ev[0] <= 0 or np.isclose(np.abs(ev[0] / ev[-1]), 0.):
+                        self._funcOptimumWarn = True
+                    else:
+                        self._funcOptimumWarn = False
+                    if self.verbosity >= 7:
+                        verbosityDepth("DEL", " Done.", inline = True)
+                    ts = self.robustTol * np.linalg.norm(ev)
+                    Nnew = np.sum(np.abs(ev) >= ts)
+                    diff = self.N - Nnew
+                    if diff <= 0: break
+                    Enew = self.E - diff
+                    Mnew = min(self.M, Enew - 1)
+                    if Mnew == self.M:
+                        strM = ""
+                    else:
+                        strM = ", M from {0} to {1},".format(self.M, Mnew)
+                    print(("Smallest {0} eigenvalues below tolerance.\n"
+                           "Reducing N from {1} to {2}{5} and E from {3} to "
+                           "{4}.").format(diff + 1, self.N, Nnew,
+                                          self.E, Enew, strM))
+                    newParameters = {"N" : Nnew, "M" : Mnew, "E" : Enew}
+                    self.approxParameters = newParameters
+                    if self.N <= 0:
+                        eV = np.ones((1, 1))
+                self.Q = np.poly1d(eV[:, 0])
+                if self.verbosity >= 7:
+                    verbosityDepth("DEL", "Done computing denominator.")
+            else:
                 if self.POD:
                     data = self.samplingEngine.RPOD.T
                 else:
                     data = self.samplingEngine.samples.T
-                RHSFull = np.empty((self.S, data.shape[1] * (self.N + 1)),
-                                   dtype = np.complex)
-                for j in range(self.S):
-                    RHSFull[j, :] = np.kron(TN[j, :], data[j, :])
-                G = np.polyfit(self.radiusPade(self.mus), RHSFull,
-                               deg = self.N, w = self.ws)[0, :].reshape(
-                                                 (self.N + 1, data.shape[1])).T
+                g = np.polyfit(self.radiusPade(self.mus), data,
+                               deg = self.E, w = self.ws)[0, :].flatten()
                 if self.POD:
-                    if self.verbosity >= 7:
-                        verbosityDepth("INIT", ("Solving svd for square root "
-                                                "of gramian matrix."),
-                                       end = "")
-                    _, ev, V = np.linalg.svd(G, full_matrices = False)
-                    ev = ev[::-1]
-                    eV = V[::-1, :].conj().T
-                else:
-                    if self.verbosity >= 10:
-                        verbosityDepth("INIT", "Building gramian matrix.",
-                                       end = "")
-                    G2 = self.HFEngine.innerProduct(G, G)
-                    if self.verbosity >= 10:
-                        verbosityDepth("DEL", "Done building gramian.",
-                                       inline = True)
-                    if self.verbosity >= 7:
-                        verbosityDepth("INIT", ("Solving eigenvalue problem "
-                                                "for gramian matrix."),
-                                       end = "")
-                    ev, eV = np.linalg.eigh(G2)
-                if self.verbosity >= 7:
-                    verbosityDepth("DEL", " Done.", inline = True)
-                ts = self.robustTol * np.linalg.norm(ev)
-                Nnew = np.sum(np.abs(ev) >= ts)
-                diff = self.N - Nnew
-                if diff <= 0: break
-                Snew = self.S - diff
-                Mnew = min(self.M, Snew - 1)
-                if Mnew == self.M:
-                    strM = ""
+                    self._funcOptimum = np.linalg.norm(g)
                 else:
-                    strM = ", M from {0} to {1},".format(self.M, Mnew)
-                print(("Smallest {0} eigenvalues below tolerance.\n"
-                       "Reducing N from {1} to {2}{5} and S from {3} to {4}.")\
-                           .format(diff + 1, self.N, Nnew, self.S, Snew, strM))
-                newParameters = {"N" : Nnew, "M" : Mnew, "S" : Snew}
-                self.approxParameters = newParameters
-            self.Q = eV[:, 0]
+                    self._funcOptimum = self.HFEngine.norm(g)
+                self._funcOptimumWarn = False
+                self.Q = np.poly1d([1])
             if self.verbosity >= 7:
-                verbosityDepth("DEL", "Done computing denominator.")
                 verbosityDepth("INIT", "Starting computation of numerator.")
-            TNQ = TN.dot(self.Q).reshape((self.S, 1))
-            if self.POD:
-                data = self.samplingEngine.samples.dot(
-                                                      self.samplingEngine.RPOD)
-            else:
-                data = self.samplingEngine.samples
-            RHS = np.multiply(data.T, TNQ)
+            Qeval = self.Q(self.radiusPade(self.mus)).reshape((self.S, 1))
+            RHS = np.multiply(data, Qeval)
             self.P = np.polyfit(self.radiusPade(self.mus), RHS, deg = self.M,
-                                w = self.ws)[::-1, :].T
+                                w = self.ws).T
+            if self.POD:
+                self.P = self.samplingEngine.samples.dot(self.P)
             if self.verbosity >= 7:
                 verbosityDepth("DEL", "Done computing numerator.")
             self.lastApproxParameters = copy(self.approxParameters)
             if hasattr(self, "lastSolvedApp"): del self.lastSolvedApp
             if self.verbosity >= 5:
                 verbosityDepth("DEL", "Done setting up approximant.\n")
 
     def radiusPade(self, mu:Np1D, mu0 : float = None) -> float:
         """
         Compute translated radius to be plugged into Pade' approximant.
 
         Args:
             mu: Parameter(s) 1.
             mu0: Parameter(s) 2. If None, set to self.mu0.
 
         Returns:
             Translated radius to be plugged into Pade' approximant.
         """
         if mu0 is None: mu0 = self.mu0
         return self.HFEngine.rescaling(mu) - self.HFEngine.rescaling(mu0)
 
-    def getPVal(self, mu:complex):
+    def getPVal(self, mu:List[complex]):
         """
         Evaluate Pade' numerator at arbitrary parameter.
 
         Args:
             mu: Target parameter.
         """
         self.setupApprox()
         if self.verbosity >= 10:
             verbosityDepth("INIT",
                            "Evaluating numerator at mu = {}.".format(mu),
                            end = "")
-        powerlist = np.power(self.radiusPade(mu), np.arange(self.M + 1))
+        try:
+            len(mu)
+        except:
+            mu = [mu]
+        powerlist = np.vander(self.radiusPade(mu), self.M + 1).T
         p = self.P.dot(powerlist)
+        if len(mu) == 1:
+            p = p.flatten()
         if self.verbosity >= 10:
             verbosityDepth("DEL", " Done.", inline = True)
         return p
 
-    def getQVal(self, mu:complex):
+    def getQVal(self, mu:List[complex]):
         """
         Evaluate Pade' denominator at arbitrary parameter.
 
         Args:
             mu: Target parameter.
         """
         self.setupApprox()
         if self.verbosity >= 10:
             verbosityDepth("INIT",
                            "Evaluating denominator at mu = {}.".format(mu),
                            end = "")
-        powerlist = np.power(self.radiusPade(mu), np.arange(self.N + 1))
-        q = self.Q.dot(powerlist)
+        q = self.Q(self.radiusPade(mu))
         if self.verbosity >= 10:
             verbosityDepth("DEL", " Done.", inline = True)
         return q
 
     def evalApprox(self, mu:complex):
         """
         Evaluate Pade' approximant at arbitrary parameter.
 
         Args:
             mu: Target parameter.
         """
         self.setupApprox()
         if (not hasattr(self, "lastSolvedApp")
          or not np.isclose(self.lastSolvedApp, mu)):
             if self.verbosity >= 5:
                 verbosityDepth("INIT",
                                "Evaluating approximant at mu = {}.".format(mu))
             try:
                 self.uApp = self.getPVal(mu) / self.getQVal(mu)
             except:
                 self.uApp = np.empty(self.P.shape[0])
                 self.uApp[:] = np.nan
             self.lastSolvedApp = mu
             if self.verbosity >= 5:
                 verbosityDepth("DEL", "Done evaluating approximant.")
 
     def getPoles(self) -> Np1D:
         """
         Obtain approximant poles.
         
         Returns:
             Numpy complex vector of poles.
         """
         self.setupApprox()
-        return self.HFEngine.rescalingInv(np.roots(self.Q[::-1])
+        return self.HFEngine.rescalingInv(self.Q.r
                                         + self.HFEngine.rescaling(self.mu0))
         
diff --git a/rrompy/reduction_methods/taylor/approximant_taylor_pade.py b/rrompy/reduction_methods/taylor/approximant_taylor_pade.py
index 9095d2a..b454091 100644
--- a/rrompy/reduction_methods/taylor/approximant_taylor_pade.py
+++ b/rrompy/reduction_methods/taylor/approximant_taylor_pade.py
@@ -1,563 +1,580 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from copy import copy
 import numpy as np
 from .generic_approximant_taylor import GenericApproximantTaylor
 from rrompy.sampling.base.pod_engine import PODEngine
-from rrompy.utilities.base.types import Np1D, Np2D, Tuple, DictAny
+from rrompy.utilities.base.types import Np1D, Np2D, List, Tuple, DictAny
 from rrompy.utilities.base.types import HFEng
 from rrompy.utilities.base import purgeDict, verbosityDepth
 from rrompy.utilities.warning_manager import warn
 
 __all__ = ['ApproximantTaylorPade']
 
 class ApproximantTaylorPade(GenericApproximantTaylor):
     """
     ROM single-point fast Pade' approximant computation for parametric
         problems.
 
     Args:
         HFEngine: HF problem solver.
         mu0(optional): Default parameter. Defaults to 0.
         approxParameters(optional): Dictionary containing values for main
             parameters of approximant. Recognized keys are:
             - 'POD': whether to compute POD of snapshots; defaults to True;
             - 'rho': weight for computation of original Pade' approximant;
                 defaults to np.inf, i.e. fast approximant;
             - 'M': degree of Pade' approximant numerator; defaults to 0;
             - 'N': degree of Pade' approximant denominator; defaults to 0;
             - 'E': total number of derivatives current approximant relies upon;
                 defaults to Emax;
             - 'Emax': total number of derivatives of solution map to be
                 computed; defaults to E;
             - 'robustTol': tolerance for robust Pade' denominator management;
                 defaults to 0;
             - 'rescaleParam': whether to rescale parameter during denominator
                 computation; defaults to True;
             - 'sampleType': label of sampling type; available values are:
                 - 'ARNOLDI': orthogonalization of solution derivatives through
                     Arnoldi algorithm;
                 - 'KRYLOV': standard computation of solution derivatives.
                 Defaults to 'KRYLOV'.
             Defaults to empty dict.
 
     Attributes:
         HFEngine: HF problem solver.
         mu0: Default parameter.
         approxParameters: Dictionary containing values for main parameters of
             approximant. Recognized keys are in parameterList.
         parameterList: Recognized keys of approximant parameters:
             - 'POD': whether to compute POD of snapshots; 
             - 'rho': weight for computation of original Pade' approximant;
             - 'M': degree of Pade' approximant numerator;
             - 'N': degree of Pade' approximant denominator;
             - 'E': total number of derivatives current approximant relies upon;
             - 'Emax': total number of derivatives of solution map to be
                 computed;
             - 'robustTol': tolerance for robust Pade' denominator management;
             - 'rescaleParam': whether to rescale parameter during denominator
                 computation;
             - 'sampleType': label of sampling type.
         POD: Whether to compute QR factorization of derivatives.
         rho: Weight of approximant.
         M: Numerator degree of approximant.
         N: Denominator degree of approximant.
         E: Number of solution derivatives over which current approximant is
             based upon.
         Emax: Total number of solution derivatives to be computed.
         robustTol: Tolerance for robust Pade' denominator management.
         sampleType: Label of sampling type.
         initialHFData: HF problem initial data.
         uHF: High fidelity solution with wavenumber lastSolvedHF as numpy
             complex vector.
         lastSolvedHF: Wavenumber corresponding to last computed high fidelity
             solution.
         G: Square Numpy 2D vector of size (N+1) corresponding to Pade'
             denominator matrix (see paper).
         Q: Numpy 1D vector containing complex coefficients of approximant
             denominator.
         P: Numpy 2D vector whose columns are FE dofs of coefficients of
             approximant numerator.
         uApp: Last evaluated approximant as numpy complex vector.
         lastApproxParameters: List of parameters corresponding to last
             computed approximant.
     """
     
     def __init__(self, HFEngine:HFEng, mu0 : complex = 0,
                  approxParameters : DictAny = {}, homogeneize : bool = False,
                  verbosity : int = 10):
         self._preInit()
         if not hasattr(self, "parameterList"):
             self.parameterList = []
         self.parameterList += ["M", "N", "robustTol", "rho", "rescaleParam"]
         super().__init__(HFEngine = HFEngine, mu0 = mu0,
                          approxParameters = approxParameters,
                          homogeneize = homogeneize,
                          verbosity = verbosity)
         self._postInit()
 
     @property
     def approxParameters(self):
         """Value of approximant parameters."""
         return self._approxParameters
     @approxParameters.setter
     def approxParameters(self, approxParams):
         approxParameters = purgeDict(approxParams, self.parameterList,
                                   dictname = self.name() + ".approxParameters",
                                   baselevel = 1)
         approxParametersCopy = purgeDict(approxParameters, 
                                          ["M", "N", "robustTol", "rho"],
                                          True, True, baselevel = 1)
         keyList = list(approxParameters.keys())
         if "rho" in keyList:
             self._rho = approxParameters["rho"]
         elif hasattr(self, "rho"):
             self._rho = self.rho
         else:
             self._rho = np.inf
         GenericApproximantTaylor.approxParameters.fset(self,
                                                        approxParametersCopy)
         self.rho = self._rho
         if "robustTol" in keyList:
             self.robustTol = approxParameters["robustTol"]
         elif hasattr(self, "robustTol"):
             self.robustTol = self.robustTol
         else:
             self.robustTol = 0
         if "rescaleParam" in keyList:
             self.rescaleParam = approxParameters["rescaleParam"]
         elif hasattr(self, "rescaleParam"):
             self.rescaleParam = self.rescaleParam
         else:
             self.rescaleParam = True
         self._ignoreParWarnings = True
         if "M" in keyList:
             self.M = approxParameters["M"]
         elif hasattr(self, "M"):
             self.M = self.M
         else:
             self.M = 0
         del self._ignoreParWarnings
         if "N" in keyList:
             self.N = approxParameters["N"]
         elif hasattr(self, "N"):
             self.N = self.N
         else:
             self.N = 0
 
     @property
     def rho(self):
         """Value of rho."""
         return self._rho
     @rho.setter
     def rho(self, rho):
         self._rho = np.abs(rho)
         self._approxParameters["rho"] = self.rho
 
     @property
     def M(self):
         """Value of M. Its assignment may change Emax and E."""
         return self._M
     @M.setter
     def M(self, M):
         if M < 0: raise ArithmeticError("M must be non-negative.")
         self._M = M
         self._approxParameters["M"] = self.M
         if not hasattr(self, "_ignoreParWarnings"):
             self.checkMNEEmax()
 
     @property
     def N(self):
         """Value of N. Its assignment may change Emax."""
         return self._N
     @N.setter
     def N(self, N):
         if N < 0: raise ArithmeticError("N must be non-negative.")
         self._N = N
         self._approxParameters["N"] = self.N
         self.checkMNEEmax()
 
     def checkMNEEmax(self):
         """Check consistency of M, N, E, and Emax."""
         M = self.M if hasattr(self, "_M") else 0
         N = self.N if hasattr(self, "_N") else 0
         E = self.E if hasattr(self, "_E") else M + N
         Emax = self.Emax if hasattr(self, "_Emax") else M + N
         if self.rho == np.inf:
             if Emax < max(N, M):
                 warn(("Prescribed Emax is too small. Updating Emax to "
                       "max(M, N)."))
                 self.Emax = max(N, M)
             if E < max(N, M):
                 warn("Prescribed E is too small. Updating E to max(M, N).")
                 self.E = max(N, M)
         else:
             if Emax < N + M:
                 warn("Prescribed Emax is too small. Updating Emax to M + N.")
                 self.Emax = self.N + M
             if E < N + M:
                 warn("Prescribed E is too small. Updating E to M + N.")
                 self.E = self.N + M
 
     @property
     def robustTol(self):
         """Value of tolerance for robust Pade' denominator management."""
         return self._robustTol
     @robustTol.setter
     def robustTol(self, robustTol):
         if robustTol < 0.:
             warn("Overriding prescribed negative robustness tolerance to 0.")
             robustTol = 0.
         self._robustTol = robustTol
         self._approxParameters["robustTol"] = self.robustTol
 
     @property
     def rescaleParam(self):
         """Value of parameter rescaling during denominator computation."""
         return self._rescaleParam
     @rescaleParam.setter
     def rescaleParam(self, rescaleParam):
         if not isinstance(rescaleParam, (bool,)):
             warn("Prescribed rescaleParam not recognized. Overriding to True.")
             rescaleParam = True
         self._rescaleParam = rescaleParam
         self._approxParameters["rescaleParam"] = self.rescaleParam
 
     @property
     def E(self):
         """Value of E. Its assignment may change Emax."""
         return self._E
     @E.setter
     def E(self, E):
         if E < 0: raise ArithmeticError("E must be non-negative.")
         self._E = E
         self.checkMNEEmax()
         self._approxParameters["E"] = self.E
         if hasattr(self, "Emax") and self.Emax < self.E:
             warn("Prescribed Emax is too small. Updating Emax to E.")
             self.Emax = self.E
 
     @property
     def Emax(self):
         """Value of Emax. Its assignment may reset computed derivatives."""
         return self._Emax
     @Emax.setter
     def Emax(self, Emax):
         if Emax < 0: raise ArithmeticError("Emax must be non-negative.")
         if hasattr(self, "Emax"): EmaxOld = self.Emax
         else: EmaxOld = -1
         if hasattr(self, "_N"): N = self.N
         else: N = 0
         if hasattr(self, "_M"): M = self.M
         else: M = 0
         if hasattr(self, "_E"): E = self.E
         else: E = 0
         if self.rho == np.inf:
             if max(N, M, E) > Emax:
                 warn(("Prescribed Emax is too small. Updating Emax to "
                       "max(N, M, E)."))
                 Emax = max(N, M, E)
         else:
             if max(N + M, E) > Emax:
                 warn(("Prescribed Emax is too small. Updating Emax to "
                       "max(N + M, E)."))
                 Emax = max(N + M, E)
         self._Emax = Emax
         self._approxParameters["Emax"] = Emax
         if EmaxOld >= self.Emax and self.samplingEngine.samples is not None:
             self.samplingEngine.samples = self.samplingEngine.samples[:,
                                                           : self.Emax + 1]
             if (self.sampleType == "ARNOLDI"
                 and self.samplingEngine.HArnoldi is not None):
                 self.samplingEngine.HArnoldi = self.samplingEngine.HArnoldi[
                                                            : self.Emax + 1,
                                                            : self.Emax + 1]
                 self.samplingEngine.RArnoldi = self.samplingEngine.RArnoldi[
                                                            : self.Emax + 1,
                                                            : self.Emax + 1]
 
+    def getTargetFunctionalOptimum(self):
+        """Compute optimum of target LS functional."""
+        self.setupApprox()
+        if self._funcOptimumWarn:
+            warn("Target functional optimum numerically zero.")
+        return np.abs(self._funcOptimum)
+
     def setupApprox(self):
         """
         Compute Pade' approximant. SVD-based robust eigenvalue management.
         """
         if not self.checkComputedApprox():
             if self.verbosity >= 5:
                 verbosityDepth("INIT", "Setting up {}.". format(self.name()))
             self.computeDerivatives()
             if self.N > 0:
                 if self.verbosity >= 7:
                     verbosityDepth("INIT", ("Starting computation of "
                                             "denominator."))
-                while True:
-                    if self.N == 0:
-                        if self.verbosity >= 7:
-                            verbosityDepth("DEL",
-                                           "Done computing denominator.")
-                        self.Q = np.ones(1)
-                        break
+                while self.N > 0:
                     if self.POD:
                         ev, eV = self.findeveVGQR()
+                        self._funcOptimum = ev[0]
                     else:
                         ev, eV = self.findeveVGExplicit()
+                        self._funcOptimum = ev[0] ** .5
+                    if ev[0] <= 0 or np.isclose(np.abs(ev[0] / ev[-1]), 0.):
+                        self._funcOptimumWarn = True
+                    else:
+                        self._funcOptimumWarn = False
                     ts = self.robustTol * np.linalg.norm(ev)
                     Nnew = np.sum(np.abs(ev) >= ts)
                     diff = self.N - Nnew
                     if diff <= 0: break
                     Enew = self.E - diff
                     Mnew = min(self.M, Enew)
                     if Mnew == self.M:
                         strM = ""
                     else:
                         strM = ", M from {0} to {1},".format(self.M, Mnew)
                     print(("Smallest {0} eigenvalues below tolerance.\n"
                            "Reducing N from {1} to {2}{5} and E from {3} to "
                            "{4}.").format(diff + 1, self.N, Nnew, self.E,
                                           Enew, strM))
                     newParameters = {"N" : Nnew, "M" : Mnew, "E" : Enew}
                     self.approxParameters = newParameters
-                self.Q = eV[::-1, 0]
+                    if self.N <= 0:
+                        eV = np.ones((1, 1))
+                self.Q = np.poly1d(eV[:, 0])
                 if self.verbosity >= 7:
                     verbosityDepth("DEL", "Done computing denominator.")
             else:
-                self.Q = np.ones(1)
+                self.Q = np.poly1d([1])
+                if self.sampleType == "KRYLOV":
+                    self._funcOptimum = self.HFEngine.norm(
+                                        self.samplingEngine.samples[:, self.E])
+                else:
+                    self._funcOptimum = np.linalg.norm(
+                                       self.samplingEngine.RArnoldi[:, self.E])
+                self._funcOptimumWarn = False
             if self.verbosity >= 7:
                 verbosityDepth("INIT", "Starting computation of numerator.")
-            QToeplitz = np.zeros((self.Emax + 1, self.M + 1),
+            QHankel = np.zeros((self.Emax + 1, self.M + 1),
                                  dtype = np.complex)
-            for i in range(len(self.Q)):
+            for i in range(self.Q.order):
                 rng = np.arange(self.M + 1 - i)
-                QToeplitz[rng, rng + i] = self.Q[i]
+                QHankel[rng, - 1 - rng - i] = self.Q[i]
             if self.sampleType == "ARNOLDI":
-                QToeplitz = self.samplingEngine.RArnoldi.dot(QToeplitz)
-            self.P = self.samplingEngine.samples.dot(QToeplitz)
+                QHankel = self.samplingEngine.RArnoldi.dot(QHankel)
+            self.P = self.samplingEngine.samples.dot(QHankel)
             if self.verbosity >= 7:
                 verbosityDepth("DEL", "Done computing numerator.")
             self.lastApproxParameters = copy(self.approxParameters)
             if hasattr(self, "lastSolvedApp"): del self.lastSolvedApp
             if self.verbosity >= 5:
                 verbosityDepth("DEL", "Done setting up approximant.\n")
 
     def rescaleParameter(self, R:Np2D, A : Np2D = None,
                          exponent : float = 1.) -> Np2D:
         """
         Prepare parameter rescaling.
         
         Args:
             R: Matrix whose columns need rescaling.
             A(optional): Matrix whose diagonal defines scaling factor. If None,
                 previous value of scaleFactor is used. Defaults to None.
             exponent(optional): Exponent of scaling factor in matrix diagonal.
                 Defaults to 1.
 
         Returns:
             Rescaled matrix.
         """
         if A is not None:
             aDiag = np.diag(A)
             scaleCoeffs = np.polyfit(np.arange(A.shape[1]),
                                      np.log(aDiag), 1)
             self.scaleFactor = np.exp(- scaleCoeffs[0] / exponent)
         return np.multiply(R, np.power(self.scaleFactor,np.arange(R.shape[1])))
 
     def buildG(self):
         """Assemble Pade' denominator matrix."""
         self.computeDerivatives()
         if self.rho == np.inf:
             Nmin = self.E - self.N
         else:
             Nmin = self.M - self.N + 1
         if self.sampleType == "KRYLOV":
             DerE = self.samplingEngine.samples[:, Nmin : self.E + 1]
             G = self.HFEngine.innerProduct(DerE, DerE)
             if self.rescaleParam:
                 DerE = self.rescaleParameter(DerE, G, 2.)
                 G = self.HFEngine.innerProduct(DerE, DerE)
         else:
             RArnE = self.samplingEngine.RArnoldi[: self.E + 1,
                                                  Nmin : self.E + 1]
             if self.rescaleParam:
                 RArnE = self.rescaleParameter(RArnE, RArnE[Nmin :, :])
             G = RArnE.conj().T.dot(RArnE)
         if self.rho == np.inf:
             self.G = G
         else:
             Gbig = G
             self.G = np.zeros((self.N + 1, self.N + 1), dtype = np.complex)
             for k in range(self.E - self.M):
                 self.G += self.rho ** (2 * k) * Gbig[k : k + self.N + 1,
                                                      k : k + self.N + 1]
             
     def findeveVGExplicit(self) -> Tuple[Np1D, Np2D]:
         """
         Compute explicitly eigenvalues and eigenvectors of Pade' denominator
             matrix.
         """
         if self.verbosity >= 10:
             verbosityDepth("INIT", "Building gramian matrix.")
         self.buildG()
         if self.verbosity >= 10:
             verbosityDepth("DEL", "Done building gramian.")
         if self.verbosity >= 7:
             verbosityDepth("INIT", 
                            "Solving eigenvalue problem for gramian matrix.")
         ev, eV = np.linalg.eigh(self.G)
         if self.rescaleParam:
             eV = self.rescaleParameter(eV.T).T
         if self.verbosity >= 7:
             verbosityDepth("DEL", "Done solving eigenvalue problem.")
         return ev, eV
 
     def findeveVGQR(self) -> Tuple[Np1D, Np2D]:
         """
         Compute eigenvalues and eigenvectors of Pade' denominator matrix
             through SVD of R factor. See ``Householder triangularization of a
             quasimatrix'', L.Trefethen, 2008 for QR algorithm.
 
         Returns:
             Eigenvalues in ascending order and corresponding eigenvector
                 matrix.
         """
         self.computeDerivatives()
         if self.rho == np.inf:
             Nmin = self.E - self.N
         else:
             Nmin = self.M - self.N + 1
         if self.sampleType == "KRYLOV":
             A = copy(self.samplingEngine.samples[:, Nmin : self.E + 1])
             self.PODEngine = PODEngine(self.HFEngine)
             if self.verbosity >= 10:
                 verbosityDepth("INIT", "Orthogonalizing samples.", end = "")
             R = self.PODEngine.QRHouseholder(A, only_R = True)
             if self.verbosity >= 10:
                 verbosityDepth("DEL", " Done.", inline = True)
         else:
             R = self.samplingEngine.RArnoldi[: self.E + 1, Nmin : self.E + 1]
         if self.rescaleParam:
             R = self.rescaleParameter(R, R[Nmin :, :])
         if self.rho == np.inf:
             if self.verbosity >= 10:
                 verbosityDepth("INIT", ("Solving svd for square root of "
                                         "gramian matrix."), end = "")
             _, s, V = np.linalg.svd(R, full_matrices = False)
         else:
             if self.verbosity >= 10:
                 verbosityDepth("INIT", ("Building matrix stack for square "
                                         "root of gramian."), end = "")
             Rtower = np.zeros((R.shape[0] * (self.E - self.M), self.N + 1),
                               dtype = np.complex)
             for k in range(self.E - self.M):
                 Rtower[k * R.shape[0] : (k + 1) * R.shape[0], :] = (
                                 self.rho ** k
                               * R[:, self.M - self.N + 1 + k : self.M + 1 + k])
             if self.verbosity >= 10:
                 verbosityDepth("DEL", " Done.", inline = True)
             if self.verbosity >= 7:
                 verbosityDepth("INIT", ("Solving svd for square root of "
                                         "gramian matrix."), end = "")
             _, s, V = np.linalg.svd(Rtower, full_matrices = False)
         eV = V.conj().T[:, ::-1]
         if self.rescaleParam:
             eV = self.rescaleParameter(eV.T).T
         if self.verbosity >= 7:
             verbosityDepth("DEL", " Done.", inline = True)
         return s[::-1], eV
             
     def radiusPade(self, mu:Np1D, mu0 : float = None) -> float:
         """
         Compute translated radius to be plugged into Pade' approximant.
 
         Args:
             mu: Parameter(s) 1.
             mu0: Parameter(s) 2. If None, set to self.mu0.
 
         Returns:
             Translated radius to be plugged into Pade' approximant.
         """
         if mu0 is None: mu0 = self.mu0
         return self.HFEngine.rescaling(mu) - self.HFEngine.rescaling(mu0)
 
-    def getPVal(self, mu:complex):
+    def getPVal(self, mu:List[complex]):
         """
         Evaluate Pade' numerator at arbitrary parameter.
 
         Args:
             mu: Target parameter.
         """
         self.setupApprox()
         if self.verbosity >= 10:
             verbosityDepth("INIT",
                            "Evaluating numerator at mu = {}.".format(mu),
                            end = "")
-        powerlist = np.power(self.radiusPade(mu), np.arange(self.M + 1))
+        try:
+            len(mu)
+        except:
+            mu = [mu]
+        powerlist = np.vander(self.radiusPade(mu), self.M + 1).T
         p = self.P.dot(powerlist)
+        if len(mu) == 1:
+            p = p.flatten()
         if self.verbosity >= 10:
             verbosityDepth("DEL", " Done.", inline = True)
         return p
 
-    def getQVal(self, mu:complex):
+    def getQVal(self, mu:List[complex]):
         """
         Evaluate Pade' denominator at arbitrary parameter.
 
         Args:
             mu: Target parameter.
         """
         self.setupApprox()
         if self.verbosity >= 10:
             verbosityDepth("INIT",
                            "Evaluating denominator at mu = {}.".format(mu),
                            end = "")
-        powerlist = np.power(self.radiusPade(mu), np.arange(self.N + 1))
-        q = self.Q.dot(powerlist)
+        q = self.Q(self.radiusPade(mu))
         if self.verbosity >= 10:
             verbosityDepth("DEL", " Done.", inline = True)
         return q
 
     def evalApprox(self, mu:complex):
         """
         Evaluate Pade' approximant at arbitrary parameter.
 
         Args:
             mu: Target parameter.
         """
         self.setupApprox()
         if (not hasattr(self, "lastSolvedApp")
          or not np.isclose(self.lastSolvedApp, mu)):
             if self.verbosity >= 5:
                 verbosityDepth("INIT",
                                "Evaluating approximant at mu = {}.".format(mu))
-            try:
-                self.uApp = self.getPVal(mu) / self.getQVal(mu)
-            except:
-                self.uApp = np.empty(self.P.shape[0])
-                self.uApp[:] = np.nan
+            self.uApp = self.getPVal(mu) / self.getQVal(mu)
             self.lastSolvedApp = mu
             if self.verbosity >= 5:
                 verbosityDepth("DEL", "Done evaluating approximant.")
 
     def getPoles(self) -> Np1D:
         """
         Obtain approximant poles.
 
         Returns:
             Numpy complex vector of poles.
         """
         self.setupApprox()
-        return self.HFEngine.rescalingInv(np.roots(self.Q[::-1])
+        return self.HFEngine.rescalingInv(self.Q.r
                                         + self.HFEngine.rescaling(self.mu0))
         
diff --git a/rrompy/sampling/base/pod_engine.py b/rrompy/sampling/base/pod_engine.py
index a376ea7..da00bf1 100644
--- a/rrompy/sampling/base/pod_engine.py
+++ b/rrompy/sampling/base/pod_engine.py
@@ -1,147 +1,150 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 import numpy as np
 from copy import copy
 from rrompy.utilities.base.types import Np1D, Np2D, Tuple, HFEng
 
 __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 norm(self, a:Np1D) -> float:
         """Compute norm of a Hilbert space object."""
         pass
 
     def GS(self, a:Np1D, Q:Np2D, n : int = None,
            aA:Np1D = None, QA:Np2D = None) -> Tuple[Np1D, Np1D, Np1D]:
         """
         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;
             aA: augmented components of vector to be projected;
             QA: augmented components of projection matrix.
 
         Returns:
             Resulting normalized vector, coefficients of a wrt the updated 
                 basis.
         """
         if n is None:
             n = Q.shape[1]
         if aA is None != QA is None:
             raise Exception(("Either both or none of augmented components "
                              "must be provided."))
         r = np.zeros((n + 1,), dtype = a.dtype)
         if n > 0:
             Q = Q[:, : n]
             for j in range(2): # twice is enough!
                 nu = self.HFEngine.innerProduct(a, Q)
                 a = a - Q.dot(nu)
                 if aA is not None:
                     aA = aA - QA.dot(nu)
                 r[:-1] = r[:-1] + nu
         r[-1] = self.HFEngine.norm(a)
         if np.isclose(np.abs(r[-1]), 0.):
             r[-1] = 1.
         a = a / r[-1]
         if aA is not None:
             aA = aA / r[-1]
         return a, r, aA
 
     def QRGramSchmidt(self, A:Np2D,
                       only_R : bool = False) -> Tuple[Np1D, Np1D]:
         """
         Compute QR decomposition of a matrix through Gram-Schmidt method.
         
         Args:
             A: matrix to be decomposed;
             only_R(optional): whether to skip reconstruction of Q; defaults to
                 False.
 
         Returns:
             Resulting orthogonal and upper-triangular factors.
         """
         N = A.shape[1]
         Q = np.zeros_like(A, dtype = A.dtype)
         R = np.zeros((N, N), dtype = A.dtype)
         for k in range(N):
             Q[:, k], R[: k + 1, k], _ = self.GS(A[:, k], Q, k)
         if only_R:
             return R
         return Q, R
     
     def QRHouseholder(self, A:Np2D, Q0 : Np2D = None,
                       only_R : bool = False) -> Tuple[Np1D, Np1D]:
         """
         Compute QR decomposition of a matrix through Householder method.
         
         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.
 
         Returns:
             Resulting (orthogonal and )upper-triangular factor(s).
         """
         B = copy(A)
         N = B.shape[1]
         V = np.zeros_like(B, dtype = B.dtype)
         R = np.zeros((N, N), dtype = B.dtype)
         if Q0 is None:
             Q = np.zeros_like(B, dtype = B.dtype) + np.random.randn(*(B.shape))
         else:
             Q = copy(Q0)
         for k in range(N):
             if Q0 is None:
                 Q[:, k], _, _ = self.GS(Q[:, k], Q, k)
             a = B[:, k]
             R[k, k] = self.HFEngine.norm(a)
             alpha = self.HFEngine.innerProduct(a, Q[:, k])
             if np.isclose(np.abs(alpha), 0.): s = 1.
             else: s = - alpha / np.abs(alpha)
             Q[:, k] = s * Q[:, k]
             V[:, k], _, _ = self.GS(R[k, k] * Q[:, k] - a, Q, k) 
             J = np.arange(k + 1, N)
             vtB = self.HFEngine.innerProduct(B[:, J], V[:, k])
             B[:, J] = B[:, J] - 2 * np.outer(V[:, k], vtB)
             R[k, J] = self.HFEngine.innerProduct(B[:, J], Q[:, k])
             B[:, J] = B[:, J] - np.outer(Q[:, k], R[k, J])
         if only_R:
             return R
         for k in range(N - 1, -1, -1):
             J = np.arange(k, N)
             vtQ = self.HFEngine.innerProduct(Q[:, J], V[:, k])
             Q[:, J] = Q[:, J] - 2 * np.outer(V[:, k], vtQ)
         return Q, R
         
diff --git a/rrompy/sampling/base/sampling_engine_base.py b/rrompy/sampling/base/sampling_engine_base.py
index b5c6066..9a537f5 100644
--- a/rrompy/sampling/base/sampling_engine_base.py
+++ b/rrompy/sampling/base/sampling_engine_base.py
@@ -1,102 +1,105 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from rrompy.utilities.base.types import Np1D, HFEng, strLst
 from rrompy.utilities.base import verbosityDepth
 
 __all__ = ['SamplingEngineBase']
 
 class SamplingEngineBase:
     """HERE"""
     
     nameBase = 0
 
     def __init__(self, HFEngine:HFEng, verbosity : int = 10):
         self.verbosity = verbosity
         if self.verbosity >= 10:
             verbosityDepth("INIT",
                            "Initializing sampling engine of type {}.".format(
                                                                   self.name()),
                            end = "")
         self.HFEngine = HFEngine
         if self.verbosity >= 10:
             verbosityDepth("DEL", " Done.", inline = True)
             
     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 resetHistory(self):
         self.samples = None
         self.nsamples = 0
         
     @property
     def HFEngine(self):
         """Value of HFEngine. Its assignment resets history."""
         return self._HFEngine
     @HFEngine.setter
     def HFEngine(self, HFEngine):
         self._HFEngine = HFEngine
         self.resetHistory()
     
     def solveLS(self, mu:complex, RHS : Np1D = None,
                 homogeneized : bool = False) -> Np1D:
         """
         Solve linear system.
 
         Args:
             mu: Parameter value.
         
         Returns:
             Solution of system.
         """
         if self.verbosity >= 5:
             verbosityDepth("INIT", "Solving HF model for mu = {}.".format(mu))
         u = self.HFEngine.solve(mu, RHS, homogeneized)
         if self.verbosity >= 5:
             verbosityDepth("DEL", "Done solving HF model.")
         return u
 
     def plotSamples(self, name : str = "u", save : str = None,
                     what : strLst = 'all', saveFormat : str = "eps",
                     saveDPI : int = 100, **figspecs):
         """
         Do some nice plots of the samples.
 
         Args:
             name(optional): Name to be shown as title of the plots. Defaults to
                 'u'.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             what(optional): Which plots to do. If list, can contain 'ABS',
                 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'.
                 Defaults to 'ALL'.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         for j in range(self.nsamples):
             self.HFEngine.plot(self.samples[:, j],
                                name = "{}_{}".format(name, j + self.nameBase),
                                save = save, what = what,
                                saveFormat = saveFormat, saveDPI = saveDPI,
                                **figspecs)
 
diff --git a/rrompy/sampling/scipy/sampling_engine_lagrange.py b/rrompy/sampling/scipy/sampling_engine_lagrange.py
index 81bf598..8ff48f2 100644
--- a/rrompy/sampling/scipy/sampling_engine_lagrange.py
+++ b/rrompy/sampling/scipy/sampling_engine_lagrange.py
@@ -1,69 +1,69 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 import numpy as np
 from rrompy.sampling.base.sampling_engine_base import SamplingEngineBase
 from rrompy.utilities.base.types import Np1D, Np2D
 from rrompy.utilities.base import verbosityDepth
 
 __all__ = ['SamplingEngineLagrange']
 
 class SamplingEngineLagrange(SamplingEngineBase):
     """HERE"""
 
     nameBase = 1
 
     def postprocessu(self, u:Np1D, overwrite : bool = False):
         return u
 
     def preallocateSamples(self, u:Np1D, n:int):
         self.samples = np.empty((u.size, n), dtype = u.dtype)
         self.samples[:, 0] = u
             
     def nextSample(self, mu:complex, overwrite : bool = False,
                    homogeneize : bool = False) -> Np1D:
         ns = self.nsamples
         u = self.postprocessu(self.solveLS(mu, homogeneized = homogeneize),
                               overwrite = overwrite)
         if overwrite:
             self.samples[:, ns] = u
         else:
             if ns == 0:
                 self.samples = u[:, None]
             else:
                 self.samples = np.hstack((self.samples, u[:, None]))
         self.nsamples += 1
         return u
 
-    def iterSample(self, mu:Np1D, homogeneize : bool = False) -> Np2D:
+    def iterSample(self, mus:Np1D, homogeneize : bool = False) -> Np2D:
         if self.verbosity >= 5:
             verbosityDepth("INIT", "Starting sampling iterations.")
-        n = mu.size
+        n = mus.size
         if n <= 0:
             raise Exception(("Number of samples must be positive."))
         self.resetHistory()
-        u = self.nextSample(mu[0], homogeneize = homogeneize)
+        u = self.nextSample(mus[0], homogeneize = homogeneize)
         if n > 1:
             self.preallocateSamples(u, n)
             for j in range(1, n):
-                self.nextSample(mu[j], overwrite = True,
+                self.nextSample(mus[j], overwrite = True,
                                 homogeneize = homogeneize)
         if self.verbosity >= 5:
             verbosityDepth("DEL", "Finished sampling iterations.")
         return self.samples
 
diff --git a/rrompy/utilities/parameter_sampling/__init__.py b/rrompy/utilities/parameter_sampling/__init__.py
index fe293d3..9577ccb 100644
--- a/rrompy/utilities/parameter_sampling/__init__.py
+++ b/rrompy/utilities/parameter_sampling/__init__.py
@@ -1,33 +1,36 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from .generic_sampler import GenericSampler
-from .quadrature_sampler import QuadratureSampler
-from .random_sampler import RandomSampler
 from .fft_sampler import FFTSampler
 from .manual_sampler import ManualSampler
+from .quadrature_sampler import QuadratureSampler
+from .random_sampler import RandomSampler
+from .warping_sampler import WarpingFunction, WarpingSampler
 
 __all__ = [
         'GenericSampler',
+        'FFTSampler',
+        'ManualSampler',
         'QuadratureSampler',
         'RandomSampler',
-        'FFTSampler',
-        'ManualSampler'
+        'WarpingFunction',
+        'WarpingSampler'
           ]
 
 
diff --git a/rrompy/utilities/parameter_sampling/generic_sampler.py b/rrompy/utilities/parameter_sampling/generic_sampler.py
index 4746b77..c3254f6 100644
--- a/rrompy/utilities/parameter_sampling/generic_sampler.py
+++ b/rrompy/utilities/parameter_sampling/generic_sampler.py
@@ -1,94 +1,97 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from abc import abstractmethod
 import numpy as np
 from rrompy.utilities.base.types import Np1D, Tuple, GenExpr, List
 
 __all__ = ['GenericSampler']
 
 class GenericSampler:
     """ABSTRACT. Generic generator of sample points."""
 
     def __init__(self, lims:Tuple[Np1D, Np1D], scaling : List[callable] = None, 
                  scalingInv : List[callable] = None):
         self.lims = lims
         self.scaling = scaling
         self.scalingInv = scalingInv
     
     def name(self) -> str:
         return self.__class__.__name__
         
     def __str__(self) -> str:
         return "{}[{}{}]".format(self.name(),
                                 np.array2string(self.lims[0], separator = '_'),
                                 np.array2string(self.lims[1], separator = '_'))
 
+    def __repr__(self) -> str:
+        return self.__str__() + " at " + hex(id(self))
+
     def __eq__(self, other) -> bool:
         return self.__dict__ == other.__dict__
     
     @property
     def lims(self):
         """Value of lims."""
         return self._lims
     @lims.setter
     def lims(self, lims):
         if len(lims) != 2:
             raise Exception("2 limits must be specified.")
         try:
             lims = lims.tolist()
         except:
             lims = list(lims)
         for j in range(2):
             try:
                 len(lims[j])
             except:
                 lims[j] = np.array([lims[j]])
         if len(lims[0]) != len(lims[1]):
             raise Exception("The limits must have the same length.")
         self._lims = lims
 
     @property
     def scaling(self):
         """Value of scaling."""
         return self._scaling
     @scaling.setter
     def scaling(self, scaling):
         if scaling is not None and not isinstance(scaling, list):
             scaling = [scaling]
         self._scaling = scaling
 
     @property
     def scalingInv(self):
         """Value of scalingInv."""
         return self._scalingInv
     @scalingInv.setter
     def scalingInv(self, scalingInv):
         if scalingInv is not None and not isinstance(scalingInv, list):
             scalingInv = [scalingInv]
         self._scalingInv = scalingInv
 
     @abstractmethod
     def generatePoints(self, n:GenExpr) -> Tuple[List[Np1D], Np1D]:
         """Array of points and array of weights."""
         assert ((self.scaling is None
               or len(self.scaling) == len(self.lims[0]))
             and (self.scalingInv is None
               or len(self.scalingInv) == len(self.lims[0])))
         pass
 
diff --git a/rrompy/utilities/parameter_sampling/manual_sampler.py b/rrompy/utilities/parameter_sampling/manual_sampler.py
index 431d28a..d06ea34 100644
--- a/rrompy/utilities/parameter_sampling/manual_sampler.py
+++ b/rrompy/utilities/parameter_sampling/manual_sampler.py
@@ -1,48 +1,59 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 import numpy as np
 from rrompy.utilities.parameter_sampling.generic_sampler import GenericSampler
 from rrompy.utilities.base.types import Np1D, Tuple, List
+from rrompy.utilities.warning_manager import warn
 
 __all__ = ['ManualSampler']
 
 class ManualSampler(GenericSampler):
     """Manual generator of sample points."""
 
     def __init__(self, lims:Tuple[Np1D, Np1D], points:Np1D,
                  scaling : List[callable] = None,
                  scalingInv : List[callable] = None):
         super().__init__(lims = lims, scaling = scaling,
                          scalingInv = scalingInv)
         self.points = points
     
     def __str__(self) -> str:
         return "{}[{}]".format(self.name(), "_".join(map(str, self.points)))
 
+    def __repr__(self) -> str:
+        return self.__str__() + " at " + hex(id(self))
+
     def generatePoints(self, n:int) -> Tuple[List[Np1D], Np1D]:
         """Array of quadrature points and array of weights."""
         super().generatePoints(None)
         size = 1. / n
         for j in range(len(self.lims[0])):
             a, b = self.lims[0][j], self.lims[1][j]
             if self.scaling is not None:
                 a, b = self.scaling[j](a), self.scaling[j](b)
             size *= np.abs(a - b)
-        return self.points[: n], np.ones(n) / size
+        if n > len(self.points):
+            warn(("Requested more points than given. Looping over first "
+                  "points."))
+            pts = np.tile(self.points,
+                          [np.int(np.ceil(len(self.points) / n))])[: n]
+        else:
+            pts = self.points[: n]
+        return pts, np.ones(n) * size
 
diff --git a/rrompy/utilities/parameter_sampling/quadrature_sampler.py b/rrompy/utilities/parameter_sampling/quadrature_sampler.py
index 13135f7..3d46842 100644
--- a/rrompy/utilities/parameter_sampling/quadrature_sampler.py
+++ b/rrompy/utilities/parameter_sampling/quadrature_sampler.py
@@ -1,90 +1,93 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 import numpy as np
 from rrompy.utilities.parameter_sampling.generic_sampler import GenericSampler
 from rrompy.utilities.base.types import Np1D, Tuple, List
 
 __all__ = ['QuadratureSampler']
 
 class QuadratureSampler(GenericSampler):
     """Generator of quadrature sample points."""
 
     allowedKinds = ["UNIFORM", "CHEBYSHEV", "GAUSSLEGENDRE"]
     
     def __init__(self, lims:Tuple[Np1D, Np1D], kind : str = "UNIFORM",
                  scaling : List[callable] = None,
                  scalingInv : List[callable] = None):
         super().__init__(lims = lims, scaling = scaling,
                          scalingInv = scalingInv)
         self.kind = kind
     
     def __str__(self) -> str:
         return "{}_{}".format(super().__str__(), self.kind)
 
+    def __repr__(self) -> str:
+        return self.__str__() + " at " + hex(id(self))
+
     @property
     def kind(self):
         """Value of kind."""
         return self._kind
     @kind.setter
     def kind(self, kind):
         if kind.upper() not in self.allowedKinds:
             raise Exception("Generator kind not recognized.")
         self._kind = kind.upper()
 
     def generatePoints(self, n:Np1D) -> Tuple[List[Np1D], Np1D]:
         """Array of quadrature points and array of weights."""
         super().generatePoints(n)
         d = len(self.lims[0])
         try:
             len(n)
         except:
             n = np.array([n])
         if len(n) != d:
             raise Exception(("Numbers of points must have same dimension as"
                              "limits."))
         for j in range(d):
             a, b = self.lims[0][j], self.lims[1][j]
             if self.scaling is not None:
                 a, b = self.scaling[j](a), self.scaling[j](b)
             if self.kind == "UNIFORM":
                 xj = np.linspace(a, b, n[j])[:, None]
                 wj = np.abs(a - b) / n[j] * np.ones(n[j])
             elif self.kind == "CHEBYSHEV":
                 nodes, weights = np.polynomial.chebyshev.chebgauss(n[j])
                 xj = (a + b) / 2 + (a - b) / 2 * nodes[:, None]
                 wj = np.abs(a - b) / np.pi * weights
             elif self.kind == "GAUSSLEGENDRE":
                 nodes, weights = np.polynomial.legendre.leggauss(n[j])
                 xj = (a + b) / 2 + (a - b) / 2 * nodes[:, None]
                 wj = np.abs(a - b) * weights
             if self.scalingInv is not None:
                 xj = self.scalingInv[j](xj)
             if j == 0:
                 x = xj
                 w = wj
                 xsize = n[0]
             else:
                 x = np.concatenate((np.kron(np.ones(n[j])[:, None], x), 
                                     np.kron(xj, np.ones(xsize)[:, None])),
                                    axis = 1)
                 w = np.multiply(np.kron(np.ones(n[j]), w),
                                 np.kron(wj, np.ones(xsize)))
                 xsize = xsize * n[j]
         return [y.flatten() for y in np.split(x, xsize)], w
 
diff --git a/rrompy/utilities/parameter_sampling/random_sampler.py b/rrompy/utilities/parameter_sampling/random_sampler.py
index d47d8e3..94dcc57 100644
--- a/rrompy/utilities/parameter_sampling/random_sampler.py
+++ b/rrompy/utilities/parameter_sampling/random_sampler.py
@@ -1,67 +1,70 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 import numpy as np
 from rrompy.utilities.base.sobol import sobolGenerate
 from rrompy.utilities.parameter_sampling.generic_sampler import GenericSampler
 from rrompy.utilities.base.types import Np1D, Tuple, List
 
 __all__ = ['RandomSampler']
 
 class RandomSampler(GenericSampler):
     """Generator of quadrature sample points."""
 
     allowedKinds = ["UNIFORM", "SOBOL"]
     
     def __init__(self, lims:Tuple[Np1D, Np1D], kind : str = "UNIFORM",
                  scaling : callable = None, scalingInv : callable = None):
         super().__init__(lims = lims, scaling = scaling,
                          scalingInv = scalingInv)
         self.kind = kind
     
     def __str__(self) -> str:
         return "{}_{}".format(super().__str__(), self.kind)
 
+    def __repr__(self) -> str:
+        return self.__str__() + " at " + hex(id(self))
+
     @property
     def kind(self):
         """Value of kind."""
         return self._kind
     @kind.setter
     def kind(self, kind):
         if kind.upper() not in self.allowedKinds:
             raise Exception("Generator kind not recognized.")
         self._kind = kind.upper()
 
     def generatePoints(self, n:int, seed : int = 0) -> Tuple[List[Np1D], Np1D]:
         """Array of quadrature points and array of weights."""
         super().generatePoints(n)
         d = len(self.lims[0])
         if self.kind == "UNIFORM":
             np.random.seed(seed)
             x = np.random.uniform(size = (n, d))
         else:
             x = sobolGenerate(d, n, seed)
         for j in range(d):
             a, b = self.lims[0][j], self.lims[1][j]
             if self.scaling is not None:
                 a, b = self.scaling[j](a), self.scaling[j](b)
             x[:, j] = a + (b - a) * x[:, j]
             if self.scalingInv is not None:
                 x[:, j] = self.scalingInv[j](x[:, j])
         return [y.flatten() for y in np.split(x, n)], np.ones(n)
 
diff --git a/rrompy/utilities/parameter_sampling/quadrature_sampler.py b/rrompy/utilities/parameter_sampling/warping_sampler.py
similarity index 55%
copy from rrompy/utilities/parameter_sampling/quadrature_sampler.py
copy to rrompy/utilities/parameter_sampling/warping_sampler.py
index 13135f7..84f0a9d 100644
--- a/rrompy/utilities/parameter_sampling/quadrature_sampler.py
+++ b/rrompy/utilities/parameter_sampling/warping_sampler.py
@@ -1,90 +1,114 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 import numpy as np
 from rrompy.utilities.parameter_sampling.generic_sampler import GenericSampler
 from rrompy.utilities.base.types import Np1D, Tuple, List
 
-__all__ = ['QuadratureSampler']
+__all__ = ['WarpingSampler', 'WarpingFunction']
 
-class QuadratureSampler(GenericSampler):
-    """Generator of quadrature sample points."""
+class WarpingFunction:
+    """Wrapper for warping function."""
 
-    allowedKinds = ["UNIFORM", "CHEBYSHEV", "GAUSSLEGENDRE"]
-    
-    def __init__(self, lims:Tuple[Np1D, Np1D], kind : str = "UNIFORM",
+    def __init__(self, other : "WarpingFunction" = None, **kwargs):
+        if other is not None:
+            self._call_ = other._call_
+            self._repr_ = other._repr_
+        else:
+            self._call_ = kwargs["call"]
+            self._repr_ = kwargs["repr"]
+        if not callable(self._call_):
+            self._call_ = lambda x: self._call_
+        if callable(self._repr_):
+            self._repr_ = self._repr_()
+
+    def __call__(self, x):
+        return self._call_(x)
+
+    def __str__(self):
+        return self._repr_
+
+    def __repr__(self) -> str:
+        return self.__str__() + " at " + hex(id(self))
+
+
+class WarpingSampler(GenericSampler):
+    """Generator of sample points from warping of uniform ones."""
+
+    def __init__(self, lims:Tuple[Np1D, Np1D], warping : List[callable],
                  scaling : List[callable] = None,
                  scalingInv : List[callable] = None):
         super().__init__(lims = lims, scaling = scaling,
                          scalingInv = scalingInv)
-        self.kind = kind
+        self.warping = warping
     
     def __str__(self) -> str:
-        return "{}_{}".format(super().__str__(), self.kind)
+        return "{}_{}".format(super().__str__(), self.warping)
+
+    def __repr__(self) -> str:
+        return self.__str__() + " at " + hex(id(self))
 
     @property
-    def kind(self):
-        """Value of kind."""
-        return self._kind
-    @kind.setter
-    def kind(self, kind):
-        if kind.upper() not in self.allowedKinds:
-            raise Exception("Generator kind not recognized.")
-        self._kind = kind.upper()
+    def warping(self):
+        """Value of warping."""
+        return self._warping
+    @warping.setter
+    def warping(self, warping):
+        if not isinstance(warping, (list, tuple,)):
+            warping = [warping] * len(self.lims[0])
+        for j in range(len(warping)):
+            if not isinstance(warping[j], (WarpingFunction,)):
+                warping[j] = WarpingFunction(call = warping[j].__call__,
+                                             repr = warping[j].__repr__)
+        self._warping = warping
 
     def generatePoints(self, n:Np1D) -> Tuple[List[Np1D], Np1D]:
         """Array of quadrature points and array of weights."""
         super().generatePoints(n)
+        assert len(self.warping) == len(self.lims[0])
         d = len(self.lims[0])
         try:
             len(n)
         except:
             n = np.array([n])
         if len(n) != d:
             raise Exception(("Numbers of points must have same dimension as"
                              "limits."))
         for j in range(d):
             a, b = self.lims[0][j], self.lims[1][j]
             if self.scaling is not None:
                 a, b = self.scaling[j](a), self.scaling[j](b)
-            if self.kind == "UNIFORM":
-                xj = np.linspace(a, b, n[j])[:, None]
-                wj = np.abs(a - b) / n[j] * np.ones(n[j])
-            elif self.kind == "CHEBYSHEV":
-                nodes, weights = np.polynomial.chebyshev.chebgauss(n[j])
-                xj = (a + b) / 2 + (a - b) / 2 * nodes[:, None]
-                wj = np.abs(a - b) / np.pi * weights
-            elif self.kind == "GAUSSLEGENDRE":
-                nodes, weights = np.polynomial.legendre.leggauss(n[j])
-                xj = (a + b) / 2 + (a - b) / 2 * nodes[:, None]
-                wj = np.abs(a - b) * weights
+            x0, sigma = (a + b) / 2, (a - b) / 2
+            xj0 = np.linspace(- 1., 1., n[j])[:, None]
+            xj = x0 + sigma * self.warping[j](xj0)
+            wj = np.abs(a - b) / n[j] * np.ones(n[j])
             if self.scalingInv is not None:
                 xj = self.scalingInv[j](xj)
             if j == 0:
                 x = xj
                 w = wj
                 xsize = n[0]
             else:
                 x = np.concatenate((np.kron(np.ones(n[j])[:, None], x), 
                                     np.kron(xj, np.ones(xsize)[:, None])),
                                    axis = 1)
                 w = np.multiply(np.kron(np.ones(n[j]), w),
                                 np.kron(wj, np.ones(xsize)))
                 xsize = xsize * n[j]
         return [y.flatten() for y in np.split(x, xsize)], w
 
diff --git a/rrompy/utilities/parameter_sweeper/parameter_sweeper.py b/rrompy/utilities/parameter_sweeper/parameter_sweeper.py
index a4d9167..5692624 100644
--- a/rrompy/utilities/parameter_sweeper/parameter_sweeper.py
+++ b/rrompy/utilities/parameter_sweeper/parameter_sweeper.py
@@ -1,489 +1,499 @@
 # Copyright (C) 2018 by the RROMPy authors
 #
 # This file is part of RROMPy.
 #
 # RROMPy is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # RROMPy is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with RROMPy. If not, see <http://www.gnu.org/licenses/>.
 #
 
 from copy import copy
 import itertools
 import csv
 import numpy as np
 from matplotlib import pyplot as plt
 from rrompy.utilities.base.types import Np1D, DictAny, List, ROMEng
 from rrompy.utilities.base import purgeList, getNewFilename, verbosityDepth
 from rrompy.utilities.warning_manager import warn
 
 __all__ = ['ParameterSweeper']
 
 def C2R2csv(x):
     x = np.ravel(x)
     y = np.concatenate((np.real(x), np.imag(x)))
     z = np.ravel(np.reshape(y, [2, np.size(x)]).T)
     return np.array2string(z, separator = '_', suppress_small = False,
                            max_line_width = np.inf, sign = '+',
                            formatter = {'all' : lambda x : "{:.15E}".format(x)}
                           )[1 : -1]
 
 class ParameterSweeper:
     """
     ROM approximant parameter sweeper.
     
     Args:
         ROMEngine(optional): Generic approximant class. Defaults to None.
         mutars(optional): Array of parameter values to sweep. Defaults to empty
             array.
         params(optional): List of parameter settings (each as a dict) to
             explore. Defaults to single empty set.
         mostExpensive(optional): String containing label of most expensive
             step, to be executed fewer times. Allowed options are 'HF' and
             'Approx'. Defaults to 'HF'.
 
     Attributes:
         ROMEngine: Generic approximant class.
         mutars: Array of parameter values to sweep.
         params: List of parameter settings (each as a dict) to explore.
         mostExpensive: String containing label of most expensive step, to be
             executed fewer times.
     """
     
     allowedOutputsStandard = ["normHF", "normApp", "normRes", "normResRel",
                               "normErr", "normErrRel"]
     allowedOutputs = allowedOutputsStandard + ["HFFunc", "AppFunc",
                                                "ErrFunc", "ErrFuncRel"]
     allowedOutputsFull = allowedOutputs + ["poles"]
 
     def __init__(self, ROMEngine : ROMEng = None, mutars : Np1D = np.array([]),
                  params : List[DictAny] = [{}], mostExpensive : str = "HF"):
         self.ROMEngine = ROMEngine
         self.mutars = mutars
         self.params = params
         self.mostExpensive = mostExpensive
 
     def name(self) -> str:
         return self.__class__.__name__
         
     def __str__(self) -> str:
         return self.name()
 
+    def __repr__(self) -> str:
+        return self.__str__() + " at " + hex(id(self))
+
     @property
     def mostExpensive(self):
         """Value of mostExpensive."""
         return self._mostExpensive
     @mostExpensive.setter
     def mostExpensive(self, mostExpensive:str):
         mostExpensive = mostExpensive.upper()
         if mostExpensive not in ["HF", "APPROX"]:
             warn(("Value of mostExpensive not recognized. Overriding to "
                   "'APPROX'."))
             mostExpensive = "APPROX"
         self._mostExpensive = mostExpensive
 
     def checkValues(self) -> bool:
         """Check if sweep can be performed."""
         if self.ROMEngine is None:
             raise Exception("ROMEngine is missing. Aborting.")
         if len(self.mutars) == 0:
             raise Exception("Empty target parameter vector. Aborting.")
         if len(self.params) == 0:
             raise Exception("Empty method parameters vector. Aborting.")
 
     def sweep(self, filename : str = "out.dat", outputs : List[str] = [],
               verbose : int = 10):
         self.checkValues()
         try:
             if outputs.upper() == "ALL":
                 outputs = self.allowedOutputsFull
         except:
             if len(outputs) == 0:
                 outputs = self.allowedOutputsStandard
         outputs = purgeList(outputs, self.allowedOutputsFull,
                             listname = self.name() + ".outputs",
                             baselevel = 1)
         poles = ("poles" in outputs)
         if len(outputs) == 0:
             raise Exception("Empty outputs. Aborting.")
         outParList = self.ROMEngine.parameterList
         Nparams = len(self.params)
         if poles: polesCheckList = []
         allowedParams = self.ROMEngine.parameterList
         
         dotPos = filename.rfind('.')
         if dotPos in [-1, len(filename) - 1]:
             filename = getNewFilename(filename[:dotPos])
         else:
             filename = getNewFilename(filename[:dotPos], filename[dotPos + 1:])
             
         append_write = "w"
         initial_row = (outParList + ["muRe", "muIm"]
                      + [x for x in self.allowedOutputs if x in outputs]
                      + ["type"] + ["poles"] * poles)
         with open(filename, append_write, buffering = 1) as fout:
             writer = csv.writer(fout, delimiter=",")
             writer.writerow(initial_row)
 
             if self.mostExpensive == "HF":
                 outerSet = self.mutars
                 innerSet = self.params
             elif self.mostExpensive == "APPROX":
                 outerSet = self.params
                 innerSet = self.mutars
     
             for outerIdx, outerPar in enumerate(outerSet):
                 if self.mostExpensive == "HF":
                     i, mutar = outerIdx, outerPar
                 elif self.mostExpensive == "APPROX":
                     j, par = outerIdx, outerPar
                     self.ROMEngine.approxParameters = {k: par[k] for k in\
                                                     par.keys() & allowedParams}
                     self.ROMEngine.setupApprox()
     
                 for innerIdx, innerPar in enumerate(innerSet):
                     if self.mostExpensive == "APPROX":
                         i, mutar = innerIdx, innerPar
                     elif self.mostExpensive == "HF":
                         j, par = innerIdx, innerPar
                         self.ROMEngine.approxParameters = {k: par[k] for k in\
                                                     par.keys() & allowedParams}
                         self.ROMEngine.setupApprox()
         
                     if verbose >= 5:
                         verbosityDepth("INIT", "Set {}/{}\tmu_{} = {:.10f}"\
                                              .format(j + 1, Nparams, i, mutar))
     
                     outData = []
                     if "normHF" in outputs:
                         valNorm = self.ROMEngine.normHF(mutar)
                         outData = outData + [valNorm]
                     if "normApp" in outputs:
                         val = self.ROMEngine.normApp(mutar)
                         outData = outData + [val]
                     if "normRes" in outputs:
                         valNRes = self.ROMEngine.normRes(mutar)
                         outData = outData + [valNRes]
                     if "normResRel" in outputs:
                         if "normRes" not in outputs:
                             valNRes = self.ROMEngine.normRes(mutar)
                         val = self.ROMEngine.normRHS(mutar)
                         outData = outData + [valNRes / val]
                     if "normErr" in outputs:
                         valNErr = self.ROMEngine.normErr(mutar)
                         outData = outData + [valNErr]
                     if "normErrRel" in outputs:
                         if "normHF" not in outputs:
                             valNorm = self.ROMEngine.normHF(mutar)
                         if "normErr" not in outputs:
                             valNErr = self.ROMEngine.normErr(mutar)
                         outData = outData + [valNErr / valNorm]
                     if "HFFunc" in outputs:
                         valFunc = self.ROMEngine.HFEngine.functional(
                                                    self.ROMEngine.getHF(mutar))
                         outData = outData + [valFunc]
                     if "AppFunc" in outputs:
                         valFApp = self.ROMEngine.HFEngine.functional(
                                                   self.ROMEngine.getApp(mutar))
                         outData = outData + [valFApp]
                     if "ErrFunc" in outputs:
                         if "HFFunc" not in outputs:
                             valFunc = self.ROMEngine.HFEngine.functional(
                                                    self.ROMEngine.getHF(mutar))
                         if "AppFunc" not in outputs:
                             valFApp = self.ROMEngine.HFEngine.functional(
                                                   self.ROMEngine.getApp(mutar))
                         valFErr = np.abs(valFApp - valFunc)
                         outData = outData + [valFErr]
                     if "ErrFuncRel" in outputs:
                         if not ("HFFunc" in outputs or "ErrFunc" in outputs):
                             valFunc = self.ROMEngine.HFEngine.functional(
                                                    self.ROMEngine.getHF(mutar))
                         if not ("AppFunc" in outputs or "ErrFunc" in outputs):
                             valFApp = self.ROMEngine.HFEngine.functional(
                                                   self.ROMEngine.getApp(mutar))
                         val = np.nan
                         if not np.isclose(valFunc, 0.):
                             val = valFApp / valFunc
                         outData = outData + [val]
                     writeData = []
                     for parn in outParList:
                         writeData = (writeData
                                    + [self.ROMEngine.approxParameters[parn]])
                     writeData = (writeData + [mutar.real, mutar.imag]
                                + outData + [self.ROMEngine.name()])
                     if poles:
                         if j not in polesCheckList:
                             polesCheckList += [j]
                             writeData = writeData + [C2R2csv(
                                                     self.ROMEngine.getPoles())]
                         else:
                             writeData = writeData + [""]
                     writer.writerow(str(x) for x in writeData)
                     if verbose >= 5:
                         verbosityDepth("DEL", "", end = "", inline = "")
         
                 if verbose >= 5:
                     if self.mostExpensive == "APPROX":
                         out = "Set {}/{}\tdone.\n".format(j + 1, Nparams)
                     elif self.mostExpensive == "HF":
                         out = "Point mu_{} = {:.10f}\tdone.\n".format(i, mutar)
                     verbosityDepth("INIT", out)
                     verbosityDepth("DEL", "", end = "", inline = "")
         self.filename = filename
         return self.filename
             
     def read(self, filename:str, restrictions : DictAny = {},
              outputs : List[str] = []) -> DictAny:
         """
         Execute a query on a custom format CSV.
     
         Args:
             filename: CSV filename.
             restrictions(optional): Parameter configurations to output.
                 Defaults to empty dictionary, i.e. output all.
             outputs(optional):  Values to output. Defaults to empty list, i.e.
                 no output.
     
         Returns:
             Dictionary of desired results, with a key for each entry of
                 outputs, and a numpy 1D array as corresponding value.
         """
         with open(filename, 'r') as f:
             reader = csv.reader(f, delimiter=',')
             header = next(reader)
             restrIndices, outputIndices, outputData = {}, {}, {}
             for key in restrictions.keys():
                 try:
                     restrIndices[key] = header.index(key)
                     if not isinstance(restrictions[key], list):
                         restrictions[key] = [restrictions[key]]
                     restrictions[key] = copy(restrictions[key])
                 except:
                     warn("Ignoring key {} from restrictions.".format(key))
             for key in outputs:
                 try:
                     outputIndices[key] = header.index(key)
                     outputData[key] = np.array([])
                 except:
                     warn("Ignoring key {} from outputs.".format(key))
 
             for row in reader:
                 restrTrue = True
                 for key in restrictions.keys():
                     if row[restrIndices[key]] == restrictions[key]:
                         continue
                     try:
                         if np.any(np.isclose(float(row[restrIndices[key]]),
                                   [float(x) for x in restrictions[key]])):
                             continue
                     except: pass
                     restrTrue = False
                 if restrTrue:
                     for key in outputIndices.keys():
                         try:
                             val = row[outputIndices[key]]
                             val = float(val)
                         finally:
                             outputData[key] = np.append(outputData[key], val)
         return outputData
 
     def plot(self, filename:str, xs:List[str], ys:List[str], zs:List[str],
              onePlot : bool = False, save : str = None,
              saveFormat : str = "eps", saveDPI : int = 100, **figspecs):
         """
         Perform plots from data in filename.
     
         Args:
             filename: CSV filename.
             xs: Values to put on x axes.
             ys: Values to put on y axes.
             zs: Meta-values for constraints.
             onePlot(optional): Whether to create a single figure per x.
                 Defaults to False.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         if save is not None:
             save = save.strip()
         zsVals = self.read(filename, outputs = zs)
         zs = list(zsVals.keys())
         zss = None
         for key in zs:
             vals = np.unique(zsVals[key])
             if zss is None:
                 zss = copy(vals)
             else:
                 zss = list(itertools.product(zss, vals))
         lzs = len(zs)
         for z in zss:
             if lzs <= 1:
                 constr = {zs[0] : z}
             else:
                 constr = {zs[j] : z[j] for j in range(len(zs))}
             data = self.read(filename, restrictions = constr, outputs = xs+ys)
             if onePlot:
                 for x in xs:
                     xVals = data[x]
                     p = plt.figure(**figspecs)
                     logScale = False
                     for y in ys:
                         yVals = data[y]
                         label = '{} vs {} for {}'.format(y, x, constr)
                         if np.min(yVals) <= - np.finfo(float).eps:
                             plt.plot(xVals, yVals, label = label)
                         else:
                             plt.plot(xVals, yVals, label = label)
                             if np.log10(np.max(yVals) / np.min(yVals)) > 1.:
                                 logScale = True
                     if logScale:
                         ax = p.get_axes()[0]
                         ax.set_yscale('log')
                     plt.legend()
                     plt.grid()
                     if save is not None:
                         prefix = "{}_{}_vs_{}_{}".format(save, ys, x, constr)
                         plt.savefig(getNewFilename(prefix, saveFormat),
                                     format = saveFormat, dpi = saveDPI)
                     plt.show()
                     plt.close()
             else:
                 for x, y in itertools.product(xs, ys):
                     xVals, yVals = data[x], data[y]
                     label = '{} vs {} for {}'.format(y, x, constr)
                     p = plt.figure(**figspecs)
                     if np.min(yVals) <= - np.finfo(float).eps:
                         plt.plot(xVals, yVals, label = label)
                     else:
                         plt.plot(xVals, yVals, label = label)
                         if np.log10(np.max(yVals) / np.min(yVals)) > 1.:
                             ax = p.get_axes()[0]
                             ax.set_yscale('log')
                     plt.legend()
                     plt.grid()
                     if save is not None:
                         prefix = "{}_{}_vs_{}_{}".format(save, y, x, constr)
                         plt.savefig(getNewFilename(prefix, saveFormat),
                                     format = saveFormat, dpi = saveDPI)
                     plt.show()
                     plt.close()
 
     def plotCompare(self, filenames:List[str], xs:List[str], ys:List[str],
                     zs:List[str], onePlot : bool = False, save : str = None,
-                    saveFormat : str = "eps", saveDPI : int = 100,
-                    labels : List[str] = None, **figspecs):
+                    ylims : dict = None, saveFormat : str = "eps",
+                    saveDPI : int = 100, labels : List[str] = None,
+                    **figspecs):
         """
         Perform plots from data in filename1 and filename2.
     
         Args:
             filenames: CSV filenames.
             xs: Values to put on x axes.
             ys: Values to put on y axes.
             zs: Meta-values for constraints.
             onePlot(optional): Whether to create a single figure per x.
                 Defaults to False.
             save(optional): Where to save plot(s). Defaults to None, i.e. no
                 saving.
+            clip(optional): Custom y axis limits. If None, automatic values are
+                kept. Defaults to None.
             saveFormat(optional): Format for saved plot(s). Defaults to "eps".
             saveDPI(optional): DPI for saved plot(s). Defaults to 100.
             labels: Label for each dataset.
             figspecs(optional key args): Optional arguments for matplotlib
                 figure creation.
         """
         nfiles = len(filenames)
         if save is not None:
             save = save.strip()
         if labels is None:
             labels = ["{}".format(j + 1) for j in range(nfiles)]
         zsVals = self.read(filenames[0], outputs = zs)
         zs = list(zsVals.keys())
         zss = None
         for key in zs:
             vals = np.unique(zsVals[key])
             if zss is None:
                 zss = copy(vals)
             else:
                 zss = list(itertools.product(zss, vals))
         lzs = len(zs)
         for z in zss:
             if lzs <= 1:
                 constr = {zs[0] : z}
             else:
                 constr = {zs[j] : z[j] for j in range(len(zs))}
             data = [None] * nfiles
             for j in range(nfiles):
                 data[j] = self.read(filenames[j], restrictions = constr,
                                     outputs = xs + ys)
             if onePlot:
                 for x in xs:
                     xVals = [None] * nfiles
                     for j in range(nfiles):
                         try:
                             xVals[j] = data[j][x]
                         except:
                             pass
                     p = plt.figure(**figspecs)
                     logScale = False
                     for y in ys:
                         for j in range(nfiles):
                             try:
                                 yVals = data[j][y]
                             except:
                                 pass
                             l = '{} vs {} for {}, {}'.format(y, x, constr,
                                                              labels[j])
                             if np.min(yVals) <= - np.finfo(float).eps:
                                 plt.plot(xVals[j], yVals, label = l)
                             else:
                                 plt.plot(xVals[j], yVals, label = l)
                                 if np.log10(np.max(yVals)/np.min(yVals)) > 1.:
                                     logScale = True
                     if logScale:
                         ax = p.get_axes()[0]
                         ax.set_yscale('log')
+                    if ylims is not None:
+                        plt.ylim(**ylims)
                     plt.legend()
                     plt.grid()
                     if save is not None:
                         prefix = "{}_{}_vs_{}_{}".format(save, ys, x, constr)
                         plt.savefig(getNewFilename(prefix, saveFormat),
                                     format = saveFormat, dpi = saveDPI)
                     plt.show()
                     plt.close()
             else:
                 for x, y in itertools.product(xs, ys):
                     p = plt.figure(**figspecs)
                     logScale = False
                     for j in range(nfiles):
                         xVals, yVals = data[j][x], data[j][y]
                         l = '{} vs {} for {}, {}'.format(y, x, constr,
                                                          labels[j])
                         if np.min(yVals) <= - np.finfo(float).eps:
                             plt.plot(xVals, yVals, label = l)
                         else:
                             plt.plot(xVals, yVals, label = l)
                             if np.log10(np.max(yVals)/np.min(yVals)) > 1.:
                                 logScale = True
                     if logScale:
                         ax = p.get_axes()[0]
                         ax.set_yscale('log')
+                    if ylims is not None:
+                        plt.ylim(**ylims)
                     plt.legend()
                     plt.grid()
                     if save is not None:
                         prefix = "{}_{}_vs_{}_{}".format(save, y, x, constr)
                         plt.savefig(getNewFilename(prefix, saveFormat),
                                     format = saveFormat, dpi = saveDPI)
                     plt.show()
                     plt.close()