diff --git a/examples/scipy/LagrangeSweep.py b/examples/scipy/LagrangeSweep.py index 7c18cc1..7194251 100644 --- a/examples/scipy/LagrangeSweep.py +++ b/examples/scipy/LagrangeSweep.py @@ -1,65 +1,85 @@ from copy import copy import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE from rrompy.reduction_methods.lagrange import ApproximantLagrangePade as Pade from rrompy.reduction_methods.lagrange import ApproximantLagrangeRB as RB from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper from rrompy.utilities.parameter_sampling import QuadratureSampler as QS +verb = 0 testNo = 1 -npoints = 11 +npoints = 100 +homog = True +homog = False if testNo == 1: - ks = np.power([4 + .5j, 14 + .5j], .5) - solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 20) + ks = np.power([24 + 5.j, 34 + 5.j], .5) + solver = HSBPE(kappa = 32 ** .5, theta = np.pi / 3, n = 15) solver.omega = np.real(np.mean(ks)) - mutars = np.linspace(0**.5, 21**.5, npoints) - filenamebase = '../data/output/HelmholtzBubbleLagrange' + mutars = np.linspace(21**.5, 42**.5, npoints) + filenamebase = '../data/output/HelmholtzBubbleSHIFTLSweep' + homog = False elif testNo == 2: - ks = [10 + .25j, 14 + .25j] + ks = [10 + 0.j, 14 + 0.j] solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, n = 10) solver.omega = np.real(np.mean(ks)) - mutars = np.linspace(9, 14, npoints) - filenamebase = '../data/output/HelmholtzBoxLagrange' + mutars = np.linspace(9, 15, npoints) + homogMSG = "" + if not homog: homogMSG = "Non" + filenamebase = '../data/output/HelmholtzBoxLSweep' + homogMSG + "Homog" k0 = np.mean(ks) shift = 6 -nsets = 2 +nsets = 5 stride = 3 Smax = stride * (nsets - 1) + shift + 2 rescaling = lambda x: np.power(x, 2.) rescalingInv = lambda x: np.power(x, .5) paramsPade = {'S':Smax, 'POD':True, 'sampler':QS(ks, "CHEBYSHEV", rescaling, rescalingInv)} paramsRB = copy(paramsPade) +paramsPoly = copy(paramsPade) paramsSetsPade = [None] * nsets paramsSetsRB = [None] * nsets +paramsSetsPoly = [None] * nsets for i in range(nsets): - paramsSetsPade[i] = {'N': stride * i + shift + 1, 'M': stride * i + shift, + paramsSetsPade[i] = {'N': stride * i + shift + 1, + 'M': stride * i + shift + 1, + 'S': stride * i + shift + 2} + paramsSetsRB[i] = {'R': stride * i + shift + 2, + 'S': stride * i + shift + 2} + paramsSetsPoly[i] = {'N': 0, + 'M': stride * i + shift + 1, 'S': stride * i + shift + 2} - paramsSetsRB[i] = {'R': stride * i + shift + 1, 'S': stride * i + shift + 2} -appPade = Pade(solver, mu0 = k0, approxParameters = paramsPade) -appRB = RB(solver, mu0 = k0, approxParameters = paramsRB) +appPade = Pade(solver, mu0 = k0, approxParameters = paramsPade, + verbosity = verb, homogeneize = homog) +appRB = RB(solver, mu0 = k0, approxParameters = paramsRB, + verbosity = verb, homogeneize = homog) +appPoly = Pade(solver, mu0 = k0, approxParameters = paramsPoly, + verbosity = verb, homogeneize = homog) sweeper = Sweeper(mutars = mutars, mostExpensive = 'Approx') sweeper.ROMEngine = appPade sweeper.params = paramsSetsPade -filenamePade = sweeper.sweep(filenamebase + 'PadeFE.dat', outputs = 'ALL') - +filenamePade = sweeper.sweep(filenamebase + 'Pade.dat') sweeper.ROMEngine = appRB sweeper.params = paramsSetsRB -filenameRB = sweeper.sweep(filenamebase + 'RBFE.dat', outputs = 'ALL') +filenameRB = sweeper.sweep(filenamebase + 'RB.dat') +sweeper.ROMEngine = appPoly +sweeper.params = paramsSetsPoly +filenamePoly = sweeper.sweep(filenamebase + 'Poly.dat') + +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normHF', 'normApp'], ['S'], onePlot = True, + save = filenamebase + 'Norm', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normResRel'], ['S'], save = filenamebase + 'Res', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normErrRel'], ['S'], save = filenamebase + 'Err', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) -print("Pade'") -sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['S'], - onePlot = True) -print("RB") -sweeper.plot(filenameRB, ['muRe'], ['normHF', 'normApp'], ['S'], - onePlot = True) -print("Pade'") -sweeper.plot(filenamePade, ['muRe'], ['normErr'], ['S']) -print("RB") -sweeper.plot(filenameRB, ['muRe'], ['normErr'], ['S']) diff --git a/examples/scipy/LagrangeSweepUnstable.py b/examples/scipy/LagrangeSweepUnstable.py index 18c7033..242b57d 100644 --- a/examples/scipy/LagrangeSweepUnstable.py +++ b/examples/scipy/LagrangeSweepUnstable.py @@ -1,42 +1,75 @@ from copy import copy import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE from rrompy.reduction_methods.lagrange import ApproximantLagrangePade as Pade from rrompy.reduction_methods.lagrange import ApproximantLagrangeRB as RB from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper from rrompy.utilities.parameter_sampling import ManualSampler as MS -npoints = 11 +npoints = 50 -solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 20) +solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 30, verbosity = 0) mutars = np.linspace(6**.5, 16**.5, npoints) -filenamebase = '../data/output/HelmholtzBubbleLagrangeUnstable' +filenamebase = '../data/output/HelmholtzBubbleLagrange' k0 = np.mean(mutars) rescaling = lambda x: np.power(x, 2.) rescalingInv = lambda x: np.power(x, .5) -paramsPade = {'S':4, 'POD':True, 'sampler':MS([6**.5, 16**.5], [[14**.5], [12**.5], [9**.5], [11**.5]])} -paramsPade = {'S':4, 'POD':True, 'sampler':MS([6**.5, 16**.5], [[8**.5], [10**.5], [13**.5], [11**.5]])} -paramsRB = copy(paramsPade) -paramsSetsPade = [{'N': 3, 'M': 3}] -paramsSetsRB = [{'R': 4}] -appPade = Pade(solver, mu0 = k0, approxParameters = paramsPade) -appRB = RB(solver, mu0 = k0, approxParameters = paramsRB) +paramsChoices = { + 'Stable': + {'S':4, 'POD':True, + 'sampler':MS([6**.5, 16**.5], [[14**.5], [12**.5], [9**.5], [11**.5]])}, + 'Unstable': + {'S':4, 'POD':True, + 'sampler':MS([6**.5, 16**.5], [[8**.5], [10**.5], [13**.5], [11**.5]])} + } -sweeper = Sweeper(mutars = mutars, mostExpensive = 'Approx') -sweeper.ROMEngine = appPade -sweeper.params = paramsSetsPade -filenamePade = sweeper.sweep(filenamebase + 'PadeFE.dat') - -sweeper.ROMEngine = appRB -sweeper.params = paramsSetsRB -filenameRB = sweeper.sweep(filenamebase + 'RBFE.dat') - -sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['S'], - onePlot = True) -sweeper.plot(filenameRB, ['muRe'], ['normHF', 'normApp'], ['S'], - onePlot = True) -sweeper.plot(filenamePade, ['muRe'], ['normErr'], ['S']) -sweeper.plot(filenameRB, ['muRe'], ['normErr'], ['S']) +j = 0 +filenamePade = [None] * len(paramsChoices.keys()) +filenameRB = [None] * len(paramsChoices.keys()) +for typeP in paramsChoices.keys(): + paramsPade = paramsChoices[typeP] + paramsRB = copy(paramsPade) + paramsSetsPade = [{'N': 3, 'M': 3}] + paramsSetsRB = [{'R': 4}] + + appPade = Pade(solver, mu0 = k0, approxParameters = paramsPade, + verbosity = 0) + appRB = RB(solver, mu0 = k0, approxParameters = paramsRB, + verbosity = 0) + + sweeper = Sweeper(mutars = mutars, mostExpensive = 'Approx') + sweeper.ROMEngine = appPade + sweeper.params = paramsSetsPade + filenamePade[j] = sweeper.sweep(filenamebase + 'Pade{}.dat'.format(typeP), + verbose = 0) + sweeper.ROMEngine = appRB + sweeper.params = paramsSetsRB + filenameRB[j] = sweeper.sweep(filenamebase + 'RB{}.dat'.format(typeP), + verbose = 0) + j += 1 + +print("Pade'") +sweeper.plotCompare(filenamePade, ['muRe'], + ['normHF', 'normApp'], ['S'], onePlot = True, + save = filenamebase + 'PadeNorm', saveFormat = "png", + labels = list(paramsChoices.keys())) +sweeper.plotCompare(filenamePade, ['muRe'], ['normResRel'], + ['S'], save = filenamebase + 'PadeRes', saveFormat = "png", + labels = list(paramsChoices.keys())) +sweeper.plotCompare(filenamePade, ['muRe'], ['normErrRel'], + ['S'], save = filenamebase + 'PadeErr', saveFormat = "png", + labels = list(paramsChoices.keys())) +print("RB") +sweeper.plotCompare(filenameRB, ['muRe'], + ['normHF', 'normApp'], ['S'], onePlot = True, + save = filenamebase + 'RBNorm', saveFormat = "png", + labels = list(paramsChoices.keys())) +sweeper.plotCompare(filenameRB, ['muRe'], ['normResRel'], + ['S'], save = filenamebase + 'RBRes', saveFormat = "png", + labels = list(paramsChoices.keys())) +sweeper.plotCompare(filenameRB, ['muRe'], ['normErrRel'], + ['S'], save = filenamebase + 'RBErr', saveFormat = "png", + labels = list(paramsChoices.keys())) diff --git a/examples/scipy/PadeLagrange.py b/examples/scipy/PadeLagrange.py index bbd16d1..6fdcec3 100644 --- a/examples/scipy/PadeLagrange.py +++ b/examples/scipy/PadeLagrange.py @@ -1,86 +1,107 @@ import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE from rrompy.hfengines.scipy import HelmholtzSquareTransmissionProblemEngine as HSTPE from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE from rrompy.reduction_methods.lagrange import ApproximantLagrangePade as Pade from rrompy.utilities.parameter_sampling import QuadratureSampler as QS -testNo = 3 +testNo = 2 +verb = 0 +homog = True +homog = False if testNo == 1: k0s = np.power([10 + 0.j, 14 + 0.j], .5) k0 = np.mean(k0s) ktar = (11 + .5j) ** .5 rescaling = lambda x: np.power(x, 2.) rescalingInv = lambda x: np.power(x, .5) params = {'N':4, 'M':3, 'S':5, 'POD':True, 'sampler':QS(k0s, "CHEBYSHEV", rescaling, rescalingInv)} - solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40) + solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40, + verbosity = verb) solver.omega = np.real(k0) - approx = Pade(solver, mu0 = k0, approxParameters = params) + approx = Pade(solver, mu0 = k0, approxParameters = params, + verbosity = verb) approx.setupApprox() # approx.plotSamples() approx.plotApp(ktar, name = 'u_Pade''') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) print('\nPoles Pade'':') print(approx.getPoles()) ############ elif testNo == 2: k0s = [3.85 + 0.j, 4.15 + 0.j] k0 = np.mean(k0s) ktar = 4 + .15j rescaling = lambda x: np.power(x, 2.) rescalingInv = lambda x: np.power(x, .5) - params = {'N':9, 'M':8, 'S':10, 'POD':True, + params = {'N':8, 'M':9, 'S':10, 'POD':True, 'sampler':QS(k0s, "CHEBYSHEV", rescaling, rescalingInv)} - solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50) + solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50, + verbosity = verb) solver.omega = np.real(k0) - approx = Pade(solver, mu0 = k0, approxParameters = params) + approx = Pade(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) approx.setupApprox() approx.plotSamples() approx.plotApp(ktar, name = 'u_Pade''') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) print('\nPoles Pade'':') print(approx.getPoles()) ############ elif testNo == 3: k0s = [2, 5] k0 = np.mean(k0s) ktar = 4.5 - 0.j - params = {'N':10, 'M':9, 'S':15, 'POD':True, + params = {'N':10, 'M':11, 'S':15, 'POD':True, 'sampler':QS(k0s, "CHEBYSHEV")} - solver = HBSPE(R = 7, kappa = 3, theta = - np.pi * 75 / 180, n = 40) + solver = HBSPE(R = 7, kappa = 3, theta = - np.pi * 75 / 180, n = 40, + verbosity = verb) solver.omega = np.real(k0) - approx = Pade(solver, mu0 = k0, approxParameters = params) + approx = Pade(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) approx.setupApprox() -# approx.plotSamples() + approx.plotSamples() approx.plotApp(ktar, name = 'u_Pade''') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') - + approx.plotRes(ktar, name = 'res') + appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) print('\nPoles Pade'':') print(approx.getPoles()) diff --git a/examples/scipy/PadeTaylor.py b/examples/scipy/PadeTaylor.py index cae5981..7bae70c 100644 --- a/examples/scipy/PadeTaylor.py +++ b/examples/scipy/PadeTaylor.py @@ -1,75 +1,97 @@ import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE -from rrompy.hfengines.scipy import HelmholtzSquareTransmissionProblemEngine as HSTPE +from rrompy.hfengines.scipy import ( + HelmholtzSquareTransmissionProblemEngine as HSTPE) from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE from rrompy.reduction_methods.taylor import ApproximantTaylorPade as Pade -testNo = 1 +testNo = 4 +verb = 0 +homog = True +#homog = False if testNo == 1: params = {'N':4, 'M':3, 'E':4, 'sampleType':'Arnoldi', 'POD':True} k0 = 12 ** .5 ktar = 10.5 ** .5 - solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40) + solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40, + verbosity = verb) solver.omega = np.real(k0) - approx = Pade(solver, mu0 = k0, approxParameters = params) + approx = Pade(solver, mu0 = k0, approxParameters = params, + verbosity = verb) approx.setupApprox() +# approx.plotSamples() approx.plotApp(ktar, name = 'u_Pade''') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) print('\nPoles Pade'':') print(approx.getPoles()) ############ elif testNo == 2: - params = {'N':7, 'M':6, 'E':7, 'sampleType':'Arnoldi', 'POD':True} + params = {'N':6, 'M':7, 'E':7, 'sampleType':'Arnoldi', 'POD':True} k0 = 16 ** .5 - ktar = 14 ** .5 + ktar = 15 ** .5 - solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50) + solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50, + verbosity = verb) solver.omega = np.real(k0) - approx = Pade(solver, mu0 = k0, approxParameters = params) + approx = Pade(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) approx.setupApprox() - approx.plotSamples() +# approx.plotSamples() approx.plotApp(ktar, name = 'u_Pade''') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) print('\nPoles Pade'':') print(approx.getPoles()) ############ elif testNo in [3, 4]: if testNo == 3: - params = {'N':8, 'M':7, 'E':8, 'sampleType':'Krylov', 'POD':True} + params = {'N':7, 'M':8, 'E':8, 'sampleType':'Krylov', 'POD':True} else: - params = {'N':8, 'M':7, 'E':8, 'sampleType':'Arnoldi', 'POD':True} + params = {'N':7, 'M':8, 'E':8, 'sampleType':'Arnoldi', 'POD':True} k0 = 3 - ktar = 4.25+.5j + ktar = 4.+0.j - solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, n = 30) + solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, n = 30, + verbosity = verb) solver.omega = np.real(k0) - approx = Pade(solver, mu0 = k0, approxParameters = params) + approx = Pade(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) approx.setupApprox() approx.plotSamples() approx.plotApp(ktar, name = 'u_Pade''') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) print('\nPoles Pade'':') print(approx.getPoles()) - diff --git a/examples/scipy/RBLagrange.py b/examples/scipy/RBLagrange.py index a8e07c0..bbfe2bc 100644 --- a/examples/scipy/RBLagrange.py +++ b/examples/scipy/RBLagrange.py @@ -1,85 +1,99 @@ import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE from rrompy.hfengines.scipy import HelmholtzSquareTransmissionProblemEngine as HSTPE from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE from rrompy.reduction_methods.lagrange import ApproximantLagrangeRB as RB from rrompy.utilities.parameter_sampling import QuadratureSampler as QS testNo = 3 +verb = 0 +homog = True +homog = False if testNo == 1: k0s = np.power([10 + 0.j, 14 + 0.j], .5) k0 = np.mean(k0s) ktar = (11 + .5j) ** .5 rescaling = lambda x: np.power(x, 2.) rescalingInv = lambda x: np.power(x, .5) params = {'S':5, 'R':4, 'POD':True, 'sampler':QS(k0s, "CHEBYSHEV", rescaling, rescalingInv)} - solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40) + solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40, + verbosity = verb) solver.omega = np.real(k0) - approx = RB(solver, mu0 = k0, approxParameters = params) + approx = RB(solver, mu0 = k0, approxParameters = params, + verbosity = verb) approx.setupApprox() # approx.plotSamples() approx.plotApp(ktar, name = 'u_RB') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) - print('\nPoles RB:') - print(approx.getPoles()) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) ############ elif testNo == 2: k0s = [3.85 + 0.j, 4.15 + 0.j] k0 = np.mean(k0s) ktar = 4 + .15j rescaling = lambda x: np.power(x, 2.) rescalingInv = lambda x: np.power(x, .5) params = {'S':10, 'R':9, 'POD':True, 'sampler':QS(k0s, "CHEBYSHEV", rescaling, rescalingInv)} - solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50) + solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50, + verbosity = verb) solver.omega = np.real(k0) - approx = RB(solver, mu0 = k0, approxParameters = params) + approx = RB(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) approx.setupApprox() - approx.plotSamples() +# approx.plotSamples() approx.plotApp(ktar, name = 'u_RB') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) - print('\nPoles RB:') - print(approx.getPoles()) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) ############ elif testNo == 3: k0s = [2, 5] k0 = np.mean(k0s) - ktar = 4.5 - .2j + ktar = 4.5 - 0.j params = {'S':15, 'R':10, 'POD':True, 'sampler':QS(k0s, "CHEBYSHEV")} - solver = HBSPE(R = 7, kappa = 3, theta = - np.pi * 75 / 180, n = 40) + solver = HBSPE(R = 7, kappa = 3, theta = - np.pi * 75 / 180, n = 40, + verbosity = verb) solver.omega = np.real(k0) - approx = RB(solver, mu0 = k0, approxParameters = params) + approx = RB(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) approx.setupApprox() # approx.plotSamples() approx.plotApp(ktar, name = 'u_RB') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') - + approx.plotRes(ktar, name = 'res') + appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) - print('\nPoles RB:') - print(approx.getPoles()) - + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) diff --git a/examples/scipy/RBTaylor.py b/examples/scipy/RBTaylor.py index aa2c384..b95241c 100644 --- a/examples/scipy/RBTaylor.py +++ b/examples/scipy/RBTaylor.py @@ -1,75 +1,90 @@ import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE from rrompy.hfengines.scipy import HelmholtzSquareTransmissionProblemEngine as HSTPE from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE from rrompy.reduction_methods.taylor import ApproximantTaylorRB as RB -testNo = 3 +testNo = 4 +verb = 0 +homog = True +#homog = False if testNo == 1: params = {'E':4, 'R':4, 'sampleType':'Arnoldi', 'POD':True} k0 = 12 ** .5 ktar = 10.5 ** .5 - solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40) + solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40, + verbosity = verb) solver.omega = np.real(k0) - approx = RB(solver, mu0 = k0, approxParameters = params) + approx = RB(solver, mu0 = k0, approxParameters = params, + verbosity = verb) approx.setupApprox() +# approx.plotSamples() approx.plotApp(ktar, name = 'u_RB') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) - print('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}'.format(solNorm, appErr, + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) + print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) - print('\nPoles RB:') - print(approx.getPoles()) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) ############ elif testNo == 2: params = {'E':7, 'R':7, 'sampleType':'Arnoldi', 'POD':True} k0 = 16**.5 - ktar = 14**.5 + ktar = 15**.5 - solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 4., n = 50) + solver = HSTPE(nT = 2, nB = 1, theta = np.pi * 45/180, kappa = 3., + n = 50, verbosity = verb) solver.omega = np.real(k0) - approx = RB(solver, mu0 = k0, approxParameters = params) + approx = RB(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) approx.setupApprox() - approx.plotSamples() +# approx.plotSamples() approx.plotApp(ktar, name = 'u_RB') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) - print('\nPoles RB:') - print(approx.getPoles()) + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) ############ elif testNo in [3, 4]: if testNo == 3: - params = {'R':8, 'E':8, 'sampleType':'Krylov', 'POD':True} + params = {'E':8, 'sampleType':'Krylov', 'POD':True} else: - params = {'R':8, 'E':8, 'sampleType':'Arnoldi', 'POD':True} + params = {'E':8, 'sampleType':'Arnoldi', 'POD':True} k0 = 3 ktar = 4.25+.5j - solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, n = 30) + solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, + n = 30, verbosity = verb) solver.omega = np.real(k0) - approx = RB(solver, mu0 = k0, approxParameters = params) + approx = RB(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) approx.setupApprox() - approx.plotSamples() +# approx.plotSamples() approx.plotApp(ktar, name = 'u_RB') approx.plotHF(ktar, name = 'u_HF') approx.plotErr(ktar, name = 'err') + approx.plotRes(ktar, name = 'res') appErr, solNorm = approx.normErr(ktar), approx.normHF(ktar) + resNorm, RHSNorm = approx.normRes(ktar), approx.normRHS(ktar) print(('SolNorm:\t{}\nErr:\t{}\nErrRel:\t{}').format(solNorm, appErr, np.divide(appErr, solNorm))) - print('\nPoles RB:') - print(approx.getPoles()) - + print(('RHSNorm:\t{}\nRes:\t{}\nResRel:\t{}').format(RHSNorm, resNorm, + np.divide(resNorm, RHSNorm))) diff --git a/examples/scipy/TaylorPoles.py b/examples/scipy/TaylorPoles.py index 2b7f3d5..d59d092 100644 --- a/examples/scipy/TaylorPoles.py +++ b/examples/scipy/TaylorPoles.py @@ -1,61 +1,68 @@ import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE from rrompy.reduction_methods.taylor import ApproximantTaylorPade as Pade from rrompy.reduction_methods.taylor import ApproximantTaylorRB as RB -from rrompy.utilities import squareResonances +from rrompy.utilities.base import squareResonances -k0 = (12+1.j) ** .5 +verb = 0 + +k0 = (12+0.j) ** .5 Nmin, Nmax = 2, 10 Nvals = np.arange(Nmin, Nmax + 1, 2) params = {'N':Nmin, 'M':0, 'Emax':Nmax, 'POD':True, 'sampleType':'Arnoldi'} #, 'robustTol':1e-14} -#boolCon = lambda x : np.abs(np.imag(x)) < 1e-1 * np.abs(np.real(x) - np.real(z0)) +#boolCon = lambda x : np.abs(np.imag(x)) < 1e-1 * np.abs(np.real(x) +# - np.real(z0)) #cleanupParameters = {'boolCondition':boolCon, 'residueCheck':True} -solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 25) +solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 25, verbosity = verb) solver.omega = np.real(k0) -approxP = Pade(solver, mu0 = k0, approxParameters = params)#, +approxP = Pade(solver, mu0 = k0, approxParameters = params, verbosity = verb)#, # equilibration = True, cleanupParameters = cleanupParameters) -approxR = RB(solver, mu0 = k0, approxParameters = params) +approxR = RB(solver, mu0 = k0, approxParameters = params, verbosity = verb) rP, rE = [None] * len(Nvals), [None] * len(Nvals) verbose = 1 for j, N in enumerate(Nvals): if verbose > 0: print('N = E = {}'.format(N)) approxP.approxParameters = {'N':N, 'E':N} approxR.approxParameters = {'R':N, 'E':N} if verbose > 1: print(approxP.approxParameters) print(approxR.approxParameters) rP[j] = approxP.getPoles() rE[j] = approxR.getPoles() if verbose > 2: print(rP) print(rE) from matplotlib import pyplot as plt plotRows = int(np.ceil(len(Nvals) / 3)) fig, axes = plt.subplots(plotRows, 3, figsize = (15, 3.5 * plotRows)) for j, N in enumerate(Nvals): i1, i2 = int(np.floor(j / 3)), j % 3 axes[i1, i2].set_title('N = E = {}'.format(N)) axes[i1, i2].plot(np.real(rP[j]), np.imag(rP[j]), 'Xb', label="Pade'", markersize = 8) - axes[i1, i2].plot(np.real(rE[j]), np.imag(rE[j]), '*r', - label="RB", markersize = 10) + axes[i1, i2].plot(np.real(rE[j]), np.imag(rE[j]), 'Pr', + label="RB", markersize = 8) axes[i1, i2].axhline(linewidth=1, color='k') xmin, xmax = axes[i1, i2].get_xlim() - res = np.array(squareResonances(xmin**2., xmax**2., False), .5) + height = (xmax - xmin) / 2. + res = np.power(squareResonances(xmin**2., xmax**2., False), .5) axes[i1, i2].plot(res, np.zeros_like(res), 'ok', markersize = 4) + axes[i1, i2].plot(np.real(k0), np.imag(k0), 'om', markersize = 5) + axes[i1, i2].plot(np.real(k0) * np.ones(2), + 1.5 * height * np.arange(-1, 3, 2), '--m') axes[i1, i2].grid() axes[i1, i2].set_xlim(xmin, xmax) - axes[i1, i2].axis('equal') + axes[i1, i2].set_ylim(- height, height) p = axes[i1, i2].legend() plt.tight_layout() for j in range((len(Nvals) - 1) % 3 + 1, 3): axes[plotRows - 1, j].axis('off') diff --git a/examples/scipy/TaylorSweep.py b/examples/scipy/TaylorSweep.py index 9edac3e..9e9c8a3 100644 --- a/examples/scipy/TaylorSweep.py +++ b/examples/scipy/TaylorSweep.py @@ -1,64 +1,77 @@ import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE from rrompy.reduction_methods.taylor import ApproximantTaylorPade as Pade from rrompy.reduction_methods.taylor import ApproximantTaylorRB as RB from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper testNo = 1 +verb = 0 +homog = True +homog = False k0 = (12 + 0.j) ** .5 -npoints = 101 +npoints = 100 shift = 5 -nsets = 3 +nsets = 2 stride = 2 Emax = stride * (nsets - 1) + shift + 1 if testNo == 1: - solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 10) + solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 15, + verbosity = verb) solver.omega = np.real(k0) params = {'Emax':Emax, 'sampleType':'ARNOLDI', 'POD':True} ktars = np.linspace(7**.5, 16**.5, npoints) - filenamebase = '../data/output/HelmholtzBubbleTaylor' + filenamebase = '../data/output/HelmholtzBubbleTSweep' + homog = False elif testNo == 2: - solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, n = 10) + solver = HBSPE(R = 5, kappa = 3, theta = - np.pi * 75 / 180, n = 10, + verbosity = verb) solver.omega = np.real(k0) params = {'Emax':Emax, 'sampleType':'Arnoldi', 'POD':True} - ktars = np.linspace(11**.5, 13**.5, npoints) - filenamebase = '../data/output/HelmholtzBoxTaylor' + ktars = np.linspace(7**.5, 17**.5, npoints) + homogMSG = "" + if not homog: homogMSG = "Non" + filenamebase = '../data/output/HelmholtzBoxTSweep' + homogMSG + "Homog" paramsSetsPade = [None] * nsets paramsSetsRB = [None] * nsets +paramsSetsPoly = [None] * nsets for i in range(nsets): paramsSetsPade[i] = {'N':stride*i+shift+1, 'M':stride*i+shift+1, 'E':stride*i+shift+1} paramsSetsRB[i] = {'E':stride*i+shift+1,'R':stride*i+shift+2} + paramsSetsPoly[i] = {'N':0, 'M':stride*i+shift+1, + 'E':stride*i+shift+1} -appPade = Pade(solver, mu0 = k0, approxParameters = params) -appRB = RB(solver, mu0 = k0, approxParameters = params) +appPade = Pade(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) +appRB = RB(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) +appPoly = Pade(solver, mu0 = k0, approxParameters = params, + verbosity = verb, homogeneize = homog) sweeper = Sweeper(mutars = ktars, mostExpensive = 'Approx') sweeper.ROMEngine = appPade sweeper.params = paramsSetsPade -filenamePade = sweeper.sweep(filenamebase + 'PadeFE.dat') - +filenamePade = sweeper.sweep(filenamebase + 'Pade.dat') sweeper.ROMEngine = appRB sweeper.params = paramsSetsRB -filenameRB = sweeper.sweep(filenamebase + 'RBFE.dat') +filenameRB = sweeper.sweep(filenamebase + 'RB.dat') +sweeper.ROMEngine = appPoly +sweeper.params = paramsSetsPoly +filenamePoly = sweeper.sweep(filenamebase + 'Poly.dat') -print('Pade''') -sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['E'], - onePlot = True) -print('RB') -sweeper.plot(filenameRB, ['muRe'], ['normHF', 'normApp'], ['E'], - onePlot = True) -print('Pade''') -sweeper.plot(filenamePade, ['muRe'], ['normResRel'], ['E']) -print('RB') -sweeper.plot(filenameRB, ['muRe'], ['normResRel'], ['E']) -print('Pade''') -sweeper.plot(filenamePade, ['muRe'], ['normErrRel'], ['E']) -print('RB') -sweeper.plot(filenameRB, ['muRe'], ['normErrRel'], ['E']) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normHF', 'normApp'], ['E'], onePlot = True, + save = filenamebase + 'Norm', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normResRel'], ['E'], save = filenamebase + 'Res', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normErrRel'], ['E'], save = filenamebase + 'Err', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) diff --git a/examples/scipy/airfoil.py b/examples/scipy/airfoil.py index e8eaf25..498aa50 100644 --- a/examples/scipy/airfoil.py +++ b/examples/scipy/airfoil.py @@ -1,192 +1,212 @@ -from copy import copy -import fenics as fen +from copy import deepcopy as copy import numpy as np -import sympy as sp -from rrompy.hfengines.scipy import ScatteringProblemEngine as SPE +import fenics as fen +import ufl +from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HSP from rrompy.reduction_methods.taylor import ApproximantTaylorPade as TP from rrompy.reduction_methods.taylor import ApproximantTaylorRB as TRB from rrompy.reduction_methods.lagrange import ApproximantLagrangePade as LP from rrompy.reduction_methods.lagrange import ApproximantLagrangeRB as LRB from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper from rrompy.utilities.parameter_sampling import QuadratureSampler as QS +from rrompy.utilities.base.fenics import fenONE from operator import itemgetter def subdict(d, ks): return dict(zip(ks, itemgetter(*ks)(d))) +verb = 0 + +#################### + +homog = True +#homog = False + #################### test = "solve" test = "Taylor" test = "Lagrange" -#test = "TaylorSweep" -#test = "LagrangeSweep" +test = "TaylorSweep" +test = "LagrangeSweep" plotSamples = True -k0 = 10 + 1.j -kLeft, kRight = 8 + 1.j, 12 + 1.j +k0 = 10 +kLeft, kRight = 8 + 0.j, 12 + 0.j ktar = 11 -ktars = np.linspace(8, 12, 33) - .5j +ktars = np.linspace(8, 12, 21) + 0.j PI = np.pi R = 2 def Dboundary(x, on_boundary): return on_boundary and (x[0]**2+x[1]**2)**.5 < .95 * R kappa = 10 theta = PI * - 45 / 180. mu = 1.1 epsilon = .1 +mesh = fen.Mesh('../data/mesh/airfoil.xml') -x, y = sp.symbols('x[0] x[1]', real=True) -phiex = kappa * (x * np.cos(theta) + y * np.sin(theta)) -u0ex = - sp.exp(1.j * phiex) +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)) checkReal = x**2-x+y**2 -rhoroot = ((x**2+y**2)/((x-1)**2+y**2))**.25 -phiroot1 = sp.atan(-y/(x**2-x+y**2)) / 2 -phiroot2 = sp.atan(-y/(x**2-x+y**2)) / 2 - PI * sp.sign(-y/(x**2-x+y**2)) / 2 -kappam1 = (((rhoroot*sp.cos(phiroot1)+.5)**2.+(rhoroot*sp.sin(phiroot1))**2.)/ - ((rhoroot*sp.cos(phiroot1)-1.)**2.+(rhoroot*sp.sin(phiroot1))**2.) +rhop5 = ((x**2+y**2)/((x-1)**2+y**2))**.25 +phiroot1 = fen.atan(-y/(x**2-x+y**2)) / 2 +phiroot2 = fen.atan(-y/(x**2-x+y**2)) / 2 - PI * ufl.sign(-y/(x**2-x+y**2)) / 2 +kappam1 = (((rhop5*fen.cos(phiroot1)+.5)**2.+(rhop5*fen.sin(phiroot1))**2.)/ + ((rhop5*fen.cos(phiroot1)-1.)**2.+(rhop5*fen.sin(phiroot1))**2.) )**.5 - mu -kappam2 = (((rhoroot*sp.cos(phiroot2)+.5)**2.+(rhoroot*sp.sin(phiroot2))**2.)/ - ((rhoroot*sp.cos(phiroot2)-1.)**2.+(rhoroot*sp.sin(phiroot2))**2.) +kappam2 = (((rhop5*fen.cos(phiroot2)+.5)**2.+(rhop5*fen.sin(phiroot2))**2.)/ + ((rhop5*fen.cos(phiroot2)-1.)**2.+(rhop5*fen.sin(phiroot2))**2.) )**.5 - mu -Heps1 = .9 * .5 * (1 + kappam1/epsilon + sp.sin(PI*kappam1/epsilon) / PI) + .1 -Heps2 = .9 * .5 * (1 + kappam2/epsilon + sp.sin(PI*kappam2/epsilon) / PI) + .1 - -mesh = fen.Mesh('../data/mesh/airfoil.xml') - -a = fen.Expression(('{0}>=0 ? ({2}>=-{1} ? ({2}<={1} ? {4} : 1) : .1) : ' - '({3}>=-{1} ? ({3}<={1} ? {5} : 1) : .1)')\ - .format(sp.printing.ccode(checkReal), str(epsilon), - sp.printing.ccode(kappam1), - sp.printing.ccode(kappam2), - sp.printing.ccode(Heps1), - sp.printing.ccode(Heps2)), degree = 3) - -DirichletTerm = [sp.printing.ccode(sp.simplify(sp.re(u0ex))), - sp.printing.ccode(sp.simplify(sp.im(u0ex)))] - -solver = SPE() -solver.omega = k0 +Heps1 = .9 * .5 * (1 + kappam1/epsilon + fen.sin(PI*kappam1/epsilon) / PI) + .1 +Heps2 = .9 * .5 * (1 + kappam2/epsilon + fen.sin(PI*kappam2/epsilon) / PI) + .1 + +cTT = ufl.conditional(ufl.le(kappam1, epsilon), Heps1, fenONE) +c_F = fen.Constant(.1) +cFT = ufl.conditional(ufl.le(kappam2, epsilon), Heps2, fenONE) +c_F = fen.Constant(.1) +cT = ufl.conditional(ufl.ge(kappam1, - epsilon), cTT, c_F) +cF = ufl.conditional(ufl.ge(kappam2, - epsilon), cFT, c_F) +a = ufl.conditional(ufl.ge(checkReal, 0.), cT, cF) + +### +solver = HSP(R, np.abs(k0), theta, n = 1, verbosity = verb, + degree_threshold = 8) solver.V = fen.FunctionSpace(mesh, "P", 3) solver.diffusivity = a solver.DirichletBoundary = Dboundary -solver.RobinBoundary = 'rest' -solver.DirichletDatum = [fen.Expression(x, degree = 3)\ - for x in DirichletTerm] - -baseRe, baseIm = solver.DirichletDatum -baseRe = fen.project(baseRe, solver.V) -baseIm = fen.project(baseIm, solver.V) -uinc = np.array(baseRe.vector()) + 1.j * np.array(baseIm.vector()) +solver.RobinBoundary = "REST" +solver.DirichletDatum = [u0R, u0I] +### if test == "solve": - aF = fen.interpolate(a, solver.V) - av = aF.vector() - uh = solver.solve(k0) + uinc = solver.liftDirichletData(k0) + if homog: + uhtot = solver.solve(k0, homogeneized = homog) + uh = uhtot + uinc + else: + uh = solver.solve(k0, homogeneized = homog) + uhtot = uh - uinc print(solver.norm(uh)) - uhtot = uh - uinc print(solver.norm(uhtot)) - solver.plot(av, what = 'Real', name = 'a') - solver.plot(uhtot - uh, what = 'Real', name = 'u_inc') + solver.plot(fen.project(a, solver.V).vector(), what = 'Real', + name = 'a') + solver.plot(uinc, what = 'Real', name = 'u_inc') solver.plot(uh, what = 'ABS') solver.plot(uhtot, what = 'ABS', name = 'u + u_inc') elif test in ["Taylor", "Lagrange"]: if test == "Taylor": - params = {'N':8, 'M':7, 'R':8, 'E':8, 'sampleType':'Krylov', 'POD':False} - params = {'N':8, 'M':7, 'R':8, 'E':8, 'sampleType':'Arnoldi', 'POD':False} + params = {'N':8, 'M':8, 'R':8, 'E':8, + 'sampleType':'Arnoldi', 'POD':True} parPade = subdict(params, ['N', 'M', 'E', 'sampleType', 'POD']) parRB = subdict(params, ['R', 'E', 'sampleType', 'POD']) - approxPade = TP(solver, mu0 = k0, approxParameters = parPade) - approxRB = TRB(solver, mu0 = k0, approxParameters = parRB) + approxPade = TP(solver, mu0 = k0, approxParameters = parPade, + verbosity = verb, homogeneize = homog) + approxRB = TRB(solver, mu0 = k0, approxParameters = parRB, + verbosity = verb, homogeneize = homog) else: - params = {'N':8, 'M':7, 'R':9, 'S':9, 'POD':True, + params = {'N':8, 'M':8, 'R':9, 'S':9, 'POD':True, 'sampler':QS([kLeft, kRight], "CHEBYSHEV")} parPade = subdict(params, ['N', 'M', 'S', 'POD', 'sampler']) parRB = subdict(params, ['R', 'S', 'POD', 'sampler']) approxPade = LP(solver, mu0 = np.mean([kLeft, kRight]), - approxParameters = parPade) + approxParameters = parPade, verbosity = verb, + homogeneize = homog) approxRB = LRB(solver, mu0 = np.mean([kLeft, kRight]), - approxParameters = parRB) + approxParameters = parRB, verbosity = verb, + homogeneize = homog) approxPade.setupApprox() approxRB.setupApprox() if plotSamples: approxPade.plotSamples() - PadeErr, solNorm = approxPade.normErr(ktar), approxPade.normHF(ktar) - RBErr = approxRB.normErr(ktar) - print(('SolNorm:\t{}\nErrPade:\t{}\nErrRelPade:\t{}\nErrRB:\t\t{}' - '\nErrRelRB:\t{}').format(solNorm, PadeErr, - np.divide(PadeErr, solNorm), RBErr, - np.divide(RBErr, solNorm))) + approxPade.plotHF(ktar, name = 'u_HF') + approxPade.plotApp(ktar, name = 'u_Pade''') + approxPade.plotErr(ktar, name = 'err_Pade''') + approxPade.plotRes(ktar, name = 'res_Pade''') + approxRB.plotApp(ktar, name = 'u_RB') + approxRB.plotErr(ktar, name = 'err_RB') + approxRB.plotRes(ktar, name = 'res_RB') + + HFNorm, RHSNorm = approxPade.normHF(ktar), approxPade.normRHS(ktar) + PadeRes, PadeErr = approxPade.normRes(ktar), approxPade.normErr(ktar) + RBRes, RBErr = approxRB.normRes(ktar), approxRB.normErr(ktar) + print('HFNorm:\t{}\nRHSNorm:\t{}'.format(HFNorm, RHSNorm)) + print('PadeRes:\t{}\nPadeErr:\t{}'.format(PadeRes, PadeErr)) + print('RBRes:\t{}\nRBErr:\t{}'.format(RBRes, RBErr)) print('\nPoles Pade'':') print(approxPade.getPoles()) - print('\nPoles RB:') - print(approxRB.getPoles()) - - uHF = approxPade.getHF(ktar) - solver.plot(uHF, name = 'u_ex') - approxPade.plotApp(ktar, name = 'u_Pade''') - approxRB.plotApp(ktar, name = 'u_RB') - approxPade.plotErr(ktar, name = 'errPade''') - approxRB.plotErr(ktar, name = 'errRB') elif test in ["TaylorSweep", "LagrangeSweep"]: if test == "TaylorSweep": shift = 10 - nsets = 3 + nsets = 2 stride = 3 Emax = stride * (nsets - 1) + shift + 1 - params = {'Emax':Emax, 'sampleType':'Krylov', 'POD':False} - params = {'Emax':Emax, 'sampleType':'Arnoldi', 'POD':False} + params = {'Emax':Emax, 'sampleType':'Arnoldi', 'POD':True} paramsSetsPade = [None] * nsets paramsSetsRB = [None] * nsets for i in range(nsets): - paramsSetsPade[i] = {'N':stride*i+shift + 1, 'M':stride*i+shift, + paramsSetsPade[i] = {'N':stride*i+shift + 1, 'M':stride*i+shift+1, 'E':stride*i+shift + 1} - paramsSetsRB[i] = {'E':stride*i+shift + 1,'R':stride*i+shift + 1} - approxPade = TP(solver, mu0 = kappa,approxParameters = params) - approxRB = TRB(solver, mu0 = kappa, approxParameters = params) + paramsSetsRB[i] = {'E':stride*i+shift + 1,'R':stride*i+shift + 2} + approxPade = TP(solver, mu0 = kappa,approxParameters = params, + verbosity = verb, homogeneize = homog) + approxRB = TRB(solver, mu0 = kappa, approxParameters = params, + verbosity = verb, homogeneize = homog) else: shift = 10 - nsets = 3 + nsets = 2 stride = 3 Smax = stride * (nsets - 1) + shift + 2 paramsPade = {'S':Smax, 'POD':True, 'sampler':QS([kLeft, kRight], "CHEBYSHEV")} paramsRB = copy(paramsPade) paramsSetsPade = [None] * nsets paramsSetsRB = [None] * nsets for i in range(nsets): - paramsSetsPade[i] = {'N': stride*i+shift+1, 'M': stride*i+shift, + paramsSetsPade[i] = {'N': stride*i+shift+1, 'M': stride*i+shift+1, 'S': stride*i+shift+2} - paramsSetsRB[i] = {'R': stride*i+shift+1, 'S': stride*i+shift+2} + paramsSetsRB[i] = {'R': stride*i+shift+2, 'S': stride*i+shift+2} approxPade = LP(solver, mu0 = np.mean([kLeft, kRight]), - approxParameters = paramsPade) + approxParameters = paramsPade, verbosity = verb, + homogeneize = homog) approxRB = LRB(solver, mu0 = np.mean([kLeft, kRight]), - approxParameters = paramsRB) + approxParameters = paramsRB, verbosity = verb, + homogeneize = homog) sweeper = Sweeper(mutars = ktars, mostExpensive = 'Approx') + homogMSG = "" + if not homog: homogMSG = "Non" + filenamebase = '../data/output/airfoil' + test[:-5] + homogMSG + "Homog" + sweeper.ROMEngine = approxPade sweeper.params = paramsSetsPade - filenamePade = sweeper.sweep('../data/output/airfoil'+test[:-5]+'Pade.dat') + filenamePade = sweeper.sweep(filenamebase +'Pade.dat') sweeper.ROMEngine = approxRB sweeper.params = paramsSetsRB - filenameRB = sweeper.sweep('../data/output/airfoil'+test[:-5]+'RB.dat') + filenameRB = sweeper.sweep(filenamebase +'RB.dat') if test == "TaylorSweep": constr = ['E'] else: constr = ['S'] - sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], constr, - onePlot = True) - sweeper.plot(filenameRB, ['muRe'], ['normHF', 'normApp'], constr, - onePlot = True) - sweeper.plot(filenamePade, ['muRe'], ['normErr'], constr) - sweeper.plot(filenameRB, ['muRe'], ['normErr'], constr) + sweeper.plotCompare([filenamePade, filenameRB], ['muRe'], + ['normHF', 'normApp'], constr, onePlot = True, + save = filenamebase + 'Norm', + saveFormat = "png", labels = ["Pade'", "RB"]) + sweeper.plotCompare([filenamePade, filenameRB], ['muRe'], ['normResRel'], + constr, save = filenamebase + 'Res', + saveFormat = "png", labels = ["Pade'", "RB"]) + sweeper.plotCompare([filenamePade, filenameRB], ['muRe'], ['normErrRel'], + constr, save = filenamebase + 'Err', + saveFormat = "png", labels = ["Pade'", "RB"]) + diff --git a/examples/scipy/domainCrack.py b/examples/scipy/domainCrack.py deleted file mode 100644 index a3ae673..0000000 --- a/examples/scipy/domainCrack.py +++ /dev/null @@ -1,109 +0,0 @@ -import fenics as fen -import numpy as np -import sympy as sp -from rrompy.hfengines.scipy import HelmholtzProblemEngine as HPE -from rrompy.reduction_methods.taylor import ApproximantTaylorPade as TP -from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper - -test = "poles" -#test = "error" -#test = "norm" - -k0 = 10 -ktars = np.power([80, 95, 98], .5) -ktarsNorm = np.linspace(78**.5, 122**.5, 250) - -def boundaryNeumann(x, on_boundary): - return on_boundary and x[1] > .25 and x[0] > 0.995 and x[0] < 1.005 - -x0, y0 = .5, .5 -Rr, Ri = .1, .1 -x, y = sp.symbols('x[0] x[1]', real=True) -fex = sp.exp(- ((x - x0)**2 + (y - y0)**2) / 2 / Rr**2) - -meshname = '../data/mesh/crack_coarse.xml' -#meshname = '../data/mesh/crack_fine.xml' -mesh = fen.Mesh(meshname) -forcingTerm = fen.Expression(sp.printing.ccode(sp.simplify(fex)), degree = 3) - -solver = HPE() -solver.omega = np.real(k0) -solver.V = fen.FunctionSpace(mesh, "P", 3) -solver.forcingTerm = forcingTerm -solver.NeumannBoundary = boundaryNeumann -solver.DirichletBoundary = 'rest' - -if test == "poles": - appPoles = {} - Emax = 13 - params = {'N':6, 'M':0, 'E':6, 'Emax':Emax, 'sampleType':'Arnoldi', - 'POD':True} - - approxPade = TP(solver, mu0 = k0, approxParameters = params) - for E in range(6, Emax + 1): - approxPade.E = E - appPoles[E] = np.sort(approxPade.getPoles()) - - a = fen.dot(fen.grad(solver.u), fen.grad(solver.v)) * fen.dx - A = fen.assemble(a) - fen.DirichletBC(solver.V, fen.Constant(0.), - solver.DirichletBoundary).apply(A) - AMat = fen.as_backend_type(A).mat() - Ar, Ac, Av = AMat.getValuesCSR() - import scipy.sparse as scsp - A = scsp.csr_matrix((Av, Ac, Ar), shape = AMat.size) - - m = fen.dot(solver.u, solver.v) * fen.dx - M = fen.assemble(m) - fen.DirichletBC(solver.V, fen.Constant(0.), - solver.DirichletBoundary).apply(M) - MMat = fen.as_backend_type(M).mat() - Mr, Mc, Mv = MMat.getValuesCSR() - import scipy.sparse as scsp - M = scsp.csr_matrix((Mv, Mc, Mr), shape = MMat.size) - - poles = scsp.linalg.eigs(A, k = 7, M = M, sigma = 100., - return_eigenvectors = False) - II = np.argsort(np.abs(poles - k0)) - poles = poles[II] - print('Exact', end = ': ') - [print('{},{}'.format(np.real(x), np.imag(x)), end = ',') for x in poles] - print() - - for E in range(6, Emax + 1): - print(E, end = ': ') - [print('{},{}'.format(np.real(x), np.imag(x)), end = ',')\ - for x in np.sort(appPoles[E])] - print() - -elif test == "error": - M0 = 5 - Emax = 13 - params = {'Emax':Emax, 'sampleType':'Arnoldi', 'POD':True} - paramsSetsPade = [None] * (Emax - M0 + 1) - for M in range(M0, Emax + 1): - paramsSetsPade[M - M0] = {'N':M0 + 1, 'M':M, 'E':max(M, M0 + 1)} - approxPade = TP(solver, mu0 = k0, approxParameters = params) - - sweeper = Sweeper(mutars = ktars, mostExpensive = 'Approx') - sweeper.ROMEngine = approxPade - sweeper.params = paramsSetsPade - filenamePade = sweeper.sweep('../data/output/membrane_error.dat', - outputs = 'ALL') - sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['E'], - onePlot = True) - sweeper.plot(filenamePade, ['muRe'], ['normErr'], ['E']) - -elif test == "norm": - params = [{'N':6, 'M':10, 'E':10, 'sampleType':'Arnoldi', 'POD':True}] - approxPade = TP(solver, mu0 = k0, approxParameters = params[0]) - - sweeper = Sweeper(mutars = ktarsNorm, mostExpensive = 'Approx') - sweeper.ROMEngine = approxPade - sweeper.params = params - filenamePade = sweeper.sweep('../data/output/membrane_norm.dat', - outputs = ["normHF", "normApp", "normErr"]) - sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['E'], - onePlot = True) - sweeper.plot(filenamePade, ['muRe'], ['normErr'], ['E']) - diff --git a/examples/scipy/laplaceGaussianTaylor.py b/examples/scipy/laplaceGaussianTaylor.py new file mode 100644 index 0000000..bd16914 --- /dev/null +++ b/examples/scipy/laplaceGaussianTaylor.py @@ -0,0 +1,100 @@ +import numpy as np +from rrompy.hfengines.scipy import LaplaceDiskGaussian as LDG +from rrompy.reduction_methods.taylor import ApproximantTaylorPade as Pade +from rrompy.reduction_methods.taylor import ApproximantTaylorRB as RB +from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper +from operator import itemgetter +def subdict(d, ks): + return dict(zip(ks, itemgetter(*ks)(d))) + +testNo = 3 +verb = 0 + +if testNo == 1: + mu = 4. + solver = LDG(n = 40, verbosity = verb) + + uh = solver.solve(mu) + solver.plotmesh() + print(solver.norm(uh)) + solver.plot(uh) + +############ +if testNo == 2: + params = {'N':8, 'M':8, 'E':8, 'sampleType':'Arnoldi', 'POD':True} +# params = {'N':8, 'M':8, 'E':8, 'sampleType':'Krylov', 'POD':True} + mu0 = 0. + solver = LDG(n = 20, degree_threshold = 15, verbosity = verb) + approxP = Pade(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) + paramsRB = subdict(params, ['E', 'sampleType', 'POD']) + approxR = RB(solver, mu0 = mu0, approxParameters = paramsRB, + verbosity = verb) + + approxP.setupApprox() + approxR.setupApprox() +# approxP.plotSamples() + + mutar = 3.25 + approxP.plotHF(mutar, name = 'u_HF') + approxP.plotApp(mutar, name = 'u_Pade''') + approxR.plotApp(mutar, name = 'u_RB') + approxP.plotErr(mutar, name = 'err_Pade''') + approxR.plotErr(mutar, name = 'err_RB') + + solNorm = approxP.normHF(mutar) + appPErr = approxP.normErr(mutar) + appRErr = approxR.normErr(mutar) + print(('SolNorm:\t{}\nErrRelP:\t{}\nErrRelR:\t{}').format(solNorm, + appPErr / solNorm, appRErr / solNorm)) + +############ +elif testNo == 3: + mu0 = 0. + mutars = np.linspace(0., 4., 60) + solver = LDG(n = 10, degree_threshold = 15, verbosity = verb) + shift = 2 + nsets = 6 + stride = 2 + Emax = stride * (nsets - 1) + shift + params = {'Emax':Emax, 'sampleType':'Arnoldi', 'POD':True} + params = {'Emax':Emax, 'sampleType':'Krylov', 'POD':False} + paramsSetsPade = [None] * nsets + paramsSetsRB = [None] * nsets + paramsSetsPoly = [None] * nsets + for i in range(nsets): + paramsSetsPade[i] = {'N':stride*i+shift, 'M':stride*i+shift, + 'E':stride*i+shift} + paramsSetsRB[i] = {'E':stride*i+shift} + paramsSetsPoly[i] = {'N':0, 'M':stride*i+shift, + 'E':stride*i+shift} + approxPade = Pade(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) + approxRB = RB(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) + approxPoly = Pade(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) + + filenamebase = '../data/output/LapGTaylor' + + sweeper = Sweeper(mutars = mutars, mostExpensive = 'Approx') + sweeper.ROMEngine = approxPade + sweeper.params = paramsSetsPade + filenamePade = sweeper.sweep(filenamebase + 'Pade.dat') + sweeper.ROMEngine = approxRB + sweeper.params = paramsSetsRB + filenameRB = sweeper.sweep(filenamebase + 'RB.dat') + sweeper.ROMEngine = approxPoly + sweeper.params = paramsSetsPoly + filenamePoly = sweeper.sweep(filenamebase + 'Poly.dat') + +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normHF', 'normApp'], ['E'], onePlot = True, + save = filenamebase + 'Norm', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normResRel'], ['E'], save = filenamebase + 'Res', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normErrRel'], ['E'], save = filenamebase + 'Err', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) diff --git a/examples/scipy/membraneTaylor.py b/examples/scipy/membraneTaylor.py index a3ae673..dee9594 100644 --- a/examples/scipy/membraneTaylor.py +++ b/examples/scipy/membraneTaylor.py @@ -1,109 +1,96 @@ import fenics as fen import numpy as np -import sympy as sp -from rrompy.hfengines.scipy import HelmholtzProblemEngine as HPE +from rrompy.hfengines.base import HelmholtzProblemEngine as HPE from rrompy.reduction_methods.taylor import ApproximantTaylorPade as TP from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper +verb = 0 + test = "poles" -#test = "error" -#test = "norm" +test = "error" k0 = 10 -ktars = np.power([80, 95, 98], .5) -ktarsNorm = np.linspace(78**.5, 122**.5, 250) +ktars = np.linspace(78**.5, 122**.5, 50) def boundaryNeumann(x, on_boundary): return on_boundary and x[1] > .25 and x[0] > 0.995 and x[0] < 1.005 -x0, y0 = .5, .5 -Rr, Ri = .1, .1 -x, y = sp.symbols('x[0] x[1]', real=True) -fex = sp.exp(- ((x - x0)**2 + (y - y0)**2) / 2 / Rr**2) - meshname = '../data/mesh/crack_coarse.xml' #meshname = '../data/mesh/crack_fine.xml' mesh = fen.Mesh(meshname) -forcingTerm = fen.Expression(sp.printing.ccode(sp.simplify(fex)), degree = 3) -solver = HPE() +x, y = fen.SpatialCoordinate(mesh)[:] +x0, y0 = .5, .5 +Rr, Ri = .1, .1 +forcingTerm = fen.exp(- ((x - x0)**2 + (y - y0)**2) / 2 / Rr**2) + +solver = HPE(verbosity = verb) solver.omega = np.real(k0) solver.V = fen.FunctionSpace(mesh, "P", 3) solver.forcingTerm = forcingTerm solver.NeumannBoundary = boundaryNeumann solver.DirichletBoundary = 'rest' if test == "poles": appPoles = {} Emax = 13 params = {'N':6, 'M':0, 'E':6, 'Emax':Emax, 'sampleType':'Arnoldi', 'POD':True} - approxPade = TP(solver, mu0 = k0, approxParameters = params) + approxPade = TP(solver, mu0 = k0, approxParameters = params, + verbosity = verb) for E in range(6, Emax + 1): approxPade.E = E appPoles[E] = np.sort(approxPade.getPoles()) a = fen.dot(fen.grad(solver.u), fen.grad(solver.v)) * fen.dx A = fen.assemble(a) fen.DirichletBC(solver.V, fen.Constant(0.), solver.DirichletBoundary).apply(A) AMat = fen.as_backend_type(A).mat() Ar, Ac, Av = AMat.getValuesCSR() import scipy.sparse as scsp A = scsp.csr_matrix((Av, Ac, Ar), shape = AMat.size) m = fen.dot(solver.u, solver.v) * fen.dx M = fen.assemble(m) fen.DirichletBC(solver.V, fen.Constant(0.), solver.DirichletBoundary).apply(M) MMat = fen.as_backend_type(M).mat() Mr, Mc, Mv = MMat.getValuesCSR() import scipy.sparse as scsp M = scsp.csr_matrix((Mv, Mc, Mr), shape = MMat.size) poles = scsp.linalg.eigs(A, k = 7, M = M, sigma = 100., return_eigenvectors = False) II = np.argsort(np.abs(poles - k0)) poles = poles[II] print('Exact', end = ': ') [print('{},{}'.format(np.real(x), np.imag(x)), end = ',') for x in poles] print() for E in range(6, Emax + 1): print(E, end = ': ') [print('{},{}'.format(np.real(x), np.imag(x)), end = ',')\ for x in np.sort(appPoles[E])] print() elif test == "error": M0 = 5 - Emax = 13 + Emax = 8 params = {'Emax':Emax, 'sampleType':'Arnoldi', 'POD':True} paramsSetsPade = [None] * (Emax - M0 + 1) for M in range(M0, Emax + 1): paramsSetsPade[M - M0] = {'N':M0 + 1, 'M':M, 'E':max(M, M0 + 1)} - approxPade = TP(solver, mu0 = k0, approxParameters = params) + approxPade = TP(solver, mu0 = k0, approxParameters = params, + verbosity = verb) sweeper = Sweeper(mutars = ktars, mostExpensive = 'Approx') sweeper.ROMEngine = approxPade sweeper.params = paramsSetsPade filenamePade = sweeper.sweep('../data/output/membrane_error.dat', outputs = 'ALL') - sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['E'], - onePlot = True) - sweeper.plot(filenamePade, ['muRe'], ['normErr'], ['E']) - -elif test == "norm": - params = [{'N':6, 'M':10, 'E':10, 'sampleType':'Arnoldi', 'POD':True}] - approxPade = TP(solver, mu0 = k0, approxParameters = params[0]) - - sweeper = Sweeper(mutars = ktarsNorm, mostExpensive = 'Approx') - sweeper.ROMEngine = approxPade - sweeper.params = params - filenamePade = sweeper.sweep('../data/output/membrane_norm.dat', - outputs = ["normHF", "normApp", "normErr"]) - sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['E'], + sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['M'], onePlot = True) - sweeper.plot(filenamePade, ['muRe'], ['normErr'], ['E']) + sweeper.plot(filenamePade, ['muRe'], ['normErr'], ['M']) diff --git a/examples/scipy/parametricDomain.py b/examples/scipy/parametricDomain.py index 6c953c5..f5c5dc4 100644 --- a/examples/scipy/parametricDomain.py +++ b/examples/scipy/parametricDomain.py @@ -1,89 +1,103 @@ import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleDomainProblemEngine as HSBDPE from rrompy.reduction_methods.taylor import ApproximantTaylorPade as Pade from rrompy.reduction_methods.taylor import ApproximantTaylorRB as RB from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper from operator import itemgetter def subdict(d, ks): return dict(zip(ks, itemgetter(*ks)(d))) testNo = 3 +verb = 0 if testNo == 1: mu = 7 ** .5 - solver = HSBDPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40) + solver = HSBDPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40, mu0 = mu, + degree_threshold = 15, verbosity = verb) uh = solver.solve(mu) solver.plotmesh() print(solver.norm(uh)) solver.plot(uh) ############ if testNo == 2: - params = {'N':7, 'M':8, 'E':8, 'sampleType':'Arnoldi', 'POD':True} + params = {'N':8, 'M':8, 'E':8, 'sampleType':'Arnoldi', 'POD':True} # params = {'N':7, 'M':8, 'E':8, 'sampleType':'Krylov', 'POD':True} mu0 = 7 ** .5 - mutar = (7.5 + .5j) ** .5 - solver = HSBDPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40) - approxP = Pade(solver, mu0 = mu0, approxParameters = params) + mutar = (7. + .1j) ** .5 + solver = HSBDPE(kappa = 12 ** .5, theta = np.pi / 3, n = 40, mu0 = mu0, + degree_threshold = 15, verbosity = verb) + approxP = Pade(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) paramsRB = subdict(params, ['E', 'sampleType', 'POD']) - approxR = RB(solver, mu0 = mu0, approxParameters = paramsRB) + approxR = RB(solver, mu0 = mu0, approxParameters = paramsRB, + verbosity = verb) approxP.setupApprox() approxR.setupApprox() # approxP.plotSamples() approxP.plotHF(mutar, name = 'u_HF') approxP.plotApp(mutar, name = 'u_Pade''') approxR.plotApp(mutar, name = 'u_RB') approxP.plotErr(mutar, name = 'err_Pade''') approxR.plotErr(mutar, name = 'err_RB') solNorm = approxP.normHF(mutar) appPErr = approxP.normErr(mutar) appRErr = approxR.normErr(mutar) print(('SolNorm:\t{}\nErrRelP:\t{}\nErrRelR:\t{}').format(solNorm, appPErr / solNorm, appRErr / solNorm)) print('\nPoles Pade'':') print(approxP.getPoles()) - print('\nPoles RB:') - print(approxR.getPoles()) ############ elif testNo == 3: mu0 = 14 ** .5 - mutars = np.linspace(9**.5, 19**.5, 250) -# mutars = np.linspace(9**.5, 19**.5, 20) - solver = HSBDPE(kappa = 12 ** .5, theta = np.pi / 3, n = 20) + mutars = np.linspace(9**.5, 19**.5, 100) + solver = HSBDPE(kappa = 12 ** .5, theta = np.pi / 3, n = 20, mu0 = mu0, + degree_threshold = 15, verbosity = verb) shift = 10 nsets = 1 stride = 2 Emax = stride * (nsets - 1) + shift params = {'Emax':Emax, 'sampleType':'Arnoldi', 'POD':True} paramsSetsPade = [None] * nsets paramsSetsRB = [None] * nsets + paramsSetsPoly = [None] * nsets for i in range(nsets): - paramsSetsPade[i] = {'N':stride*i+shift, 'M':stride*i+shift, 'E':stride*i+shift} + paramsSetsPade[i] = {'N':stride*i+shift, 'M':stride*i+shift, + 'E':stride*i+shift} paramsSetsRB[i] = {'E':stride*i+shift} - approxPade = Pade(solver, mu0 = mu0, approxParameters = params) - approxRB = RB(solver, mu0 = mu0, approxParameters = params) + paramsSetsPoly[i] = {'N':0, 'M':stride*i+shift, + 'E':stride*i+shift} + approxPade = Pade(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) + approxRB = RB(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) + approxPoly = Pade(solver, mu0 = mu0, approxParameters = params, + verbosity = verb) + + filenamebase = '../data/output/domainTaylor' sweeper = Sweeper(mutars = mutars, mostExpensive = 'Approx') sweeper.ROMEngine = approxPade sweeper.params = paramsSetsPade - filenamePade = sweeper.sweep('../data/output/domain_errorPade.dat', - outputs = 'ALL') + filenamePade = sweeper.sweep(filenamebase + 'Pade.dat') sweeper.ROMEngine = approxRB sweeper.params = paramsSetsRB - filenameRB = sweeper.sweep('../data/output/domain_errorRB.dat', - outputs = 'ALL') + filenameRB = sweeper.sweep(filenamebase + 'RB.dat') + sweeper.ROMEngine = approxPoly + sweeper.params = paramsSetsPoly + filenamePoly = sweeper.sweep(filenamebase + 'Poly.dat') - print('Pade''') - sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], ['E'], - onePlot = True) - print('RB') - sweeper.plot(filenameRB, ['muRe'], ['normHF', 'normApp'], ['E'], - onePlot = True) - print('Pade''') - sweeper.plot(filenamePade, ['muRe'], ['normErrRel'], ['E']) - print('RB') - sweeper.plot(filenameRB, ['muRe'], ['normErrRel'], ['E']) + sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normHF', 'normApp'], ['E'], onePlot = True, + save = filenamebase + 'Norm', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) + sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normResRel'], ['E'], save = filenamebase + 'Res', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) + sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normErrRel'], ['E'], save = filenamebase + 'Err', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) diff --git a/examples/scipy/scatteringSquare.py b/examples/scipy/scatteringSquare.py index d4b00e8..ffb92c7 100644 --- a/examples/scipy/scatteringSquare.py +++ b/examples/scipy/scatteringSquare.py @@ -1,140 +1,166 @@ from copy import copy import numpy as np -from rrompy.hfengines.scipy import HelmholtzCavityScatteringProblemEngine as CSPE +from rrompy.hfengines.scipy import ( + HelmholtzCavityScatteringProblemEngine as CSPE) from rrompy.reduction_methods.taylor import ApproximantTaylorPade as TP from rrompy.reduction_methods.lagrange import ApproximantLagrangePade as LP from rrompy.reduction_methods.taylor import ApproximantTaylorRB as TRB from rrompy.reduction_methods.lagrange import ApproximantLagrangeRB as LRB from rrompy.utilities.parameter_sweeper import ParameterSweeper as Sweeper from rrompy.utilities.parameter_sampling import QuadratureSampler as QS from operator import itemgetter def subdict(d, ks): return dict(zip(ks, itemgetter(*ks)(d))) +verb = 0 + #################### test = "solve" test = "Taylor" test = "Lagrange" -#test = "TaylorSweep" +test = "TaylorSweep" #test = "LagrangeSweep" plotSamples = True k0 = 10 kLeft, kRight = 9, 11 ktar = 9.5 ktars = np.linspace(8.5, 11.5, 125) #ktars = np.array([k0]) kappa = 5 n = 50 -solver = CSPE(kappa = kappa, n = n) +solver = CSPE(kappa = kappa, n = n, verbosity = verb) solver.omega = k0 if test == "solve": uh = solver.solve(k0) print(solver.norm(uh)) solver.plot(uh, what = ['ABS', 'REAL']) elif test in ["Taylor", "Lagrange"]: if test == "Taylor": params = {'N':8, 'M':7, 'R':8, 'E':8, 'sampleType':'Krylov', 'POD':True} params = {'N':8, 'M':7, 'R':8, 'E':8, 'sampleType':'Arnoldi', 'POD':True} parPade = subdict(params, ['N', 'M', 'E', 'sampleType', 'POD']) parRB = subdict(params, ['R', 'E', 'sampleType', 'POD']) - approxPade = TP(solver, mu0 = k0, approxParameters = parPade) - approxRB = TRB(solver, mu0 = k0, approxParameters = parRB) + approxPade = TP(solver, mu0 = k0, approxParameters = parPade, + verbosity = verb) + approxRB = TRB(solver, mu0 = k0, approxParameters = parRB, + verbosity = verb) else: params = {'N':8, 'M':8, 'R':9, 'S':9, 'POD':True, 'sampler':QS([kLeft, kRight], "CHEBYSHEV")} parPade = subdict(params, ['N', 'M', 'S', 'POD', 'sampler']) parRB = subdict(params, ['R', 'S', 'POD', 'sampler']) approxPade = LP(solver, mu0 = np.mean([kLeft, kRight]), - approxParameters = parPade) + approxParameters = parPade, + verbosity = verb) approxRB = LRB(solver, mu0 = np.mean([kLeft, kRight]), - approxParameters = parRB) + approxParameters = parRB, + verbosity = verb) approxPade.setupApprox() approxRB.setupApprox() if plotSamples: approxPade.plotSamples() PadeErr, solNorm = approxPade.normErr(ktar), approxPade.normHF(ktar) RBErr = approxRB.normErr(ktar) print(('SolNorm:\t{}\nErrPade:\t{}\nErrRelPade:\t{}\nErrRB:\t\t{}' '\nErrRelRB:\t{}').format(solNorm, PadeErr, np.divide(PadeErr, solNorm), RBErr, np.divide(RBErr, solNorm))) print('\nPoles Pade'':') print(approxPade.getPoles()) print('\nPoles RB:') print(approxRB.getPoles()) approxPade.plotHF(ktar, name = 'u_ex') approxPade.plotApp(ktar, name = 'u_Pade''') approxRB.plotApp(ktar, name = 'u_RB') approxPade.plotErr(ktar, name = 'errPade''') approxRB.plotErr(ktar, name = 'errRB') elif test in ["TaylorSweep", "LagrangeSweep"]: if test == "TaylorSweep": shift = 5 nsets = 4 stride = 3 Emax = stride * (nsets - 1) + shift + 1 params = {'Emax':Emax, 'sampleType':'Krylov', 'POD':True} params = {'Emax':Emax, 'sampleType':'Arnoldi', 'POD':True} paramsSetsPade = [None] * nsets paramsSetsRB = [None] * nsets for i in range(nsets): paramsSetsPade[i] = {'N': stride*i+shift+1, 'M': stride*i+shift,#+1, 'E': stride*i+shift+1} paramsSetsRB[i] = {'R': stride*i+shift+1,#+1, 'E': stride*i+shift+1} - approxPade = TP(solver, mu0 = k0,approxParameters = params) - approxRB = TRB(solver, mu0 = k0, approxParameters = params) + approxPade = TP(solver, mu0 = k0,approxParameters = params, + verbosity = verb) + approxRB = TRB(solver, mu0 = k0, approxParameters = params, + verbosity = verb) else: shift = 7 nsets = 4 stride = 3 Smax = stride * (nsets - 1) + shift + 2 paramsPade = {'S':Smax, 'POD':True, 'sampler':QS([kLeft, kRight], "CHEBYSHEV")} paramsRB = copy(paramsPade) + paramsPoly = copy(paramsPade) paramsSetsPade = [None] * nsets paramsSetsRB = [None] * nsets + paramsSetsPoly = [None] * nsets for i in range(nsets): paramsSetsPade[i] = {'N': stride*i+shift+1, - 'M': stride*i+shift,#+1, + 'M': stride*i+shift+1, 'S': stride*i+shift+2} - paramsSetsRB[i] = {'R': stride*i+shift+1,#+1, + paramsSetsRB[i] = {'R': stride*i+shift+2, 'S': stride*i+shift+2} + paramsSetsPoly[i] = {'N': 0, + 'M': stride*i+shift+1, + 'S': stride*i+shift+2} approxPade = LP(solver, mu0 = np.mean([kLeft, kRight]), - approxParameters = paramsPade) + approxParameters = paramsPade, + verbosity = verb) approxRB = LRB(solver, mu0 = np.mean([kLeft, kRight]), - approxParameters = paramsRB) + approxParameters = paramsRB, + verbosity = verb) + approxPoly = LP(solver, mu0 = np.mean([kLeft, kRight]), + approxParameters = paramsPoly, + verbosity = verb) + + filenamebase = '../data/output/scatSquare' + test[:-5] sweeper = Sweeper(mutars = ktars, mostExpensive = 'Approx') - sweeper.ROMEngine = approxPade sweeper.params = paramsSetsPade - filenamePade = sweeper.sweep('../data/output/ssquare'+test[:-5]+'Pade.dat') - + filenamePade = sweeper.sweep(filenamebase + 'Pade.dat') sweeper.ROMEngine = approxRB sweeper.params = paramsSetsRB - filenameRB = sweeper.sweep('../data/output/ssquare'+test[:-5]+'RB.dat') + filenameRB = sweeper.sweep(filenamebase + 'RB.dat') + sweeper.ROMEngine = approxPoly + sweeper.params = paramsSetsPoly + filenamePoly = sweeper.sweep(filenamebase + 'Poly.dat') if test == "TaylorSweep": constr = ['E'] else: constr = ['S'] - sweeper.plot(filenamePade, ['muRe'], ['normHF', 'normApp'], constr, - onePlot = True) - sweeper.plot(filenameRB, ['muRe'], ['normHF', 'normApp'], constr, - onePlot = True) - sweeper.plot(filenamePade, ['muRe'], ['normApp'], constr) - sweeper.plot(filenameRB, ['muRe'], ['normApp'], constr) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normHF', 'normApp'], constr, onePlot = True, + save = filenamebase + 'Norm', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normResRel'], constr, save = filenamebase + 'Res', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) +sweeper.plotCompare([filenamePade, filenameRB, filenamePoly], ['muRe'], + ['normErrRel'], constr, save = filenamebase + 'Err', + saveFormat = "png", labels = ["Pade'", "RB", "Poly"]) diff --git a/examples/scipy/solver.py b/examples/scipy/solver.py index f71387f..b34ceae 100644 --- a/examples/scipy/solver.py +++ b/examples/scipy/solver.py @@ -1,38 +1,72 @@ import numpy as np from rrompy.hfengines.scipy import HelmholtzSquareBubbleProblemEngine as HSBPE -from rrompy.hfengines.scipy import HelmholtzSquareTransmissionProblemEngine as HSTPE +from rrompy.hfengines.scipy import ( + HelmholtzSquareTransmissionProblemEngine as HSTPE) from rrompy.hfengines.scipy import HelmholtzBoxScatteringProblemEngine as HBSPE +from rrompy.hfengines.scipy import ( + HelmholtzCavityScatteringProblemEngine as HCSPE) testNo = 3 +verb = 0 if testNo == 1: - solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 20) - + solver = HSBPE(kappa = 12 ** .5, theta = np.pi / 3, n = 20, + verbosity = verb) + mu = 12.**.5 uh = solver.solve(mu) - solver.plotmesh(save = False) + solver.plotmesh() print(solver.norm(uh)) - solver.plot(uh, save = False) - solver.plot(solver.residual(uh, mu)) + solver.plot(uh) + solver.plot(solver.residual(uh, mu), 'res') ########### -elif testNo == 2: - solver = HSTPE(nT = 1, nB = 2, theta = np.pi * 20 / 180, - kappa = 4., n = 50) +elif testNo == [2, -2]: + solver = HSTPE(nT = 1, nB = 2, theta = np.pi * 20 / 180, kappa = 4., + n = 50, verbosity = verb) mu = 4. - uh = solver.solve(mu) + uref = solver.liftDirichletData(mu) + if testNo > 0: + uh = solver.solve(mu) + utot = uh - uref + else: + utot = solver.solve(mu, homogeneized = True) + uh = utot + uref + print(solver.norm(uh)) + print(solver.norm(uref)) + solver.plot(uh) + solver.plot(uref, name = 'u_Dir') + solver.plot(utot, name = 'u_tot') + solver.plot(solver.residual(uh, mu), 'res') + solver.plot(solver.residual(utot, mu, homogeneized = True), 'res_tot') + +########### +elif testNo in [3, -3]: + solver = HBSPE(R = 5, kappa = 12**.5, theta = - np.pi * 60 / 180, n = 30, + verbosity = verb) + mu = 12**.5 + uref = solver.liftDirichletData(mu) + if testNo > 0: + uh = solver.solve(mu) + utot = uh - uref + else: + utot = solver.solve(mu, homogeneized = True) + uh = utot + uref + solver.plotmesh() print(solver.norm(uh)) + print(solver.norm(utot)) solver.plot(uh) - solver.plot(solver.residual(uh, mu)) + solver.plot(utot, name = 'u_tot') + solver.plot(solver.residual(uh, mu), 'res') + solver.plot(solver.residual(utot, mu, homogeneized = True), 'res_tot') ########### -elif testNo == 3: - solver = HBSPE(R = 5, kappa = 12**.5, theta = - np.pi * 60 / 180, n = 30) - uinc = - solver.liftDirichletData() - uh = solver.solve(12**.5) +elif testNo == 4: + solver = HCSPE(kappa = 5, n = 30, verbosity = verb) + mu = 10 + uh = solver.solve(mu) solver.plotmesh() print(solver.norm(uh)) - print(solver.norm(uh + uinc)) solver.plot(uh) - solver.plot(uh + uinc, name = 'u_tot') + solver.plot(solver.residual(uh, mu), 'res') diff --git a/rrompy/hfengines/base/__init__.py b/rrompy/hfengines/base/__init__.py index febe1c9..de93c79 100644 --- a/rrompy/hfengines/base/__init__.py +++ b/rrompy/hfengines/base/__init__.py @@ -1,28 +1,34 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.hfengines.base.problem_engine_base import ProblemEngineBase -from rrompy.hfengines.base.boundary_conditions import BoundaryConditions +from .problem_engine_base import ProblemEngineBase +from .boundary_conditions import BoundaryConditions +from .laplace_base_problem_engine import LaplaceBaseProblemEngine +from .helmholtz_problem_engine import HelmholtzProblemEngine +from .scattering_problem_engine import ScatteringProblemEngine __all__ = [ 'ProblemEngineBase', - 'BoundaryConditions' + 'BoundaryConditions', + 'LaplaceBaseProblemEngine', + 'HelmholtzProblemEngine', + 'ScatteringProblemEngine' ] diff --git a/rrompy/hfengines/base/boundary_conditions.py b/rrompy/hfengines/base/boundary_conditions.py index fb35b41..48bebb9 100644 --- a/rrompy/hfengines/base/boundary_conditions.py +++ b/rrompy/hfengines/base/boundary_conditions.py @@ -1,106 +1,110 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from rrompy.utilities.base.types import GenExpr from rrompy.utilities.base.fenics import bdrTrue, bdrFalse __all__ = ['BoundaryConditions'] class BoundaryConditions: """Boundary conditions manager.""" allowedKinds = ["Dirichlet", "Neumann", "Robin"] def __init__(self, kind : str = None): if kind is None: return kind = kind[0].upper() + kind[1:].lower() if kind in self.allowedKinds: getattr(self.__class__, kind + "Boundary", None).fset(self, "ALL") else: raise Exception("BC kind not recognized.") def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() def _generalManagement(self, kind:str, value:GenExpr): if isinstance(value, (str,)): value = value.upper() if value.upper() == "ALL": self._complementaryManagementAll(kind) elif value.upper() == "REST": self._complementaryManagementRest(kind) else: raise Exception("Wildcard not recognized.") elif callable(value): self._standardManagement(kind, value) else: raise Exception(kind + "Boundary type not recognized.") def _complementaryManagementAll(self, kind:str): if kind not in self.allowedKinds: raise Exception("BC kind not recognized.") for k in self.allowedKinds: if k != kind: getattr(self.__class__, k + "Boundary").fset(self, bdrFalse) super().__setattr__("_" + kind + "Boundary", bdrTrue) if hasattr(self, "_" + kind + "Rest"): super().__delattr__("_" + kind + "Rest") def _complementaryManagementRest(self, kind:str): if kind not in self.allowedKinds: raise Exception("BC kind not recognized.") + otherBCs = [] for k in self.allowedKinds: - if k != kind and hasattr(self, "_" + k + "Rest"): - raise Exception("Only 1 'REST' wildcard can be specified.") + if k != kind: + if hasattr(self, "_" + k + "Rest"): + raise Exception("Only 1 'REST' wildcard can be specified.") + otherBCs += [getattr(self, k + "Boundary")] def restCall(x, on_boundary): return (on_boundary - and not any([getattr(self, k + "Boundary")(x, on_boundary)])) + and not any([bc(x, on_boundary) for bc in otherBCs])) super().__setattr__("_" + kind + "Boundary", restCall) super().__setattr__("_" + kind + "Rest", 1) def _standardManagement(self, kind:str, bc:callable): super().__setattr__("_" + kind + "Boundary", bc) if hasattr(self, "_" + kind + "Rest"): super().__delattr__("_" + kind + "Rest") @property def DirichletBoundary(self): """Function handle to DirichletBoundary.""" return self._DirichletBoundary @DirichletBoundary.setter def DirichletBoundary(self, DirichletBoundary): self._generalManagement("Dirichlet", DirichletBoundary) @property def NeumannBoundary(self): """Function handle to NeumannBoundary.""" return self._NeumannBoundary @NeumannBoundary.setter def NeumannBoundary(self, NeumannBoundary): self._generalManagement("Neumann", NeumannBoundary) @property def RobinBoundary(self): """Function handle to RobinBoundary.""" return self._RobinBoundary @RobinBoundary.setter def RobinBoundary(self, RobinBoundary): self._generalManagement("Robin", RobinBoundary) + diff --git a/rrompy/hfengines/scipy/helmholtz_problem_engine.py b/rrompy/hfengines/base/helmholtz_problem_engine.py similarity index 54% rename from rrompy/hfengines/scipy/helmholtz_problem_engine.py rename to rrompy/hfengines/base/helmholtz_problem_engine.py index e3a0b12..33a0b8d 100644 --- a/rrompy/hfengines/scipy/helmholtz_problem_engine.py +++ b/rrompy/hfengines/base/helmholtz_problem_engine.py @@ -1,149 +1,154 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # import numpy as np import scipy.sparse as scsp import fenics as fen -from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List -from rrompy.hfengines.scipy.helmholtz_base_problem_engine import HelmholtzBaseProblemEngine, fenZERO +from .laplace_base_problem_engine import LaplaceBaseProblemEngine +from rrompy.utilities.base.types import Np1D, ScOp +from rrompy.utilities.base.fenics import fenZERO, fenONE +from rrompy.utilities.base import verbosityDepth __all__ = ['HelmholtzProblemEngine'] -class HelmholtzProblemEngine(HelmholtzBaseProblemEngine): +class HelmholtzProblemEngine(LaplaceBaseProblemEngine): """ - Solver for Helmholtz problems with parametric wavenumber. + 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: omega: Value of omega. diffusivity: Value of a. refractionIndex: Value of n. forcingTerm: Value of f. DirichletDatum: Value of u0. NeumannDatum: Value of g1. - RobinDatumH: Value of h. 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. V: Real FE space. u: Generic trial functions for variational form evaluation. v: Generic test functions for variational form evaluation. 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. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. """ - _RobinDatumH = [fenZERO, fenZERO] - def __init__(self): - super().__init__() - self.V = fen.FunctionSpace(fen.UnitSquareMesh(10, 10), "P", 1) - self.DirichletBoundary = "ALL" + nAs = 2 - @property - def RobinDatumH(self): - """Value of h.""" - return self._RobinDatumH - @RobinDatumH.setter - def RobinDatumH(self, RobinDatumH): - if hasattr(self, "A0"): del self.A0 - if not isinstance(RobinDatumH, (list, tuple,)): - RobinDatumH = [RobinDatumH, fenZERO] - self._RobinDatumH = RobinDatumH + def __init__(self, degree_threshold : int = np.inf, verbosity : int = 10): + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) + self.omega = 1. + self.refractionIndex = fenONE + @property + def refractionIndex(self): + """Value of n.""" + return self._refractionIndex + @refractionIndex.setter + def refractionIndex(self, refractionIndex): + self.resetAs() + if not isinstance(refractionIndex, (list, tuple,)): + refractionIndex = [refractionIndex, fenZERO] + self._refractionIndex = refractionIndex + def rescaling(self, x:Np1D) -> Np1D: """Rescaling in parameter dependence.""" return np.power(x, 2.) def rescalingInv(self, x:Np1D) -> Np1D: """Inverse rescaling in parameter dependence.""" return np.power(x, .5) - def A(self, mu:complex, der : int = 0) -> Np2D: + def A(self, mu:complex, der : int = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" - if der > 1 or der < 0: - d = self.V.dim() - return scsp.csr_matrix((np.zeros(0), np.zeros(0), np.zeros(d + 1)), - shape = (d, d), dtype = np.complex) + Anull = self.checkAInBounds(der) + if Anull is not None: return Anull self.autoSetDS() - if der <= 0 and not hasattr(self, "A0"): + if der <= 0 and self.As[0] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A0.") 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) - A0Im = fen.assemble(a0Im) + 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.A0 = (scsp.csr_matrix((A0Rev, A0Rec, A0Rer), - shape = A0ReMat.size) - + 1.j * scsp.csr_matrix((A0Imv, A0Imc, A0Imr), - shape = A0ImMat.size)) - if der <= 1 and not hasattr(self, "A1"): + 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.") + if der <= 1 and self.As[1] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A1.") 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) - A1Im = fen.assemble(a1Im) + 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.A1 = (scsp.csr_matrix((A1Rev, A1Rec, A1Rer), - shape = A1ReMat.size) - + 1.j * scsp.csr_matrix((A1Imv, A1Imc, A1Imr), - shape = A1ImMat.size)) + 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.") if der == 0: - return self.A0 + mu**2 * self.A1 - return self.A1 - - def affineBlocksA(self, mu : complex = 0.) -> Tuple[List[Np2D], callable]: - """Assemble affine blocks of operator of linear system.""" - def lambdas(x, j): - if j == 0: return np.ones(np.size(x)) - if j == 1: return self.rescaling(x) - self.rescaling(mu) - raise Exception("Wrong j value.") - A0 = self.A(mu, 0) - return [A0, self.A1], lambdas + return self.As[0] + mu**2 * self.As[1] + return self.As[1] - def affineBlocksb(self, mu : complex = 0.) -> Tuple[List[Np1D], callable]: - """Assemble affine blocks of RHS of linear system.""" - def lambdas(x, j): - if j == 0: return np.ones(np.size(x)) - raise Exception("Wrong j value.") - self.b(mu, 0) - return [self.b0], lambdas diff --git a/rrompy/hfengines/base/laplace_base_problem_engine.py b/rrompy/hfengines/base/laplace_base_problem_engine.py new file mode 100644 index 0000000..6265459 --- /dev/null +++ b/rrompy/hfengines/base/laplace_base_problem_engine.py @@ -0,0 +1,315 @@ +# Copyright (C) 2018 by the RROMPy authors +# +# This file is part of RROMPy. +# +# RROMPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RROMPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with RROMPy. If not, see <http://www.gnu.org/licenses/>. +# + +import numpy as np +import scipy.sparse as scsp +import fenics as fen +from .problem_engine_base import ProblemEngineBase +from rrompy.utilities.base.types import Np1D, ScOp +from rrompy.utilities.base.fenics import fenZERO, fenONE +from rrompy.utilities.base import verbosityDepth + +__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: + 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. + V: Real FE space. + u: Generic trial functions for variational form evaluation. + v: Generic test functions for variational form evaluation. + 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. + As: Scipy sparse array representation (in CSC format) of As. + bs: Numpy array representation of bs. + """ + + def __init__(self, degree_threshold : int = np.inf, verbosity : int = 10): + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) + self.omega = 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): + self.As = [None] * self.nAs + self.bs = [None] * self.nbs + if not type(V).__name__ == 'FunctionSpace': + raise Exception("V type not recognized.") + self._V = V + self.u = fen.TrialFunction(V) + self.v = fen.TestFunction(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.", + end = "") + NB = self.NeumannBoundary + RB = self.RobinBoundary + class NBoundary(fen.SubDomain): + def inside(self, x, on_boundary): + return NB(x, on_boundary) + class RBoundary(fen.SubDomain): + def inside(self, x, on_boundary): + return RB(x, on_boundary) + boundary_markers = fen.MeshFunction("size_t", self.V.mesh(), + self.V.mesh().topology().dim() - 1) + NBoundary().mark(boundary_markers, 0) + RBoundary().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.", inline = True) + + def buildEnergyNormForm(self): + """ + Build sparse matrix (in CSR format) representative of scalar product. + """ + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling energy matrix.", end = "") + normMatFen = fen.assemble((fen.dot(fen.grad(self.u), fen.grad(self.v)) + + np.abs(self.omega)**2 * fen.dot(self.u, self.v)) *fen.dx) + normMat = fen.as_backend_type(normMatFen).mat() + self.energyNormMatrix = scsp.csr_matrix(normMat.getValuesCSR()[::-1], + shape = normMat.size) + if self.verbosity >= 20: + verbosityDepth("DEL", " Done.", inline = True) + + def A(self, mu:complex, der : int = 0) -> ScOp: + """Assemble (derivative of) operator of linear system.""" + Anull = self.checkAInBounds(der) + if Anull is not None: return Anull + self.autoSetDS() + if self.As[0] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A0.") + 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.") + return self.As[0] + + def b(self, mu:complex, der : int = 0, + homogeneized : bool = False) -> Np1D: + """Assemble (derivative of) RHS of linear system.""" + bnull = self.checkbInBounds(der, homogeneized) + if bnull is not None: return bnull + if homogeneized and not np.isclose(self.mu0BC, mu): + self.u0BC = self.liftDirichletData(mu) + if not np.isclose(self.bsmu, mu): + self.bsmu = mu + self.resetbs() + if self.bs[homogeneized][der] is None: + self.autoSetDS() + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling forcing term b{}."\ + .format(der)) + if der < self.nbs: + fRe, fIm = self.forcingTerm + g1Re, g1Im = self.NeumannDatum + g2Re, g2Im = self.RobinDatumG + else: + fRe, fIm = fenZERO, fenZERO + g1Re, g1Im = fenZERO, fenZERO + g2Re, g2Im = fenZERO, fenZERO + 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.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(mu, der) + b0Re[:] -= np.real(Ader.dot(self.u0BC)) + b0Im[:] -= np.imag(Ader.dot(self.u0BC)) + 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.apply(b0Re) + DBCI.apply(b0Im) + self.bs[homogeneized][der] = np.array(b0Re[:] + + 1.j * b0Im[:], dtype = np.complex) + if self.verbosity >= 20: + verbosityDepth("DEL", "Done assembling forcing term.") + return self.bs[homogeneized][der] + diff --git a/rrompy/hfengines/base/problem_engine_base.py b/rrompy/hfengines/base/problem_engine_base.py index 817304e..fd909ab 100644 --- a/rrompy/hfengines/base/problem_engine_base.py +++ b/rrompy/hfengines/base/problem_engine_base.py @@ -1,109 +1,333 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from abc import abstractmethod -from numpy import abs -from rrompy.utilities.base.types import Np1D, HS1D, HSOp, strLst +import fenics as fen +import numpy as np +from scipy.sparse import csr_matrix +import scipy.sparse as scsp +import scipy.sparse.linalg as scspla +from matplotlib import pyplot as plt +from rrompy.utilities.base.types import (Np1D, ScOp, strLst, FenFunc, + Tuple, List) +from rrompy.utilities.base import purgeList, getNewFilename, verbosityDepth from .boundary_conditions import BoundaryConditions __all__ = ['ProblemEngineBase'] class ProblemEngineBase: """ Generic solver for parametric problems. """ + + nAs, nbs = 1, 1 functional = lambda self, u: 0. - def __init__(self): + def __init__(self, degree_threshold : int = np.inf, verbosity : int = 10): self.BCManager = BoundaryConditions("Dirichlet") + self.V = fen.FunctionSpace(fen.UnitSquareMesh(10, 10), "P", 1) + self.verbosity = verbosity + self.resetAs() + self.resetbs() + self.bsmu = np.nan + self.liftDirichletDatamu = np.nan + self.mu0BC = np.nan + self.degree_threshold = degree_threshold def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() - @abstractmethod - def innerProduct(self, u:HS1D, v:HS1D) -> float: + def __dir_base__(self): + return [x for x in self.__dir__() if x[:2] != "__"] + + def innerProduct(self, u:Np1D, v:Np1D) -> float: """Hilbert space scalar product.""" - pass + if not hasattr(self, "energyNormMatrix"): + self.buildEnergyNormForm() + return v.conj().T.dot(self.energyNormMatrix.dot(u)) - def norm(self, u:HS1D) -> float: - return abs(self.innerProduct(u, u)) ** .5 + def buildEnergyNormForm(self): + """ + Build sparse matrix (in CSR format) representative of scalar product. + """ + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling energy matrix.", end = "") + normMat = fen.assemble(fen.dot(self.u, self.v) * fen.dx) + normMatFen = fen.as_backend_type(normMat).mat() + self.energyNormMatrix = csr_matrix(normMatFen.getValuesCSR()[::-1], + shape = normMat.size) + if self.verbosity >= 20: + verbosityDepth("DEL", " Done.", inline = True) + + def norm(self, u:Np1D) -> float: + return np.abs(self.innerProduct(u, u)) ** .5 def rescaling(self, x:Np1D) -> Np1D: """Rescaling in parameter dependence.""" return x def rescalingInv(self, x:Np1D) -> Np1D: """Inverse rescaling in parameter dependence.""" return x + def checkAInBounds(self, der : int = 0): + """Check if derivative index is oob for operator of linear system.""" + if der < 0 or der >= self.nAs: + d = self.V.dim() + return scsp.csr_matrix((np.zeros(0), np.zeros(0), np.zeros(d + 1)), + shape = (d, d), dtype = np.complex) + + def checkbInBounds(self, der : int = 0, homogeneized : bool = False): + """Check if derivative index is oob for RHS of linear system.""" + if der < 0 or der >= max(self.nbs, self.nAs * homogeneized): + return np.zeros(self.V.dim(), dtype = np.complex) + + def setDirichletDatum(self, mu:complex): + """Set Dirichlet datum if parametric.""" + if hasattr(self, "liftedDirichletDatum"): + self.liftDirichletDatamu = mu + + def liftDirichletData(self, mu:complex) -> Np1D: + """Lift Dirichlet datum.""" + self.setDirichletDatum(mu) + if not np.isclose(self.liftDirichletDatamu, mu): + try: + liftRe = fen.interpolate(self.DirichletDatum[0], self.V) + except: + liftRe = fen.project(self.DirichletDatum[0], self.V) + try: + liftIm = fen.interpolate(self.DirichletDatum[1], self.V) + except: + liftIm = fen.project(self.DirichletDatum[1], self.V) + self.liftedDirichletDatum = (np.array(liftRe.vector()) + + 1.j * np.array(liftIm.vector())) + return self.liftedDirichletDatum + + def resetAs(self): + """Reset (derivatives of) operator of linear system.""" + self.As = [None] * self.nAs + + def resetbs(self): + """Reset (derivatives of) RHS of linear system.""" + self.bs = {True: [None] * max(self.nbs, self.nAs), + False: [None] * self.nbs} + + def reduceQuadratureDegree(self, fun:FenFunc, name:str): + """Check whether to reduce compiler parameters to degree threshold.""" + if not np.isinf(self.degree_threshold): + from ufl.algorithms.estimate_degrees import ( + estimate_total_polynomial_degree as ETPD) + try: + deg = ETPD(fun) + except: + return False + if deg > self.degree_threshold: + if self.verbosity >= 15: + verbosityDepth("MAIN", ("Reducing quadrature degree from " + "{} to {} for {}.").format( + deg, + self.degree_threshold, + name)) + return True + return False + + def iterReduceQuadratureDegree(self, funsNames:List[Tuple[FenFunc, str]]): + """ + Iterate reduceQuadratureDegree over list and define reduce compiler + parameters. + """ + if funsNames is not None: + for fun, name in funsNames: + if self.reduceQuadratureDegree(fun, name): + return {"quadrature_degree" : self.degree_threshold} + return {} + @abstractmethod - def A(self, mu:complex, der : int = 0) -> HSOp: + def A(self, mu:complex, der : int = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" - pass + Anull = self.checkAInBounds(der) + if Anull is not None: return Anull + if self.As[der] is None: + self.As[der] = 0. + return self.As[der] @abstractmethod - def b(self, mu:complex, der : int = 0) -> HS1D: + def b(self, mu:complex, der : int = 0, + homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" - pass + bnull = self.checkbInBounds(der, homogeneized) + if bnull is not None: return bnull + if self.bs[homogeneized][der] is None: + self.bs[homogeneized][der] = 0. + return self.bs[homogeneized][der] - @abstractmethod - def solve(self, mu:complex, RHS : HS1D = None) -> HS1D: - """Find solution of linear system.""" - pass + def affineBlocksA(self, mu : complex = 0.) -> Tuple[List[Np1D], callable]: + """Assemble affine blocks of operator of linear system.""" + def lambdas(x, j): + if j == 0: return np.ones(np.size(x)) + if j in range(1, self.nAs): return np.power(self.rescaling(x) + - self.rescaling(mu), j) + raise Exception("Wrong j value.") + As = [None] * self.nAs + for j in range(self.nAs): + As[j] = self.A(mu, j) + return As, lambdas - @abstractmethod - def residual(self, u:HS1D, mu:complex) -> HS1D: - """Find residual of linear system for given approximate solution.""" - pass + def affineBlocksb(self, mu : complex = 0., homogeneized : bool = False)\ + -> Tuple[List[Np1D], callable]: + """Assemble affine blocks of RHS of linear system.""" + def lambdas(x, j): + if j == 0: return np.ones(np.size(x)) + if j in range(1, self.nbsEff): return np.power(self.rescaling(x) + - self.rescaling(mu), + j) + raise Exception("Wrong j value.") + if homogeneized: + self.nbsEff = max(self.nAs, self.nbs) + else: + self.nbsEff = self.nbs + bs = [None] * self.nbsEff + for j in range(self.nbsEff): + bs[j] = self.b(mu, j, homogeneized) + return bs, lambdas - @abstractmethod - def plot(self, u:HS1D, name : str = "u", save : bool = False, - what : strLst = 'all', **figspecs): + def solve(self, mu:complex, RHS : Np1D = None, + homogeneized : bool = False) -> Np1D: + """ + Find solution of linear system. + + Args: + mu: parameter value. + RHS: RHS of linear system. If None, defaults to that of parametric + system. Defaults to None. + """ + A = self.A(mu) + if RHS is None: RHS = self.b(mu, 0, homogeneized) + return scspla.spsolve(A, RHS) + + def residual(self, u:Np1D, mu:complex, + homogeneized : bool = False) -> Np1D: + """ + Find residual of linear system for given approximate solution. + + Args: + u: numpy complex array with function dofs. If None, set to 0. + mu: parameter value. + """ + A = self.A(mu) + RHS = self.b(mu, 0, homogeneized) + if u is None: return RHS + return RHS - A.dot(u) + + def plot(self, u:Np1D, name : str = "u", save : str = None, + what : strLst = 'all', saveFormat : str = "eps", + saveDPI : int = 100, **figspecs): """ Do some nice plots of the complex-valued function with given dofs. Args: - u: Hilbert space element. + 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'. - save(optional): Whether to save plot(s). Defaults to False. + 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. """ - pass + 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 - @abstractmethod - def plotmesh(self, name : str = "mesh", save : bool = False, **figspecs): + plt.figure(**figspecs) + plt.jet() + if 'ABS' in what: + uAb = fen.Function(self.V) + uAb.vector()[:] = np.array(np.abs(u), dtype = float) + subplotcode = subplotcode + 1 + plt.subplot(subplotcode) + p = fen.plot(uAb, title = "|{0}|".format(name)) + plt.colorbar(p) + if 'PHASE' in what: + uPh = fen.Function(self.V) + uPh.vector()[:] = np.array(np.angle(u), dtype = float) + subplotcode = subplotcode + 1 + plt.subplot(subplotcode) + p = fen.plot(uPh, title = "phase({0})".format(name)) + plt.colorbar(p) + if 'REAL' in what: + uRe = fen.Function(self.V) + uRe.vector()[:] = np.array(np.real(u), dtype = float) + subplotcode = subplotcode + 1 + plt.subplot(subplotcode) + p = fen.plot(uRe, title = "Re({0})".format(name)) + plt.colorbar(p) + if 'IMAG' in what: + uIm = fen.Function(self.V) + uIm.vector()[:] = np.array(np.imag(u), dtype = float) + subplotcode = subplotcode + 1 + plt.subplot(subplotcode) + p = fen.plot(uIm, title = "Im({0})".format(name)) + plt.colorbar(p) + if save is not None: + save = save.strip() + plt.savefig(getNewFilename("{}_fig_".format(save), saveFormat), + format = saveFormat, dpi = saveDPI) + plt.show() + plt.close() + + def plotmesh(self, name : str = "Mesh", save : str = None, + saveFormat : str = "eps", saveDPI : int = 100, **figspecs): """ Do a nice plot of the mesh. Args: - u: Hilbert space element. + u: numpy complex array with function dofs. name(optional): Name to be shown as title of the plots. Defaults to - 'mesh'. - save(optional): Whether to save plot(s). Defaults to False. + 'u'. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ - pass + plt.figure(**figspecs) + fen.plot(self.V.mesh()) + if save is not None: + save = save.strip() + plt.savefig(getNewFilename("{}_msh_".format(save), saveFormat), + format = saveFormat, dpi = saveDPI) + plt.show() + plt.close() + diff --git a/rrompy/hfengines/scipy/scattering_problem_engine.py b/rrompy/hfengines/base/scattering_problem_engine.py similarity index 51% rename from rrompy/hfengines/scipy/scattering_problem_engine.py rename to rrompy/hfengines/base/scattering_problem_engine.py index 6b9deef..2b51334 100644 --- a/rrompy/hfengines/scipy/scattering_problem_engine.py +++ b/rrompy/hfengines/base/scattering_problem_engine.py @@ -1,139 +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 <http://www.gnu.org/licenses/>. # -import numpy as np +from numpy import inf import scipy.sparse as scsp import fenics as fen -from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List +from rrompy.utilities.base.types import Np1D, ScOp from rrompy.utilities.base.fenics import fenZERO -from .helmholtz_base_problem_engine import HelmholtzBaseProblemEngine +from rrompy.utilities.base import verbosityDepth +from .helmholtz_problem_engine import HelmholtzProblemEngine +from rrompy.utilities.warning_manager import warn __all__ = ['ScatteringProblemEngine'] -class ScatteringProblemEngine(HelmholtzBaseProblemEngine): +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 k u = g2 on \Gamma_R + \partial_nu +- i omega u = g2 on \Gamma_R Attributes: signR: Sign in ABC. 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. DirichletBoundary: Function handle to \Gamma_D. NeumannBoundary: Function handle to \Gamma_N. RobinBoundary: Function handle to \Gamma_R. V: Real FE space. u: Generic trial functions for variational form evaluation. v: Generic test functions for variational form evaluation. 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 A1. b0: Numpy array representation of b0. As: Scipy sparse array representation (in CSC format) of As. bs: Numpy array representation of bs. """ + + nAs = 3 signR = - 1. - def A(self, mu:complex, der : int = 0) -> Np2D: + def __init__(self, degree_threshold : int = inf, verbosity : int = 10): + self.silenceWarnings = True + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) + del self.silenceWarnings + + def rescaling(self, x:Np1D) -> Np1D: + """Rescaling in parameter dependence.""" + return x + + def rescalingInv(self, x:Np1D) -> Np1D: + """Inverse rescaling in parameter dependence.""" + return x + + @property + def RobinDatumH(self): + """Value of h.""" + return self.signR * self.omega + @RobinDatumH.setter + def RobinDatumH(self, RobinDatumH): + if not hasattr(self, "silenceWarnings"): + warn(("Scattering problems do not allow changes of h. Ignoring " + "assignment.")) + return + + def A(self, mu:complex, der : int = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" - if der > 2 or der < 0: - d = self.V.dim() - return scsp.csr_matrix((np.zeros(0), np.zeros(0), np.zeros(d + 1)), - shape = (d, d), dtype = np.complex) + Anull = self.checkAInBounds(der) + if Anull is not None: return Anull self.autoSetDS() - if der <= 0 and not hasattr(self, "A0"): + if der <= 0 and self.As[0] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A0.") 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) - A0Im = fen.assemble(a0Im) + 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.A0 = (scsp.csr_matrix((A0Rev, A0Rec, A0Rer), - shape = A0ReMat.size) - + 1.j * scsp.csr_matrix((A0Imv, A0Imc, A0Imr), - shape = A0ImMat.size)) - if der <= 1 and not hasattr(self, "A1"): + 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.") + if der <= 1 and self.As[1] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A1.") 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.A1 = self.signR * 1.j * scsp.csr_matrix((A1v, A1c, A1r), - shape = A1Mat.size) - if der <= 2 and not hasattr(self, "A2"): + 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.") + if der <= 2 and self.As[2] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A2.") 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) - A2Im = fen.assemble(a2Im) + 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.A2 = (scsp.csr_matrix((A2Rev, A2Rec, A2Rer), - shape = A2ReMat.size) - + 1.j * scsp.csr_matrix((A2Imv, A2Imc, A2Imr), - shape = A2ImMat.size)) + 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.") if der == 0: - return self.A0 + mu * self.A1 + mu**2. * self.A2 + return self.As[0] + mu * self.As[1] + mu**2. * self.As[2] if der == 1: - return self.A1 + 2 * mu * self.A2 - return self.A2 - - def affineBlocksA(self, mu : complex = 0.) -> Tuple[List[Np2D], callable]: - """Assemble affine blocks of operator of linear system.""" - def lambdas(x, j): - if j == 0: return np.ones(np.size(x)) - if j in [1, 2]: return np.power(self.rescaling(x) - - self.rescaling(mu), j) - raise Exception("Wrong j value.") - A0 = self.A(mu, 0) - A1 = self.A(mu, 1) - return [A0, A1, self.A2], lambdas - - def affineBlocksb(self, mu : complex = 0.) -> Tuple[List[Np1D], callable]: - """Assemble affine blocks of RHS of linear system.""" - def lambdas(x, j): - if j == 0: return np.ones(np.size(x)) - raise Exception("Wrong j value.") - self.b(mu, 0) - return [self.b0], lambdas + return self.As[1] + 2 * mu * self.As[2] + return self.As[2] diff --git a/rrompy/hfengines/scipy/__init__.py b/rrompy/hfengines/scipy/__init__.py index 870f0a5..7bd5728 100644 --- a/rrompy/hfengines/scipy/__init__.py +++ b/rrompy/hfengines/scipy/__init__.py @@ -1,47 +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 <http://www.gnu.org/licenses/>. # -from .generic_problem_engine import GenericProblemEngine -from .helmholtz_base_problem_engine import HelmholtzBaseProblemEngine from .helmholtz_box_scattering_problem_engine import ( HelmholtzBoxScatteringProblemEngine) from .helmholtz_cavity_scattering_problem_engine import ( HelmholtzCavityScatteringProblemEngine) -from .helmholtz_problem_engine import HelmholtzProblemEngine from .helmholtz_square_bubble_problem_engine import ( HelmholtzSquareBubbleProblemEngine) from .helmholtz_square_bubble_domain_problem_engine import ( HelmholtzSquareBubbleDomainProblemEngine) from .helmholtz_square_transmission_problem_engine import ( HelmholtzSquareTransmissionProblemEngine) -from .scattering_problem_engine import ScatteringProblemEngine +from .laplace_disk_gaussian import ( + LaplaceDiskGaussian) __all__ = [ - 'GenericProblemEngine', - 'HelmholtzBaseProblemEngine', 'HelmholtzBoxScatteringProblemEngine', 'HelmholtzCavityScatteringProblemEngine', - 'HelmholtzProblemEngine', 'HelmholtzSquareBubbleProblemEngine', 'HelmholtzSquareBubbleDomainProblemEngine', 'HelmholtzSquareTransmissionProblemEngine', - 'ScatteringProblemEngine' + 'LaplaceDiskGaussian' ] diff --git a/rrompy/hfengines/scipy/generic_problem_engine.py b/rrompy/hfengines/scipy/generic_problem_engine.py deleted file mode 100644 index 0ef4250..0000000 --- a/rrompy/hfengines/scipy/generic_problem_engine.py +++ /dev/null @@ -1,185 +0,0 @@ -# Copyright (C) 2018 by the RROMPy authors -# -# This file is part of RROMPy. -# -# RROMPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# RROMPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with RROMPy. If not, see <http://www.gnu.org/licenses/>. -# - -from abc import abstractmethod -import fenics as fen -import numpy as np -from scipy.sparse import csr_matrix -import scipy.sparse.linalg as scspla -from matplotlib import pyplot as plt -from rrompy.hfengines.base import ProblemEngineBase -from rrompy.utilities.base.types import Np1D, Np2D, strLst, Tuple, List -from rrompy.utilities.base import purgeList, getNewFilename - -__all__ = ['GenericProblemEngine'] - -class GenericProblemEngine(ProblemEngineBase): - """ - Generic solver for parametric problems. - """ - - def __init__(self): - super().__init__() - self.V = fen.FunctionSpace(fen.UnitSquareMesh(10, 10), "P", 1) - - def name(self) -> str: - return self.__class__.__name__ - - def __str__(self) -> str: - return self.name() - - def innerProduct(self, u:Np1D, v:Np1D) -> float: - """Hilbert space scalar product.""" - if not hasattr(self, "energyNormMatrix"): - self.buildEnergyNormForm() - return v.conj().T.dot(self.energyNormMatrix.dot(u)) - - def buildEnergyNormForm(self): - """ - Build sparse matrix (in CSR format) representative of scalar product. - """ - normMat = fen.assemble(fen.dot(self.u, self.v) * fen.dx) - normMatFen = fen.as_backend_type(normMat).mat() - self.energyNormMatrix = csr_matrix(normMatFen.getValuesCSR()[::-1], - shape = normMat.size) - - def solve(self, mu:complex, RHS : Np1D = None) -> Np1D: - """ - Find solution of linear system. - - Args: - mu: parameter value. - RHS: RHS of linear system. If None, defaults to that of parametric - system. Defaults to None. - """ - if RHS is None: RHS = self.b(mu) - return scspla.spsolve(self.A(mu), RHS) - - def residual(self, u:Np1D, mu:complex) -> Np1D: - """ - Find residual of linear system for given approximate solution. - - Args: - u: numpy complex array with function dofs. If None, set to 0. - mu: parameter value. - """ - RHS = self.b(mu) - if u is None: return RHS - return RHS - self.A(mu).dot(u) - - def plot(self, u:Np1D, name : str = "u", save : bool = False, - what : strLst = 'all', **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'. - 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): Whether to save plot(s). Defaults to False. - figspecs(optional key args): Optional arguments for matplotlib - figure creation. - """ - if isinstance(what, (str,)): - if what.upper() == 'ALL': - what = ['ABS', 'PHASE', 'REAL', 'IMAG'] - else: - what = [what] - what = purgeList(what, ['ABS', 'PHASE', 'REAL', 'IMAG'], - listname = self.name() + ".what", baselevel = 1) - if len(what) == 0: return - if 'figsize' not in figspecs.keys(): - figspecs['figsize'] = (13. * len(what) / 4, 3) - subplotcode = 100 + len(what) * 10 - - plt.figure(**figspecs) - plt.jet() - if 'ABS' in what: - uAb = fen.Function(self.V) - uAb.vector()[:] = np.array(np.abs(u), dtype = float) - subplotcode = subplotcode + 1 - plt.subplot(subplotcode) - p = fen.plot(uAb, title = "|{0}|".format(name)) - plt.colorbar(p) - if 'PHASE' in what: - uPh = fen.Function(self.V) - uPh.vector()[:] = np.array(np.angle(u), dtype = float) - subplotcode = subplotcode + 1 - plt.subplot(subplotcode) - p = fen.plot(uPh, title = "phase({0})".format(name)) - plt.colorbar(p) - if 'REAL' in what: - uRe = fen.Function(self.V) - uRe.vector()[:] = np.array(np.real(u), dtype = float) - subplotcode = subplotcode + 1 - plt.subplot(subplotcode) - p = fen.plot(uRe, title = "Re({0})".format(name)) - plt.colorbar(p) - if 'IMAG' in what: - uIm = fen.Function(self.V) - uIm.vector()[:] = np.array(np.imag(u), dtype = float) - subplotcode = subplotcode + 1 - plt.subplot(subplotcode) - p = fen.plot(uIm, title = "Im({0})".format(name)) - plt.colorbar(p) - if save: - plt.savefig(getNewFilename("fig", "eps"), format='eps', dpi=1000) - plt.show() - plt.close() - - def plotmesh(self, name : str = "Mesh", save : bool = False, **figspecs): - """ - Do a nice plot of the mesh. - - Args: - u: numpy complex array with function dofs. - name(optional): Name to be shown as title of the plots. Defaults to - 'u'. - save(optional): Whether to save plot(s). Defaults to False. - figspecs(optional key args): Optional arguments for matplotlib - figure creation. - """ - plt.figure(**figspecs) - fen.plot(self.V.mesh()) - if save: - plt.savefig(getNewFilename("msh", "eps"), format='eps', dpi=1000) - plt.show() - plt.close() - - @abstractmethod - def A(self, mu:complex, der : int = 0) -> Np2D: - """Assemble (derivative of) operator of linear system.""" - pass - - @abstractmethod - def b(self, mu:complex, der : int = 0) -> Np1D: - """Assemble (derivative of) RHS of linear system.""" - pass - - @abstractmethod - def affineBlocksA(self, mu : complex = 0.) -> Tuple[List[Np2D], callable]: - """Assemble affine blocks of operator of linear system.""" - pass - - @abstractmethod - def affineBlocksb(self, mu : complex = 0.) -> Tuple[List[Np1D], callable]: - """Assemble affine blocks of RHS of linear system.""" - pass diff --git a/rrompy/hfengines/scipy/helmholtz_base_problem_engine.py b/rrompy/hfengines/scipy/helmholtz_base_problem_engine.py deleted file mode 100644 index e097221..0000000 --- a/rrompy/hfengines/scipy/helmholtz_base_problem_engine.py +++ /dev/null @@ -1,311 +0,0 @@ -# Copyright (C) 2018 by the RROMPy authors -# -# This file is part of RROMPy. -# -# RROMPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# RROMPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with RROMPy. If not, see <http://www.gnu.org/licenses/>. -# - -import numpy as np -import scipy.sparse as scsp -import fenics as fen -from .generic_problem_engine import GenericProblemEngine -from rrompy.utilities.base.types import Np1D -from rrompy.utilities.base.fenics import fenZERO, fenONE, bdrTrue, bdrFalse - -__all__ = ['HelmholtzBaseProblemEngine'] - -class HelmholtzBaseProblemEngine(GenericProblemEngine): - """ - ABSTRACT - 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: - 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. - DirichletBoundary: Function handle to \Gamma_D. - NeumannBoundary: Function handle to \Gamma_N. - RobinBoundary: Function handle to \Gamma_R. - V: Real FE space. - u: Generic trial functions for variational form evaluation. - v: Generic test functions for variational form evaluation. - 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. - As: Scipy sparse array representation (in CSC format) of As. - bs: Numpy array representation of bs. - """ - omega = 1. - - def __init__(self): - super().__init__() - self.diffusivity = fenONE - self.refractionIndex = fenONE - self.forcingTerm = fenZERO - self.DirichletDatum = fenZERO - self.NeumannDatum = fenZERO - self.RobinDatumG = fenZERO - - @property - def V(self): - """Value of V.""" - return self._V - @V.setter - def V(self, V): - if hasattr(self, "A0"): del self.A0 - if hasattr(self, "A1"): del self.A1 - if hasattr(self, "b0"): del self.b0 - if not type(V).__name__ == 'FunctionSpace': - raise Exception("V type not recognized.") - self._V = V - self.u = fen.TrialFunction(V) - self.v = fen.TestFunction(V) - self.dsToBeSet = True - - @property - def diffusivity(self): - """Value of a.""" - return self._diffusivity - @diffusivity.setter - def diffusivity(self, diffusivity): - if hasattr(self, "A0"): del self.A0 - if not isinstance(diffusivity, (list, tuple,)): - diffusivity = [diffusivity, fenZERO] - self._diffusivity = diffusivity - - @property - def refractionIndex(self): - """Value of n.""" - return self._refractionIndex - @refractionIndex.setter - def refractionIndex(self, refractionIndex): - if hasattr(self, "A1"): del self.A1 - if not isinstance(refractionIndex, (list, tuple,)): - refractionIndex = [refractionIndex, fenZERO] - self._refractionIndex = refractionIndex - - @property - def forcingTerm(self): - """Value of f.""" - return self._forcingTerm - @forcingTerm.setter - def forcingTerm(self, forcingTerm): - if hasattr(self, "b0"): del self.b0 - if not isinstance(forcingTerm, (list, tuple,)): - forcingTerm = [forcingTerm, fenZERO] - self._forcingTerm = forcingTerm - - @property - def DirichletDatum(self): - """ - Value of u0. Its assignment changes u0Re, u0Im, DirichletBCRe and - DirichletBCIm. - """ - return self._DirichletDatum - @DirichletDatum.setter - def DirichletDatum(self, DirichletDatum): - if hasattr(self, "b0"): del self.b0 - 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): - if hasattr(self, "b0"): del self.b0 - 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): - if hasattr(self, "b0"): del self.b0 - if not isinstance(RobinDatumG, (list, tuple,)): - RobinDatumG = [RobinDatumG, fenZERO] - self._RobinDatumG = RobinDatumG - - @property - def DirichletBoundary(self): - """Function handle to DirichletBoundary.""" - return self._DirichletBoundary - @DirichletBoundary.setter - def DirichletBoundary(self, DirichletBoundary): - if hasattr(self, "A0"): del self.A0 - if hasattr(self, "A1"): del self.A1 - if isinstance(DirichletBoundary, (str,)): - if DirichletBoundary.upper() == "NONE": - self._DirichletBoundary = bdrFalse - self.DREST = 0 - elif DirichletBoundary.upper() == "ALL": - self._DirichletBoundary = bdrTrue - self.NeumannBoundary = "NONE" - self.RobinBoundary = "NONE" - self.DREST = 0 - elif DirichletBoundary.upper() == "REST": - if self.NREST + self.RREST > 0: - raise Exception("Only 1 'REST' wildcard can be specified.") - self._DirichletBoundary = lambda x, on_boundary : (on_boundary - and not self.NeumannBoundary(x, on_boundary) - and not self.RobinBoundary(x, on_boundary)) - self.DREST = 1 - else: - raise Exception("DirichletBoundary label not recognized.") - elif callable(DirichletBoundary): - self._DirichletBoundary = DirichletBoundary - self.DREST = 0 - else: - raise Exception("DirichletBoundary type not recognized.") - - @property - def NeumannBoundary(self): - """Function handle to NeumannBoundary.""" - return self._NeumannBoundary - @NeumannBoundary.setter - def NeumannBoundary(self, NeumannBoundary): - if hasattr(self, "b0"): del self.b0 - self.dsToBeSet = True - if isinstance(NeumannBoundary, (str,)): - if NeumannBoundary.upper() == "NONE": - self._NeumannBoundary = bdrFalse - self.NREST = 0 - elif NeumannBoundary.upper() == "ALL": - self._NeumannBoundary = bdrTrue - self.DirichletBoundary = "NONE" - self.RobinBoundary = "NONE" - self.NREST = 0 - elif NeumannBoundary.upper() == "REST": - if self.DREST + self.RREST > 0: - raise Exception("Only 1 'REST' wildcard can be specified.") - self._NeumannBoundary = lambda x, on_boundary : (on_boundary - and not self.DirichletBoundary(x, on_boundary) - and not self.RobinBoundary(x, on_boundary)) - self.NREST = 1 - else: - raise Exception("NeumannBoundary label not recognized.") - elif callable(NeumannBoundary): - self._NeumannBoundary = NeumannBoundary - self.NREST = 0 - else: - raise Exception("DirichletBoundary type not recognized.") - - @property - def RobinBoundary(self): - """Function handle to RobinBoundary.""" - return self._RobinBoundary - @RobinBoundary.setter - def RobinBoundary(self, RobinBoundary): - if hasattr(self, "A0"): del self.A0 - if hasattr(self, "A1"): del self.A1 - self.dsToBeSet = True - if isinstance(RobinBoundary, (str,)): - if RobinBoundary.upper() == "NONE": - self._RobinBoundary = bdrFalse - self.RREST = 0 - elif RobinBoundary.upper() == "ALL": - self._RobinBoundary = bdrTrue - self.DirichletBoundary = "NONE" - self.NeumannBoundary = "NONE" - self.RREST = 0 - elif RobinBoundary.upper() == "REST": - if self.DREST + self.NREST > 0: - raise Exception("Only 1 'REST' wildcard can be specified.") - self._RobinBoundary = lambda x, on_boundary : (on_boundary - and not self.DirichletBoundary(x, on_boundary) - and not self.NeumannBoundary(x, on_boundary)) - self.RREST = 1 - else: - raise Exception("RobinBoundary label not recognized.") - return - elif callable(RobinBoundary): - self._RobinBoundary = RobinBoundary - self.RREST = 0 - else: - raise Exception("RobinBoundary type not recognized.") - - def autoSetDS(self): - """Set FEniCS boundary measure based on boundary function handles.""" - if self.dsToBeSet: - NB = self.NeumannBoundary - RB = self.RobinBoundary - class NBoundary(fen.SubDomain): - def inside(self, x, on_boundary): - return NB(x, on_boundary) - class RBoundary(fen.SubDomain): - def inside(self, x, on_boundary): - return RB(x, on_boundary) - boundary_markers = fen.MeshFunction("size_t", self.V.mesh(), - self.V.mesh().topology().dim() - 1) - NBoundary().mark(boundary_markers, 0) - RBoundary().mark(boundary_markers, 1) - self.ds = fen.Measure("ds", domain = self.V.mesh(), - subdomain_data = boundary_markers) - self.dsToBeSet = False - - def buildEnergyNormForm(self): - """ - Build sparse matrix (in CSR format) representative of scalar product. - """ - normMatFen = fen.assemble((fen.dot(fen.grad(self.u), fen.grad(self.v)) - + np.abs(self.omega)**2 * fen.dot(self.u, self.v)) *fen.dx) - normMat = fen.as_backend_type(normMatFen).mat() - self.energyNormMatrix = scsp.csr_matrix(normMat.getValuesCSR()[::-1], - shape = normMat.size) - - def liftDirichletData(self) -> Np1D: - """Lift Dirichlet datum.""" - solLRe = fen.interpolate(self.DirichletDatum[0], self.V) - solLIm = fen.interpolate(self.DirichletDatum[1], self.V) - return np.array(solLRe.vector()) + 1.j * np.array(solLIm.vector()) - - def b(self, mu:complex, der : int = 0) -> Np1D: - """Assemble (derivative of) RHS of linear system.""" - if der != 0: - return np.zeros(self.V.dim(), dtype = np.complex) - self.autoSetDS() - if not hasattr(self, "b0"): - fRe, fIm = self.forcingTerm - g1Re, g1Im = self.NeumannDatum - g2Re, g2Im = self.RobinDatumG - 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) - b0Im = fen.assemble(L0Im) - fen.DirichletBC(self.V, self.DirichletDatum[0], - self.DirichletBoundary).apply(b0Re) - fen.DirichletBC(self.V, self.DirichletDatum[1], - self.DirichletBoundary).apply(b0Im) - self.b0 = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) - return self.b0 - - diff --git a/rrompy/hfengines/scipy/helmholtz_box_scattering_problem_engine.py b/rrompy/hfengines/scipy/helmholtz_box_scattering_problem_engine.py index ca6c830..3bb09c2 100644 --- a/rrompy/hfengines/scipy/helmholtz_box_scattering_problem_engine.py +++ b/rrompy/hfengines/scipy/helmholtz_box_scattering_problem_engine.py @@ -1,59 +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 <http://www.gnu.org/licenses/>. # import numpy as np import fenics as fen -from rrompy.hfengines.scipy.scattering_problem_engine import ScatteringProblemEngine +from rrompy.hfengines.base.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): - super().__init__() + def __init__(self, R:float, kappa:float, theta:float, n:int, + degree_threshold : int = np.inf, verbosity : int = 10): + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) self.omega = kappa 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) self.DirichletBoundary = (lambda x, on_boundary: on_boundary and (x[0]**2+x[1]**2)**.5 < .95 * R) - self.RobinBoundary = (lambda x, on_boundary: - on_boundary and (x[0]**2+x[1]**2)**.5 > .95 * R) + self.RobinBoundary = "REST" - import sympy as sp - x, y = sp.symbols('x[0] x[1]', real=True) - phiex = kappa * (x * np.cos(theta) + y * np.sin(theta)) - u0ex = - sp.exp(1.j * phiex) - u0 = [sp.printing.ccode(sp.simplify(sp.re(u0ex))), - sp.printing.ccode(sp.simplify(sp.im(u0ex)))] - self.DirichletDatum = [fen.Expression(x, degree = 3) for x in u0] - + 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/scipy/helmholtz_cavity_scattering_problem_engine.py b/rrompy/hfengines/scipy/helmholtz_cavity_scattering_problem_engine.py index 3535b3b..f9263b0 100644 --- a/rrompy/hfengines/scipy/helmholtz_cavity_scattering_problem_engine.py +++ b/rrompy/hfengines/scipy/helmholtz_cavity_scattering_problem_engine.py @@ -1,54 +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 <http://www.gnu.org/licenses/>. # import numpy as np import fenics as fen -from rrompy.hfengines.scipy.scattering_problem_engine import ScatteringProblemEngine +from rrompy.hfengines.base.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): - super().__init__() + signR : int = -1, degree_threshold : int = np.inf, + verbosity : int = 10): + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) + self.signR = signR self.omega = kappa - mesh = fen.RectangleMesh(fen.Point(0,0), fen.Point(np.pi,np.pi), n, n) + pi = np.pi + mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), n, n) self.V = fen.FunctionSpace(mesh, "P", 3) self.RobinBoundary = (lambda x, on_boundary: on_boundary and np.isclose(x[1], np.pi)) self.DirichletBoundary = "REST" - import sympy as sp - x, y = sp.symbols('x[0] x[1]', real=True) - wex = 4/np.pi**4 * x * y * (x - np.pi) * (y - 2 * np.pi) - phiex = signR * kappa * (x * gamma + y) - uex = wex * sp.exp(-1.j * phiex) - fex = - uex.diff(x, 2) - uex.diff(y, 2) - kappa**2 * uex - forcingTerm = [sp.printing.ccode(sp.simplify(sp.re(fex))), - sp.printing.ccode(sp.simplify(sp.im(fex)))] - self.forcingTerm = [fen.Expression(x, degree = 3) for x in forcingTerm] + 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/scipy/helmholtz_square_bubble_domain_problem_engine.py b/rrompy/hfengines/scipy/helmholtz_square_bubble_domain_problem_engine.py index 8c73294..3a54b7b 100644 --- a/rrompy/hfengines/scipy/helmholtz_square_bubble_domain_problem_engine.py +++ b/rrompy/hfengines/scipy/helmholtz_square_bubble_domain_problem_engine.py @@ -1,185 +1,243 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # import numpy as np import scipy.sparse as scsp import fenics as fen -from rrompy.utilities.base.types import Np1D, Np2D, Tuple, List, FenExpr -from rrompy.hfengines.scipy.helmholtz_problem_engine import ( - HelmholtzProblemEngine, fenZERO) +from rrompy.utilities.base.types import Np1D, ScOp, Tuple, List, FenExpr +from rrompy.utilities.base.fenics import fenZERO +from rrompy.hfengines.base.helmholtz_problem_engine import ( + HelmholtzProblemEngine) +from rrompy.utilities.base import verbosityDepth __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. """ - def __init__(self, kappa:float, theta:float, n:int): - super().__init__() - + nAs, nbs = 3, 20 + + def __init__(self, kappa:float, theta:float, n:int, mu0 : np.complex = 1., + degree_threshold : int = np.inf, verbosity : int = 10): + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) self.omega = kappa self.kappa = kappa self.theta = theta - - self.derMaxB = 20 - self.bs = [None] * self.derMaxB + self.mu0 = mu0 + 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) + def buildEnergyNormForm(self): + """ + Build sparse matrix (in CSR format) representative of scalar product. + """ + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling energy matrix.", end = "") + mudx = np.abs(self.mu0) * fen.dot(self.u.dx(0), self.v.dx(0)) * fen.dx + muM = np.abs(self.mu0) * fen.dot(self.u, self.v) * fen.dx + imudy = 1. / np.abs(self.mu0) * (fen.dot(self.u.dx(1), self.v.dx(1)) + * fen.dx) + normMatFen = fen.assemble(mudx + imudy + muM) + normMat = fen.as_backend_type(normMatFen).mat() + self.energyNormMatrix = scsp.csr_matrix(normMat.getValuesCSR()[::-1], + shape = normMat.size) + if self.verbosity >= 20: + verbosityDepth("DEL", " Done.", inline = True) + def getForcingTerm(self, mu:complex) -> Tuple[FenExpr, FenExpr]: """Compute forcing term.""" - if (not hasattr(self, "forcingTermMu") - or not np.isclose(mu, self.forcingTermMu)): - import sympy as sp - x, y = sp.symbols('x[0] x[1]', real=True) - wex = 16/np.pi**4 * x * y * (x - np.pi) * (y - np.pi) - phiex = self.kappa * (x * np.cos(self.theta) + y * mu * np.sin(self.theta)) - uex = wex * sp.exp(-1.j * phiex) - fex = - uex.diff(x, 2) - uex.diff(y, 2) - self.kappa**2 * uex - forcingTerm = [sp.printing.ccode(sp.simplify(sp.re(fex))), - sp.printing.ccode(sp.simplify(sp.im(fex)))] - self.forcingTerm = [fen.Expression(x, degree = 3) for x in forcingTerm] + if not np.isclose(mu, self.forcingTermMu): + if self.verbosity >= 25: + verbosityDepth("INIT", + "Assembling base expression for forcing term.", + end = "") + 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), np.imag(mu) + mu2R, mu2I = np.real(mu ** 2.), np.imag(mu ** 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.", inline = True) return self.forcingTerm def getExtraFactorB(self, mu:complex, der:int) -> Tuple[FenExpr, FenExpr]: """Compute extra expression in RHS.""" - import sympy as sp + 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."), end = "") from math import factorial as fact - y = sp.symbols('x[1]', real=True) - expr = mu**2.*np.power(-1.j * self.kappa * np.sin(self.theta) * y, der) + y = fen.SpatialCoordinate(self.V.mesh())[1] + powR, powI = [(self.kappa * np.sin(self.theta)) ** der * k\ + for k in getPowMinusj(y, der)] + mu2R, mu2I = np.real(mu ** 2.), np.imag(mu ** 2.) + exprR = mu2R * powR - mu2I * powI + exprI = mu2I * powR + mu2R * powI if der >= 1: - expr += 2.*mu*np.power(-1.j * self.kappa * np.sin(self.theta) * y, - der - 1) * der + muR, muI = np.real(2. * mu), np.imag(2. * mu) + powR, powI = [(self.kappa * np.sin(self.theta)) ** (der - 1) * k\ + * der for k in getPowMinusj(y, der - 1)] + exprR += muR * powR - muI * powI + exprI += muI * powR + muR * powI if der >= 2: - expr += np.power(-1.j * self.kappa * np.sin(self.theta) * y, - der - 2) * der * (der - 1) - extraTerm = [sp.printing.ccode(sp.simplify(sp.re(expr / fact(der)))), - sp.printing.ccode(sp.simplify(sp.im(expr / fact(der))))] - return [fen.Expression(x, degree = 3) for x in extraTerm] + powR, powI = [(self.kappa * np.sin(self.theta)) ** (der - 2) * k\ + * der * (der - 1) for k in getPowMinusj(y, der - 2)] + exprR += powR + exprI += powI + fac = fact(der) + if self.verbosity >= 25: + verbosityDepth("DEL", " Done.", inline = True) + return [exprR / fac, exprI / fac] def rescaling(self, x:Np1D) -> Np1D: """Rescaling in parameter dependence.""" return x def rescalingInv(self, x:Np1D) -> Np1D: """Inverse rescaling in parameter dependence.""" return x - def A(self, mu:complex, der : int = 0) -> Np2D: + def A(self, mu:complex, der : int = 0) -> ScOp: """Assemble (derivative of) operator of linear system.""" - if der > 2 or der < 0: - d = self.V.dim() - return scsp.csr_matrix((np.zeros(0), np.zeros(0), np.zeros(d + 1)), - shape = (d, d), dtype = np.complex) + Anull = self.checkAInBounds(der) + if Anull is not None: return Anull self.autoSetDS() - if der <= 0 and not hasattr(self, "A0"): + if der <= 0 and self.As[0] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A0.") 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.A0 = scsp.csr_matrix((A0Rev, A0Rec, A0Rer), - shape = A0ReMat.size, - dtype = np.complex) - if der <= 2 and not hasattr(self, "A2"): + 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.") + if der <= 2 and self.As[2] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", "Assembling operator term A2.") 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) - A2Im = fen.assemble(a2Im) + 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.A2 = (scsp.csr_matrix((A2Rev, A2Rec, A2Rer), - shape = A2ReMat.size) - + 1.j * scsp.csr_matrix((A2Imv, A2Imc, A2Imr), - shape = A2ImMat.size)) + 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.") if der == 0: - return self.A0 + mu ** 2 * self.A2 + return self.As[0] + mu ** 2 * self.As[2] if der == 1: - return 2. * mu * self.A2 - return self.A2 + return 2. * mu * self.As[2] + return self.As[2] - def b(self, mu:complex, der : int = 0) -> Np1D: + def b(self, mu:complex, der : int = 0, + homogeneized : bool = False) -> Np1D: """Assemble (derivative of) RHS of linear system.""" - if der < 0 or der >= self.derMaxB: - return np.zeros(self.V.dim(), dtype = np.complex) - if self.bs[der] is None: - fRe, fIm = self.getForcingTerm(mu) - cRe, cIm = self.getExtraFactorB(mu, der) - cfRe = cRe * fRe - cIm * fIm - cfIm = cRe * fIm + cIm * fRe + bnull = self.checkbInBounds(der, homogeneized) + if bnull is not None: return bnull + if homogeneized and not np.isclose(self.mu0BC, mu): + self.u0BC = self.liftDirichletData(mu) + if not np.isclose(self.bsmu, mu): + self.bsmu = mu + self.resetbs() + if self.bs[homogeneized][der] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", + "Assembling forcing term b{}.".format(der)) + if der < self.nbs: + fRe, fIm = self.getForcingTerm(mu) + cRe, cIm = self.getExtraFactorB(mu, der) + cfRe = cRe * fRe - cIm * fIm + cfIm = cRe * fIm + cIm * fRe + else: + cfRe, cfIm = fenZERO, fenZERO + parsRe = self.iterReduceQuadratureDegree(zip([cfRe], + ["forcingTermDer{}Real".format(der)])) + parsIm = self.iterReduceQuadratureDegree(zip([cfIm], + ["forcingTermDer{}Imag".format(der)])) L0Re = fen.dot(cfRe, self.v) * fen.dx L0Im = fen.dot(cfIm, self.v) * fen.dx - b0Re = fen.assemble(L0Re) - b0Im = fen.assemble(L0Im) - if der == 0: - DirichletBCRe = fen.DirichletBC(self.V, self.DirichletDatum[0], - self.DirichletBoundary) - DirichletBCIm = fen.DirichletBC(self.V, self.DirichletDatum[1], - self.DirichletBoundary) - else: - DirichletBCRe = fen.DirichletBC(self.V, fenZERO, - self.DirichletBoundary) - DirichletBCIm = DirichletBCRe - DirichletBCRe.apply(b0Re) - DirichletBCIm.apply(b0Im) - self.bs[der] = np.array(b0Re[:] + 1.j * b0Im[:], dtype = np.complex) - return self.bs[der] - - def affineBlocksA(self, mu : complex = 0.) -> Tuple[List[Np2D], callable]: - """Assemble affine blocks of operator of linear system.""" - def lambdas(x, j): - if j == 0: return np.ones(np.size(x)) - if j in [1, 2]: return np.power(self.rescaling(x) - - self.rescaling(mu), j) - raise Exception("Wrong j value.") - A0 = self.A(mu, 0) - A1 = self.A(mu, 1) - return [A0, A1, self.A2], lambdas - - def affineBlocksb(self, mu : complex = 0.) -> Tuple[List[Np1D], callable]: - """Assemble affine blocks of RHS of linear system.""" - def lambdas(x, j): - if j == 0: return np.ones(np.size(x)) - if j in range(1, self.derMaxB): return np.power(self.rescaling(x) - - self.rescaling(mu), - j) - raise Exception("Wrong j value.") - for j in range(self.derMaxB): - self.b(mu, j) - return self.bs, lambdas + b0Re = fen.assemble(L0Re, form_compiler_parameters = parsRe) + b0Im = fen.assemble(L0Im, form_compiler_parameters = parsIm) + if homogeneized: + Ader = self.A(mu, der) + b0Re[:] -= np.real(Ader.dot(self.u0BC)) + b0Im[:] -= np.imag(Ader.dot(self.u0BC)) + DirichletBC0 = fen.DirichletBC(self.V, fenZERO, + self.DirichletBoundary) + DirichletBC0.apply(b0Re) + DirichletBC0.apply(b0Im) + self.bs[homogeneized][der] = np.array(b0Re[:] + + 1.j * b0Im[:], dtype = np.complex) + if self.verbosity >= 20: + verbosityDepth("DEL", "Done assembling forcing term.") + return self.bs[homogeneized][der] diff --git a/rrompy/hfengines/scipy/helmholtz_square_bubble_problem_engine.py b/rrompy/hfengines/scipy/helmholtz_square_bubble_problem_engine.py index 4dbcbff..7e6035f 100644 --- a/rrompy/hfengines/scipy/helmholtz_square_bubble_problem_engine.py +++ b/rrompy/hfengines/scipy/helmholtz_square_bubble_problem_engine.py @@ -1,50 +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 <http://www.gnu.org/licenses/>. # import numpy as np import fenics as fen -from rrompy.hfengines.scipy.helmholtz_problem_engine import ( +from rrompy.hfengines.base.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): - super().__init__() + def __init__(self, kappa:float, theta:float, n:int, + degree_threshold : int = np.inf, verbosity : int = 10): + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) self.omega = kappa - mesh = fen.RectangleMesh(fen.Point(0,0), fen.Point(np.pi,np.pi), n, n) + pi = np.pi + mesh = fen.RectangleMesh(fen.Point(0, 0), fen.Point(pi, pi), n, n) self.V = fen.FunctionSpace(mesh, "P", 3) - import sympy as sp - x, y = sp.symbols('x[0] x[1]', real=True) - wex = 16/np.pi**4 * x * y * (x - np.pi) * (y - np.pi) - phiex = kappa * (x * np.cos(theta) + y * np.sin(theta)) - uex = wex * sp.exp(-1.j * phiex) - fex = - uex.diff(x, 2) - uex.diff(y, 2) - kappa**2 * uex - forcingTerm = [sp.printing.ccode(sp.simplify(sp.re(fex))), - sp.printing.ccode(sp.simplify(sp.im(fex)))] - self.forcingTerm = [fen.Expression(x, degree = 3) for x in forcingTerm] + 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/scipy/helmholtz_square_transmission_problem_engine.py b/rrompy/hfengines/scipy/helmholtz_square_transmission_problem_engine.py index fbcca0f..dc5cce5 100644 --- a/rrompy/hfengines/scipy/helmholtz_square_transmission_problem_engine.py +++ b/rrompy/hfengines/scipy/helmholtz_square_transmission_problem_engine.py @@ -1,62 +1,78 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # import numpy as np import fenics as fen -from .helmholtz_problem_engine import HelmholtzProblemEngine +import ufl +from rrompy.hfengines.base.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): - super().__init__() + def __init__(self, nT:float, nB:float, kappa:float, theta:float, n:int, + degree_threshold : int = np.inf, verbosity : int = 10): + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) self.omega = kappa 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) - import sympy as sp 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 = sp.symbols('x[0] x[1]', real=True) - uT = T * sp.exp(1.j * (Kx*x + Ky*y)) - uB = ( sp.exp(1.j * kappa * nB * (dx*x + dy*y)) - + (T - 1) * sp.exp(1.j * kappa * nB * (dx*x - dy*y))) - uRe = fen.Expression('x[1]>=0 ? {} : {}'.format( - sp.printing.ccode(sp.re(uT)), - sp.printing.ccode(sp.re(uB))), - degree = 3) - uIm = fen.Expression('x[1]>=0 ? {} : {}'.format( - sp.printing.ccode(sp.im(uT)), - sp.printing.ccode(sp.im(uB))), - degree = 3) - self.refractionIndex = fen.Expression('x[1] >= 0 ? nT : nB', - nT = nT, nB = nB, degree = 0) - self.DirichletDatum = [uRe, uIm] + 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/scipy/laplace_disk_gaussian.py b/rrompy/hfengines/scipy/laplace_disk_gaussian.py new file mode 100644 index 0000000..277fe8d --- /dev/null +++ b/rrompy/hfengines/scipy/laplace_disk_gaussian.py @@ -0,0 +1,150 @@ +# Copyright (C) 2018 by the RROMPy authors +# +# This file is part of RROMPy. +# +# RROMPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# RROMPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with RROMPy. If not, see <http://www.gnu.org/licenses/>. +# + +import numpy as np +import fenics as fen +from rrompy.utilities.base.types import Np1D, Tuple, List, FenExpr +from rrompy.hfengines.base.laplace_base_problem_engine import ( + LaplaceBaseProblemEngine) +from rrompy.utilities.base.fenics import fenZERO, fenONE +from rrompy.utilities.base import verbosityDepth + +__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, degree_threshold : int = np.inf, + verbosity : int = 10): + super().__init__(degree_threshold = degree_threshold, + verbosity = verbosity) + 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) + + def getForcingTerm(self, mu:complex) -> Tuple[FenExpr, FenExpr]: + """Compute forcing term.""" + if not np.isclose(mu, self.forcingTermMu): + if self.verbosity >= 25: + verbosityDepth("INIT", + "Assembling base expression for forcing term.", + end = "") + x, y = fen.SpatialCoordinate(self.V.mesh())[:] + C = np.exp(-.5 * mu ** 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), np.imag(mu) + 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.", inline = True) + 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:complex, der:int) -> Tuple[FenExpr, FenExpr]: + """Compute extra expression in RHS.""" + if self.verbosity >= 25: + verbosityDepth("INIT", ("Assembling auxiliary expression for " + "forcing term derivative."), end = "") + muR, muI = np.real(mu), np.imag(mu) + x = fen.SpatialCoordinate(self.V.mesh())[0] + l = der % 2 + if l == 0: + powR, powI = fenONE, fenZERO + else: + powR, powI = x - muR, fen.Constant(muI) + exprR, exprI = [self.bsFactors[der, l] * k for k in [powR, powI]] + for j in range(l + 2, der + 1, 2): + for _ in range(2): + powR, powI = (powR * (x - muR) - powI * muI, + powR * muI + powI * (x - muR)) + exprR += self.bsFactors[der, j] * powR + exprI += self.bsFactors[der, j] * powI + if self.verbosity >= 25: + verbosityDepth("DEL", " Done.", inline = True) + return[exprR, exprI] + + def b(self, mu:complex, der : int = 0, + homogeneized : bool = False) -> Np1D: + """Assemble (derivative of) RHS of linear system.""" + bnull = self.checkbInBounds(der, homogeneized) + if bnull is not None: return bnull + if homogeneized and not np.isclose(self.mu0BC, mu): + self.u0BC = self.liftDirichletData(mu) + if not np.isclose(self.bsmu, mu): + self.bsmu = mu + self.resetbs() + if self.bs[homogeneized][der] is None: + if self.verbosity >= 20: + verbosityDepth("INIT", + "Assembling forcing term b{}.".format(der)) + if der < self.nbs: + fRe, fIm = self.getForcingTerm(mu) + cRe, cIm = self.getExtraFactorB(mu, der) + cfRe = cRe * fRe - cIm * fIm + cfIm = cRe * fIm + cIm * fRe + else: + cfRe, cfIm = fenZERO, fenZERO + parsRe = self.iterReduceQuadratureDegree(zip([cfRe], + ["forcingTermDer{}Real".format(der)])) + parsIm = self.iterReduceQuadratureDegree(zip([cfIm], + ["forcingTermDer{}Imag".format(der)])) + 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(mu, der) + b0Re[:] -= np.real(Ader.dot(self.u0BC)) + b0Im[:] -= np.imag(Ader.dot(self.u0BC)) + DirichletBC0 = fen.DirichletBC(self.V, fenZERO, + self.DirichletBoundary) + DirichletBC0.apply(b0Re) + DirichletBC0.apply(b0Im) + self.bs[homogeneized][der] = np.array(b0Re[:] + + 1.j * b0Im[:], dtype = np.complex) + if self.verbosity >= 20: + verbosityDepth("DEL", "Done assembling forcing term.") + return self.bs[homogeneized][der] + diff --git a/rrompy/reduction_methods/base/__init__.py b/rrompy/reduction_methods/base/__init__.py index 1c37f64..fdc4ada 100644 --- a/rrompy/reduction_methods/base/__init__.py +++ b/rrompy/reduction_methods/base/__init__.py @@ -1,25 +1,25 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.reduction_methods.base.generic_approximant import GenericApproximant +from .generic_approximant import GenericApproximant __all__ = [ 'GenericApproximant' ] diff --git a/rrompy/reduction_methods/base/generic_approximant.py b/rrompy/reduction_methods/base/generic_approximant.py index 64e2624..924764e 100644 --- a/rrompy/reduction_methods/base/generic_approximant.py +++ b/rrompy/reduction_methods/base/generic_approximant.py @@ -1,384 +1,499 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from abc import abstractmethod import numpy as np from copy import copy from rrompy.sampling.base.sampling_engine_base import SamplingEngineBase -from rrompy.utilities.base.types import Np1D, HS1D, DictAny, HFEng, strLst +from rrompy.utilities.base.types import Np1D, DictAny, HFEng, sampleEng, strLst from rrompy.utilities.base import purgeDict, verbosityDepth __all__ = ['GenericApproximant'] class GenericApproximant: """ ABSTRACT ROM approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True. Defaults to empty dict. verbosity(optional): Verbosity level. Defaults to 10. Attributes: HFEngine: HF problem solver. mu0: Default parameter. w: Weight for norm computation. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterList: Recognized keys of approximant parameters: - 'POD': whether to compute POD of snapshots. verbosity: Verbosity level. POD: Whether to compute POD of snapshots. samplingEngine: Sampling engine. uHF: High fidelity solution with wavenumber lastSolvedHF as numpy complex vector. lastSolvedHF: Wavenumber corresponding to last computed high fidelity solution. - solLifting: Lifting of Dirichlet boundary data as numpy vector. uApp: Last evaluated approximant as numpy complex vector. lastApproxParameters: List of parameters corresponding to last computed approximant. """ def __init__(self, HFEngine:HFEng, mu0 : complex = 0, - approxParameters : DictAny = {}, verbosity : int = 10): + approxParameters : DictAny = {}, homogeneize : bool = False, + verbosity : int = 10): self._preInit() self.verbosity = verbosity - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", ("Initializing approximant engine of " "type {}.").format(self.name())) self.HFEngine = HFEngine self._HFEngine0 = copy(HFEngine) if not hasattr(self, "parameterList"): self.parameterList = [] self.parameterList += ["POD"] - self.solLifting = self.HFEngine.liftDirichletData() self.mu0 = mu0 + self.homogeneize = homogeneize self.approxParameters = approxParameters self._postInit() def _preInit(self): if not hasattr(self, "depth"): self.depth = 0 else: self.depth += 1 def _postInit(self): if self.depth == 0: - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", "Done initializing.\n") del self.depth else: self.depth -= 1 def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() - def setupSampling(self): + def setupSampling(self, SamplingEngine : sampleEng = SamplingEngineBase): """Setup sampling engine.""" - self.samplingEngine = SamplingEngineBase(self.HFEngine, - verbosity = self.verbosity) + self.samplingEngine = SamplingEngine(self.HFEngine, + verbosity = self.verbosity) @property def approxParameters(self): """Value of approximant parameters.""" return self._approxParameters @approxParameters.setter def approxParameters(self, approxParams): if not hasattr(self, "approxParameters"): self._approxParameters = {} approxParameters = purgeDict(approxParams, self.parameterList, dictname = self.name() + ".approxParameters", baselevel = 1) keyList = list(approxParameters.keys()) if "POD" in keyList: self.POD = approxParameters["POD"] elif hasattr(self, "POD"): self.POD = self.POD else: self.POD = True @property def POD(self): """Value of POD.""" return self._POD @POD.setter def POD(self, POD): if hasattr(self, "POD"): PODold = self.POD else: PODold = -1 self._POD = POD self._approxParameters["POD"] = self.POD if PODold != self.POD: self.setupSampling() self.resetSamples() + @property + def homogeneize(self): + """Value of homogeneize.""" + return self._homogeneize + @homogeneize.setter + def homogeneize(self, homogeneize): + if not hasattr(self, "_homogeneize"): + self._homogeneize = None + if homogeneize != self.homogeneize: + self._homogeneize = homogeneize + self.resetSamples() + def solveHF(self, mu : complex = None): """ Find high fidelity solution with original parameters and arbitrary parameter. Args: mu: Target parameter. """ if mu is None: mu = self.mu0 if (not hasattr(self, "lastSolvedHF") or not np.isclose(self.lastSolvedHF, mu)): - self.uHF = self.samplingEngine.solveLS(mu) + self.uHF = self.samplingEngine.solveLS(mu, + homogeneized = self.homogeneize) self.lastSolvedHF = mu def resetSamples(self): """Reset samples.""" if hasattr(self, "samplingEngine"): self.samplingEngine.resetHistory() else: self.setupSampling() - def plotSamples(self, name : str = "u", save : bool = False, - what : strLst = 'all', **figspecs): + 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): Whether to save plot(s). Defaults to False. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ self.samplingEngine.plotSamples(name = name, save = save, what = what, + saveFormat = saveFormat, + saveDPI = saveDPI, **figspecs) @abstractmethod def setupApprox(self): """ Setup approximant. (ABSTRACT) Any specialization should include something like self.computeDerivatives() if not self.checkComputedApprox(): ... self.lastApproxParameters = copy(self.approxParameters) """ pass def checkComputedApprox(self) -> bool: """ Check if setup of new approximant is not needed. Returns: True if new setup is not needed. False otherwise. """ return (hasattr(self, "lastApproxParameters") and self.approxParameters == self.lastApproxParameters) @abstractmethod def evalApprox(self, mu:complex): """ Evaluate approximant at arbitrary parameter. (ABSTRACT) Any specialization should include something like self.setupApprox() self.uApp = ... Args: mu: Target parameter. """ pass - def getHF(self, mu:complex) -> HS1D: + def getHF(self, mu:complex, homogeneized : bool = False) -> Np1D: """ Get HF solution at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: HFsolution. """ self.solveHF(mu) + if self.homogeneize and not homogeneized: + return self.uHF + self.HFEngine.liftDirichletData(mu) + if not self.homogeneize and homogeneized: + return self.uHF - self.HFEngine.liftDirichletData(mu) return self.uHF - def getRHS(self, mu:complex) -> HS1D: + def getRHS(self, mu:complex, homogeneized : bool = False) -> Np1D: """ Get linear system RHS at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Linear system RHS. """ - return self.HFEngine.residual(None, mu) + return self.HFEngine.residual(None, mu, homogeneized = homogeneized) - def getApp(self, mu:complex) -> HS1D: + def getApp(self, mu:complex, homogeneized : bool = False) -> Np1D: """ Get approximant at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Approximant. """ self.evalApprox(mu) + if self.homogeneize and not homogeneized: + return self.uApp + self.HFEngine.liftDirichletData(mu) + if not self.homogeneize and homogeneized: + return self.uApp - self.HFEngine.liftDirichletData(mu) return self.uApp - def getRes(self, mu:complex) -> HS1D: + def getRes(self, mu:complex, homogeneized : bool = False) -> Np1D: """ Get residual at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Approximant residual. """ - return self.HFEngine.residual(self.getApp(mu), mu) + return self.HFEngine.residual(self.getApp(mu, homogeneized), mu, + homogeneized = homogeneized) - def getErr(self, mu:complex) -> HS1D: + def getErr(self, mu:complex, homogeneized : bool = False) -> Np1D: """ Get error at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Approximant error. """ - return self.getApp(mu) - self.getHF(mu) + return self.getApp(mu, homogeneized) - self.getHF(mu, homogeneized) @abstractmethod def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ pass - def normHF(self, mu:complex) -> float: + def normHF(self, mu:complex, homogeneized : bool = False) -> float: """ Compute norm of HF solution at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Target norm of HFsolution. """ - return self.HFEngine.norm(self.getHF(mu)) + return self.HFEngine.norm(self.getHF(mu, homogeneized)) - def normRHS(self, mu:complex) -> HS1D: + def normRHS(self, mu:complex, homogeneized : bool = False) -> Np1D: """ Compute norm of linear system RHS at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Norm of linear system RHS. """ - return self.HFEngine.norm(self.getRHS(mu)) + return self.HFEngine.norm(self.getRHS(mu, homogeneized)) - def normApp(self, mu:complex) -> float: + def normApp(self, mu:complex, homogeneized : bool = False) -> float: """ Compute norm of (translated) approximant at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Target norm of approximant. """ - return self.HFEngine.norm(self.getApp(mu)) + return self.HFEngine.norm(self.getApp(mu, homogeneized)) - def normRes(self, mu:complex) -> float: + def normRes(self, mu:complex, homogeneized : bool = False) -> float: """ Compute norm of approximant residual at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Target norm of (A(mu)app(mu) - f(mu)). """ - return self.HFEngine.norm(self.getRes(mu)) + return self.HFEngine.norm(self.getRes(mu, homogeneized)) - def normErr(self, mu:complex) -> float: + def normErr(self, mu:complex, homogeneized : bool = False) -> float: """ Compute norm of approximant error at arbitrary parameter. Args: mu: Target parameter. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. Returns: Target norm of (approximant - HFsolution). """ - return self.HFEngine.norm(self.getErr(mu)) + return self.HFEngine.norm(self.getErr(mu, homogeneized)) - def plotHF(self, mu:complex, name : str = "u", **figspecs): + def plotHF(self, mu:complex, name : str = "uHF", save : str = None, + what : strLst = 'all', saveFormat : str = "eps", + saveDPI : int = 100, homogeneized : bool = False, **figspecs): """ Do some nice plots of the HF solution at arbitrary parameter. Args: mu: Target parameter. name(optional): Name to be shown as title of the plots. Defaults to 'u'. + what(optional): Which plots to do. If list, can contain 'ABS', + 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. + Defaults to 'ALL'. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ - uHF = self.getHF(mu) - self.HFEngine.plot(uHF, name = name, **figspecs) + uHF = self.getHF(mu, homogeneized) + self.HFEngine.plot(uHF, name = name, save = save, what = what, + saveFormat = saveFormat, saveDPI = saveDPI, + **figspecs) - def plotApp(self, mu:complex, name : str = "u", **figspecs): + def plotApp(self, mu:complex, name : str = "uApp", save : str = None, + what : strLst = 'all', saveFormat : str = "eps", + saveDPI : int = 100, homogeneized : bool = False, **figspecs): """ Do some nice plots of approximant at arbitrary parameter. Args: mu: Target parameter. name(optional): Name to be shown as title of the plots. Defaults to 'u'. + what(optional): Which plots to do. If list, can contain 'ABS', + 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. + Defaults to 'ALL'. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. + figspecs(optional key args): Optional arguments for matplotlib + figure creation. + """ + uApp = self.getApp(mu, homogeneized) + self.HFEngine.plot(uApp, name = name, save = save, what = what, + saveFormat = saveFormat, saveDPI = saveDPI, + **figspecs) + + def plotRes(self, mu:complex, name : str = "res", save : str = None, + what : strLst = 'all', saveFormat : str = "eps", + saveDPI : int = 100, homogeneized : bool = False, **figspecs): + """ + Do some nice plots of approximation residual at arbitrary parameter. + + Args: + mu: Target parameter. + name(optional): Name to be shown as title of the plots. Defaults to + 'u'. + what(optional): Which plots to do. If list, can contain 'ABS', + 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. + Defaults to 'ALL'. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ - uApp = self.getApp(mu) - self.HFEngine.plot(uApp, name = name, **figspecs) + uRes = self.getRes(mu, homogeneized) + self.HFEngine.plot(uRes, name = name, save = save, what = what, + saveFormat = saveFormat, saveDPI = saveDPI, + **figspecs) - def plotErr(self, mu:complex, name : str = "u", **figspecs): + def plotErr(self, mu:complex, name : str = "err", save : str = None, + what : strLst = 'all', saveFormat : str = "eps", + saveDPI : int = 100, homogeneized : bool = False, **figspecs): """ Do some nice plots of approximation error at arbitrary parameter. Args: mu: Target parameter. name(optional): Name to be shown as title of the plots. Defaults to 'u'. + what(optional): Which plots to do. If list, can contain 'ABS', + 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. + Defaults to 'ALL'. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. + homogeneized(optional): Whether to remove Dirichlet BC. Defaults to + False. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ - uErr = self.getErr(mu) - self.HFEngine.plot(uErr, name = name, **figspecs) + uErr = self.getErr(mu, homogeneized) + self.HFEngine.plot(uErr, name = name, save = save, what = what, + saveFormat = saveFormat, saveDPI = saveDPI, + **figspecs) + diff --git a/rrompy/reduction_methods/lagrange/__init__.py b/rrompy/reduction_methods/lagrange/__init__.py index 24a3521..822bd2a 100644 --- a/rrompy/reduction_methods/lagrange/__init__.py +++ b/rrompy/reduction_methods/lagrange/__init__.py @@ -1,29 +1,29 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.reduction_methods.lagrange.generic_approximant_lagrange import GenericApproximantLagrange -from rrompy.reduction_methods.lagrange.approximant_lagrange_pade import ApproximantLagrangePade -from rrompy.reduction_methods.lagrange.approximant_lagrange_rb import ApproximantLagrangeRB +from .generic_approximant_lagrange import GenericApproximantLagrange +from .approximant_lagrange_pade import ApproximantLagrangePade +from .approximant_lagrange_rb import ApproximantLagrangeRB __all__ = [ 'GenericApproximantLagrange', 'ApproximantLagrangePade', 'ApproximantLagrangeRB' ] diff --git a/rrompy/reduction_methods/lagrange/approximant_lagrange_pade.py b/rrompy/reduction_methods/lagrange/approximant_lagrange_pade.py index b3d7cda..438501f 100644 --- a/rrompy/reduction_methods/lagrange/approximant_lagrange_pade.py +++ b/rrompy/reduction_methods/lagrange/approximant_lagrange_pade.py @@ -1,365 +1,358 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from copy import copy -import warnings import numpy as np -from rrompy.reduction_methods.lagrange.generic_approximant_lagrange import ( - GenericApproximantLagrange) +from .generic_approximant_lagrange import GenericApproximantLagrange from rrompy.utilities.base.types import Np1D, DictAny, HFEng from rrompy.utilities.base import purgeDict, verbosityDepth +from rrompy.utilities.warning_manager import warn __all__ = ['ApproximantLagrangePade'] class ApproximantLagrangePade(GenericApproximantLagrange): """ ROM Lagrange Pade' interpolant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon; defaults to 2; - 'sampler': sample point generator; defaults to uniform sampler on [0, 1]; - 'M': degree of Pade' interpolant numerator; defaults to 0; - 'N': degree of Pade' interpolant denominator; defaults to 0. Defaults to empty dict. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. ws: Array of snapshot weigths. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterList: Recognized keys of approximant parameters: - 'POD': whether to compute POD of snapshots; - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator; - 'M': degree of Pade' interpolant numerator; - 'N': degree of Pade' interpolant denominator; - 'robustTol': tolerance for robust Pade' denominator management. extraApproxParameters: List of approxParameters keys in addition to mother class's. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. M: Numerator degree of approximant. N: Denominator degree of approximant. POD: Whether to compute POD of snapshots. robustTol: Tolerance for robust Pade' denominator management. samplingEngine: Sampling engine. uHF: High fidelity solution with wavenumber lastSolvedHF as numpy complex vector. lastSolvedHF: Wavenumber corresponding to last computed high fidelity solution. - solLifting: Lifting of Dirichlet boundary data as numpy vector. Q: Numpy 1D vector containing complex coefficients of approximant denominator. P: Numpy 2D vector whose columns are FE dofs of coefficients of approximant numerator. uApp: Last evaluated approximant as numpy complex vector. lastApproxParameters: List of parameters corresponding to last computed approximant. """ def __init__(self, HFEngine:HFEng, mu0 : complex = 0., - approxParameters : DictAny = {}, verbosity : int = 10): + approxParameters : DictAny = {}, homogeneize : bool = False, + verbosity : int = 10): self._preInit() if not hasattr(self, "parameterList"): self.parameterList = [] self.parameterList += ["M", "N", "robustTol"] super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, + homogeneize = homogeneize, verbosity = verbosity) self._postInit() @property def approxParameters(self): """ Value of approximant parameters. Its assignment may change M, N and S. """ 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"], True, True, baselevel = 1) GenericApproximantLagrange.approxParameters.fset(self, approxParametersCopy) keyList = list(approxParameters.keys()) if "robustTol" in keyList: self.robustTol = approxParameters["robustTol"] elif hasattr(self, "robustTol"): self.robustTol = self.robustTol else: self.robustTol = 0 if "M" in keyList: self.M = approxParameters["M"] elif hasattr(self, "M"): self.M = self.M else: self.M = 0 if "N" in keyList: self.N = approxParameters["N"] elif hasattr(self, "N"): self.N = self.N else: self.N = 0 @property def M(self): """Value of M. Its assignment may change S.""" return self._M @M.setter def M(self, M): if M < 0: raise ArithmeticError("M must be non-negative.") self._M = M self._approxParameters["M"] = self.M if hasattr(self, "S") and self.S < self.M + 1: - warnings.warn("Prescribed S is too small. Updating S to M + 1.", - stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Prescribed S is too small. Updating S to M + 1.") self.S = self.M + 1 @property def N(self): """Value of N. Its assignment may change S.""" return self._N @N.setter def N(self, N): if N < 0: raise ArithmeticError("N must be non-negative.") self._N = N self._approxParameters["N"] = self.N if hasattr(self, "S") and self.S < self.N + 1: - warnings.warn("Prescribed S is too small. Updating S to N + 1.", - stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Prescribed S is too small. Updating S to N + 1.") self.S = self.N + 1 @property def robustTol(self): """Value of tolerance for robust Pade' denominator management.""" return self._robustTol @robustTol.setter def robustTol(self, robustTol): if robustTol < 0.: - warnings.warn(("Overriding prescribed negative robustness " - "tolerance to 0."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Overriding prescribed negative robustness tolerance to 0.") robustTol = 0. self._robustTol = robustTol self._approxParameters["robustTol"] = self.robustTol @property def S(self): """Value of S.""" return self._S @S.setter def S(self, S): if S <= 0: raise ArithmeticError("S must be positive.") if hasattr(self, "S"): Sold = self.S else: Sold = -1 vals, label = [0] * 2, {0:"M", 1:"N"} if hasattr(self, "M"): vals[0] = self.M if hasattr(self, "N"): vals[1] = self.N idxmax = np.argmax(vals) if vals[idxmax] + 1 > S: - warnings.warn("Prescribed S is too small. Updating S to {} + 1."\ - .format(label[idxmax]), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Prescribed S is too small. Updating S to {} + 1."\ + .format(label[idxmax])) self.S = vals[idxmax] + 1 else: self._S = S self._approxParameters["S"] = self.S if Sold != self.S: self.resetSamples() def setupApprox(self): """ Compute Pade' interpolant. SVD-based robust eigenvalue management. """ if not self.checkComputedApprox(): if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name())) + self.computeSnapshots() + if self.verbosity >= 7: verbosityDepth("INIT", "Starting computation of denominator.") while True: self.computeSnapshots() TN = np.vander(self.radiusPade(self.mus), N = self.N + 1, increasing = True) if self.POD: data = self.samplingEngine.RPOD.T else: data = self.samplingEngine.samples.T RHSFull = np.empty((self.S, data.shape[1] * (self.N + 1)), dtype = np.complex) for j in range(self.S): RHSFull[j, :] = np.kron(TN[j, :], data[j, :]) G = np.polyfit(self.radiusPade(self.mus), RHSFull, deg = self.N, w = self.ws)[0, :].reshape( (self.N + 1, data.shape[1])).T if self.POD: - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("INIT", ("Solving svd for square root " "of gramian matrix."), end = "") _, ev, V = np.linalg.svd(G, full_matrices = False) ev = ev[::-1] eV = V[::-1, :].conj().T else: - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Building gramian matrix.", end = "") G2 = self.HFEngine.innerProduct(G, G) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", "Done building gramian.", inline = True) + if self.verbosity >= 7: verbosityDepth("INIT", ("Solving eigenvalue problem " "for gramian matrix."), end = "") ev, eV = np.linalg.eigh(G2) - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("DEL", " Done.", inline = True) ts = self.robustTol * np.linalg.norm(ev) Nnew = np.sum(np.abs(ev) >= ts) diff = self.N - Nnew if diff <= 0: break Snew = self.S - diff Mnew = min(self.M, Snew - 1) if Mnew == self.M: strM = "" else: strM = ", M from {0} to {1},".format(self.M, Mnew) print(("Smallest {0} eigenvalues below tolerance.\n" "Reducing N from {1} to {2}{5} and S from {3} to {4}.")\ .format(diff + 1, self.N, Nnew, self.S, Snew, strM)) newParameters = {"N" : Nnew, "M" : Mnew, "S" : Snew} self.approxParameters = newParameters self.Q = eV[:, 0] - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("DEL", "Done computing denominator.") verbosityDepth("INIT", "Starting computation of numerator.") TNQ = TN.dot(self.Q).reshape((self.S, 1)) if self.POD: data = self.samplingEngine.samples.dot( self.samplingEngine.RPOD) else: data = self.samplingEngine.samples RHS = np.multiply(data.T, TNQ) self.P = np.polyfit(self.radiusPade(self.mus), RHS, deg = self.M, w = self.ws)[::-1, :].T - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("DEL", "Done computing numerator.") self.lastApproxParameters = copy(self.approxParameters) if hasattr(self, "lastSolvedApp"): del self.lastSolvedApp if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.\n") def radiusPade(self, mu:Np1D, mu0 : float = None) -> float: """ Compute translated radius to be plugged into Pade' approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.mu0. Returns: Translated radius to be plugged into Pade' approximant. """ if mu0 is None: mu0 = self.mu0 return self.HFEngine.rescaling(mu) - self.HFEngine.rescaling(mu0) def getPVal(self, mu:complex): """ Evaluate Pade' numerator at arbitrary parameter. Args: mu: Target parameter. """ self.setupApprox() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Evaluating numerator at mu = {}.".format(mu), end = "") powerlist = np.power(self.radiusPade(mu), np.arange(self.M + 1)) p = self.P.dot(powerlist) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", " Done.", inline = True) return p def getQVal(self, mu:complex): """ Evaluate Pade' denominator at arbitrary parameter. Args: mu: Target parameter. """ self.setupApprox() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Evaluating denominator at mu = {}.".format(mu), end = "") powerlist = np.power(self.radiusPade(mu), np.arange(self.N + 1)) q = self.Q.dot(powerlist) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", " Done.", inline = True) return q def evalApprox(self, mu:complex): """ Evaluate Pade' approximant at arbitrary parameter. Args: mu: Target parameter. """ self.setupApprox() if (not hasattr(self, "lastSolvedApp") or not np.isclose(self.lastSolvedApp, mu)): + if self.verbosity >= 5: + verbosityDepth("INIT", + "Evaluating approximant at mu = {}.".format(mu)) try: self.uApp = self.getPVal(mu) / self.getQVal(mu) except: self.uApp = np.empty(self.P.shape[0]) self.uApp[:] = np.nan self.lastSolvedApp = mu + if self.verbosity >= 5: + verbosityDepth("DEL", "Done evaluating approximant.") def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ self.setupApprox() return self.HFEngine.rescalingInv(np.roots(self.Q[::-1]) + self.HFEngine.rescaling(self.mu0)) diff --git a/rrompy/reduction_methods/lagrange/approximant_lagrange_rb.py b/rrompy/reduction_methods/lagrange/approximant_lagrange_rb.py index 4ccfff7..cfdc6d2 100644 --- a/rrompy/reduction_methods/lagrange/approximant_lagrange_rb.py +++ b/rrompy/reduction_methods/lagrange/approximant_lagrange_rb.py @@ -1,276 +1,282 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from copy import copy -import warnings import numpy as np import scipy as sp -from rrompy.reduction_methods.lagrange.generic_approximant_lagrange import GenericApproximantLagrange +from .generic_approximant_lagrange import GenericApproximantLagrange from rrompy.utilities.base.types import Np1D, DictAny, HFEng from rrompy.utilities.base import purgeDict, verbosityDepth +from rrompy.utilities.warning_manager import warn __all__ = ['ApproximantLagrangeRB'] class ApproximantLagrangeRB(GenericApproximantLagrange): """ ROM RB approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'S': total number of samples current approximant relies upon; defaults to 2; - 'sampler': sample point generator; defaults to uniform sampler on [0, 1]; - 'R': rank for Galerkin projection; defaults to S. Defaults to empty dict. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. ws: Array of snapshot weigths (unused). 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; - '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. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. R: Rank for Galerkin projection. POD: Whether to compute POD of snapshots. samplingEngine: Sampling engine. projMat: Projection matrix for RB system assembly. uHF: High fidelity solution with wavenumber lastSolvedHF as numpy complex vector. lastSolvedHF: Wavenumber corresponding to last computed high fidelity solution. - solLifting: Lifting of Dirichlet boundary data as numpy vector. uApp: Last evaluated approximant as numpy complex vector. lastApproxParameters: List of parameters corresponding to last computed approximant. 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 : complex = 0., - approxParameters : DictAny = {}, verbosity : int = 10): + approxParameters : DictAny = {}, homogeneize : bool = False, + verbosity : int = 10): self._preInit() if not hasattr(self, "parameterList"): self.parameterList = [] self.parameterList += ["R"] super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, + homogeneize = homogeneize, verbosity = verbosity) + if self.verbosity >= 10: + verbosityDepth("INIT", "Computing affine blocks of system.") self.As, self.thetaAs = self.HFEngine.affineBlocksA(self.mu0) - self.bs, self.thetabs = self.HFEngine.affineBlocksb(self.mu0) + self.bs, self.thetabs = self.HFEngine.affineBlocksb(self.mu0, + self.homogeneize) + if self.verbosity >= 10: + verbosityDepth("DEL", "Done computing affine blocks.") self._postInit() def resetSamples(self): """Reset samples.""" super().resetSamples() self.projMat = None @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) GenericApproximantLagrange.approxParameters.fset(self, approxParametersCopy) keyList = list(approxParameters.keys()) if "R" in keyList: self.R = approxParameters["R"] elif hasattr(self, "R"): self.R = self.R else: self.R = 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 ArithmeticError("R must be non-negative.") self._R = R self._approxParameters["R"] = self.R if hasattr(self, "S") and self.S < self.R: - warnings.warn("Prescribed S is too small. Updating S to R.", - stacklevel = 2) + warn("Prescribed S is too small. Updating S to R.") self.S = self.R 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.projMat is not None and super().checkComputedApprox()) def setupApprox(self): """Compute RB projection matrix.""" if not self.checkComputedApprox(): if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name())) self.computeSnapshots() - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("INIT", "Computing projection matrix.", end = "") if self.POD: U, _, _ = np.linalg.svd(self.samplingEngine.RPOD, full_matrices = False) self.projMat = self.samplingEngine.samples.dot(U[:, : self.R]) else: self.projMat = self.samplingEngine.samples[:, : self.R] - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("DEL", " Done.", inline = True) self.lastApproxParameters = copy(self.approxParameters) if hasattr(self, "lastSolvedApp"): del self.lastSolvedApp self.assembleReducedSystem() if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.\n") def assembleReducedSystem(self): """Build affine blocks of RB linear system through projections.""" self.setupApprox() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Projecting affine terms of HF model.", end = "") projMatH = self.projMat.T.conjugate() self.ARBs = [None] * len(self.As) self.bRBs = [None] * len(self.bs) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("MAIN", ".", end = "", inline = True) for j in range(len(self.As)): self.ARBs[j] = projMatH.dot(self.As[j].dot(self.projMat)) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("MAIN", ".", end = "", inline = True) for j in range(len(self.bs)): self.bRBs[j] = projMatH.dot(self.bs[j]) - if self.verbosity >= 5: - verbosityDepth("DEL", " Done.", inline = True) + if self.verbosity >= 10: + verbosityDepth("DEL", "Done.", inline = True) def solveReducedSystem(self, mu:complex) -> Np1D: """ Solve RB linear system. Args: mu: Target parameter. Returns: Solution of RB linear system. """ self.setupApprox() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Assembling reduced model for mu = {}.".format(mu), end = "") ARBmu = self.thetaAs(mu, 0) * self.ARBs[0][:self.R,:self.R] bRBmu = self.thetabs(mu, 0) * self.bRBs[0][:self.R] - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("MAIN", ".", end = "", inline = True) for j in range(1, len(self.ARBs)): ARBmu += self.thetaAs(mu, j) * self.ARBs[j][:self.R, :self.R] - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("MAIN", ".", end = "", inline = True) for j in range(1, len(self.bRBs)): bRBmu += self.thetabs(mu, j) * self.bRBs[j][:self.R] + if self.verbosity >= 10: + verbosityDepth("DEL", "Done.", inline = True) if self.verbosity >= 5: - verbosityDepth("DEL", " Done.", inline = True) verbosityDepth("INIT", "Solving reduced model for mu = {}.".format(mu), end = "") uRB = self.projMat[:, :self.R].dot(np.linalg.solve(ARBmu, bRBmu)) if self.verbosity >= 5: verbosityDepth("DEL", " Done.", inline = True) return uRB def evalApprox(self, mu:complex): """ Evaluate RB approximant at arbitrary wavenumber. Args: mu: Target parameter. """ self.setupApprox() if (not hasattr(self, "lastSolvedApp") or not np.isclose(self.lastSolvedApp, mu)): + if self.verbosity >= 5: + verbosityDepth("INIT", + "Computing RB solution at mu = {}.".format(mu)) self.uApp = self.solveReducedSystem(mu) self.lastSolvedApp = mu + if self.verbosity >= 5: + verbosityDepth("DEL", "Done computing RB solution.") def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ - warnings.warn(("Impossible to compute poles in general affine " - "parameter dependence. Results subject to " - "interpretation/rescaling, or possibly completely " - "wrong."), stacklevel = 2) + warn(("Impossible to compute poles in general affine parameter " + "dependence. Results subject to interpretation/rescaling, or " + "possibly completely wrong.")) self.setupApprox() + if len(self.ARBs) < 2: + return A = np.eye(self.ARBs[0].shape[0] * (len(self.ARBs) - 1), dtype = np.complex) B = np.zeros_like(A) A[: self.ARBs[0].shape[0], : self.ARBs[0].shape[0]] = - self.ARBs[0] for j in range(len(self.ARBs) - 1): Aj = self.ARBs[j + 1] B[: Aj.shape[0], j * Aj.shape[0] : (j + 1) * Aj.shape[0]] = Aj II = np.arange(self.ARBs[0].shape[0], self.ARBs[0].shape[0] * (len(self.ARBs) - 1)) B[II, II - self.ARBs[0].shape[0]] = 1. - try: - return sp.linalg.eigvals(A, B) - except: - warnings.warn("Generalized eig algorithm did not converge.", - stacklevel = 2) - x = np.empty(A.shape[0]) - x[:] = np.NaN - return x + return self.HFEngine.rescalingInv(sp.linalg.eigvals(A, B) + + self.HFEngine.rescaling(self.mu0)) diff --git a/rrompy/reduction_methods/lagrange/generic_approximant_lagrange.py b/rrompy/reduction_methods/lagrange/generic_approximant_lagrange.py index 88bba75..446a6fc 100644 --- a/rrompy/reduction_methods/lagrange/generic_approximant_lagrange.py +++ b/rrompy/reduction_methods/lagrange/generic_approximant_lagrange.py @@ -1,199 +1,202 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # import numpy as np -from rrompy.reduction_methods.base.generic_approximant import GenericApproximant +from rrompy.reduction_methods.base.generic_approximant import ( + GenericApproximant) from rrompy.utilities.base.types import DictAny, HFEng from rrompy.utilities.base import purgeDict, verbosityDepth __all__ = ['GenericApproximantLagrange'] class GenericApproximantLagrange(GenericApproximant): """ ROM Lagrange 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; - 'S': total number of samples current approximant relies upon; - 'sampler': sample point generator; defaults to uniform sampler on [0, 1]. Defaults to empty dict. Attributes: HFEngine: HF problem solver. mu0: Default parameter. mus: Array of snapshot parameters. ws: Array of snapshot weigths. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterList: Recognized keys of approximant parameters: - 'POD': whether to compute POD of snapshots; - 'S': total number of snapshots current approximant relies upon; - 'sampler': sample point generator. extraApproxParameters: List of approxParameters keys in addition to mother class's. S: Number of solution snapshots over which current approximant is based upon. sampler: Sample point generator. POD: Whether to compute POD of snapshots. samplingEngine: Sampling engine. uHF: High fidelity solution with wavenumber lastSolvedHF as numpy complex vector. lastSolvedHF: Wavenumber corresponding to last computed high fidelity solution. - solLifting: Lifting of Dirichlet boundary data as numpy vector. uApp: Last evaluated approximant as numpy complex vector. lastApproxParameters: List of parameters corresponding to last computed approximant. """ def __init__(self, HFEngine:HFEng, mu0 : complex = 0., - approxParameters : DictAny = {}, verbosity : int = 10): + approxParameters : DictAny = {}, homogeneize : bool = False, + verbosity : int = 10): self._preInit() if not hasattr(self, "parameterList"): self.parameterList = [] self.parameterList += ["S", "sampler"] super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, + homogeneize = homogeneize, verbosity = verbosity) self._postInit() def setupSampling(self): """Setup sampling engine.""" if not hasattr(self, "POD"): return if self.POD: from rrompy.sampling.scipy.sampling_engine_lagrange_pod import ( - SamplingEngineLagrangePOD as SamplingEngine) + SamplingEngineLagrangePOD) + super().setupSampling(SamplingEngineLagrangePOD) else: from rrompy.sampling.scipy.sampling_engine_lagrange import ( - SamplingEngineLagrange as SamplingEngine) - self.samplingEngine = SamplingEngine(self.HFEngine, - verbosity = self.verbosity) + SamplingEngineLagrange) + super().setupSampling(SamplingEngineLagrange) @property def mus(self): """Value of mus. Its assignment may reset snapshots.""" return self._mus @mus.setter def mus(self, mus): musOld = self.mus if hasattr(self, 'mus') else None self._mus = np.array(mus) _, musCounts = np.unique(self._mus, return_counts = True) if len(np.where(musCounts > 1)[0]) > 0: raise Exception("Repeated sample points not allowed.") if (musOld is None or len(self.mus) != len(musOld) or not np.allclose(self.mus, musOld, 1e-14)): self.resetSamples() self.autoNode = None @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, ["S", "sampler"], True, True, baselevel = 1) GenericApproximant.approxParameters.fset(self, approxParametersCopy) keyList = list(approxParameters.keys()) if "S" in keyList: self.S = approxParameters["S"] elif hasattr(self, "S"): self.S = self.S else: self.S = 2 if "sampler" in keyList: self.sampler = approxParameters["sampler"] elif not hasattr(self, "S"): from rrompy.utilities.parameter_sampling import QuadratureSampler self.sampler = QuadratureSampler([0., 1.], "UNIFORM") del QuadratureSampler @property def POD(self): """Value of POD.""" return self._POD @POD.setter def POD(self, POD): if hasattr(self, "POD"): PODold = self.POD else: PODold = -1 self._POD = POD self._approxParameters["POD"] = self.POD if PODold != self.POD: self.setupSampling() self.resetSamples() @property def S(self): """Value of S.""" return self._S @S.setter def S(self, S): if S <= 0: raise ArithmeticError("S must be positive.") if hasattr(self, "S"): Sold = self.S else: Sold = -1 self._S = S self._approxParameters["S"] = self.S if Sold != self.S: self.resetSamples() @property def sampler(self): """Value of sampler.""" return self._sampler @sampler.setter def sampler(self, sampler): if 'generatePoints' not in dir(sampler): raise Exception("Sampler type not recognized.") if hasattr(self, '_sampler'): samplerOld = self.sampler self._sampler = sampler self._approxParameters["sampler"] = self.sampler if not 'samplerOld' in locals() or samplerOld != self.sampler: self.resetSamples() def computeSnapshots(self): """ Compute snapshots of solution map. """ if self.samplingEngine.samples is None: if self.verbosity >= 5: verbosityDepth("INIT", "Starting computation of snapshots.") self.mus, self.ws = self.sampler.generatePoints(self.S) self.mus = np.array([x[0] for x in self.mus]) - self.samplingEngine.iterSample(self.mus) + self.samplingEngine.iterSample(self.mus, + homogeneize = self.homogeneize) if self.verbosity >= 5: verbosityDepth("DEL", "Done computing snapshots.") 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.samplingEngine.samples is not None and super().checkComputedApprox()) diff --git a/rrompy/reduction_methods/taylor/__init__.py b/rrompy/reduction_methods/taylor/__init__.py index 9c0745a..09d9bec 100644 --- a/rrompy/reduction_methods/taylor/__init__.py +++ b/rrompy/reduction_methods/taylor/__init__.py @@ -1,29 +1,29 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.reduction_methods.taylor.generic_approximant_taylor import GenericApproximantTaylor -from rrompy.reduction_methods.taylor.approximant_taylor_pade import ApproximantTaylorPade -from rrompy.reduction_methods.taylor.approximant_taylor_rb import ApproximantTaylorRB +from .generic_approximant_taylor import GenericApproximantTaylor +from .approximant_taylor_pade import ApproximantTaylorPade +from .approximant_taylor_rb import ApproximantTaylorRB __all__ = [ 'GenericApproximantTaylor', 'ApproximantTaylorPade', 'ApproximantTaylorRB' ] diff --git a/rrompy/reduction_methods/taylor/approximant_taylor_pade.py b/rrompy/reduction_methods/taylor/approximant_taylor_pade.py index 08b1dbb..9095d2a 100644 --- a/rrompy/reduction_methods/taylor/approximant_taylor_pade.py +++ b/rrompy/reduction_methods/taylor/approximant_taylor_pade.py @@ -1,577 +1,563 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from copy import copy -import warnings import numpy as np -from rrompy.reduction_methods.taylor.generic_approximant_taylor import ( - GenericApproximantTaylor) -from rrompy.sampling.scipy.pod_engine import PODEngine +from .generic_approximant_taylor import GenericApproximantTaylor +from rrompy.sampling.base.pod_engine import PODEngine from rrompy.utilities.base.types import Np1D, Np2D, Tuple, DictAny from rrompy.utilities.base.types import HFEng from rrompy.utilities.base import purgeDict, verbosityDepth +from rrompy.utilities.warning_manager import warn __all__ = ['ApproximantTaylorPade'] class ApproximantTaylorPade(GenericApproximantTaylor): """ ROM single-point fast Pade' approximant computation for parametric problems. Args: HFEngine: HF problem solver. mu0(optional): Default parameter. Defaults to 0. approxParameters(optional): Dictionary containing values for main parameters of approximant. Recognized keys are: - 'POD': whether to compute POD of snapshots; defaults to True; - 'rho': weight for computation of original Pade' approximant; defaults to np.inf, i.e. fast approximant; - 'M': degree of Pade' approximant numerator; defaults to 0; - 'N': degree of Pade' approximant denominator; defaults to 0; - 'E': total number of derivatives current approximant relies upon; defaults to Emax; - 'Emax': total number of derivatives of solution map to be computed; defaults to E; - 'robustTol': tolerance for robust Pade' denominator management; defaults to 0; - 'rescaleParam': whether to rescale parameter during denominator computation; defaults to True; - 'sampleType': label of sampling type; available values are: - 'ARNOLDI': orthogonalization of solution derivatives through Arnoldi algorithm; - 'KRYLOV': standard computation of solution derivatives. Defaults to 'KRYLOV'. Defaults to empty dict. Attributes: HFEngine: HF problem solver. mu0: Default parameter. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterList: Recognized keys of approximant parameters: - 'POD': whether to compute POD of snapshots; - 'rho': weight for computation of original Pade' approximant; - 'M': degree of Pade' approximant numerator; - 'N': degree of Pade' approximant denominator; - 'E': total number of derivatives current approximant relies upon; - 'Emax': total number of derivatives of solution map to be computed; - 'robustTol': tolerance for robust Pade' denominator management; - 'rescaleParam': whether to rescale parameter during denominator computation; - 'sampleType': label of sampling type. POD: Whether to compute QR factorization of derivatives. rho: Weight of approximant. M: Numerator degree of approximant. N: Denominator degree of approximant. E: Number of solution derivatives over which current approximant is based upon. Emax: Total number of solution derivatives to be computed. robustTol: Tolerance for robust Pade' denominator management. sampleType: Label of sampling type. initialHFData: HF problem initial data. uHF: High fidelity solution with wavenumber lastSolvedHF as numpy complex vector. lastSolvedHF: Wavenumber corresponding to last computed high fidelity solution. - solLifting: Lifting of Dirichlet boundary data as numpy vector. G: Square Numpy 2D vector of size (N+1) corresponding to Pade' denominator matrix (see paper). Q: Numpy 1D vector containing complex coefficients of approximant denominator. P: Numpy 2D vector whose columns are FE dofs of coefficients of approximant numerator. uApp: Last evaluated approximant as numpy complex vector. lastApproxParameters: List of parameters corresponding to last computed approximant. """ def __init__(self, HFEngine:HFEng, mu0 : complex = 0, - approxParameters : DictAny = {}, verbosity : int = 10): + approxParameters : DictAny = {}, homogeneize : bool = False, + verbosity : int = 10): self._preInit() if not hasattr(self, "parameterList"): self.parameterList = [] self.parameterList += ["M", "N", "robustTol", "rho", "rescaleParam"] super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, + homogeneize = homogeneize, verbosity = verbosity) self._postInit() @property def approxParameters(self): """Value of approximant parameters.""" return self._approxParameters @approxParameters.setter def approxParameters(self, approxParams): approxParameters = purgeDict(approxParams, self.parameterList, dictname = self.name() + ".approxParameters", baselevel = 1) approxParametersCopy = purgeDict(approxParameters, ["M", "N", "robustTol", "rho"], True, True, baselevel = 1) keyList = list(approxParameters.keys()) if "rho" in keyList: self._rho = approxParameters["rho"] elif hasattr(self, "rho"): self._rho = self.rho else: self._rho = np.inf GenericApproximantTaylor.approxParameters.fset(self, approxParametersCopy) self.rho = self._rho if "robustTol" in keyList: self.robustTol = approxParameters["robustTol"] elif hasattr(self, "robustTol"): self.robustTol = self.robustTol else: self.robustTol = 0 if "rescaleParam" in keyList: self.rescaleParam = approxParameters["rescaleParam"] elif hasattr(self, "rescaleParam"): self.rescaleParam = self.rescaleParam else: self.rescaleParam = True self._ignoreParWarnings = True if "M" in keyList: self.M = approxParameters["M"] elif hasattr(self, "M"): self.M = self.M else: self.M = 0 del self._ignoreParWarnings if "N" in keyList: self.N = approxParameters["N"] elif hasattr(self, "N"): self.N = self.N else: self.N = 0 @property def rho(self): """Value of rho.""" return self._rho @rho.setter def rho(self, rho): self._rho = np.abs(rho) self._approxParameters["rho"] = self.rho @property def M(self): """Value of M. Its assignment may change Emax and E.""" return self._M @M.setter def M(self, M): if M < 0: raise ArithmeticError("M must be non-negative.") self._M = M self._approxParameters["M"] = self.M if not hasattr(self, "_ignoreParWarnings"): self.checkMNEEmax() @property def N(self): """Value of N. Its assignment may change Emax.""" return self._N @N.setter def N(self, N): if N < 0: raise ArithmeticError("N must be non-negative.") self._N = N self._approxParameters["N"] = self.N self.checkMNEEmax() def checkMNEEmax(self): """Check consistency of M, N, E, and Emax.""" M = self.M if hasattr(self, "_M") else 0 N = self.N if hasattr(self, "_N") else 0 E = self.E if hasattr(self, "_E") else M + N Emax = self.Emax if hasattr(self, "_Emax") else M + N if self.rho == np.inf: if Emax < max(N, M): - warnings.warn(("Prescribed Emax is too small. Updating Emax " - "to max(M, N)."), stacklevel = 3) - from sys.stderr import flush - flush() - del flush + warn(("Prescribed Emax is too small. Updating Emax to " + "max(M, N).")) self.Emax = max(N, M) if E < max(N, M): - warnings.warn(("Prescribed E is too small. Updating E to " - "max(M, N)."), stacklevel = 3) - from sys.stderr import flush - flush() - del flush + warn("Prescribed E is too small. Updating E to max(M, N).") self.E = max(N, M) else: if Emax < N + M: - warnings.warn(("Prescribed Emax is too small. Updating " - "Emax to M + N."), stacklevel = 3) - from sys.stderr import flush - flush() - del flush + warn("Prescribed Emax is too small. Updating Emax to M + N.") self.Emax = self.N + M if E < N + M: - warnings.warn(("Prescribed E is too small. Updating E to " - "M + N."), stacklevel = 3) - from sys.stderr import flush - flush() - del flush + warn("Prescribed E is too small. Updating E to M + N.") self.E = self.N + M @property def robustTol(self): """Value of tolerance for robust Pade' denominator management.""" return self._robustTol @robustTol.setter def robustTol(self, robustTol): if robustTol < 0.: - warnings.warn(("Overriding prescribed negative robustness " - "tolerance to 0."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Overriding prescribed negative robustness tolerance to 0.") robustTol = 0. self._robustTol = robustTol self._approxParameters["robustTol"] = self.robustTol @property def rescaleParam(self): """Value of parameter rescaling during denominator computation.""" return self._rescaleParam @rescaleParam.setter def rescaleParam(self, rescaleParam): if not isinstance(rescaleParam, (bool,)): - warnings.warn(("Prescribed rescaleParam not recognized. " - "Overriding to True."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Prescribed rescaleParam not recognized. Overriding to True.") rescaleParam = True self._rescaleParam = rescaleParam self._approxParameters["rescaleParam"] = self.rescaleParam @property def E(self): """Value of E. Its assignment may change Emax.""" return self._E @E.setter def E(self, E): if E < 0: raise ArithmeticError("E must be non-negative.") self._E = E self.checkMNEEmax() self._approxParameters["E"] = self.E if hasattr(self, "Emax") and self.Emax < self.E: - warnings.warn(("Prescribed Emax is too small. Updating Emax " - "to E."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Prescribed Emax is too small. Updating Emax to E.") self.Emax = self.E @property def Emax(self): """Value of Emax. Its assignment may reset computed derivatives.""" return self._Emax @Emax.setter def Emax(self, Emax): if Emax < 0: raise ArithmeticError("Emax must be non-negative.") if hasattr(self, "Emax"): EmaxOld = self.Emax else: EmaxOld = -1 if hasattr(self, "_N"): N = self.N else: N = 0 if hasattr(self, "_M"): M = self.M else: M = 0 if hasattr(self, "_E"): E = self.E else: E = 0 if self.rho == np.inf: if max(N, M, E) > Emax: - warnings.warn(("Prescribed Emax is too small. Updating Emax " - "to max(N, M, E)."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn(("Prescribed Emax is too small. Updating Emax to " + "max(N, M, E).")) Emax = max(N, M, E) else: if max(N + M, E) > Emax: - warnings.warn(("Prescribed Emax is too small. Updating Emax " - "to max(N + M, E)."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn(("Prescribed Emax is too small. Updating Emax to " + "max(N + M, E).")) Emax = max(N + M, E) self._Emax = Emax self._approxParameters["Emax"] = Emax if EmaxOld >= self.Emax and self.samplingEngine.samples is not None: self.samplingEngine.samples = self.samplingEngine.samples[:, : self.Emax + 1] if (self.sampleType == "ARNOLDI" and self.samplingEngine.HArnoldi is not None): self.samplingEngine.HArnoldi = self.samplingEngine.HArnoldi[ : self.Emax + 1, : self.Emax + 1] self.samplingEngine.RArnoldi = self.samplingEngine.RArnoldi[ : self.Emax + 1, : self.Emax + 1] def setupApprox(self): """ Compute Pade' approximant. SVD-based robust eigenvalue management. """ if not self.checkComputedApprox(): if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name())) self.computeDerivatives() - if self.verbosity >= 5: - verbosityDepth("INIT", "Starting computation of denominator.") - while True: - if self.POD: - ev, eV = self.findeveVGQR() - else: - ev, eV = self.findeveVGExplicit() - ts = self.robustTol * np.linalg.norm(ev) - Nnew = np.sum(np.abs(ev) >= ts) - diff = self.N - Nnew - if diff <= 0: break - Enew = self.E - diff - Mnew = min(self.M, Enew) - if Mnew == self.M: - strM = "" - else: - strM = ", M from {0} to {1},".format(self.M, Mnew) - print(("Smallest {0} eigenvalues below tolerance.\n" - "Reducing N from {1} to {2}{5} and E from {3} to {4}.")\ - .format(diff + 1, self.N, Nnew, self.E, Enew, strM)) - newParameters = {"N" : Nnew, "M" : Mnew, "E" : Enew} - self.approxParameters = newParameters - self.Q = eV[::-1, 0] - if self.verbosity >= 5: - verbosityDepth("DEL", "Done computing denominator.") + if self.N > 0: + if self.verbosity >= 7: + verbosityDepth("INIT", ("Starting computation of " + "denominator.")) + while True: + if self.N == 0: + if self.verbosity >= 7: + verbosityDepth("DEL", + "Done computing denominator.") + self.Q = np.ones(1) + break + if self.POD: + ev, eV = self.findeveVGQR() + else: + ev, eV = self.findeveVGExplicit() + ts = self.robustTol * np.linalg.norm(ev) + Nnew = np.sum(np.abs(ev) >= ts) + diff = self.N - Nnew + if diff <= 0: break + Enew = self.E - diff + Mnew = min(self.M, Enew) + if Mnew == self.M: + strM = "" + else: + strM = ", M from {0} to {1},".format(self.M, Mnew) + print(("Smallest {0} eigenvalues below tolerance.\n" + "Reducing N from {1} to {2}{5} and E from {3} to " + "{4}.").format(diff + 1, self.N, Nnew, self.E, + Enew, strM)) + newParameters = {"N" : Nnew, "M" : Mnew, "E" : Enew} + self.approxParameters = newParameters + self.Q = eV[::-1, 0] + if self.verbosity >= 7: + verbosityDepth("DEL", "Done computing denominator.") + else: + self.Q = np.ones(1) + if self.verbosity >= 7: verbosityDepth("INIT", "Starting computation of numerator.") QToeplitz = np.zeros((self.Emax + 1, self.M + 1), dtype = np.complex) for i in range(len(self.Q)): rng = np.arange(self.M + 1 - i) QToeplitz[rng, rng + i] = self.Q[i] if self.sampleType == "ARNOLDI": QToeplitz = self.samplingEngine.RArnoldi.dot(QToeplitz) self.P = self.samplingEngine.samples.dot(QToeplitz) - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("DEL", "Done computing numerator.") self.lastApproxParameters = copy(self.approxParameters) if hasattr(self, "lastSolvedApp"): del self.lastSolvedApp if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.\n") def rescaleParameter(self, R:Np2D, A : Np2D = None, exponent : float = 1.) -> Np2D: """ Prepare parameter rescaling. Args: R: Matrix whose columns need rescaling. A(optional): Matrix whose diagonal defines scaling factor. If None, previous value of scaleFactor is used. Defaults to None. exponent(optional): Exponent of scaling factor in matrix diagonal. Defaults to 1. Returns: Rescaled matrix. """ if A is not None: aDiag = np.diag(A) scaleCoeffs = np.polyfit(np.arange(A.shape[1]), np.log(aDiag), 1) self.scaleFactor = np.exp(- scaleCoeffs[0] / exponent) return np.multiply(R, np.power(self.scaleFactor,np.arange(R.shape[1]))) def buildG(self): """Assemble Pade' denominator matrix.""" self.computeDerivatives() if self.rho == np.inf: Nmin = self.E - self.N else: Nmin = self.M - self.N + 1 if self.sampleType == "KRYLOV": DerE = self.samplingEngine.samples[:, Nmin : self.E + 1] G = self.HFEngine.innerProduct(DerE, DerE) if self.rescaleParam: DerE = self.rescaleParameter(DerE, G, 2.) G = self.HFEngine.innerProduct(DerE, DerE) else: RArnE = self.samplingEngine.RArnoldi[: self.E + 1, Nmin : self.E + 1] if self.rescaleParam: RArnE = self.rescaleParameter(RArnE, RArnE[Nmin :, :]) G = RArnE.conj().T.dot(RArnE) if self.rho == np.inf: self.G = G else: Gbig = G self.G = np.zeros((self.N + 1, self.N + 1), dtype = np.complex) for k in range(self.E - self.M): self.G += self.rho ** (2 * k) * Gbig[k : k + self.N + 1, k : k + self.N + 1] def findeveVGExplicit(self) -> Tuple[Np1D, Np2D]: """ Compute explicitly eigenvalues and eigenvectors of Pade' denominator matrix. """ - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Building gramian matrix.") self.buildG() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", "Done building gramian.") + if self.verbosity >= 7: verbosityDepth("INIT", "Solving eigenvalue problem for gramian matrix.") ev, eV = np.linalg.eigh(self.G) if self.rescaleParam: eV = self.rescaleParameter(eV.T).T - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("DEL", "Done solving eigenvalue problem.") return ev, eV def findeveVGQR(self) -> Tuple[Np1D, Np2D]: """ Compute eigenvalues and eigenvectors of Pade' denominator matrix through SVD of R factor. See ``Householder triangularization of a quasimatrix'', L.Trefethen, 2008 for QR algorithm. Returns: Eigenvalues in ascending order and corresponding eigenvector matrix. """ self.computeDerivatives() if self.rho == np.inf: Nmin = self.E - self.N else: Nmin = self.M - self.N + 1 if self.sampleType == "KRYLOV": A = copy(self.samplingEngine.samples[:, Nmin : self.E + 1]) self.PODEngine = PODEngine(self.HFEngine) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Orthogonalizing samples.", end = "") R = self.PODEngine.QRHouseholder(A, only_R = True) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", " Done.", inline = True) else: R = self.samplingEngine.RArnoldi[: self.E + 1, Nmin : self.E + 1] if self.rescaleParam: R = self.rescaleParameter(R, R[Nmin :, :]) if self.rho == np.inf: - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", ("Solving svd for square root of " "gramian matrix."), end = "") _, s, V = np.linalg.svd(R, full_matrices = False) else: - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", ("Building matrix stack for square " "root of gramian."), end = "") Rtower = np.zeros((R.shape[0] * (self.E - self.M), self.N + 1), dtype = np.complex) for k in range(self.E - self.M): Rtower[k * R.shape[0] : (k + 1) * R.shape[0], :] = ( self.rho ** k * R[:, self.M - self.N + 1 + k : self.M + 1 + k]) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", " Done.", inline = True) + if self.verbosity >= 7: verbosityDepth("INIT", ("Solving svd for square root of " "gramian matrix."), end = "") _, s, V = np.linalg.svd(Rtower, full_matrices = False) eV = V.conj().T[:, ::-1] if self.rescaleParam: eV = self.rescaleParameter(eV.T).T - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("DEL", " Done.", inline = True) return s[::-1], eV def radiusPade(self, mu:Np1D, mu0 : float = None) -> float: """ Compute translated radius to be plugged into Pade' approximant. Args: mu: Parameter(s) 1. mu0: Parameter(s) 2. If None, set to self.mu0. Returns: Translated radius to be plugged into Pade' approximant. """ if mu0 is None: mu0 = self.mu0 return self.HFEngine.rescaling(mu) - self.HFEngine.rescaling(mu0) def getPVal(self, mu:complex): """ Evaluate Pade' numerator at arbitrary parameter. Args: mu: Target parameter. """ self.setupApprox() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Evaluating numerator at mu = {}.".format(mu), end = "") powerlist = np.power(self.radiusPade(mu), np.arange(self.M + 1)) p = self.P.dot(powerlist) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", " Done.", inline = True) return p def getQVal(self, mu:complex): """ Evaluate Pade' denominator at arbitrary parameter. Args: mu: Target parameter. """ self.setupApprox() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Evaluating denominator at mu = {}.".format(mu), end = "") powerlist = np.power(self.radiusPade(mu), np.arange(self.N + 1)) q = self.Q.dot(powerlist) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", " Done.", inline = True) return q def evalApprox(self, mu:complex): """ Evaluate Pade' approximant at arbitrary parameter. Args: mu: Target parameter. """ self.setupApprox() if (not hasattr(self, "lastSolvedApp") or not np.isclose(self.lastSolvedApp, mu)): + if self.verbosity >= 5: + verbosityDepth("INIT", + "Evaluating approximant at mu = {}.".format(mu)) try: self.uApp = self.getPVal(mu) / self.getQVal(mu) except: self.uApp = np.empty(self.P.shape[0]) self.uApp[:] = np.nan self.lastSolvedApp = mu + if self.verbosity >= 5: + verbosityDepth("DEL", "Done evaluating approximant.") def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ self.setupApprox() return self.HFEngine.rescalingInv(np.roots(self.Q[::-1]) + self.HFEngine.rescaling(self.mu0)) diff --git a/rrompy/reduction_methods/taylor/approximant_taylor_rb.py b/rrompy/reduction_methods/taylor/approximant_taylor_rb.py index 4e81634..6e1044c 100644 --- a/rrompy/reduction_methods/taylor/approximant_taylor_rb.py +++ b/rrompy/reduction_methods/taylor/approximant_taylor_rb.py @@ -1,312 +1,302 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from copy import copy -import warnings import numpy as np import scipy as sp -from rrompy.reduction_methods.taylor.generic_approximant_taylor import GenericApproximantTaylor -from rrompy.sampling.scipy.pod_engine import PODEngine +from .generic_approximant_taylor import GenericApproximantTaylor +from rrompy.sampling.base.pod_engine import PODEngine from rrompy.utilities.base.types import Np1D, DictAny, HFEng from rrompy.utilities.base import purgeDict, verbosityDepth +from rrompy.utilities.warning_manager import warn __all__ = ['ApproximantTaylorRB'] class ApproximantTaylorRB(GenericApproximantTaylor): """ 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; - 'R': rank for Galerkin projection; defaults to E + 1; - 'E': total number of derivatives current approximant relies upon; defaults to Emax; - 'Emax': total number of derivatives of solution map to be computed; defaults to E; - 'sampleType': label of sampling type; available values are: - 'ARNOLDI': orthogonalization of solution derivatives through Arnoldi algorithm; - 'KRYLOV': standard computation of solution derivatives. Defaults to 'KRYLOV'. Defaults to empty dict. Attributes: HFEngine: HF problem solver. mu0: Default parameter. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterList: Recognized keys of approximant parameters: - 'POD': whether to compute POD of snapshots; - 'R': rank for Galerkin projection; - 'E': total number of derivatives current approximant relies upon; - 'Emax': total number of derivatives of solution map to be computed; - 'sampleType': label of sampling type. POD: Whether to compute QR factorization of derivatives. R: Rank for Galerkin projection. E: Number of solution derivatives over which current approximant is based upon. Emax: Total number of solution derivatives to be computed. sampleType: Label of sampling type, i.e. 'KRYLOV'. uHF: High fidelity solution with wavenumber lastSolvedHF as numpy complex vector. lastSolvedHF: Wavenumber corresponding to last computed high fidelity solution. uApp: Last evaluated approximant as numpy complex vector. lastApproxParameters: List of parameters corresponding to last computed approximant. - solLifting: Numpy complex vector with lifting of real part of Dirichlet - boundary datum. projMat: Numpy matrix representing projection onto RB space. projMat: Numpy matrix representing projection onto RB space. As: List of sparse matrices (in CSC format) representing coefficients of linear system matrix wrt mu. bs: List of numpy vectors representing coefficients of linear system RHS wrt mu. 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 : complex = 0, - approxParameters : DictAny = {}, verbosity : int = 10): + approxParameters : DictAny = {}, homogeneize : bool = False, + verbosity : int = 10): self._preInit() if not hasattr(self, "parameterList"): self.parameterList = [] self.parameterList += ["R"] super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, + homogeneize = homogeneize, verbosity = verbosity) + if self.verbosity >= 10: + verbosityDepth("INIT", "Computing affine blocks of system.") self.As, self.thetaAs = self.HFEngine.affineBlocksA(self.mu0) - self.bs, self.thetabs = self.HFEngine.affineBlocksb(self.mu0) + self.bs, self.thetabs = self.HFEngine.affineBlocksb(self.mu0, + self.homogeneize) + if self.verbosity >= 10: + verbosityDepth("DEL", "Done computing affine blocks.") self._postInit() def resetSamples(self): """Reset samples.""" super().resetSamples() self.projMat = None @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) GenericApproximantTaylor.approxParameters.fset(self, approxParametersCopy) keyList = list(approxParameters.keys()) if "R" in keyList: self.R = approxParameters["R"] else: self.R = self.E + 1 @property def POD(self): """Value of POD.""" return self._POD @POD.setter def POD(self, POD): GenericApproximantTaylor.POD.fset(self, POD) if (hasattr(self, "sampleType") and self.sampleType == "ARNOLDI" and not self.POD): - warnings.warn(("Arnoldi sampling implicitly forces POD-type " - "derivative management."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn(("Arnoldi sampling implicitly forces POD-type derivative " + "management.")) @property def sampleType(self): """Value of sampleType.""" return self._sampleType @sampleType.setter def sampleType(self, sampleType): GenericApproximantTaylor.sampleType.fset(self, sampleType) if (hasattr(self, "POD") and not self.POD and self.sampleType == "ARNOLDI"): - warnings.warn(("Arnoldi sampling implicitly forces POD-type " - "derivative management."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn(("Arnoldi sampling implicitly forces POD-type derivative " + "management.")) @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 ArithmeticError("R must be non-negative.") self._R = R self._approxParameters["R"] = self.R if hasattr(self, "E") and self.E + 1 < self.R: - warnings.warn("Prescribed E is too small. Updating E to R - 1.", - stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Prescribed E is too small. Updating E to R - 1.") self.E = self.R - 1 def setupApprox(self): """Setup RB system.""" if not self.checkComputedApprox(): if self.verbosity >= 5: verbosityDepth("INIT", "Setting up {}.". format(self.name())) self.computeDerivatives() - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("INIT", "Computing projection matrix.", end = "") if self.POD and not self.sampleType == "ARNOLDI": self.PODEngine = PODEngine(self.HFEngine) self.projMatQ, self.projMatR = self.PODEngine.QRHouseholder( self.samplingEngine.samples) if self.POD: if self.sampleType == "ARNOLDI": self.projMatR = self.samplingEngine.RArnoldi self.projMatQ = self.samplingEngine.samples U, _, _ = np.linalg.svd(self.projMatR[: self.E + 1, : self.E + 1]) self.projMat = self.projMatQ[:, : self.E + 1].dot(U[:, : self.R]) else: self.projMat = self.samplingEngine.samples[:, : self.R + 1] - if self.verbosity >= 5: + if self.verbosity >= 7: verbosityDepth("DEL", " Done.", inline = True) self.lastApproxParameters = copy(self.approxParameters) if hasattr(self, "lastSolvedApp"): del self.lastSolvedApp self.assembleReducedSystem() if self.verbosity >= 5: verbosityDepth("DEL", "Done setting up approximant.\n") def assembleReducedSystem(self): """Build affine blocks of RB linear system through projections.""" if not self.checkComputedApprox(): self.setupApprox() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Projecting affine terms of HF model.", end = "") projMatH = self.projMat.T.conj() self.ARBs = [None] * len(self.As) self.bRBs = [None] * len(self.bs) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("MAIN", ".", end = "", inline = True) for j in range(len(self.As)): self.ARBs[j] = projMatH.dot(self.As[j].dot(self.projMat)) - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("MAIN", ".", end = "", inline = True) for j in range(len(self.bs)): self.bRBs[j] = projMatH.dot(self.bs[j]) - if self.verbosity >= 5: - verbosityDepth("DEL", " Done.", inline = True) + if self.verbosity >= 10: + verbosityDepth("DEL", "Done.", inline = True) def solveReducedSystem(self, mu:complex) -> Np1D: """ Solve RB linear system. Args: mu: Target parameter. Returns: Solution of RB linear system. """ self.setupApprox() - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Assembling reduced model for mu = {}.".format(mu), end = "") ARBmu = self.thetaAs(mu, 0) * self.ARBs[0][:self.R,:self.R] bRBmu = self.thetabs(mu, 0) * self.bRBs[0][:self.R] - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("MAIN", ".", end = "", inline = True) for j in range(1, len(self.ARBs)): ARBmu += self.thetaAs(mu, j) * self.ARBs[j][:self.R, :self.R] - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("MAIN", ".", end = "", inline = True) for j in range(1, len(self.bRBs)): bRBmu += self.thetabs(mu, j) * self.bRBs[j][:self.R] + if self.verbosity >= 10: + verbosityDepth("DEL", "Done.", inline = True) if self.verbosity >= 5: - verbosityDepth("DEL", " Done.", inline = True) verbosityDepth("INIT", "Solving reduced model for mu = {}.".format(mu), end = "") uRB = self.projMat[:, :self.R].dot(np.linalg.solve(ARBmu, bRBmu)) if self.verbosity >= 5: verbosityDepth("DEL", " Done.", inline = True) return uRB def evalApprox(self, mu:complex): """ Evaluate RB approximant at arbitrary wavenumber. Args: mu: Target parameter. """ self.setupApprox() if (not hasattr(self, "lastSolvedApp") or not np.isclose(self.lastSolvedApp, mu)): + if self.verbosity >= 5: + verbosityDepth("INIT", + "Computing RB solution at mu = {}.".format(mu)) self.uApp = self.solveReducedSystem(mu) self.lastSolvedApp = mu + if self.verbosity >= 5: + verbosityDepth("DEL", "Done computing RB solution.") def getPoles(self) -> Np1D: """ Obtain approximant poles. Returns: Numpy complex vector of poles. """ - warnings.warn(("Impossible to compute poles in general affine " - "parameter dependence. Results subject to " - "interpretation/rescaling, or possibly completely " - "wrong."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn(("Impossible to compute poles in general affine parameter " + "dependence. Results subject to interpretation/rescaling, or " + "possibly completely wrong.")) self.setupApprox() + if len(self.ARBs) < 2: + return A = np.eye(self.ARBs[0].shape[0] * (len(self.ARBs) - 1), dtype = np.complex) B = np.zeros_like(A) A[: self.ARBs[0].shape[0], : self.ARBs[0].shape[0]] = - self.ARBs[0] for j in range(len(self.ARBs) - 1): Aj = self.ARBs[j + 1] B[: Aj.shape[0], j * Aj.shape[0] : (j + 1) * Aj.shape[0]] = Aj II = np.arange(self.ARBs[0].shape[0], self.ARBs[0].shape[0] * (len(self.ARBs) - 1)) B[II, II - self.ARBs[0].shape[0]] = 1. - try: - return sp.linalg.eigvals(A, B) - except: - warnings.warn("Generalized eig algorithm did not converge.", - stacklevel = 2) - from sys.stderr import flush - flush() - del flush - x = np.empty(A.shape[0]) - x[:] = np.NaN - return x + return self.HFEngine.rescalingInv(sp.linalg.eigvals(A, B) + + self.HFEngine.rescaling(self.mu0)) diff --git a/rrompy/reduction_methods/taylor/generic_approximant_taylor.py b/rrompy/reduction_methods/taylor/generic_approximant_taylor.py index c8b1f6c..a8e5c55 100644 --- a/rrompy/reduction_methods/taylor/generic_approximant_taylor.py +++ b/rrompy/reduction_methods/taylor/generic_approximant_taylor.py @@ -1,235 +1,227 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -import warnings -from rrompy.reduction_methods.base.generic_approximant import GenericApproximant +from rrompy.reduction_methods.base.generic_approximant import ( + GenericApproximant) from rrompy.utilities.base.types import DictAny, HFEng from rrompy.utilities.base import purgeDict, verbosityDepth +from rrompy.utilities.warning_manager import warn __all__ = ['GenericApproximantTaylor'] class GenericApproximantTaylor(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; - 'E': total number of derivatives current approximant relies upon; defaults to Emax; - 'Emax': total number of derivatives of solution map to be computed; defaults to E; - 'sampleType': label of sampling type; available values are: - 'ARNOLDI': orthogonalization of solution derivatives through Arnoldi algorithm; - 'KRYLOV': standard computation of solution derivatives. Defaults to 'KRYLOV'. Defaults to empty dict. Attributes: HFEngine: HF problem solver. mu0: Default parameter. approxParameters: Dictionary containing values for main parameters of approximant. Recognized keys are in parameterList. parameterList: Recognized keys of approximant parameters: - 'POD': whether to compute POD of snapshots; - 'E': total number of derivatives current approximant relies upon; - 'Emax': total number of derivatives of solution map to be computed; - 'sampleType': label of sampling type. POD: Whether to compute QR factorization of derivatives. E: Number of solution derivatives over which current approximant is based upon. Emax: Total number of solution derivatives to be computed. sampleType: Label of sampling type. initialHFData: HF problem initial data. samplingEngine: Sampling engine. uHF: High fidelity solution with wavenumber lastSolvedHF as numpy complex vector. lastSolvedHF: Wavenumber corresponding to last computed high fidelity solution. - solLifting: Lifting of Dirichlet boundary data as numpy vector. uApp: Last evaluated approximant as numpy complex vector. lastApproxParameters: List of parameters corresponding to last computed approximant. """ def __init__(self, HFEngine:HFEng, mu0 : complex = 0, - approxParameters : DictAny = {}, verbosity : int = 10): + approxParameters : DictAny = {}, homogeneize : bool = False, + verbosity : int = 10): self._preInit() if not hasattr(self, "parameterList"): self.parameterList = [] self.parameterList += ["E", "Emax", "sampleType"] super().__init__(HFEngine = HFEngine, mu0 = mu0, approxParameters = approxParameters, + homogeneize = homogeneize, verbosity = verbosity) self._postInit() def setupSampling(self): """Setup sampling engine.""" if not hasattr(self, "sampleType"): return if self.sampleType == "ARNOLDI": from rrompy.sampling.scipy.sampling_engine_arnoldi import ( - SamplingEngineArnoldi as SamplingEngine) + SamplingEngineArnoldi) + super().setupSampling(SamplingEngineArnoldi) elif self.sampleType == "KRYLOV": from rrompy.sampling.scipy.sampling_engine_krylov import ( - SamplingEngineKrylov as SamplingEngine) + SamplingEngineKrylov) + super().setupSampling(SamplingEngineKrylov) else: raise Exception("Sample type not recognized.") - self.samplingEngine = SamplingEngine(self.HFEngine, - verbosity = self.verbosity) @property def approxParameters(self): """ Value of approximant parameters. Its assignment may change E and Emax. """ return self._approxParameters @approxParameters.setter def approxParameters(self, approxParams): approxParameters = purgeDict(approxParams, self.parameterList, dictname = self.name() + ".approxParameters", baselevel = 1) approxParametersCopy = purgeDict(approxParameters, ["E", "Emax", "sampleType"], True, True, baselevel = 1) GenericApproximant.approxParameters.fset(self, approxParametersCopy) keyList = list(approxParameters.keys()) if "E" in keyList: self._E = approxParameters["E"] self._approxParameters["E"] = self.E if "Emax" in keyList: self.Emax = approxParameters["Emax"] else: if not hasattr(self, "Emax"): self.Emax = self.E else: self.Emax = self.Emax else: if "Emax" in keyList: self._E = approxParameters["Emax"] self._approxParameters["E"] = self.E self.Emax = self.E else: if not (hasattr(self, "Emax") and hasattr(self, "E")): raise Exception("At least one of E and Emax must be set.") if "sampleType" in keyList: self.sampleType = approxParameters["sampleType"] elif hasattr(self, "sampleType"): self.sampleType = self.sampleType else: self.sampleType = "KRYLOV" @property def E(self): """Value of E. Its assignment may change Emax.""" return self._E @E.setter def E(self, E): if E < 0: raise ArithmeticError("E must be non-negative.") self._E = E self._approxParameters["E"] = self.E if hasattr(self, "Emax") and self.Emax < self.E: - warnings.warn("Prescribed E is too large. Updating Emax to E.", - stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Prescribed E is too large. Updating Emax to E.") self.Emax = self.E @property def Emax(self): """Value of Emax. Its assignment may reset computed derivatives.""" return self._Emax @Emax.setter def Emax(self, Emax): if Emax < 0: raise ArithmeticError("Emax must be non-negative.") if hasattr(self, "Emax"): EmaxOld = self.Emax else: EmaxOld = -1 self._Emax = Emax if hasattr(self, "E") and self.Emax < self.E: - warnings.warn("Prescribed Emax is too small. Updating Emax to E.", - stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Prescribed Emax is too small. Updating Emax to E.") self.Emax = self.E else: self._approxParameters["Emax"] = self.Emax if (EmaxOld >= self.Emax and self.samplingEngine.samples is not None): self.samplingEngine.samples = self.samplingEngine.samples[:, : self.Emax + 1] if (self.sampleType == "ARNOLDI" and self.samplingEngine.HArnoldi is not None): self.samplingEngine.HArnoldi= self.samplingEngine.HArnoldi[ : self.Emax + 1, : self.Emax + 1] self.samplingEngine.RArnoldi= self.samplingEngine.RArnoldi[ : self.Emax + 1, : self.Emax + 1] else: self.resetSamples() @property def sampleType(self): """Value of sampleType.""" return self._sampleType @sampleType.setter def sampleType(self, sampleType): if hasattr(self, "sampleType"): sampleTypeOld = self.sampleType else: sampleTypeOld = -1 try: sampleType = sampleType.upper().strip().replace(" ","") if sampleType not in ["ARNOLDI", "KRYLOV"]: raise Exception("Sample type not recognized.") self._sampleType = sampleType except: - warnings.warn(("Prescribed sampleType not recognized. Overriding " - "to 'KRYLOV'."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn(("Prescribed sampleType not recognized. Overriding to " + "'KRYLOV'.")) self._sampleType = "KRYLOV" self._approxParameters["sampleType"] = self.sampleType if sampleTypeOld != self.sampleType: self.resetSamples() def computeDerivatives(self): """Compute derivatives of solution map starting from order 0.""" if self.samplingEngine.samples is None: if self.verbosity >= 5: verbosityDepth("INIT", "Starting computation of derivatives.") - self.samplingEngine.iterSample(self.mu0, self.Emax + 1) + self.samplingEngine.iterSample(self.mu0, self.Emax + 1, + homogeneize = self.homogeneize) if self.verbosity >= 5: verbosityDepth("DEL", "Done computing derivatives.") 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.samplingEngine.samples is not None and super().checkComputedApprox()) diff --git a/rrompy/sampling/base/__init__.py b/rrompy/sampling/base/__init__.py index a8d83f1..09e673f 100644 --- a/rrompy/sampling/base/__init__.py +++ b/rrompy/sampling/base/__init__.py @@ -1,27 +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 <http://www.gnu.org/licenses/>. # -from rrompy.sampling.base.pod_engine_base import PODEngineBase -from rrompy.sampling.base.sampling_engine_base import SamplingEngineBase +from .pod_engine import PODEngine +from .sampling_engine_base import SamplingEngineBase __all__ = [ - 'PODEngineBase', + 'PODEngine', 'SamplingEngineBase' ] diff --git a/rrompy/sampling/scipy/pod_engine.py b/rrompy/sampling/base/pod_engine.py similarity index 94% rename from rrompy/sampling/scipy/pod_engine.py rename to rrompy/sampling/base/pod_engine.py index b8a5474..a376ea7 100644 --- a/rrompy/sampling/scipy/pod_engine.py +++ b/rrompy/sampling/base/pod_engine.py @@ -1,140 +1,147 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # import numpy as np from copy import copy -from rrompy.sampling.base.pod_engine_base import PODEngineBase from rrompy.utilities.base.types import Np1D, Np2D, Tuple, HFEng __all__ = ['PODEngine'] -class PODEngine(PODEngineBase): +class PODEngine: """ POD engine for general matrix orthogonalization. - - Args/Attributes: - HFEngine: HF problem solver. """ def __init__(self, HFEngine:HFEng): self.HFEngine = HFEngine + def name(self) -> str: + return self.__class__.__name__ + + def __str__(self) -> str: + return self.name() + + def norm(self, a:Np1D) -> float: + """Compute norm of a Hilbert space object.""" + pass + def GS(self, a:Np1D, Q:Np2D, n : int = None, aA:Np1D = None, QA:Np2D = None) -> Tuple[Np1D, Np1D, Np1D]: """ Compute 1 Gram-Schmidt step with given projector. Args: a: vector to be projected; Q: orthogonal projection matrix; n: number of columns of Q to be considered; aA: augmented components of vector to be projected; QA: augmented components of projection matrix. Returns: Resulting normalized vector, coefficients of a wrt the updated basis. """ if n is None: n = Q.shape[1] if aA is None != QA is None: raise Exception(("Either both or none of augmented components " "must be provided.")) r = np.zeros((n + 1,), dtype = a.dtype) if n > 0: Q = Q[:, : n] for j in range(2): # twice is enough! nu = self.HFEngine.innerProduct(a, Q) a = a - Q.dot(nu) if aA is not None: aA = aA - QA.dot(nu) r[:-1] = r[:-1] + nu r[-1] = self.HFEngine.norm(a) if np.isclose(np.abs(r[-1]), 0.): r[-1] = 1. a = a / r[-1] if aA is not None: aA = aA / r[-1] return a, r, aA def QRGramSchmidt(self, A:Np2D, only_R : bool = False) -> Tuple[Np1D, Np1D]: """ Compute QR decomposition of a matrix through Gram-Schmidt method. Args: A: matrix to be decomposed; only_R(optional): whether to skip reconstruction of Q; defaults to False. Returns: Resulting orthogonal and upper-triangular factors. """ N = A.shape[1] Q = np.zeros_like(A, dtype = A.dtype) R = np.zeros((N, N), dtype = A.dtype) for k in range(N): Q[:, k], R[: k + 1, k], _ = self.GS(A[:, k], Q, k) if only_R: return R return Q, R def QRHouseholder(self, A:Np2D, Q0 : Np2D = None, only_R : bool = False) -> Tuple[Np1D, Np1D]: """ Compute QR decomposition of a matrix through Householder method. Args: A: matrix to be decomposed; Q0(optional): initial orthogonal guess for Q; defaults to random; only_R(optional): whether to skip reconstruction of Q; defaults to False. Returns: Resulting (orthogonal and )upper-triangular factor(s). """ B = copy(A) N = B.shape[1] V = np.zeros_like(B, dtype = B.dtype) R = np.zeros((N, N), dtype = B.dtype) if Q0 is None: Q = np.zeros_like(B, dtype = B.dtype) + np.random.randn(*(B.shape)) else: Q = copy(Q0) for k in range(N): if Q0 is None: Q[:, k], _, _ = self.GS(Q[:, k], Q, k) a = B[:, k] R[k, k] = self.HFEngine.norm(a) alpha = self.HFEngine.innerProduct(a, Q[:, k]) if np.isclose(np.abs(alpha), 0.): s = 1. else: s = - alpha / np.abs(alpha) Q[:, k] = s * Q[:, k] V[:, k], _, _ = self.GS(R[k, k] * Q[:, k] - a, Q, k) J = np.arange(k + 1, N) vtB = self.HFEngine.innerProduct(B[:, J], V[:, k]) B[:, J] = B[:, J] - 2 * np.outer(V[:, k], vtB) R[k, J] = self.HFEngine.innerProduct(B[:, J], Q[:, k]) B[:, J] = B[:, J] - np.outer(Q[:, k], R[k, J]) if only_R: return R for k in range(N - 1, -1, -1): J = np.arange(k, N) vtQ = self.HFEngine.innerProduct(Q[:, J], V[:, k]) Q[:, J] = Q[:, J] - 2 * np.outer(V[:, k], vtQ) return Q, R + diff --git a/rrompy/sampling/base/pod_engine_base.py b/rrompy/sampling/base/pod_engine_base.py deleted file mode 100644 index 9356768..0000000 --- a/rrompy/sampling/base/pod_engine_base.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (C) 2018 by the RROMPy authors -# -# This file is part of RROMPy. -# -# RROMPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# RROMPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with RROMPy. If not, see <http://www.gnu.org/licenses/>. -# - -from rrompy.utilities.base.types import HS1D, HS2D, Tuple - -__all__ = ['PODEngineBase'] - -class PODEngineBase: - """ - POD engine for general matrix orthogonalization. ABSTRACT. - """ - - def name(self) -> str: - return self.__class__.__name__ - - def __str__(self) -> str: - return self.name() - - def norm(self, a:HS1D) -> float: - """Compute norm of a Hilbert space object.""" - pass - - def GS(self, a:HS1D, Q:HS2D, n : int = None, - aA:HS1D = None, QA:HS2D = None) -> Tuple[HS1D, HS1D, HS1D]: - """ - Compute 1 Gram-Schmidt step with given projector. - - Args: - a: Hilbert space object to be projected; - Q: orthogonal projection Hilbert space quasi-matrix; - n: number of columns of Q to be considered; - aA: augmented components of Hilbert space object to be projected; - QA: augmented components of Hilbert space object projection - quasi-matrix. - - Returns: - Resulting normalized Hilbert space object, coefficients of a wrt - the updated basis. - """ - pass - - def QRGramSchmidt(self, A:HS2D, - only_R : bool = False) -> Tuple[HS1D, HS1D]: - """ - Compute QR decomposition of a matrix through Gram-Schmidt method. - - Args: - A: Hilbert space quasi-matrix to be decomposed; - only_R(optional): whether to skip reconstruction of Q; defaults to - False. - - Returns: - Resulting orthogonal and upper-triangular quasi-factors. - """ - pass - - def QRHouseholder(self, A:HS2D, Q0 : HS2D = None, - only_R : bool = False) -> Tuple[HS1D, HS1D]: - """ - Compute QR decomposition of a matrix through Householder method. - - Args: - A: Hilbert space quasi-matrix to be decomposed; - Q0(optional): initial orthogonal guess for Q; defaults to random; - only_R(optional): whether to skip reconstruction of Q; defaults to - False. - - Returns: - Resulting (orthogonal and )upper-triangular quasi-factor(s). - """ - pass - diff --git a/rrompy/sampling/base/sampling_engine_base.py b/rrompy/sampling/base/sampling_engine_base.py index ab1c51a..b5c6066 100644 --- a/rrompy/sampling/base/sampling_engine_base.py +++ b/rrompy/sampling/base/sampling_engine_base.py @@ -1,97 +1,102 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.utilities.base.types import HS1D, HFEng, strLst +from rrompy.utilities.base.types import Np1D, HFEng, strLst from rrompy.utilities.base import verbosityDepth __all__ = ['SamplingEngineBase'] class SamplingEngineBase: """HERE""" nameBase = 0 def __init__(self, HFEngine:HFEng, verbosity : int = 10): self.verbosity = verbosity - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", "Initializing sampling engine of type {}.".format( self.name()), end = "") self.HFEngine = HFEngine - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("DEL", " Done.", inline = True) def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() def resetHistory(self): self.samples = None self.nsamples = 0 @property def HFEngine(self): """Value of HFEngine. Its assignment resets history.""" return self._HFEngine @HFEngine.setter def HFEngine(self, HFEngine): self._HFEngine = HFEngine self.resetHistory() - def solveLS(self, mu:complex, RHS : HS1D = None) -> HS1D: + def solveLS(self, mu:complex, RHS : Np1D = None, + homogeneized : bool = False) -> Np1D: """ Solve linear system. Args: mu: Parameter value. Returns: Solution of system. """ if self.verbosity >= 5: - verbosityDepth("INIT", "Solving HF model for mu = {}.".format(mu), - end = "") - u = self.HFEngine.solve(mu, RHS) + verbosityDepth("INIT", "Solving HF model for mu = {}.".format(mu)) + u = self.HFEngine.solve(mu, RHS, homogeneized) if self.verbosity >= 5: - verbosityDepth("DEL", " Done.", inline = True) + verbosityDepth("DEL", "Done solving HF model.") return u - def plotSamples(self, name : str = "u", save : bool = False, - what : strLst = 'all', **figspecs): + def plotSamples(self, name : str = "u", save : str = None, + what : strLst = 'all', saveFormat : str = "eps", + saveDPI : int = 100, **figspecs): """ Do some nice plots of the samples. Args: name(optional): Name to be shown as title of the plots. Defaults to 'u'. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. what(optional): Which plots to do. If list, can contain 'ABS', 'PHASE', 'REAL', 'IMAG'. If str, same plus wildcard 'ALL'. Defaults to 'ALL'. - save(optional): Whether to save plot(s). Defaults to False. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. figspecs(optional key args): Optional arguments for matplotlib figure creation. """ for j in range(self.nsamples): self.HFEngine.plot(self.samples[:, j], name = "{}_{}".format(name, j + self.nameBase), - what = what, **figspecs) - + save = save, what = what, + saveFormat = saveFormat, saveDPI = saveDPI, + **figspecs) diff --git a/rrompy/sampling/scipy/__init__.py b/rrompy/sampling/scipy/__init__.py index 1c1b986..31e0790 100644 --- a/rrompy/sampling/scipy/__init__.py +++ b/rrompy/sampling/scipy/__init__.py @@ -1,33 +1,31 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.sampling.scipy.sampling_engine_krylov import SamplingEngineKrylov -from rrompy.sampling.scipy.sampling_engine_arnoldi import SamplingEngineArnoldi -from rrompy.sampling.scipy.sampling_engine_lagrange import SamplingEngineLagrange -from rrompy.sampling.scipy.sampling_engine_lagrange_pod import SamplingEngineLagrangePOD -from rrompy.sampling.scipy.pod_engine import PODEngine +from .sampling_engine_krylov import SamplingEngineKrylov +from .sampling_engine_arnoldi import SamplingEngineArnoldi +from .sampling_engine_lagrange import SamplingEngineLagrange +from .sampling_engine_lagrange_pod import SamplingEngineLagrangePOD __all__ = [ 'SamplingEngineKrylov', 'SamplingEngineArnoldi', 'SamplingEngineLagrange', - 'SamplingEngineLagrangePOD', - 'PODEngine' + 'SamplingEngineLagrangePOD' ] diff --git a/rrompy/sampling/scipy/sampling_engine_arnoldi.py b/rrompy/sampling/scipy/sampling_engine_arnoldi.py index d2247fd..d50de53 100644 --- a/rrompy/sampling/scipy/sampling_engine_arnoldi.py +++ b/rrompy/sampling/scipy/sampling_engine_arnoldi.py @@ -1,128 +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 <http://www.gnu.org/licenses/>. # from copy import copy import numpy as np -from rrompy.sampling.scipy.pod_engine import PODEngine -from rrompy.sampling.scipy.sampling_engine_krylov import SamplingEngineKrylov +from rrompy.sampling.base.pod_engine import PODEngine +from .sampling_engine_krylov import SamplingEngineKrylov from rrompy.utilities.base.types import Np1D from rrompy.utilities.base import verbosityDepth __all__ = ['SamplingEngineArnoldi'] class SamplingEngineArnoldi(SamplingEngineKrylov): """HERE""" def resetHistory(self): super().resetHistory() self.HArnoldi = None self.RArnoldi = None self.RHSs = None self.samplesAug = None @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): ns = self.nsamples if ns <= 0: return return self.samplesAug[:, ns - 1].reshape((-1,self.HFEngine.V.dim())).T - def preprocessb(self, mu:complex, overwrite : bool = False): + def preprocessb(self, mu:complex, overwrite : bool = False, + homogeneize : bool = False): ns = self.nsamples - r = self.HFEngine.b(mu, ns) + r = super().preprocessb(mu, overwrite, homogeneize) if ns == 0: return r elif ns == 1: r = r / self.RArnoldi[0, 0] else: r = ((r - self.RHSs[:, :ns-1].dot(self.RArnoldi[:ns-1, ns-1])) / self.RArnoldi[ns-1, ns-1]) if overwrite: self.RHSs[:, ns - 1] = r else: if ns == 1: self.RHSs = r.reshape((- 1, 1)) else: self.RHSs = np.hstack((self.RHSs, r[:, None])) return r def postprocessu(self, u:Np1D, overwrite : bool = False): - if self.verbosity >= 5: - verbosityDepth("INIT", "Starting orthogonalization.", end = "") + if self.verbosity >= 10: + verbosityDepth("INIT", "Starting orthogonalization.") ns = self.nsamples nsAug = (ns + 1) * self.HFEngine.V.dim() if ns == 0: u, h, _ = self.PODEngine.GS(u, np.empty((0, 0))) r = h[0] uAug = copy(u) else: uAug = np.concatenate((self.samplesAug[self.HFEngine.V.dim() - nsAug :, ns - 1], u), axis = None) u, h, uAug = self.PODEngine.GS(u, self.samples[:, : ns], ns, uAug, self.samplesAug[- nsAug :, : ns]) if overwrite: self.HArnoldi[: ns + 1, ns] = h if ns > 0: r = self.HArnoldi[: ns + 1, 1 : ns + 1].dot( self.RArnoldi[: ns, ns - 1]) self.RArnoldi[: ns + 1, ns] = r self.samplesAug[- nsAug :, ns] = uAug else: if ns == 0: self.HArnoldi = h.reshape((1, 1)) self.RArnoldi = r.reshape((1, 1)) self.samplesAug = uAug.reshape((-1, 1)) else: self.HArnoldi=np.block([[ self.HArnoldi, h[:-1, None]], [np.zeros((1, ns)), h[-1]]]) if ns > 0: r = self.HArnoldi[: ns + 1, 1 : ns + 1].dot( self.RArnoldi[: ns, ns - 1]) self.RArnoldi=np.block([[ self.RArnoldi, r[:-1, None]], [np.zeros((1, ns)), r[-1]]]) self.samplesAug=np.vstack((np.zeros((self.HFEngine.V.dim(), ns)), self.samplesAug)) self.samplesAug = np.hstack((self.samplesAug, uAug[:, None])) - if self.verbosity >= 5: - verbosityDepth("DEL", " Done.", inline = True) + if self.verbosity >= 10: + verbosityDepth("DEL", "Done orthogonalizing.") return u def preallocateSamples(self, u:Np1D, n:int): super().preallocateSamples(u, n) h = self.HArnoldi r = self.RArnoldi saug = self.samplesAug self.HArnoldi = np.zeros((n, n), dtype = u.dtype) self.HArnoldi[0, 0] = h[0, 0] self.RArnoldi = np.zeros((n, n), dtype = u.dtype) self.RArnoldi[0, 0] = r[0, 0] self.RHSs = np.empty((u.size, n - 1), dtype = u.dtype) self.samplesAug = np.zeros((self.HFEngine.V.dim() * (n + 1), n), dtype = u.dtype) self.samplesAug[- self.HFEngine.V.dim() :, 0] = saug[:, 0] diff --git a/rrompy/sampling/scipy/sampling_engine_krylov.py b/rrompy/sampling/scipy/sampling_engine_krylov.py index 059cfed..253d543 100644 --- a/rrompy/sampling/scipy/sampling_engine_krylov.py +++ b/rrompy/sampling/scipy/sampling_engine_krylov.py @@ -1,85 +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 <http://www.gnu.org/licenses/>. # import numpy as np from rrompy.sampling.base.sampling_engine_base import SamplingEngineBase from rrompy.utilities.base.types import Np1D, Np2D from rrompy.utilities.base import verbosityDepth __all__ = ['SamplingEngineKrylov'] class SamplingEngineKrylov(SamplingEngineBase): """HERE""" def preprocesssamples(self): if self.samples is None: return return self.samples[:, : self.nsamples] - def preprocessb(self, mu:complex, overwrite : bool = False): - return self.HFEngine.b(mu, self.nsamples) + def preprocessb(self, mu:complex, overwrite : bool = False, + homogeneize : bool = False): + return self.HFEngine.b(mu, self.nsamples, homogeneized = homogeneize) def postprocessu(self, u:Np1D, overwrite : bool = False): return u def preallocateSamples(self, u:Np1D, n:int): self.samples = np.empty((u.size, n), dtype = u.dtype) self.samples[:, 0] = u - def nextSample(self, mu:complex, overwrite : bool = False) -> Np1D: + def nextSample(self, mu:complex, overwrite : bool = False, + homogeneize : bool = False) -> Np1D: ns = self.nsamples - if self.verbosity >= 5: + if self.verbosity >= 10: verbosityDepth("INIT", ("Setting up computation of {}-th Taylor " - "coefficient.").format(ns), end = "") + "coefficient.").format(ns)) samplesOld = self.preprocesssamples() - if self.verbosity >= 5: - verbosityDepth("MAIN", ".", end = "", inline = True) - RHS = self.preprocessb(mu, overwrite = overwrite) - if self.verbosity >= 5: - verbosityDepth("MAIN", ".", end = "", inline = True) + RHS = self.preprocessb(mu, overwrite = overwrite, + homogeneize = homogeneize) for i in range(1, ns + 1): RHS -= self.HFEngine.A(mu, i).dot(samplesOld[:, - i]) - if self.verbosity >= 5: - verbosityDepth("DEL", " Done.", inline = True) - u = self.postprocessu(self.solveLS(mu, RHS = RHS), + if self.verbosity >= 10: + verbosityDepth("DEL", "Done setting up for Taylor coefficient.") + u = self.postprocessu(self.solveLS(mu, RHS = RHS, + homogeneized = homogeneize), overwrite = overwrite) if overwrite: self.samples[:, ns] = u else: if ns == 0: self.samples = u[:, None] else: self.samples = np.hstack((self.samples, u[:, None])) self.nsamples += 1 return u - def iterSample(self, mu:complex, n:int) -> Np2D: + def iterSample(self, mu:complex, n:int, + homogeneize : bool = False) -> Np2D: if self.verbosity >= 5: verbosityDepth("INIT", "Starting sampling iterations at mu = {}."\ .format(mu)) if n <= 0: raise Exception(("Number of Krylov iterations must be positive.")) self.resetHistory() - u = self.nextSample(mu) + u = self.nextSample(mu, homogeneize = homogeneize) if n > 1: self.preallocateSamples(u, n) for _ in range(1, n): - self.nextSample(mu, overwrite = True) + self.nextSample(mu, overwrite = True, + homogeneize = homogeneize) if self.verbosity >= 5: verbosityDepth("DEL", "Finished sampling iterations.") return self.samples diff --git a/rrompy/sampling/scipy/sampling_engine_lagrange.py b/rrompy/sampling/scipy/sampling_engine_lagrange.py index f4af0e9..81bf598 100644 --- a/rrompy/sampling/scipy/sampling_engine_lagrange.py +++ b/rrompy/sampling/scipy/sampling_engine_lagrange.py @@ -1,66 +1,69 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # import numpy as np from rrompy.sampling.base.sampling_engine_base import SamplingEngineBase from rrompy.utilities.base.types import Np1D, Np2D from rrompy.utilities.base import verbosityDepth __all__ = ['SamplingEngineLagrange'] class SamplingEngineLagrange(SamplingEngineBase): """HERE""" nameBase = 1 def postprocessu(self, u:Np1D, overwrite : bool = False): return u def preallocateSamples(self, u:Np1D, n:int): self.samples = np.empty((u.size, n), dtype = u.dtype) self.samples[:, 0] = u - def nextSample(self, mu:complex, overwrite : bool = False) -> Np1D: + def nextSample(self, mu:complex, overwrite : bool = False, + homogeneize : bool = False) -> Np1D: ns = self.nsamples - u = self.postprocessu(self.solveLS(mu), overwrite = overwrite) + u = self.postprocessu(self.solveLS(mu, homogeneized = homogeneize), + overwrite = overwrite) if overwrite: self.samples[:, ns] = u else: if ns == 0: self.samples = u[:, None] else: self.samples = np.hstack((self.samples, u[:, None])) self.nsamples += 1 return u - def iterSample(self, mu:Np1D) -> Np2D: + def iterSample(self, mu:Np1D, homogeneize : bool = False) -> Np2D: if self.verbosity >= 5: verbosityDepth("INIT", "Starting sampling iterations.") n = mu.size if n <= 0: raise Exception(("Number of samples must be positive.")) self.resetHistory() - u = self.nextSample(mu[0]) + u = self.nextSample(mu[0], homogeneize = homogeneize) if n > 1: self.preallocateSamples(u, n) for j in range(1, n): - self.nextSample(mu[j], overwrite = True) + self.nextSample(mu[j], overwrite = True, + homogeneize = homogeneize) if self.verbosity >= 5: verbosityDepth("DEL", "Finished sampling iterations.") return self.samples diff --git a/rrompy/sampling/scipy/sampling_engine_lagrange_pod.py b/rrompy/sampling/scipy/sampling_engine_lagrange_pod.py index e9ea0ad..b239295 100644 --- a/rrompy/sampling/scipy/sampling_engine_lagrange_pod.py +++ b/rrompy/sampling/scipy/sampling_engine_lagrange_pod.py @@ -1,70 +1,70 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # import numpy as np -from rrompy.sampling.scipy.pod_engine import PODEngine -from rrompy.sampling.scipy.sampling_engine_lagrange import SamplingEngineLagrange +from rrompy.sampling.base.pod_engine import PODEngine +from .sampling_engine_lagrange import SamplingEngineLagrange from rrompy.utilities.base.types import Np1D from rrompy.utilities.base import verbosityDepth __all__ = ['SamplingEngineLagrangePOD'] class SamplingEngineLagrangePOD(SamplingEngineLagrange): """HERE""" def resetHistory(self): super().resetHistory() self.RPOD = None @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 postprocessu(self, u:Np1D, overwrite : bool = False): - if self.verbosity >= 5: - verbosityDepth("INIT", "Starting orthogonalization.", end = "") + if self.verbosity >= 10: + verbosityDepth("INIT", "Starting orthogonalization.") 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[:, : 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 >= 5: - verbosityDepth("DEL", " Done.", inline = True) + if self.verbosity >= 10: + verbosityDepth("DEL", "Done orthogonalizing.") return u def preallocateSamples(self, u:Np1D, n:int): super().preallocateSamples(u, 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 5b77131..99a448d 100644 --- a/rrompy/utilities/base/__init__.py +++ b/rrompy/utilities/base/__init__.py @@ -1,43 +1,43 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.utilities.base.find_dict_str_key import findDictStrKey -from rrompy.utilities.base.get_new_filename import getNewFilename -from rrompy.utilities.base.purge_dict import purgeDict -from rrompy.utilities.base.purge_list import purgeList -from rrompy.utilities.base.number_theory import squareResonances, primeFactorize, getLowestPrimeFactor -from rrompy.utilities.base.sobol import sobolGenerate -from rrompy.utilities.base import types as Types -from rrompy.utilities.base import fenics as Fenics -from rrompy.utilities.base.verbosity_depth import verbosityDepth +from .find_dict_str_key import findDictStrKey +from .get_new_filename import getNewFilename +from .purge_dict import purgeDict +from .purge_list import purgeList +from .number_theory import squareResonances, primeFactorize, getLowestPrimeFactor +from .sobol import sobolGenerate +from . import types as Types +from . import fenics as Fenics +from .verbosity_depth import verbosityDepth __all__ = [ 'findDictStrKey', 'getNewFilename', 'purgeDict', 'purgeList', 'squareResonances', 'primeFactorize', 'getLowestPrimeFactor', 'sobolGenerate', 'Types', 'Fenics', 'verbosityDepth' ] diff --git a/rrompy/utilities/base/purge_dict.py b/rrompy/utilities/base/purge_dict.py index 9a41612..dcb2108 100644 --- a/rrompy/utilities/base/purge_dict.py +++ b/rrompy/utilities/base/purge_dict.py @@ -1,45 +1,43 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -import warnings from rrompy.utilities.base.find_dict_str_key import findDictStrKey from rrompy.utilities.base.types import ListAny, DictAny +from rrompy.utilities.warning_manager import warn __all__ = ['purgeDict'] def purgeDict(dct:DictAny, allowedKeys : ListAny = [], silent : bool = False, complement : bool = False, dictname : str = "", baselevel : int = 0) -> DictAny: if dictname != "": dictname = " in " + dictname dctcp = {} for key in dct.keys(): akey = findDictStrKey(key, allowedKeys) if (akey is None) != complement: if not silent: - warnings.warn("Ignoring key {0}{2} with value {1}."\ - .format(key, dct[key], dictname), - stacklevel = baselevel + 2) - from sys.stderr import flush - flush() - del flush + warn("Ignoring key {0}{2} with value {1}.".format(key, + dct[key], + dictname), + baselevel) else: if akey is None: akey = key dctcp[akey] = dct[key] return dctcp diff --git a/rrompy/utilities/base/purge_list.py b/rrompy/utilities/base/purge_list.py index 92cd744..70cf5c0 100644 --- a/rrompy/utilities/base/purge_list.py +++ b/rrompy/utilities/base/purge_list.py @@ -1,43 +1,39 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -import warnings from rrompy.utilities.base.find_dict_str_key import findDictStrKey from rrompy.utilities.base.types import ListAny +from rrompy.utilities.warning_manager import warn __all__ = ['purgeList'] def purgeList(lst:ListAny, allowedEntries : ListAny = [], silent : bool = False, complement : bool = False, listname : str = "", baselevel : int = 0) -> ListAny: if listname != "": listname = " in " + listname lstcp = [] for x in lst: ax = findDictStrKey(x, allowedEntries) if (ax is None) != complement: if not silent: - warnings.warn("Ignoring entry {0}{1}.".format(x, listname), - stacklevel = baselevel + 2) - from sys.stderr import flush - flush() - del flush + warn("Ignoring entry {0}{1}.".format(x, listname), baselevel) else: lstcp = lstcp + [ax] return lstcp diff --git a/rrompy/utilities/base/types.py b/rrompy/utilities/base/types.py index fe26a19..e93358d 100644 --- a/rrompy/utilities/base/types.py +++ b/rrompy/utilities/base/types.py @@ -1,51 +1,51 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from typing import TypeVar, List, Tuple, Dict, Any -__all__ = ['TupleAny','ListAny','DictAny','HS1D','HS2D','HSOp','Np1D','Np2D', - 'Np1DLst','N2FSExpr','FenExpr','HFEng','ROMEng','GenExpr','strLst', - 'BfSExpr'] +__all__ = ['TupleAny','ListAny','DictAny','ScOp','Np1D','Np2D','Np1DLst', + 'N2FSExpr','FenExpr','FenFunc','HFEng','ROMEng','sampleEng', + 'GenExpr','strLst','BfSExpr'] # ANY TupleAny = Tuple[Any] ListAny = List[Any] DictAny = Dict[Any, Any] -# GENERIC -HS1D = TypeVar("Hilbert space element") -HS2D = TypeVar("Hilbert space quasi-matrix") -HSOp = TypeVar("Hilbert space operator") +# SCIPY +ScOp = TypeVar("Scipy sparse matrix for space operator") # NUMPY Np1D = TypeVar("NumPy 1D array") Np2D = TypeVar("NumPy 2D array") Np1DLst = TypeVar("NumPy 1D array or list of NumPy 1D array") N2FSExpr = TypeVar("NumPy 2D array, float or str") # FENICS FenExpr = TypeVar("FEniCS expression") +FenFunc = TypeVar("FEniCS function") # ENGINES HFEng = TypeVar("High fidelity engine") ROMEng = TypeVar("ROM engine") +sampleEng = TypeVar("Sampling engine") # OTHERS GenExpr = TypeVar("Generic expression") strLst = TypeVar("str or list of str") BfSExpr = TypeVar("Boolean function or string") diff --git a/rrompy/utilities/parameter_sampling/__init__.py b/rrompy/utilities/parameter_sampling/__init__.py index cdf181b..fe293d3 100644 --- a/rrompy/utilities/parameter_sampling/__init__.py +++ b/rrompy/utilities/parameter_sampling/__init__.py @@ -1,33 +1,33 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.utilities.parameter_sampling.generic_sampler import GenericSampler -from rrompy.utilities.parameter_sampling.quadrature_sampler import QuadratureSampler -from rrompy.utilities.parameter_sampling.random_sampler import RandomSampler -from rrompy.utilities.parameter_sampling.fft_sampler import FFTSampler -from rrompy.utilities.parameter_sampling.manual_sampler import ManualSampler +from .generic_sampler import GenericSampler +from .quadrature_sampler import QuadratureSampler +from .random_sampler import RandomSampler +from .fft_sampler import FFTSampler +from .manual_sampler import ManualSampler __all__ = [ 'GenericSampler', 'QuadratureSampler', 'RandomSampler', 'FFTSampler', 'ManualSampler' ] diff --git a/rrompy/utilities/parameter_sweeper/__init__.py b/rrompy/utilities/parameter_sweeper/__init__.py index 1571153..03ca6b3 100644 --- a/rrompy/utilities/parameter_sweeper/__init__.py +++ b/rrompy/utilities/parameter_sweeper/__init__.py @@ -1,25 +1,25 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.utilities.parameter_sweeper.parameter_sweeper import ParameterSweeper +from .parameter_sweeper import ParameterSweeper __all__ = [ 'ParameterSweeper' ] diff --git a/rrompy/utilities/parameter_sweeper/parameter_sweeper.py b/rrompy/utilities/parameter_sweeper/parameter_sweeper.py index 88fadaa..a4d9167 100644 --- a/rrompy/utilities/parameter_sweeper/parameter_sweeper.py +++ b/rrompy/utilities/parameter_sweeper/parameter_sweeper.py @@ -1,361 +1,489 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # from copy import copy import itertools import csv -import warnings import numpy as np from matplotlib import pyplot as plt from rrompy.utilities.base.types import Np1D, DictAny, List, ROMEng from rrompy.utilities.base import purgeList, getNewFilename, verbosityDepth +from rrompy.utilities.warning_manager import warn __all__ = ['ParameterSweeper'] def C2R2csv(x): x = np.ravel(x) y = np.concatenate((np.real(x), np.imag(x))) z = np.ravel(np.reshape(y, [2, np.size(x)]).T) return np.array2string(z, separator = '_', suppress_small = False, max_line_width = np.inf, sign = '+', formatter = {'all' : lambda x : "{:.15E}".format(x)} )[1 : -1] class ParameterSweeper: """ ROM approximant parameter sweeper. Args: ROMEngine(optional): Generic approximant class. Defaults to None. mutars(optional): Array of parameter values to sweep. Defaults to empty array. params(optional): List of parameter settings (each as a dict) to explore. Defaults to single empty set. mostExpensive(optional): String containing label of most expensive step, to be executed fewer times. Allowed options are 'HF' and 'Approx'. Defaults to 'HF'. Attributes: ROMEngine: Generic approximant class. mutars: Array of parameter values to sweep. params: List of parameter settings (each as a dict) to explore. mostExpensive: String containing label of most expensive step, to be executed fewer times. """ allowedOutputsStandard = ["normHF", "normApp", "normRes", "normResRel", "normErr", "normErrRel"] allowedOutputs = allowedOutputsStandard + ["HFFunc", "AppFunc", "ErrFunc", "ErrFuncRel"] allowedOutputsFull = allowedOutputs + ["poles"] def __init__(self, ROMEngine : ROMEng = None, mutars : Np1D = np.array([]), params : List[DictAny] = [{}], mostExpensive : str = "HF"): self.ROMEngine = ROMEngine self.mutars = mutars self.params = params self.mostExpensive = mostExpensive def name(self) -> str: return self.__class__.__name__ def __str__(self) -> str: return self.name() @property def mostExpensive(self): """Value of mostExpensive.""" return self._mostExpensive @mostExpensive.setter def mostExpensive(self, mostExpensive:str): mostExpensive = mostExpensive.upper() if mostExpensive not in ["HF", "APPROX"]: - warnings.warn(("Value of mostExpensive not recognized. Overriding " - "to 'APPROX'."), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn(("Value of mostExpensive not recognized. Overriding to " + "'APPROX'.")) mostExpensive = "APPROX" self._mostExpensive = mostExpensive def checkValues(self) -> bool: """Check if sweep can be performed.""" if self.ROMEngine is None: raise Exception("ROMEngine is missing. Aborting.") if len(self.mutars) == 0: raise Exception("Empty target parameter vector. Aborting.") if len(self.params) == 0: raise Exception("Empty method parameters vector. Aborting.") def sweep(self, filename : str = "out.dat", outputs : List[str] = [], - verbose : int = 1): + verbose : int = 10): self.checkValues() try: if outputs.upper() == "ALL": outputs = self.allowedOutputsFull except: if len(outputs) == 0: outputs = self.allowedOutputsStandard outputs = purgeList(outputs, self.allowedOutputsFull, listname = self.name() + ".outputs", baselevel = 1) poles = ("poles" in outputs) if len(outputs) == 0: raise Exception("Empty outputs. Aborting.") outParList = self.ROMEngine.parameterList Nparams = len(self.params) if poles: polesCheckList = [] allowedParams = self.ROMEngine.parameterList dotPos = filename.rfind('.') if dotPos in [-1, len(filename) - 1]: filename = getNewFilename(filename[:dotPos]) else: filename = getNewFilename(filename[:dotPos], filename[dotPos + 1:]) append_write = "w" initial_row = (outParList + ["muRe", "muIm"] + [x for x in self.allowedOutputs if x in outputs] + ["type"] + ["poles"] * poles) with open(filename, append_write, buffering = 1) as fout: writer = csv.writer(fout, delimiter=",") writer.writerow(initial_row) if self.mostExpensive == "HF": outerSet = self.mutars innerSet = self.params elif self.mostExpensive == "APPROX": outerSet = self.params innerSet = self.mutars for outerIdx, outerPar in enumerate(outerSet): if self.mostExpensive == "HF": i, mutar = outerIdx, outerPar elif self.mostExpensive == "APPROX": j, par = outerIdx, outerPar self.ROMEngine.approxParameters = {k: par[k] for k in\ par.keys() & allowedParams} self.ROMEngine.setupApprox() for innerIdx, innerPar in enumerate(innerSet): if self.mostExpensive == "APPROX": i, mutar = innerIdx, innerPar elif self.mostExpensive == "HF": j, par = innerIdx, innerPar self.ROMEngine.approxParameters = {k: par[k] for k in\ par.keys() & allowedParams} self.ROMEngine.setupApprox() - if verbose >= 1: + if verbose >= 5: verbosityDepth("INIT", "Set {}/{}\tmu_{} = {:.10f}"\ .format(j + 1, Nparams, i, mutar)) outData = [] if "normHF" in outputs: valNorm = self.ROMEngine.normHF(mutar) outData = outData + [valNorm] if "normApp" in outputs: val = self.ROMEngine.normApp(mutar) outData = outData + [val] if "normRes" in outputs: valNRes = self.ROMEngine.normRes(mutar) outData = outData + [valNRes] if "normResRel" in outputs: if "normRes" not in outputs: valNRes = self.ROMEngine.normRes(mutar) val = self.ROMEngine.normRHS(mutar) outData = outData + [valNRes / val] if "normErr" in outputs: valNErr = self.ROMEngine.normErr(mutar) outData = outData + [valNErr] if "normErrRel" in outputs: if "normHF" not in outputs: valNorm = self.ROMEngine.normHF(mutar) if "normErr" not in outputs: valNErr = self.ROMEngine.normErr(mutar) outData = outData + [valNErr / valNorm] if "HFFunc" in outputs: valFunc = self.ROMEngine.HFEngine.functional( self.ROMEngine.getHF(mutar)) outData = outData + [valFunc] if "AppFunc" in outputs: valFApp = self.ROMEngine.HFEngine.functional( self.ROMEngine.getApp(mutar)) outData = outData + [valFApp] if "ErrFunc" in outputs: if "HFFunc" not in outputs: valFunc = self.ROMEngine.HFEngine.functional( self.ROMEngine.getHF(mutar)) if "AppFunc" not in outputs: valFApp = self.ROMEngine.HFEngine.functional( self.ROMEngine.getApp(mutar)) valFErr = np.abs(valFApp - valFunc) outData = outData + [valFErr] if "ErrFuncRel" in outputs: if not ("HFFunc" in outputs or "ErrFunc" in outputs): valFunc = self.ROMEngine.HFEngine.functional( self.ROMEngine.getHF(mutar)) if not ("AppFunc" in outputs or "ErrFunc" in outputs): valFApp = self.ROMEngine.HFEngine.functional( self.ROMEngine.getApp(mutar)) val = np.nan if not np.isclose(valFunc, 0.): val = valFApp / valFunc outData = outData + [val] writeData = [] for parn in outParList: writeData = (writeData + [self.ROMEngine.approxParameters[parn]]) writeData = (writeData + [mutar.real, mutar.imag] + outData + [self.ROMEngine.name()]) if poles: if j not in polesCheckList: polesCheckList += [j] writeData = writeData + [C2R2csv( self.ROMEngine.getPoles())] else: writeData = writeData + [""] writer.writerow(str(x) for x in writeData) - if verbose >= 1: + if verbose >= 5: verbosityDepth("DEL", "", end = "", inline = "") - if verbose >= 1: + if verbose >= 5: if self.mostExpensive == "APPROX": out = "Set {}/{}\tdone.\n".format(j + 1, Nparams) elif self.mostExpensive == "HF": out = "Point mu_{} = {:.10f}\tdone.\n".format(i, mutar) verbosityDepth("INIT", out) verbosityDepth("DEL", "", end = "", inline = "") self.filename = filename return self.filename def read(self, filename:str, restrictions : DictAny = {}, outputs : List[str] = []) -> DictAny: """ Execute a query on a custom format CSV. Args: filename: CSV filename. restrictions(optional): Parameter configurations to output. Defaults to empty dictionary, i.e. output all. outputs(optional): Values to output. Defaults to empty list, i.e. no output. Returns: Dictionary of desired results, with a key for each entry of outputs, and a numpy 1D array as corresponding value. """ with open(filename, 'r') as f: reader = csv.reader(f, delimiter=',') header = next(reader) restrIndices, outputIndices, outputData = {}, {}, {} for key in restrictions.keys(): try: restrIndices[key] = header.index(key) if not isinstance(restrictions[key], list): restrictions[key] = [restrictions[key]] restrictions[key] = copy(restrictions[key]) except: - warnings.warn("Ignoring key {} from restrictions"\ - .format(key), stacklevel = 2) - from sys.stderr import flush - flush() - del flush + warn("Ignoring key {} from restrictions.".format(key)) for key in outputs: try: outputIndices[key] = header.index(key) outputData[key] = np.array([]) except: - warnings.warn("Ignoring key {} from outputs".format(key), - stacklevel = 2) - from sys.stderr import flush - flush() - del flush - + warn("Ignoring key {} from outputs.".format(key)) + for row in reader: restrTrue = True for key in restrictions.keys(): if row[restrIndices[key]] == restrictions[key]: continue try: if np.any(np.isclose(float(row[restrIndices[key]]), [float(x) for x in restrictions[key]])): continue except: pass restrTrue = False if restrTrue: for key in outputIndices.keys(): try: val = row[outputIndices[key]] val = float(val) finally: outputData[key] = np.append(outputData[key], val) return outputData - + def plot(self, filename:str, xs:List[str], ys:List[str], zs:List[str], - onePlot : bool = False): + onePlot : bool = False, save : str = None, + saveFormat : str = "eps", saveDPI : int = 100, **figspecs): """ Perform plots from data in filename. Args: filename: CSV filename. xs: Values to put on x axes. ys: Values to put on y axes. zs: Meta-values for constraints. - onePlot: Whether to create a single figure per x. Defaults to - False. + onePlot(optional): Whether to create a single figure per x. + Defaults to False. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. + figspecs(optional key args): Optional arguments for matplotlib + figure creation. """ + if save is not None: + save = save.strip() zsVals = self.read(filename, outputs = zs) zs = list(zsVals.keys()) zss = None for key in zs: vals = np.unique(zsVals[key]) if zss is None: zss = copy(vals) else: zss = list(itertools.product(zss, vals)) lzs = len(zs) for z in zss: if lzs <= 1: constr = {zs[0] : z} else: constr = {zs[j] : z[j] for j in range(len(zs))} data = self.read(filename, restrictions = constr, outputs = xs+ys) if onePlot: for x in xs: xVals = data[x] - plt.figure() + p = plt.figure(**figspecs) + logScale = False for y in ys: yVals = data[y] label = '{} vs {} for {}'.format(y, x, constr) - plt.semilogy(xVals, yVals, label = label) + if np.min(yVals) <= - np.finfo(float).eps: + plt.plot(xVals, yVals, label = label) + else: + plt.plot(xVals, yVals, label = label) + if np.log10(np.max(yVals) / np.min(yVals)) > 1.: + logScale = True + if logScale: + ax = p.get_axes()[0] + ax.set_yscale('log') plt.legend() plt.grid() + if save is not None: + prefix = "{}_{}_vs_{}_{}".format(save, ys, x, constr) + plt.savefig(getNewFilename(prefix, saveFormat), + format = saveFormat, dpi = saveDPI) plt.show() plt.close() else: for x, y in itertools.product(xs, ys): xVals, yVals = data[x], data[y] label = '{} vs {} for {}'.format(y, x, constr) - plt.figure() - plt.semilogy(xVals, yVals, label = label) + p = plt.figure(**figspecs) + if np.min(yVals) <= - np.finfo(float).eps: + plt.plot(xVals, yVals, label = label) + else: + plt.plot(xVals, yVals, label = label) + if np.log10(np.max(yVals) / np.min(yVals)) > 1.: + ax = p.get_axes()[0] + ax.set_yscale('log') + plt.legend() + plt.grid() + if save is not None: + prefix = "{}_{}_vs_{}_{}".format(save, y, x, constr) + plt.savefig(getNewFilename(prefix, saveFormat), + format = saveFormat, dpi = saveDPI) + plt.show() + plt.close() + + def plotCompare(self, filenames:List[str], xs:List[str], ys:List[str], + zs:List[str], onePlot : bool = False, save : str = None, + saveFormat : str = "eps", saveDPI : int = 100, + labels : List[str] = None, **figspecs): + """ + Perform plots from data in filename1 and filename2. + + Args: + filenames: CSV filenames. + xs: Values to put on x axes. + ys: Values to put on y axes. + zs: Meta-values for constraints. + onePlot(optional): Whether to create a single figure per x. + Defaults to False. + save(optional): Where to save plot(s). Defaults to None, i.e. no + saving. + saveFormat(optional): Format for saved plot(s). Defaults to "eps". + saveDPI(optional): DPI for saved plot(s). Defaults to 100. + labels: Label for each dataset. + figspecs(optional key args): Optional arguments for matplotlib + figure creation. + """ + nfiles = len(filenames) + if save is not None: + save = save.strip() + if labels is None: + labels = ["{}".format(j + 1) for j in range(nfiles)] + zsVals = self.read(filenames[0], outputs = zs) + zs = list(zsVals.keys()) + zss = None + for key in zs: + vals = np.unique(zsVals[key]) + if zss is None: + zss = copy(vals) + else: + zss = list(itertools.product(zss, vals)) + lzs = len(zs) + for z in zss: + if lzs <= 1: + constr = {zs[0] : z} + else: + constr = {zs[j] : z[j] for j in range(len(zs))} + data = [None] * nfiles + for j in range(nfiles): + data[j] = self.read(filenames[j], restrictions = constr, + outputs = xs + ys) + if onePlot: + for x in xs: + xVals = [None] * nfiles + for j in range(nfiles): + try: + xVals[j] = data[j][x] + except: + pass + p = plt.figure(**figspecs) + logScale = False + for y in ys: + for j in range(nfiles): + try: + yVals = data[j][y] + except: + pass + l = '{} vs {} for {}, {}'.format(y, x, constr, + labels[j]) + if np.min(yVals) <= - np.finfo(float).eps: + plt.plot(xVals[j], yVals, label = l) + else: + plt.plot(xVals[j], yVals, label = l) + if np.log10(np.max(yVals)/np.min(yVals)) > 1.: + logScale = True + if logScale: + ax = p.get_axes()[0] + ax.set_yscale('log') + plt.legend() + plt.grid() + if save is not None: + prefix = "{}_{}_vs_{}_{}".format(save, ys, x, constr) + plt.savefig(getNewFilename(prefix, saveFormat), + format = saveFormat, dpi = saveDPI) + plt.show() + plt.close() + else: + for x, y in itertools.product(xs, ys): + p = plt.figure(**figspecs) + logScale = False + for j in range(nfiles): + xVals, yVals = data[j][x], data[j][y] + l = '{} vs {} for {}, {}'.format(y, x, constr, + labels[j]) + if np.min(yVals) <= - np.finfo(float).eps: + plt.plot(xVals, yVals, label = l) + else: + plt.plot(xVals, yVals, label = l) + if np.log10(np.max(yVals)/np.min(yVals)) > 1.: + logScale = True + if logScale: + ax = p.get_axes()[0] + ax.set_yscale('log') plt.legend() plt.grid() + if save is not None: + prefix = "{}_{}_vs_{}_{}".format(save, y, x, constr) + plt.savefig(getNewFilename(prefix, saveFormat), + format = saveFormat, dpi = saveDPI) plt.show() plt.close() diff --git a/rrompy/utilities/parameter_sweeper/__init__.py b/rrompy/utilities/warning_manager/__init__.py similarity index 87% copy from rrompy/utilities/parameter_sweeper/__init__.py copy to rrompy/utilities/warning_manager/__init__.py index 1571153..7138de5 100644 --- a/rrompy/utilities/parameter_sweeper/__init__.py +++ b/rrompy/utilities/warning_manager/__init__.py @@ -1,25 +1,25 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.utilities.parameter_sweeper.parameter_sweeper import ParameterSweeper +from .warning_manager import warn __all__ = [ - 'ParameterSweeper' + 'warn' ] diff --git a/rrompy/hfengines/base/__init__.py b/rrompy/utilities/warning_manager/warning_manager.py similarity index 53% copy from rrompy/hfengines/base/__init__.py copy to rrompy/utilities/warning_manager/warning_manager.py index febe1c9..2443872 100644 --- a/rrompy/hfengines/base/__init__.py +++ b/rrompy/utilities/warning_manager/warning_manager.py @@ -1,28 +1,34 @@ # Copyright (C) 2018 by the RROMPy authors # # This file is part of RROMPy. # # RROMPy is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # RROMPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with RROMPy. If not, see <http://www.gnu.org/licenses/>. # -from rrompy.hfengines.base.problem_engine_base import ProblemEngineBase -from rrompy.hfengines.base.boundary_conditions import BoundaryConditions - -__all__ = [ - 'ProblemEngineBase', - 'BoundaryConditions' - ] - +import traceback as tb +__all__ = ['warn'] +def warn(msg : str = "", stacklevel : int = 0): + frameSummary = tb.extract_stack()[- 3 - stacklevel] + if frameSummary.name == "<module>": + name = "" + else: + name = ", within {}".format(frameSummary.name) + print("\n\x1b[3m Warning at {}:{}{}:\x1b[0m".format(frameSummary.filename, + frameSummary.lineno, + name)) + print("> \x1b[31m{}\x1b[0m".format(frameSummary.line)) + if len(msg) > 0: + print("\x1b[3m {}\x1b[0m\n".format(msg))