diff --git a/VERSION b/VERSION index 840ca8c..400122e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4 \ No newline at end of file +1.5 \ No newline at end of file diff --git a/examples/2d/base/square.py b/examples/2d/base/square.py new file mode 100644 index 0000000..ae2428e --- /dev/null +++ b/examples/2d/base/square.py @@ -0,0 +1,13 @@ +import numpy as np +from rrompy.hfengines.linear_problem.bidimensional import \ + HelmholtzSquareDomainProblemEngine as HSDPE +verb = 100 + +mu0 = [4 ** .5, 1.5 ** .5] +solver = HSDPE(kappa = 2.5, theta = np.pi / 3, mu0 = mu0, n = 50, + verbosity = verb) +uh = solver.solve(mu0)[0] +solver.plotmesh() +print(solver.norm(uh)) +solver.plot(uh) +solver.plot(solver.residual(uh, mu0)[0], 'res') diff --git a/examples/2d/pod/plot_zero_set.py b/examples/2d/pod/plot_zero_set.py new file mode 100644 index 0000000..7281164 --- /dev/null +++ b/examples/2d/pod/plot_zero_set.py @@ -0,0 +1,49 @@ +import numpy as np +from matplotlib import pyplot as plt + +def plotZeroSet(murange, murangeEff, approx, mu0, nSamples = 200, + exps = [2., 2.], clip = -1): + if hasattr(approx, "mus"): + mu2x, mu2y = approx.mus(0) ** exps[0], approx.mus(1) ** exps[1] + else: + mu2x, mu2y = mu0[0] ** exps[0], mu0[1] ** exps[1] + murangeExp = [[murange[0][0] ** exps[0], murange[0][1] ** exps[1]], + [murange[1][0] ** exps[0], murange[1][1] ** exps[1]]] + mu1 = np.linspace(murangeEff[0][0] ** exps[0], murangeEff[1][0] ** exps[0], + nSamples) + mu2 = np.linspace(murangeEff[0][1] ** exps[1], murangeEff[1][1] ** exps[1], + nSamples) + mu1s = np.power(mu1, 1. / exps[0]) + mu2s = np.power(mu2, 1. / exps[1]) + Mu1, Mu2 = np.meshgrid(np.real(mu1), np.real(mu2)) + mus = [(m1, m2) for m2 in mu2s for m1 in mu1s] + Z = approx.trainedModel.getQVal(mus).reshape(Mu1.shape) + Zabs = np.log(np.abs(Z)) + Zabsmin, Zabsmax = np.min(Zabs), np.max(Zabs) + if clip > 0: + Zabsmin += clip * (Zabsmax - Zabsmin) + cmap = plt.cm.bone_r + else: + cmap = plt.cm.jet + plt.figure(figsize = (15, 7)) + plt.jet() + p = plt.contourf(Mu1, Mu2, Zabs, cmap = cmap, + levels = np.linspace(Zabsmin, Zabsmax, 50)) + if clip > 0: + plt.contour(Mu1, Mu2, Zabs, [Zabsmin]) + plt.contour(Mu1, Mu2, np.real(Z), [0.], linestyles = 'dashed') + plt.contour(Mu1, Mu2, np.imag(Z), [0.], linewidths = 1, + linestyles = 'dotted') + plt.plot(mu2x, mu2y, 'kx') + plt.plot([murangeExp[0][0]] * 2, + [murangeExp[0][1], murangeExp[1][1]], 'm:') + plt.plot([murangeExp[0][0], murangeExp[1][0]], + [murangeExp[1][1]] * 2, 'm:') + plt.plot([murangeExp[1][0]] * 2, + [murangeExp[1][1], murangeExp[0][1]], 'm:') + plt.plot([murangeExp[1][0], murangeExp[0][0]], + [murangeExp[0][1]] * 2, 'm:') + plt.colorbar(p) + plt.grid() + plt.show() + diff --git a/examples/2d/pod/square_pod.py b/examples/2d/pod/square_pod.py new file mode 100644 index 0000000..56de10d --- /dev/null +++ b/examples/2d/pod/square_pod.py @@ -0,0 +1,144 @@ +import numpy as np +from rrompy.hfengines.linear_problem.bidimensional import \ + HelmholtzSquareDomainProblemEngine as HSDPE +from rrompy.reduction_methods.centered import RationalPade as RP +from rrompy.reduction_methods.distributed import RationalInterpolant as RI +from rrompy.reduction_methods.centered import RBCentered as RBC +from rrompy.reduction_methods.distributed import RBDistributed as RBD +from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, + QuadratureSamplerTotal as QST, + ManualSampler as MS, + RandomSampler as RS) + +verb = 5 +size = 1 +show_sample = False +ignore_forcing = True +ignore_forcing = False +clip = -1 +#clip = .4 +#clip = .6 + +MN = 6 +R = (MN + 2) * (MN + 1) // 2 +S = [int(np.ceil(R ** .5))] * 2 + +samples = "centered" +samples = "centered_fake" +samples = "distributed" +algo = "rational" +#algo = "RB" +sampling = "quadrature" +sampling = "quadrature_total" +#sampling = "random" + +if size == 1: # small + mu0 = [4 ** .5, 1.5 ** .5] + mutar = [5 ** .5, 1.75 ** .5] + murange = [[2 ** .5, 1. ** .5], [6 ** .5, 2. ** .5]] +elif size == 2: # medium + mu0 = [4 ** .5, 1.75 ** .5] + mutar = [5 ** .5, 1.25 ** .5] + murange = [[1 ** .5, 1. ** .5], [7 ** .5, 2.5 ** .5]] +elif size == 3: # fat + mu0 = [6 ** .5, 4 ** .5] + mutar = [2 ** .5, 2.5 ** .5] + murange = [[0 ** .5, 2 ** .5], [12 ** .5, 6 ** .5]] +elif size == 4: # crowded + mu0 = [10 ** .5, 2 ** .5] + mutar = [9 ** .5, 2.25 ** .5] + murange = [[8 ** .5, 1.5 ** .5], [12 ** .5, 2.5 ** .5]] +elif size == 5: # tall + mu0 = [11 ** .5, 2.25 ** .5] + mutar = [10.5 ** .5, 2.5 ** .5] + murange = [[10 ** .5, 1.5 ** .5], [12 ** .5, 3 ** .5]] +elif size == 6: # taller + mu0 = [11 ** .5, 2.25 ** .5] + mutar = [10.5 ** .5, 2.5 ** .5] + murange = [[10 ** .5, 1.25 ** .5], [12 ** .5, 3.25 ** .5]] +elif size == 7: # low + mu0 = [7 ** .5, .75 ** .5] + mutar = [6.5 ** .5, .9 ** .5] + murange = [[6 ** .5, .5 ** .5], [8 ** .5, 1. ** .5]] + +aEff = 1.25 +bEff = 1. - aEff +murangeEff = [[(aEff*murange[0][0]**2. + bEff*murange[1][0]**2.) ** .5, + (aEff*murange[0][1]**2. + bEff*murange[1][1]**2.) ** .5], + [(aEff*murange[1][0]**2. + bEff*murange[0][0]**2.) ** .5, + (aEff*murange[1][1]**2. + bEff*murange[0][1]**2.) ** .5]] + +solver = HSDPE(kappa = 2.5, theta = np.pi / 3, mu0 = mu0, n = 20, + verbosity = verb) +if ignore_forcing: solver.nbs = 1 + +rescaling = [lambda x: np.power(x, 2.), lambda x: x] +rescalingInv = [lambda x: np.power(x, .5), lambda x: x] +if algo == "rational": + params = {'N':MN, 'M':MN, 'S':S, 'POD':True} + if samples == "distributed": + params['polybasis'] = "CHEBYSHEV" +# params['polybasis'] = "LEGENDRE" +# params['polybasis'] = "MONOMIAL" + params['E'] = MN + method = RI + elif samples == "centered_fake": + params['polybasis'] = "MONOMIAL" + params['S'] = R + method = RI + else: + params['S'] = R + method = RP +else: #if algo == "RB": + params = {'R':R, 'S':S, 'POD':True} + if samples == "distributed": + method = RBD + elif samples == "centered_fake": + params['S'] = R + method = RBD + else: + params['S'] = R + method = RBC + +if samples == "distributed": + if sampling == "quadrature": + params['sampler'] = QS(murange, "CHEBYSHEV", scaling = rescaling, + scalingInv = rescalingInv) +# params['sampler'] = QS(murange, "GAUSSLEGENDRE", scaling = rescaling, +# scalingInv = rescalingInv) +# params['sampler'] = QS(murange, "UNIFORM", scaling = rescaling, +# scalingInv = rescalingInv) + params['S'] = [max(j, MN + 1) for j in params['S']] + elif sampling == "quadrature_total": + params['sampler'] = QST(murange, "CHEBYSHEV", scaling = rescaling, + scalingInv = rescalingInv) + params['S'] = R + else: # if sampling == "random": + params['sampler'] = RS(murange, "HALTON", scaling = rescaling, + scalingInv = rescalingInv) + params['S'] = R + +elif samples == "centered_fake": + params['sampler'] = MS(murange, points = [mu0], scaling = rescaling, + scalingInv = rescalingInv) + +approx = method(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) + +approx.setupApprox() +if show_sample: + approx.plotApprox(mutar, name = 'u_app') + approx.plotHF(mutar, name = 'u_HF') + approx.plotErr(mutar, name = 'err') + approx.plotRes(mutar, name = 'res') + appErr, solNorm = approx.normErr(mutar), approx.normHF(mutar) + resNorm, RHSNorm = approx.normRes(mutar), approx.normRHS(mutar) + 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))) + +if algo == "rational": + from plot_zero_set import plotZeroSet + plotZeroSet(murange, murangeEff, approx, mu0, + 200, [2., 1.], clip = clip) \ No newline at end of file diff --git a/examples/2d/pod/square_simplified_pod_hermite.py b/examples/2d/pod/square_pod_hermite.py similarity index 58% copy from examples/2d/pod/square_simplified_pod_hermite.py copy to examples/2d/pod/square_pod_hermite.py index 453cf0c..9fc5401 100644 --- a/examples/2d/pod/square_simplified_pod_hermite.py +++ b/examples/2d/pod/square_pod_hermite.py @@ -1,107 +1,100 @@ import numpy as np from rrompy.hfengines.linear_problem.bidimensional import \ - HelmholtzSquareSimplifiedDomainProblemEngine as HSSDPE + HelmholtzSquareDomainProblemEngine as HSDPE from rrompy.reduction_methods.centered import RationalPade as RP from rrompy.reduction_methods.distributed import RationalInterpolant as RI from rrompy.reduction_methods.centered import RBCentered as RBC from rrompy.reduction_methods.distributed import RBDistributed as RBD from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, RandomSampler as RS, ManualSampler as MS) verb = 0 -size = 2 +size = 1 +show_sample = False MN = 4 R = (MN + 2) * (MN + 1) // 2 S0 = [3] * 2 S = [25] assert R < np.prod(S) samples = "centered" samples = "distributed" algo = "rational" #algo = "RB" sampling = "quadrature" -#sampling = "random" +sampling = "random" if size == 1: # small mu0 = [4 ** .5, 1.5 ** .5] mutar = [5 ** .5, 1.75 ** .5] murange = [[2 ** .5, 1. ** .5], [6 ** .5, 2. ** .5]] elif size == 2: # medium mu0 = [4 ** .5, 1.75 ** .5] mutar = [4.5 ** .5, 1.25 ** .5] murange = [[1 ** .5, 1. ** .5], [7 ** .5, 2.5 ** .5]] elif size == 3: # large mu0 = [6 ** .5, 4 ** .5] mutar = [2 ** .5, 2.5 ** .5] murange = [[0 ** .5, 2 ** .5], [12 ** .5, 6 ** .5]] elif size == 4: # crowded mu0 = [10 ** .5, 2 ** .5] mutar = [9 ** .5, 2.25 ** .5] murange = [[8 ** .5, 1.5 ** .5], [12 ** .5, 2.5 ** .5]] -solver = HSSDPE(kappa = 2.5, theta = np.pi / 3, mu0 = mu0, n = 20, - verbosity = verb) +aEff = 1.25 +bEff = 1. - aEff +murangeEff = [[(aEff*murange[0][0]**2. + bEff*murange[1][0]**2.) ** .5, + (aEff*murange[0][1]**2. + bEff*murange[1][1]**2.) ** .5], + [(aEff*murange[1][0]**2. + bEff*murange[0][0]**2.) ** .5, + (aEff*murange[1][1]**2. + bEff*murange[0][1]**2.) ** .5]] + +solver = HSDPE(kappa = 2.5, theta = np.pi / 3, mu0 = mu0, n = 20, + verbosity = verb) -rescaling = [lambda x: np.power(x, 2.)] * 2 -rescalingInv = [lambda x: np.power(x, .5)] * 2 +rescaling = [lambda x: np.power(x, 2.), lambda x: x] +rescalingInv = [lambda x: np.power(x, .5), lambda x: x] if algo == "rational": params = {'N':MN, 'M':MN, 'S':S, 'POD':True} if samples == "distributed": params['polybasis'] = "CHEBYSHEV" method = RI else: method = RP else: #if algo == "RB": params = {'R':R, 'S':S, 'POD':True} if samples == "distributed": method = RBD else: method = RBC if samples == "distributed": if sampling == "quadrature": sampler0 = QS(murange, "CHEBYSHEV", scaling = rescaling, scalingInv = rescalingInv) else: # if sampling == "random": sampler0 = RS(murange, "SOBOL", scaling = rescaling, scalingInv = rescalingInv) S0 = np.prod(S0) params['sampler'] = MS(murange, points = sampler0.generatePoints(S0)) approx = method(solver, mu0 = mu0, approxParameters = params, verbosity = verb) approx.setupApprox() -approx.plotApprox(mutar, name = 'u_app') -approx.plotHF(mutar, name = 'u_HF') -approx.plotErr(mutar, name = 'err') -approx.plotRes(mutar, name = 'res') -appErr, solNorm = approx.normErr(mutar), approx.normHF(mutar) -resNorm, RHSNorm = approx.normRes(mutar), approx.normRHS(mutar) -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))) +if show_sample: + approx.plotApprox(mutar, name = 'u_app') + approx.plotHF(mutar, name = 'u_HF') + approx.plotErr(mutar, name = 'err') + approx.plotRes(mutar, name = 'res') + appErr, solNorm = approx.normErr(mutar), approx.normHF(mutar) + resNorm, RHSNorm = approx.normRes(mutar), approx.normRHS(mutar) + 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))) if algo == "rational": - from matplotlib import pyplot as plt - mu1 = np.linspace(murange[0][0]**2, murange[1][0]**2, 100) - mu2 = np.linspace(murange[0][1]**2, murange[1][1]**2, 100) - mu1s = np.power(mu1, .5) - mu2s = np.power(mu2, .5) - Mu1, Mu2 = np.meshgrid(mu1, mu2) - mus = [(m1, m2) for m2 in mu2s for m1 in mu1s] - Z = approx.trainedModel.getQVal(mus).reshape(Mu1.shape) - plt.figure(figsize = (15, 7)) - p = plt.contourf(Mu1, Mu2, np.log(np.abs(Z)), 50) - plt.contour(Mu1, Mu2, np.real(Z), [0.], linestyles = 'dashed') - plt.contour(Mu1, Mu2, np.imag(Z), [0.], linewidths = 1, - linestyles = 'dotted') - plt.plot(approx.mus(0) ** 2, approx.mus(1) ** 2, 'kx') - plt.colorbar(p) - plt.grid() - plt.show() - \ No newline at end of file + from plot_zero_set import plotZeroSet + plotZeroSet(murange, murangeEff, approx, mu0, 200, [2., 2.]) diff --git a/examples/2d/pod/square_simplified_pod.py b/examples/2d/pod/square_simplified_pod.py index e8c47bf..bd0d439 100644 --- a/examples/2d/pod/square_simplified_pod.py +++ b/examples/2d/pod/square_simplified_pod.py @@ -1,106 +1,138 @@ import numpy as np from rrompy.hfengines.linear_problem.bidimensional import \ HelmholtzSquareSimplifiedDomainProblemEngine as HSSDPE from rrompy.reduction_methods.centered import RationalPade as RP from rrompy.reduction_methods.distributed import RationalInterpolant as RI from rrompy.reduction_methods.centered import RBCentered as RBC from rrompy.reduction_methods.distributed import RBDistributed as RBD from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, + QuadratureSamplerTotal as QST, + ManualSampler as MS, RandomSampler as RS) -verb = 0 -size = 2 +verb = 5 +size = 1 +show_sample = False -MN = 4 +MN = 5 R = (MN + 2) * (MN + 1) // 2 -S = [5] * 2 -assert R < np.prod(S) +S = [int(np.ceil(R ** .5))] * 2 samples = "centered" +samples = "centered_fake" samples = "distributed" algo = "rational" #algo = "RB" sampling = "quadrature" +sampling = "quadrature_total" sampling = "random" if size == 1: # small mu0 = [4 ** .5, 1.5 ** .5] mutar = [5 ** .5, 1.75 ** .5] murange = [[2 ** .5, 1. ** .5], [6 ** .5, 2. ** .5]] elif size == 2: # medium mu0 = [4 ** .5, 1.75 ** .5] mutar = [5 ** .5, 1.25 ** .5] murange = [[1 ** .5, 1. ** .5], [7 ** .5, 2.5 ** .5]] -elif size == 3: # large +elif size == 3: # fat mu0 = [6 ** .5, 4 ** .5] mutar = [2 ** .5, 2.5 ** .5] murange = [[0 ** .5, 2 ** .5], [12 ** .5, 6 ** .5]] elif size == 4: # crowded mu0 = [10 ** .5, 2 ** .5] mutar = [9 ** .5, 2.25 ** .5] murange = [[8 ** .5, 1.5 ** .5], [12 ** .5, 2.5 ** .5]] +elif size == 5: # tall + mu0 = [11 ** .5, 2.25 ** .5] + mutar = [10.5 ** .5, 2.5 ** .5] + murange = [[10 ** .5, 1.5 ** .5], [12 ** .5, 3 ** .5]] +elif size == 6: # taller + mu0 = [11 ** .5, 2.25 ** .5] + mutar = [10.5 ** .5, 2.5 ** .5] + murange = [[10 ** .5, 1.25 ** .5], [12 ** .5, 3.25 ** .5]] +elif size == 7: # low + mu0 = [7 ** .5, .75 ** .5] + mutar = [8 ** .5, 1 ** .5] + murange = [[6 ** .5, .25 ** .5], [8 ** .5, 1.25 ** .5]] + +aEff = 1.25 +bEff = 1. - aEff +murangeEff = [[(aEff*murange[0][0]**2. + bEff*murange[1][0]**2.) ** .5, + (aEff*murange[0][1]**2. + bEff*murange[1][1]**2.) ** .5], + [(aEff*murange[1][0]**2. + bEff*murange[0][0]**2.) ** .5, + (aEff*murange[1][1]**2. + bEff*murange[0][1]**2.) ** .5]] solver = HSSDPE(kappa = 2.5, theta = np.pi / 3, mu0 = mu0, n = 20, verbosity = verb) rescaling = [lambda x: np.power(x, 2.)] * 2 rescalingInv = [lambda x: np.power(x, .5)] * 2 if algo == "rational": params = {'N':MN, 'M':MN, 'S':S, 'POD':True} if samples == "distributed": params['polybasis'] = "CHEBYSHEV" + params['polybasis'] = "LEGENDRE" + params['polybasis'] = "MONOMIAL" + params['E'] = MN + method = RI + elif samples == "centered_fake": + params['polybasis'] = "MONOMIAL" + params['S'] = R method = RI else: - params['S'] = np.prod(params['S']) + params['S'] = R method = RP else: #if algo == "RB": params = {'R':R, 'S':S, 'POD':True} if samples == "distributed": method = RBD + elif samples == "centered_fake": + params['S'] = R + method = RBD else: - params['S'] = np.prod(params['S']) + params['S'] = R method = RBC if samples == "distributed": if sampling == "quadrature": params['sampler'] = QS(murange, "CHEBYSHEV", scaling = rescaling, scalingInv = rescalingInv) +# params['sampler'] = QS(murange, "GAUSSLEGENDRE", scaling = rescaling, +# scalingInv = rescalingInv) +# params['sampler'] = QS(murange, "UNIFORM", scaling = rescaling, +# scalingInv = rescalingInv) + params['S'] = [max(j, MN + 1) for j in params['S']] + elif sampling == "quadrature_total": + params['sampler'] = QST(murange, "CHEBYSHEV", scaling = rescaling, + scalingInv = rescalingInv) + params['S'] = R else: # if sampling == "random": - params['sampler'] = RS(murange, "SOBOL", scaling = rescaling, + params['sampler'] = RS(murange, "HALTON", scaling = rescaling, scalingInv = rescalingInv) - params['S'] = np.prod(params['S']) + params['S'] = R + +elif samples == "centered_fake": + params['sampler'] = MS(murange, points = [mu0], scaling = rescaling, + scalingInv = rescalingInv) approx = method(solver, mu0 = mu0, approxParameters = params, verbosity = verb) approx.setupApprox() -approx.plotApprox(mutar, name = 'u_app') -approx.plotHF(mutar, name = 'u_HF') -approx.plotErr(mutar, name = 'err') -approx.plotRes(mutar, name = 'res') -appErr, solNorm = approx.normErr(mutar), approx.normHF(mutar) -resNorm, RHSNorm = approx.normRes(mutar), approx.normRHS(mutar) -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))) +if show_sample: + approx.plotApprox(mutar, name = 'u_app') + approx.plotHF(mutar, name = 'u_HF') + approx.plotErr(mutar, name = 'err') + approx.plotRes(mutar, name = 'res') + appErr, solNorm = approx.normErr(mutar), approx.normHF(mutar) + resNorm, RHSNorm = approx.normRes(mutar), approx.normRHS(mutar) + 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))) if algo == "rational": - from matplotlib import pyplot as plt - mu1 = np.linspace(murange[0][0]**2, murange[1][0]**2, 100) - mu2 = np.linspace(murange[0][1]**2, murange[1][1]**2, 100) - mu1s = np.power(mu1, .5) - mu2s = np.power(mu2, .5) - Mu1, Mu2 = np.meshgrid(mu1, mu2) - mus = [(m1, m2) for m2 in mu2s for m1 in mu1s] - Z = approx.trainedModel.getQVal(mus).reshape(Mu1.shape) - plt.figure(figsize = (15, 7)) - p = plt.contourf(Mu1, Mu2, np.log(np.abs(Z)), 50) - plt.contour(Mu1, Mu2, np.real(Z), [0.], linestyles = 'dashed') - plt.contour(Mu1, Mu2, np.imag(Z), [0.], linewidths = 1, - linestyles = 'dotted') - plt.plot(approx.mus(0) ** 2, approx.mus(1) ** 2, 'kx') - plt.colorbar(p) - plt.grid() - plt.show() + from plot_zero_set import plotZeroSet + plotZeroSet(murange, murangeEff, approx, mu0, 200, [2., 2.]) \ No newline at end of file diff --git a/examples/2d/pod/square_simplified_pod_hermite.py b/examples/2d/pod/square_simplified_pod_hermite.py index 453cf0c..ce63c07 100644 --- a/examples/2d/pod/square_simplified_pod_hermite.py +++ b/examples/2d/pod/square_simplified_pod_hermite.py @@ -1,107 +1,100 @@ import numpy as np from rrompy.hfengines.linear_problem.bidimensional import \ HelmholtzSquareSimplifiedDomainProblemEngine as HSSDPE from rrompy.reduction_methods.centered import RationalPade as RP from rrompy.reduction_methods.distributed import RationalInterpolant as RI from rrompy.reduction_methods.centered import RBCentered as RBC from rrompy.reduction_methods.distributed import RBDistributed as RBD from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, RandomSampler as RS, ManualSampler as MS) verb = 0 -size = 2 +size = 1 +show_sample = False MN = 4 R = (MN + 2) * (MN + 1) // 2 S0 = [3] * 2 S = [25] assert R < np.prod(S) samples = "centered" samples = "distributed" algo = "rational" #algo = "RB" sampling = "quadrature" #sampling = "random" if size == 1: # small mu0 = [4 ** .5, 1.5 ** .5] mutar = [5 ** .5, 1.75 ** .5] murange = [[2 ** .5, 1. ** .5], [6 ** .5, 2. ** .5]] elif size == 2: # medium mu0 = [4 ** .5, 1.75 ** .5] mutar = [4.5 ** .5, 1.25 ** .5] murange = [[1 ** .5, 1. ** .5], [7 ** .5, 2.5 ** .5]] elif size == 3: # large mu0 = [6 ** .5, 4 ** .5] mutar = [2 ** .5, 2.5 ** .5] murange = [[0 ** .5, 2 ** .5], [12 ** .5, 6 ** .5]] elif size == 4: # crowded mu0 = [10 ** .5, 2 ** .5] mutar = [9 ** .5, 2.25 ** .5] murange = [[8 ** .5, 1.5 ** .5], [12 ** .5, 2.5 ** .5]] +aEff = 1.25 +bEff = 1. - aEff +murangeEff = [[(aEff*murange[0][0]**2. + bEff*murange[1][0]**2.) ** .5, + (aEff*murange[0][1]**2. + bEff*murange[1][1]**2.) ** .5], + [(aEff*murange[1][0]**2. + bEff*murange[0][0]**2.) ** .5, + (aEff*murange[1][1]**2. + bEff*murange[0][1]**2.) ** .5]] + solver = HSSDPE(kappa = 2.5, theta = np.pi / 3, mu0 = mu0, n = 20, verbosity = verb) rescaling = [lambda x: np.power(x, 2.)] * 2 rescalingInv = [lambda x: np.power(x, .5)] * 2 if algo == "rational": params = {'N':MN, 'M':MN, 'S':S, 'POD':True} if samples == "distributed": params['polybasis'] = "CHEBYSHEV" method = RI else: method = RP else: #if algo == "RB": params = {'R':R, 'S':S, 'POD':True} if samples == "distributed": method = RBD else: method = RBC if samples == "distributed": if sampling == "quadrature": sampler0 = QS(murange, "CHEBYSHEV", scaling = rescaling, scalingInv = rescalingInv) else: # if sampling == "random": sampler0 = RS(murange, "SOBOL", scaling = rescaling, scalingInv = rescalingInv) S0 = np.prod(S0) params['sampler'] = MS(murange, points = sampler0.generatePoints(S0)) approx = method(solver, mu0 = mu0, approxParameters = params, verbosity = verb) approx.setupApprox() -approx.plotApprox(mutar, name = 'u_app') -approx.plotHF(mutar, name = 'u_HF') -approx.plotErr(mutar, name = 'err') -approx.plotRes(mutar, name = 'res') -appErr, solNorm = approx.normErr(mutar), approx.normHF(mutar) -resNorm, RHSNorm = approx.normRes(mutar), approx.normRHS(mutar) -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))) +if show_sample: + approx.plotApprox(mutar, name = 'u_app') + approx.plotHF(mutar, name = 'u_HF') + approx.plotErr(mutar, name = 'err') + approx.plotRes(mutar, name = 'res') + appErr, solNorm = approx.normErr(mutar), approx.normHF(mutar) + resNorm, RHSNorm = approx.normRes(mutar), approx.normRHS(mutar) + 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))) if algo == "rational": - from matplotlib import pyplot as plt - mu1 = np.linspace(murange[0][0]**2, murange[1][0]**2, 100) - mu2 = np.linspace(murange[0][1]**2, murange[1][1]**2, 100) - mu1s = np.power(mu1, .5) - mu2s = np.power(mu2, .5) - Mu1, Mu2 = np.meshgrid(mu1, mu2) - mus = [(m1, m2) for m2 in mu2s for m1 in mu1s] - Z = approx.trainedModel.getQVal(mus).reshape(Mu1.shape) - plt.figure(figsize = (15, 7)) - p = plt.contourf(Mu1, Mu2, np.log(np.abs(Z)), 50) - plt.contour(Mu1, Mu2, np.real(Z), [0.], linestyles = 'dashed') - plt.contour(Mu1, Mu2, np.imag(Z), [0.], linewidths = 1, - linestyles = 'dotted') - plt.plot(approx.mus(0) ** 2, approx.mus(1) ** 2, 'kx') - plt.colorbar(p) - plt.grid() - plt.show() - \ No newline at end of file + from plot_zero_set import plotZeroSet + plotZeroSet(murange, murangeEff, approx, mu0, 200, [2., 2.]) diff --git a/rrompy/hfengines/base/matrix_engine_base.py b/rrompy/hfengines/base/matrix_engine_base.py index cb3e2e6..664e74e 100644 --- a/rrompy/hfengines/base/matrix_engine_base.py +++ b/rrompy/hfengines/base/matrix_engine_base.py @@ -1,421 +1,441 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from abc import abstractmethod import numpy as np import scipy.sparse as scsp from matplotlib import pyplot as plt from copy import deepcopy as copy from rrompy.utilities.base.types import (Np1D, Np2D, ScOp, strLst, Tuple, List, DictAny, paramVal, paramList, sampList) from rrompy.utilities.base import (purgeList, getNewFilename, verbosityDepth, multibinom) from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert from rrompy.parameter import checkParameter, checkParameterList from rrompy.sampling import sampleList, emptySampleList from rrompy.solver import setupSolver __all__ = ['MatrixEngineBase'] class MatrixEngineBase: """ Generic solver for parametric matrix problems. Attributes: verbosity: Verbosity level. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. bsH: Numpy array representation of homogeneized bs. energyNormMatrix: Scipy sparse matrix representing inner product. """ - nAs, nbs = 1, 1 - def __init__(self, verbosity : int = 10, timestamp : bool = True): self.verbosity = verbosity self.timestamp = timestamp - self.resetAs() - self.resetbs() + self.nAs, self.nbs = 1, 1 self.setSolver("SPSOLVE", {"use_umfpack" : False}) self.npar = 0 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] != "__"] @property def npar(self): """Value of npar.""" return self._npar @npar.setter def npar(self, npar): nparOld = self._npar if hasattr(self, "_npar") else -1 if npar != nparOld: self.rescalingExp = [1.] * npar self._npar = npar + @property + def nAs(self): + """Value of nAs.""" + return self._nAs + @nAs.setter + def nAs(self, nAs): + self._nAs = nAs + self.resetAs() + + @property + def nbs(self): + """Value of nbs.""" + return self._nbs + @nbs.setter + def nbs(self, nbs): + self._nbs = nbs + self.resetbs() + @property def nbsH(self) -> int: return max(self.nbs, self.nAs) def spacedim(self): return self.As[0].shape[1] def checkParameter(self, mu:paramList): return checkParameter(mu, self.npar) def checkParameterList(self, mu:paramList): return checkParameterList(mu, self.npar) def buildEnergyNormForm(self): # eye """ Build sparse matrix (in CSR format) representative of scalar product. """ self.energyNormMatrix = np.eye(self.spacedim()) def innerProduct(self, u:Np2D, v:Np2D, onlyDiag : bool = False) -> Np2D: """Scalar product.""" if not hasattr(self, "energyNormMatrix"): if self.verbosity >= 20: verbosityDepth("INIT", "Assembling energy matrix.", timestamp = self.timestamp) self.buildEnergyNormForm() if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling energy matrix.", timestamp = self.timestamp) if not isinstance(u, (np.ndarray,)): u = u.data if not isinstance(v, (np.ndarray,)): v = v.data if onlyDiag: return np.sum(self.energyNormMatrix.dot(u) * v.conj(), axis = 0) return v.T.conj().dot(self.energyNormMatrix.dot(u)) def norm(self, u:Np2D) -> Np1D: return np.abs(self.innerProduct(u, u, onlyDiag = True)) ** .5 def checkAInBounds(self, derI : int = 0): """Check if derivative index is oob for operator of linear system.""" if derI < 0 or derI >= self.nAs: d = self.spacedim() return scsp.csr_matrix((np.zeros(0), np.zeros(0), np.zeros(d + 1)), shape = (d, d), dtype = np.complex) def checkbInBounds(self, derI : int = 0, homogeneized : bool = False): """Check if derivative index is oob for RHS of linear system.""" nbs = self.nbsH if homogeneized else self.nbs if derI < 0 or derI >= nbs: return np.zeros(self.spacedim(), dtype = np.complex) def resetAs(self): """Reset (derivatives of) operator of linear system.""" - self.resetbsH() self.setAs([None] * self.nAs) + if hasattr(self, "_nbs"): self.resetbsH() def resetbs(self): """Reset (derivatives of) RHS of linear system.""" - self.resetbsH() self.setbs([None] * self.nbs) + if hasattr(self, "_nAs"): self.resetbsH() def resetbsH(self): """Reset (derivatives of) homogeneized RHS of linear system.""" self.setbsH([None] * self.nbsH) def setAs(self, As:List[Np2D]): """Assign terms of operator of linear system.""" if len(As) != self.nAs: raise RROMPyException(("Expected number {} of terms of As not " "matching given list length {}.").format(self.nAs, len(As))) self.As = [copy(A) for A in As] def setbs(self, bs:List[Np1D]): """Assign terms of RHS of linear system.""" if len(bs) != self.nbs: raise RROMPyException(("Expected number {} of terms of bs not " "matching given list length {}.").format(self.nbs, len(bs))) self.bs = [copy(b) for b in bs] def setbsH(self, bsH:List[Np1D]): """Assign terms of homogeneized RHS of linear system.""" if len(bsH) != self.nbsH: raise RROMPyException(("Expected number {} of terms of bsH not " "matching given list length {}.").format(self.nbsH, len(bsH))) self.bsH = [copy(bH) for bH in bsH] def _assembleA(self, mu : paramVal = [], der : List[int] = 0, - derI : int = None) -> ScOp: + derI : int = None, muBase : paramVal = None) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) + if muBase is None: muBase = [0.] * self.npar + muBase = self.checkParameter(muBase) + if self.npar > 0: mu, muBase = mu[0], muBase[0] if not hasattr(der, "__len__"): der = [der] * self.npar if derI is None: derI = hashD(der) Anull = self.checkAInBounds(derI) if Anull is not None: return Anull - As0 = self.As[derI] + rExp = self.rescalingExp + A = copy(self.As[derI]) for j in range(derI + 1, self.nAs): derIdx = hashI(j, self.npar) diffIdx = [x - y for (x, y) in zip(derIdx, der)] if np.all([x >= 0 for x in diffIdx]): - coeff = multibinom(derIdx, diffIdx) - for d in range(self.npar): - coeff *= mu(0, d) ** (self.rescalingExp[d] * diffIdx[d]) - As0 = As0 + coeff * self.As[j] - return As0 + A = A + (np.prod((mu ** rExp - muBase ** rExp) ** diffIdx) + * multibinom(derIdx, diffIdx) * self.As[j]) + return A @abstractmethod def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """ Assemble terms of operator of linear system and return it (or its derivative) at a given parameter. """ if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) for j in range(derI, self.nAs): if self.As[j] is None: self.As[j] = 0. return self._assembleA(mu, der, derI) def affineLinearSystemA(self, mu : paramVal = []) -> List[Np2D]: """ Assemble affine blocks of operator of linear system (just linear blocks). """ As = [None] * self.nAs for j in range(self.nAs): As[j] = self.A(mu, hashI(j, self.npar)) return As def affineWeightsA(self, mu : paramVal = []) -> List[str]: """ Assemble affine blocks of operator of linear system (just affine weights). Stored as strings for the sake of pickling. """ mu = self.checkParameter(mu) lambdasA = ["1."] mu0Eff = mu ** self.rescalingExp for j in range(1, self.nAs): lambdasA += ["np.prod((mu ** ({1}) - [{0}]) ** ({2}))".format( ','.join([str(x) for x in mu0Eff[0]]), self.rescalingExp, hashI(j, self.npar))] return lambdasA def affineBlocksA(self, mu : paramVal = [])\ -> Tuple[List[Np2D], List[str]]: """Assemble affine blocks of operator of linear system.""" return self.affineLinearSystemA(mu), self.affineWeightsA(mu) def _assembleb(self, mu : paramVal = [], der : List[int] = 0, - derI : int = None, homogeneized : bool = False) -> ScOp: + derI : int = None, homogeneized : bool = False, + muBase : paramVal = None) -> ScOp: """Assemble (derivative of) (homogeneized) RHS of linear system.""" mu = self.checkParameter(mu) + if muBase is None: muBase = [0.] * self.npar + muBase = self.checkParameter(muBase) + if self.npar > 0: mu, muBase = mu[0], muBase[0] if not hasattr(der, "__len__"): der = [der] * self.npar if derI is None: derI = hashD(der) - bnull = self.checkbInBounds(derI) + bnull = self.checkbInBounds(derI, homogeneized) if bnull is not None: return bnull bs = self.bsH if homogeneized else self.bs - b = bs[derI] + rExp = self.rescalingExp + b = copy(bs[derI]) for j in range(derI + 1, len(bs)): derIdx = hashI(j, self.npar) diffIdx = [x - y for (x, y) in zip(derIdx, der)] if np.all([x >= 0 for x in diffIdx]): - coeff = multibinom(derIdx, diffIdx) - for d in range(self.npar): - coeff *= mu(0, d) ** (self.rescalingExp[d] * diffIdx[d]) - b = b + coeff * bs[j] + b = b + (np.prod((mu ** rExp - muBase ** rExp) ** diffIdx) + * multibinom(derIdx, diffIdx) * bs[j]) return b @abstractmethod def b(self, mu : paramVal = [], der : List[int] = 0, homogeneized : bool = False) -> Np1D: """ Assemble terms of (homogeneized) RHS of linear system and return it (or its derivative) at a given parameter. """ if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) if homogeneized: for j in range(derI, self.nbsH): if self.bsH[j] is None: self.bsH[j] = 0. else: for j in range(derI, self.nbs): if self.bs[j] is None: self.bs[j] = 0. return self._assembleb(mu, der, derI, homogeneized) def affineLinearSystemb(self, mu : paramVal = [], homogeneized : bool = False) -> List[Np1D]: """ Assemble affine blocks of RHS of linear system (just linear blocks). """ nbs = self.nbsH if homogeneized else self.nbs bs = [None] * nbs for j in range(nbs): bs[j] = self.b(mu, hashI(j, self.npar), homogeneized) return bs def affineWeightsb(self, mu : paramVal = [], homogeneized : bool = False) -> List[str]: """ Assemble affine blocks of RHS of linear system (just affine weights). Stored as strings for the sake of pickling. """ mu = self.checkParameter(mu) nbs = self.nbsH if homogeneized else self.nbs lambdasb = ["1."] mu0Eff = mu ** self.rescalingExp for j in range(1, nbs): lambdasb += ["np.prod((mu ** ({1}) - [{0}]) ** ({2}))".format( ','.join([str(x) for x in mu0Eff[0]]), self.rescalingExp, hashI(j, self.npar))] return lambdasb def affineBlocksb(self, mu : paramVal = [], homogeneized : bool = False)\ -> Tuple[List[Np1D], List[str]]: """Assemble affine blocks of RHS of linear system.""" return (self.affineLinearSystemb(mu, homogeneized), self.affineWeightsb(mu, homogeneized)) def setSolver(self, solverType:str, solverArgs : DictAny = {}): """Choose solver type and parameters.""" self._solver, self._solverArgs = setupSolver(solverType, solverArgs) def solve(self, mu : paramList = [], RHS : sampList = None, homogeneized : bool = False) -> sampList: """ Find solution of linear system. Args: mu: parameter value. RHS: RHS of linear system. If None, defaults to that of parametric system. Defaults to None. """ mu, _ = self.checkParameterList(mu) if mu.shape[0] == 0: mu.reset((1, self.npar), mu.dtype) if RHS is None: RHS = [self.b(m, homogeneized = homogeneized) for m in mu] RHS = sampleList(RHS) mult = 0 if len(RHS) == 1 else 1 RROMPyAssert(mult * (len(mu) - 1) + 1, len(RHS), "Sample size") sol = emptySampleList() for j in range(len(mu)): u = self._solver(self.A(mu[j]), RHS[mult * j], self._solverArgs) if j == 0: sol.reset((len(u), len(mu)), dtype = u.dtype) sol[j] = u return sol def residual(self, u:sampList, mu : paramList = [], homogeneized : bool = False) -> sampList: """ 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. """ mu, _ = self.checkParameterList(mu) if mu.shape[0] == 0: mu.reset((1, self.npar), mu.dtype) if u is not None: u = sampleList(u) mult = 0 if len(u) == 1 else 1 RROMPyAssert(mult * (len(mu) - 1) + 1, len(u), "Sample size") res = emptySampleList() for j in range(len(mu)): b = self.b(mu[j], homogeneized = homogeneized) if u is None: r = b else: r = b - self.A(mu[j]).dot(u[mult * j]) if j == 0: res.reset((len(r), len(mu)), dtype = r.dtype) res[j] = r return res def plot(self, u:Np1D, name : str = "u", save : str = None, what : strLst = 'all', saveFormat : str = "eps", saveDPI : int = 100, show : bool = True, **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. show(optional): Whether to show figure. Defaults to True. 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 idxs = np.arange(self.spacedim()) plt.figure(**figspecs) plt.jet() if 'ABS' in what: subplotcode = subplotcode + 1 plt.subplot(subplotcode) plt.plot(idxs, np.abs(u)) plt.title("|{0}|".format(name)) if 'PHASE' in what: subplotcode = subplotcode + 1 plt.subplot(subplotcode) plt.plot(idxs, np.angle(u)) plt.title("phase({0})".format(name)) if 'REAL' in what: subplotcode = subplotcode + 1 plt.subplot(subplotcode) plt.plot(idxs, np.real(u)) plt.title("Re({0})".format(name)) if 'IMAG' in what: subplotcode = subplotcode + 1 plt.subplot(subplotcode) plt.plot(idxs, np.imag(u)) plt.title("Im({0})".format(name)) if save is not None: save = save.strip() plt.savefig(getNewFilename("{}_fig_".format(save), saveFormat), format = saveFormat, dpi = saveDPI) if show: plt.show() plt.close() diff --git a/rrompy/hfengines/linear_problem/bidimensional/__init__.py b/rrompy/hfengines/linear_problem/bidimensional/__init__.py index 03b8e69..8e2d377 100644 --- a/rrompy/hfengines/linear_problem/bidimensional/__init__.py +++ b/rrompy/hfengines/linear_problem/bidimensional/__init__.py @@ -1,29 +1,32 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from .laplace_disk_gaussian_2 import LaplaceDiskGaussian2 from .helmholtz_square_simplified_domain_problem_engine import \ HelmholtzSquareSimplifiedDomainProblemEngine +from .helmholtz_square_domain_problem_engine import \ + HelmholtzSquareDomainProblemEngine __all__ = [ 'LaplaceDiskGaussian2', - 'HelmholtzSquareSimplifiedDomainProblemEngine' + 'HelmholtzSquareSimplifiedDomainProblemEngine', + 'HelmholtzSquareDomainProblemEngine' ] diff --git a/rrompy/hfengines/linear_problem/bidimensional/helmholtz_square_domain_problem_engine.py b/rrompy/hfengines/linear_problem/bidimensional/helmholtz_square_domain_problem_engine.py new file mode 100644 index 0000000..04a296b --- /dev/null +++ b/rrompy/hfengines/linear_problem/bidimensional/helmholtz_square_domain_problem_engine.py @@ -0,0 +1,229 @@ +# Copyright (C) 2018 by the RROMPy authors +# +# This file is part of RROMPy. +# +# RROMPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RROMPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with RROMPy. If not, see . +# + +import numpy as np +import scipy.sparse as scsp +from scipy.special import factorial as fact +import fenics as fen +from rrompy.utilities.base.types import (ScOp, List, Tuple, paramVal, Np1D, + FenExpr) +from rrompy.solver.fenics import fenZERO, fenONE +from rrompy.hfengines.linear_problem.helmholtz_problem_engine import ( + HelmholtzProblemEngine) +from rrompy.utilities.base import verbosityDepth +from rrompy.utilities.poly_fitting.polynomial import ( + hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) + +__all__ = ['HelmholtzSquareDomainProblemEngine'] + +class HelmholtzSquareDomainProblemEngine(HelmholtzProblemEngine): + """ + Solver for square Helmholtz problems with parametric laplacian. + - \dxx u - mu_2**2 \dyy u - mu_1**2 * u = f(mu_2) in \Omega = [0,\pi]^2 + u = 0 on \partial\Omega + """ + + def __init__(self, kappa:float, theta:float, n:int, + mu0 : paramVal = [12. ** .5, 1.], + degree_threshold : int = np.inf, verbosity : int = 10, + timestamp : bool = True): + super().__init__(mu0 = mu0, degree_threshold = degree_threshold, + verbosity = verbosity, timestamp = timestamp) + self.nAs, self.nbs = 6, 11 * 12 // 2 + self.npar = 2 + self.rescalingExp = [2., 1.] + pi = np.pi + mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), + 3 * n, 3 * n) + self.V = fen.FunctionSpace(mesh, "P", 1) + + c, s = np.cos(theta), np.sin(theta) + x, y = fen.SpatialCoordinate(mesh)[:] + self.forcingTerm = [fen.cos(kappa * (c * x + s / self.mu0(0, 1) * y)), + fen.sin(kappa * (c * x + s / self.mu0(0, 1) * y))] + self.forcingTermDer = kappa * s * y + + def getExtraFactorB(self, mu : paramVal = [], + derI : int = 0) -> Tuple[FenExpr, FenExpr]: + """Compute extra expression in RHS.""" + mu = self.checkParameter(mu) + def getPowMinusj(x, power): + powR = x ** power + powI = fenZERO + if power % 2 == 1: + powR, powI = powI, powR + if power % 4 > 1: + powR, powI = - powR, - powI + return powR, powI + if self.verbosity >= 25: + verbosityDepth("INIT", ("Assembling auxiliary expression for " + "forcing term derivative."), + timestamp = self.timestamp) + if derI == 0: return fenONE, fenZERO + coeffs = np.zeros(derI + 1) + coeffs[1] = - 2. + for j in range(2, derI + 1): + coeffs[1 :] = (-2. / j * coeffs[1 :] + - (3 - (1 + 2 * np.arange(derI)) / j) * coeffs[: -1]) + for j in range(derI): + powR, powI = getPowMinusj(self.forcingTermDer, derI - j) + mupBase = coeffs[j + 1] * mu(0, 1) ** (- 3 * derI + 2 * j) + mupR, mupI = np.real(mupBase), np.imag(mupBase) + if j == 0: + exprR = mupR * powR - mupI * powI + exprI = mupI * powR + mupR * powI + else: + exprR += mupR * powR - mupI * powI + exprI += mupI * powR + mupR * powI + if self.verbosity >= 25: + verbosityDepth("DEL", "Done assembling auxiliary expression.", + timestamp = self.timestamp) + return exprR, exprI + + def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: + """Assemble (derivative of) operator of linear system.""" + mu = self.checkParameter(mu) + if not hasattr(der, "__len__"): der = [der] * self.npar + derI = hashD(der) + self.autoSetDS() + if derI <= 0 and self.As[0] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A0.", + timestamp = self.timestamp) + DirichletBC0 = fen.DirichletBC(self.V, fenZERO, + self.DirichletBoundary) + a0Re = fen.dot(self.u.dx(0), self.v.dx(0)) * fen.dx + A0Re = fen.assemble(a0Re) + DirichletBC0.apply(A0Re) + A0ReMat = fen.as_backend_type(A0Re).mat() + A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() + self.As[0] = scsp.csr_matrix((A0Rev, A0Rec, A0Rer), + shape = A0ReMat.size, + dtype = np.complex) + if self.verbosity >= 20: + verbosityDepth("DEL", "Done assembling operator term.", + timestamp = self.timestamp) + if derI <= 1 and self.As[1] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A1.", + timestamp = self.timestamp) + DirichletBC0 = fen.DirichletBC(self.V, fenZERO, + self.DirichletBoundary) + nRe, nIm = self.refractionIndex + n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm + parsRe = self.iterReduceQuadratureDegree(zip([n2Re], + ["refractionIndexSquaredReal"])) + parsIm = self.iterReduceQuadratureDegree(zip([n2Im], + ["refractionIndexSquaredImag"])) + a1Re = - n2Re * fen.dot(self.u, self.v) * fen.dx + a1Im = - n2Im * fen.dot(self.u, self.v) * fen.dx + A1Re = fen.assemble(a1Re, form_compiler_parameters = parsRe) + A1Im = fen.assemble(a1Im, form_compiler_parameters = parsIm) + DirichletBC0.zero(A1Re) + DirichletBC0.zero(A1Im) + A1ReMat = fen.as_backend_type(A1Re).mat() + A1ImMat = fen.as_backend_type(A1Im).mat() + A1Rer, A1Rec, A1Rev = A1ReMat.getValuesCSR() + A1Imr, A1Imc, A1Imv = A1ImMat.getValuesCSR() + self.As[1] = (scsp.csr_matrix((A1Rev, A1Rec, A1Rer), + shape = A1ReMat.size) + + 1.j * scsp.csr_matrix((A1Imv, A1Imc, A1Imr), + shape = A1ImMat.size)) + if self.verbosity >= 20: + verbosityDepth("DEL", "Done assembling operator term.", + timestamp = self.timestamp) + if derI <= 2 and self.As[2] is None: self.As[2] = 0. + if derI <= 3 and self.As[3] is None: self.As[3] = 0. + if derI <= 4 and self.As[4] is None: self.As[4] = 0. + if derI <= 5 and self.As[5] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A5.", + timestamp = self.timestamp) + DirichletBC0 = fen.DirichletBC(self.V, fenZERO, + self.DirichletBoundary) + a5Re = fen.dot(self.u.dx(1), self.v.dx(1)) * fen.dx + A5Re = fen.assemble(a5Re) + DirichletBC0.zero(A5Re) + A5ReMat = fen.as_backend_type(A5Re).mat() + A5Rer, A5Rec, A5Rev = A5ReMat.getValuesCSR() + self.As[5] = scsp.csr_matrix((A5Rev, A5Rec, A5Rer), + shape = A5ReMat.size, + dtype = np.complex) + if self.verbosity >= 20: + verbosityDepth("DEL", "Done assembling operator term.", + timestamp = self.timestamp) + return self._assembleA(mu, der, derI) + + def b(self, mu : paramVal = [], der : List[int] = 0, + homogeneized : bool = False) -> Np1D: + """Assemble (derivative of) RHS of linear system.""" + mu = self.checkParameter(mu) + if not hasattr(der, "__len__"): der = [der] * self.npar + derI = hashD(der) + nbsTot = self.nbsH if homogeneized else self.nbs + bs = self.bsH if homogeneized else self.bs + if homogeneized and self.mu0 != self.mu0BC: + self.liftDirichletData(self.mu0) + for j in range(derI, nbsTot): + derH = hashI(j, self.npar) + if bs[j] is None: + if np.sum(derH) != derH[-1]: + if homogeneized: + self.bsH[j] = 0. + else: + self.bs[j] = 0. + continue + if self.verbosity >= 20: + verbosityDepth("INIT", ("Assembling forcing term " + "b{}.").format(j), + timestamp = self.timestamp) + if j == 0: + u0Re, u0Im = self.DirichletDatum + else: + u0Re, u0Im = fenZERO, fenZERO + if j < self.nbs: + fRe, fIm = self.forcingTerm + cRe, cIm = self.getExtraFactorB(self.mu0, derH[-1]) + cfRe, cfIm = cRe * fRe - cIm * fIm, cRe * fIm + cIm * fRe + else: + cfRe, cfIm = fenZERO, fenZERO + parsRe = self.iterReduceQuadratureDegree(zip([cfRe], + ["forcingTermDer{}Real".format(j)])) + parsIm = self.iterReduceQuadratureDegree(zip([cfIm], + ["forcingTermDer{}Imag".format(j)])) + L0Re = fen.dot(cfRe, self.v) * fen.dx + L0Im = fen.dot(cfIm, self.v) * fen.dx + b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) + b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) + DBCR = fen.DirichletBC(self.V, u0Re, self.DirichletBoundary) + DBCI = fen.DirichletBC(self.V, u0Im, self.DirichletBoundary) + DBCR.apply(b0Re) + DBCI.apply(b0Im) + b = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) + if homogeneized: + Ader = self.A(self.mu0, derH) + b -= Ader.dot(self.liftedDirichletDatum) + if homogeneized: + self.bsH[j] = b + else: + self.bs[j] = b + if self.verbosity >= 20: + verbosityDepth("DEL", "Done assembling forcing term.", + timestamp = self.timestamp) + return self._assembleb(mu, der, derI, homogeneized, self.mu0) + diff --git a/rrompy/hfengines/linear_problem/bidimensional/helmholtz_square_simplified_domain_problem_engine.py b/rrompy/hfengines/linear_problem/bidimensional/helmholtz_square_simplified_domain_problem_engine.py index 026f3b4..d310906 100644 --- a/rrompy/hfengines/linear_problem/bidimensional/helmholtz_square_simplified_domain_problem_engine.py +++ b/rrompy/hfengines/linear_problem/bidimensional/helmholtz_square_simplified_domain_problem_engine.py @@ -1,133 +1,129 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import scipy.sparse as scsp import fenics as fen from rrompy.utilities.base.types import ScOp, List, paramVal from rrompy.solver.fenics import fenZERO from rrompy.hfengines.linear_problem.helmholtz_problem_engine import ( HelmholtzProblemEngine) from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD) __all__ = ['HelmholtzSquareSimplifiedDomainProblemEngine'] class HelmholtzSquareSimplifiedDomainProblemEngine(HelmholtzProblemEngine): """ Solver for square Helmholtz problems with parametric laplacian. - \dxx u - mu_2**2 \dyy u - mu_1**2 * u = f in \Omega_mu = [0,\pi]^2 u = 0 on \partial\Omega """ - nAs, nbs = 3, 1 - def __init__(self, kappa:float, theta:float, n:int, mu0 : paramVal = [12. ** .5, 1.], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = mu0, degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nAs = 3 self.npar = 2 self.rescalingExp = [2., 2.] pi = np.pi - mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), n, n) - self.V = fen.FunctionSpace(mesh, "P", 3) + mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), + 3 * n, 3 * n) + self.V = fen.FunctionSpace(mesh, "P", 1) c, s = np.cos(theta), np.sin(theta) x, y = fen.SpatialCoordinate(mesh)[:] C = 16. / pi ** 4. - bR = C * 2 * (x * (pi - x) + y * (pi - y)) - bI = C * 2 * kappa * (c * (pi - 2 * x) * y * (pi - y) - + s * x * (pi - x) * (pi - 2 * y)) - wR = fen.cos(kappa * (c * x + s * y)) - wI = fen.sin(kappa * (c * x + s * y)) - self.forcingTerm = [bR * wR + bI * wI, bI * wR - bR * wI] + self.forcingTerm = [fen.cos(kappa * (c * x + s * y)), + fen.sin(kappa * (c * x + s * y))] def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) self.autoSetDS() if derI <= 0 and self.As[0] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a0Re = fen.dot(self.u.dx(0), self.v.dx(0)) * fen.dx A0Re = fen.assemble(a0Re) DirichletBC0.apply(A0Re) A0ReMat = fen.as_backend_type(A0Re).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() self.As[0] = scsp.csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size, dtype = np.complex) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 1 and self.As[1] is None: if self.verbosity >= 20: - verbosityDepth("INIT", "Assembling operator term A2.", + verbosityDepth("INIT", "Assembling operator term A1.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) nRe, nIm = self.refractionIndex n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm parsRe = self.iterReduceQuadratureDegree(zip([n2Re], ["refractionIndexSquaredReal"])) parsIm = self.iterReduceQuadratureDegree(zip([n2Im], ["refractionIndexSquaredImag"])) - a2Re = - n2Re * fen.dot(self.u, self.v) * fen.dx - a2Im = - n2Im * fen.dot(self.u, self.v) * fen.dx - A2Re = fen.assemble(a2Re, form_compiler_parameters = parsRe) - A2Im = fen.assemble(a2Im, form_compiler_parameters = parsIm) - DirichletBC0.zero(A2Re) - DirichletBC0.zero(A2Im) - A2ReMat = fen.as_backend_type(A2Re).mat() - A2ImMat = fen.as_backend_type(A2Im).mat() - A2Rer, A2Rec, A2Rev = A2ReMat.getValuesCSR() - A2Imr, A2Imc, A2Imv = A2ImMat.getValuesCSR() - self.As[1] = (scsp.csr_matrix((A2Rev, A2Rec, A2Rer), - shape = A2ReMat.size) - + 1.j * scsp.csr_matrix((A2Imv, A2Imc, A2Imr), - shape = A2ImMat.size)) + a1Re = - n2Re * fen.dot(self.u, self.v) * fen.dx + a1Im = - n2Im * fen.dot(self.u, self.v) * fen.dx + A1Re = fen.assemble(a1Re, form_compiler_parameters = parsRe) + A1Im = fen.assemble(a1Im, form_compiler_parameters = parsIm) + DirichletBC0.zero(A1Re) + DirichletBC0.zero(A1Im) + A1ReMat = fen.as_backend_type(A1Re).mat() + A1ImMat = fen.as_backend_type(A1Im).mat() + A1Rer, A1Rec, A1Rev = A1ReMat.getValuesCSR() + A1Imr, A1Imc, A1Imv = A1ImMat.getValuesCSR() + self.As[1] = (scsp.csr_matrix((A1Rev, A1Rec, A1Rer), + shape = A1ReMat.size) + + 1.j * scsp.csr_matrix((A1Imv, A1Imc, A1Imr), + shape = A1ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 2 and self.As[2] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A2.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a2Re = fen.dot(self.u.dx(1), self.v.dx(1)) * fen.dx - A2Re = fen.assemble(a2Re, form_compiler_parameters = parsRe) + A2Re = fen.assemble(a2Re) DirichletBC0.zero(A2Re) A2ReMat = fen.as_backend_type(A2Re).mat() A2Rer, A2Rec, A2Rev = A2ReMat.getValuesCSR() self.As[2] = scsp.csr_matrix((A2Rev, A2Rec, A2Rer), shape = A2ReMat.size, dtype = np.complex) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) diff --git a/rrompy/hfengines/linear_problem/bidimensional/laplace_disk_gaussian_2.py b/rrompy/hfengines/linear_problem/bidimensional/laplace_disk_gaussian_2.py index b27b96c..db0ed1d 100644 --- a/rrompy/hfengines/linear_problem/bidimensional/laplace_disk_gaussian_2.py +++ b/rrompy/hfengines/linear_problem/bidimensional/laplace_disk_gaussian_2.py @@ -1,128 +1,125 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import fenics as fen from rrompy.utilities.base.types import Np1D, Tuple, List, FenExpr, paramVal from rrompy.hfengines.linear_problem.laplace_disk_gaussian import ( LaplaceDiskGaussian) from rrompy.solver.fenics import fenZERO, fenONE from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) from rrompy.utilities.exception_manager import RROMPyException __all__ = ['LaplaceDiskGaussian2'] class LaplaceDiskGaussian2(LaplaceDiskGaussian): """ Solver for disk Laplace problems with parametric forcing term center. - \Delta u = C exp(-.5 * ||\cdot - (mu1, mu2)||^2) in \Omega = B(0, 5) u = 0 on \partial\Omega. """ - nAs, nbs = 1, 1 - def __init__(self, n:int, mu0 : paramVal = [0., 0.], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(n = n, mu0 = mu0, degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nbs = 1 self.npar = 2 def getForcingTerm(self, mu : paramVal = []) -> Tuple[FenExpr, FenExpr]: """Compute forcing term.""" mu = self.checkParameter(mu) if mu != self.forcingTermMu: if self.verbosity >= 25: verbosityDepth("INIT", ("Assembling base expression for " "forcing term."), timestamp = self.timestamp) x, y = fen.SpatialCoordinate(self.V.mesh())[:] C = np.exp(-.5 * (mu(0, 0) ** 2. + mu(0, 1) ** 2.)) CR, CI = np.real(C), np.imag(C) f0 = (2 * np.pi) ** -.5 * fen.exp(-.5 * (x ** 2. + y ** 2.)) muxR, muxI = np.real(mu(0, 0)), np.imag(mu(0, 0)) muyR, muyI = np.real(mu(0, 1)), np.imag(mu(0, 1)) f1R = fen.exp(muxR * x + muyR * y) * fen.cos(muxI * x + muyI * y) f1I = fen.exp(muxR * x + muyR * y) * fen.sin(muxI * x + muyI * y) self.forcingTerm = [f0 * (CR * f1R - CI * f1I), f0 * (CR * f1I + CI * f1R)] self.forcingTermMu = mu if self.verbosity >= 25: verbosityDepth("DEL", "Done assembling base expression.", timestamp = self.timestamp) return self.forcingTerm def computebsFactors(self): pass def getExtraFactorB(self, mu : paramVal = [], derI : int = 0) -> Tuple[FenExpr, FenExpr]: if derI == 0: return [fenONE, fenZERO] raise RROMPyException("Not implemented.") def b(self, mu : paramVal = [], der : List[int] = 0, homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) nbsTot = self.nbsH if homogeneized else self.nbs bs = self.bsH if homogeneized else self.bs if homogeneized and self.mu0 != self.mu0BC: self.liftDirichletData(self.mu0) for j in range(derI, nbsTot): if True or bs[j] is None: if self.verbosity >= 20: verbosityDepth("INIT", ("Assembling forcing term " "b{}.").format(j), timestamp = self.timestamp) if j < self.nbs: fRe, fIm = self.getForcingTerm(mu) cRe, cIm = self.getExtraFactorB(mu, j) cfRe, cfIm = cRe * fRe - cIm * fIm, cRe * fIm + cIm * fRe else: cfRe, cfIm = fenZERO, fenZERO parsRe = self.iterReduceQuadratureDegree(zip([cfRe], ["forcingTermDer{}Real".format(j)])) parsIm = self.iterReduceQuadratureDegree(zip([cfIm], ["forcingTermDer{}Imag".format(j)])) L0Re = fen.dot(cfRe, self.v) * fen.dx L0Im = fen.dot(cfIm, self.v) * fen.dx b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) - if homogeneized: - Ader = self.A(self.mu0, hashI(j)) - b0Re[:] -= np.real(Ader.dot(self.liftedDirichletDatum)) - b0Im[:] -= np.imag(Ader.dot(self.liftedDirichletDatum)) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) DirichletBC0.apply(b0Re) DirichletBC0.apply(b0Im) + b = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) + if homogeneized: + Ader = self.A(self.mu0, hashI(j, self.npar)) + b -= Ader.dot(self.liftedDirichletDatum) if homogeneized: - self.bsH[j] = np.array(b0Re[:] + 1.j * b0Im[:], - dtype = np.complex) + self.bsH[j] = b else: - self.bs[j] = np.array(b0Re[:] + 1.j * b0Im[:], - dtype = np.complex) + self.bs[j] = b if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) - return self._assembleb(mu - self.mu0, der, derI, homogeneized) + return self._assembleb(mu, der, derI, homogeneized, self.mu0) diff --git a/rrompy/hfengines/linear_problem/helmholtz_box_scattering_problem_engine.py b/rrompy/hfengines/linear_problem/helmholtz_box_scattering_problem_engine.py index 94dd364..20a0480 100644 --- a/rrompy/hfengines/linear_problem/helmholtz_box_scattering_problem_engine.py +++ b/rrompy/hfengines/linear_problem/helmholtz_box_scattering_problem_engine.py @@ -1,57 +1,58 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import fenics as fen from .scattering_problem_engine import ScatteringProblemEngine __all__ = ['HelmholtzBoxScatteringProblemEngine'] class HelmholtzBoxScatteringProblemEngine(ScatteringProblemEngine): """ Solver for scattering problem outside a box with parametric wavenumber. - \Delta u - omega^2 * n^2 * u = 0 in \Omega u = 0 on \Gamma_D \partial_nu - i k u = 0 on \Gamma_R with exact solution a transmitted plane wave. """ def __init__(self, R:float, kappa:float, theta:float, n:int, degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = [kappa], degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) import mshr scatterer = mshr.Polygon([fen.Point(-1, -.5), fen.Point(1, -.5), fen.Point(1, .5), fen.Point(.8, .5), fen.Point(.8, -.3), fen.Point(-.8, -.3), fen.Point(-.8, .5), fen.Point(-1, .5),]) - mesh = mshr.generate_mesh(mshr.Circle(fen.Point(0, 0), R)-scatterer, n) - self.V = fen.FunctionSpace(mesh, "P", 3) + mesh = mshr.generate_mesh(mshr.Circle(fen.Point(0, 0), R) - scatterer, + 3 * n) + self.V = fen.FunctionSpace(mesh, "P", 1) self.DirichletBoundary = (lambda x, on_boundary: on_boundary and (x[0]**2+x[1]**2)**.5 < .95 * R) self.RobinBoundary = "REST" c, s = np.cos(theta), np.sin(theta) x, y = fen.SpatialCoordinate(mesh)[:] u0R = - fen.cos(kappa * (c * x + s * y)) u0I = - fen.sin(kappa * (c * x + s * y)) self.DirichletDatum = [u0R, u0I] diff --git a/rrompy/hfengines/linear_problem/helmholtz_cavity_scattering_problem_engine.py b/rrompy/hfengines/linear_problem/helmholtz_cavity_scattering_problem_engine.py index 976eaa5..5efe8ec 100644 --- a/rrompy/hfengines/linear_problem/helmholtz_cavity_scattering_problem_engine.py +++ b/rrompy/hfengines/linear_problem/helmholtz_cavity_scattering_problem_engine.py @@ -1,58 +1,59 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import fenics as fen from .scattering_problem_engine import ScatteringProblemEngine __all__ = ['HelmholtzCavityScatteringProblemEngine'] class HelmholtzCavityScatteringProblemEngine(ScatteringProblemEngine): """ Solver for scattering problem inside a cavity with parametric wavenumber. - \Delta u - omega^2 * n^2 * u = 0 in \Omega u = 0 on \Gamma_D \partial_nu - i k u = 0 on \Gamma_R with exact solution a transmitted plane wave. """ def __init__(self, kappa:float, n:int, gamma : float = 0., signR : int = -1, degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = [kappa], degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) self.signR = signR pi = np.pi - mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), n, n) - self.V = fen.FunctionSpace(mesh, "P", 3) + mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), + 3 * n, 3 * n) + self.V = fen.FunctionSpace(mesh, "P", 1) self.RobinBoundary = (lambda x, on_boundary: on_boundary and np.isclose(x[1], np.pi)) self.DirichletBoundary = "REST" x, y = fen.SpatialCoordinate(mesh)[:] C = 4. / pi ** 4. bR = C * (2 * (x * (pi - x) + y * (2 * pi - y)) + (kappa * gamma) ** 2. * x * (pi - x) * y * (2 * pi - y)) bI = C * signR * 2 * kappa * (gamma * (pi - 2 * x) * y * (pi - y) + 2 * x * (pi - x) * (pi - y)) wR = fen.cos(kappa * signR * (gamma * x + y)) wI = fen.sin(kappa * signR * (gamma * x + y)) self.forcingTerm = [bR * wR + bI * wI, bI * wR - bR * wI] diff --git a/rrompy/hfengines/linear_problem/helmholtz_problem_engine.py b/rrompy/hfengines/linear_problem/helmholtz_problem_engine.py index 1ceeba4..a77dd46 100644 --- a/rrompy/hfengines/linear_problem/helmholtz_problem_engine.py +++ b/rrompy/hfengines/linear_problem/helmholtz_problem_engine.py @@ -1,161 +1,160 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import scipy.sparse as scsp import fenics as fen from .laplace_base_problem_engine import LaplaceBaseProblemEngine from rrompy.utilities.base.types import List, ScOp, paramVal from rrompy.solver.fenics import fenZERO, fenONE from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD) __all__ = ['HelmholtzProblemEngine'] class HelmholtzProblemEngine(LaplaceBaseProblemEngine): """ Solver for generic Helmholtz problems with parametric wavenumber. - \nabla \cdot (a \nabla u) - omega^2 * n**2 * u = f in \Omega u = u0 on \Gamma_D \partial_nu = g1 on \Gamma_N \partial_nu + h u = g2 on \Gamma_R Attributes: verbosity: Verbosity level. BCManager: Boundary condition manager. V: Real FE space. u: Generic trial functions for variational form evaluation. v: Generic test functions for variational form evaluation. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. bsH: Numpy array representation of homogeneized bs. energyNormMatrix: Scipy sparse matrix representing inner product over V. liftedDirichletDatum: Dofs of Dirichlet datum lifting. mu0BC: Mu value of last Dirichlet datum lifting. degree_threshold: Threshold for ufl expression interpolation degree. omega: Value of omega. diffusivity: Value of a. refractionIndex: Value of n. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. RobinDatumG: Value of g2. RobinDatumH: Value of h. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). A0: Scipy sparse array representation (in CSC format) of A0. A1: Scipy sparse array representation (in CSC format) of A1. b0: Numpy array representation of b0. dsToBeSet: Whether ds needs to be set. """ - nAs = 2 - def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = mu0, degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nAs = 2 self.rescalingExp = [2.] self.refractionIndex = fenONE @property def refractionIndex(self): """Value of n.""" return self._refractionIndex @refractionIndex.setter def refractionIndex(self, refractionIndex): self.resetAs() if not isinstance(refractionIndex, (list, tuple,)): refractionIndex = [refractionIndex, fenZERO] self._refractionIndex = refractionIndex def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) if derI <= 0 and self.As[0] is None: self.autoSetDS() if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) aRe, aIm = self.diffusivity hRe, hIm = self.RobinDatumH termNames = ["diffusivity", "RobinDatumH"] parsRe = self.iterReduceQuadratureDegree(zip( [aRe, hRe], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip( [aIm, hIm], [x + "Imag" for x in termNames])) a0Re = (aRe * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx + hRe * fen.dot(self.u, self.v) * self.ds(1)) a0Im = (aIm * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx + hIm * fen.dot(self.u, self.v) * self.ds(1)) A0Re = fen.assemble(a0Re, form_compiler_parameters = parsRe) A0Im = fen.assemble(a0Im, form_compiler_parameters = parsIm) DirichletBC0.apply(A0Re) DirichletBC0.zero(A0Im) A0ReMat = fen.as_backend_type(A0Re).mat() A0ImMat = fen.as_backend_type(A0Im).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() A0Imr, A0Imc, A0Imv = A0ImMat.getValuesCSR() self.As[0] = (scsp.csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size) + 1.j * scsp.csr_matrix((A0Imv, A0Imc, A0Imr), shape = A0ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 1 and self.As[1] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A1.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) nRe, nIm = self.refractionIndex n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm parsRe = self.iterReduceQuadratureDegree(zip([n2Re], ["refractionIndexSquaredReal"])) parsIm = self.iterReduceQuadratureDegree(zip([n2Im], ["refractionIndexSquaredImag"])) a1Re = - n2Re * fen.dot(self.u, self.v) * fen.dx a1Im = - n2Im * fen.dot(self.u, self.v) * fen.dx A1Re = fen.assemble(a1Re, form_compiler_parameters = parsRe) A1Im = fen.assemble(a1Im, form_compiler_parameters = parsIm) DirichletBC0.zero(A1Re) DirichletBC0.zero(A1Im) A1ReMat = fen.as_backend_type(A1Re).mat() A1ImMat = fen.as_backend_type(A1Im).mat() A1Rer, A1Rec, A1Rev = A1ReMat.getValuesCSR() A1Imr, A1Imc, A1Imv = A1ImMat.getValuesCSR() self.As[1] = (scsp.csr_matrix((A1Rev, A1Rec, A1Rer), shape = A1ReMat.size) + 1.j * scsp.csr_matrix((A1Imv, A1Imc, A1Imr), shape = A1ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) diff --git a/rrompy/hfengines/linear_problem/helmholtz_square_bubble_domain_problem_engine.py b/rrompy/hfengines/linear_problem/helmholtz_square_bubble_domain_problem_engine.py index d8f039d..b29b809 100644 --- a/rrompy/hfengines/linear_problem/helmholtz_square_bubble_domain_problem_engine.py +++ b/rrompy/hfengines/linear_problem/helmholtz_square_bubble_domain_problem_engine.py @@ -1,245 +1,245 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import scipy.sparse as scsp import fenics as fen from rrompy.utilities.base.types import (Np1D, ScOp, Tuple, List, FenExpr, paramVal) from rrompy.solver.fenics import fenZERO from .helmholtz_problem_engine import HelmholtzProblemEngine from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) __all__ = ['HelmholtzSquareBubbleDomainProblemEngine'] class HelmholtzSquareBubbleDomainProblemEngine(HelmholtzProblemEngine): """ Solver for square bubble Helmholtz problems with parametric domain heigth. - \Delta u - kappa^2 * u = f in \Omega_mu = [0,\pi] x [0,\mu\pi] u = 0 on \Gamma_mu = \partial\Omega_mu with exact solution square bubble times plane wave. """ - nAs, nbs = 3, 20 - def __init__(self, kappa:float, theta:float, n:int, mu0 : paramVal = [1.], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = mu0, degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nAs, self.nbs = 3, 20 self.kappa = kappa self.theta = theta self.forcingTermMu = np.nan - mesh = fen.RectangleMesh(fen.Point(0,0), fen.Point(np.pi,np.pi), n, n) - self.V = fen.FunctionSpace(mesh, "P", 3) + mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(np.pi, np.pi), + 3 * n, 3 * n) + self.V = fen.FunctionSpace(mesh, "P", 1) self.rescalingExp = [1.] def buildEnergyNormForm(self): # H1 """ Build sparse matrix (in CSR format) representative of scalar product. """ mudxM = np.abs(self.mu0(0, 0)) * (fen.dot(self.u.dx(0), self.v.dx(0)) + fen.dot(self.u, self.v)) imudy = 1. / np.abs(self.mu0(0, 0)) * fen.dot(self.u.dx(1), self.v.dx(1)) normMatFen = fen.assemble((mudxM + imudy) * fen.dx) normMat = fen.as_backend_type(normMatFen).mat() self.energyNormMatrix = scsp.csr_matrix(normMat.getValuesCSR()[::-1], shape = normMat.size) def getForcingTerm(self, mu : paramVal = []) -> Tuple[FenExpr, FenExpr]: """Compute forcing term.""" mu = self.checkParameter(mu) if mu != self.forcingTermMu: if self.verbosity >= 25: verbosityDepth("INIT", ("Assembling base expression for " "forcing term."), timestamp = self.timestamp) pi = np.pi c, s = np.cos(self.theta), np.sin(self.theta) x, y = fen.SpatialCoordinate(self.V.mesh())[:] muR, muI = np.real(mu(0, 0)), np.imag(mu(0, 0)) mu2R, mu2I = np.real(mu(0, 0) ** 2.), np.imag(mu(0, 0) ** 2.) C = 16. / pi ** 4. bR = C * (2 * (x * (pi - x) + y * (pi - y)) + (self.kappa * s) ** 2. * (mu2R - 1.) * x * (pi - x) * y * (pi - y)) bI = C * (2 * self.kappa * (c * (pi - 2 * x) * y * (pi - y) + s * x * (pi - x) * (pi - 2 * y)) + (self.kappa * s) ** 2. * mu2I * x * (pi - x) * y * (pi - y)) wR = (fen.cos(self.kappa * (c * x + s * muR * y)) * fen.exp(self.kappa * s * muI * y)) wI = (fen.sin(self.kappa * (c * x + s * muR * y)) * fen.exp(self.kappa * s * muI * y)) self.forcingTerm = [bR * wR + bI * wI, bI * wR - bR * wI] self.forcingTermMu = mu if self.verbosity >= 25: verbosityDepth("DEL", "Done assembling base expression.", timestamp = self.timestamp) return self.forcingTerm def getExtraFactorB(self, mu : paramVal = [], derI : int = 0) -> Tuple[FenExpr, FenExpr]: """Compute extra expression in RHS.""" mu = self.checkParameter(mu) def getPowMinusj(x, power): powR = x ** power powI = fenZERO if power % 2 == 1: powR, powI = powI, powR if (power + 3) % 4 < 2: powR, powI = - powR, - powI return powR, powI if self.verbosity >= 25: verbosityDepth("INIT", ("Assembling auxiliary expression for " "forcing term derivative."), timestamp = self.timestamp) from scipy.special import factorial as fact y = fen.SpatialCoordinate(self.V.mesh())[1] powR, powI = [(self.kappa * np.sin(self.theta)) ** derI * k\ for k in getPowMinusj(y, derI)] mu2R, mu2I = np.real(mu(0, 0) ** 2.), np.imag(mu(0, 0) ** 2.) exprR = mu2R * powR - mu2I * powI exprI = mu2I * powR + mu2R * powI if derI >= 1: muR, muI = np.real(2. * mu(0, 0)), np.imag(2. * mu(0, 0)) powR, powI = [(self.kappa * np.sin(self.theta)) ** (derI - 1) * k\ * derI for k in getPowMinusj(y, derI - 1)] exprR += muR * powR - muI * powI exprI += muI * powR + muR * powI if derI >= 2: powR, powI = [(self.kappa * np.sin(self.theta)) ** (derI - 2) * k\ * derI * (derI - 1) for k in getPowMinusj(y, derI - 2)] exprR += powR exprI += powI fac = fact(derI) if self.verbosity >= 25: verbosityDepth("DEL", "Done assembling auxiliary expression.", timestamp = self.timestamp) return [exprR / fac, exprI / fac] def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) self.autoSetDS() if derI <= 0 and self.As[0] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a0Re = fen.dot(self.u.dx(1), self.v.dx(1)) * fen.dx A0Re = fen.assemble(a0Re) DirichletBC0.apply(A0Re) A0ReMat = fen.as_backend_type(A0Re).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() self.As[0] = scsp.csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size, dtype = np.complex) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 1 and self.As[1] is None: self.As[1] = 0. if derI <= 2 and self.As[2] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A2.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) nRe, nIm = self.refractionIndex n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm k2Re, k2Im = np.real(self.omega ** 2), np.imag(self.omega ** 2) k2n2Re = k2Re * n2Re - k2Im * n2Im k2n2Im = k2Re * n2Im + k2Im * n2Re parsRe = self.iterReduceQuadratureDegree(zip([k2n2Re], ["kappaSquaredRefractionIndexSquaredReal"])) parsIm = self.iterReduceQuadratureDegree(zip([k2n2Im], ["kappaSquaredRefractionIndexSquaredImag"])) a2Re = (fen.dot(self.u.dx(0), self.v.dx(0)) - k2n2Re * fen.dot(self.u, self.v)) * fen.dx a2Im = - k2n2Im * fen.dot(self.u, self.v) * fen.dx A2Re = fen.assemble(a2Re, form_compiler_parameters = parsRe) A2Im = fen.assemble(a2Im, form_compiler_parameters = parsIm) DirichletBC0.zero(A2Re) DirichletBC0.zero(A2Im) A2ReMat = fen.as_backend_type(A2Re).mat() A2ImMat = fen.as_backend_type(A2Im).mat() A2Rer, A2Rec, A2Rev = A2ReMat.getValuesCSR() A2Imr, A2Imc, A2Imv = A2ImMat.getValuesCSR() self.As[2] = (scsp.csr_matrix((A2Rev, A2Rec, A2Rer), shape = A2ReMat.size) + 1.j * scsp.csr_matrix((A2Imv, A2Imc, A2Imr), shape = A2ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) def b(self, mu : paramVal = [], der : List[int] = 0, homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) nbsTot = self.nbsH if homogeneized else self.nbs bs = self.bsH if homogeneized else self.bs if homogeneized and self.mu0 != self.mu0BC: self.liftDirichletData(self.mu0) for j in range(derI, nbsTot): if bs[j] is None: if self.verbosity >= 20: verbosityDepth("INIT", ("Assembling forcing term " "b{}.").format(j), timestamp = self.timestamp) if j < self.nbs: fRe, fIm = self.getForcingTerm(self.mu0) cRe, cIm = self.getExtraFactorB(self.mu0, j) cfRe, cfIm = cRe * fRe - cIm * fIm, cRe * fIm + cIm * fRe else: cfRe, cfIm = fenZERO, fenZERO parsRe = self.iterReduceQuadratureDegree(zip([cfRe], ["forcingTermDer{}Real".format(j)])) parsIm = self.iterReduceQuadratureDegree(zip([cfIm], ["forcingTermDer{}Imag".format(j)])) L0Re = fen.dot(cfRe, self.v) * fen.dx L0Im = fen.dot(cfIm, self.v) * fen.dx b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) - if homogeneized: - Ader = self.A(self.mu0, hashI(j)) - b0Re[:] -= np.real(Ader.dot(self.liftedDirichletDatum)) - b0Im[:] -= np.imag(Ader.dot(self.liftedDirichletDatum)) + DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) DirichletBC0.apply(b0Re) DirichletBC0.apply(b0Im) + b = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) if homogeneized: - self.bsH[j] = np.array(b0Re[:] + 1.j * b0Im[:], - dtype = np.complex) + Ader = self.A(self.mu0, hashI(j, self.npar)) + b -= Ader.dot(self.liftedDirichletDatum) + if homogeneized: + self.bsH[j] = b else: - self.bs[j] = np.array(b0Re[:] + 1.j * b0Im[:], - dtype = np.complex) + self.bs[j] = b if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) - return self._assembleb(mu - self.mu0, der, derI, homogeneized) + return self._assembleb(mu, der, derI, homogeneized, self.mu0) + diff --git a/rrompy/hfengines/linear_problem/helmholtz_square_bubble_problem_engine.py b/rrompy/hfengines/linear_problem/helmholtz_square_bubble_problem_engine.py index 1fc3ca8..59b81c1 100644 --- a/rrompy/hfengines/linear_problem/helmholtz_square_bubble_problem_engine.py +++ b/rrompy/hfengines/linear_problem/helmholtz_square_bubble_problem_engine.py @@ -1,52 +1,53 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import fenics as fen from .helmholtz_problem_engine import HelmholtzProblemEngine __all__ = ['HelmholtzSquareBubbleProblemEngine'] class HelmholtzSquareBubbleProblemEngine(HelmholtzProblemEngine): """ Solver for square bubble Helmholtz problems with parametric wavenumber. - \Delta u - omega^2 * u = f in \Omega u = 0 on \Gamma_D with exact solution square bubble times plane wave. """ def __init__(self, kappa:float, theta:float, n:int, degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = [kappa], degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) pi = np.pi - mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), n, n) - self.V = fen.FunctionSpace(mesh, "P", 3) + mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), + 3 * n, 3 * n) + self.V = fen.FunctionSpace(mesh, "P", 1) c, s = np.cos(theta), np.sin(theta) x, y = fen.SpatialCoordinate(mesh)[:] C = 16. / pi ** 4. bR = C * 2 * (x * (pi - x) + y * (pi - y)) bI = C * 2 * kappa * (c * (pi - 2 * x) * y * (pi - y) + s * x * (pi - x) * (pi - 2 * y)) wR = fen.cos(kappa * (c * x + s * y)) wI = fen.sin(kappa * (c * x + s * y)) self.forcingTerm = [bR * wR + bI * wI, bI * wR - bR * wI] diff --git a/rrompy/hfengines/linear_problem/helmholtz_square_transmission_problem_engine.py b/rrompy/hfengines/linear_problem/helmholtz_square_transmission_problem_engine.py index fbc05fc..5726350 100644 --- a/rrompy/hfengines/linear_problem/helmholtz_square_transmission_problem_engine.py +++ b/rrompy/hfengines/linear_problem/helmholtz_square_transmission_problem_engine.py @@ -1,74 +1,74 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import fenics as fen import ufl from .helmholtz_problem_engine import HelmholtzProblemEngine __all__ = ['HelmholtzSquareTransmissionProblemEngine'] class HelmholtzSquareTransmissionProblemEngine(HelmholtzProblemEngine): """ Solver for square transmission Helmholtz problems with parametric wavenumber. - \Delta u - omega^2 * n^2 * u = 0 in \Omega u = 0 on \Gamma_D with exact solution a transmitted plane wave. """ def __init__(self, nT:float, nB:float, kappa:float, theta:float, n:int, degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = [kappa], degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) mesh = fen.RectangleMesh(fen.Point(-np.pi/2, -np.pi/2), - fen.Point(np.pi/2, np.pi/2), n, n) - self.V = fen.FunctionSpace(mesh, "P", 3) + fen.Point(np.pi/2, np.pi/2), 3 * n, 3 * n) + self.V = fen.FunctionSpace(mesh, "P", 1) dx, dy = np.cos(theta), np.sin(theta) Kx = kappa * nB * dx Ky = kappa * (nT**2. - (nB * dx)**2. + 0.j)**.5 T = 2 * kappa * nB * dy / (Ky + kappa * nB * dy) x, y = fen.SpatialCoordinate(mesh)[:] TR, TI = np.real(T), np.imag(T) if np.isclose(np.imag(Ky), 0.): u0RT = (TR * fen.cos(Kx * x + np.real(Ky) * y) - TI * fen.sin(Kx * x + np.real(Ky) * y)) u0IT = (TR * fen.sin(Kx * x + np.real(Ky) * y) + TI * fen.cos(Kx * x + np.real(Ky) * y)) else: u0RT = fen.exp(- np.imag(Ky) * y) * (TR * fen.cos(Kx * x) - TI * fen.sin(Kx * x)) u0IT = fen.exp(- np.imag(Ky) * y) * (TR * fen.sin(Kx * x) + TI * fen.cos(Kx * x)) u0RB = (fen.cos(kappa * nB * (dx * x + dy * y)) + (TR - 1) * fen.cos(kappa * nB * (dx*x - dy*y)) - TI * fen.sin(kappa * nB * (dx*x - dy*y))) u0IB = (fen.sin(kappa * nB * (dx * x + dy * y)) + (TR - 1) * fen.sin(kappa * nB * (dx*x - dy*y)) + TI * fen.cos(kappa * nB * (dx*x - dy*y))) u0R = ufl.conditional(ufl.ge(y, 0.), u0RT, u0RB) u0I = ufl.conditional(ufl.ge(y, 0.), u0IT, u0IB) self.refractionIndex = ufl.conditional(ufl.ge(y, 0.), fen.Constant(nT), fen.Constant(nB)) self.DirichletDatum = [u0R, u0I] diff --git a/rrompy/hfengines/linear_problem/laplace_base_problem_engine.py b/rrompy/hfengines/linear_problem/laplace_base_problem_engine.py index cbab415..b2bbe18 100644 --- a/rrompy/hfengines/linear_problem/laplace_base_problem_engine.py +++ b/rrompy/hfengines/linear_problem/laplace_base_problem_engine.py @@ -1,321 +1,320 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import scipy.sparse as scsp import fenics as fen from rrompy.hfengines.base.problem_engine_base import ProblemEngineBase from rrompy.utilities.base.types import Np1D, List, ScOp, paramVal, paramList from rrompy.solver.fenics import fenZERO, fenONE, H1NormMatrix from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( - hashDerivativeToIdx as hashD) + hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) from rrompy.parameter import checkParameter __all__ = ['LaplaceBaseProblemEngine'] class LaplaceBaseProblemEngine(ProblemEngineBase): """ Solver for generic Laplace problems. - \nabla \cdot (a \nabla u) = f in \Omega u = u0 on \Gamma_D \partial_nu = g1 on \Gamma_N \partial_nu + h u = g2 on \Gamma_R Attributes: verbosity: Verbosity level. BCManager: Boundary condition manager. V: Real FE space. u: Generic trial functions for variational form evaluation. v: Generic test functions for variational form evaluation. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. bsH: Numpy array representation of homogeneized bs. energyNormMatrix: Scipy sparse matrix representing inner product over V. liftedDirichletDatum: Dofs of Dirichlet datum lifting. mu0BC: Mu value of last Dirichlet datum lifting. degree_threshold: Threshold for ufl expression interpolation degree. omega: Value of omega. diffusivity: Value of a. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. RobinDatumG: Value of g2. RobinDatumH: Value of h. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). A0: Scipy sparse array representation (in CSC format) of A0. b0: Numpy array representation of b0. dsToBeSet: Whether ds needs to be set. """ def __init__(self, mu0 : paramVal = [], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) self.mu0 = checkParameter(mu0) self.npar = self.mu0.shape[1] self.omega = np.abs(self.mu0(0, 0)) if self.npar > 0 else 0. self.diffusivity = fenONE self.forcingTerm = fenZERO self.DirichletDatum = fenZERO self.NeumannDatum = fenZERO self.RobinDatumG = fenZERO self.RobinDatumH = fenZERO @property def V(self): """Value of V.""" return self._V @V.setter def V(self, V): ProblemEngineBase.V.fset(self, V) self.dsToBeSet = True @property def diffusivity(self): """Value of a.""" return self._diffusivity @diffusivity.setter def diffusivity(self, diffusivity): self.resetAs() if not isinstance(diffusivity, (list, tuple,)): diffusivity = [diffusivity, fenZERO] self._diffusivity = diffusivity @property def forcingTerm(self): """Value of f.""" return self._forcingTerm @forcingTerm.setter def forcingTerm(self, forcingTerm): self.resetbs() if not isinstance(forcingTerm, (list, tuple,)): forcingTerm = [forcingTerm, fenZERO] self._forcingTerm = forcingTerm @property def DirichletDatum(self): """Value of u0.""" return self._DirichletDatum @DirichletDatum.setter def DirichletDatum(self, DirichletDatum): self.resetbs() if not isinstance(DirichletDatum, (list, tuple,)): DirichletDatum = [DirichletDatum, fenZERO] self._DirichletDatum = DirichletDatum @property def NeumannDatum(self): """Value of g1.""" return self._NeumannDatum @NeumannDatum.setter def NeumannDatum(self, NeumannDatum): self.resetbs() if not isinstance(NeumannDatum, (list, tuple,)): NeumannDatum = [NeumannDatum, fenZERO] self._NeumannDatum = NeumannDatum @property def RobinDatumG(self): """Value of g2.""" return self._RobinDatumG @RobinDatumG.setter def RobinDatumG(self, RobinDatumG): self.resetbs() if not isinstance(RobinDatumG, (list, tuple,)): RobinDatumG = [RobinDatumG, fenZERO] self._RobinDatumG = RobinDatumG @property def RobinDatumH(self): """Value of h.""" return self._RobinDatumH @RobinDatumH.setter def RobinDatumH(self, RobinDatumH): self.resetAs() if not isinstance(RobinDatumH, (list, tuple,)): RobinDatumH = [RobinDatumH, fenZERO] self._RobinDatumH = RobinDatumH @property def DirichletBoundary(self): """Function handle to DirichletBoundary.""" return self.BCManager.DirichletBoundary @DirichletBoundary.setter def DirichletBoundary(self, DirichletBoundary): self.resetAs() self.resetbs() self.BCManager.DirichletBoundary = DirichletBoundary @property def NeumannBoundary(self): """Function handle to NeumannBoundary.""" return self.BCManager.NeumannBoundary @NeumannBoundary.setter def NeumannBoundary(self, NeumannBoundary): self.resetAs() self.resetbs() self.dsToBeSet = True self.BCManager.NeumannBoundary = NeumannBoundary @property def RobinBoundary(self): """Function handle to RobinBoundary.""" return self.BCManager.RobinBoundary @RobinBoundary.setter def RobinBoundary(self, RobinBoundary): self.resetAs() self.resetbs() self.dsToBeSet = True self.BCManager.RobinBoundary = RobinBoundary def autoSetDS(self): """Set FEniCS boundary measure based on boundary function handles.""" if self.dsToBeSet: if self.verbosity >= 20: verbosityDepth("INIT", "Initializing boundary measures.", timestamp = self.timestamp) mesh = self.V.mesh() NB = self.NeumannBoundary RB = self.RobinBoundary boundary_markers = fen.MeshFunction("size_t", mesh, mesh.topology().dim() - 1) NB.mark(boundary_markers, 0) RB.mark(boundary_markers, 1) self.ds = fen.Measure("ds", domain = mesh, subdomain_data = boundary_markers) self.dsToBeSet = False if self.verbosity >= 20: verbosityDepth("DEL", "Done initializing boundary measures.", timestamp = self.timestamp) def buildEnergyNormForm(self): """ Build sparse matrix (in CSR format) representative of scalar product. """ self.energyNormMatrix = H1NormMatrix(self.V, np.abs(self.omega)**2) def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) if derI <= 0 and self.As[0] is None: self.autoSetDS() if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) aRe, aIm = self.diffusivity hRe, hIm = self.RobinDatumH termNames = ["diffusivity", "RobinDatumH"] parsRe = self.iterReduceQuadratureDegree(zip([aRe, hRe], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip([aIm, hIm], [x + "Imag" for x in termNames])) a0Re = (aRe * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx + hRe * fen.dot(self.u, self.v) * self.ds(1)) a0Im = (aIm * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx + hIm * fen.dot(self.u, self.v) * self.ds(1)) A0Re = fen.assemble(a0Re, form_compiler_parameters = parsRe) A0Im = fen.assemble(a0Im, form_compiler_parameters = parsIm) DirichletBC0.apply(A0Re) DirichletBC0.zero(A0Im) A0ReMat = fen.as_backend_type(A0Re).mat() A0ImMat = fen.as_backend_type(A0Im).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() A0Imr, A0Imc, A0Imv = A0ImMat.getValuesCSR() self.As[0] = (scsp.csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size) + 1.j * scsp.csr_matrix((A0Imv, A0Imc, A0Imr), shape = A0ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) def b(self, mu : paramVal = [], der : List[int] = 0, homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) nbsTot = self.nbsH if homogeneized else self.nbs bs = self.bsH if homogeneized else self.bs if homogeneized and self.mu0 != self.mu0BC: self.liftDirichletData(self.mu0) for j in range(derI, nbsTot): if bs[j] is None: self.autoSetDS() if self.verbosity >= 20: verbosityDepth("INIT", ("Assembling forcing term " "b{}.").format(j), timestamp = self.timestamp) - if derI == 0: + termNames, terms = [], [] + if j == 0: + u0Re, u0Im = self.DirichletDatum fRe, fIm = self.forcingTerm g1Re, g1Im = self.NeumannDatum g2Re, g2Im = self.RobinDatumG + termNames += ["forcingTerm", "NeumannDatum", "RobinDatumG"] + terms += [[fRe, fIm], [g1Re, g1Im], [g2Re, g2Im]] else: + u0Re, u0Im = fenZERO, fenZERO fRe, fIm = fenZERO, fenZERO g1Re, g1Im = fenZERO, fenZERO g2Re, g2Im = fenZERO, fenZERO - termNames = ["forcingTerm", "NeumannDatum", "RobinDatumG"] - parsRe = self.iterReduceQuadratureDegree(zip( - [fRe, g1Re, g2Re], + if len(termNames) > 0: + parsRe = self.iterReduceQuadratureDegree(zip( + [term[0] for term in terms], [x + "Real" for x in termNames])) - parsIm = self.iterReduceQuadratureDegree(zip( - [fIm, g1Im, g2Im], + parsIm = self.iterReduceQuadratureDegree(zip( + [term[1] for term in terms], [x + "Imag" for x in termNames])) + else: + parsRe, parsIm = {}, {} L0Re = (fen.dot(fRe, self.v) * fen.dx + fen.dot(g1Re, self.v) * self.ds(0) + fen.dot(g2Re, self.v) * self.ds(1)) L0Im = (fen.dot(fIm, self.v) * fen.dx + fen.dot(g1Im, self.v) * self.ds(0) + fen.dot(g2Im, self.v) * self.ds(1)) b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) - if homogeneized: - Ader = self.A(self.mu0, der) - b0Re[:] -= np.real(Ader.dot(self.liftedDirichletDatum)) - b0Im[:] -= np.imag(Ader.dot(self.liftedDirichletDatum)) - DBCR = fen.DirichletBC(self.V, fenZERO, - self.DirichletBoundary) - DBCI = fen.DirichletBC(self.V, fenZERO, - self.DirichletBoundary) - else: - DBCR = fen.DirichletBC(self.V, self.DirichletDatum[0], - self.DirichletBoundary) - DBCI = fen.DirichletBC(self.V, self.DirichletDatum[1], - self.DirichletBoundary) + DBCR = fen.DirichletBC(self.V, u0Re, self.DirichletBoundary) + DBCI = fen.DirichletBC(self.V, u0Im, self.DirichletBoundary) DBCR.apply(b0Re) DBCI.apply(b0Im) b = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) + if homogeneized: + Ader = self.A(self.mu0, hashI(j, self.npar)) + b -= Ader.dot(self.liftedDirichletDatum) if homogeneized: self.bsH[j] = b else: self.bs[j] = b if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) - return self._assembleb(mu - self.mu0, der, derI, homogeneized) + return self._assembleb(mu, der, derI, homogeneized, self.mu0) diff --git a/rrompy/hfengines/linear_problem/laplace_disk_gaussian.py b/rrompy/hfengines/linear_problem/laplace_disk_gaussian.py index cbd6187..4da30a2 100644 --- a/rrompy/hfengines/linear_problem/laplace_disk_gaussian.py +++ b/rrompy/hfengines/linear_problem/laplace_disk_gaussian.py @@ -1,163 +1,160 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import fenics as fen from rrompy.utilities.base.types import Np1D, Tuple, FenExpr, paramVal from .laplace_base_problem_engine import LaplaceBaseProblemEngine from rrompy.solver.fenics import fenZERO, fenONE from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) __all__ = ['LaplaceDiskGaussian'] class LaplaceDiskGaussian(LaplaceBaseProblemEngine): """ Solver for disk Laplace problems with parametric forcing term center. - \Delta u = C exp(-.5 * ||\cdot - (mu, 0)||^2) in \Omega = B(0, 5) u = 0 on \partial\Omega. """ - nbs = 20 - def __init__(self, n:int, mu0 : paramVal = [0.], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = mu0, degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nbs = 20 self.computebsFactors() self.forcingTermMu = np.nan import mshr - mesh = mshr.generate_mesh(mshr.Circle(fen.Point(0., 0.), 5.), n) - self.V = fen.FunctionSpace(mesh, "P", 3) + mesh = mshr.generate_mesh(mshr.Circle(fen.Point(0., 0.), 5.), 3 * n) + self.V = fen.FunctionSpace(mesh, "P", 1) def getForcingTerm(self, mu : paramVal = []) -> Tuple[FenExpr, FenExpr]: """Compute forcing term.""" mu = self.checkParameter(mu) if mu != self.forcingTermMu: if self.verbosity >= 25: verbosityDepth("INIT", ("Assembling base expression for " "forcing term."), timestamp = self.timestamp) x, y = fen.SpatialCoordinate(self.V.mesh())[:] C = np.exp(-.5 * mu(0, 0) ** 2.) CR, CI = np.real(C), np.imag(C) f0 = (2 * np.pi) ** -.5 * fen.exp(-.5 * (x ** 2. + y ** 2.)) muR, muI = np.real(mu(0, 0)), np.imag(mu(0, 0)) f1R = fen.exp(muR * x) * fen.cos(muI * x) f1I = fen.exp(muR * x) * fen.sin(muI * x) self.forcingTerm = [f0 * (CR * f1R - CI * f1I), f0 * (CR * f1I + CI * f1R)] self.forcingTermMu = mu if self.verbosity >= 25: verbosityDepth("DEL", "Done assembling base expression.", timestamp = self.timestamp) return self.forcingTerm def computebsFactors(self): self.bsFactors = np.zeros((self.nbs, self.nbs), dtype = float) self.bsFactors[0, 0] = 1. self.bsFactors[1, 1] = 1. for j in range(2, self.nbs): l = (j + 1) % 2 + 1 J = np.arange(l, j + 1, 2) self.bsFactors[j, J] = self.bsFactors[j - 1, J - 1] if l == 2: l = 0 J = np.arange(l, j, 2) self.bsFactors[j, J] += np.multiply(- 1 - J, self.bsFactors[j - 1, J + 1]) self.bsFactors[j, l : j + 2 : 2] /= j def getExtraFactorB(self, mu : paramVal = [], derI : int = 0) -> Tuple[FenExpr, FenExpr]: """Compute extra expression in RHS.""" mu = self.checkParameter(mu) if self.verbosity >= 25: verbosityDepth("INIT", ("Assembling auxiliary expression for " "forcing term derivative."), timestamp = self.timestamp) muR, muI = np.real(mu(0, 0)), np.imag(mu(0, 0)) x = fen.SpatialCoordinate(self.V.mesh())[0] l = derI % 2 if l == 0: powR, powI = fenONE, fenZERO else: powR, powI = x - muR, fen.Constant(muI) exprR, exprI = [self.bsFactors[derI, l] * k for k in [powR, powI]] for j in range(l + 2, derI + 1, 2): for _ in range(2): powR, powI = (powR * (x - muR) - powI * muI, powR * muI + powI * (x - muR)) exprR += self.bsFactors[derI, j] * powR exprI += self.bsFactors[derI, j] * powI if self.verbosity >= 25: verbosityDepth("DEL", "Done assembling auxiliary expression.", timestamp = self.timestamp) return[exprR, exprI] def b(self, mu : paramVal = [], der : int = 0, homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) nbsTot = self.nbsH if homogeneized else self.nbs bs = self.bsH if homogeneized else self.bs if homogeneized and self.mu0 != self.mu0BC: self.liftDirichletData(self.mu0) for j in range(derI, nbsTot): if bs[j] is None: if self.verbosity >= 20: verbosityDepth("INIT", ("Assembling forcing term " "b{}.").format(j), timestamp = self.timestamp) if j < self.nbs: fRe, fIm = self.getForcingTerm(self.mu0) cRe, cIm = self.getExtraFactorB(self.mu0, j) cfRe, cfIm = cRe * fRe - cIm * fIm, cRe * fIm + cIm * fRe else: cfRe, cfIm = fenZERO, fenZERO parsRe = self.iterReduceQuadratureDegree(zip([cfRe], ["forcingTermDer{}Real".format(j)])) parsIm = self.iterReduceQuadratureDegree(zip([cfIm], ["forcingTermDer{}Imag".format(j)])) L0Re = fen.dot(cfRe, self.v) * fen.dx L0Im = fen.dot(cfIm, self.v) * fen.dx b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) - if homogeneized: - Ader = self.A(self.mu0, hashI(j)) - b0Re[:] -= np.real(Ader.dot(self.liftedDirichletDatum)) - b0Im[:] -= np.imag(Ader.dot(self.liftedDirichletDatum)) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) DirichletBC0.apply(b0Re) DirichletBC0.apply(b0Im) + b = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) + if homogeneized: + Ader = self.A(self.mu0, hashI(j, self.npar)) + b -= Ader.dot(self.liftedDirichletDatum) if homogeneized: - self.bsH[j] = np.array(b0Re[:] + 1.j * b0Im[:], - dtype = np.complex) + self.bsH[j] = b else: - self.bs[j] = np.array(b0Re[:] + 1.j * b0Im[:], - dtype = np.complex) + self.bs[j] = b if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) - return self._assembleb(mu - self.mu0, der, derI, homogeneized) + return self._assembleb(mu, der, derI, homogeneized, self.mu0) diff --git a/rrompy/hfengines/linear_problem/scattering_problem_engine.py b/rrompy/hfengines/linear_problem/scattering_problem_engine.py index 2a6b838..2b62cea 100644 --- a/rrompy/hfengines/linear_problem/scattering_problem_engine.py +++ b/rrompy/hfengines/linear_problem/scattering_problem_engine.py @@ -1,174 +1,174 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from numpy import inf import scipy.sparse as scsp import fenics as fen from rrompy.utilities.base.types import List, ScOp, paramVal from rrompy.solver.fenics import fenZERO from rrompy.utilities.base import verbosityDepth from .helmholtz_problem_engine import HelmholtzProblemEngine from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD) from rrompy.utilities.exception_manager import RROMPyWarning __all__ = ['ScatteringProblemEngine'] class ScatteringProblemEngine(HelmholtzProblemEngine): """ Solver for scattering problems with parametric wavenumber. - \nabla \cdot (a \nabla u) - omega^2 * n**2 * u = f in \Omega u = u0 on \Gamma_D \partial_nu = g1 on \Gamma_N \partial_nu +- i omega u = g2 on \Gamma_R Attributes: verbosity: Verbosity level. BCManager: Boundary condition manager. V: Real FE space. u: Generic trial functions for variational form evaluation. v: Generic test functions for variational form evaluation. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. bsH: Numpy array representation of homogeneized bs. energyNormMatrix: Scipy sparse matrix representing inner product over V. liftedDirichletDatum: Dofs of Dirichlet datum lifting. mu0BC: Mu value of last Dirichlet datum lifting. degree_threshold: Threshold for ufl expression interpolation degree. signR: Sign in ABC. omega: Value of omega. diffusivity: Value of a. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. RobinDatumG: Value of g2. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). A0: Scipy sparse array representation (in CSC format) of A0. A1: Scipy sparse array representation (in CSC format) of A1. A2: Scipy sparse array representation (in CSC format) of A2. b0: Numpy array representation of b0. dsToBeSet: Whether ds needs to be set. """ - nAs = 3 signR = - 1. def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = inf, verbosity : int = 10, timestamp : bool = True): self.silenceWarnings = True super().__init__(mu0 = mu0, degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) del self.silenceWarnings + self.nAs = 3 self.rescalingExp = [1.] @property def RobinDatumH(self): """Value of h.""" return self.signR * self.omega @RobinDatumH.setter def RobinDatumH(self, RobinDatumH): if not hasattr(self, "silenceWarnings"): RROMPyWarning(("Scattering problems do not allow changes of h. " "Ignoring assignment.")) return def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) if derI <= 0 and self.As[0] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) aRe, aIm = self.diffusivity parsRe = self.iterReduceQuadratureDegree(zip([aRe], ["diffusivityReal"])) parsIm = self.iterReduceQuadratureDegree(zip([aIm], ["diffusivityImag"])) a0Re = aRe * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx a0Im = aIm * fen.dot(fen.grad(self.u), fen.grad(self.v)) * fen.dx A0Re = fen.assemble(a0Re, form_compiler_parameters = parsRe) A0Im = fen.assemble(a0Im, form_compiler_parameters = parsIm) DirichletBC0.apply(A0Re) DirichletBC0.zero(A0Im) A0ReMat = fen.as_backend_type(A0Re).mat() A0ImMat = fen.as_backend_type(A0Im).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() A0Imr, A0Imc, A0Imv = A0ImMat.getValuesCSR() self.As[0] = (scsp.csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size) + 1.j * scsp.csr_matrix((A0Imv, A0Imc, A0Imr), shape = A0ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 1 and self.As[1] is None: self.autoSetDS() if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A1.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) a1 = fen.dot(self.u, self.v) * self.ds(1) A1 = fen.assemble(a1) DirichletBC0.zero(A1) A1Mat = fen.as_backend_type(A1).mat() A1r, A1c, A1v = A1Mat.getValuesCSR() self.As[1] = self.signR * 1.j * scsp.csr_matrix((A1v, A1c, A1r), shape = A1Mat.size) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 2 and self.As[2] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A2.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZERO, self.DirichletBoundary) nRe, nIm = self.refractionIndex n2Re, n2Im = nRe * nRe - nIm * nIm, 2 * nRe * nIm parsRe = self.iterReduceQuadratureDegree(zip([n2Re], ["refractionIndexSquaredReal"])) parsIm = self.iterReduceQuadratureDegree(zip([n2Im], ["refractionIndexSquaredImag"])) a2Re = - n2Re * fen.dot(self.u, self.v) * fen.dx a2Im = - n2Im * fen.dot(self.u, self.v) * fen.dx A2Re = fen.assemble(a2Re, form_compiler_parameters = parsRe) A2Im = fen.assemble(a2Im, form_compiler_parameters = parsIm) DirichletBC0.zero(A2Re) DirichletBC0.zero(A2Im) A2ReMat = fen.as_backend_type(A2Re).mat() A2ImMat = fen.as_backend_type(A2Im).mat() A2Rer, A2Rec, A2Rev = A2ReMat.getValuesCSR() A2Imr, A2Imc, A2Imv = A2ImMat.getValuesCSR() self.As[2] = (scsp.csr_matrix((A2Rev, A2Rec, A2Rer), shape = A2ReMat.size) + 1.j * scsp.csr_matrix((A2Imv, A2Imc, A2Imr), shape = A2ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) diff --git a/rrompy/hfengines/vector_linear_problem/bidimensional/linear_elasticity_beam_elasticity_constants.py b/rrompy/hfengines/vector_linear_problem/bidimensional/linear_elasticity_beam_elasticity_constants.py index cd4023a..e43a097 100644 --- a/rrompy/hfengines/vector_linear_problem/bidimensional/linear_elasticity_beam_elasticity_constants.py +++ b/rrompy/hfengines/vector_linear_problem/bidimensional/linear_elasticity_beam_elasticity_constants.py @@ -1,150 +1,149 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import scipy.sparse as scsp import fenics as fen from rrompy.hfengines.vector_linear_problem.\ linear_elasticity_beam_poisson_ratio import LinearElasticityBeamPoissonRatio from rrompy.solver.fenics import fenZEROS from rrompy.utilities.base.types import Np1D, List, ScOp, paramVal from rrompy.utilities.base import verbosityDepth from rrompy.utilities.exception_manager import RROMPyException __all__ = ['LinearElasticityBeamElasticityConstants'] class LinearElasticityBeamElasticityConstants( LinearElasticityBeamPoissonRatio): """ Solver for linear elasticity problem of a beam subject to its own weight, with parametric Joung modulus and Poisson's ratio. - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) = rho_ * g in \Omega u = 0 on \Gamma_D \partial_nu = 0 on \Gamma_N """ - nAs, nbs = 5, 4 - def __init__(self, n:int, rho_:float, g:float, E0:float, nu0:float, length:float, degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = [nu0, E0], degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nAs, self.nbs = 5, 4 mesh = fen.RectangleMesh(fen.Point(0., 0.), fen.Point(length, 1), n, max(int(n / length), 1)) self.V = fen.VectorFunctionSpace(mesh, "P", 1) self.forcingTerm = [fen.Constant((0., - rho_ * g)), fenZEROS(2)] self.DirichletBoundary = lambda x, on_b: on_b and fen.near(x[0], 0.) self.NeumannBoundary = "REST" def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) self.autoSetDS() if derI <= 0 and self.As[0] is None: self.As[0] = 0. if derI <= 1 and self.As[1] is None: self.As[1] = 0. if derI <= 4 and self.As[2] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A2.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(2), self.DirichletBoundary) epsilon = lambda u: .5 * (fen.grad(u) + fen.nabla_grad(u)) a0Re = fen.inner(epsilon(self.u), epsilon(self.v)) * fen.dx A0Re = fen.assemble(a0Re) DirichletBC0.apply(A0Re) A0ReMat = fen.as_backend_type(A0Re).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() self.As[2] = scsp.csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size, dtype = np.complex) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 3 and self.As[3] is None: self.As[3] = 0. if derI <= 4 and self.As[4] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A4.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(2), self.DirichletBoundary) a1Re = fen.div(self.u) * fen.div(self.v) * fen.dx A1Re = fen.assemble(a1Re) DirichletBC0.apply(A1Re) A1ReMat = fen.as_backend_type(A1Re).mat() A1Rer, A1Rec, A1Rev = A1ReMat.getValuesCSR() self.As[4] = 2. * (scsp.csr_matrix((A1Rev, A1Rec, A1Rer), shape = A1ReMat.size, dtype = np.complex) - self.As[2]) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) def b(self, mu : paramVal = [], der : List[int] = 0, homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" RROMPyAssert(homogeneized, False, "Homogeneized") mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) if derI <= 3 and self.bs[0] is None: self.autoSetDS() if self.verbosity >= 20: verbosityDepth("INIT", "Assembling forcing term b0.", timestamp = self.timestamp) fRe, fIm = self.forcingTerm parsRe = self.iterReduceQuadratureDegree(zip([fRe], ["forcingTermReal"])) parsIm = self.iterReduceQuadratureDegree(zip([fIm], ["forcingTermImag"])) L0Re = fen.inner(fRe, self.v) * fen.dx L0Im = fen.inner(fIm, self.v) * fen.dx b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) DBCR = fen.DirichletBC(self.V, self.DirichletDatum[0], self.DirichletBoundary) DBCI = fen.DirichletBC(self.V, self.DirichletDatum[1], self.DirichletBoundary) DBCR.apply(b0Re) DBCI.apply(b0Im) self.bs[0] = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) if derI <= 1 and self.bs[1] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling forcing term b1.", timestamp = self.timestamp) self.bs[1] = - self.bs[0] if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) if derI <= 2 and self.bs[2] is None: self.bs[2] = 0. if derI <= 3 and self.bs[3] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling forcing term b3.", timestamp = self.timestamp) self.bs[3] = - 2. * self.bs[0] if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) return self._assembleb(mu, der, derI, homogeneized) diff --git a/rrompy/hfengines/vector_linear_problem/linear_elasticity_beam_poisson_ratio.py b/rrompy/hfengines/vector_linear_problem/linear_elasticity_beam_poisson_ratio.py index b6ffcc5..8b80487 100644 --- a/rrompy/hfengines/vector_linear_problem/linear_elasticity_beam_poisson_ratio.py +++ b/rrompy/hfengines/vector_linear_problem/linear_elasticity_beam_poisson_ratio.py @@ -1,149 +1,148 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import scipy.sparse as scsp import fenics as fen from .linear_elasticity_problem_engine import LinearElasticityProblemEngine from rrompy.solver.fenics import fenZEROS from rrompy.utilities.base.types import Np1D, List, ScOp, paramVal from rrompy.utilities.base import verbosityDepth from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD) __all__ = ['LinearElasticityBeamPoissonRatio'] class LinearElasticityBeamPoissonRatio(LinearElasticityProblemEngine): """ Solver for linear elasticity problem of a beam subject to its own weight, with parametric Poisson's ratio. - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) = rho_ * g in \Omega u = 0 on \Gamma_D \partial_nu = 0 on \Gamma_N """ - nAs, nbs = 2, 3 - def __init__(self, n:int, rho_:float, g:float, E:float, nu0:float, length:float, degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = [nu0], degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nAs, self.nbs = 2, 3 self.E_ = E mesh = fen.RectangleMesh(fen.Point(0., 0.), fen.Point(length, 1), n, max(int(n / length), 1)) self.V = fen.VectorFunctionSpace(mesh, "P", 1) self.forcingTerm = [fen.Constant((0., - rho_ * g / E)), fenZEROS(2)] self.DirichletBoundary = lambda x, on_b: on_b and fen.near(x[0], 0.) self.NeumannBoundary = "REST" def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) self.autoSetDS() if derI <= 1 and self.As[0] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(2), self.DirichletBoundary) epsilon = lambda u: .5 * (fen.grad(u) + fen.nabla_grad(u)) a0Re = self.E_ * fen.inner(epsilon(self.u), epsilon(self.v)) * fen.dx A0Re = fen.assemble(a0Re) DirichletBC0.apply(A0Re) A0ReMat = fen.as_backend_type(A0Re).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() self.As[0] = scsp.csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size, dtype = np.complex) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 1 and self.As[1] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A1.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(2), self.DirichletBoundary) a1Re = self.E_ * fen.div(self.u) * fen.div(self.v) * fen.dx A1Re = fen.assemble(a1Re) DirichletBC0.apply(A1Re) A1ReMat = fen.as_backend_type(A1Re).mat() A1Rer, A1Rec, A1Rev = A1ReMat.getValuesCSR() self.As[1] = 2. * (scsp.csr_matrix((A1Rev, A1Rec, A1Rer), shape = A1ReMat.size, dtype = np.complex) - self.As[0]) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) def b(self, mu : paramVal = [], der : List[int] = 0, homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" RROMPyAssert(homogeneized, False, "Homogeneized") mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) if derI <= 2 and self.bs[0] is None: self.autoSetDS() if self.verbosity >= 20: verbosityDepth("INIT", "Assembling forcing term b0.", timestamp = self.timestamp) fRe, fIm = self.forcingTerm parsRe = self.iterReduceQuadratureDegree(zip([fRe], ["forcingTermReal"])) parsIm = self.iterReduceQuadratureDegree(zip([fIm], ["forcingTermImag"])) L0Re = fen.inner(fRe, self.v) * fen.dx L0Im = fen.inner(fIm, self.v) * fen.dx b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) DBCR = fen.DirichletBC(self.V, self.DirichletDatum[0], self.DirichletBoundary) DBCI = fen.DirichletBC(self.V, self.DirichletDatum[1], self.DirichletBoundary) DBCR.apply(b0Re) DBCI.apply(b0Im) self.bs[0] = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) if derI <= 1 and self.bs[1] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling forcing term b1.", timestamp = self.timestamp) self.bs[1] = - self.bs[0] if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) if derI <= 2 and self.bs[2] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling forcing term b2.", timestamp = self.timestamp) self.bs[2] = - 2. * self.bs[0] if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) return self._assembleb(mu, der, derI, homogeneized) diff --git a/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine.py b/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine.py index fb278ff..0290795 100644 --- a/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine.py +++ b/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine.py @@ -1,181 +1,180 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from scipy.sparse import csr_matrix import fenics as fen from .linear_elasticity_problem_engine import LinearElasticityProblemEngine from rrompy.utilities.base.types import List, ScOp, paramVal from rrompy.solver.fenics import fenZERO, fenZEROS, fenONE, elasticNormMatrix from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD) __all__ = ['LinearElasticityHelmholtzProblemEngine'] class LinearElasticityHelmholtzProblemEngine(LinearElasticityProblemEngine): """ Solver for generic linear elasticity Helmholtz problems with parametric wavenumber. - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) - rho_ * mu^2 * u = f in \Omega u = u0 on \Gamma_D \partial_nu = g1 on \Gamma_N \partial_nu + h u = g2 on \Gamma_R Attributes: verbosity: Verbosity level. BCManager: Boundary condition manager. V: Real vector FE space. u: Generic vector trial functions for variational form evaluation. v: Generic vector test functions for variational form evaluation. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. energyNormMatrix: Scipy sparse matrix representing inner product over V. liftedDirichletDatum: Dofs of Dirichlet datum lifting. mu0BC: Mu value of last Dirichlet datum lifting. degree_threshold: Threshold for ufl expression interpolation degree. omega: Value of omega. lambda_: Value of lambda_. mu_: Value of mu_. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. RobinDatumG: Value of g2. RobinDatumH: Value of h. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). A0: Scipy sparse array representation (in CSC format) of A0. A1: Scipy sparse array representation (in CSC format) of A1. b0: Numpy array representation of b0. dsToBeSet: Whether ds needs to be set. """ - nAs = 2 - def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = [mu0], degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nAs = 2 self.omega = np.abs(self.mu0(0, 0)) self.rho_ = fenONE self.rescalingExp = [2.] @property def rho_(self): """Value of rho_.""" return self._rho_ @rho_.setter def rho_(self, rho_): self.resetAs() if not isinstance(rho_, (list, tuple,)): rho_ = [rho_, fenZERO] self._rho_ = rho_ def buildEnergyNormForm(self): # energy + omega norm """ Build sparse matrix (in CSR format) representative of scalar product. """ lambda_Re, _ = self.lambda_ mu_Re, _ = self.mu_ r_Re, _ = self.rho_ self.energyNormMatrix = elasticNormMatrix(self.V, lambda_Re, mu_Re, np.abs(self.omega)**2 * r_Re) def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) self.autoSetDS() if derI <= 0 and self.As[0] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) lambda_Re, lambda_Im = self.lambda_ mu_Re, mu_Im = self.mu_ hRe, hIm = self.RobinDatumH termNames = ["lambda_", "mu_", "RobinDatumH"] parsRe = self.iterReduceQuadratureDegree(zip( [lambda_Re, mu_Re, hRe], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip( [lambda_Im, mu_Re, hIm], [x + "Imag" for x in termNames])) epsilon = lambda u: 0.5 * (fen.grad(u) + fen.nabla_grad(u)) sigma = lambda u, l_, m_: ( l_ * fen.div(u) * fen.Identity(u.geometric_dimension()) + 2. * m_ * epsilon(u)) a0Re = (fen.inner(sigma(self.u, lambda_Re, mu_Re), epsilon(self.v)) * fen.dx + hRe * fen.inner(self.u, self.v) * self.ds(1)) a0Im = (fen.inner(sigma(self.u, lambda_Im, mu_Im), epsilon(self.v)) * fen.dx + hIm * fen.inner(self.u, self.v) * self.ds(1)) A0Re = fen.assemble(a0Re, form_compiler_parameters = parsRe) A0Im = fen.assemble(a0Im, form_compiler_parameters = parsIm) DirichletBC0.apply(A0Re) DirichletBC0.zero(A0Im) A0ReMat = fen.as_backend_type(A0Re).mat() A0ImMat = fen.as_backend_type(A0Im).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() A0Imr, A0Imc, A0Imv = A0ImMat.getValuesCSR() self.As[0] = (csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size) + 1.j * csr_matrix((A0Imv, A0Imc, A0Imr), shape = A0ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 1 and self.As[1] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A1.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) rho_Re, rho_Im = self.rho_ parsRe = self.iterReduceQuadratureDegree(zip([rho_Re], ["rho_Real"])) parsIm = self.iterReduceQuadratureDegree(zip([rho_Im], ["rho_Imag"])) a1Re = - rho_Re * fen.inner(self.u, self.v) * fen.dx a1Im = - rho_Im * fen.inner(self.u, self.v) * fen.dx A1Re = fen.assemble(a1Re, form_compiler_parameters = parsRe) A1Im = fen.assemble(a1Im, form_compiler_parameters = parsIm) DirichletBC0.zero(A1Re) DirichletBC0.zero(A1Im) A1ReMat = fen.as_backend_type(A1Re).mat() A1ImMat = fen.as_backend_type(A1Im).mat() A1Rer, A1Rec, A1Rev = A1ReMat.getValuesCSR() A1Imr, A1Imc, A1Imv = A1ImMat.getValuesCSR() self.As[1] = (csr_matrix((A1Rev, A1Rec, A1Rer), shape = A1ReMat.size) + 1.j * csr_matrix((A1Imv, A1Imc, A1Imr), shape = A1ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) diff --git a/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine_damped.py b/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine_damped.py index 035e321..898e32d 100644 --- a/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine_damped.py +++ b/rrompy/hfengines/vector_linear_problem/linear_elasticity_helmholtz_problem_engine_damped.py @@ -1,206 +1,205 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from scipy.sparse import csr_matrix import fenics as fen from .linear_elasticity_helmholtz_problem_engine import \ LinearElasticityHelmholtzProblemEngine from rrompy.utilities.base.types import List, ScOp, paramVal from rrompy.solver.fenics import fenZERO, fenZEROS from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD) __all__ = ['LinearElasticityHelmholtzProblemEngineDamped'] class LinearElasticityHelmholtzProblemEngineDamped( LinearElasticityHelmholtzProblemEngine): """ Solver for generic linear elasticity Helmholtz problems with parametric wavenumber. - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) - rho_ * (mu^2 - i * eta * mu) * u = f in \Omega u = u0 on \Gamma_D \partial_nu = g1 on \Gamma_N \partial_nu + h u = g2 on \Gamma_R Attributes: verbosity: Verbosity level. BCManager: Boundary condition manager. V: Real vector FE space. u: Generic vector trial functions for variational form evaluation. v: Generic vector test functions for variational form evaluation. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. energyNormMatrix: Scipy sparse matrix representing inner product over V. liftedDirichletDatum: Dofs of Dirichlet datum lifting. mu0BC: Mu value of last Dirichlet datum lifting. degree_threshold: Threshold for ufl expression interpolation degree. omega: Value of omega. lambda_: Value of lambda_. mu_: Value of mu_. eta: Value of eta. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. RobinDatumG: Value of g2. RobinDatumH: Value of h. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). A0: Scipy sparse array representation (in CSC format) of A0. A1: Scipy sparse array representation (in CSC format) of A1. b0: Numpy array representation of b0. dsToBeSet: Whether ds needs to be set. """ - nAs = 3 - def __init__(self, mu0 : paramVal = [0.], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(mu0 = [mu0], degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) + self.nAs = 3 self.eta = fenZERO self.rescalingExp = [1.] @property def eta(self): """Value of eta.""" return self._eta @eta.setter def eta(self, eta): self.resetAs() if not isinstance(eta, (list, tuple,)): eta = [eta, fenZERO] self._eta = eta def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) self.autoSetDS() if derI <= 0 and self.As[0] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) lambda_Re, lambda_Im = self.lambda_ mu_Re, mu_Im = self.mu_ hRe, hIm = self.RobinDatumH termNames = ["lambda_", "mu_", "RobinDatumH"] parsRe = self.iterReduceQuadratureDegree(zip( [lambda_Re, mu_Re, hRe], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip( [lambda_Im, mu_Re, hIm], [x + "Imag" for x in termNames])) epsilon = lambda u: 0.5 * (fen.grad(u) + fen.nabla_grad(u)) sigma = lambda u, l_, m_: ( l_ * fen.div(u) * fen.Identity(u.geometric_dimension()) + 2. * m_ * epsilon(u)) a0Re = (fen.inner(sigma(self.u, lambda_Re, mu_Re), epsilon(self.v)) * fen.dx + hRe * fen.inner(self.u, self.v) * self.ds(1)) a0Im = (fen.inner(sigma(self.u, lambda_Im, mu_Im), epsilon(self.v)) * fen.dx + hIm * fen.inner(self.u, self.v) * self.ds(1)) A0Re = fen.assemble(a0Re, form_compiler_parameters = parsRe) A0Im = fen.assemble(a0Im, form_compiler_parameters = parsIm) DirichletBC0.apply(A0Re) DirichletBC0.zero(A0Im) A0ReMat = fen.as_backend_type(A0Re).mat() A0ImMat = fen.as_backend_type(A0Im).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() A0Imr, A0Imc, A0Imv = A0ImMat.getValuesCSR() self.As[0] = (csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size) + 1.j * csr_matrix((A0Imv, A0Imc, A0Imr), shape = A0ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 1 and self.As[1] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A1.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) rho_Re, rho_Im = self.rho_ eta_Re, eta_Im = self.eta termNames = ["rho_", "eta"] parsRe = self.iterReduceQuadratureDegree(zip([rho_Re, eta_Re], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip([rho_Im, eta_Im], [x + "Imag" for x in termNames])) a1Re = - ((eta_Re * rho_Im + eta_Im * rho_Re) * fen.inner(self.u, self.v)) * fen.dx a1Im = ((eta_Re * rho_Re - eta_Im * rho_Im) * fen.inner(self.u, self.v)) * fen.dx A1Re = fen.assemble(a1Re, form_compiler_parameters = parsRe) A1Im = fen.assemble(a1Im, form_compiler_parameters = parsIm) DirichletBC0.zero(A1Re) DirichletBC0.zero(A1Im) A1ReMat = fen.as_backend_type(A1Re).mat() A1ImMat = fen.as_backend_type(A1Im).mat() A1Rer, A1Rec, A1Rev = A1ReMat.getValuesCSR() A1Imr, A1Imc, A1Imv = A1ImMat.getValuesCSR() self.As[1] = (csr_matrix((A1Rev, A1Rec, A1Rer), shape = A1ReMat.size) + 1.j * csr_matrix((A1Imv, A1Imc, A1Imr), shape = A1ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) if derI <= 2 and self.As[2] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A2.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) rho_Re, rho_Im = self.rho_ parsRe = self.iterReduceQuadratureDegree(zip([rho_Re], ["rho_Real"])) parsIm = self.iterReduceQuadratureDegree(zip([rho_Im], ["rho_Imag"])) a2Re = - rho_Re * fen.inner(self.u, self.v) * fen.dx a2Im = - rho_Im * fen.inner(self.u, self.v) * fen.dx A2Re = fen.assemble(a2Re, form_compiler_parameters = parsRe) A2Im = fen.assemble(a2Im, form_compiler_parameters = parsIm) DirichletBC0.zero(A2Re) DirichletBC0.zero(A2Im) A2ReMat = fen.as_backend_type(A2Re).mat() A2ImMat = fen.as_backend_type(A2Im).mat() A2Rer, A2Rec, A2Rev = A2ReMat.getValuesCSR() A2Imr, A2Imc, A2Imv = A2ImMat.getValuesCSR() self.As[2] = (csr_matrix((A2Rev, A2Rec, A2Rer), shape = A2ReMat.size) + 1.j * csr_matrix((A2Imv, A2Imc, A2Imr), shape = A2ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) diff --git a/rrompy/hfengines/vector_linear_problem/linear_elasticity_problem_engine.py b/rrompy/hfengines/vector_linear_problem/linear_elasticity_problem_engine.py index d99de76..5fe95b0 100644 --- a/rrompy/hfengines/vector_linear_problem/linear_elasticity_problem_engine.py +++ b/rrompy/hfengines/vector_linear_problem/linear_elasticity_problem_engine.py @@ -1,357 +1,346 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from scipy.sparse import csr_matrix import fenics as fen from rrompy.hfengines.base.vector_problem_engine_base import \ VectorProblemEngineBase from rrompy.utilities.base.types import Np1D, List, ScOp, paramVal from rrompy.solver.fenics import fenZERO, fenZEROS, fenONE, elasticNormMatrix from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import ( hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) from rrompy.parameter import checkParameter __all__ = ['LinearElasticityProblemEngine'] class LinearElasticityProblemEngine(VectorProblemEngineBase): """ Solver for generic linear elasticity problems. - div(lambda_ * div(u) * I + 2 * mu_ * epsilon(u)) = f in \Omega u = u0 on \Gamma_D \partial_nu = g1 on \Gamma_N \partial_nu + h u = g2 on \Gamma_R Attributes: verbosity: Verbosity level. BCManager: Boundary condition manager. V: Real vector FE space. u: Generic vector trial functions for variational form evaluation. v: Generic vector test functions for variational form evaluation. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. energyNormMatrix: Scipy sparse matrix representing inner product over V. liftedDirichletDatum: Dofs of Dirichlet datum lifting. mu0BC: Mu value of last Dirichlet datum lifting. degree_threshold: Threshold for ufl expression interpolation degree. lambda_: Value of lambda_. mu_: Value of mu_. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. RobinDatumG: Value of g2. RobinDatumH: Value of h. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. ds: Boundary measure 2-tuple (resp. for Neumann and Robin boundaries). A0: Scipy sparse array representation (in CSC format) of A0. b0: Numpy array representation of b0. dsToBeSet: Whether ds needs to be set. """ def __init__(self, mu0 : paramVal = [], degree_threshold : int = np.inf, verbosity : int = 10, timestamp : bool = True): super().__init__(degree_threshold = degree_threshold, verbosity = verbosity, timestamp = timestamp) self.lambda_ = fenONE self.mu_ = fenONE self.mu0 = checkParameter(mu0) self.npar = self.mu0.shape[1] self.forcingTerm = fenZEROS(self.V.mesh().topology().dim()) self.DirichletDatum = fenZEROS(self.V.mesh().topology().dim()) self.NeumannDatum = fenZEROS(self.V.mesh().topology().dim()) self.RobinDatumG = fenZEROS(self.V.mesh().topology().dim()) self.RobinDatumH = fenZERO @property def V(self): """Value of V.""" return self._V @V.setter def V(self, V): VectorProblemEngineBase.V.fset(self, V) self.forcingTerm = fenZEROS(self.V.mesh().topology().dim()) self.DirichletDatum = fenZEROS(self.V.mesh().topology().dim()) self.NeumannDatum = fenZEROS(self.V.mesh().topology().dim()) self.RobinDatumG = fenZEROS(self.V.mesh().topology().dim()) self.dsToBeSet = True @property def lambda_(self): """Value of lambda_.""" return self._lambda_ @lambda_.setter def lambda_(self, lambda_): self.resetAs() if not isinstance(lambda_, (list, tuple,)): lambda_ = [lambda_, fenZERO] self._lambda_ = lambda_ @property def mu_(self): """Value of mu_.""" return self._mu_ @mu_.setter def mu_(self, mu_): self.resetAs() if not isinstance(mu_, (list, tuple,)): mu_ = [mu_, fenZERO] self._mu_ = mu_ @property def forcingTerm(self): """Value of f.""" return self._forcingTerm @forcingTerm.setter def forcingTerm(self, forcingTerm): self.resetbs() if not isinstance(forcingTerm, (list, tuple,)): forcingTerm = [forcingTerm, fenZEROS(self.V.mesh().topology().dim())] self._forcingTerm = forcingTerm @property def DirichletDatum(self): """Value of u0.""" return self._DirichletDatum @DirichletDatum.setter def DirichletDatum(self, DirichletDatum): self.resetbs() if not isinstance(DirichletDatum, (list, tuple,)): DirichletDatum = [DirichletDatum, fenZEROS(self.V.mesh().topology().dim())] self._DirichletDatum = DirichletDatum @property def NeumannDatum(self): """Value of g1.""" return self._NeumannDatum @NeumannDatum.setter def NeumannDatum(self, NeumannDatum): self.resetbs() if not isinstance(NeumannDatum, (list, tuple,)): NeumannDatum = [NeumannDatum, fenZEROS(self.V.mesh().topology().dim())] self._NeumannDatum = NeumannDatum @property def RobinDatumG(self): """Value of g2.""" return self._RobinDatumG @RobinDatumG.setter def RobinDatumG(self, RobinDatumG): self.resetbs() if not isinstance(RobinDatumG, (list, tuple,)): RobinDatumG = [RobinDatumG, fenZEROS(self.V.mesh().topology().dim())] self._RobinDatumG = RobinDatumG @property def RobinDatumH(self): """Value of h.""" return self._RobinDatumH @RobinDatumH.setter def RobinDatumH(self, RobinDatumH): self.resetAs() if not isinstance(RobinDatumH, (list, tuple,)): RobinDatumH = [RobinDatumH, fenZERO] self._RobinDatumH = RobinDatumH @property def DirichletBoundary(self): """Function handle to DirichletBoundary.""" return self.BCManager.DirichletBoundary @DirichletBoundary.setter def DirichletBoundary(self, DirichletBoundary): self.resetAs() self.resetbs() self.BCManager.DirichletBoundary = DirichletBoundary @property def NeumannBoundary(self): """Function handle to NeumannBoundary.""" return self.BCManager.NeumannBoundary @NeumannBoundary.setter def NeumannBoundary(self, NeumannBoundary): self.resetAs() self.resetbs() self.dsToBeSet = True self.BCManager.NeumannBoundary = NeumannBoundary @property def RobinBoundary(self): """Function handle to RobinBoundary.""" return self.BCManager.RobinBoundary @RobinBoundary.setter def RobinBoundary(self, RobinBoundary): self.resetAs() self.resetbs() self.dsToBeSet = True self.BCManager.RobinBoundary = RobinBoundary def autoSetDS(self): """Set FEniCS boundary measure based on boundary function handles.""" if self.dsToBeSet: if self.verbosity >= 20: verbosityDepth("INIT", "Initializing boundary measures.", timestamp = self.timestamp) NB = self.NeumannBoundary RB = self.RobinBoundary boundary_markers = fen.MeshFunction("size_t", self.V.mesh(), self.V.mesh().topology().dim() - 1) NB.mark(boundary_markers, 0) RB.mark(boundary_markers, 1) self.ds = fen.Measure("ds", domain = self.V.mesh(), subdomain_data = boundary_markers) self.dsToBeSet = False if self.verbosity >= 20: verbosityDepth("DEL", "Done initializing boundary measures.", timestamp = self.timestamp) def buildEnergyNormForm(self): """ Build sparse matrix (in CSR format) representative of scalar product. """ lambda_Re, _ = self.lambda_ mu_Re, _ = self.mu_ self.energyNormMatrix = elasticNormMatrix(self.V, lambda_Re, mu_Re) def A(self, mu : paramVal = [], der : List[int] = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) self.autoSetDS() if derI <= 0 and self.As[0] is None: if self.verbosity >= 20: verbosityDepth("INIT", "Assembling operator term A0.", timestamp = self.timestamp) DirichletBC0 = fen.DirichletBC(self.V, fenZEROS(self.V.mesh().topology().dim()), self.DirichletBoundary) lambda_Re, lambda_Im = self.lambda_ mu_Re, mu_Im = self.mu_ hRe, hIm = self.RobinDatumH termNames = ["lambda_", "mu_", "RobinDatumH"] parsRe = self.iterReduceQuadratureDegree(zip( [lambda_Re, mu_Re, hRe], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip( [lambda_Im, mu_Re, hIm], [x + "Imag" for x in termNames])) epsilon = lambda u: 0.5 * (fen.grad(u) + fen.nabla_grad(u)) sigma = lambda u, l_, m_: ( l_ * fen.div(u) * fen.Identity(u.geometric_dimension()) + 2. * m_ * epsilon(u)) a0Re = (fen.inner(sigma(self.u, lambda_Re, mu_Re), epsilon(self.v)) * fen.dx + hRe * fen.inner(self.u, self.v) * self.ds(1)) a0Im = (fen.inner(sigma(self.u, lambda_Im, mu_Im), epsilon(self.v)) * fen.dx + hIm * fen.inner(self.u, self.v) * self.ds(1)) A0Re = fen.assemble(a0Re, form_compiler_parameters = parsRe) A0Im = fen.assemble(a0Im, form_compiler_parameters = parsIm) DirichletBC0.apply(A0Re) DirichletBC0.zero(A0Im) A0ReMat = fen.as_backend_type(A0Re).mat() A0ImMat = fen.as_backend_type(A0Im).mat() A0Rer, A0Rec, A0Rev = A0ReMat.getValuesCSR() A0Imr, A0Imc, A0Imv = A0ImMat.getValuesCSR() self.As[0] = (csr_matrix((A0Rev, A0Rec, A0Rer), shape = A0ReMat.size) + 1.j * csr_matrix((A0Imv, A0Imc, A0Imr), shape = A0ImMat.size)) if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling operator term.", timestamp = self.timestamp) return self._assembleA(mu, der, derI) def b(self, mu : paramVal = [], der : List[int] = 0, homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" mu = self.checkParameter(mu) if not hasattr(der, "__len__"): der = [der] * self.npar derI = hashD(der) nbsTot = self.nbsH if homogeneized else self.nbs bs = self.bsH if homogeneized else self.bs if homogeneized and self.mu != self.mu0BC: self.liftDirichletData(self.mu) + fenZEROSEff = fenZEROS(self.V.mesh().topology().dim()) for j in range(derI, nbsTot): if bs[j] is None: self.autoSetDS() if self.verbosity >= 20: verbosityDepth("INIT", ("Assembling forcing term " "b{}.").format(j), timestamp = self.timestamp) if j == 0: + u0Re, u0Im = self.DirichletDatum fRe, fIm = self.forcingTerm g1Re, g1Im = self.NeumannDatum g2Re, g2Im = self.RobinDatumG else: - fRe = fenZEROS(self.V.mesh().topology().dim()) - fIm = fenZEROS(self.V.mesh().topology().dim()) - g1Re = fenZEROS(self.V.mesh().topology().dim()) - g1Im = fenZEROS(self.V.mesh().topology().dim()) - g2Re = fenZEROS(self.V.mesh().topology().dim()) - g2Im = fenZEROS(self.V.mesh().topology().dim()) + u0Re, u0Im = fenZEROSEff, fenZEROSEff + fRe, fIm = fenZEROSEff, fenZEROSEff + g1Re, g1Im = fenZEROSEff, fenZEROSEff + g2Re, g2Im = fenZEROSEff, fenZEROSEff termNames = ["forcingTerm", "NeumannDatum", "RobinDatumG"] parsRe = self.iterReduceQuadratureDegree(zip( [fRe, g1Re, g2Re], [x + "Real" for x in termNames])) parsIm = self.iterReduceQuadratureDegree(zip( [fIm, g1Im, g2Im], [x + "Imag" for x in termNames])) L0Re = (fen.inner(fRe, self.v) * fen.dx + fen.inner(g1Re, self.v) * self.ds(0) + fen.inner(g2Re, self.v) * self.ds(1)) L0Im = (fen.inner(fIm, self.v) * fen.dx + fen.inner(g1Im, self.v) * self.ds(0) + fen.inner(g2Im, self.v) * self.ds(1)) b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) - if homogeneized: - Ader = self.A(self.mu0, hashI(j)) - b0Re[:] -= np.real(Ader.dot(self.liftedDirichletDatum)) - b0Im[:] -= np.imag(Ader.dot(self.liftedDirichletDatum)) - DBCR = fen.DirichletBC(self.V, - fenZEROS(self.V.mesh().topology().dim()), - self.DirichletBoundary) - DBCI = fen.DirichletBC(self.V, - fenZEROS(self.V.mesh().topology().dim()), - self.DirichletBoundary) - else: - DBCR = fen.DirichletBC(self.V, self.DirichletDatum[0], - self.DirichletBoundary) - DBCI = fen.DirichletBC(self.V, self.DirichletDatum[1], - self.DirichletBoundary) + DBCR = fen.DirichletBC(self.V, u0Re, self.DirichletBoundary) + DBCI = fen.DirichletBC(self.V, u0Im, self.DirichletBoundary) DBCR.apply(b0Re) DBCI.apply(b0Im) + b = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) + if homogeneized: + Ader = self.A(self.mu0, hashI(j, self.npar)) + b -= Ader.dot(self.liftedDirichletDatum) if homogeneized: - self.bsH[j] = np.array(b0Re[:] + 1.j * b0Im[:], - dtype = np.complex) + self.bsH[j] = b else: - self.bs[j] = np.array(b0Re[:] + 1.j * b0Im[:], - dtype = np.complex) + self.bs[j] = b if self.verbosity >= 20: verbosityDepth("DEL", "Done assembling forcing term.", timestamp = self.timestamp) - return self._assembleb(mu - self.mu0, der, derI, homogeneized) + return self._assembleb(mu, der, derI, homogeneized, self.mu0) diff --git a/rrompy/parameter/parameter_sampling/__init__.py b/rrompy/parameter/parameter_sampling/__init__.py index 746a0ae..12e89ee 100644 --- a/rrompy/parameter/parameter_sampling/__init__.py +++ b/rrompy/parameter/parameter_sampling/__init__.py @@ -1,33 +1,35 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from .generic_sampler import GenericSampler from .fft_sampler import FFTSampler from .manual_sampler import ManualSampler from .quadrature_sampler import QuadratureSampler +from .quadrature_sampler_total import QuadratureSamplerTotal from .random_sampler import RandomSampler __all__ = [ 'GenericSampler', 'FFTSampler', 'ManualSampler', 'QuadratureSampler', + 'QuadratureSamplerTotal', 'RandomSampler' ] diff --git a/rrompy/parameter/parameter_sampling/fft_sampler.py b/rrompy/parameter/parameter_sampling/fft_sampler.py index a41f44b..b59d4e0 100644 --- a/rrompy/parameter/parameter_sampling/fft_sampler.py +++ b/rrompy/parameter/parameter_sampling/fft_sampler.py @@ -1,51 +1,50 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from .generic_sampler import GenericSampler -from rrompy.utilities.base.types import Np1D, List, paramList +from rrompy.utilities.base.types import List, paramList from rrompy.utilities.base import lowDiscrepancy, kroneckerer from rrompy.parameter import checkParameterList __all__ = ['FFTSampler'] class FFTSampler(GenericSampler): """Generator of FFT-type sample points on scaled roots of unity.""" def generatePoints(self, n:List[int], reorder : bool = True) -> paramList: """Array of sample points.""" if not hasattr(n, "__len__"): n = [n] super().generatePoints(n) nleft, nright = 1, np.prod(n) xmat = np.empty((nright, self.npar), dtype = np.complex) for d in range(self.npar): nright //= n[d] a, b = self.lims(0, d), self.lims(1, d) if self.scaling is not None: a, b = self.scaling[d](a), self.scaling[d](b) c, r = (a + b) / 2., np.abs(a - b) / 2. xd = c + r * np.exp(1.j * np.linspace(0, 2 * np.pi, n[d] + 1)[:-1]) - if reorder: - fejerOrdering = lowDiscrepancy(n[d]) - xd = xd[fejerOrdering] if self.scalingInv is not None: xd = self.scalingInv[d](xd) xmat[:, d] = kroneckerer(xd, nleft, nright) nleft *= n[d] + if reorder: + xmat = xmat[lowDiscrepancy(np.prod(n)), :] x, _ = checkParameterList(xmat, self.npar) return x diff --git a/rrompy/parameter/parameter_sampling/generic_sampler.py b/rrompy/parameter/parameter_sampling/generic_sampler.py index 8e8a0d7..52510f3 100644 --- a/rrompy/parameter/parameter_sampling/generic_sampler.py +++ b/rrompy/parameter/parameter_sampling/generic_sampler.py @@ -1,98 +1,98 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from abc import abstractmethod -from rrompy.utilities.base.types import Np1D, List, paramList +from rrompy.utilities.base.types import List, paramList from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert from rrompy.parameter import checkParameterList __all__ = ['GenericSampler'] class GenericSampler: """ABSTRACT. Generic generator of sample points.""" def __init__(self, lims:paramList, 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(), self.lims[0], self.lims[1]) def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) def __eq__(self, other) -> bool: return self.__dict__ == other.__dict__ @property def npar(self): """Number of parameters.""" return self._lims.shape[1] @property def lims(self): """Value of lims.""" return self._lims @lims.setter def lims(self, lims): lims, _ = checkParameterList(lims) if len(lims) != 2: raise RROMPyException("2 limits must be specified.") self._lims = lims @property def scaling(self): """Value of scaling.""" return self._scaling @scaling.setter def scaling(self, scaling): if scaling is not None: if not hasattr(scaling, "__len__"): scaling = [scaling] RROMPyAssert(self.npar, len(scaling), "Number of scaling terms") if not all([callable(s) for s in scaling]): raise RROMPyException(("Each value of scaling must be a " "callable.")) self._scaling = scaling @property def scalingInv(self): """Value of scalingInv.""" return self._scalingInv @scalingInv.setter def scalingInv(self, scalingInv): if scalingInv is not None: if not hasattr(scalingInv, "__len__"): scalingInv = [scalingInv] RROMPyAssert(self.npar, len(scalingInv), "Number of scalingInv terms") if not all([callable(sInv) for sInv in scalingInv]): raise RROMPyException(("Each value of scalingInv must be a " "callable.")) self._scalingInv = scalingInv @abstractmethod def generatePoints(self, n:List[int]) -> paramList: """Array of points.""" if not hasattr(n, "__len__"): n = [n] RROMPyAssert(self.npar, len(n), "Point number") pass diff --git a/rrompy/parameter/parameter_sampling/manual_sampler.py b/rrompy/parameter/parameter_sampling/manual_sampler.py index 60474b5..9a2c943 100644 --- a/rrompy/parameter/parameter_sampling/manual_sampler.py +++ b/rrompy/parameter/parameter_sampling/manual_sampler.py @@ -1,66 +1,63 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from .generic_sampler import GenericSampler -from rrompy.utilities.base.types import Np1D, List, paramList -from rrompy.utilities.exception_manager import RROMPyWarning, RROMPyAssert +from rrompy.utilities.base.types import List, paramList from rrompy.parameter import checkParameterList __all__ = ['ManualSampler'] class ManualSampler(GenericSampler): """Manual generator of sample points.""" def __init__(self, lims:paramList, points:paramList, scaling : List[callable] = None, scalingInv : List[callable] = None): super().__init__(lims = lims, scaling = scaling, scalingInv = scalingInv) self.points = points @property def points(self): """Value of points.""" return self._points @points.setter def points(self, points): points, _ = checkParameterList(points, self.npar) self._points = points def __str__(self) -> str: return "{}[{}]".format(self.name(), "_".join(map(str, self.points))) def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) def generatePoints(self, n:int) -> paramList: """Array of sample points.""" if hasattr(n, "__len__"): n = n[0] if n > len(self.points): - RROMPyWarning(("Requested more points than given. Looping over " - "first points.")) pts = copy(self.points) - for j in range(np.int(np.ceil(n / len(self.points)))): + for j in range(int(np.ceil(n / len(self.points)))): pts.append(self.points) else: pts = self.points x, _ = checkParameterList(pts[list(range(n))], self.npar) return x diff --git a/rrompy/parameter/parameter_sampling/quadrature_sampler.py b/rrompy/parameter/parameter_sampling/quadrature_sampler.py index 0900816..4ddfbcf 100644 --- a/rrompy/parameter/parameter_sampling/quadrature_sampler.py +++ b/rrompy/parameter/parameter_sampling/quadrature_sampler.py @@ -1,86 +1,87 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from .generic_sampler import GenericSampler -from rrompy.utilities.base.types import Np1D, List, paramList +from rrompy.utilities.base.types import List, paramList from rrompy.utilities.exception_manager import RROMPyException from rrompy.utilities.base import lowDiscrepancy, kroneckerer from rrompy.parameter import checkParameterList __all__ = ['QuadratureSampler'] class QuadratureSampler(GenericSampler): """Generator of quadrature sample points.""" allowedKinds = ["UNIFORM", "CHEBYSHEV", "GAUSSLEGENDRE"] def __init__(self, lims:paramList, 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 RROMPyException("Generator kind not recognized.") self._kind = kind.upper() def generatePoints(self, n:List[int], reorder : bool = True) -> paramList: """Array of sample points.""" if not hasattr(n, "__len__"): n = [n] super().generatePoints(n) nleft, nright = 1, np.prod(n) xmat = np.empty((nright, self.npar), dtype = self.lims.dtype) for d in range(self.npar): nright //= n[d] a, b = self.lims(0, d), self.lims(1, d) if self.scaling is not None: a, b = self.scaling[d](a), self.scaling[d](b) c, r = (a + b) / 2., (a - b) / 2. dAbs = 2. * np.abs(r) if self.kind == "UNIFORM": xd = np.linspace(a, b, n[d]) elif self.kind == "CHEBYSHEV": nodes, _ = np.polynomial.chebyshev.chebgauss(n[d]) xd = c + r * nodes elif self.kind == "GAUSSLEGENDRE": nodes, _ = np.polynomial.legendre.leggauss(n[d]) xd = c + r * nodes[::-1] - if len(xd) > 1 and reorder: - fejerOrdering = [n[d] - 1] + lowDiscrepancy(n[d] - 1) - xd = xd[fejerOrdering] if self.scalingInv is not None: xd = self.scalingInv[d](xd) xmat[:, d] = kroneckerer(xd, nleft, nright) nleft *= n[d] + nright = np.prod(n) + if nright > 1 and reorder: + fejerOrdering = [nright - 1] + lowDiscrepancy(nright - 1) + xmat = xmat[fejerOrdering, :] x, _ = checkParameterList(xmat, self.npar) return x diff --git a/rrompy/parameter/parameter_sampling/quadrature_sampler_total.py b/rrompy/parameter/parameter_sampling/quadrature_sampler_total.py new file mode 100644 index 0000000..c08a94c --- /dev/null +++ b/rrompy/parameter/parameter_sampling/quadrature_sampler_total.py @@ -0,0 +1,79 @@ +# Copyright (C) 2018 by the RROMPy authors +# +# This file is part of RROMPy. +# +# RROMPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RROMPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with RROMPy. If not, see . +# + +import numpy as np +from scipy.special import binom, factorial as fact +from .quadrature_sampler import QuadratureSampler +from rrompy.utilities.base.types import paramList +from rrompy.utilities.base import lowDiscrepancy +from rrompy.parameter import checkParameterList + +__all__ = ['QuadratureSamplerTotal'] + +class QuadratureSamplerTotal(QuadratureSampler): + """ + Generator of quadrature sample points for total degree polynomial + computations. + """ + + def generatePoints(self, n:int, reorder : bool = True) -> paramList: + """Array of sample points.""" + if hasattr(n, "__len__"): n = n[0] + d = self.npar + n1d = int((fact(d) * n) ** (1. / d)) + while binom(n1d + d - 1, d) > n: n1d -= 1 + + x = super().generatePoints([n1d] * d, reorder = False) + nTot = n1d ** d + indicesBase = np.zeros(nTot, dtype = int) + idxBase = [x + 1 for x in lowDiscrepancy(n1d - 1, inverse = True)] + linearIdxs = np.array(idxBase + [0]) + nleft, nright = 1, nTot + for j in range(d): + nright //= n1d + kronIn = np.repeat(linearIdxs, nright) + indicesBase += np.tile(kronIn, nleft) + nleft *= n1d + keepIdxs = np.zeros(nTot, dtype = bool) + keepIdxs[indicesBase < n1d] = True + xmat = x.data[keepIdxs, :] + if reorder: + fejerTot = np.array([nTot - 1] + list(lowDiscrepancy(nTot - 1))) + xmat = xmat[np.argsort(np.argsort(fejerTot[keepIdxs])), :] + x, _ = checkParameterList(xmat, d) + +# x = super().generatePoints([n1d] * d, reorder = True)[list(range(n))] +# if not reorder: +# fejerOrderingInv = lowDiscrepancy(n, inverse = True) +# xmat = x.data[fejerOrderingInv, :] +# x, _ = checkParameterList(xmat, d) + +# x = super().generatePoints([n1d] * d, reorder = False)[list(range(n))] +# nTot = n1d ** d +# keepIdxs = np.ones(nTot, dtype = bool) +# eps = nTot * 1e-10 +# gridPts = np.linspace(- eps, nTot - 1 + eps, 2 * (nTot - n) + 1)[1::2] +# keepIdxs[np.round(gridPts).astype(int)] = False +# xmat = x.data[keepIdxs, :] +# if reorder: +# fejerTot = np.array([nTot - 1] + list(lowDiscrepancy(nTot - 1))) +# xmat = xmat[np.argsort(np.argsort(fejerTot[keepIdxs])), :] +# x, _ = checkParameterList(xmat, d) + + return x + diff --git a/rrompy/parameter/parameter_sampling/random_sampler.py b/rrompy/parameter/parameter_sampling/random_sampler.py index cc68c21..4f51013 100644 --- a/rrompy/parameter/parameter_sampling/random_sampler.py +++ b/rrompy/parameter/parameter_sampling/random_sampler.py @@ -1,73 +1,77 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from .generic_sampler import GenericSampler +from rrompy.utilities.base.halton import haltonGenerate from rrompy.utilities.base.sobol import sobolGenerate -from rrompy.utilities.base.types import Np1D, List, paramList +from rrompy.utilities.base.types import List, paramList from rrompy.utilities.exception_manager import RROMPyException from rrompy.parameter import checkParameterList __all__ = ['RandomSampler'] class RandomSampler(GenericSampler): """Generator of quadrature sample points.""" - allowedKinds = ["UNIFORM", "SOBOL"] + allowedKinds = ["UNIFORM", "HALTON", "SOBOL"] def __init__(self, lims:paramList, kind : str = "UNIFORM", scaling : List[callable] = None, - scalingInv : List[callable] = None): + scalingInv : List[callable] = None, seed : int = 42): super().__init__(lims = lims, scaling = scaling, scalingInv = scalingInv) self.kind = kind + self.seed = seed def __str__(self) -> str: return "{}_{}".format(super().__str__(), self.kind) def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) @property def kind(self): """Value of kind.""" return self._kind @kind.setter def kind(self, kind): if kind.upper() not in self.allowedKinds: raise RROMPyException("Generator kind not recognized.") self._kind = kind.upper() - def generatePoints(self, n:int, seed : int = 420) -> paramList: + def generatePoints(self, n:int) -> paramList: """Array of quadrature points.""" if hasattr(n, "__len__"): n = n[0] if self.kind == "UNIFORM": - np.random.seed(seed) + np.random.seed(self.seed) xmat = np.random.uniform(size = (n, self.npar)) + elif self.kind == "HALTON": + xmat = haltonGenerate(self.npar, n, self.seed) else: - xmat = sobolGenerate(self.npar, n, seed) + xmat = sobolGenerate(self.npar, n, self.seed) for d in range(self.npar): a, b = self.lims(0, d), self.lims(1, d) if self.scaling is not None: a, b = self.scaling[d](a), self.scaling[d](b) xmat[:, d] = a + (b - a) * xmat[:, d] if self.scalingInv is not None: xmat[:, d] = self.scalingInv[d](xmat[:, d]) x, _ = checkParameterList(xmat, self.npar) return x - + diff --git a/rrompy/reduction_methods/base/generic_approximant.py b/rrompy/reduction_methods/base/generic_approximant.py index b6bbf9b..0a604b8 100644 --- a/rrompy/reduction_methods/base/generic_approximant.py +++ b/rrompy/reduction_methods/base/generic_approximant.py @@ -1,854 +1,909 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from abc import abstractmethod import numpy as np from itertools import product as iterprod from copy import deepcopy as copy from os import remove as osrm from rrompy.sampling.linear_problem import (SamplingEngineLinear, SamplingEngineLinearPOD) -from rrompy.utilities.base.types import (Np1D, DictAny, HFEng, List, strLst, - paramVal, paramList, sampList) +from rrompy.utilities.base.types import (Np1D, DictAny, HFEng, List, ListAny, + strLst, paramVal, paramList, sampList) from rrompy.utilities.base import purgeDict, verbosityDepth, getNewFilename from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPy_READY, RROMPy_FRAGILE) from rrompy.utilities.base import pickleDump, pickleLoad from rrompy.parameter import (emptyParameterList, checkParameter, checkParameterList) from rrompy.sampling import sampleList, emptySampleList __all__ = ['GenericApproximant'] def addNormFieldToClass(self, fieldName): def objFunc(self, mu:paramList, homogeneized : bool = False) -> float: uV = getattr(self.__class__, "get" + fieldName)(self, mu, homogeneized) val = self.HFEngine.norm(uV) return val setattr(self.__class__, "norm" + fieldName, objFunc) def addPlotFieldToClass(self, fieldName): def objFunc(self, mu:paramList, name : str = fieldName, save : str = None, what : strLst = 'all', saveFormat : str = "eps", saveDPI : int = 100, show : bool = True, homogeneized : bool = False, **figspecs): uV = getattr(self.__class__, "get" + fieldName)(self, mu, homogeneized) for j, u in enumerate(uV): self.HFEngine.plot(u, name = name + str(j), save = save, what = what, saveFormat = saveFormat, saveDPI = saveDPI, show = show, **figspecs) setattr(self.__class__, "plot" + fieldName, objFunc) def addOutParaviewFieldToClass(self, fieldName): def objFunc(self, mu:paramVal, name : str = fieldName, filename : str = "out", time : float = 0., what : strLst = 'all', forceNewFile : bool = True, folder : bool = False, filePW = None, homogeneized : bool = False): if not hasattr(self.HFEngine, "outParaview"): raise RROMPyException(("High fidelity engine cannot output to " "Paraview.")) uV = getattr(self.__class__, "get" + fieldName)(self, mu, homogeneized) for j, u in enumerate(uV): self.HFEngine.outParaview(u, name = name + str(j), filename = filename, time = time, what = what, forceNewFile = forceNewFile, folder = folder, filePW = filePW) setattr(self.__class__, "outParaview" + fieldName, objFunc) def addOutParaviewTimeDomainFieldToClass(self, fieldName): def objFunc(self, mu:paramVal, omega : float = None, timeFinal : float = None, periodResolution : int = 20, name : str = fieldName, filename : str = "out", forceNewFile : bool = True, folder : bool = False, homogeneized : bool = False): if not hasattr(self.HFEngine, "outParaviewTimeDomain"): raise RROMPyException(("High fidelity engine cannot output to " "Paraview.")) uV = getattr(self.__class__, "get" + fieldName)(self, mu, homogeneized) if omega is None: omega = np.real(mu) for j, u in enumerate(uV): self.HFEngine.outParaviewTimeDomain(u, omega = omega, timeFinal = timeFinal, periodResolution = periodResolution, name = name + str(j), filename = filename, forceNewFile = forceNewFile, folder = folder) setattr(self.__class__, "outParaviewTimeDomain" + fieldName, objFunc) class GenericApproximant: """ ABSTRACT ROM approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon. Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. trainedModel: Trained model evaluator. mu0: Default parameter. homogeneized: Whether to homogeneize Dirichlet BCs. 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; + approximant. Recognized keys are in parameterList{Soft,Critical}. + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots. + parameterListCritical: Recognized keys of critical approximant + parameters: - 'S': total number of samples current approximant relies upon. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. S: Number of solution snapshots over which current approximant is based upon. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ __all__ += [ftype + dtype for ftype, dtype in iterprod( ["norm", "plot", "outParaview", "outParaviewTimeDomain"], ["HF", "RHS", "Approx", "Res", "Err"])] def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() self._mode = RROMPy_READY self.verbosity = verbosity self.timestamp = timestamp if self.verbosity >= 10: verbosityDepth("INIT", ("Initializing approximant engine of " "type {}.").format(self.name()), timestamp = self.timestamp) self._HFEngine = HFEngine self.trainedModel = None self.lastSolvedHF = emptyParameterList() self.uHF = emptySampleList() - self._addParametersToList(["POD", "S"]) + self._addParametersToList(["POD"], [True], ["S"], [[1]]) if mu0 is None: if hasattr(self.HFEngine, "mu0"): self.mu0 = checkParameter(self.HFEngine.mu0) else: raise RROMPyException(("Center of approximation cannot be " "inferred from HF engine. Parameter " "required")) else: self.mu0 = checkParameter(mu0, self.HFEngine.npar) + self.resetSamples() self.homogeneized = homogeneized self.approxParameters = approxParameters - self.resetSamples() self._postInit() ### add norm{HF,RHS,Approx,Res,Err} methods """ Compute norm of * at arbitrary parameter. Args: mu: Target parameter. homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. Returns: Target norm of *. """ for objName in ["HF", "RHS", "Approx", "Res", "Err"]: addNormFieldToClass(self, objName) ### add plot{HF,RHS,Approx,Res,Err} methods """ Do some nice plots of * at arbitrary parameter. Args: mu: Target parameter. name(optional): Name to be shown as title of the plots. Defaults to 'u'. what(optional): Which plots to do. If list, can contain 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. save(optional): Where to save plot(s). Defaults to None, i.e. no saving. saveFormat(optional): Format for saved plot(s). Defaults to "eps". saveDPI(optional): DPI for saved plot(s). Defaults to 100. show(optional): Whether to show figure. Defaults to True. homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ for objName in ["HF", "RHS", "Approx", "Res", "Err"]: addPlotFieldToClass(self, objName) ### add outParaview{HF,RHS,Approx,Res,Err} methods """ Output * to ParaView file. Args: mu: Target parameter. name(optional): Base name to be used for data output. filename(optional): Name of output file. time(optional): Timestamp. what(optional): Which plots to do. If list, can contain 'MESH', 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. forceNewFile(optional): Whether to create new output file. filePW(optional): Fenics File entity (for time series). homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. """ for objName in ["HF", "RHS", "Approx", "Res", "Err"]: addOutParaviewFieldToClass(self, objName) ### add outParaviewTimeDomain{HF,RHS,Approx,Res,Err} methods """ Output * to ParaView file, converted to time domain. Args: mu: Target parameter. omega(optional): frequency. timeFinal(optional): final time of simulation. periodResolution(optional): number of time steps per period. name(optional): Base name to be used for data output. filename(optional): Name of output file. forceNewFile(optional): Whether to create new output file. homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. """ for objName in ["HF", "RHS", "Approx", "Res", "Err"]: addOutParaviewTimeDomainFieldToClass(self, objName) def _preInit(self): if not hasattr(self, "depth"): self.depth = 0 else: self.depth += 1 - def _addParametersToList(self, what:strLst): - if not hasattr(self, "parameterList"): - self.parameterList = [] - self.parameterList += what + @property + def parameterList(self): + """Value of parameterListSoft + parameterListCritical.""" + return self.parameterListSoft + self.parameterListCritical + + def _addParametersToList(self, whatSoft:strLst, defaultSoft:ListAny, + whatCritical : strLst = [], + defaultCritical : ListAny = [], + toBeExcluded : strLst = []): + if not hasattr(self, "parameterToBeExcluded"): + self.parameterToBeExcluded = [] + self.parameterToBeExcluded += toBeExcluded + if not hasattr(self, "parameterListSoft"): + self.parameterListSoft = [] + if not hasattr(self, "parameterDefaultSoft"): + self.parameterDefaultSoft = {} + if not hasattr(self, "parameterListCritical"): + self.parameterListCritical = [] + if not hasattr(self, "parameterDefaultCritical"): + self.parameterDefaultCritical = {} + for j, what in enumerate(whatSoft): + if what not in self.parameterToBeExcluded: + self.parameterListSoft += [what] + self.parameterDefaultSoft[what] = defaultSoft[j] + for j, what in enumerate(whatCritical): + if what not in self.parameterToBeExcluded: + self.parameterListCritical += [what] + self.parameterDefaultCritical[what] = defaultCritical[j] def _postInit(self): if self.depth == 0: if self.verbosity >= 10: verbosityDepth("DEL", "Done initializing.", timestamp = self.timestamp) del self.depth else: self.depth -= 1 def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() def __repr__(self) -> str: return self.__str__() + " at " + hex(id(self)) def setupSampling(self): """Setup sampling engine.""" RROMPyAssert(self._mode, message = "Cannot setup sampling engine.") if not hasattr(self, "_POD") or self._POD is None: return if self.POD: SamplingEngine = SamplingEngineLinearPOD else: SamplingEngine = SamplingEngineLinear self.samplingEngine = SamplingEngine(self.HFEngine, verbosity = self.verbosity) @property def HFEngine(self): """Value of HFEngine.""" return self._HFEngine @HFEngine.setter def HFEngine(self, HFEngine): raise RROMPyException("Cannot change HFEngine.") @property def mu0(self): """Value of mu0.""" return self._mu0 @mu0.setter def mu0(self, mu0): mu0 = checkParameter(mu0) if not hasattr(self, "_mu0") or mu0 != self.mu0: self.resetSamples() self._mu0 = mu0 @property def npar(self): """Number of parameters.""" return self.mu0.shape[1] @property def approxParameters(self): """Value of approximant parameters.""" return self._approxParameters @approxParameters.setter def approxParameters(self, approxParams): if not hasattr(self, "approxParameters"): self._approxParameters = {} approxParameters = purgeDict(approxParams, self.parameterList, dictname = self.name() + ".approxParameters", baselevel = 1) keyList = list(approxParameters.keys()) - if "POD" in keyList: - self.POD = approxParameters["POD"] - elif not hasattr(self, "_POD") or self._POD is None: - self.POD = True - if "S" in keyList: - self.S = approxParameters["S"] - elif hasattr(self, "_S") and self._S is not None: - self.S = self.S - else: - raise RROMPyException("Must specify S.") + for key in self.parameterListCritical: + if key in keyList: + setattr(self, "_" + key, self.parameterDefaultCritical[key]) + for key in self.parameterListSoft: + if key in keyList: + setattr(self, "_" + key, self.parameterDefaultSoft[key]) + fragile = False + for key in self.parameterListCritical: + if key in keyList: + val = approxParameters[key] + else: + val = getattr(self, "_" + key, None) + if val is None: + val = self.parameterDefaultCritical[key] + getattr(self.__class__, key, None).fset(self, val) + fragile = fragile or val is None + for key in self.parameterListSoft: + if key in keyList: + val = approxParameters[key] + else: + val = getattr(self, "_" + key, None) + if val is None: + val = self.parameterDefaultSoft[key] + getattr(self.__class__, key, None).fset(self, val) + if fragile: + self._mode = RROMPy_FRAGILE @property def POD(self): """Value of POD.""" return self._POD @POD.setter def POD(self, POD): if hasattr(self, "_POD"): PODold = self.POD else: PODold = -1 self._POD = POD self._approxParameters["POD"] = self.POD if PODold != self.POD: self.samplingEngine = None self.resetSamples() @property def S(self): """Value of S.""" return self._S @S.setter def S(self, S): if not hasattr(S, "__len__"): S = [S] if any([s <= 0 for s in S]): raise RROMPyException("S must be positive.") if hasattr(self, "_S") and self._S is not None: Sold = tuple(self.S) else: Sold = -1 self._S = S self._approxParameters["S"] = self.S if Sold != tuple(self.S): self.resetSamples() @property def homogeneized(self): """Value of homogeneized.""" return self._homogeneized @homogeneized.setter def homogeneized(self, homogeneized): if not hasattr(self, "_homogeneized"): self._homogeneized = None if homogeneized != self.homogeneized: self._homogeneized = homogeneized self.resetSamples() @property def trainedModel(self): """Value of trainedModel.""" return self._trainedModel @trainedModel.setter def trainedModel(self, trainedModel): self._trainedModel = trainedModel + if self._trainedModel is not None: + self._trainedModel.lastSolvedAppReduced = emptyParameterList() + self._trainedModel.lastSolvedApp = emptyParameterList() self.lastSolvedAppReduced = emptyParameterList() self.lastSolvedApp = emptyParameterList() self.uAppReduced = emptySampleList() self.uApp = emptySampleList() def resetSamples(self): if hasattr(self, "samplingEngine") and self.samplingEngine is not None: self.samplingEngine.resetHistory() else: self.setupSampling() self._mode = RROMPy_READY def plotSamples(self, 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. """ RROMPyAssert(self._mode, message = "Cannot plot samples.") self.samplingEngine.plotSamples(name = name, save = save, what = what, saveFormat = saveFormat, saveDPI = saveDPI, **figspecs) def outParaviewSamples(self, name : str = "u", filename : str = "out", times : Np1D = None, what : strLst = 'all', forceNewFile : bool = True, folders : bool = False, filePW = None): """ Output samples to ParaView file. Args: name(optional): Base name to be used for data output. filename(optional): Name of output file. times(optional): Timestamps. what(optional): Which plots to do. If list, can contain 'MESH', 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. forceNewFile(optional): Whether to create new output file. folders(optional): Whether to split output in folders. filePW(optional): Fenics File entity (for time series). """ RROMPyAssert(self._mode, message = "Cannot output samples.") self.samplingEngine.outParaviewSamples(name = name, filename = filename, times = times, what = what, forceNewFile = forceNewFile, folders = folders, filePW = filePW) def outParaviewTimeDomainSamples(self, omegas : Np1D = None, timeFinal : Np1D = None, periodResolution : int = 20, name : str = "u", filename : str = "out", forceNewFile : bool = True, folders : bool = False): """ Output samples to ParaView file, converted to time domain. Args: omegas(optional): frequencies. timeFinal(optional): final time of simulation. periodResolution(optional): number of time steps per period. name(optional): Base name to be used for data output. filename(optional): Name of output file. forceNewFile(optional): Whether to create new output file. folders(optional): Whether to split output in folders. """ RROMPyAssert(self._mode, message = "Cannot output samples.") self.samplingEngine.outParaviewTimeDomainSamples(omegas = omegas, timeFinal = timeFinal, periodResolution = periodResolution, name = name, filename = filename, forceNewFile = forceNewFile, folders = folders) + def setSamples(self, samplingEngine): + """Copy samplingEngine and samples.""" + if self.verbosity >= 10: + verbosityDepth("INIT", "Transfering samples.", + timestamp = self.timestamp) + self.samplingEngine = samplingEngine + if self.verbosity >= 10: + verbosityDepth("DEL", "Done transfering samples.", + timestamp = self.timestamp) + def setTrainedModel(self, model): """Deepcopy approximation from trained model.""" if hasattr(model, "storeTrainedModel"): verb = model.verbosity model.verbosity = 0 fileOut = model.storeTrainedModel() model.verbosity = verb else: try: fileOut = getNewFilename("trained_model", "pkl") pickleDump(model.data.__dict__, fileOut) except: raise RROMPyException(("Failed to store model data. Parameter " "model must have either " "storeTrainedModel or " "data.__dict__ properties.")) self.loadTrainedModel(fileOut) osrm(fileOut) @abstractmethod def setupApprox(self): """ Setup approximant. (ABSTRACT) Any specialization should include something like if self.checkComputedApprox(): return RROMPyAssert(self._mode, message = "Cannot setup approximant.") ... self.trainedModel = ... self.trainedModel.data = ... self.trainedModel.data.approxParameters = copy( self.approxParameters) """ pass def checkComputedApprox(self) -> bool: """ Check if setup of new approximant is not needed. Returns: True if new setup is not needed. False otherwise. """ return self._mode == RROMPy_FRAGILE or (self.trainedModel is not None and self.trainedModel.data.approxParameters == self.approxParameters) def setHF(self, muHF:paramList, uHF:sampleList, append : bool = False) -> List[int]: """Assign high fidelity solution.""" newSolvedHF, _ = checkParameterList(muHF, self.npar) newuHF = sampleList(uHF) if append: self.lastSolvedHF.append(newSolvedHF) self.uHF.append(newuHF) return list(range(len(self.uHF) - len(newuHF), len(self.uHF))) self.lastSolvedHF, _ = checkParameterList(newSolvedHF, self.npar) self.uHF = sampleList(newuHF) return list(range(len(self.uHF))) def evalHF(self, mu:paramList, append : bool = False, prune : bool = True) -> List[int]: """ Find high fidelity solution with original parameters and arbitrary parameter. Args: mu: Target parameter. append(optional): Whether to append new HF solutions to old ones. prune(optional): Whether to remove duplicates of already appearing HF solutions. """ mu, _ = checkParameterList(mu, self.npar) idx = np.empty(len(mu), dtype = np.int) if prune: jExtra = np.zeros(len(mu), dtype = bool) muKeep = emptyParameterList() muExtra = copy(muKeep) for j in range(len(mu)): jPos = self.lastSolvedHF.find(mu[j]) if jPos is not None: idx[j] = jPos muKeep.append(mu[j]) else: jExtra[j] = True muExtra.append(mu[j]) if len(muKeep) > 0 and not append: idx[~jExtra] = self.setHF(muKeep, self.uHF[idx[~jExtra]], append) append = True else: jExtra = np.ones(len(mu), dtype = bool) muExtra = mu if len(muExtra) > 0: newuHFs = self.samplingEngine.solveLS(muExtra, homogeneized = self.homogeneized) idx[jExtra] = self.setHF(muExtra, newuHFs, append) return list(idx) def setApproxReduced(self, muApp:paramList, uApp:sampleList, append : bool = False) -> List[int]: """Assign high fidelity solution.""" newSolvedApp, _ = checkParameterList(muApp, self.npar) newuApp = sampleList(uApp) if append: self.lastSolvedAppReduced.append(newSolvedApp) self.uAppReduced.append(newuApp) return list(range(len(self.uAppReduced) - len(newuApp), len(self.uAppReduced))) self.lastSolvedAppReduced, _ = checkParameterList(newSolvedApp, self.npar) self.uAppReduced = sampleList(newuApp) return list(range(len(self.uAppReduced))) def evalApproxReduced(self, mu:paramList, append : bool = False, prune : bool = True) -> List[int]: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. """ self.setupApprox() mu, _ = checkParameterList(mu, self.npar) idx = np.empty(len(mu), dtype = np.int) if prune: jExtra = np.zeros(len(mu), dtype = bool) muKeep = emptyParameterList() muExtra = copy(muKeep) for j in range(len(mu)): jPos = self.lastSolvedAppReduced.find(mu[j]) if jPos is not None: idx[j] = jPos muKeep.append(mu[j]) else: jExtra[j] = True muExtra.append(mu[j]) if len(muKeep) > 0 and not append: idx[~jExtra] = self.setApproxReduced(muKeep, self.uAppReduced[idx[~jExtra]], append) append = True else: jExtra = np.ones(len(mu), dtype = bool) muExtra = mu if len(muExtra) > 0: newuApps = self.trainedModel.getApproxReduced(muExtra) idx[jExtra] = self.setApproxReduced(muExtra, newuApps, append) return list(idx) def setApprox(self, muApp:paramList, uApp:sampleList, append : bool = False) -> List[int]: """Assign high fidelity solution.""" newSolvedApp, _ = checkParameterList(muApp, self.npar) newuApp = sampleList(uApp) if append: self.lastSolvedApp.append(newSolvedApp) self.uApp.append(newuApp) return list(range(len(self.uApp) - len(newuApp), len(self.uApp))) self.lastSolvedApp, _ = checkParameterList(newSolvedApp, self.npar) self.uApp = sampleList(newuApp) return list(range(len(self.uApp))) def evalApprox(self, mu:paramList, append : bool = False, prune : bool = True) -> List[int]: """ Evaluate approximant at arbitrary parameter. Args: mu: Target parameter. """ self.setupApprox() mu, _ = checkParameterList(mu, self.npar) idx = np.empty(len(mu), dtype = np.int) if prune: jExtra = np.zeros(len(mu), dtype = bool) muKeep = emptyParameterList() muExtra = copy(muKeep) for j in range(len(mu)): jPos = self.lastSolvedApp.find(mu[j]) if jPos is not None: idx[j] = jPos muKeep.append(mu[j]) else: jExtra[j] = True muExtra.append(mu[j]) if len(muKeep) > 0 and not append: idx[~jExtra] = self.setApprox(muKeep, self.uApp[idx[~jExtra]], append) append = True else: jExtra = np.ones(len(mu), dtype = bool) muExtra = mu if len(muExtra) > 0: newuApps = self.trainedModel.getApprox(muExtra) idx[jExtra] = self.setApprox(muExtra, newuApps, append) return list(idx) def getHF(self, mu:paramList, homogeneized : bool = False, append : bool = False, prune : bool = True) -> sampList: """ Get HF solution at arbitrary parameter. Args: mu: Target parameter. homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. Returns: HFsolution. """ mu, _ = checkParameterList(mu, self.npar) idx = self.evalHF(mu, append = append, prune = prune) uHFs = self.uHF(idx) if self.homogeneized and not homogeneized: for j, m in enumerate(mu): uHFs[j] += self.HFEngine.liftDirichletData(m) if not self.homogeneized and homogeneized: for j, m in enumerate(mu): uHFs[j] -= self.HFEngine.liftDirichletData(m) return uHFs def getRHS(self, mu:paramList, homogeneized : bool = False) -> sampList: """ 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 getApproxReduced(self, mu:paramList, append : bool = False, prune : bool = True) -> sampList: """ Get approximant at arbitrary parameter. Args: mu: Target parameter. Returns: Reduced approximant. """ mu, _ = checkParameterList(mu, self.npar) idx = self.evalApproxReduced(mu, append = append, prune = prune) uAppRs = self.uAppReduced(idx) return uAppRs def getApprox(self, mu:paramList, homogeneized : bool = False, append : bool = False, prune : bool = True) -> sampList: """ Get approximant at arbitrary parameter. Args: mu: Target parameter. homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. Returns: Approximant. """ mu, _ = checkParameterList(mu, self.npar) idx = self.evalApprox(mu, append = append, prune = prune) uApps = self.uApp(idx) if self.homogeneized and not homogeneized: for j, m in enumerate(mu): uApps[j] += self.HFEngine.liftDirichletData(m) if not self.homogeneized and homogeneized: for j, m in enumerate(mu): uApps[j] -= self.HFEngine.liftDirichletData(m) return uApps def getRes(self, mu:paramList, homogeneized : bool = False) -> sampList: """ 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.getApprox(mu, homogeneized), mu, homogeneized = homogeneized) def getErr(self, mu:paramList, homogeneized : bool = False) -> sampList: """ Get error at arbitrary parameter. Args: mu: Target parameter. homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. Returns: Approximant error. """ return self.getApprox(mu, homogeneized) - self.getHF(mu, homogeneized) def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ self.setupApprox() if self.verbosity >= 20: verbosityDepth("INIT", "Computing poles of model.", timestamp = self.timestamp) poles = self.trainedModel.getPoles() if self.verbosity >= 20: verbosityDepth("DEL", "Done computing poles.", timestamp = self.timestamp) return poles def storeTrainedModel(self, filenameBase : str = "trained_model", forceNewFile : bool = True) -> str: """Store trained reduced model to file.""" self.setupApprox() if self.verbosity >= 20: verbosityDepth("INIT", "Storing trained model to file.", timestamp = self.timestamp) if forceNewFile: filename = getNewFilename(filenameBase, "pkl") else: filename = "{}.pkl".format(filenameBase) pickleDump(self.trainedModel.data.__dict__, filename) if self.verbosity >= 20: verbosityDepth("DEL", "Done storing trained model.", timestamp = self.timestamp) return filename def loadTrainedModel(self, filename:str): """Load trained reduced model from file.""" if self.verbosity >= 20: verbosityDepth("INIT", "Loading pre-trained model from file.", timestamp = self.timestamp) datadict = pickleLoad(filename) name = datadict.pop("name") if name == "TrainedModelPade": from rrompy.reduction_methods.trained_model import \ TrainedModelPade as tModel elif name == "TrainedModelRB": from rrompy.reduction_methods.trained_model import \ TrainedModelRB as tModel else: raise RROMPyException(("Trained model name not recognized. " "Loading failed.")) self.mu0 = datadict.pop("mu0") from rrompy.reduction_methods.trained_model import TrainedModelData trainedModel = tModel() trainedModel.verbosity = self.verbosity trainedModel.timestamp = self.timestamp data = TrainedModelData(name, self.mu0, datadict.pop("projMat"), datadict.pop("rescalingExp")) if "mus" in datadict: data.mus = datadict.pop("mus") approxParameters = datadict.pop("approxParameters") data.approxParameters = copy(approxParameters) if "sampler" in approxParameters: self._approxParameters["sampler"] = approxParameters.pop("sampler") self.approxParameters = copy(approxParameters) if "mus" in data.__dict__: self.mus = copy(data.mus) if name == "TrainedModelPade": self.scaleFactor = datadict.pop("scaleFactor") data.scaleFactor = self.scaleFactor for key in datadict: setattr(data, key, datadict[key]) trainedModel.data = data self.trainedModel = trainedModel self._mode = RROMPy_FRAGILE if self.verbosity >= 20: verbosityDepth("DEL", "Done loading pre-trained model.", timestamp = self.timestamp) diff --git a/rrompy/reduction_methods/base/rb_utils.py b/rrompy/reduction_methods/base/rb_utils.py index 5286ac1..78b37cc 100644 --- a/rrompy/reduction_methods/base/rb_utils.py +++ b/rrompy/reduction_methods/base/rb_utils.py @@ -1,64 +1,62 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np -#from copy import deepcopy as copy from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List, sampList from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.sampling import sampleList __all__ = ['projectAffineDecomposition'] def projectAffineDecomposition(As:List[Np2D], bs:List[Np1D], pMat:sampList, ARBsOld : List[Np2D] = None, bRBsOld : List[Np1D] = None, pMatOld : sampList = None)\ -> Tuple[List[Np2D], List[Np1D]]: """Project affine decomposition of linear system onto basis.""" RROMPyAssert((ARBsOld is None, bRBsOld is None), (pMatOld is None, pMatOld is None), "Old affine projected terms") if isinstance(pMat, (sampleList,)): pMat = pMat.data pMatH = pMat.T.conj() ARBs = [None] * len(As) bRBs = [None] * len(bs) if pMatOld is None: for j in range(len(As)): ARBs[j] = pMatH.dot(As[j].dot(pMat)) for j in range(len(bs)): bRBs[j] = pMatH.dot(bs[j]) else: RROMPyAssert((len(ARBsOld), len(bRBsOld)), (len(As), len(bs)), "Old affine projected terms") if isinstance(pMatOld, (sampleList,)): pMatOld = pMatOld.data pMatOldH = pMatOld.T.conj() Sold = pMatOld.shape[1] Snew = pMat.shape[1] for j in range(len(As)): ARBs[j] = np.empty((Sold + Snew, Sold + Snew), dtype = np.complex) ARBs[j][: Sold, : Sold] = ARBsOld[j] ARBs[j][: Sold, Sold :] = pMatOldH.dot(As[j].dot(pMat)) ARBs[j][Sold :, : Sold] = pMatH.dot(As[j].dot(pMatOld)) ARBs[j][Sold :, Sold :] = pMatH.dot(As[j].dot(pMat)) for j in range(len(bs)): bRBs[j] = np.empty((Sold + Snew), dtype = np.complex) bRBs[j][: Sold] = bRBsOld[j] -# bRBs[j][: Sold] = copy(bRBsOld[j]) bRBs[j][Sold :] = pMatH.dot(bs[j]) return ARBs, bRBs diff --git a/rrompy/reduction_methods/centered/generic_centered_approximant.py b/rrompy/reduction_methods/centered/generic_centered_approximant.py index cf3b5db..48b9846 100644 --- a/rrompy/reduction_methods/centered/generic_centered_approximant.py +++ b/rrompy/reduction_methods/centered/generic_centered_approximant.py @@ -1,111 +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 . # import numpy as np from rrompy.reduction_methods.base.generic_approximant import ( GenericApproximant) -from rrompy.utilities.base.types import DictAny, HFEng, paramVal -from rrompy.utilities.base import purgeDict, verbosityDepth -from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert +from rrompy.utilities.base.types import paramList +from rrompy.utilities.base import verbosityDepth +from rrompy.utilities.exception_manager import RROMPyAssert __all__ = ['GenericCenteredApproximant'] class GenericCenteredApproximant(GenericApproximant): """ ROM single-point approximant computation for parametric problems (ABSTRACT). Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon. Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. homogeneized: Whether to homogeneize Dirichlet BCs. 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; + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots. + parameterListCritical: Recognized keys of critical approximant + parameters: - 'S': total number of samples current approximant relies upon. POD: Whether to compute QR factorization of derivatives. S: Number of solution snapshots over which current approximant is based upon. initialHFData: HF problem initial data. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ @property def S(self): """Value of S.""" return self._S @S.setter def S(self, S): GenericApproximant.S.fset(self, S) RROMPyAssert(len(self.S), 1, "Length of S") def computeDerivatives(self): """Compute derivatives of solution map starting from order 0.""" RROMPyAssert(self._mode, message = "Cannot start derivative computation.") - if self.samplingEngine.nsamples < np.prod(self.S): + if self.samplingEngine.nsamples != np.prod(self.S): if self.verbosity >= 5: verbosityDepth("INIT", "Starting computation of derivatives.", timestamp = self.timestamp) self.samplingEngine.iterSample([self.mu0[0]] * np.prod(self.S), homogeneized = self.homogeneized) if self.verbosity >= 5: verbosityDepth("DEL", "Done computing derivatives.", timestamp = self.timestamp) - def normApprox(self, mu:complex, homogeneized : bool = False) -> float: + def normApprox(self, mu:paramList, homogeneized : bool = False) -> float: """ Compute norm of approximant at arbitrary parameter. Args: mu: Target parameter. homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. Returns: Target norm of approximant. """ if not self.POD or self.homogeneized != homogeneized: return super().normApprox(mu, homogeneized) return np.linalg.norm(self.getApproxReduced(mu), axis = 0) diff --git a/rrompy/reduction_methods/centered/rational_pade.py b/rrompy/reduction_methods/centered/rational_pade.py index e54de27..5356430 100644 --- a/rrompy/reduction_methods/centered/rational_pade.py +++ b/rrompy/reduction_methods/centered/rational_pade.py @@ -1,441 +1,440 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.reduction_methods.base import checkRobustTolerance from rrompy.reduction_methods.trained_model import (TrainedModelData, TrainedModelPade as tModel) from .generic_centered_approximant import GenericCenteredApproximant from rrompy.utilities.base.types import (Np1D, Np2D, Tuple, DictAny, HFEng, paramVal, paramList, sampList) -from rrompy.utilities.base import verbosityDepth, purgeDict +from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import (nextDerivativeIndices, hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI) from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) __all__ = ['RationalPade'] class RationalPade(GenericCenteredApproximant): """ 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; - 'S': total number of samples current approximant relies upon; + - 'E': number of derivatives used to compute Pade'; defaults to 0; - 'M': degree of Pade' approximant numerator; defaults to 0; - 'N': degree of Pade' approximant denominator; defaults to 0; - 'robustTol': tolerance for robust Pade' denominator management; defaults to 0. Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. homogeneized: Whether to homogeneize Dirichlet BCs. 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; + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots; + - 'E': number of derivatives used to compute Pade'; - 'M': degree of Pade' approximant numerator; - 'N': degree of Pade' approximant denominator; - 'robustTol': tolerance for robust Pade' denominator management. + parameterListCritical: Recognized keys of critical approximant + parameters: + - 'S': total number of samples current approximant relies upon. POD: Whether to compute QR factorization of derivatives. S: Number of solution snapshots over which current approximant is based upon. M: Numerator degree of approximant. N: Denominator degree of approximant. robustTol: Tolerance for robust Pade' denominator management. E: Complete derivative depth level of S. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. G: Square Numpy 2D vector of size (N+1) corresponding to Pade' denominator matrix (see paper). """ def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() - self._addParametersToList(["M", "N", "robustTol"]) + self._addParametersToList(["E", "M", "N", "robustTol"], [-1, 0, 0, 0]) super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, homogeneized = homogeneized, verbosity = verbosity, timestamp = timestamp) 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"], - True, True, baselevel = 1) - keyList = list(approxParameters.keys()) - GenericCenteredApproximant.approxParameters.fset(self, - approxParametersCopy) - if "robustTol" in keyList: - self.robustTol = approxParameters["robustTol"] - elif not hasattr(self, "_robustTol") or self._robustTol is None: - self.robustTol = 0 - if "M" in keyList: - self.M = approxParameters["M"] - elif hasattr(self, "_M") and self._M is not None: - self.M = self.M - else: - self.M = 0 - if "N" in keyList: - self.N = approxParameters["N"] - elif hasattr(self, "_N") and self._N is not None: - self.N = self.N - else: - self.N = 0 - @property def M(self): """Value of M..""" return self._M @M.setter def M(self, M): if M < 0: raise RROMPyException("M must be non-negative.") self._M = M self._approxParameters["M"] = self.M - if hasattr(self, "E") and self.E < self.M: - RROMPyWarning("Prescribed S is too small. Decreasing M.") + if hasattr(self, "_E") and self.E >= 0 and self.E < self.M: + RROMPyWarning("Prescribed E is too small. Decreasing M.") self.M = self.E @property def N(self): """Value of N.""" return self._N @N.setter def N(self, N): if N < 0: raise RROMPyException("N must be non-negative.") self._N = N self._approxParameters["N"] = self.N - if hasattr(self, "E") and self.E < self.N: - RROMPyWarning("Prescribed S is too small. Decreasing N.") + if hasattr(self, "_E") and self.E >= 0 and self.E < self.N: + RROMPyWarning("Prescribed E is too small. Decreasing N.") self.N = self.E + @property + def E(self): + """Value of E.""" + return self._E + @E.setter + def E(self, E): + if E < 0: + if not hasattr(self, "_S"): + raise RROMPyException(("Value of E must be positive if S is " + "not yet assigned.")) + E = np.sum(hashI(np.prod(self.S), self.npar)) - 1 + self._E = E + self._approxParameters["E"] = self.E + if (hasattr(self, "_S") + and self.E >= np.sum(hashI(np.prod(self.S), self.npar))): + RROMPyWarning("Prescribed S is too small. Decreasing E.") + self.E = -1 + if hasattr(self, "_M"): self.M = self.M + if hasattr(self, "_N"): self.N = self.N + @property def robustTol(self): """Value of tolerance for robust Pade' denominator management.""" return self._robustTol @robustTol.setter def robustTol(self, robustTol): if robustTol < 0.: RROMPyWarning(("Overriding prescribed negative robustness " "tolerance to 0.")) robustTol = 0. self._robustTol = robustTol self._approxParameters["robustTol"] = self.robustTol @property def S(self): """Value of S.""" return self._S @S.setter def S(self, S): GenericCenteredApproximant.S.fset(self, S) - self.E = np.sum(hashI(np.prod(self.S), self.npar)) - 1 if hasattr(self, "_M"): self.M = self.M if hasattr(self, "_N"): self.N = self.N + if hasattr(self, "_E"): self.E = self.E def _setupDenominator(self): """Compute Pade' denominator.""" + RROMPyAssert(self._mode, message = "Cannot setup denominator.") if self.verbosity >= 7: verbosityDepth("INIT", "Starting computation of denominator.", timestamp = self.timestamp) while self.N > 0: if self.POD: ev, eV = self.findeveVGQR() else: ev, eV = self.findeveVGExplicit() newParameters = checkRobustTolerance(ev, self.N, self.robustTol) if not newParameters: break self.approxParameters = newParameters if self.N <= 0: eV = np.ones((1, 1)) q = np.zeros(tuple([self.N + 1] * self.npar), dtype = np.complex) for j in range(eV.shape[0]): q[tuple(hashI(j, self.npar))] = eV[j, 0] if self.verbosity >= 7: verbosityDepth("DEL", "Done computing denominator.", timestamp = self.timestamp) return q def _setupNumerator(self): """Compute Pade' numerator.""" + RROMPyAssert(self._mode, message = "Cannot setup numerator.") if self.verbosity >= 7: verbosityDepth("INIT", "Starting computation of numerator.", timestamp = self.timestamp) - P = np.zeros((np.prod(self.S),) + tuple([self.M + 1] * self.npar), + P = np.zeros(tuple([self.M + 1] * self.npar) + (np.prod(self.S),), dtype = np.complex) mEnd = hashD([self.M + 1] + [0] * (self.npar - 1)) nEnd = hashD([self.N + 1] + [0] * (self.npar - 1)) mnIdxs = nextDerivativeIndices([], self.npar, max(mEnd, nEnd)) for j in range(mEnd): mIdx = mnIdxs[j] for n in range(nEnd): diffIdx = [x - y for (x, y) in zip(mIdx, mnIdxs[n])] if all([x >= 0 for x in diffIdx]): - P[(hashD(diffIdx),) + (mIdx,)] = ( + P[tuple(mIdx) + (hashD(diffIdx),)] = ( self.trainedModel.data.Q[tuple(mnIdxs[n])]) - return self.rescaleByParameter(P.T).T + if self.verbosity >= 7: + verbosityDepth("DEL", "Done computation numerator.", + timestamp = self.timestamp) + return self.rescaleByParameter(P).T def setupApprox(self): """ Compute Pade' approximant. SVD-based robust eigenvalue management. """ if self.checkComputedApprox(): return + RROMPyAssert(self._mode, message = "Cannot setup approximant.") if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name()), timestamp = self.timestamp) self.computeDerivatives() if self.trainedModel is None: self.trainedModel = tModel() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp data = TrainedModelData(self.trainedModel.name(), self.mu0, None, self.HFEngine.rescalingExp) data.polytype = "MONOMIAL" self.trainedModel.data = data else: self.trainedModel = self.trainedModel if self.N > 0: Q = self._setupDenominator() else: self.setScaleParameter() Q = np.ones(1, dtype = np.complex) self.trainedModel.data.Q = copy(Q) self.trainedModel.data.scaleFactor = self.scaleFactor self.trainedModel.data.projMat = copy(self.samplingEngine.samples( list(range(np.prod(self.S))))) P = self._setupNumerator() if self.POD: P = np.tensordot(self.samplingEngine.RPOD, P, axes = ([-1], [0])) self.trainedModel.data.P = copy(P) self.trainedModel.data.approxParameters = copy(self.approxParameters) if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.", timestamp = self.timestamp) def setScaleParameter(self) -> Np2D: """Compute parameter for rescaling.""" RROMPyAssert(self._mode, message = "Cannot compute rescaling factor.") self.computeDerivatives() self.scaleFactor = [1.] * self.npar for d in range(self.npar): hashesd = [0] for n in range(1, self.E + 1): hashesd += [hashD([0] * (d - 1) + [n] + [0] * (self.npar - d - 1))] if self.POD: Rd = self.samplingEngine.RPOD[: hashesd[-1] + 1, hashesd] Gd = np.diag(Rd.T.conj().dot(Rd)) else: DerEd = self.samplingEngine.samples(hashesd) Gd = self.HFEngine.norm(DerEd) - scaleCoeffs = np.polyfit(np.arange(len(Gd)), np.log(Gd), 1) - self.scaleFactor[d] = np.exp(- scaleCoeffs[0] / 2.) + if len(Gd) > 1: + scaleCoeffs = np.polyfit(np.arange(len(Gd)), np.log(Gd), 1) + self.scaleFactor[d] = np.exp(- scaleCoeffs[0] / 2.) def rescaleByParameter(self, R:Np2D) -> Np2D: """ Rescale by scale parameter. Args: R: Matrix whose columns need rescaling. Returns: Rescaled matrix. """ RIdxs = nextDerivativeIndices([], self.npar, R.shape[-1]) Rscaled = copy(R) for j, RIdx in enumerate(RIdxs): Rscaled[..., j] *= np.prod([x ** y for (x, y) in zip(self.scaleFactor, RIdx)]) return Rscaled def buildG(self): """Assemble Pade' denominator matrix.""" RROMPyAssert(self._mode, message = "Cannot compute G matrix.") self.computeDerivatives() if self.verbosity >= 10: verbosityDepth("INIT", "Building gramian matrix.", timestamp = self.timestamp) eStart = hashD([self.E] + [0] * (self.npar - 1)) eEnd = hashD([self.E + 1] + [0] * (self.npar - 1)) eIdxs = [hashI(e, self.npar) for e in range(eStart, eEnd)] nEnd = hashD([self.N + 1] + [0] * (self.npar - 1)) nIdxs = nextDerivativeIndices([], self.npar, nEnd) self.setScaleParameter() if self.POD: RPODE = self.rescaleByParameter(self.samplingEngine.RPOD[: eEnd, : eEnd]) else: DerE = self.rescaleByParameter(self.samplingEngine.samples( list(range(eEnd))).data) self.G = np.zeros((nEnd, nEnd), dtype = np.complex) for eIdx in eIdxs: nLoc = [] samplesIdxs = [] for n, nIdx in enumerate(nIdxs): diffIdx = [x - y for (x, y) in zip(eIdx, nIdx)] if all([x >= 0 for x in diffIdx]): nLoc += [n] samplesIdxs += [hashD(diffIdx)] if self.POD: RPODELoc = RPODE[: samplesIdxs[-1] + 1, samplesIdxs] GLoc = RPODELoc.T.conj().dot(RPODELoc) else: DerELoc = DerE[:, samplesIdxs] GLoc = self.HFEngine.innerProduct(DerELoc, DerELoc) for j in range(len(nLoc)): self.G[nLoc[j], nLoc] = self.G[nLoc[j], nLoc] + GLoc[j] if self.verbosity >= 10: verbosityDepth("DEL", "Done building gramian.", timestamp = self.timestamp) def findeveVGExplicit(self) -> Tuple[Np1D, Np2D]: """ Compute explicitly eigenvalues and eigenvectors of Pade' denominator matrix. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") self.buildG() if self.verbosity >= 7: verbosityDepth("INIT", "Solving eigenvalue problem for gramian matrix.", timestamp = self.timestamp) ev, eV = np.linalg.eigh(self.G) if self.verbosity >= 5: try: condev = ev[-1] / ev[0] except: condev = np.inf verbosityDepth("MAIN", ("Solved eigenvalue problem of size {} " "with condition number {:.4e}.").format( self.G.shape[0], condev), timestamp = self.timestamp) if self.verbosity >= 7: verbosityDepth("DEL", "Done solving eigenvalue problem.", timestamp = self.timestamp) return ev, eV def findeveVGQR(self) -> Tuple[Np1D, Np2D]: """ Compute eigenvalues and eigenvectors of Pade' denominator matrix through SVD of R factor. Returns: Eigenvalues in ascending order and corresponding eigenvector matrix. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") RROMPyAssert(self.POD, True, "POD value") self.computeDerivatives() eStart = hashD([self.E] + [0] * (self.npar - 1)) eEnd = hashD([self.E + 1] + [0] * (self.npar - 1)) eIdxs = [hashI(e, self.npar) for e in range(eStart, eEnd)] nEnd = hashD([self.N + 1] + [0] * (self.npar - 1)) nIdxs = nextDerivativeIndices([], self.npar, nEnd) self.setScaleParameter() RPODE = self.rescaleByParameter(self.samplingEngine.RPOD[: eEnd, : eEnd]) Rstack = np.zeros((RPODE.shape[0] * (eEnd - eStart), nEnd), dtype = np.complex) for k, eIdx in enumerate(eIdxs): nLoc = [] samplesIdxs = [] for n, nIdx in enumerate(nIdxs): diffIdx = [x - y for (x, y) in zip(eIdx, nIdx)] if all([x >= 0 for x in diffIdx]): nLoc += [n] samplesIdxs += [hashD(diffIdx)] RPODELoc = RPODE[:, samplesIdxs] for j in range(len(nLoc)): Rstack[k * RPODE.shape[0] : (k + 1) * RPODE.shape[0], nLoc[j]] = RPODELoc[:, j] if self.verbosity >= 7: verbosityDepth("INIT", ("Solving svd for square root of " "gramian matrix."), timestamp = self.timestamp) sizeI = Rstack.shape _, s, V = np.linalg.svd(Rstack, full_matrices = False) eV = V[::-1, :].T.conj() if self.verbosity >= 5: try: condev = s[0] / s[-1] except: condev = np.inf verbosityDepth("MAIN", ("Solved svd problem of size {} x {} with " "condition number {:.4e}.").format(*sizeI, condev), timestamp = self.timestamp) if self.verbosity >= 7: verbosityDepth("DEL", "Done solving eigenvalue problem.", timestamp = self.timestamp) return s[::-1], eV def centerNormalize(self, mu : paramList = [], mu0 : paramVal = None) -> paramList: """ Compute normalized parameter to be plugged into approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.mu0. Returns: Normalized parameter. """ return self.trainedModel.centerNormalize(mu, mu0) def getResidues(self) -> sampList: """ Obtain approximant residues. Returns: Matrix with residues as columns. """ return self.trainedModel.getResidues() diff --git a/rrompy/reduction_methods/centered/rb_centered.py b/rrompy/reduction_methods/centered/rb_centered.py index 9751b9b..c690b2a 100644 --- a/rrompy/reduction_methods/centered/rb_centered.py +++ b/rrompy/reduction_methods/centered/rb_centered.py @@ -1,203 +1,191 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .generic_centered_approximant import GenericCenteredApproximant from rrompy.reduction_methods.trained_model import TrainedModelRB as tModel from rrompy.reduction_methods.trained_model import TrainedModelData from rrompy.reduction_methods.base.rb_utils import projectAffineDecomposition from rrompy.utilities.base.types import (Np1D, Np2D, Tuple, List, DictAny, HFEng, paramVal, sampList) -from rrompy.utilities.base import purgeDict, verbosityDepth -from rrompy.utilities.exception_manager import RROMPyException, RROMPyWarning +from rrompy.utilities.base import verbosityDepth +from rrompy.utilities.exception_manager import (RROMPyException, RROMPyWarning, + RROMPyAssert) __all__ = ['RBCentered'] class RBCentered(GenericCenteredApproximant): """ ROM single-point fast RB approximant computation for parametric problems with polynomial dependence up to degree 2. 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; - 'R': rank for Galerkin projection; defaults to prod(S). Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. homogeneized: Whether to homogeneize Dirichlet BCs. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. - parameterList: Recognized keys of approximant parameters: + parameterListSoft: Recognized keys of soft approximant parameters: - 'POD': whether to compute POD of snapshots; - - 'S': total number of samples current approximant relies upon; - 'R': rank for Galerkin projection. + parameterListCritical: Recognized keys of critical approximant + parameters: + - 'S': total number of samples current approximant relies upon. POD: Whether to compute QR factorization of derivatives. R: Rank for Galerkin projection. S: Number of solution snapshots over which current approximant is based upon. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. ARBs: List of sparse matrices (in CSC format) representing RB coefficients of linear system matrix wrt mu. bRBs: List of numpy vectors representing RB coefficients of linear system RHS wrt mu. """ def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() - self._addParametersToList(["R"]) + self._addParametersToList(["R"], [1]) super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, homogeneized = homogeneized, verbosity = verbosity, timestamp = timestamp) if self.verbosity >= 10: verbosityDepth("INIT", "Computing affine blocks of system.", timestamp = self.timestamp) if self.verbosity >= 10: verbosityDepth("DEL", "Done computing affine blocks.", timestamp = self.timestamp) self._postInit() @property - def approxParameters(self): - """ - Value of approximant parameters. Its assignment may change 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, ["R"], - True, True, baselevel = 1) - GenericCenteredApproximant.approxParameters.fset(self, - approxParametersCopy) - keyList = list(approxParameters.keys()) - if "R" in keyList: - self.R = approxParameters["R"] - else: - self.R = np.prod(self.S) + def S(self): + """Value of S.""" + return self._S + @S.setter + def S(self, S): + GenericCenteredApproximant.S.fset(self, S) + if not hasattr(self, "_R"): self._R = np.prod(self.S) @property def R(self): """Value of R. Its assignment may change S.""" return self._R @R.setter def R(self, R): if R < 0: raise RROMPyException("R must be non-negative.") self._R = R self._approxParameters["R"] = self.R if hasattr(self, "_S") and np.prod(self.S) < self.R: RROMPyWarning("Prescribed S is too small. Reducing R.") self.R = np.prod(self.S) def setupApprox(self): """Setup RB system.""" if self.checkComputedApprox(): return + RROMPyAssert(self._mode, message = "Cannot setup approximant.") if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name()), timestamp = self.timestamp) self.computeDerivatives() if self.verbosity >= 7: verbosityDepth("INIT", "Computing projection matrix.", timestamp = self.timestamp) if self.POD: - U, _, _ = np.linalg.svd(self.samplingEngine.RPOD[: np.prod(self.S), - : np.prod(self.S)]) - pMat = self.samplingEngine.samples(list(range(np.prod(self.S))))\ - .dot(U[:, : self.R]) + Sprod = np.prod(self.S) + U, _, _ = np.linalg.svd(self.samplingEngine.RPOD[: Sprod,: Sprod]) + pMat = self.samplingEngine.samples(list(range(Sprod))).dot( + U[:, : self.R]) else: pMat = self.samplingEngine.samples(list(range(self.R))) if self.trainedModel is None: self.trainedModel = tModel() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp data = TrainedModelData(self.trainedModel.name(), self.mu0, pMat, self.HFEngine.rescalingExp) data.thetaAs = self.HFEngine.affineWeightsA(self.mu0) data.thetabs = self.HFEngine.affineWeightsb(self.mu0, self.homogeneized) - data.ARBs, data.bRBs = self.assembleReducedSystem(pMat) self.trainedModel.data = data else: self.trainedModel = self.trainedModel - pMatOld = self.trainedModel.data.projMat - Sold = pMatOld.shape[1] - ARBs, bRBs = self.assembleReducedSystem( - pMat(list(range(Sold, pMat.shape[1]))), pMatOld) - self.trainedModel.data.ARBs = ARBs - self.trainedModel.data.bRBs = bRBs self.trainedModel.data.projMat = copy(pMat) + ARBs, bRBs = self.assembleReducedSystem(pMat) + self.trainedModel.data.ARBs = ARBs + self.trainedModel.data.bRBs = bRBs if self.verbosity >= 7: verbosityDepth("DEL", "Done computing projection matrix.", timestamp = self.timestamp) self.trainedModel.data.approxParameters = copy(self.approxParameters) if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.", timestamp = self.timestamp) def assembleReducedSystem(self, pMat : sampList = None, pMatOld : sampList = None)\ -> Tuple[List[Np2D], List[Np1D]]: """Build affine blocks of RB linear system through projections.""" if pMat is None: self.setupApprox() ARBs = self.trainedModel.data.ARBs bRBs = self.trainedModel.data.bRBs else: if self.verbosity >= 10: verbosityDepth("INIT", "Projecting affine terms of HF model.", timestamp = self.timestamp) As = self.HFEngine.affineLinearSystemA(self.mu0) bs = self.HFEngine.affineLinearSystemb(self.mu0, self.homogeneized) ARBsOld = None if pMatOld is None else self.trainedModel.data.ARBs bRBsOld = None if pMatOld is None else self.trainedModel.data.bRBs ARBs, bRBs = projectAffineDecomposition(As, bs, pMat, ARBsOld, bRBsOld, pMatOld) if self.verbosity >= 10: verbosityDepth("DEL", "Done projecting affine terms.", timestamp = self.timestamp) return ARBs, bRBs diff --git a/rrompy/reduction_methods/distributed/generic_distributed_approximant.py b/rrompy/reduction_methods/distributed/generic_distributed_approximant.py index b3859da..13ac033 100644 --- a/rrompy/reduction_methods/distributed/generic_distributed_approximant.py +++ b/rrompy/reduction_methods/distributed/generic_distributed_approximant.py @@ -1,196 +1,167 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from rrompy.reduction_methods.base.generic_approximant import ( GenericApproximant) from rrompy.utilities.base.types import DictAny, HFEng, paramVal, paramList -from rrompy.utilities.base import purgeDict, verbosityDepth +from rrompy.utilities.base import verbosityDepth from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert from rrompy.parameter import checkParameterList __all__ = ['GenericDistributedApproximant'] class GenericDistributedApproximant(GenericApproximant): """ ROM interpolant computation for parametric problems (ABSTRACT). Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - - 'muBounds': list of bounds for parameter values; - 'S': total number of samples current approximant relies upon; - - 'sampler': sample point generator; defaults to uniform sampler on - muBounds. + - 'sampler': sample point generator. Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. homogeneized: Whether to homogeneize Dirichlet BCs. 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; - - 'muBounds': list of bounds for parameter values; + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots. + parameterListCritical: Recognized keys of critical approximant + parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator. - extraApproxParameters: List of approxParameters keys in addition to - mother class's. POD: Whether to compute POD of snapshots. - muBounds: list of bounds for parameter values. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. + muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() - self._addParametersToList(["muBounds", "sampler"]) + from rrompy.parameter.parameter_sampling import QuadratureSampler as QS + self._addParametersToList([], [], ["sampler"], + [QS([[0], [1]], "UNIFORM")]) + del QS super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, homogeneized = homogeneized, verbosity = verbosity, timestamp = timestamp) self._postInit() @property def mus(self): """Value of mus. Its assignment may reset snapshots.""" return self._mus @mus.setter def mus(self, mus): mus, _ = checkParameterList(mus, self.npar) musOld = copy(self.mus) if hasattr(self, '_mus') else None if (musOld is None or len(mus) != len(musOld) or not mus == musOld): self.resetSamples() self._mus = mus - @property - def approxParameters(self): - """Value of approximant parameters. Its assignment may change S.""" - return self._approxParameters - @approxParameters.setter - def approxParameters(self, approxParams): - approxParameters = purgeDict(approxParams, self.parameterList, - dictname = self.name() + ".approxParameters", - baselevel = 1) - approxParametersCopy = purgeDict(approxParameters, - ["muBounds", "sampler"], - True, True, baselevel = 1) - GenericApproximant.approxParameters.fset(self, approxParametersCopy) - keyList = list(approxParameters.keys()) - if "muBounds" in keyList: - self.muBounds = approxParameters["muBounds"] - if "sampler" in keyList: - self.sampler = approxParameters["sampler"] - if ((not hasattr(self, "_muBounds") or self.muBounds is None) - and (not hasattr(self, "_sampler") or self.sampler is None)): - raise RROMPyException("Must specify either muBounds or sampler.") - if not hasattr(self, "_sampler") or self.sampler is None: - from rrompy.parameter.parameter_sampling import QuadratureSampler - self.sampler = QuadratureSampler(self.muBounds, "UNIFORM") - del QuadratureSampler - if not hasattr(self, "_muBounds") or self.muBounds is None: - self.muBounds = self.sampler.lims - @property def muBounds(self): """Value of muBounds.""" - return self._muBounds - @muBounds.setter - def muBounds(self, muBounds): - muBounds, _ = checkParameterList(muBounds, self.npar) - if len(muBounds) != 2: - raise RROMPyException("2 limits must be specified.") - self._muBounds = muBounds + return self.sampler.lims @property def sampler(self): """Value of sampler.""" return self._sampler @sampler.setter def sampler(self, sampler): if 'generatePoints' not in dir(sampler): raise RROMPyException("Sampler type not recognized.") if hasattr(self, '_sampler') and self._sampler is not None: samplerOld = self.sampler self._sampler = sampler self._approxParameters["sampler"] = self.sampler.__str__() if not 'samplerOld' in locals() or samplerOld != self.sampler: self.resetSamples() + def setSamples(self, samplingEngine): + """Copy samplingEngine and samples.""" + super().setSamples(samplingEngine) + self.mus = copy(self.samplingEngine.mus) + def computeSnapshots(self): """Compute snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start snapshot computation.") - if self.samplingEngine.nsamples == 0: + if self.samplingEngine.nsamples != np.prod(self.S): if self.verbosity >= 5: verbosityDepth("INIT", "Starting computation of snapshots.", timestamp = self.timestamp) self.mus = self.sampler.generatePoints(self.S) self.samplingEngine.iterSample(self.mus, homogeneized = self.homogeneized) if self.verbosity >= 5: verbosityDepth("DEL", "Done computing snapshots.", timestamp = self.timestamp) def normApprox(self, mu:paramList, homogeneized : bool = False) -> float: """ Compute norm of approximant at arbitrary parameter. Args: mu: Target parameter. homogeneized(optional): Whether to remove Dirichlet BC. Defaults to False. Returns: Target norm of approximant. """ if not self.POD or self.homogeneized != homogeneized: return super().normApprox(mu, homogeneized) return np.linalg.norm(self.getApproxReduced(mu), axis = 0) def computeScaleFactor(self): """Compute parameter rescaling factor.""" RROMPyAssert(self._mode, message = "Cannot compute rescaling factor.") self.scaleFactor = .5 * np.abs( self.muBounds[0] ** self.HFEngine.rescalingExp - self.muBounds[1] ** self.HFEngine.rescalingExp) diff --git a/rrompy/reduction_methods/distributed/rational_interpolant.py b/rrompy/reduction_methods/distributed/rational_interpolant.py index 7d3d7de..85a6af9 100644 --- a/rrompy/reduction_methods/distributed/rational_interpolant.py +++ b/rrompy/reduction_methods/distributed/rational_interpolant.py @@ -1,557 +1,540 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.reduction_methods.base import checkRobustTolerance from .generic_distributed_approximant import GenericDistributedApproximant -from rrompy.utilities.poly_fitting import customFit +from rrompy.utilities.poly_fitting import customFit, customPInv from rrompy.utilities.poly_fitting.polynomial import (polybases, polyfitname, nextDerivativeIndices, hashDerivativeToIdx as hashD, hashIdxToDerivative as hashI, homogeneizedpolyvander) from rrompy.reduction_methods.trained_model import TrainedModelPade as tModel from rrompy.reduction_methods.trained_model import TrainedModelData from rrompy.utilities.base.types import (Np1D, Np2D, HFEng, DictAny, Tuple, List, paramVal, paramList, sampList) -from rrompy.utilities.base import verbosityDepth, purgeDict, multifactorial +from rrompy.utilities.base import verbosityDepth, multifactorial from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) __all__ = ['RationalInterpolant'] class RationalInterpolant(GenericDistributedApproximant): """ ROM rational interpolant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - - 'muBounds': list of bounds for parameter values; - 'S': total number of samples current approximant relies upon; - - 'sampler': sample point generator; defaults to uniform sampler on - muBounds; + - 'sampler': sample point generator; - 'polybasis': type of polynomial basis for interpolation; allowed values include 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to 'MONOMIAL'; + - 'E': number of derivatives used to compute interpolant; defaults + to 0; - 'M': degree of Pade' interpolant numerator; defaults to 0; - 'N': degree of Pade' interpolant denominator; defaults to 0; - 'interpRcond': tolerance for interpolation; defaults to None; - 'robustTol': tolerance for robust Pade' denominator management; defaults to 0. Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. homogeneized: Whether to homogeneize Dirichlet BCs. 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; - - 'muBounds': list of bounds for parameter values; - - 'S': total number of samples current approximant relies upon; - - 'sampler': sample point generator; + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots; - 'polybasis': type of polynomial basis for interpolation; + - 'E': number of derivatives used to compute interpolant; - 'M': degree of Pade' interpolant numerator; - 'N': degree of Pade' interpolant denominator; - 'interpRcond': tolerance for interpolation via numpy.polyfit; - 'robustTol': tolerance for robust Pade' denominator management. - extraApproxParameters: List of approxParameters keys in addition to - mother class's. + parameterListCritical: Recognized keys of critical approximant + parameters: + - 'S': total number of samples current approximant relies upon; + - 'sampler': sample point generator. POD: Whether to compute POD of snapshots. - muBounds: list of bounds for parameter values. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. polybasis: type of polynomial basis for interpolation. M: Numerator degree of approximant. N: Denominator degree of approximant. interpRcond: Tolerance for interpolation via numpy.polyfit. robustTol: Tolerance for robust Pade' denominator management. E: Complete derivative depth level of S. + muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. Q: Numpy 1D vector containing complex coefficients of approximant denominator. P: Numpy 2D vector whose columns are FE dofs of coefficients of approximant numerator. """ def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() - self._addParametersToList(["polybasis", "M", "N", "interpRcond", - "robustTol"]) + self._addParametersToList(["polybasis", "E", "M", "N", "interpRcond", + "robustTol"], ["MONOMIAL", -1, 0, 0, -1, 0]) super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, homogeneized = homogeneized, verbosity = verbosity, timestamp = timestamp) 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, ["polybasis", - "M", "N", - "interpRcond", - "robustTol"], - True, True, baselevel = 1) - GenericDistributedApproximant.approxParameters.fset(self, - approxParametersCopy) - keyList = list(approxParameters.keys()) - if "polybasis" in keyList: - self.polybasis = approxParameters["polybasis"] - elif not hasattr(self, "_polybasis") or self._polybasis is None: - self.polybasis = "MONOMIAL" - if "interpRcond" in keyList: - self.interpRcond = approxParameters["interpRcond"] - elif not hasattr(self, "interpRcond") or self.interpRcond is None: - self.interpRcond = None - if "robustTol" in keyList: - self.robustTol = approxParameters["robustTol"] - elif not hasattr(self, "_robustTol") or self._robustTol is None: - self.robustTol = 0 - if "M" in keyList: - self.M = approxParameters["M"] - elif hasattr(self, "_M") and self.M is not None: - self.M = self.M - else: - self.M = 0 - if "N" in keyList: - self.N = approxParameters["N"] - elif hasattr(self, "_N") and self.N is not None: - self.N = self.N - else: - self.N = 0 - @property def polybasis(self): """Value of polybasis.""" return self._polybasis @polybasis.setter def polybasis(self, polybasis): try: polybasis = polybasis.upper().strip().replace(" ","") if polybasis not in polybases: raise RROMPyException("Prescribed polybasis not recognized.") self._polybasis = polybasis except: RROMPyWarning(("Prescribed polybasis not recognized. Overriding " "to 'MONOMIAL'.")) self._polybasis = "MONOMIAL" self._approxParameters["polybasis"] = self.polybasis @property def interpRcond(self): """Value of interpRcond.""" return self._interpRcond @interpRcond.setter def interpRcond(self, interpRcond): self._interpRcond = interpRcond self._approxParameters["interpRcond"] = self.interpRcond @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 RROMPyException("M must be non-negative.") self._M = M self._approxParameters["M"] = self.M - if hasattr(self, "E") and self.E < self.M: + if hasattr(self, "_E") and self.E >= 0 and self.E < self.M: RROMPyWarning("Prescribed S is too small. Decreasing M.") self.M = self.E @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 RROMPyException("N must be non-negative.") self._N = N self._approxParameters["N"] = self.N - if hasattr(self, "E") and self.E < self.N: + if hasattr(self, "_E") and self.E >= 0 and self.E < self.N: RROMPyWarning("Prescribed S is too small. Decreasing N.") self.N = self.E + @property + def E(self): + """Value of E.""" + return self._E + @E.setter + def E(self, E): + if E < 0: + if not hasattr(self, "_S"): + raise RROMPyException(("Value of E must be positive if S is " + "not yet assigned.")) + E = np.sum(hashI(np.prod(self.S), self.npar)) - 1 + self._E = E + self._approxParameters["E"] = self.E + if (hasattr(self, "_S") + and self.E >= np.sum(hashI(np.prod(self.S), self.npar))): + RROMPyWarning("Prescribed S is too small. Decreasing E.") + self.E = -1 + if hasattr(self, "_M"): self.M = self.M + if hasattr(self, "_N"): self.N = self.N + @property def robustTol(self): """Value of tolerance for robust Pade' denominator management.""" return self._robustTol @robustTol.setter def robustTol(self, robustTol): if robustTol < 0.: RROMPyWarning(("Overriding prescribed negative robustness " "tolerance to 0.")) robustTol = 0. self._robustTol = robustTol self._approxParameters["robustTol"] = self.robustTol @property def S(self): """Value of S.""" return self._S @S.setter def S(self, S): GenericDistributedApproximant.S.fset(self, S) - self.E = np.sum(hashI(np.prod(self.S), self.npar)) - 1 if hasattr(self, "_M"): self.M = self.M if hasattr(self, "_N"): self.N = self.N + if hasattr(self, "_E"): self.E = self.E def resetSamples(self): """Reset samples.""" super().resetSamples() self._musUniqueCN = None self._derIdxs = None self._reorder = None def _setupInterpolationIndices(self): """Setup parameters for polyvander.""" + RROMPyAssert(self._mode, + message = "Cannot setup interpolation indices.") if self._musUniqueCN is None or len(self._reorder) != len(self.mus): self._musUniqueCN, musIdxsTo, musIdxs, musCount = ( self.centerNormalize(self.mus).unique(return_index = True, return_inverse = True, return_counts = True)) self._musUnique = self.mus[musIdxsTo] self._derIdxs = [None] * len(self._musUniqueCN) self._reorder = np.empty(len(musIdxs), dtype = int) filled = 0 for j, cnt in enumerate(musCount): self._derIdxs[j] = nextDerivativeIndices([], self.mus.shape[1], cnt) jIdx = np.nonzero(musIdxs == j)[0] self._reorder[jIdx] = np.arange(filled, filled + cnt) filled += cnt def _setupDenominator(self): """Compute Pade' denominator.""" + RROMPyAssert(self._mode, message = "Cannot setup denominator.") if self.verbosity >= 7: verbosityDepth("INIT", "Starting computation of denominator.", timestamp = self.timestamp) while self.N > 0: invD = self._computeInterpolantInverseBlocks() if self.POD: ev, eV = self.findeveVGQR(self.samplingEngine.RPOD, invD) else: ev, eV = self.findeveVGExplicit(self.samplingEngine.samples, invD) newParams = checkRobustTolerance(ev, self.N, self.robustTol) if not newParams: break self.approxParameters = newParams if self.N <= 0: self._N = 0 eV = np.ones((1, 1)) if self.verbosity >= 7: verbosityDepth("DEL", "Done computing denominator.", timestamp = self.timestamp) q = np.zeros(tuple([self.N + 1] * self.npar), dtype = eV.dtype) for j in range(eV.shape[0]): q[tuple(hashI(j, self.npar))] = eV[j, 0] return q def _setupNumerator(self): """Compute Pade' numerator.""" + RROMPyAssert(self._mode, message = "Cannot setup numerator.") if self.verbosity >= 7: verbosityDepth("INIT", "Starting computation of numerator.", timestamp = self.timestamp) Qevaldiag = np.zeros((len(self.mus), len(self.mus)), dtype = np.complex) verb = self.trainedModel.verbosity self.trainedModel.verbosity = 0 self._setupInterpolationIndices() idxGlob = 0 for j, derIdxs in enumerate(self._derIdxs): nder = len(derIdxs) idxLoc = np.arange(len(self.mus))[(self._reorder >= idxGlob) * (self._reorder < idxGlob + nder)] idxGlob += nder Qval = [0] * nder for der in range(nder): derIdx = hashI(der, self.npar) Qval[der] = (self.trainedModel.getQVal( self._musUnique[j], derIdx, scl = np.power(self.scaleFactor, -1.)) / multifactorial(derIdx)) for derU, derUIdx in enumerate(derIdxs): for derQ, derQIdx in enumerate(derIdxs): diffIdx = [x - y for (x, y) in zip(derUIdx, derQIdx)] if all([x >= 0 for x in diffIdx]): diffj = hashD(diffIdx) Qevaldiag[idxLoc[derU], idxLoc[derQ]] = Qval[diffj] self.trainedModel.verbosity = verb while self.M >= 0: fitVander, _, argIdxs = homogeneizedpolyvander(self._musUniqueCN, self.M, self.polybasis, self._derIdxs, self._reorder, scl = np.power(self.scaleFactor, -1.)) fitVander = fitVander[:, argIdxs] fitOut = customFit(fitVander, Qevaldiag, full = True, rcond = self.interpRcond) if self.verbosity >= 5: condfit = fitOut[1][2][0] / fitOut[1][2][-1] verbosityDepth("MAIN", ("Fitting {} samples with degree {} " "through {}... Conditioning of LS " "system: {:.4e}.").format( fitVander.shape[0], self.M, polyfitname(self.polybasis), condfit), timestamp = self.timestamp) if fitOut[1][1] == fitVander.shape[1]: P = fitOut[0] break RROMPyWarning("Polyfit is poorly conditioned. Reducing M by 1.") self.M -= 1 if self.M < 0: raise RROMPyException(("Instability in computation of numerator. " "Aborting.")) if self.verbosity >= 7: verbosityDepth("DEL", "Done computing numerator.", timestamp = self.timestamp) p = np.zeros(tuple([self.M + 1] * self.npar) + (P.shape[1],), dtype = P.dtype) for j in range(P.shape[0]): p[tuple(hashI(j, self.npar))] = P[j, :] return p.T def setupApprox(self): """ Compute Pade' interpolant. SVD-based robust eigenvalue management. """ if self.checkComputedApprox(): return RROMPyAssert(self._mode, message = "Cannot setup approximant.") if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name()), timestamp = self.timestamp) self.computeScaleFactor() self.computeSnapshots() if self.trainedModel is None: self.trainedModel = tModel() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp data = TrainedModelData(self.trainedModel.name(), self.mu0, self.samplingEngine.samples, self.HFEngine.rescalingExp) data.polytype = self.polybasis data.scaleFactor = self.scaleFactor data.mus = copy(self.mus) self.trainedModel.data = data else: self.trainedModel = self.trainedModel self.trainedModel.data.projMat = copy(self.samplingEngine.samples) if self.N > 0: Q = self._setupDenominator() else: Q = np.ones(1, dtype = np.complex) self.trainedModel.data.Q = copy(Q) P = self._setupNumerator() if self.POD: P = np.tensordot(self.samplingEngine.RPOD, P, axes = ([-1], [0])) self.trainedModel.data.P = copy(P) self.trainedModel.data.approxParameters = copy(self.approxParameters) if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.", timestamp = self.timestamp) def _computeInterpolantInverseBlocks(self) -> List[Np2D]: """ Compute inverse factors for minimal interpolant target functional. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") self._setupInterpolationIndices() - eStart = hashD([self.E] + [0] * (self.npar - 1)) - eEnd = hashD([self.E + 1] + [0] * (self.npar - 1)) - TE, _, argIdxs = homogeneizedpolyvander(self._musUniqueCN, self.E, - self.polybasis, self._derIdxs, self._reorder, - scl = np.power(self.scaleFactor, -1.)) - TE = TE[:, argIdxs].T - RHS = np.zeros((TE.shape[0], eEnd - eStart)) - RHS[range(eStart, eEnd), range(eEnd - eStart)] = 1. - fitOut = customFit(TE, RHS, full = True, rcond = self.interpRcond) - if self.verbosity >= 5: - condfit = fitOut[1][2][0] / fitOut[1][2][-1] - verbosityDepth("MAIN", ("Fitting {} samples with degree {} " - "through {}... Conditioning of LS " - "system: {:.4e}.").format( - TE.shape[1], self.E, + while self.E >= 0: + eWidth = (hashD([self.E + 1] + [0] * (self.npar - 1)) + - hashD([self.E] + [0] * (self.npar - 1))) + TE, _, argIdxs = homogeneizedpolyvander(self._musUniqueCN, + self.E, self.polybasis, + self._derIdxs, self._reorder, + scl = np.power(self.scaleFactor, -1.)) + fitOut = customPInv(TE[:, argIdxs], rcond = self.interpRcond, + full = True) + if self.verbosity >= 5: + condfit = fitOut[1][1][0] / fitOut[1][1][-1] + verbosityDepth("MAIN", ("Fitting {} samples with degree {} " + "through {}... Conditioning of " + "pseudoinverse system: {:.4e}.")\ + .format(TE.shape[0], self.E, polyfitname(self.polybasis), condfit), - timestamp = self.timestamp) - self._fitinv = fitOut[0][:, -1] + timestamp = self.timestamp) + if fitOut[1][0] == len(argIdxs): + self._fitinv = fitOut[0][- eWidth : , :] + break + RROMPyWarning("Polyfit is poorly conditioned. Reducing E by 1.") + self.E -= 1 + if self.E < 0: + raise RROMPyException(("Instability in computation of " + "denominator. Aborting.")) TN, _, argIdxs = homogeneizedpolyvander(self._musUniqueCN, self.N, self.polybasis, self._derIdxs, self._reorder, scl = np.power(self.scaleFactor, -1.)) TN = TN[:, argIdxs] - invD = [None] * (eEnd - eStart) - for k in range(eEnd - eStart): - pseudoInv = np.diag(fitOut[0][:, k]) + invD = [None] * (eWidth) + for k in range(eWidth): + pseudoInv = np.diag(self._fitinv[k, :]) idxGlob = 0 for j, derIdxs in enumerate(self._derIdxs): nder = len(derIdxs) idxGlob += nder - idxLoc = np.arange(len(self.mus))[ - (self._reorder >= idxGlob - nder) - * (self._reorder < idxGlob)] if nder > 1: - invLoc = fitOut[0][idxLoc, k] + idxLoc = np.arange(len(self.mus))[ + (self._reorder >= idxGlob - nder) + * (self._reorder < idxGlob)] + invLoc = self._fitinv[k, idxLoc] pseudoInv[np.ix_(idxLoc, idxLoc)] = 0. for diffj, diffjIdx in enumerate(derIdxs): for derQ, derQIdx in enumerate(derIdxs): derUIdx = [x - y for (x, y) in zip(diffjIdx, derQIdx)] if all([x >= 0 for x in derUIdx]): derU = hashD(derUIdx) pseudoInv[idxLoc[derU], idxLoc[derQ]] = ( invLoc[diffj]) invD[k] = pseudoInv.dot(TN) return invD def findeveVGExplicit(self, sampleE:sampList, invD:List[Np2D]) -> Tuple[Np1D, Np2D]: """ Compute explicitly eigenvalues and eigenvectors of Pade' denominator matrix. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") nEnd = invD[0].shape[1] - eStart = hashD([self.E] + [0] * (self.npar - 1)) - eEnd = hashD([self.E + 1] + [0] * (self.npar - 1)) + eWidth = len(invD) if self.verbosity >= 10: verbosityDepth("INIT", "Building gramian matrix.", timestamp = self.timestamp) gramian = self.HFEngine.innerProduct(sampleE, sampleE) G = np.zeros((nEnd, nEnd), dtype = np.complex) - for k in range(eEnd - eStart): + for k in range(eWidth): G += invD[k].T.conj().dot(gramian.dot(invD[k])) if self.verbosity >= 10: verbosityDepth("DEL", "Done building gramian.", timestamp = self.timestamp) if self.verbosity >= 7: verbosityDepth("INIT", ("Solving eigenvalue problem for " "gramian matrix."), timestamp = self.timestamp) ev, eV = np.linalg.eigh(G) if self.verbosity >= 5: try: condev = ev[-1] / ev[0] except: condev = np.inf verbosityDepth("MAIN", ("Solved eigenvalue problem of " "size {} with condition number " "{:.4e}.").format(nEnd, condev), timestamp = self.timestamp) if self.verbosity >= 7: verbosityDepth("DEL", "Done solving eigenvalue problem.", timestamp = self.timestamp) return ev, eV def findeveVGQR(self, RPODE:Np2D, invD:List[Np2D]) -> Tuple[Np1D, Np2D]: """ Compute eigenvalues and eigenvectors of Pade' denominator matrix through SVD of R factor. """ RROMPyAssert(self._mode, message = "Cannot solve eigenvalue problem.") nEnd = invD[0].shape[1] - eStart = hashD([self.E] + [0] * (self.npar - 1)) - eEnd = hashD([self.E + 1] + [0] * (self.npar - 1)) + S = RPODE.shape[0] + eWidth = len(invD) if self.verbosity >= 10: verbosityDepth("INIT", "Building half-gramian matrix stack.", timestamp = self.timestamp) - Rstack = np.zeros((RPODE.shape[0] * (eEnd - eStart), nEnd), - dtype = np.complex) - for k in range(eEnd - eStart): - Rstack[k * RPODE.shape[0] : (k + 1) * RPODE.shape[0], :] =\ - RPODE.dot(invD[k]) + Rstack = np.zeros((S * eWidth, nEnd), dtype = np.complex) + for k in range(eWidth): + Rstack[k * S : (k + 1) * S, :] = RPODE.dot(invD[k]) if self.verbosity >= 10: verbosityDepth("DEL", "Done building half-gramian.", timestamp = self.timestamp) if self.verbosity >= 7: verbosityDepth("INIT", ("Solving svd for square root of " "gramian matrix."), timestamp = self.timestamp) _, s, eV = np.linalg.svd(Rstack, full_matrices = False) ev = s[::-1] eV = eV[::-1, :].T.conj() if self.verbosity >= 5: try: condev = s[0] / s[-1] except: condev = np.inf verbosityDepth("MAIN", ("Solved svd problem of size {} x " "{} with condition number " - "{:.4e}.").format(*Rstack.shape, - condev), + "{:.4e}.").format(*Rstack.shape, condev), timestamp = self.timestamp) if self.verbosity >= 7: verbosityDepth("DEL", "Done solving svd.", timestamp = self.timestamp) return ev, eV def centerNormalize(self, mu : paramList = [], mu0 : paramVal = None) -> paramList: """ Compute normalized parameter to be plugged into approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.mu0. Returns: Normalized parameter. """ return self.trainedModel.centerNormalize(mu, mu0) def getResidues(self) -> Np1D: """ Obtain approximant residues. Returns: Matrix with residues as columns. """ return self.trainedModel.getResidues() diff --git a/rrompy/reduction_methods/distributed/rb_distributed.py b/rrompy/reduction_methods/distributed/rb_distributed.py index 2c56a75..a7dfa03 100644 --- a/rrompy/reduction_methods/distributed/rb_distributed.py +++ b/rrompy/reduction_methods/distributed/rb_distributed.py @@ -1,223 +1,206 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .generic_distributed_approximant import GenericDistributedApproximant from rrompy.reduction_methods.trained_model import TrainedModelRB as tModel from rrompy.reduction_methods.trained_model import TrainedModelData from rrompy.reduction_methods.base.rb_utils import projectAffineDecomposition from rrompy.utilities.base.types import (Np1D, Np2D, List, Tuple, DictAny, HFEng, paramVal) -from rrompy.utilities.base import purgeDict, verbosityDepth -from rrompy.utilities.exception_manager import RROMPyWarning, RROMPyException +from rrompy.utilities.base import verbosityDepth +from rrompy.utilities.exception_manager import (RROMPyWarning, RROMPyException, + RROMPyAssert) __all__ = ['RBDistributed'] class RBDistributed(GenericDistributedApproximant): """ ROM RB approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - - 'muBounds': list of bounds for parameter values; defaults to - [0, 1]; + - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon; - - 'sampler': sample point generator; defaults to uniform sampler on - muBounds; + - 'sampler': sample point generator; - 'R': rank for Galerkin projection; defaults to prod(S). Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. homogeneized: Whether to homogeneize Dirichlet BCs. approxRadius: Dummy radius of approximant (i.e. distance from mu0 to farthest sample point). 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; - - 'muBounds': list of bounds for parameter values; + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots. + - 'R': rank for Galerkin projection. + parameterListCritical: Recognized keys of critical approximant + parameters: - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator; - - 'R': rank for Galerkin projection. - extraApproxParameters: List of approxParameters keys in addition to - mother class's. POD: Whether to compute POD of snapshots. - muBounds: list of bounds for parameter values. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. R: Rank for Galerkin projection. + muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. As: List of sparse matrices (in CSC format) representing coefficients of linear system matrix wrt theta(mu). bs: List of numpy vectors representing coefficients of linear system RHS wrt theta(mu). thetaAs: List of callables representing coefficients of linear system matrix wrt mu. thetabs: List of callables representing coefficients of linear system RHS wrt mu. ARBs: List of sparse matrices (in CSC format) representing coefficients of compressed linear system matrix wrt theta(mu). bRBs: List of numpy vectors representing coefficients of compressed linear system RHS wrt theta(mu). """ def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() - self._addParametersToList(["R"]) + self._addParametersToList(["R"], [1]) super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, homogeneized = homogeneized, verbosity = verbosity, timestamp = timestamp) if self.verbosity >= 10: verbosityDepth("INIT", "Computing affine blocks of system.", timestamp = self.timestamp) self.As = self.HFEngine.affineLinearSystemA(self.mu0) self.bs = self.HFEngine.affineLinearSystemb(self.mu0, self.homogeneized) if self.verbosity >= 10: verbosityDepth("DEL", "Done computing affine blocks.", timestamp = self.timestamp) self._postInit() @property - def approxParameters(self): - """ - Value of approximant parameters. Its assignment may change 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, ["R"], True, True, - baselevel = 1) - GenericDistributedApproximant.approxParameters.fset(self, - approxParametersCopy) - keyList = list(approxParameters.keys()) - if "R" in keyList: - self.R = approxParameters["R"] - elif hasattr(self, "_R") and self._R is not None: - self.R = self.R - else: - self.R = np.prod(self.S) + def S(self): + """Value of S.""" + return self._S + @S.setter + def S(self, S): + GenericDistributedApproximant.S.fset(self, S) + if not hasattr(self, "_R"): self._R = np.prod(self.S) @property def R(self): """Value of R. Its assignment may change S.""" return self._R @R.setter def R(self, R): if R < 0: raise RROMPyException("R must be non-negative.") self._R = R self._approxParameters["R"] = self.R if hasattr(self, "_S") and np.prod(self.S) < self.R: RROMPyWarning("Prescribed S is too small. Decreasing R.") self.R = np.prod(self.S) def setupApprox(self): """Compute RB projection matrix.""" if self.checkComputedApprox(): return + RROMPyAssert(self._mode, message = "Cannot setup approximant.") if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name()), timestamp = self.timestamp) self.computeSnapshots() if self.verbosity >= 7: verbosityDepth("INIT", "Computing projection matrix.", timestamp = self.timestamp) if self.POD: U, _, _ = np.linalg.svd(self.samplingEngine.RPOD, full_matrices = False) pMat = self.samplingEngine.samples.dot(U[:, : self.R]) else: pMat = self.samplingEngine.samples[: self.R] if self.trainedModel is None: self.trainedModel = tModel() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp data = TrainedModelData(self.trainedModel.name(), self.mu0, pMat, self.HFEngine.rescalingExp) data.thetaAs = self.HFEngine.affineWeightsA(self.mu0) data.thetabs = self.HFEngine.affineWeightsb(self.mu0, self.homogeneized) - data.ARBs, data.bRBs = self.assembleReducedSystem(pMat) data.mus = copy(self.mus) self.trainedModel.data = data else: self.trainedModel = self.trainedModel - pMatOld = self.trainedModel.data.projMat - Sold = pMatOld.shape[1] - ARBs, bRBs = self.assembleReducedSystem(pMat[:, Sold :], pMatOld) - self.trainedModel.data.ARBs = ARBs - self.trainedModel.data.bRBs = bRBs self.trainedModel.data.projMat = copy(pMat) + ARBs, bRBs = self.assembleReducedSystem(pMat) + self.trainedModel.data.ARBs = ARBs + self.trainedModel.data.bRBs = bRBs + if self.verbosity >= 7: verbosityDepth("DEL", "Done computing projection matrix.", timestamp = self.timestamp) self.trainedModel.data.approxParameters = copy(self.approxParameters) if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.", timestamp = self.timestamp) def assembleReducedSystem(self, pMat : Np2D = None, pMatOld : Np2D = None)\ -> Tuple[List[Np2D], List[Np1D]]: """Build affine blocks of RB linear system through projections.""" if pMat is None: self.setupApprox() ARBs = self.trainedModel.data.ARBs bRBs = self.trainedModel.data.bRBs else: if self.verbosity >= 10: verbosityDepth("INIT", "Projecting affine terms of HF model.", timestamp = self.timestamp) ARBsOld = None if pMatOld is None else self.trainedModel.data.ARBs bRBsOld = None if pMatOld is None else self.trainedModel.data.bRBs ARBs, bRBs = projectAffineDecomposition(self.As, self.bs, pMat, ARBsOld, bRBsOld, pMatOld) if self.verbosity >= 10: verbosityDepth("DEL", "Done projecting affine terms.", timestamp = self.timestamp) return ARBs, bRBs diff --git a/rrompy/reduction_methods/distributed_greedy/generic_distributed_greedy_approximant.py b/rrompy/reduction_methods/distributed_greedy/generic_distributed_greedy_approximant.py index 6f0bf3a..f4c223c 100644 --- a/rrompy/reduction_methods/distributed_greedy/generic_distributed_greedy_approximant.py +++ b/rrompy/reduction_methods/distributed_greedy/generic_distributed_greedy_approximant.py @@ -1,654 +1,591 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from rrompy.reduction_methods.distributed.generic_distributed_approximant \ import GenericDistributedApproximant from rrompy.utilities.base.types import (Np1D, Np2D, DictAny, HFEng, Tuple, List, normEng, paramVal, paramList, sampList) -from rrompy.utilities.base import purgeDict, verbosityDepth +from rrompy.utilities.base import verbosityDepth from rrompy.solver import normEngine from rrompy.utilities.exception_manager import (RROMPyException, RROMPyAssert, RROMPyWarning) from rrompy.parameter import checkParameterList, emptyParameterList __all__ = ['GenericDistributedGreedyApproximant'] def pruneSamples(mus:paramList, badmus:paramList, tol : float = 1e-8) -> paramList: """Remove from mus all the elements which are too close to badmus.""" if len(badmus) == 0: return mus musNp = np.array(mus(0)) badmus = np.array(badmus(0)) proximity = np.min(np.abs(musNp.reshape(-1, 1) - np.tile(badmus.reshape(1, -1), [len(mus), 1])), axis = 1).flatten() idxPop = np.arange(len(mus))[proximity <= tol] for i, j in enumerate(idxPop): mus.pop(j - i) return mus class GenericDistributedGreedyApproximant(GenericDistributedApproximant): """ ROM greedy interpolant computation for parametric problems (ABSTRACT). Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - - 'muBounds': list of bounds for parameter values; defaults to - [0, 1]; - - 'S': number of starting training points; defaults to 2; - - 'sampler': sample point generator; defaults to uniform sampler on - muBounds; + - 'S': number of starting training points; + - 'sampler': sample point generator; - 'greedyTol': uniform error tolerance for greedy algorithm; defaults to 1e-2; - 'interactive': whether to interactively terminate greedy algorithm; defaults to False; - 'maxIter': maximum number of greedy steps; defaults to 1e2; - 'refinementRatio': ratio of test points to be exhausted before test set refinement; defaults to 0.2; - - 'nTestPoints': number of test points; defaults to maxIter / - refinementRatio; - - 'trainSetGenerator': training sample points generator; defaults - to Chebyshev sampler within muBounds. + - 'nTestPoints': number of test points; defaults to 5e2; + - 'trainSetGenerator': training sample points generator. Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. homogeneized: Whether to homogeneize Dirichlet BCs. 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; - - 'muBounds': list of bounds for parameter values; - - 'S': number of starting training points; - - 'sampler': sample point generator; + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots. - 'greedyTol': uniform error tolerance for greedy algorithm; - 'interactive': whether to interactively terminate greedy algorithm; - 'maxIter': maximum number of greedy steps; - 'refinementRatio': ratio of test points to be exhausted before test set refinement; - - 'nTestPoints': number of test points; + - 'nTestPoints': number of test points. + parameterListCritical: Recognized keys of critical approximant + parameters: + - 'S': total number of samples current approximant relies upon; + - 'sampler': sample point generator; - 'trainSetGenerator': training sample points generator. - extraApproxParameters: List of approxParameters keys in addition to - mother class's. POD: whether to compute POD of snapshots. - muBounds: list of bounds for parameter values. S: number of test points. sampler: Sample point generator. greedyTol: uniform error tolerance for greedy algorithm. + interactive: whether to interactively terminate greedy algorithm. maxIter: maximum number of greedy steps. refinementRatio: ratio of training points to be exhausted before training set refinement. nTestPoints: number of starting training points. trainSetGenerator: training sample points generator. - robustTol: tolerance for robust Pade' denominator management. + muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. estimatorNormEngine: Engine for estimator norm computation. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ TOL_INSTABILITY = 1e-6 def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() + from rrompy.parameter.parameter_sampling import QuadratureSampler as QS self._addParametersToList(["greedyTol", "interactive", "maxIter", - "refinementRatio", "nTestPoints", - "trainSetGenerator"]) + "refinementRatio", "nTestPoints"], + [1e-2, False, 1e2, .2, 5e2], + ["trainSetGenerator"], + [QS([[0], [1]], "UNIFORM")]) + del QS super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, homogeneized = homogeneized, verbosity = verbosity, timestamp = timestamp) RROMPyAssert(self.HFEngine.npar, 1, "Parameter dimension") self._postInit() - @property - def approxParameters(self): - """Value of approximant parameters. Its assignment may change S.""" - return self._approxParameters - @approxParameters.setter - def approxParameters(self, approxParams): - approxParameters = purgeDict(approxParams, self.parameterList, - dictname = self.name() + ".approxParameters", - baselevel = 1) - approxParametersCopy = purgeDict(approxParameters, - ["greedyTol", "interactive", - "maxIter", "refinementRatio", - "nTestPoints", "trainSetGenerator"], - True, True, baselevel = 1) - GenericDistributedApproximant.approxParameters.fset(self, - approxParametersCopy) - keyList = list(approxParameters.keys()) - if "greedyTol" in keyList: - self.greedyTol = approxParameters["greedyTol"] - elif not hasattr(self, "_greedyTol") or self.greedyTol is None: - self.greedyTol = 1e-2 - if "interactive" in keyList: - self.interactive = approxParameters["interactive"] - elif not hasattr(self, "interactive") or self.interactive is None: - self.interactive = False - if "maxIter" in keyList: - self.maxIter = approxParameters["maxIter"] - elif not hasattr(self, "_maxIter") or self.maxIter is None: - self.maxIter = 1e2 - if "refinementRatio" in keyList: - self.refinementRatio = approxParameters["refinementRatio"] - elif (not hasattr(self, "_refinementRatio") - or self.refinementRatio is None): - self.refinementRatio = 0.2 - if "nTestPoints" in keyList: - self.nTestPoints = approxParameters["nTestPoints"] - elif (not hasattr(self, "_nTestPoints") - or self.nTestPoints is None): - self.nTestPoints = np.int(np.ceil(self.maxIter - / self.refinementRatio)) - if "trainSetGenerator" in keyList: - self.trainSetGenerator = approxParameters["trainSetGenerator"] - elif (not hasattr(self, "_trainSetGenerator") - or self.trainSetGenerator is None): - from rrompy.parameter.parameter_sampling import QuadratureSampler - self.trainSetGenerator = QuadratureSampler(self.muBounds, - "CHEBYSHEV") - del QuadratureSampler - @property def greedyTol(self): """Value of greedyTol.""" return self._greedyTol @greedyTol.setter def greedyTol(self, greedyTol): if greedyTol < 0: raise RROMPyException("greedyTol must be non-negative.") if hasattr(self, "_greedyTol") and self.greedyTol is not None: greedyTolold = self.greedyTol else: greedyTolold = -1 self._greedyTol = greedyTol self._approxParameters["greedyTol"] = self.greedyTol if greedyTolold != self.greedyTol: self.resetSamples() + @property + def interactive(self): + """Value of interactive.""" + return self._interactive + @interactive.setter + def interactive(self, interactive): + self._interactive = interactive + @property def maxIter(self): """Value of maxIter.""" return self._maxIter @maxIter.setter def maxIter(self, maxIter): if maxIter <= 0: raise RROMPyException("maxIter must be positive.") if hasattr(self, "_maxIter") and self.maxIter is not None: maxIterold = self.maxIter else: maxIterold = -1 self._maxIter = maxIter self._approxParameters["maxIter"] = self.maxIter if maxIterold != self.maxIter: self.resetSamples() @property def refinementRatio(self): """Value of refinementRatio.""" return self._refinementRatio @refinementRatio.setter def refinementRatio(self, refinementRatio): if refinementRatio <= 0. or refinementRatio > 1.: raise RROMPyException(("refinementRatio must be between 0 " "(excluded) and 1.")) if (hasattr(self, "_refinementRatio") and self.refinementRatio is not None): refinementRatioold = self.refinementRatio else: refinementRatioold = -1 self._refinementRatio = refinementRatio self._approxParameters["refinementRatio"] = self.refinementRatio if refinementRatioold != self.refinementRatio: self.resetSamples() @property def nTestPoints(self): """Value of nTestPoints.""" return self._nTestPoints @nTestPoints.setter def nTestPoints(self, nTestPoints): if nTestPoints <= 0: raise RROMPyException("nTestPoints must be positive.") if not np.isclose(nTestPoints, np.int(nTestPoints)): raise RROMPyException("nTestPoints must be an integer.") nTestPoints = np.int(nTestPoints) if hasattr(self, "_nTestPoints") and self.nTestPoints is not None: nTestPointsold = self.nTestPoints else: nTestPointsold = -1 self._nTestPoints = nTestPoints self._approxParameters["nTestPoints"] = self.nTestPoints if nTestPointsold != self.nTestPoints: self.resetSamples() @property def trainSetGenerator(self): """Value of trainSetGenerator.""" return self._trainSetGenerator @trainSetGenerator.setter def trainSetGenerator(self, trainSetGenerator): if 'generatePoints' not in dir(trainSetGenerator): raise RROMPyException("trainSetGenerator type not recognized.") if (hasattr(self, '_trainSetGenerator') and self.trainSetGenerator is not None): trainSetGeneratorOld = self.trainSetGenerator self._trainSetGenerator = trainSetGenerator self._approxParameters["trainSetGenerator"] = self.trainSetGenerator if (not 'trainSetGeneratorOld' in locals() or trainSetGeneratorOld != self.trainSetGenerator): self.resetSamples() def resetSamples(self): """Reset samples.""" super().resetSamples() self._mus = emptyParameterList() def initEstimatorNormEngine(self, normEngn : normEng = None): """Initialize estimator norm engine.""" if (normEngn is not None or not hasattr(self, "estimatorNormEngine") or self.estimatorNormEngine is None): if normEngn is None: if not hasattr(self.HFEngine, "energyNormMatrix"): self.HFEngine.buildEnergyNormForm() estimatorEnergyMatrix = self.HFEngine.energyNormMatrix else: if hasattr(normEngn, "buildEnergyNormForm"): if not hasattr(normEngn, "energyNormMatrix"): normEngn.buildEnergyNormForm() estimatorEnergyMatrix = normEngn.energyNormMatrix else: estimatorEnergyMatrix = normEngn self.estimatorNormEngine = normEngine(estimatorEnergyMatrix) def errorEstimator(self, mus:paramList) -> List[complex]: """ Standard residual-based error estimator with explicit residual computation. """ self.setupApprox() if self.HFEngine.nbs == 1: RHS = self.getRHS(mus[0], homogeneized = self.homogeneized) RHSNorm = self.estimatorNormEngine.norm(RHS) res = self.getRes(mus, homogeneized = self.homogeneized) err = self.estimatorNormEngine.norm(res) / RHSNorm else: res = self.getRes(mus, homogeneized = self.homogeneized) RHS = self.getRHS(mus, homogeneized = self.homogeneized) err = (self.estimatorNormEngine.norm(res) / self.estimatorNormEngine.norm(RHS)) return np.abs(err) def getMaxErrorEstimator(self, mus:paramList, plot : bool = False) -> Tuple[Np1D, int, float]: """ Compute maximum of (and index of maximum of) error estimator over given parameters. """ errorEstTest = self.errorEstimator(mus) idxMaxEst = np.argmax(errorEstTest) maxEst = errorEstTest[idxMaxEst] if plot and not np.all(np.isinf(errorEstTest)): musre = mus.re(0) from matplotlib import pyplot as plt plt.figure() plt.semilogy(musre, errorEstTest, 'k') plt.semilogy([musre[0], musre[-1]], [self.greedyTol] * 2, 'r--') plt.semilogy(self.mus.re(0), 2. * self.greedyTol * np.ones(len(self.mus)), '*m') plt.semilogy(musre[idxMaxEst], maxEst, 'xr') plt.grid() plt.show() plt.close() return errorEstTest, idxMaxEst, maxEst def greedyNextSample(self, muidx:int, plotEst : bool = False)\ -> Tuple[Np1D, int, float, paramVal]: """Compute next greedy snapshot of solution map.""" RROMPyAssert(self._mode, message = "Cannot add greedy sample.") mu = copy(self.muTest[muidx]) self.muTest.pop(muidx) if self.verbosity >= 2: verbosityDepth("MAIN", ("Adding {}-th sample point at {} to " "training set.").format( self.samplingEngine.nsamples + 1, mu), timestamp = self.timestamp) self.mus.append(mu) self.samplingEngine.nextSample(mu, homogeneized = self.homogeneized) errorEstTest, muidx, maxErrorEst = self.getMaxErrorEstimator( self.muTest, plotEst) return errorEstTest, muidx, maxErrorEst, self.muTest[muidx] def _preliminaryTraining(self): """Initialize starting snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") self.computeScaleFactor() if self.samplingEngine.nsamples > 0: return if self.verbosity >= 2: verbosityDepth("INIT", "Starting computation of snapshots.", timestamp = self.timestamp) self.resetSamples() self.mus = self.trainSetGenerator.generatePoints(self.S) muLast = copy(self.mus[-1]) self.mus.pop() muTestBase = self.sampler.generatePoints(self.nTestPoints) if len(self.mus) > 0: if self.verbosity >= 2: verbosityDepth("MAIN", ("Adding first {} samples point at {} to " "training set.").format(np.prod(self.S) - 1, self.mus), timestamp = self.timestamp) self.samplingEngine.iterSample(self.mus, homogeneized = self.homogeneized) muTestBase = pruneSamples(muTestBase, self.mus, 1e-10 * self.scaleFactor[0]).sort() self.muTest = emptyParameterList() self.muTest.reset((len(muTestBase) + 1, self.mus.shape[1])) self.muTest[: -1] = muTestBase self.muTest[-1] = muLast def _enrichTestSet(self, nTest:int): """Add extra elements to test set.""" - + RROMPyAssert(self._mode, message = "Cannot enrich test set.") muTestExtra = self.sampler.generatePoints(2 * nTest) muTotal = copy(self.mus) muTotal.append(self.muTest) muTestExtra = pruneSamples(muTestExtra, muTotal, 1e-10 * self.scaleFactor[0]) muTestNew = np.empty(len(self.muTest) + len(muTestExtra), dtype = np.complex) muTestNew[: len(self.muTest)] = self.muTest(0) muTestNew[len(self.muTest) :] = muTestExtra(0) self.muTest = checkParameterList(muTestNew.sort(), self.npar) if self.verbosity >= 5: verbosityDepth("MAIN", "Enriching test set by {} elements.".format( len(muTestExtra)), timestamp = self.timestamp) def greedy(self, plotEst : bool = False): """Compute greedy snapshots of solution map.""" RROMPyAssert(self._mode, message = "Cannot start greedy algorithm.") if self.samplingEngine.nsamples > 0: return self._preliminaryTraining() nTest = self.nTestPoints errorEstTest, muidx, maxErrorEst, mu = self.greedyNextSample(-1, plotEst) if self.verbosity >= 2: verbosityDepth("MAIN", ("Uniform testing error estimate " "{:.4e}.").format(maxErrorEst), timestamp = self.timestamp) trainedModelOld = copy(self.trainedModel) while (self.samplingEngine.nsamples < self.maxIter and maxErrorEst > self.greedyTol): if (1. - self.refinementRatio) * nTest > len(self.muTest): self._enrichTestSet(nTest) nTest = len(self.muTest) muTestOld, maxErrorEstOld = self.muTest, maxErrorEst errorEstTest, muidx, maxErrorEst, mu = self.greedyNextSample( muidx, plotEst) if self.verbosity >= 2: verbosityDepth("MAIN", ("Uniform testing error estimate " "{:.4e}.").format(maxErrorEst), timestamp = self.timestamp) if (np.isnan(maxErrorEst) or np.isinf(maxErrorEst) or maxErrorEstOld < maxErrorEst * self.TOL_INSTABILITY): RROMPyWarning(("Instability in a posteriori estimator. " "Starting preemptive greedy loop termination.")) maxErrorEst = maxErrorEstOld self.muTest = muTestOld self.mus = self.mus[:-1] self.samplingEngine.popSample() self.trainedModel.data = copy(trainedModelOld.data) break trainedModelOld.data = copy(self.trainedModel.data) if (self.interactive and maxErrorEst <= self.greedyTol): verbosityDepth("MAIN", ("Required tolerance {} achieved. Want " "to decrease greedyTol and continue? " "Y/N").format(self.greedyTol), timestamp = self.timestamp, end = "") increasemaxIter = input() if increasemaxIter.upper() == "Y": verbosityDepth("MAIN", "Reducing value of greedyTol...", timestamp = self.timestamp) while maxErrorEst <= self._greedyTol: self._greedyTol *= .5 if (self.interactive and self.samplingEngine.nsamples >= self.maxIter): verbosityDepth("MAIN", ("Maximum number of iterations {} " "reached. Want to increase maxIter " "and continue? Y/N").format( self.maxIter), timestamp = self.timestamp, end = "") increasemaxIter = input() if increasemaxIter.upper() == "Y": verbosityDepth("MAIN", "Doubling value of maxIter...", timestamp = self.timestamp) self._maxIter *= 2 if self.verbosity >= 2: verbosityDepth("DEL", ("Done computing snapshots (final snapshot " "count: {}).").format( self.samplingEngine.nsamples), timestamp = self.timestamp) def checkComputedApprox(self) -> bool: """ Check if setup of new approximant is not needed. Returns: True if new setup is not needed. False otherwise. """ return (super().checkComputedApprox() and len(self.mus) == self.trainedModel.data.projMat.shape[1]) def assembleReducedResidualGramian(self, pMat:sampList): """ Build residual gramian of reduced linear system through projections. """ self.initEstimatorNormEngine() if (not hasattr(self.trainedModel.data, "gramian") or self.trainedModel.data.gramian is None): gramian = self.estimatorNormEngine.innerProduct(pMat, pMat) else: Sold = self.trainedModel.data.gramian.shape[0] S = len(self.mus) if Sold > S: gramian = self.trainedModel.data.gramian[: S, : S] else: idxOld = list(range(Sold)) idxNew = list(range(Sold, S)) gramian = np.empty((S, S), dtype = np.complex) gramian[: Sold, : Sold] = self.trainedModel.data.gramian gramian[: Sold, Sold :] = ( self.estimatorNormEngine.innerProduct(pMat(idxNew), pMat(idxOld))) gramian[Sold :, : Sold] = gramian[: Sold, Sold :].T.conj() gramian[Sold :, Sold :] = ( self.estimatorNormEngine.innerProduct(pMat(idxNew), pMat(idxNew))) self.trainedModel.data.gramian = gramian def assembleReducedResidualBlocksbb(self, bs:List[Np1D], scaling : float = 1.): """ Build blocks (of type bb) of reduced linear system through projections. """ self.initEstimatorNormEngine() nbs = len(bs) if (not hasattr(self.trainedModel.data, "resbb") or self.trainedModel.data.resbb is None): resbb = np.empty((nbs, nbs), dtype = np.complex) for i in range(nbs): Mbi = scaling ** i * bs[i] resbb[i, i] = self.estimatorNormEngine.innerProduct(Mbi, Mbi) for j in range(i): Mbj = scaling ** j * bs[j] resbb[i, j] = self.estimatorNormEngine.innerProduct(Mbj, Mbi) for i in range(nbs): for j in range(i + 1, nbs): resbb[i, j] = resbb[j, i].conj() self.trainedModel.data.resbb = resbb def assembleReducedResidualBlocksAb(self, As:List[Np2D], bs:List[Np1D], pMat:sampList, scaling : float = 1.): """ Build blocks (of type Ab) of reduced linear system through projections. """ self.initEstimatorNormEngine() nAs = len(As) nbs = len(bs) S = len(self.mus) if (not hasattr(self.trainedModel.data, "resAb") or self.trainedModel.data.resAb is None): if not isinstance(pMat, (np.ndarray,)): pMat = pMat.data resAb = np.empty((nbs, S, nAs), dtype = np.complex) for j in range(nAs): MAj = scaling ** (j + 1) * As[j].dot(pMat) for i in range(nbs): Mbi = scaling ** (i + 1) * bs[i] resAb[i, :, j] = self.estimatorNormEngine.innerProduct(MAj, Mbi) else: Sold = self.trainedModel.data.resAb.shape[1] if Sold == S: return if Sold > S: resAb = self.trainedModel.data.resAb[:, : S, :] else: if not isinstance(pMat, (np.ndarray,)): pMat = pMat.data resAb = np.empty((nbs, S, nAs), dtype = np.complex) resAb[:, : Sold, :] = self.trainedModel.data.resAb for j in range(nAs): MAj = scaling ** (j + 1) * As[j].dot(pMat[:, Sold :]) for i in range(nbs): Mbi = scaling ** (i + 1) * bs[i] resAb[i, Sold :, j] = ( self.estimatorNormEngine.innerProduct(MAj, Mbi)) self.trainedModel.data.resAb = resAb def assembleReducedResidualBlocksAA(self, As:List[Np2D], pMat:sampList, - scaling : float = 1., - basic : bool = False): + scaling : float = 1.): """ Build blocks (of type AA) of reduced linear system through projections. """ self.initEstimatorNormEngine() nAs = len(As) S = len(self.mus) if (not hasattr(self.trainedModel.data, "resAA") or self.trainedModel.data.resAA is None): if not isinstance(pMat, (np.ndarray,)): pMat = pMat.data - if basic: - MAEnd = scaling ** nAs * As[-1].dot(pMat) - resAA = self.estimatorNormEngine.innerProduct(MAEnd, MAEnd) - else: - resAA = np.empty((S, nAs, S, nAs), dtype = np.complex) - for i in range(nAs): - MAi = scaling ** (i + 1) * As[i].dot(pMat) - resAA[:, i, :, i] = ( + resAA = np.empty((S, nAs, S, nAs), dtype = np.complex) + for i in range(nAs): + MAi = scaling ** (i + 1) * As[i].dot(pMat) + resAA[:, i, :, i] = ( self.estimatorNormEngine.innerProduct(MAi, MAi)) - for j in range(i): - MAj = scaling ** (j + 1) * As[j].dot(pMat) - resAA[:, i, :, j] = ( + for j in range(i): + MAj = scaling ** (j + 1) * As[j].dot(pMat) + resAA[:, i, :, j] = ( self.estimatorNormEngine.innerProduct(MAj, MAi)) - for i in range(nAs): - for j in range(i + 1, nAs): - resAA[:, i, :, j] = resAA[:, j, :, i].T.conj() + for i in range(nAs): + for j in range(i + 1, nAs): + resAA[:, i, :, j] = resAA[:, j, :, i].T.conj() else: Sold = self.trainedModel.data.resAA.shape[0] if Sold == S: return if Sold > S: - if basic: - resAA = self.trainedModel.data.resAA[: S, : S] - else: - resAA = self.trainedModel.data.resAA[: S, :, : S, :] + resAA = self.trainedModel.data.resAA[: S, :, : S, :] else: if not isinstance(pMat, (np.ndarray,)): pMat = pMat.data - if basic: - resAA = np.empty((S, S), dtype = np.complex) - resAA[: Sold, : Sold] = self.trainedModel.data.resAA - MAi = scaling ** nAs * As[-1].dot(pMat) - resAA[: Sold, Sold :] = ( - self.estimatorNormEngine.innerProduct(MAi[:, Sold :], - MAi[:, : Sold])) - resAA[Sold :, : Sold] = resAA[: Sold, Sold :].T.conj() - resAA[Sold :, Sold :] = ( - self.estimatorNormEngine.innerProduct(MAi[:, Sold :], - MAi[:, Sold :])) - else: - resAA = np.empty((S, nAs, S, nAs), dtype = np.complex) - resAA[: Sold, :, : Sold, :] = self.trainedModel.data.resAA - for i in range(nAs): - MAi = scaling ** (i + 1) * As[i].dot(pMat) - resAA[: Sold, i, Sold :, i] = ( + resAA = np.empty((S, nAs, S, nAs), dtype = np.complex) + resAA[: Sold, :, : Sold, :] = self.trainedModel.data.resAA + for i in range(nAs): + MAi = scaling ** (i + 1) * As[i].dot(pMat) + resAA[: Sold, i, Sold :, i] = ( self.estimatorNormEngine.innerProduct(MAi[:, Sold :], MAi[:, : Sold])) - resAA[Sold :, i, : Sold, i] = resAA[: Sold, i, - Sold :, i].T.conj() - resAA[Sold :, i, Sold :, i] = ( + resAA[Sold :, i, : Sold, i] = resAA[: Sold, i, + Sold :, i].T.conj() + resAA[Sold :, i, Sold :, i] = ( self.estimatorNormEngine.innerProduct(MAi[:, Sold :], MAi[:, Sold :])) - for j in range(i): - MAj = scaling ** (j + 1) * As[j].dot(pMat) - resAA[: Sold, i, Sold :, j] = ( + for j in range(i): + MAj = scaling ** (j + 1) * As[j].dot(pMat) + resAA[: Sold, i, Sold :, j] = ( self.estimatorNormEngine.innerProduct(MAj[:, Sold :], MAi[:, : Sold])) - resAA[Sold :, i, : Sold, j] = ( + resAA[Sold :, i, : Sold, j] = ( self.estimatorNormEngine.innerProduct(MAj[:, : Sold], MAi[:, Sold :])) - resAA[Sold :, i, Sold :, j] = ( + resAA[Sold :, i, Sold :, j] = ( self.estimatorNormEngine.innerProduct(MAj[:, Sold :], MAi[:, Sold :])) - for i in range(nAs): - for j in range(i + 1, nAs): - resAA[: Sold, i, Sold :, j] = ( + for i in range(nAs): + for j in range(i + 1, nAs): + resAA[: Sold, i, Sold :, j] = ( resAA[Sold :, j, : Sold, i].T.conj()) - resAA[Sold :, i, : Sold, j] = ( + resAA[Sold :, i, : Sold, j] = ( resAA[: Sold, j, Sold :, i].T.conj()) - resAA[Sold :, i, Sold :, j] = ( + resAA[Sold :, i, Sold :, j] = ( resAA[Sold :, j, Sold :, i].T.conj()) self.trainedModel.data.resAA = resAA diff --git a/rrompy/reduction_methods/distributed_greedy/rational_interpolant_greedy.py b/rrompy/reduction_methods/distributed_greedy/rational_interpolant_greedy.py index bbf14d2..0ec150b 100644 --- a/rrompy/reduction_methods/distributed_greedy/rational_interpolant_greedy.py +++ b/rrompy/reduction_methods/distributed_greedy/rational_interpolant_greedy.py @@ -1,456 +1,416 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from copy import deepcopy as copy import numpy as np from .generic_distributed_greedy_approximant import \ GenericDistributedGreedyApproximant from rrompy.utilities.poly_fitting.polynomial import polybases, polydomcoeff from rrompy.reduction_methods.distributed import RationalInterpolant from rrompy.reduction_methods.trained_model import TrainedModelPade as tModel from rrompy.reduction_methods.trained_model import TrainedModelData from rrompy.utilities.base.types import Np1D, Np2D, DictAny, HFEng, paramVal -from rrompy.utilities.base import purgeDict, verbosityDepth +from rrompy.utilities.base import verbosityDepth from rrompy.utilities.exception_manager import RROMPyWarning from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert __all__ = ['RationalInterpolantGreedy'] class RationalInterpolantGreedy(GenericDistributedGreedyApproximant, RationalInterpolant): """ ROM greedy rational interpolant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - - 'muBounds': list of bounds for parameter values; defaults to - [0, 1]; - 'S': number of starting training points; - - 'sampler': sample point generator; defaults to uniform sampler on - muBounds; - - 'basis': type of basis for interpolation; allowed values include - 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to 'MONOMIAL'; - - 'Delta': difference between M and N in rational approximant; - defaults to 0; + - 'sampler': sample point generator; - 'greedyTol': uniform error tolerance for greedy algorithm; defaults to 1e-2; - - 'errorEstimatorKind': kind of error estimator; available values - include 'EXACT', 'BASIC', and 'BARE'; defaults to 'EXACT'; + - 'interactive': whether to interactively terminate greedy + algorithm; defaults to False; - 'maxIter': maximum number of greedy steps; defaults to 1e2; - 'refinementRatio': ratio of training points to be exhausted before training set refinement; defaults to 0.2; - - 'nTestPoints': number of test points; defaults to maxIter / - refinementRatio; + - 'nTestPoints': number of test points; defaults to 5e2; - 'trainSetGenerator': training sample points generator; defaults to Chebyshev sampler within muBounds; - - 'interpRcond': tolerance for interpolation via numpy.polyfit; - defaults to None; + - 'polybasis': type of basis for interpolation; allowed values + include 'MONOMIAL', 'CHEBYSHEV' and 'LEGENDRE'; defaults to + 'MONOMIAL'; + - 'Delta': difference between M and N in rational approximant; + defaults to 0; + - 'errorEstimatorKind': kind of error estimator; available values + include 'EXACT', 'BASIC', and 'BARE'; defaults to 'EXACT'; + - 'interpRcond': tolerance for interpolation; defaults to None; - 'robustTol': tolerance for robust Pade' denominator management; defaults to 0. Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. homogeneized: Whether to homogeneize Dirichlet BCs. 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; - - 'muBounds': list of bounds for parameter values; - - 'S': number of starting training points; - - 'sampler': sample point generator; - - 'basis': type of basis for interpolation; - - 'Delta': difference between M and N in rational approximant; + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots. - 'greedyTol': uniform error tolerance for greedy algorithm; - - 'errorEstimatorKind': kind of error estimator; + - 'interactive': whether to interactively terminate greedy + algorithm; - 'maxIter': maximum number of greedy steps; - - 'refinementRatio': ratio of training points to be exhausted - before training set refinement; + - 'refinementRatio': ratio of test points to be exhausted before + test set refinement; - 'nTestPoints': number of test points; - 'trainSetGenerator': training sample points generator; - - 'interpRcond': tolerance for interpolation via numpy.polyfit; + - 'Delta': difference between M and N in rational approximant; + - 'errorEstimatorKind': kind of error estimator; + - 'interpRcond': tolerance for interpolation; - 'robustTol': tolerance for robust Pade' denominator management. - extraApproxParameters: List of approxParameters keys in addition to - mother class's. + parameterListCritical: Recognized keys of critical approximant + parameters: + - 'S': total number of samples current approximant relies upon; + - 'sampler': sample point generator. POD: whether to compute POD of snapshots. - muBounds: list of bounds for parameter values. - S: number of starting training points. + S: number of test points. sampler: Sample point generator. greedyTol: uniform error tolerance for greedy algorithm. - errorEstimatorKind: kind of error estimator. + interactive: whether to interactively terminate greedy algorithm. maxIter: maximum number of greedy steps. refinementRatio: ratio of training points to be exhausted before training set refinement. nTestPoints: number of starting training points. trainSetGenerator: training sample points generator. - interpRcond: Tolerance for interpolation via numpy.polyfit. - robustTol: Tolerance for robust Pade' denominator management. + robustTol: tolerance for robust Pade' denominator management. + Delta: difference between M and N in rational approximant. + errorEstimatorKind: kind of error estimator. + interpRcond: tolerance for interpolation. + robustTol: tolerance for robust Pade' denominator management. + muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. estimatorNormEngine: Engine for estimator norm computation. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. """ _allowedEstimatorKinds = ["EXACT", "BASIC", "BARE"] def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() - self._addParametersToList(["polybasis", "Delta", "errorEstimatorKind", - "interpRcond", "robustTol"]) + self._addParametersToList(["Delta", "polybasis", "errorEstimatorKind", + "interpRcond", "robustTol"], + [0, "MONOMIAL", "EXACT", -1, 0], + toBeExcluded = ["E"]) super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, homogeneized = homogeneized, verbosity = verbosity, timestamp = timestamp) if self.verbosity >= 7: verbosityDepth("INIT", "Computing Taylor blocks of system.", timestamp = self.timestamp) nAs = self.HFEngine.nAs - 1 nbs = max(self.HFEngine.nbs, (nAs + 1) * self.homogeneized) self.As = [self.HFEngine.A(self.mu0, j + 1) for j in range(nAs)] self.bs = [self.HFEngine.b(self.mu0, j, self.homogeneized) for j in range(nbs)] if self.verbosity >= 7: verbosityDepth("DEL", "Done computing Taylor blocks.", timestamp = self.timestamp) self._postInit() - @property - def approxParameters(self): - """ - Value of approximant parameters. Its assignment may change robustTol. - """ - return self._approxParameters - @approxParameters.setter - def approxParameters(self, approxParams): - approxParameters = purgeDict(approxParams, self.parameterList, - dictname = self.name() + ".approxParameters", - baselevel = 1) - approxParametersCopy = purgeDict(approxParameters, ["polybasis", - "Delta", - "errorEstimatorKind", - "interpRcond", - "robustTol"], - True, True, baselevel = 1) - if "Delta" in list(approxParameters.keys()): - self._Delta = approxParameters["Delta"] - elif not hasattr(self, "_Delta") or self._Delta is None: - self._Delta = 0 - GenericDistributedGreedyApproximant.approxParameters.fset(self, - approxParametersCopy) - keyList = list(approxParameters.keys()) - self.Delta = self.Delta - if "polybasis" in keyList: - self.polybasis = approxParameters["polybasis"] - elif not hasattr(self, "_polybasis") or self._polybasis is None: - self.polybasis = "MONOMIAL" - if "errorEstimatorKind" in keyList: - self.errorEstimatorKind = approxParameters["errorEstimatorKind"] - elif (not hasattr(self, "_errorEstimatorKind") - or self.errorEstimatorKind is None): - self.errorEstimatorKind = "EXACT" - if "interpRcond" in keyList: - self.interpRcond = approxParameters["interpRcond"] - elif not hasattr(self, "interpRcond") or self.interpRcond is None: - self.interpRcond = None - if "robustTol" in keyList: - self.robustTol = approxParameters["robustTol"] - elif not hasattr(self, "_robustTol") or self._robustTol is None: - self.robustTol = 0 - @property def polybasis(self): """Value of polybasis.""" return self._polybasis @polybasis.setter def polybasis(self, polybasis): try: polybasis = polybasis.upper().strip().replace(" ","") if polybasis not in polybases: raise RROMPyException("Sample type not recognized.") self._polybasis = polybasis except: RROMPyWarning(("Prescribed polybasis not recognized. Overriding " "to 'MONOMIAL'.")) self._polybasis = "MONOMIAL" self._approxParameters["polybasis"] = self.polybasis @property def Delta(self): """Value of Delta.""" return self._Delta @Delta.setter def Delta(self, Delta): if not np.isclose(Delta, np.floor(Delta)): raise RROMPyException("Delta must be an integer.") if Delta < 0: RROMPyWarning(("Error estimator unreliable for Delta < 0. " "Overloading of errorEstimator is suggested.")) else: Deltamin = (max(self.HFEngine.nbs, self.HFEngine.nAs * self.homogeneized) - 1 - 1 * (self.HFEngine.nAs > 1)) if Delta < Deltamin: RROMPyWarning(("Method may be unreliable for selected Delta. " "Suggested minimal value of Delta: {}.").format( Deltamin)) self._Delta = Delta self._approxParameters["Delta"] = self.Delta @property def errorEstimatorKind(self): """Value of errorEstimatorKind.""" return self._errorEstimatorKind @errorEstimatorKind.setter def errorEstimatorKind(self, errorEstimatorKind): errorEstimatorKind = errorEstimatorKind.upper() if errorEstimatorKind not in self._allowedEstimatorKinds: RROMPyWarning(("Error estimator kind not recognized. Overriding " "to 'EXACT'.")) errorEstimatorKind = "EXACT" self._errorEstimatorKind = errorEstimatorKind self._approxParameters["errorEstimatorKind"] = self.errorEstimatorKind @property def nTestPoints(self): """Value of nTestPoints.""" return self._nTestPoints @nTestPoints.setter def nTestPoints(self, nTestPoints): if nTestPoints <= np.abs(self.Delta): RROMPyWarning(("nTestPoints must be at least abs(Delta) + 1. " "Increasing value to abs(Delta) + 1.")) nTestPoints = np.abs(self.Delta) + 1 if not np.isclose(nTestPoints, np.int(nTestPoints)): raise RROMPyException("nTestPoints must be an integer.") nTestPoints = np.int(nTestPoints) if hasattr(self, "_nTestPoints") and self.nTestPoints is not None: nTestPointsold = self.nTestPoints else: nTestPointsold = -1 self._nTestPoints = nTestPoints self._approxParameters["nTestPoints"] = self.nTestPoints if nTestPointsold != self.nTestPoints: self.resetSamples() def _errorSamplingRatio(self, mus:Np1D, muRTest:Np1D, muRTrain:Np1D) -> Np1D: """Scalar ratio in explicit error estimator.""" + self.setupApprox() testTile = np.tile(np.reshape(muRTest, (-1, 1)), [1, len(muRTrain)]) nodalVals = np.prod(testTile - np.reshape(muRTrain, (1, -1)), axis = 1) denVals = self.trainedModel.getQVal(mus) return np.abs(nodalVals / denVals) def _RHSNorms(self, radiusb0:Np2D) -> Np1D: """High fidelity system RHS norms.""" + self.assembleReducedResidualBlocks(full = False) # 'ij,jk,ik->k', resbb, radiusb0, radiusb0.conj() b0resb0 = np.sum(self.trainedModel.data.resbb.dot(radiusb0) * radiusb0.conj(), axis = 0) RHSnorms = np.power(np.abs(b0resb0), .5) return RHSnorms def _errorEstimatorBare(self) -> Np1D: """Bare residual-based error estimator.""" + self.setupApprox() self.assembleReducedResidualGramian(self.trainedModel.data.projMat) pDom = self.trainedModel.data.P[:, -1] LL = pDom.conj().dot(self.trainedModel.data.gramian.dot(pDom)) Adiag = self.As[0].diagonal() LL = ((self.scaleFactor[0] * np.linalg.norm(Adiag)) ** 2. * LL / np.size(Adiag)) scalingDom = polydomcoeff(len(self.mus) - 1, self.polybasis) return scalingDom * np.power(np.abs(LL), .5) - def _errorEstimatorBasic(self, muTest:complex, ratioTest:complex) -> Np1D: + def _errorEstimatorBasic(self, muTest:paramVal, ratioTest:complex) -> Np1D: """Basic residual-based error estimator.""" resmu = self.HFEngine.residual(self.trainedModel.getApprox(muTest), muTest, self.homogeneized)[0] return np.abs(self.estimatorNormEngine.norm(resmu) / ratioTest) def _errorEstimatorExact(self, muRTrain:Np1D, vanderBase:Np2D) -> Np1D: """Exact residual-based error estimator.""" + self.setupApprox() + self.assembleReducedResidualBlocks(full = True) nAs = self.HFEngine.nAs - 1 nbs = max(self.HFEngine.nbs - 1, nAs * self.homogeneized) delta = len(self.mus) - len(self.trainedModel.data.Q) nbsEff = max(0, nbs - delta) momentQ = np.zeros(nbsEff, dtype = np.complex) momentQu = np.zeros((len(self.mus), nAs), dtype = np.complex) radiusbTen = np.zeros((nbsEff, nbsEff, vanderBase.shape[1]), dtype = np.complex) radiusATen = np.zeros((nAs, nAs, vanderBase.shape[1]), dtype = np.complex) if nbsEff > 0: momentQ[0] = self.trainedModel.data.Q[-1] radiusbTen[0, :, :] = vanderBase[: nbsEff, :] momentQu[:, 0] = self.trainedModel.data.P[:, -1] radiusATen[0, :, :] = vanderBase[: nAs, :] Qvals = self.trainedModel.getQVal(self.mus) for k in range(1, max(nAs, nbs * (nbsEff > 0))): Qvals = Qvals * muRTrain if k > delta and k < nbs: momentQ[k - delta] = self._fitinv.dot(Qvals) radiusbTen[k - delta, k :, :] = ( radiusbTen[0, : delta - k, :]) if k < nAs: momentQu[:, k] = Qvals * self._fitinv radiusATen[k, k :, :] = radiusATen[0, : - k, :] if self.POD and nAs > 1: momentQu[:, 1 :] = self.samplingEngine.RPOD.dot( momentQu[:, 1 :]) radiusA = np.tensordot(momentQu, radiusATen, 1) if nbsEff > 0: radiusb = np.tensordot(momentQ, radiusbTen, 1) # 'ij,jk,ik->k', resbb, radiusb, radiusb.conj() ff = np.sum(self.trainedModel.data.resbb[delta + 1 :, delta + 1 :]\ .dot(radiusb) * radiusb.conj(), axis = 0) # 'ijk,jkl,il->l', resAb, radiusA, radiusb.conj() Lf = np.sum(np.tensordot( self.trainedModel.data.resAb[delta :, :, :], radiusA, 2) * radiusb.conj(), axis = 0) else: ff, Lf = 0., 0. # 'ijkl,klt,ijt->t', resAA, radiusA, radiusA.conj() LL = np.sum(np.tensordot(self.trainedModel.data.resAA, radiusA, 2) * radiusA.conj(), axis = (0, 1)) scalingDom = polydomcoeff(len(self.mus) - 1, self.polybasis) return scalingDom * np.power(np.abs(ff - 2. * np.real(Lf) + LL), .5) def errorEstimator(self, mus:Np1D) -> Np1D: """Standard residual-based error estimator.""" self.setupApprox() if (np.any(np.isnan(self.trainedModel.data.P[:, -1])) or np.any(np.isinf(self.trainedModel.data.P[:, -1]))): err = np.empty(len(mus)) err[:] = np.inf return err nAs = self.HFEngine.nAs - 1 nbs = max(self.HFEngine.nbs - 1, nAs * self.homogeneized) muRTest = self.centerNormalize(mus)(0) muRTrain = self.centerNormalize(self.mus)(0) - self.assembleReducedResidualBlocks(kind = self.errorEstimatorKind) samplingRatio = self._errorSamplingRatio(mus, muRTest, muRTrain) vanderBase = np.polynomial.polynomial.polyvander(muRTest, max(nAs, nbs)).T RHSnorms = self._RHSNorms(vanderBase[: nbs + 1, :]) if self.errorEstimatorKind == "BARE": jOpt = self._errorEstimatorBare() elif self.errorEstimatorKind == "BASIC": idx_muTestSample = np.argmax(samplingRatio) - muTestSample = mus[idx_muTestSample] - samplingRatioTestSample = samplingRatio[idx_muTestSample] - jOpt = self._errorEstimatorBasic(muTestSample, - samplingRatioTestSample) + jOpt = self._errorEstimatorBasic(mus[idx_muTestSample], + samplingRatio[idx_muTestSample]) else: #if self.errorEstimatorKind == "EXACT": jOpt = self._errorEstimatorExact(muRTrain, vanderBase[: -1, :]) return jOpt * samplingRatio / RHSnorms def setupApprox(self, plotEst : bool = False): """ Compute Pade' interpolant. SVD-based robust eigenvalue management. """ if self.checkComputedApprox(): return RROMPyAssert(self._mode, message = "Cannot setup approximant.") if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name()), timestamp = self.timestamp) self.computeScaleFactor() self.greedy(plotEst) - self._N = len(self.mus) - 1 - self._M = copy(self._N) - self.E = copy(self._N) + self._S = len(self.mus) + self._N, self._M, self._E = [self._S - 1] * 3 if self.Delta < 0: self._M += self.Delta else: self._N -= self.Delta if self.trainedModel is None: self.trainedModel = tModel() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp data = TrainedModelData(self.trainedModel.name(), self.mu0, self.samplingEngine.samples, self.HFEngine.rescalingExp) data.polytype = self.polybasis data.scaleFactor = self.scaleFactor data.mus = copy(self.mus) self.trainedModel.data = data else: self.trainedModel = self.trainedModel self.trainedModel.data.projMat = copy(self.samplingEngine.samples) self.trainedModel.data.mus = copy(self.mus) if min(self.M, self.N) < 0: if self.verbosity >= 5: verbosityDepth("MAIN", "Minimal sample size not achieved.", timestamp = self.timestamp) Q = np.empty(max(self.N, 0) + 1, dtype = np.complex) P = np.empty((len(self.mus), max(self.M, 0) + 1), dtype = np.complex) Q[:] = np.nan P[:] = np.nan self.trainedModel.data.Q = copy(Q) self.trainedModel.data.P = copy(P) self.trainedModel.data.approxParameters = copy( self.approxParameters) if self.verbosity >= 5: verbosityDepth("DEL", "Aborting computation of approximant.", timestamp = self.timestamp) return if self.N > 0: Q = self._setupDenominator() else: Q = np.ones(1, dtype = np.complex) self.trainedModel.data.Q = copy(Q) P = self._setupNumerator() if self.POD: P = np.tensordot(self.samplingEngine.RPOD, P, axes = ([-1], [0])) self.trainedModel.data.P = copy(P) self.trainedModel.data.approxParameters = copy(self.approxParameters) if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.", timestamp = self.timestamp) - def assembleReducedResidualBlocks(self, kind : str = "EXACT"): + def assembleReducedResidualBlocks(self, full : bool = False): """Build affine blocks of reduced linear system through projections.""" scaling = self.trainedModel.data.scaleFactor[0] self.assembleReducedResidualBlocksbb(self.bs, scaling) - if kind == "EXACT": + if full: pMat = self.trainedModel.data.projMat self.assembleReducedResidualBlocksAb(self.As, self.bs[1 :], pMat, scaling) - self.assembleReducedResidualBlocksAA(self.As, pMat, scaling, - basic = (kind == "BASIC")) + self.assembleReducedResidualBlocksAA(self.As, pMat, scaling) diff --git a/rrompy/reduction_methods/distributed_greedy/rb_distributed_greedy.py b/rrompy/reduction_methods/distributed_greedy/rb_distributed_greedy.py index ab9d918..fc03fd1 100644 --- a/rrompy/reduction_methods/distributed_greedy/rb_distributed_greedy.py +++ b/rrompy/reduction_methods/distributed_greedy/rb_distributed_greedy.py @@ -1,262 +1,245 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from copy import deepcopy as copy from .generic_distributed_greedy_approximant import \ GenericDistributedGreedyApproximant from rrompy.reduction_methods.distributed import RBDistributed from rrompy.reduction_methods.trained_model import TrainedModelRB as tModel from rrompy.reduction_methods.trained_model import TrainedModelData from rrompy.utilities.base.types import Np1D, DictAny, HFEng, paramVal from rrompy.utilities.base import verbosityDepth -from rrompy.utilities.exception_manager import RROMPyException, RROMPyAssert +from rrompy.utilities.exception_manager import RROMPyWarning, RROMPyAssert from rrompy.parameter import checkParameterList __all__ = ['RBDistributedGreedy'] class RBDistributedGreedy(GenericDistributedGreedyApproximant, RBDistributed): """ ROM greedy RB approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - - 'muBounds': list of bounds for parameter values; defaults to - [0, 1]; - 'S': number of starting training points; - - 'sampler': sample point generator; defaults to uniform sampler on - muBounds; + - 'sampler': sample point generator; - 'greedyTol': uniform error tolerance for greedy algorithm; defaults to 1e-2; + - 'interactive': whether to interactively terminate greedy + algorithm; defaults to False; - 'maxIter': maximum number of greedy steps; defaults to 1e2; - - 'refinementRatio': ratio of training points to be exhausted - before training set refinement; defaults to 0.2; - - 'nTestPoints': number of test points; defaults to maxIter / - refinementRatio; + - 'refinementRatio': ratio of test points to be exhausted before + test set refinement; defaults to 0.2; + - 'nTestPoints': number of test points; defaults to 5e2; - 'trainSetGenerator': training sample points generator; defaults to Chebyshev sampler within muBounds. Defaults to empty dict. homogeneized(optional): Whether to homogeneize Dirichlet BCs. Defaults to False. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. homogeneized: Whether to homogeneize Dirichlet BCs. 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; - - 'muBounds': list of bounds for parameter values; - - 'S': number of starting training points; - - 'sampler': sample point generator; + parameterListSoft: Recognized keys of soft approximant parameters: + - 'POD': whether to compute POD of snapshots. - 'greedyTol': uniform error tolerance for greedy algorithm; + - 'interactive': whether to interactively terminate greedy + algorithm; - 'maxIter': maximum number of greedy steps; - - 'refinementRatio': ratio of training points to be exhausted - before training set refinement; + - 'refinementRatio': ratio of test points to be exhausted before + test set refinement; - 'nTestPoints': number of test points; - 'trainSetGenerator': training sample points generator. - extraApproxParameters: List of approxParameters keys in addition to - mother class's. + parameterListCritical: Recognized keys of critical approximant + parameters: + - 'S': total number of samples current approximant relies upon; + - 'sampler': sample point generator. POD: whether to compute POD of snapshots. - muBounds: list of bounds for parameter values. S: number of test points. sampler: Sample point generator. greedyTol: uniform error tolerance for greedy algorithm. + interactive: whether to interactively terminate greedy algorithm. maxIter: maximum number of greedy steps. refinementRatio: ratio of training points to be exhausted before training set refinement. nTestPoints: number of starting training points. trainSetGenerator: training sample points generator. + muBounds: list of bounds for parameter values. samplingEngine: Sampling engine. estimatorNormEngine: Engine for estimator norm computation. uHF: High fidelity solution(s) with parameter(s) lastSolvedHF as sampleList. lastSolvedHF: Parameter(s) corresponding to last computed high fidelity solution(s) as parameterList. uAppReduced: Reduced approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedAppReduced: Parameter(s) corresponding to last computed reduced approximate solution(s) as parameterList. uApp: Approximate solution(s) with parameter(s) lastSolvedApp as sampleList. lastSolvedApp: Parameter(s) corresponding to last computed approximate solution(s) as parameterList. As: List of sparse matrices (in CSC format) representing coefficients of linear system matrix wrt theta(mu). bs: List of numpy vectors representing coefficients of linear system RHS wrt theta(mu). thetaAs: List of callables representing coefficients of linear system matrix wrt mu. thetabs: List of callables representing coefficients of linear system RHS wrt mu. ARBs: List of sparse matrices (in CSC format) representing coefficients of compressed linear system matrix wrt theta(mu). bRBs: List of numpy vectors representing coefficients of compressed linear system RHS wrt theta(mu). """ def __init__(self, HFEngine:HFEng, mu0 : paramVal = None, approxParameters : DictAny = {}, homogeneized : bool = False, verbosity : int = 10, timestamp : bool = True): self._preInit() super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, homogeneized = homogeneized, verbosity = verbosity, timestamp = timestamp) if self.verbosity >= 10: verbosityDepth("INIT", "Computing affine blocks of system.", timestamp = self.timestamp) self.As = self.HFEngine.affineLinearSystemA(self.mu0) self.bs = self.HFEngine.affineLinearSystemb(self.mu0, self.homogeneized) if self.verbosity >= 10: verbosityDepth("DEL", "Done computing affine blocks.", timestamp = self.timestamp) self._postInit() @property def R(self): """Value of R.""" - return self._S + self._R = np.prod(self._S) + return self._R @R.setter def R(self, R): - raise RROMPyException(("R is used just to simplify inheritance, and " - "its value cannot be changed from that of S.")) + RROMPyWarning(("R is used just to simplify inheritance, and its value " + "cannot be changed from that of prod(S).")) def errorEstimator(self, mus:Np1D) -> Np1D: """ Standard residual-based error estimator. Unreliable for unstable problems (inf-sup constant is missing). """ self.setupApprox() - self.assembleReducedResidualBlocks() + self.assembleReducedResidualBlocks(full = True) nmus = len(mus) nAs = self.trainedModel.data.resAA.shape[1] nbs = self.trainedModel.data.resbb.shape[0] thetaAs = self.trainedModel.data.thetaAs thetabs = self.trainedModel.data.thetabs radiusA = np.empty((len(self.mus), nAs, nmus), dtype = np.complex) radiusb = np.empty((nbs, nmus), dtype = np.complex) verb = self.trainedModel.verbosity self.trainedModel.verbosity = 0 if verb >= 5: mustr = mus if nmus > 2: mustr = "[{} ..({}).. {}]".format(mus[0], nmus - 2, mus[-1]) verbosityDepth("INIT", ("Computing RB solution at mu = " "{}.").format(mustr), timestamp = self.timestamp) parmus, _ = checkParameterList(mus, self.npar) uApps = self.getApproxReduced(parmus) for j, muPL in enumerate(parmus): mu = muPL[0] uApp = uApps[j] for i in range(nAs): radiusA[:, i, j] = eval(thetaAs[i]) * uApp for i in range(nbs): radiusb[i, j] = eval(thetabs[i]) if verb >= 5: verbosityDepth("DEL", "Done computing RB solution.", timestamp = self.timestamp) self.trainedModel.verbosity = verb # 'ij,jk,ik->k', resbb, radiusb, radiusb.conj() ff = np.sum(self.trainedModel.data.resbb.dot(radiusb) * radiusb.conj(), axis = 0) # 'ijk,jkl,il->l', resAb, radiusA, radiusb.conj() Lf = np.sum(np.tensordot(self.trainedModel.data.resAb, radiusA, 2) * radiusb.conj(), axis = 0) # 'ijkl,klt,ijt->t', resAA, radiusA, radiusA.conj() LL = np.sum(np.tensordot(self.trainedModel.data.resAA, radiusA, 2) * radiusA.conj(), axis = (0, 1)) return np.abs((LL - 2. * np.real(Lf) + ff) / ff) ** .5 def setupApprox(self, plotEst : bool = False): """Compute RB projection matrix.""" if self.checkComputedApprox(): return RROMPyAssert(self._mode, message = "Cannot setup approximant.") if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name()), timestamp = self.timestamp) self.greedy(plotEst) if self.verbosity >= 7: verbosityDepth("INIT", "Computing projection matrix.", timestamp = self.timestamp) pMat = self.samplingEngine.samples if self.trainedModel is None: self.trainedModel = tModel() self.trainedModel.verbosity = self.verbosity self.trainedModel.timestamp = self.timestamp data = TrainedModelData(self.trainedModel.name(), self.mu0, pMat, self.HFEngine.rescalingExp) data.thetaAs = self.HFEngine.affineWeightsA(self.mu0) data.thetabs = self.HFEngine.affineWeightsb(self.mu0, self.homogeneized) - data.ARBs, data.bRBs = self.assembleReducedSystem(pMat) - data.mus = copy(self.mus) + ARBs, bRBs = self.assembleReducedSystem(pMat) self.trainedModel.data = data else: self.trainedModel = self.trainedModel pMatOld = self.trainedModel.data.projMat Sold = pMatOld.shape[1] idxNew = list(range(Sold, pMat.shape[1])) ARBs, bRBs = self.assembleReducedSystem(pMat(idxNew), pMatOld) - self.trainedModel.data.ARBs = ARBs - self.trainedModel.data.bRBs = bRBs self.trainedModel.data.projMat = copy(pMat) - self.trainedModel.data.mus = copy(self.mus) + self.trainedModel.data.mus = copy(self.mus) + self.trainedModel.data.ARBs = ARBs + self.trainedModel.data.bRBs = bRBs if self.verbosity >= 7: verbosityDepth("DEL", "Done computing projection matrix.", timestamp = self.timestamp) self.trainedModel.data.approxParameters = copy(self.approxParameters) if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.", timestamp = self.timestamp) - def assembleReducedResidualBlocks(self): + def assembleReducedResidualBlocks(self, full : bool = False): """Build affine blocks of RB linear system through projections.""" - computeResbb = not hasattr(self.trainedModel.data, "resbb") - computeResAb = (not hasattr(self.trainedModel.data, "resAb") - or self.trainedModel.data.resAb.shape[1] != len(self.mus)) - computeResAA = (not hasattr(self.trainedModel.data, "resAA") - or self.trainedModel.data.resAA.shape[0] != len(self.mus)) - if computeResbb or computeResAb or computeResAA: - if self.verbosity >= 7: - verbosityDepth("INIT", "Projecting affine terms of residual.", - timestamp = self.timestamp) - if computeResAb or computeResAA: - pMat = self.trainedModel.data.projMat - if computeResbb: - self.assembleReducedResidualBlocksbb(self.bs) - if computeResAb: - self.assembleReducedResidualBlocksAb(self.As, self.bs, pMat) - if computeResAA: - self.assembleReducedResidualBlocksAA(self.As, pMat) - if self.verbosity >= 7: - verbosityDepth("DEL", ("Done setting up affine decomposition " - "of residual."), - timestamp = self.timestamp) - + self.assembleReducedResidualBlocksbb(self.bs) + if full: + pMat = self.trainedModel.data.projMat + self.assembleReducedResidualBlocksAb(self.As, self.bs, pMat) + self.assembleReducedResidualBlocksAA(self.As, pMat) diff --git a/rrompy/reduction_methods/trained_model/trained_model_pade.py b/rrompy/reduction_methods/trained_model/trained_model_pade.py index cff94fd..5891875 100644 --- a/rrompy/reduction_methods/trained_model/trained_model_pade.py +++ b/rrompy/reduction_methods/trained_model/trained_model_pade.py @@ -1,145 +1,145 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from . import TrainedModel from rrompy.utilities.base.types import (Np1D, List, paramVal, paramList, sampList) from rrompy.utilities.base import verbosityDepth from rrompy.utilities.poly_fitting.polynomial import polyval, polyroots from rrompy.utilities.exception_manager import RROMPyAssert from rrompy.parameter import checkParameterList from rrompy.sampling import sampleList __all__ = ['TrainedModelPade'] class TrainedModelPade(TrainedModel): """ ROM approximant evaluation for Pade' approximant. Attributes: Data: dictionary with all that can be pickled. """ def centerNormalize(self, mu : paramList = [], mu0 : paramVal = None) -> paramList: """ Compute normalized parameter to be plugged into approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.data.mu0. Returns: Normalized parameter. """ mu, _ = checkParameterList(mu, self.data.npar) if mu0 is None: mu0 = self.data.mu0 rad = ((mu ** self.data.rescalingExp - mu0 ** self.data.rescalingExp) / self.data.scaleFactor) return rad def getPVal(self, mu : paramList = [], der : List[int] = None) -> sampList: """ Evaluate Pade' numerator at arbitrary parameter. Args: mu: Target parameter. der(optional): Derivatives to take before evaluation. """ mu, _ = checkParameterList(mu, self.data.npar) - if self.verbosity >= 10: + if self.verbosity >= 17: verbosityDepth("INIT", ("Evaluating numerator at mu = " "{}.").format(mu), timestamp = self.timestamp) muCenter = self.centerNormalize(mu) p = sampleList(polyval(muCenter, self.data.P.T, self.data.polytype, der)) - if self.verbosity >= 10: + if self.verbosity >= 17: verbosityDepth("DEL", "Done evaluating numerator.", timestamp = self.timestamp) return p def getQVal(self, mu:Np1D, der : List[int] = None, scl : Np1D = None) -> Np1D: """ Evaluate Pade' denominator at arbitrary parameter. Args: mu: Target parameter. der(optional): Derivatives to take before evaluation. """ mu, _ = checkParameterList(mu, self.data.npar) - if self.verbosity >= 10: + if self.verbosity >= 17: verbosityDepth("INIT", ("Evaluating denominator at mu = " "{}.").format(mu), timestamp = self.timestamp) muCenter = self.centerNormalize(mu) q = polyval(muCenter, self.data.Q, self.data.polytype, der, scl) - if self.verbosity >= 10: + if self.verbosity >= 17: verbosityDepth("DEL", "Done evaluating denominator.", timestamp = self.timestamp) return q def getApproxReduced(self, mu : paramList = []) -> sampList: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. """ mu, _ = checkParameterList(mu, self.data.npar) if (not hasattr(self, "lastSolvedAppReduced") or self.lastSolvedAppReduced != mu): - if self.verbosity >= 5: + if self.verbosity >= 12: verbosityDepth("INIT", ("Evaluating approximant at mu = " "{}.").format(mu), timestamp = self.timestamp) self.uAppReduced = self.getPVal(mu) / self.getQVal(mu) - if self.verbosity >= 5: + if self.verbosity >= 12: verbosityDepth("DEL", "Done evaluating approximant.", timestamp = self.timestamp) self.lastSolvedAppReduced = mu return self.uAppReduced def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ RROMPyAssert(self.data.npar, 1, "Number of parameters") return np.power(self.data.mu0(0) ** self.data.rescalingExp[0] + self.data.scaleFactor * polyroots(self.data.Q, self.data.polytype), 1. / self.data.rescalingExp[0]) def getResidues(self) -> Np1D: """ Obtain approximant residues. Returns: Numpy matrix with residues as columns. """ pls = self.getPoles() poles, _ = checkParameterList(pls, 1) res = (self.data.projMat.dot(self.getPVal(poles).data) / self.getQVal(poles, 1)) return pls, res diff --git a/rrompy/reduction_methods/trained_model/trained_model_rb.py b/rrompy/reduction_methods/trained_model/trained_model_rb.py index ce630fe..29bf5a9 100644 --- a/rrompy/reduction_methods/trained_model/trained_model_rb.py +++ b/rrompy/reduction_methods/trained_model/trained_model_rb.py @@ -1,113 +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 . # import numpy as np from scipy.linalg import eigvals from .trained_model import TrainedModel from rrompy.utilities.base.types import Np1D, paramList, sampList from rrompy.utilities.base import verbosityDepth from rrompy.utilities.exception_manager import RROMPyWarning, RROMPyAssert from rrompy.parameter import checkParameterList from rrompy.sampling import emptySampleList __all__ = ['TrainedModelRB'] class TrainedModelRB(TrainedModel): """ ROM approximant evaluation for RB approximant. Attributes: Data: dictionary with all that can be pickled. """ def getApproxReduced(self, mu : paramList = []) -> sampList: """ Evaluate reduced representation of approximant at arbitrary parameter. Args: mu: Target parameter. """ mus, _ = checkParameterList(mu, self.data.npar) if (not hasattr(self, "lastSolvedAppReduced") or self.lastSolvedAppReduced != mus): - if self.verbosity >= 5: + if self.verbosity >= 12: verbosityDepth("INIT", ("Computing RB solution at mu = " "{}.").format(mus), timestamp = self.timestamp) thetaAs, thetabs = self.data.thetaAs, self.data.thetabs ARBs, bRBs = self.data.ARBs, self.data.bRBs self.uAppReduced = emptySampleList() self.uAppReduced.reset((ARBs[0].shape[0], len(mu)), self.data.projMat.dtype) for i, muPL in enumerate(mus): mu = muPL[0] - if self.verbosity >= 10: + if self.verbosity >= 17: verbosityDepth("INIT", ("Assembling reduced model for mu " "= {}.").format(mu), timestamp = self.timestamp) ARBmu = eval(thetaAs[0]) * ARBs[0] bRBmu = eval(thetabs[0]) * bRBs[0] for j in range(1, len(ARBs)): ARBmu += eval(thetaAs[j]) * ARBs[j] for j in range(1, len(bRBs)): bRBmu += eval(thetabs[j]) * bRBs[j] - if self.verbosity >= 10: + if self.verbosity >= 17: verbosityDepth("DEL", "Done assembling reduced model.", timestamp = self.timestamp) - if self.verbosity >= 5: + if self.verbosity >= 15: verbosityDepth("INIT", ("Solving reduced model for mu = " "{}.").format(mu), timestamp = self.timestamp) self.uAppReduced[i] = np.linalg.solve(ARBmu, bRBmu) - if self.verbosity >= 5: + if self.verbosity >= 15: verbosityDepth("DEL", "Done solving reduced model.", timestamp = self.timestamp) - if self.verbosity >= 5: + if self.verbosity >= 12: verbosityDepth("DEL", "Done computing RB solution.", timestamp = self.timestamp) self.lastSolvedAppReduced = mus return self.uAppReduced def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ RROMPyAssert(self.data.npar, 1, "Number of parameters") RROMPyWarning(("Impossible to compute poles in general affine " "parameter dependence. Results subject to " "interpretation/rescaling, or possibly completely " "wrong.")) ARBs = self.data.ARBs R = ARBs[0].shape[0] if len(ARBs) < 2: return A = np.eye(R * (len(ARBs) - 1), dtype = np.complex) B = np.zeros_like(A) A[: R, : R] = - ARBs[0] for j in range(len(ARBs) - 1): Aj = ARBs[j + 1] B[: R, j * R : (j + 1) * R] = Aj II = np.arange(R, R * (len(ARBs) - 1)) B[II, II - R] = 1. return np.power(eigvals(A, B) + self.data.mu0(0, 0) ** self.data.rescalingExp[0], 1. / self.data.rescalingExp[0]) diff --git a/rrompy/sampling/base/sampling_engine_base.py b/rrompy/sampling/base/sampling_engine_base.py index a18fcac..292204f 100644 --- a/rrompy/sampling/base/sampling_engine_base.py +++ b/rrompy/sampling/base/sampling_engine_base.py @@ -1,194 +1,194 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from rrompy.utilities.base.types import (Np1D, HFEng, strLst, paramVal, paramList, sampList) from rrompy.utilities.base import verbosityDepth from rrompy.utilities.exception_manager import RROMPyWarning from rrompy.parameter import (emptyParameterList, checkParameter, checkParameterList) from rrompy.sampling import emptySampleList __all__ = ['SamplingEngineBase'] class SamplingEngineBase: """HERE""" def __init__(self, HFEngine:HFEng, verbosity : int = 10, timestamp : bool = True): self.verbosity = verbosity self.timestamp = timestamp if self.verbosity >= 10: verbosityDepth("INIT", "Initializing sampling engine of type {}.".format( self.name()), timestamp = self.timestamp) self.HFEngine = HFEngine if self.verbosity >= 10: verbosityDepth("DEL", "Done initializing sampling engine.", timestamp = self.timestamp) 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 = emptySampleList() self.nsamples = 0 self.mus = emptyParameterList() self._derIdxs = [] def popSample(self): if hasattr(self, "nsamples") and self.nsamples > 1: if self.samples.shape[1] > self.nsamples: RROMPyWarning(("More than 'nsamples' memory allocated for " "samples. Popping empty sample column.")) self.nsamples += 1 self.nsamples -= 1 self.samples.pop() self.mus.pop() else: self.resetHistory() def preallocateSamples(self, u:sampList, mu:paramVal, n:int): self.samples.reset((u.shape[0], n), u.dtype) self.samples[0] = u mu = checkParameter(mu, self.HFEngine.npar) self.mus.reset((n, self.HFEngine.npar)) self.mus[0] = mu[0] @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 : paramList = [], RHS : sampList = None, homogeneized : bool = False) -> sampList: """ Solve linear system. Args: mu: Parameter value. Returns: Solution of system. """ mu, _ = checkParameterList(mu, self.HFEngine.npar) - if self.verbosity >= 5: + if self.verbosity >= 15: verbosityDepth("INIT", "Solving HF model for mu = {}.".format(mu), timestamp = self.timestamp) u = self.HFEngine.solve(mu, RHS, homogeneized) - if self.verbosity >= 5: + if self.verbosity >= 15: verbosityDepth("DEL", "Done solving HF model.", timestamp = self.timestamp) return u def plotSamples(self, name : str = "u", save : str = None, what : strLst = 'all', saveFormat : str = "eps", saveDPI : int = 100, show : bool = True, **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. show(optional): Whether to show figure. Defaults to True. 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), save = save, what = what, saveFormat = saveFormat, saveDPI = saveDPI, show = show, **figspecs) def outParaviewSamples(self, name : str = "u", folders : bool = True, filename : str = "out", times : Np1D = None, what : strLst = 'all', forceNewFile : bool = True, filePW = None): """ Output samples to ParaView file. Args: name(optional): Base name to be used for data output. folders(optional): Whether to split output in folders. filename(optional): Name of output file. times(optional): Timestamps. what(optional): Which plots to do. If list, can contain 'MESH', 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. forceNewFile(optional): Whether to create new output file. filePW(optional): Fenics File entity (for time series). """ if times is None: times = [0.] * self.nsamples for j in range(self.nsamples): self.HFEngine.outParaview(self.samples[j], name = "{}_{}".format(name, j), filename = "{}_{}".format(filename, j), time = times[j], what = what, forceNewFile = forceNewFile, folder = folders, filePW = filePW) def outParaviewTimeDomainSamples(self, omegas : Np1D = None, timeFinal : Np1D = None, periodResolution : int = 20, name : str = "u", folders : bool = True, filename : str = "out", forceNewFile : bool = True): """ Output samples to ParaView file, converted to time domain. Args: omegas(optional): frequencies. timeFinal(optional): final time of simulation. periodResolution(optional): number of time steps per period. name(optional): Base name to be used for data output. folders(optional): Whether to split output in folders. filename(optional): Name of output file. forceNewFile(optional): Whether to create new output file. """ if omegas is None: omegas = np.real(self.mus) if not isinstance(timeFinal, (list, tuple,)): timeFinal = [timeFinal] * self.nsamples for j in range(self.nsamples): self.HFEngine.outParaviewTimeDomain(self.samples[j], omega = omegas[j], timeFinal = timeFinal[j], periodResolution = periodResolution, name = "{}_{}".format(name, j), filename = "{}_{}".format(filename, j), forceNewFile = forceNewFile, folder = folders) diff --git a/rrompy/sampling/linear_problem/sampling_engine_linear_pod.py b/rrompy/sampling/linear_problem/sampling_engine_linear_pod.py index 9be02c5..2f4a100 100644 --- a/rrompy/sampling/linear_problem/sampling_engine_linear_pod.py +++ b/rrompy/sampling/linear_problem/sampling_engine_linear_pod.py @@ -1,84 +1,83 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from rrompy.sampling.base.pod_engine import PODEngine from .sampling_engine_linear import SamplingEngineLinear from rrompy.utilities.base.types import Np1D, paramVal, sampList from rrompy.utilities.base import verbosityDepth __all__ = ['SamplingEngineLinearPOD'] class SamplingEngineLinearPOD(SamplingEngineLinear): """HERE""" def resetHistory(self): super().resetHistory() self.RPOD = None def popSample(self): if hasattr(self, "nsamples") and self.nsamples > 1: self.RPOD = self.RPOD[: -1, : -1] super().popSample() @property def HFEngine(self): """Value of HFEngine. Its assignment resets history.""" return self._HFEngine @HFEngine.setter def HFEngine(self, HFEngine): self._HFEngine = HFEngine self.resetHistory() self.PODEngine = PODEngine(self._HFEngine) def preprocesssamples(self, idxs:Np1D) -> sampList: idxMax = np.max(idxs) + 1 sampleBase = super().preprocesssamples(np.arange(idxMax)) - ##### maybe square brackets should be used below... RPODBase = self.RPOD[: idxMax, idxs] return sampleBase.dot(RPODBase) def postprocessu(self, u:sampList, overwrite : bool = False) -> Np1D: if self.verbosity >= 10: verbosityDepth("INIT", "Starting orthogonalization.", timestamp = self.timestamp) ns = self.nsamples if ns == 0: u, r, _ = self.PODEngine.GS(u, np.empty((0, 0))) r = r[0] else: u, r, _ = self.PODEngine.GS(u, self.samples(np.arange(ns)), ns) if overwrite: self.RPOD[: ns + 1, ns] = r else: if ns == 0: self.RPOD = r.reshape((1, 1)) else: self.RPOD=np.block([[ self.RPOD, r[:-1, None]], [np.zeros((1, ns)), r[-1]]]) if self.verbosity >= 10: verbosityDepth("DEL", "Done orthogonalizing.", timestamp = self.timestamp) return u def preallocateSamples(self, u:Np1D, mu:paramVal, n:int): super().preallocateSamples(u, mu, n) r = self.RPOD self.RPOD = np.zeros((n, n), dtype = u.dtype) self.RPOD[0, 0] = r[0, 0] diff --git a/rrompy/utilities/base/__init__.py b/rrompy/utilities/base/__init__.py index c6c8528..6bcf126 100644 --- a/rrompy/utilities/base/__init__.py +++ b/rrompy/utilities/base/__init__.py @@ -1,53 +1,55 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from .find_dict_str_key import findDictStrKey from .get_new_filename import getNewFilename from .kroneckerer import kroneckerer from .factorials import multibinom, multifactorial from .pickle_utilities import pickleDump, pickleLoad from .purge_dict import purgeDict from .purge_list import purgeList from .number_theory import (squareResonances, primeFactorize, getLowestPrimeFactor) +from .halton import haltonGenerate from .sobol import sobolGenerate from .low_discrepancy import vanderCorput, lowDiscrepancy from . import types as Types from .verbosity_depth import verbosityDepth __all__ = [ 'findDictStrKey', 'getNewFilename', 'kroneckerer', 'multibinom', 'multifactorial', 'pickleDump', 'pickleLoad', 'purgeDict', 'purgeList', 'squareResonances', 'primeFactorize', 'getLowestPrimeFactor', + 'haltonGenerate', 'sobolGenerate', 'vanderCorput', 'lowDiscrepancy', 'Types', 'verbosityDepth' ] diff --git a/rrompy/utilities/base/halton.py b/rrompy/utilities/base/halton.py new file mode 100644 index 0000000..509de0c --- /dev/null +++ b/rrompy/utilities/base/halton.py @@ -0,0 +1,45 @@ +# Copyright (C) 2018 by the RROMPy authors +# +# This file is part of RROMPy. +# +# RROMPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RROMPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with RROMPy. If not, see . +# + +import numpy as np + +__all__ = ['haltonGenerate'] + +def haltonGenerate(dim, size, seed = 0, step = True): + seq = np.empty((size, dim), dtype = float) + primes = [2, 3] + for j in range(2, dim * (1 + 1 * step) + 1): + primeBase = primes[j - 1] + 2 + while True: + for i in range(1, j): + if primeBase % primes[i] == 0: break + else: + break + primeBase += 2 + primes += [primeBase] + base = np.array(primes[: dim]) + stepSize = primes[dim * 2 - 1] if step else 1 + seq = np.zeros((size, dim), dtype = float) + seqBase = np.tile((np.arange(seed, stepSize * size + seed, stepSize) + + 1).reshape(-1, 1), [1, dim]) + powerB = 1 + while seqBase[-1, 0] > 0: + seqBase, remainder = np.divmod(seqBase, base) + seq += remainder / np.power(base, powerB) + powerB += 1 + return seq diff --git a/rrompy/utilities/base/low_discrepancy.py b/rrompy/utilities/base/low_discrepancy.py index 47f83ca..0dc73eb 100644 --- a/rrompy/utilities/base/low_discrepancy.py +++ b/rrompy/utilities/base/low_discrepancy.py @@ -1,46 +1,42 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np -from rrompy.utilities.base.types import Np1D +from rrompy.utilities.base.types import List from rrompy.utilities.exception_manager import RROMPyException __all__ = ['vanderCorput', 'lowDiscrepancy'] -def vanderCorput(n:int) -> Np1D: +def vanderCorput(n:int) -> List[int]: if n <= 0: raise RROMPyException("Only positive integers allowed.") x = [0] * n ln = int(np.ceil(np.log2(n))) for j in range(n): x[j] = int(np.binary_repr(j, width = ln)[::-1], 2) return x -def lowDiscrepancy(n:int) -> Np1D: +def lowDiscrepancy(n:int, inverse : bool = False) -> List[int]: if n <= 0: raise RROMPyException("Only positive integers allowed.") - x = [0] * n - max2Pow = np.binary_repr(n)[::-1].find('1') - max2Fac = 2 ** max2Pow - res2 = n // max2Fac - xBase = res2 * np.array(vanderCorput(max2Fac), dtype = np.int) - stride = ((n - 1) // 2 - 1) * (max2Pow == 0) + 1 - for i in range(res2): - x[i * max2Fac : (i + 1) * max2Fac] = (i * stride + xBase) % n + max2Fac = 2 ** int(np.ceil(np.log2(n))) + xBase = np.array(vanderCorput(max2Fac), dtype = np.int) + x = list(xBase[xBase < n]) + if inverse: x = list(np.argsort(x)) return x diff --git a/rrompy/utilities/poly_fitting/__init__.py b/rrompy/utilities/poly_fitting/__init__.py index dc4d3ac..c9d4457 100644 --- a/rrompy/utilities/poly_fitting/__init__.py +++ b/rrompy/utilities/poly_fitting/__init__.py @@ -1,25 +1,27 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # from .custom_fit import customFit +from .custom_pinv import customPInv __all__ = [ - 'customFit' + 'customFit', + 'customPInv' ] diff --git a/rrompy/utilities/poly_fitting/custom_fit.py b/rrompy/utilities/poly_fitting/custom_fit.py index f4164b7..79a9260 100644 --- a/rrompy/utilities/poly_fitting/custom_fit.py +++ b/rrompy/utilities/poly_fitting/custom_fit.py @@ -1,135 +1,135 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np import numpy.linalg as la from rrompy.utilities.exception_manager import RROMPyException, RROMPyWarning __all__ = ["customFit"] -def customFit(van, y, rcond=None, full=False, w=None): +def customFit(van, y, rcond=-1, full=False, w=None): """ Least-squares fit of a polynomial to data. Copied from numpy.polynomial.polynomial. Parameters ---------- va : array_like, shape (`M`,`deg` + 1) Vandermonde-like matrix. y : array_like, shape (`M`,) or (`M`, `K`) y-coordinates of the sample points. Several sets of sample points sharing the same x-coordinates can be (independently) fit with one call to `polyfit` by passing in for `y` a 2-D array that contains one data set per column. rcond : float, optional Relative condition number of the fit. Singular values smaller than `rcond`, relative to the largest singular value, will be ignored. The default value is ``len(van)*eps``, where `eps` is the relative precision of the platform's float type, about 2e-16 in most cases. full : bool, optional Switch determining the nature of the return value. When ``False`` (the default) just the coefficients are returned; when ``True``, diagnostic information from the singular value decomposition (used to solve the fit's matrix equation) is also returned. w : array_like, shape (`M`,), optional Weights. If not None, the contribution of each point ``(x[i],y[i])`` to the fit is weighted by `w[i]`. Ideally the weights are chosen so that the errors of the products ``w[i]*y[i]`` all have the same variance. The default value is None. Returns ------- coef : ndarray, shape (`deg` + 1,) or (`deg` + 1, `K`) Polynomial coefficients ordered from low to high. If `y` was 2-D, the coefficients in column `k` of `coef` represent the polynomial fit to the data in `y`'s `k`-th column. [residuals, rank, singular_values, rcond] : list These values are only returned if `full` = True resid -- sum of squared residuals of the least squares fit rank -- the numerical rank of the scaled Vandermonde matrix sv -- singular values of the scaled Vandermonde matrix rcond -- value of `rcond`. For more details, see `linalg.lstsq`. """ van = np.asarray(van) + 0.0 y = np.asarray(y) + 0.0 # check arguments. if van.ndim != 2: raise RROMPyException("expected 2D vector for van") if van.size == 0: raise RROMPyException("expected non-empty vector for van") if y.ndim < 1 or y.ndim > 2: raise RROMPyException("expected 1D or 2D array for y") if len(van) != len(y): raise RROMPyException("expected van and y to have same length") order = van.shape[1] # set up the least squares matrices in transposed form lhs = van.T rhs = y.T if isinstance(w, (str, )) and w.upper() == "AUTO": # Determine the norms of the design matrix rows. if issubclass(van.dtype.type, np.complexfloating): w = np.sqrt((np.square(van.real) + np.square(van.imag)).sum(1)) else: w = np.sqrt(np.square(van).sum(1)) w[w == 0] = 1 w = np.power(w, -1.) if w is not None: w = np.asarray(w) + 0.0 if w.ndim != 1: raise RROMPyException("expected 1D vector for w") if len(van) != len(w): raise RROMPyException("expected van and w to have same length") # apply weights. Don't use inplace operations as they # can cause problems with NA. lhs = lhs * w rhs = rhs * w # set rcond - if rcond is None: + if rcond < 0: rcond = len(van)*np.finfo(van.dtype).eps # Determine the norms of the design matrix columns. if issubclass(lhs.dtype.type, np.complexfloating): scl = np.sqrt((np.square(lhs.real) + np.square(lhs.imag)).sum(1)) else: scl = np.sqrt(np.square(lhs).sum(1)) scl[scl == 0] = 1 # Solve the least squares problem. c, resids, rank, s = la.lstsq(lhs.T/scl, rhs.T, rcond) c = (c.T/scl).T # warn on rank reduction if rank != order and not full: RROMPyWarning("The fit may be poorly conditioned", stacklevel = 2) if full: return c, [resids, rank, s, rcond] else: return c diff --git a/rrompy/hfengines/linear_problem/bidimensional/__init__.py b/rrompy/utilities/poly_fitting/custom_pinv.py similarity index 50% copy from rrompy/hfengines/linear_problem/bidimensional/__init__.py copy to rrompy/utilities/poly_fitting/custom_pinv.py index 03b8e69..45b5fbd 100644 --- a/rrompy/hfengines/linear_problem/bidimensional/__init__.py +++ b/rrompy/utilities/poly_fitting/custom_pinv.py @@ -1,29 +1,46 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # -from .laplace_disk_gaussian_2 import LaplaceDiskGaussian2 -from .helmholtz_square_simplified_domain_problem_engine import \ - HelmholtzSquareSimplifiedDomainProblemEngine - -__all__ = [ - 'LaplaceDiskGaussian2', - 'HelmholtzSquareSimplifiedDomainProblemEngine' - ] +from copy import deepcopy as copy +import numpy as np +__all__ = ["customPInv"] +def customPInv(A, rcond=1e-15, full=False): + """ + Compute the (Moore-Penrose) pseudo-inverse of a matrix. + Calculate the generalized inverse of a matrix using its + singular-value decomposition (SVD) and including all + *large* singular values. + """ + A = A.conjugate() + u, s, vt = np.linalg.svd(A, full_matrices=False) + cutoff = rcond * np.amax(s) + large = s > cutoff + + sinv = copy(s) + sinv = np.divide(1, s, where = large, out = sinv) + sinv[~large] = 0 + + res = (vt.T * sinv) @ u.T + + if full: + return res, [np.sum(large), s, rcond] + else: + return res diff --git a/rrompy/utilities/poly_fitting/polynomial/homogeneization.py b/rrompy/utilities/poly_fitting/polynomial/homogeneization.py index e261241..f42d5e1 100644 --- a/rrompy/utilities/poly_fitting/polynomial/homogeneization.py +++ b/rrompy/utilities/poly_fitting/polynomial/homogeneization.py @@ -1,46 +1,55 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List, paramList from rrompy.utilities.poly_fitting.polynomial import polyvander from rrompy.parameter import checkParameterList __all__ = ['homogeneizationMask', 'homogeneizedpolyvander'] def homogeneizationMask(degs:List[int]) -> Tuple[List[int], List[bool]]: dim = len(degs) N = np.prod([d + 1 for d in degs]) maskN = np.arange(N, dtype = int) remN = np.zeros((N, dim), dtype = int) for j in range(dim - 1, -1, -1): remN[:, j] = maskN % (degs[j] + 1) maskN //= degs[j] + 1 mask = np.sum(remN, axis = 1) <= min(degs) return remN[mask], mask def homogeneizedpolyvander(x:paramList, deg:int, basis:str, derIdxs : List[List[List[int]]] = None, reorder : List[int] = None, scl : Np1D = None)\ -> Tuple[Np2D, List[List[int]], List[int]]: x, _ = checkParameterList(x) degs = [deg] * x.shape[1] VanBase = polyvander(x, degs, basis, derIdxs, reorder, scl) derIdxs, mask = homogeneizationMask(degs) - derDepth = [- np.sum(x) for x in derIdxs] - return VanBase[:, mask], derIdxs, np.argsort(derDepth)[::-1] + + ordIdxs = np.empty(len(derIdxs), dtype = int) + derTotal = np.array([np.sum(y) for y in derIdxs]) + idxPrev = 0 + rangeAux = np.arange(len(derIdxs)) + for j in range(deg + 1): + idxLocal = rangeAux[derTotal == j][::-1] + idxPrev += len(idxLocal) + ordIdxs[idxPrev - len(idxLocal) : idxPrev] = idxLocal + return VanBase[:, mask], derIdxs, ordIdxs + diff --git a/setup.py b/setup.py index fa46095..248956a 100644 --- a/setup.py +++ b/setup.py @@ -1,52 +1,52 @@ # Copyright (C) 2015-2018 by the RBniCS authors # # This file is part of RBniCS. # # RBniCS is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RBniCS is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RBniCS. If not, see . # import os from setuptools import find_packages, setup rrompy_directory = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) #rrompy_directory = os.path.join(rrompy_directory, 'rrompy') setup(name="RROMPy", description="Rational reduced order modelling in Python", long_description="Rational reduced order modelling in Python", author="Davide Pradovera", author_email="davide.pradovera@epfl.ch", - version="1.4", + version="1.5", license="GNU Library or Lesser General Public License (LGPL)", classifiers=[ "Development Status :: 1 - Planning" "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Software Development :: Libraries :: Python Modules", ], packages=find_packages(rrompy_directory), setup_requires=[ "pytest-runner" ], tests_require=[ "pytest" ], zip_safe=False ) diff --git a/tests/test_1_utilities/parameter_sampling.py b/tests/test_1_utilities/parameter_sampling.py index 6e08e73..b94792e 100644 --- a/tests/test_1_utilities/parameter_sampling.py +++ b/tests/test_1_utilities/parameter_sampling.py @@ -1,60 +1,60 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from rrompy.parameter.parameter_sampling import (ManualSampler, QuadratureSampler, RandomSampler, FFTSampler) from rrompy.parameter import checkParameter def test_manual(): sampler = ManualSampler(lims = [0., 3.], points = np.linspace(0, 3, 101), scaling = lambda x: np.power(x, 2.), scalingInv = lambda x: np.power(x, .5)) assert sampler.name() == "ManualSampler" x = sampler.generatePoints(10) assert np.allclose(x(0), np.linspace(0, 3, 101)[:10], rtol = 1e-5) def test_quadrature(): sampler = QuadratureSampler(lims = [0., 3.], kind = "CHEBYSHEV") x = sampler.generatePoints(9) assert np.isclose(x(0)[2], 1.5, rtol = 1e-5) def test_random(): - sampler = RandomSampler(lims = [0., 3.], kind = "SOBOL") - x = sampler.generatePoints(100, seed = 13432) + sampler = RandomSampler(lims = [0., 3.], kind = "SOBOL", seed = 13432) + x = sampler.generatePoints(100) assert np.isclose(x(0)[47], 0.55609130859375, rtol = 1e-5) def test_fft(): sampler = FFTSampler(lims = [-1., 1.]) x = sampler.generatePoints(100) assert np.allclose(np.power(x(0), 100), 1., rtol = 1e-5) def test_2D(): sampler = QuadratureSampler(lims = [(0., 0.), (3., 1.)], kind = "GAUSSLEGENDRE") x = sampler.generatePoints((9, 5)) assert sum(np.isclose(x(0), 1.5)) == 5 assert sum(np.isclose(x(1), .5)) == 9 def test_4D(): sampler = RandomSampler(lims = [tuple([0.] * 4), tuple([1.] * 4)], - kind = "UNIFORM") - x = sampler.generatePoints(10, seed = 1234) + kind = "UNIFORM", seed = 1234) + x = sampler.generatePoints(10) assert x.shape[1] == 4 assert checkParameter([x[0]]) == checkParameter([(0.191519450378892, 0.622108771039832, 0.437727739007115, 0.785358583713769)]) diff --git a/tests/test_2_hfengines/helmholtz_external.py b/tests/test_2_hfengines/helmholtz_external.py index 4dc418d..009ab89 100644 --- a/tests/test_2_hfengines/helmholtz_external.py +++ b/tests/test_2_hfengines/helmholtz_external.py @@ -1,64 +1,67 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # +import pytest import numpy as np from rrompy.hfengines.linear_problem import ( HelmholtzCavityScatteringProblemEngine, HelmholtzBoxScatteringProblemEngine) def test_helmholtz_square_scattering(): solver = HelmholtzCavityScatteringProblemEngine(kappa = 4, gamma = 2., n = 20, verbosity = 0) mu = 5 uh = solver.solve(mu)[0] - assert np.isclose(solver.norm(uh), 20.719752682674923, rtol = 1e-2) + assert np.isclose(solver.norm(uh), 19.9362, rtol = 1e-2) assert np.isclose(solver.norm(solver.residual(uh, mu)[0]), 4.25056407e-13, rtol = 1e-1) def test_helmholtz_scattering_copy(capsys): solver1 = HelmholtzCavityScatteringProblemEngine(kappa = 4, gamma = 2., n = 20, verbosity = 0) mu = 5 uh1 = solver1.solve(mu)[0] solver2 = HelmholtzCavityScatteringProblemEngine(kappa = 4, gamma = 2., n = 20, verbosity = 100) assert solver1.As[0] is not None and solver1.bs[0] is not None assert solver2.As[0] is None and solver2.bs[0] is None solver2.setAs(solver1.As) solver2.setbs(solver1.bs) uh2 = solver2.solve(mu)[0] assert np.allclose(uh1, uh2, rtol = 1e-8) out, err = capsys.readouterr() assert ("Assembling operator term" not in out and "Assembling forcing term" not in out) assert len(err) == 0 +@pytest.mark.xfail(raises = RuntimeError('Invalid DISPLAY variable'), + reason = "no graphical interface") def test_helmholtz_box_scattering(): solver = HelmholtzBoxScatteringProblemEngine(R = 2, kappa = 10., theta = np.pi * 30 / 180, n = 20, verbosity = 0) mu = 15 uh = solver.solve(mu)[0] solver.plotmesh(show = False, figsize = (7, 7)) - assert np.isclose(solver.norm(uh), 63.98946657389119, rtol = 1e-2) + assert np.isclose(solver.norm(uh), 62.113, rtol = 1e-2) assert np.isclose(solver.norm(solver.residual(uh, mu)[0]), 9.62989935e-13, rtol = 1e-1) from matplotlib import pyplot as plt plt.close('all') diff --git a/tests/test_2_hfengines/helmholtz_internal.py b/tests/test_2_hfengines/helmholtz_internal.py index 998fa6f..00724bb 100644 --- a/tests/test_2_hfengines/helmholtz_internal.py +++ b/tests/test_2_hfengines/helmholtz_internal.py @@ -1,95 +1,98 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # +import pytest import os, shutil import numpy as np from rrompy.hfengines.linear_problem import ( HelmholtzSquareBubbleDomainProblemEngine, HelmholtzSquareBubbleProblemEngine, HelmholtzSquareTransmissionProblemEngine) def test_helmholtz_square_io(): solver = HelmholtzSquareBubbleProblemEngine(kappa = 4, theta = 1., n = 50, verbosity = 0) mu = 5 uh = solver.solve(mu)[0] - assert np.isclose(solver.norm(uh), 70762597.99694124, rtol = 1e-3) - assert np.isclose(solver.norm(solver.residual(uh, mu)[0]), 2.1855986e-06, + assert np.isclose(solver.norm(uh), 913.396, rtol = 1e-3) + assert np.isclose(solver.norm(solver.residual(uh, mu)[0]), 1.19934e-11, rtol = 1e-1) if not os.path.isdir("./.pytest_cache"): os.mkdir("./.pytest_cache") filesOut = [x for x in os.listdir("./.pytest_cache") if (x[-4:] == ".pvd" and x[:9] == "outSquare")] filesOutData = [x for x in os.listdir("./.pytest_cache") if (x[-4:] == ".vtu" and x[:9] == "outSquare")] for fileOut in filesOut: os.remove("./.pytest_cache/" + fileOut) for fileOut in filesOutData: os.remove("./.pytest_cache/" + fileOut) solver.outParaview(uh, what = ["MESH", "ABS"], filename = ".pytest_cache/outSquare", forceNewFile = False) filesOut = [x for x in os.listdir("./.pytest_cache") if (x[-4:] == ".pvd" and x[:9] == "outSquare")] filesOutData = [x for x in os.listdir("./.pytest_cache") if (x[-4:] == ".vtu" and x[:9] == "outSquare")] assert len(filesOut) == 1 assert len(filesOutData) == 1 os.remove("./.pytest_cache/" + filesOut[0]) os.remove("./.pytest_cache/" + filesOutData[0]) def test_helmholtz_transmission_io(): solver = HelmholtzSquareTransmissionProblemEngine(nT = 1, nB = 2, theta = np.pi * 40 / 180, kappa = 4., n = 50, verbosity = 0) mu = 5. uh = solver.solve(mu)[0] - assert np.isclose(solver.norm(uh), 46.4528217234862, rtol = 1e-5) + assert np.isclose(solver.norm(uh), 43.9268, rtol = 1e-2) assert np.isclose(solver.norm(solver.residual(uh, mu)[0]), 3.7288565e-12, rtol = 1e-1) if not os.path.isdir("./.pytest_cache"): os.mkdir("./.pytest_cache") solver.outParaviewTimeDomain(uh, omega = mu, filename = ".pytest_cache/outTrans", forceNewFile = False, folder = True) filesOut = [x for x in os.listdir("./.pytest_cache/outTrans") if (x[-4:] == ".pvd" and x[:8] == "outTrans")] filesOutData = [x for x in os.listdir("./.pytest_cache/outTrans") if (x[-4:] == ".vtu" and x[:8] == "outTrans")] assert len(filesOut) == 1 assert len(filesOutData) == 20 shutil.rmtree("./.pytest_cache/outTrans") +@pytest.mark.xfail(raises = RuntimeError('Invalid DISPLAY variable'), + reason = "no graphical interface") def test_helmholtz_domain_io(): solver = HelmholtzSquareBubbleDomainProblemEngine(kappa = 4, theta = 1., - n = 20, mu0 = 1.5, verbosity = 0) + n = 10, mu0 = 1.5, verbosity = 0) mu = 1.5 uh = solver.solve(mu)[0] if not os.path.isdir("./.pytest_cache"): os.mkdir("./.pytest_cache") solver.plot(uh, save = "./.pytest_cache/outDomain", show = False) filesOut = [x for x in os.listdir("./.pytest_cache") if (x[-4:] == ".eps" and x[:9] == "outDomain")] assert len(filesOut) == 1 os.remove("./.pytest_cache/" + filesOut[0]) - assert np.isclose(solver.norm(uh), 9.408738562323533, rtol = 1e-2) + assert np.isclose(solver.norm(uh), 8.9947, rtol = 1e-2) assert np.isclose(solver.norm(solver.residual(uh, mu)[0]), 6.14454989e-13, rtol = 1e-1) diff --git a/tests/test_3_reduction_methods_1D/rational_interpolant_1d.py b/tests/test_3_reduction_methods_1D/rational_interpolant_1d.py index 908c8f7..5011114 100644 --- a/tests/test_3_reduction_methods_1D/rational_interpolant_1d.py +++ b/tests/test_3_reduction_methods_1D/rational_interpolant_1d.py @@ -1,69 +1,68 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from matrix_fft import matrixFFT from rrompy.reduction_methods.distributed import RationalInterpolant as RI from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, ManualSampler as MS) from rrompy.parameter import checkParameterList def test_monomials(capsys): mu = 1.5 solver = matrixFFT() params = {"POD": False, "M": 9, "N": 9, "S": 10, "robustTol": 1e-6, "interpRcond": 1e-3, "polybasis": "MONOMIAL", - "sampler": QS([1.5, 6.5], "UNIFORM"), "muBounds":[1.5, 6.5]} + "sampler": QS([1.5, 6.5], "UNIFORM")} approx = RI(solver, 4., params, verbosity = 0) approx.setupApprox() out, err = capsys.readouterr() - assert (("poorly conditioned. Reducing M " in out) - and ("eigenvalues below tolerance. Reducing N " in out)) + assert "poorly conditioned. Reducing E " in out assert len(err) == 0 - assert np.isclose(approx.normErr(mu)[0], 8e-5, atol = 1e-4) + assert np.isclose(approx.normErr(mu)[0], 2.9051e-4, atol = 1e-4) def test_well_cond(): mu = 1.5 solver = matrixFFT() params = {"POD": True, "M": 9, "N": 9, "S": 10, "robustTol": 1e-14, "interpRcond": 1e-10, "polybasis": "CHEBYSHEV", "sampler": QS([1., 7.], "CHEBYSHEV")} approx = RI(solver, 4., params, verbosity = 0) approx.setupApprox() poles = approx.getPoles() for lambda_ in np.arange(1, 8): assert np.isclose(np.min(np.abs(poles - lambda_)), 0., atol = 1e-4) for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0], 0., atol = 1e-8) def test_hermite(): mu = 1.5 solver = matrixFFT() sampler0 = QS([1., 7.], "CHEBYSHEV") points, _ = checkParameterList(np.tile(sampler0.generatePoints(4)(0), 3)) params = {"POD": True, "M": 11, "N": 11, "S": 12, "polybasis": "CHEBYSHEV", "sampler": MS([1., 7.], points = points)} approx = RI(solver, 4., params, verbosity = 0) approx.setupApprox() poles = approx.getPoles() for lambda_ in np.arange(1, 8): assert np.isclose(np.min(np.abs(poles - lambda_)), 0., atol = 1e-4) for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0], 0., atol = 1e-8) diff --git a/tests/test_3_reduction_methods_1D/rational_interpolant_greedy_1d.py b/tests/test_3_reduction_methods_1D/rational_interpolant_greedy_1d.py index 78bbf2c..4ceb0ee 100644 --- a/tests/test_3_reduction_methods_1D/rational_interpolant_greedy_1d.py +++ b/tests/test_3_reduction_methods_1D/rational_interpolant_greedy_1d.py @@ -1,89 +1,94 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from matrix_fft import matrixFFT from rrompy.reduction_methods.distributed_greedy import \ RationalInterpolantGreedy as RIG +from rrompy.parameter.parameter_sampling import QuadratureSampler as QS def test_lax_tolerance(capsys): mu = 2.25 solver = matrixFFT() - params = {"POD": True, "muBounds": [1.5, 6.5], "S": 5, + params = {"POD": True, "sampler": QS([1.5, 6.5], "UNIFORM"), "S": 4, "polybasis": "CHEBYSHEV", "greedyTol": 1e-2, - "errorEstimatorKind": "bare"} - approx = RIG(solver, 4., params, verbosity = 10) + "errorEstimatorKind": "bare", + "trainSetGenerator": QS([1.5, 6.5], "CHEBYSHEV")} + approx = RIG(solver, 4., params, verbosity = 100) approx.greedy() out, err = capsys.readouterr() - assert "Done computing snapshots (final snapshot count: 16)." in out + assert "Done computing snapshots (final snapshot count: 10)." in out assert len(err) == 0 - assert np.isclose(approx.normErr(mu)[0], 3.534746e-3, rtol = 1e-1) + assert np.isclose(approx.normErr(mu)[0], 2.169678e-4, rtol = 1e-1) def test_samples_at_poles(): solver = matrixFFT() - params = {"POD": True, "muBounds": [1.5, 6.5], "S": 4, "nTestPoints": 100, - "polybasis": "CHEBYSHEV", "greedyTol": 1e-5, - "errorEstimatorKind": "exact"} + params = {"POD": True, "sampler": QS([1.5, 6.5], "UNIFORM"), "S": 4, + "nTestPoints": 100, "polybasis": "CHEBYSHEV", "greedyTol": 1e-5, + "errorEstimatorKind": "exact", + "trainSetGenerator": QS([1.5, 6.5], "CHEBYSHEV")} approx = RIG(solver, 4., params, verbosity = 0) approx.greedy() for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0] / (1e-15+approx.normHF(mu)[0]), 0., atol = 1e-4) poles = approx.getPoles() for lambda_ in range(2, 7): assert np.isclose(np.min(np.abs(poles - lambda_)), 0., atol = 1e-3) assert np.isclose(np.min(np.abs(np.array(approx.mus(0)) - lambda_)), 0., atol = 1e-1) def test_maxIter(): solver = matrixFFT() - params = {"POD": True, "muBounds": [1.5, 6.5], "S": 5, "nTestPoints": 500, - "polybasis": "CHEBYSHEV", "greedyTol": 1e-6, "maxIter": 10, - "errorEstimatorKind": "basic"} + params = {"POD": True, "sampler": QS([1.5, 6.5], "UNIFORM"), + "S": 5, "nTestPoints": 500, "polybasis": "CHEBYSHEV", + "greedyTol": 1e-6, "maxIter": 10, "errorEstimatorKind": "basic", + "trainSetGenerator": QS([1.5, 6.5], "CHEBYSHEV")} approx = RIG(solver, 4., params, verbosity = 0) approx.input = lambda: "N" approx.greedy() assert len(approx.mus) == 10 _, _, maxEst = approx.getMaxErrorEstimator(approx.muTest) assert maxEst > 1e-6 def test_load_copy(capsys): mu = 3. solver = matrixFFT() - params = {"POD": True, "muBounds": [1.5, 6.5], "S": 4, "nTestPoints": 100, - "polybasis": "CHEBYSHEV", "greedyTol": 1e-5, - "errorEstimatorKind": "exact"} + params = {"POD": True, "sampler": QS([1.5, 6.5], "UNIFORM"), "S": 4, + "nTestPoints": 100, "polybasis": "CHEBYSHEV", + "greedyTol": 1e-5, "errorEstimatorKind": "exact", + "trainSetGenerator": QS([1.5, 6.5], "CHEBYSHEV")} approx1 = RIG(solver, 4., params, verbosity = 100) approx1.greedy() err1 = approx1.normErr(mu)[0] out, err = capsys.readouterr() assert "Solving HF model for mu =" in out assert len(err) == 0 approx2 = RIG(solver, 4., params, verbosity = 100) approx2.setTrainedModel(approx1) approx2.setHF(mu, approx1.uHF) err2 = approx2.normErr(mu)[0] out, err = capsys.readouterr() assert "Solving HF model for mu =" not in out assert len(err) == 0 assert np.isclose(err1, err2, rtol = 1e-10) diff --git a/tests/test_3_reduction_methods_1D/rational_pade_1d.py b/tests/test_3_reduction_methods_1D/rational_pade_1d.py index 79e189f..99e9aaf 100644 --- a/tests/test_3_reduction_methods_1D/rational_pade_1d.py +++ b/tests/test_3_reduction_methods_1D/rational_pade_1d.py @@ -1,86 +1,86 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import os import numpy as np from matrix_fft import matrixFFT from rrompy.reduction_methods.centered import RationalPade as RP def test_rho(): mu = 1.5 mu0 = 2. + 1.j solver = matrixFFT() uh = solver.solve(mu)[0] params = {"POD": False, "M": 9, "N": 10, "S": 11, "robustTol": 1e-6} approx = RP(solver, mu0, params, verbosity = 0) approx.setupApprox() if not os.path.isdir("./.pytest_cache"): os.mkdir("./.pytest_cache") filesOut = [x for x in os.listdir("./.pytest_cache") if (x[-4:] == ".pkl" and x[:6] == "outPad")] for fileOut in filesOut: os.remove("./.pytest_cache/" + fileOut) fileStored = approx.storeTrainedModel(".pytest_cache/outPad") filesOut = [x for x in os.listdir("./.pytest_cache") if (x[-4:] == ".pkl" and x[:6] == "outPad")] assert len(filesOut) == 1 assert filesOut[0] == fileStored[- len(filesOut[0]) :] uhP1 = approx.getApprox(mu)[0] errP = approx.getErr(mu)[0] errNP = approx.normErr(mu)[0] myerrP = uhP1 - uh assert np.allclose(np.abs(errP - myerrP), 0., rtol = 1e-3) assert np.isclose(solver.norm(errP), errNP, rtol = 1e-3) resP = approx.getRes(mu)[0] resNP = approx.normRes(mu) assert np.isclose(solver.norm(resP), resNP, rtol = 1e-3) assert np.allclose(np.abs(resP - (solver.b(mu) - solver.A(mu).dot(uhP1))), 0., rtol = 1e-3) del approx approx = RP(solver, mu0, {"S": 3}, verbosity = 0) approx.loadTrainedModel(fileStored) for fileOut in filesOut: os.remove("./.pytest_cache/" + fileOut) uhP2 = approx.getApprox(mu)[0] assert np.allclose(np.abs(uhP1 - uhP2), 0., rtol = 1e-3) def test_E_warn(capsys): mu = 1.5 mu0 = 2. + 1.j solver = matrixFFT() uh = solver.solve(mu)[0] params = {"POD": True, "M": 14, "N": 15, "S": 10} approx = RP(solver, mu0, params, verbosity = 0) approx.setupApprox() out, err = capsys.readouterr() - assert "Prescribed S is too small. Decreasing M." in out - assert "Prescribed S is too small. Decreasing N." in out + assert "Prescribed E is too small. Decreasing M." in out + assert "Prescribed E is too small. Decreasing N." in out assert len(err) == 0 uhP = approx.getApprox(mu)[0] errP = approx.getErr(mu)[0] errNP = approx.normErr(mu)[0] assert np.allclose(np.abs(errP - (uhP - uh)), 0., rtol = 1e-3) assert np.isclose(errNP, 3.5197568e-07, rtol = 1e-1) poles, ress = approx.getResidues() condres = np.linalg.cond(solver.innerProduct(ress, ress)) assert np.isclose(condres, 192.1791778, rtol = 1e-3) assert np.isclose(np.min(np.abs(poles - 2.)), 0., atol = 1e-3) assert np.isclose(np.min(np.abs(poles - 1.)), 0., atol = 1e-2) assert np.isclose(np.min(np.abs(poles - 3.)), 0., atol = 1e-2) diff --git a/tests/test_3_reduction_methods_1D/rb_distributed_greedy_1d.py b/tests/test_3_reduction_methods_1D/rb_distributed_greedy_1d.py index 52fb7a3..512a049 100644 --- a/tests/test_3_reduction_methods_1D/rb_distributed_greedy_1d.py +++ b/tests/test_3_reduction_methods_1D/rb_distributed_greedy_1d.py @@ -1,56 +1,60 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from matrix_fft import matrixFFT from rrompy.reduction_methods.distributed_greedy import RBDistributedGreedy \ as RBDG +from rrompy.parameter.parameter_sampling import QuadratureSampler as QS def test_lax_tolerance(capsys): mu = 2.25 solver = matrixFFT() - params = {"POD": True, "muBounds": [1.5, 6.5], "S": 4, "greedyTol": 1e-2} + params = {"POD": True, "sampler": QS([1.5, 6.5], "UNIFORM"), + "S": 4, "greedyTol": 1e-2, + "trainSetGenerator": QS([1.5, 6.5], "CHEBYSHEV")} approx = RBDG(solver, 4., params, verbosity = 10) approx.greedy() out, err = capsys.readouterr() assert "Done computing snapshots (final snapshot count: 10)." in out assert len(err) == 0 assert len(approx.mus) == 10 _, _, maxEst = approx.getMaxErrorEstimator(approx.muTest) assert maxEst < 1e-2 assert np.isclose(approx.normErr(mu)[0], 1.5056e-05, rtol = 1e-1) def test_samples_at_poles(): solver = matrixFFT() - params = {"POD": True, "muBounds": [1.5, 6.5], "S": 4, "nTestPoints": 100, - "greedyTol": 1e-5} + params = {"POD": True, "sampler": QS([1.5, 6.5], "UNIFORM"), + "S": 4, "nTestPoints": 100, "greedyTol": 1e-5, + "trainSetGenerator": QS([1.5, 6.5], "CHEBYSHEV")} approx = RBDG(solver, 4., params, verbosity = 0) approx.greedy() for mu in approx.mus: assert np.isclose(approx.normErr(mu)[0] / (1e-15+approx.normHF(mu)[0]), 0., atol = 1e-4) poles = approx.getPoles() for lambda_ in range(2, 7): assert np.isclose(np.min(np.abs(poles - lambda_)), 0., atol = 1e-3) assert np.isclose(np.min(np.abs(np.array(approx.mus(0)) - lambda_)), 0., atol = 1e-1) diff --git a/tests/test_4_reduction_methods_multiD/rational_interpolant_2d.py b/tests/test_4_reduction_methods_multiD/rational_interpolant_2d.py index ed14275..fe75aa8 100644 --- a/tests/test_4_reduction_methods_multiD/rational_interpolant_2d.py +++ b/tests/test_4_reduction_methods_multiD/rational_interpolant_2d.py @@ -1,77 +1,79 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from matrix_random import matrixRandom from rrompy.reduction_methods.distributed import RationalInterpolant as RI from rrompy.parameter.parameter_sampling import (QuadratureSampler as QS, ManualSampler as MS) def test_monomials(capsys): mu = [5.05, 7.1] mu0 = [5., 7.] solver = matrixRandom() uh = solver.solve(mu)[0] params = {"POD": False, "M": 4, "N": 4, "S": [4, 4], "robustTol": 1e-6, "interpRcond": 1e-3, "polybasis": "MONOMIAL", "sampler": QS([[4.9, 6.85], [5.1, 7.15]], "UNIFORM")} approx = RI(solver, mu0, params, verbosity = 0) approx.setupApprox() uhP1 = approx.getApprox(mu)[0] errP = approx.getErr(mu)[0] errNP = approx.normErr(mu)[0] myerrP = uhP1 - uh assert np.allclose(np.abs(errP - myerrP), 0., rtol = 1e-3) assert np.isclose(solver.norm(errP), errNP, rtol = 1e-3) resP = approx.getRes(mu)[0] resNP = approx.normRes(mu) assert np.isclose(solver.norm(resP), resNP, rtol = 1e-3) assert np.allclose(np.abs(resP - (solver.b(mu) - solver.A(mu).dot(uhP1))), 0., rtol = 1e-3) - assert np.isclose(errNP / solver.norm(uh), 7.537e-4, rtol = 1e-1) + assert np.isclose(errNP / solver.norm(uh), 5.2667e-05, rtol = 1e-1) out, err = capsys.readouterr() - assert ("poorly conditioned. Reducing M" in out) + print(out) + assert ("poorly conditioned. Reducing E" in out) assert len(err) == 0 def test_well_cond(): mu = [5.05, 7.1] mu0 = [5., 7.] solver = matrixRandom() params = {"POD": True, "M": 3, "N": 3, "S": [4, 4], "interpRcond": 1e-10, "polybasis": "CHEBYSHEV", "sampler": QS([[4.9, 6.85], [5.1, 7.15]], "UNIFORM")} approx = RI(solver, mu0, params, verbosity = 0) approx.setupApprox() print(approx.normErr(mu)[0] / approx.normHF(mu)[0]) assert np.isclose(approx.normErr(mu)[0] / approx.normHF(mu)[0], - 8.46624e-3, rtol = 1e-1) + 5.98695e-05, rtol = 1e-1) def test_hermite(): mu = [5.05, 7.1] mu0 = [5., 7.] solver = matrixRandom() sampler0 = QS([[4.9, 6.85], [5.1, 7.15]], "UNIFORM") params = {"POD": True, "M": 3, "N": 3, "S": [25], "polybasis": "CHEBYSHEV", "sampler": MS([[4.9, 6.85], [5.1, 7.15]], points = sampler0.generatePoints([3, 3]))} approx = RI(solver, mu0, params, verbosity = 0) approx.setupApprox() assert np.isclose(approx.normErr(mu)[0] / approx.normHF(mu)[0], - 1.449341e-4, rtol = 1e-1) + 0.000236598, rtol = 1e-1) + diff --git a/tests/test_4_reduction_methods_multiD/rational_pade_2d.py b/tests/test_4_reduction_methods_multiD/rational_pade_2d.py index 5dc7977..01ea5b7 100644 --- a/tests/test_4_reduction_methods_multiD/rational_pade_2d.py +++ b/tests/test_4_reduction_methods_multiD/rational_pade_2d.py @@ -1,64 +1,64 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see . # import numpy as np from matrix_random import matrixRandom from rrompy.reduction_methods.centered import RationalPade as RP def test_rho(): mu = [5.5, 7.5] mu0 = [5., 7.] solver = matrixRandom() uh = solver.solve(mu)[0] params = {"POD": False, "M": 3, "N": 3, "S": 10, "robustTol": 1e-6} approx = RP(solver, mu0, params, verbosity = 0) approx.setupApprox() uhP1 = approx.getApprox(mu)[0] errP = approx.getErr(mu)[0] errNP = approx.normErr(mu)[0] myerrP = uhP1 - uh assert np.allclose(np.abs(errP - myerrP), 0., rtol = 1e-3) assert np.isclose(solver.norm(errP), errNP, rtol = 1e-3) resP = approx.getRes(mu)[0] resNP = approx.normRes(mu) assert np.isclose(solver.norm(resP), resNP, rtol = 1e-3) assert np.allclose(np.abs(resP - (solver.b(mu) - solver.A(mu).dot(uhP1))), 0., rtol = 1e-3) def test_E_warn(capsys): mu = [5.5, 7.5] mu0 = [5., 7.] solver = matrixRandom() uh = solver.solve(mu)[0] params = {"POD": True, "M": 3, "N": 4, "S": 10} approx = RP(solver, mu0, params, verbosity = 0) approx.setupApprox() out, err = capsys.readouterr() - assert "Prescribed S is too small. Decreasing M" not in out - assert "Prescribed S is too small. Decreasing N" in out + assert "Prescribed E is too small. Decreasing M" not in out + assert "Prescribed E is too small. Decreasing N" in out assert len(err) == 0 uhP = approx.getApprox(mu)[0] uhNP = approx.normApprox(mu)[0] errP = approx.getErr(mu)[0] errNP = approx.normErr(mu)[0] assert np.allclose(np.abs(errP - (uhP - uh)), 0., rtol = 1e-3) - assert np.isclose(errNP / uhNP, .9302, rtol = 1e-1) + assert np.isclose(errNP / uhNP, 1.41336e-2, rtol = 1e-1)