def prec_inv(psi): prec_orig = pyfvm.get_fvm_matrix(self.mesh, edge_kernels=[Energy(mu)]) diag = prec_orig.diagonal() diag += self.g * 2.0 * (psi[0::2]**2 + psi[1::2]**2) * cv[0::2] prec_orig.setdiag(diag) return split_sparse_matrix(prec_orig).tocsr()
def test_keo(filename, control_values): filename = download_mesh(filename) mu = 1.0e-2 # read the mesh mesh, point_data, field_data, _ = meshplex.read(filename) keo = pyfvm.get_fvm_matrix(mesh, edge_kernels=[Energy(mu)]) tol = 1.0e-13 # Check that the matrix is Hermitian. KK = keo - keo.H assert abs(KK.sum()) < tol # Check the matrix sum. assert abs(control_values[0] - keo.sum()) < tol # Check the 1-norm of the matrix |Re(K)| + |Im(K)|. # This equals the 1-norm of the matrix defined by the block # structure # Re(K) -Im(K) # Im(K) Re(K). K = abs(keo.real) + abs(keo.imag) assert abs(control_values[1] - numpy.max(K.sum(0))) < tol return
def test_keo(filename, control_values): filename = download_mesh(filename) mu = 1.0e-2 # read the mesh mesh = meshplex.read(filename) keo = pyfvm.get_fvm_matrix(mesh, edge_kernels=[Energy(mu)]) tol = 1.0e-13 # Check that the matrix is Hermitian. KK = keo - keo.H assert abs(KK.sum()) < tol # Check the matrix sum. assert abs(control_values[0] - keo.sum()) < tol # Check the 1-norm of the matrix |Re(K)| + |Im(K)|. # This equals the 1-norm of the matrix defined by the block # structure # Re(K) -Im(K) # Im(K) Re(K). K = abs(keo.real) + abs(keo.imag) assert abs(control_values[1] - numpy.max(K.sum(0))) < tol return
def df_dlmbda(self, psi, mu): keo_prime = pyfvm.get_fvm_matrix(self.mesh, edge_kernels=[EnergyPrime(mu)]) out = (keo_prime * psi) / self.mesh.control_volumes # same as in f() i_psi = 1j * psi out -= self.inner(i_psi, out) / self.inner(i_psi, i_psi) * i_psi return out
def test(): class EnergyEdgeKernel: def __init__(self): self.subdomains = [None] return def eval(self, mesh, cell_mask): edge_ce_ratio = mesh.ce_ratios[..., cell_mask] beta = 1.0 return numpy.array([ [edge_ce_ratio, -edge_ce_ratio * numpy.exp(1j * beta)], [-edge_ce_ratio * numpy.exp(-1j * beta), edge_ce_ratio], ]) vertices, cells = meshzoo.rectangle(0.0, 2.0, 0.0, 1.0, 101, 51) mesh = meshplex.MeshTri(vertices, cells) matrix = pyfvm.get_fvm_matrix(mesh, [EnergyEdgeKernel()], [], [], []) rhs = mesh.control_volumes.copy() sa = pyamg.smoothed_aggregation_solver(matrix, smooth="energy") u = sa.solve(rhs, tol=1e-10) # Cannot write complex data ot VTU; split real and imaginary parts first. # <http://stackoverflow.com/a/38902227/353337> mesh.write("out.vtk", point_data={"u": u.view("(2,)float")}) return
def jacobian(self, psi, mu): keo = split_sparse_matrix( pyfvm.get_fvm_matrix(self.mesh, edge_kernels=[Energy(mu)])) cv = to_real(self.mesh.control_volumes) # cv * alpha V = numpy.zeros(cv.shape[0]) V[0::2] = self.V alpha = V + self.g * 2.0 * abs2(psi) jac = keo.copy() diag0 = jac.diagonal(0) b = multiply(cv, alpha)[0::2] diag0[0::2] += b diag0[1::2] += b jac.setdiag(diag0, k=0) # cv * beta beta = self.g * square(psi) diag0 = jac.diagonal(0) b = multiply(cv, beta)[0::2] diag0[0::2] += b diag0[1::2] -= b jac.setdiag(diag0, k=0) # b = multiply(cv, beta)[1::2] diag1 = jac.diagonal(1) diag1[0::2] += b jac.setdiag(diag1, k=1) # diag2 = jac.diagonal(-1) diag2[0::2] += b jac.setdiag(diag2, k=-1) return jac
def test(): class EnergyEdgeKernel(object): def __init__(self): self.subdomains = [None] return def eval(self, mesh, cell_mask): edge_ce_ratio = mesh.ce_ratios[..., cell_mask] beta = 1.0 return numpy.array( [ [edge_ce_ratio, -edge_ce_ratio * numpy.exp(1j * beta)], [-edge_ce_ratio * numpy.exp(-1j * beta), edge_ce_ratio], ] ) vertices, cells = meshzoo.rectangle(0.0, 2.0, 0.0, 1.0, 101, 51) mesh = meshplex.MeshTri(vertices, cells) matrix = pyfvm.get_fvm_matrix(mesh, [EnergyEdgeKernel()], [], [], []) rhs = mesh.control_volumes.copy() sa = pyamg.smoothed_aggregation_solver(matrix, smooth="energy") u = sa.solve(rhs, tol=1e-10) # Cannot write complex data ot VTU; split real and imaginary parts first. # <http://stackoverflow.com/a/38902227/353337> mesh.write("out.vtk", point_data={"u": u.view("(2,)float")}) return
def df_dlmbda(self, psi, mu): keo_prime = split_sparse_matrix( pyfvm.get_fvm_matrix(self.mesh, edge_kernels=[EnergyPrime(mu)])) out = keo_prime * psi # same as in f() i_psi = to_real(1j * to_complex(psi)) out -= self.inner(i_psi, out) / self.inner(i_psi, i_psi) * i_psi return out
def f(self, psi, mu): keo = pyfvm.get_fvm_matrix(self.mesh, edge_kernels=[Energy(mu)]) cv = self.mesh.control_volumes out = (keo * psi) / cv + (self.V + self.g * np.abs(psi) ** 2) * psi # Algebraically, The inner product of <f(psi), i*psi> is always 0. We project # out that component numerically to avoid convergence failure for the Jacobian # updates close to a solution. If this is not done, the Krylov method might hang # at something like 10^{-7}. i_psi = 1j * psi out -= self.inner(i_psi, out) / self.inner(i_psi, i_psi) * i_psi return out
def test_jacobian(filename, control_values): filename = download_mesh(filename) mu = 1.0e-2 mesh = meshplex.read(filename) m2 = meshio.read(filename) psi = m2.point_data["psi"][:, 0] + 1j * m2.point_data["psi"][:, 1] V = -1.0 g = 1.0 keo = pyfvm.get_fvm_matrix(mesh, edge_kernels=[Energy(mu)]) def jacobian(psi): def _apply_jacobian(phi): cv = mesh.control_volumes y = keo * phi / cv + alpha * phi + gPsi0Squared * phi.conj() return y alpha = V + g * 2.0 * (psi.real**2 + psi.imag**2) gPsi0Squared = g * psi**2 num_unknowns = len(mesh.node_coords) return pykry.LinearOperator( (num_unknowns, num_unknowns), complex, dot=_apply_jacobian, dot_adj=_apply_jacobian, ) # Get the Jacobian J = jacobian(psi) tol = 1.0e-12 num_unknowns = psi.shape[0] # [1+i, 1+i, 1+i, ... ] phi = numpy.full(num_unknowns, 1 + 1j) val = numpy.vdot(phi, mesh.control_volumes * (J * phi)).real assert abs(control_values[0] - val) < tol # [1, 1, 1, ... ] phi = numpy.full(num_unknowns, 1.0, dtype=complex) val = numpy.vdot(phi, mesh.control_volumes * (J * phi)).real assert abs(control_values[1] - val) < tol # [i, i, i, ... ] phi = numpy.full(num_unknowns, 1j, dtype=complex) val = numpy.vdot(phi, mesh.control_volumes * (J * phi)).real assert abs(control_values[2] - val) < tol return
def test_jacobian(filename, control_values): filename = download_mesh(filename) mu = 1.0e-2 mesh, point_data, field_data, _ = meshplex.read(filename) psi = point_data["psi"][:, 0] + 1j * point_data["psi"][:, 1] V = -1.0 g = 1.0 keo = pyfvm.get_fvm_matrix(mesh, edge_kernels=[Energy(mu)]) def jacobian(psi): def _apply_jacobian(phi): cv = mesh.control_volumes y = keo * phi / cv + alpha * phi + gPsi0Squared * phi.conj() return y alpha = V + g * 2.0 * (psi.real ** 2 + psi.imag ** 2) gPsi0Squared = g * psi ** 2 num_unknowns = len(mesh.node_coords) return pykry.LinearOperator( (num_unknowns, num_unknowns), complex, dot=_apply_jacobian, dot_adj=_apply_jacobian, ) # Get the Jacobian J = jacobian(psi) tol = 1.0e-12 num_unknowns = psi.shape[0] # [1+i, 1+i, 1+i, ... ] phi = numpy.full(num_unknowns, 1 + 1j) val = numpy.vdot(phi, mesh.control_volumes * (J * phi)).real assert abs(control_values[0] - val) < tol # [1, 1, 1, ... ] phi = numpy.full(num_unknowns, 1.0, dtype=complex) val = numpy.vdot(phi, mesh.control_volumes * (J * phi)).real assert abs(control_values[1] - val) < tol # [i, i, i, ... ] phi = numpy.full(num_unknowns, 1j, dtype=complex) val = numpy.vdot(phi, mesh.control_volumes * (J * phi)).real assert abs(control_values[2] - val) < tol return
def __init__(self): a = 20.0 points, cells = meshzoo.rectangle(-a / 2, a / 2, -a / 2, a / 2, 40, 40) self.mesh = meshplex.MeshTri(points, cells) self.A, _ = pyfvm.get_fvm_matrix(self.mesh, [Poisson()]) # k = 1 # ka = 4.5 # DD = 8 # nu = np.sqrt(1 / DD) # kbcrit = np.sqrt(1 + ka * nu) self.a = 4.0 self.d1 = 1.0 self.d2 = 2.0
def jacobian(self, psi, mu): keo = pyfvm.get_fvm_matrix(self.mesh, edge_kernels=[Energy(mu)]) cv = self.mesh.control_volumes def _apply_jacobian(phi): return (keo * phi) / cv + alpha * phi + beta * phi.conj() alpha = self.V + self.g * 2.0 * (psi.real ** 2 + psi.imag ** 2) beta = self.g * psi ** 2 num_unknowns = len(self.mesh.points) return pykry.LinearOperator( (num_unknowns, num_unknowns), complex, dot=_apply_jacobian, dot_adj=_apply_jacobian, )
def f(self, psi, mu): keo = split_sparse_matrix( pyfvm.get_fvm_matrix(self.mesh, edge_kernels=[Energy(mu)])) cv = to_real(self.mesh.control_volumes) V = numpy.zeros(cv.shape[0]) V[0::2] = self.V out = keo * psi + multiply(cv, multiply(psi, V + self.g * abs2(psi))) # Algebraically, The inner product of <f(psi), i*psi> is always 0. We project # out that component numerically to avoid convergence failure for the Jacobian # updates close to a solution. If this is not done, the Krylov method might hang # at something like 10^{-7}. i_psi = to_real(1j * to_complex(psi)) out -= self.inner(i_psi, out) / self.inner(i_psi, i_psi) * i_psi return out
def test_f(filename, control_values): filename = download_mesh(filename) mesh = meshplex.read(filename) mu = 1.0e-2 V = -1.0 g = 1.0 keo = pyfvm.get_fvm_matrix(mesh, edge_kernels=[Energy(mu)]) # compute the Ginzburg-Landau residual m2 = meshio.read(filename) psi = m2.point_data["psi"][:, 0] + 1j * m2.point_data["psi"][:, 1] cv = mesh.control_volumes # One divides by the control volumes here. No idea why this has been done in pynosh. # Perhaps to make sure that even the small control volumes have a significant # contribution to the residual? r = keo * psi / cv + psi * (V + g * abs(psi)**2) # scale with D for compliance with the Nosh (C++) tests if mesh.control_volumes is None: mesh.compute_control_volumes() r *= mesh.control_volumes tol = 1.0e-13 # For C++ Nosh compatibility: # Compute 1-norm of vector (Re(psi[0]), Im(psi[0]), Re(psi[1]), ... ) alpha = numpy.linalg.norm(r.real, ord=1) + numpy.linalg.norm(r.imag, ord=1) assert abs(control_values[0] - alpha) < tol assert abs(control_values[1] - numpy.linalg.norm(r, ord=2)) < tol # For C++ Nosh compatibility: # Compute inf-norm of vector (Re(psi[0]), Im(psi[0]), Re(psi[1]), ... ) alpha = max( numpy.linalg.norm(r.real, ord=numpy.inf), numpy.linalg.norm(r.imag, ord=numpy.inf), ) assert abs(control_values[2] - alpha) < tol return
def test_f(filename, control_values): filename = download_mesh(filename) mesh, point_data, field_data, _ = meshplex.read(filename) mu = 1.0e-2 V = -1.0 g = 1.0 keo = pyfvm.get_fvm_matrix(mesh, edge_kernels=[Energy(mu)]) # compute the Ginzburg-Landau residual psi = point_data["psi"][:, 0] + 1j * point_data["psi"][:, 1] cv = mesh.control_volumes # One divides by the control volumes here. No idea why this has been done in pynosh. # Perhaps to make sure that even the small control volumes have a significant # contribution to the residual? r = keo * psi / cv + psi * (V + g * abs(psi) ** 2) # scale with D for compliance with the Nosh (C++) tests if mesh.control_volumes is None: mesh.compute_control_volumes() r *= mesh.control_volumes tol = 1.0e-13 # For C++ Nosh compatibility: # Compute 1-norm of vector (Re(psi[0]), Im(psi[0]), Re(psi[1]), ... ) alpha = numpy.linalg.norm(r.real, ord=1) + numpy.linalg.norm(r.imag, ord=1) assert abs(control_values[0] - alpha) < tol assert abs(control_values[1] - numpy.linalg.norm(r, ord=2)) < tol # For C++ Nosh compatibility: # Compute inf-norm of vector (Re(psi[0]), Im(psi[0]), Re(psi[1]), ... ) alpha = max( numpy.linalg.norm(r.real, ord=numpy.inf), numpy.linalg.norm(r.imag, ord=numpy.inf), ) assert abs(control_values[2] - alpha) < tol return
def jacobian_solver(self, psi, mu, rhs): keo = pyfvm.get_fvm_matrix(self.mesh, edge_kernels=[Energy(mu)]) cv = self.mesh.control_volumes def prec_inv(psi): prec = keo.copy() # Add diagonal to avoid singularity for mu = 0. Also, this is a better # preconditioner. diag = prec.diagonal() diag += self.g * 2.0 * (psi.real ** 2 + psi.imag ** 2) * cv prec.setdiag(diag) return prec def prec(psi): p = prec_inv(psi) def _apply(phi): # ml = pyamg.smoothed_aggregation_solver(p, phi) # out = ml.solve(b=phi, tol=1e-12) out = spsolve(p, phi) return out num_unknowns = len(self.mesh.points) return pykry.LinearOperator( (num_unknowns, num_unknowns), complex, dot=_apply, dot_adj=_apply ) jac = self.jacobian(psi, mu) out = pykry.gmres( A=jac, b=rhs, M=prec(psi), inner_product=self.inner, maxiter=100, tol=1.0e-12, # Minv=prec_inv(psi), # U=1j * psi, ) # print("Krylov iterations:", out.iter) # print("Krylov residual:", out.resnorms[-1]) # res = jac * out.xk - rhs # print("Krylov residual (explicit):", np.sqrt(self.norm2_r(res))) # self.ax1.semilogy(out.resnorms) # self.ax1.grid() # plt.show() # Since # # (J_psi) psi = K psi + (-1 +2|psi|^2) psi - psi^2 conj(psi) # = K psi - psi + psi |psi|^2 # = f(psi), # # we have (J_psi)(i psi) = i f(psi). The RHS is typically f(psi) or # df/dlmbda(psi), but obviously # # <f(psi), (J_psi)(i psi)> = <f(psi), i f(psi)> = 0, # # so the i*psi-component in the solution plays no role if the rhs is f(psi). # Using 0 as a starting guess for Krylov, the solution will have no component in # the i*psi-direction. This means that the Newton updates won't jump around the # solution manifold. It wouldn't matter if they did, though. # TODO show this for df/dlmbda as well # i_psi = 1j * psi # out.xk -= self.inner(i_psi, out.xk) / self.inner(i_psi, i_psi) * i_psi # print("solution component i*psi", self.inner(i_psi, out.xk) / np.sqrt(self.inner(i_psi, i_psi))) return out.xk
def test(): mu = 5.0e-2 V = -1.0 g = 1.0 class Energy(object): """Specification of the kinetic energy operator. """ def __init__(self): self.magnetic_field = mu * numpy.array([0.0, 0.0, 1.0]) self.subdomains = [None] return def eval(self, mesh, cell_mask): nec = mesh.idx_hierarchy[..., cell_mask] X = mesh.node_coords[nec] edge_midpoint = 0.5 * (X[0] + X[1]) edge = X[1] - X[0] edge_ce_ratio = mesh.ce_ratios[..., cell_mask] # project the magnetic potential on the edge at the midpoint magnetic_potential = 0.5 * numpy.cross(self.magnetic_field, edge_midpoint) # The dot product <magnetic_potential, edge>, executed for many # points at once; cf. <http://stackoverflow.com/a/26168677/353337>. beta = numpy.einsum("...k,...k->...", magnetic_potential, edge) return numpy.array( [ [edge_ce_ratio, -edge_ce_ratio * numpy.exp(-1j * beta)], [-edge_ce_ratio * numpy.exp(1j * beta), edge_ce_ratio], ] ) vertices, cells = meshzoo.rectangle(-5.0, 5.0, -5.0, 5.0, 51, 51) mesh = meshplex.MeshTri(vertices, cells) keo = pyfvm.get_fvm_matrix(mesh, edge_kernels=[Energy()]) def f(psi): cv = mesh.control_volumes return keo * psi + cv * psi * (V + g * abs(psi) ** 2) def jacobian(psi): def _apply_jacobian(phi): cv = mesh.control_volumes y = keo * phi + cv * alpha * phi + cv * gPsi0Squared * phi.conj() return y alpha = V + g * 2.0 * (psi.real ** 2 + psi.imag ** 2) gPsi0Squared = g * psi ** 2 num_unknowns = len(mesh.node_coords) return pykry.LinearOperator( (num_unknowns, num_unknowns), complex, dot=_apply_jacobian, dot_adj=_apply_jacobian, ) def jacobian_solver(psi0, rhs): jac = jacobian(psi0) out = pykry.gmres( A=jac, b=rhs, inner_product=lambda a, b: numpy.dot(a.T.conj(), b).real, maxiter=1000, tol=1.0e-10, ) return out.xk u0 = numpy.ones(len(vertices), dtype=complex) u = pyfvm.newton(f, jacobian_solver, u0) mesh.write("out.vtk", point_data={"u": u.view("(2,)float")}) return