def test_rational_chebyshev_interpolation(show=False): """Test the inperpolation routine of ChebyshevRationalGrid""" import numpy as np from psecas import ChebyshevRationalGrid def psi(x, c): from numpy.polynomial.hermite import hermval return hermval(x, c) * np.exp(-x**2 / 2) N = 95 grid = ChebyshevRationalGrid(N, C=4) grid_fine = ChebyshevRationalGrid(N * 4, C=1) z = grid_fine.zg y = psi(grid.zg, np.array(np.ones(4))) y_fine = psi(z, np.array(np.ones(4))) y_interpolated = grid.interpolate(z, y) if show: import matplotlib.pyplot as plt plt.figure(3) plt.clf() plt.title("Interpolation with rational Chebyshev") plt.plot(z, y_fine, "-") plt.plot(z, y_interpolated, "--") plt.plot(grid.zg, y, "+") plt.xlim(-15, 15) plt.show() np.testing.assert_allclose(y_fine, y_interpolated, atol=1e-12) return (y_fine, y_interpolated)
def test_channel(show=False, verbose=False): """Test eigenvalue solver using ChebyshevRationalGrid""" import numpy as np from psecas import Solver, ChebyshevRationalGrid, System grid = ChebyshevRationalGrid(N=199, z='z') class Channel(System): def make_background(self): import numpy as np zg = self.grid.zg self.h = np.exp(-zg**2 / 2) # Create the Channel system system = Channel(grid, variables='G', eigenvalue='K2') # Add the first (and only) equation system.add_equation("-h*K2*G = dz(dz(G)) +z*dz(G)", boundary=True) solver = Solver(grid, system, do_gen_evp=True) # Number of modes to test modes = 3 results = np.zeros(modes, dtype=np.complex128) checks = np.array([85.08037778, 69.4741069099, 55.4410282999]) def sorting_strategy(E): """Sorting strategy for channel modes""" E[E.real > 100.0] = 0 E[E.real < -10.0] = 0 index = np.argsort(np.real(E))[::-1] return (E, index) solver.sorting_strategy = sorting_strategy if show: import matplotlib.pyplot as plt plt.figure(3) plt.clf() fig, axes = plt.subplots(num=3, ncols=modes, sharey=True) for mode in range(modes): Ns = np.arange(1, 6) * 32 omega, vec, err = solver.iterate_solver(Ns, mode=mode, verbose=True) results[mode] = omega if show: phi = np.arctan(vec[2].imag / vec[2].real) solver.keep_result(omega, vec * np.exp(-1j * phi), mode) axes[mode].set_title(r"$\sigma = ${:1.4f}".format(omega.real), fontsize=10) axes[mode].plot(grid.zg, system.result['G'].real) axes[mode].plot(grid.zg, system.result['G'].imag) axes[mode].set_xlim(-4, 4) if show: plt.show() np.testing.assert_allclose(results, checks, rtol=1e-6)
def test_rational_chebyshev_differentation(show=False): """Test the differentation routine of ChebyshevRationalGrid""" import numpy as np from psecas import ChebyshevRationalGrid def psi(x, c): from numpy.polynomial.hermite import hermval return hermval(x, c) * np.exp(-x**2 / 2) def dpsi(x, c): from numpy.polynomial.hermite import hermval, hermder yp = hermval(x, hermder(c)) * np.exp(-x**2 / 2) - x * psi(x, c) return yp def d2psi(x, c): """Second derivative of psi""" from numpy.polynomial.hermite import hermval, hermder yp = hermval(x, hermder(hermder(c))) * np.exp(-x**2 / 2) yp += -x * hermval(x, hermder(c)) * np.exp(-x**2 / 2) yp += -psi(x, c) - x * dpsi(x, c) return yp N = 100 grid = ChebyshevRationalGrid(N, C=4) assert grid.zg[0] < grid.zg[-1] c = np.ones(4) y = psi(grid.zg, c) yp_exac = dpsi(grid.zg, c) yp_num = np.matmul(grid.d1, y) ypp_exac = d2psi(grid.zg, c) ypp_num = np.matmul(grid.d2, y) if show: import matplotlib.pyplot as plt plt.figure(1) plt.clf() fig, axes = plt.subplots(num=1, nrows=2) axes[0].set_title("Differentation with matrix (Sinc)") axes[0].plot(grid.zg, yp_exac, "-", grid.zg, yp_num, "--") axes[1].plot(grid.zg, ypp_exac, "-", grid.zg, ypp_num, "--") for ax in axes: ax.set_xlim(-15, 15) axes[0].set_ylim(-15, 15) plt.show() np.testing.assert_allclose(yp_num, yp_exac, atol=1e-12) np.testing.assert_allclose(ypp_num, ypp_exac, atol=1e-10) return (yp_num, yp_exac)
def test_hermite_solutions(show=False): import numpy as np from psecas import Solver, System from psecas import HermiteGrid, SincGrid, ChebyshevRationalGrid """ Test of the behaviour of three different grids on the infinite domain. See also the example hermite.py and note that the Chebyshev Rational grid uses roughly twice as many grid points. The example can be found in Boyd page 131-133. We consider the eigenvalue problem uₓₓ + (λ - x²) u = 0 with |u| → 0 as |x| → ∞ which has exact solutions uⱼ(x) = exp(-x²/2)H₋j(x) where H₋j(x) is the jᵗʰ Hermite polynomial. The exact eigenvalues are λⱼ = 2 j + 1. """ N = 40 # Create grids grid1 = ChebyshevRationalGrid(N=2 * N - 1, C=2) grid2 = SincGrid(N=N, C=2) grid3 = HermiteGrid(N=N, C=1) grids = list([grid1, grid2, grid3]) tols = [1e-8, 1e-7, 1e-12] class HermiteSolver(Solver): def sorting_strategy(self, E): """Sorting strategy for hermite modes. E is a list of eigenvalues""" E[E.real > 100.0] = 0 # Ignore eigenvalues that are zero E[E.real == 0.0] = 1e5 # Sort from smallest to largest eigenvalue index = np.argsort(np.real(E)) return (E, index) if show: import matplotlib.pyplot as plt plt.figure(1) plt.clf() fig, axes = plt.subplots(num=1, nrows=3, ncols=10, sharex=True) for ii, grid in enumerate(grids): system = System(grid, variables="u", eigenvalue="sigma") if isinstance(grid, ChebyshevRationalGrid): boundary = True else: boundary = False system.add_equation("sigma*u = -dz(dz(u)) + z**2*u", boundary=boundary) solver = HermiteSolver(grid, system) for j in range(10): omega, vec = solver.solve(mode=j) np.testing.assert_allclose(2.0 * j + 1.0, omega.real, atol=tols[ii], rtol=tols[ii]) if show: solver.keep_result(omega, vec / np.max(np.abs(vec)), mode=j) msg = r" $\sigma = ${:1.8f}" ylabel = type(grid).__name__ axes[ii, j].set_title(msg.format(omega.real), fontsize=10) if isinstance(grid, HermiteGrid): z = np.linspace(3 * grid.zmin / 5, 3 * grid.zmax / 5, 5000) else: z = np.linspace(grid.zmin, grid.zmax, 5000) axes[ii, j].plot(z, grid.interpolate(z, system.result["u"].real), "C0-") axes[ii, j].plot(z, grid.interpolate(z, system.result["u"].imag), "C1-") axes[ii, j].plot(grid.zg, system.result["u"].real, "C0+") axes[ii, j].plot(grid.zg, system.result["u"].imag, "C1+") axes[ii, j].set_xlim(-15, 15) axes[ii, 0].set_ylabel(ylabel) plt.show()
is F'' + K² h F = 0 and is solved by employing a Neumann boundary condition on F. One of the modes obtained here, with KH=3.1979, is not found when solving -h K² G = z G' + G'' Visual inspection reveals that the extra mode is numerical garbage. Automated detection of such modes would be a good feature to implement. """ grid = ChebyshevRationalGrid(N=199, z='z') system = Channel(grid) ch = Solver(grid, system) def sorting_strategy(E): """Sorting strategy for channel modes""" E[E.real > 100.0] = 0 E[E.real < -10.0] = 0 index = np.argsort(np.real(E))[::-1] return (E, index) # Overwrite the default sorting strategy in the Solver class ch.sorting_strategy = sorting_strategy
""" Illustration of the behaviour of three different grids on the infinite domain. The example can be found in Boyd page 131-133. We consider the eigenvalue problem uₓₓ + (λ - x²) u = 0 with |u| → 0 as |x| → ∞ which has exact solutions uⱼ(x) = exp(-x²/2)H₋j(x) where H₋j(x) is the jᵗʰ Hermite polynomial. The exact eigenvalues are λⱼ = 2 j + 1. """ N = 40 # Create grids grid1 = ChebyshevRationalGrid(N=N - 1, C=2) grid2 = SincGrid(N=N, C=2) grid3 = HermiteGrid(N=N, C=1) grids = list([grid1, grid2, grid3]) # Mode number j = 8 class HermiteSolver(Solver): def sorting_strategy(self, E): """Sorting strategy for hermite modes. E is a list of eigenvalues""" E[E.real > 100.0] = 0 # Ignore eigenvalues that are zero E[E.real == 0.0] = 1e5
After solving for G, F can be found from F = -1/(K h) G' The division by h gives some numerical issues, since h → 0 as z → ± ∞. Alternatively, we can solve their equation 17 for F directly, F'' + K² h F = 0 by employing a Neumann boundary condition on F. This is explored in the script channel.py. """ # Create grid grid = ChebyshevRationalGrid(N=199, C=1, z='z') # grid = SincGrid(N=299, C=1, z='z') # Make a Child of the System class and override the make_background method class Channel(System): def make_background(self): import numpy as np zg = self.grid.zg self.h = np.exp(-zg ** 2 / 2) # Create the Channel system system = Channel(grid, variables='G', eigenvalue='K2')
This problem is described in the paper *MRI channel flows in vertically stratified models of accretion discs*, https://doi.org/10.1111/j.1365-2966.2010.16759.x, by Henrik N. Latter, Sebastien Fromang, Oliver Gressel We solve their equation 16 as a coupled system: h K F = - G' K G = F' The spurious mode found in channel.py also appears here. """ # Create grid grid = ChebyshevRationalGrid(N=199, C=1, z='z') # grid = SincGrid(N=599, C=2, z='z') # Make a Child of the System class and override the make_background method class Channel(System): def make_background(self): import numpy as np zg = self.grid.zg self.h = np.exp(-zg ** 2 / 2) # Create the Channel system system = Channel(grid, variables=['F', 'G'], eigenvalue='K')
(1 + 1 / 3 * (2 - q) * (p + q) * h**2 + q * (1 - (1 + 2 / 3 * z**2 * h**2) / ((1 + z**2 * h**2)**(3 / 2))))) drhodz_sym = sym.diff(rho_sym, z) domgdz_sym = sym.diff(Omg_sym, z) zg = self.grid.zg self.rho = np.ones_like(zg) * lambdify(z, rho_sym)(zg) self.Omg = np.ones_like(zg) * lambdify(z, Omg_sym)(zg) self.shr = np.ones_like(zg) * lambdify(z, shr_sym)(zg) self.drhodz = np.ones_like(zg) * lambdify(z, drhodz_sym)(zg) self.domgdz = np.ones_like(zg) * lambdify(z, domgdz_sym)(zg) # Create a grid grid = ChebyshevRationalGrid(N=200, C=0.2) # grid = ChebyshevExtremaGrid(N=199, zmin=-5, zmax=5) # Create the system system = VerticalShearInstability(grid, variables=['rh', 'wx', 'wy', 'wz'], eigenvalue='sigma') # The linearized equations system.add_equation("-sigma*rh = - 1j*kx*wx - 1/h*dz(wz) - 1/h*drhodz/rho*wz") system.add_equation("-sigma*wx = + 2*Omg*wy - 1j*kx*(h/O0)**2*rh") system.add_equation("-sigma*wy = - (2*Omg + shr)*wx - 1/h*domgdz*wz") system.add_equation("-sigma*wz = - h/O0**2*dz(rh)", boundary=True) solver = Solver(grid, system, do_gen_evp=True)
from psecas import Solver, ChebyshevRationalGrid from psecas.systems.kh_hydro_sheet import KelvinHelmholtzHydroOnlySlab from psecas import plot_solution, get_2Dmap import numpy as np import matplotlib.pyplot as plt """ Find the eigenmodes of the KH instability on the domain z ∈ [-∞, ∞] assuming that the perturbations are zero at the boundaries. This the pure hydro version of the Kelvin-Helmholtz instability. The equilibrium changes sign at z=0 and is therefore not periodic. """ grid = ChebyshevRationalGrid(N=32, C=0.4) u0 = 1.0 delta = 1.0 kx = 5.1540899 kx = 3.5128310 system = KelvinHelmholtzHydroOnlySlab(grid, u0, delta, kx) system.boundaries = [True, True, True, True] solver = Solver(grid, system) Ns = np.arange(1, 32) * 32 - 1 omega, vec, err = solver.iterate_solver(Ns, mode=0, verbose=True) xmin = 0 xmax = 2 * np.pi / kx zmin = -4