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
def jacobian_solver(self, psi, mu, rhs): def _apply_jacobian(phi): out = ((0.5 * self.A * phi) / self.mesh.control_volumes + (self.V - mu + 2.0 * (psi.real**2 + psi.imag**2)) * phi + psi**2 * phi.conj()) # out[self.mesh.is_boundary_node] = 1.0 return out n = len(self.mesh.points) jac = pykry.LinearOperator((n, n), complex, dot=_apply_jacobian, dot_adj=_apply_jacobian) def prec(psi): def _apply(phi): prec = 0.5 * self.A diag = prec.diagonal() cv = self.mesh.control_volumes diag += (self.V + 2.0 * (psi.real**2 + psi.imag**2)) * cv prec.setdiag(diag) # TODO pyamg solve out = spsolve(prec, phi) return out num_unknowns = len(self.mesh.points) return pykry.LinearOperator((num_unknowns, num_unknowns), complex, dot=_apply, dot_adj=_apply) 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, ) return out.xk
def jacobian_solver(self, psi, mu, rhs): abs_psi2 = numpy.zeros(psi.shape[0]) abs_psi2[0::2] += psi[0::2]**2 + psi[1::2]**2 cv = to_real(self.mesh.control_volumes) 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 prec(psi): p = prec_inv(psi) def _apply(phi): out = spsolve(p, phi) return out num_unknowns = len(self.mesh.node_coords) return pykry.LinearOperator((2 * num_unknowns, 2 * num_unknowns), float, dot=_apply, dot_adj=_apply) jac = self.jacobian(psi, mu) # Cannot use direct solve since jacobian is always singular # return spsolve(jac, rhs) out = pykry.gmres( A=jac, b=rhs, # TODO enable preconditioner # M=prec(psi), inner_product=self.inner, maxiter=100, tol=1.0e-12, ) return out.xk
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