def compute_eig(M, filename, parameters=[]): """ Compute eigenvalues of a PETScMatrix M, and print to filename """ mpirank = MPI.rank(M.mpi_comm()) if mpirank == 0: print '\t\tCompute eigenvalues' eigsolver = SLEPcEigenSolver(M) eigsolver.parameters.update(parameters) eigsolver.solve() if mpirank == 0: print '\t\tSort eigenvalues' eig = [] for ii in range(eigsolver.get_number_converged()): eig.append(eigsolver.get_eigenvalue(ii)[0]) eig.sort() print '\t\tPrint results to file' np.savetxt(filename, np.array(eig))
def solve(self, n=None): """ solve(n=None): Solve the eigenvalue problem and return the converged eigenvalues n indicates the number of eigenvalues to be calculated """ # Standard eigenvalue problem if self.b is None: A = PETScMatrix() assemble(self.a, tensor=A) if self.bc: self.bc.apply(A) solver = SLEPcEigenSolver(A) solver.solve() # Generalized eigenvalue problem else: A = PETScMatrix() assemble(self.a, tensor=A) B = PETScMatrix() assemble(self.b, tensor=B) # Set options for eigensolver solver = SLEPcEigenSolver(A, B) for key, key_info in self.parameters.iterdata(): solver.parameters[key] = self.parameters[key] if n is None: n = A.size(0) if self.bc: self.bc.apply(A) self.bc.apply(B) solver.solve(n) # Pick real part of eigenvalues computed m = solver.get_number_converged() complex_eps = 0.001 eigenvalues = [solver.get_eigenvalue(i)[0] for i in range(m) if abs(solver.get_eigenvalue(i)[1]) < complex_eps] if len(eigenvalues) == 0: raise RuntimeError("No real-valued eigenvalues found") # Compute all eigenvalues if eigenvalue is zero and not only # testing for stability if (n < A.size(0) and abs(eigenvalues[0]) < ascot_parameters["eps"] and not ascot_parameters["only_stable"]): info("Only zero eigenvalues detected. Computing all.") solver.solve(A.size(0)) m = solver.get_number_converged() eigenvalues = [solver.get_eigenvalue(i)[0] for i in range(m) if abs(solver.get_eigenvalue(i)[1]) < 0.1] return eigenvalues
class ELSolver(ModeSolver): """ fully tensorial elastic mode solver """ def __init__(self,mesh,materials): super(ELSolver, self).__init__(mesh, materials) self.q_b = 0.0 self.lagrange_order = 2 self._VComplex = None def assemble_matrices(self): V = VectorElement("Lagrange", self.mesh.ufl_cell(), self.lagrange_order, dim = 3) self._VComplex = FunctionSpace(self.mesh, V*V) u = TrialFunction(self._VComplex) (ur, ui) = split(u) v = TestFunction(self._VComplex) (vr, vi) = split(v) A_ij = None B_ij = None # construct matrices for each domain if not isinstance(self.materials, collections.Iterable): material = self.materials C = as_matrix(material.el.C) rho = material.el.rho DX = material.domain A_ij = LHS(self.q_b, C, ur, ui, vr, vi, DX) B_ij = RHS(rho, ur, ui, vr, vi, DX) else: counter = 0 # a work around for summing forms for material in self.materials: C = as_matrix(material.el.C) rho = material.el.rho DX = material.domain if counter == 0: A_ij = LHS(self.q_b, C, ur, ui, vr, vi, DX) B_ij = RHS(rho, ur, ui, vr, vi, DX) counter += 1 else: a_ij = LHS(self.q_b, C, ur, ui, vr, vi, DX) b_ij = RHS(rho, ur, ui, vr, vi, DX) A_ij += a_ij B_ij += b_ij # assemble the matrices assemble(A_ij, tensor=self._A) assemble(B_ij, tensor=self._B) def setup_solver(self): shift = (2.0*pi*self.eigenmode_guess)**2 self.esolver = SLEPcEigenSolver(self._A, self._B) self.esolver.parameters["solver"] = "krylov-schur" self.esolver.parameters["tolerance"] = 1e-12 self.esolver.parameters["spectral_transform"] = "shift-and-invert" self.esolver.parameters["spectral_shift"] = shift self.esolver.parameters["spectrum"] = "target magnitude" def set_clamped_walls(self): bcs = build_clamped_walls(self._VComplex) bcs.apply(self._A) bcs.apply(self._B) def set_free_walls(self): # this is the natural BC when no changes are made # to the assembled matrices pass def calculate_power(self, eigenvalue_index): r, c, rx, cx = self.esolver.get_eigenpair(eigenvalue_index) u = Function(self._VComplex, rx) omega_sqrd = r*(1e9)**2 # construct matrices for each domain if not isinstance(self.materials, collections.Iterable): material = self.materials rho = material.el.rho DX = material.domain norm = assemble(dot(u, rho*u)*DX) p = 0.5*omega_sqrd*norm return p else: p = 0.0 for material in self.materials: rho = material.el.rho DX = material.domain norm = assemble(dot(u, rho*u)*DX) p += 0.5*omega_sqrd*norm return p def compute_eigenvalues(self): self.esolver.solve(self.n_modes) if self.esolver.get_number_converged()==0: print( 'Eigensolver did not calculate eigenmodes. Increase log level') for i in range(0,self.esolver.get_number_converged(),1): r, c = self.esolver.get_eigenvalue(i) try: f_b = (r)**0.5/(2*pi) print('Frequency {} GHz'.format(f_b)) if self.plot_eigenmodes == True: # TODO save plots to a folder r, c, xr, xi = self.esolver.get_eigenpair(i) f = Function(self._VComplex, xr) plot_displacement(self.mesh, f) except: print("Found negative frequency") def extract_field(self, eigenvalue_index): r, c, xr, xi = self.esolver.get_eigenpair(eigenvalue_index) f = Function(self._VComplex, xr) f_b = (r)**0.5/(2*pi) return (f, f_b)
class EigenSolver(AbstractEigenSolver): def __init__(self, V, A, B=None, bcs=None): self.V = V if bcs is not None: self._set_boundary_conditions(bcs) A = self._assemble_if_form(A) if B is not None: B = self._assemble_if_form(B) self._set_operators(A, B) if self.B is not None: self.eigen_solver = SLEPcEigenSolver(self.condensed_A, self.condensed_B) else: self.eigen_solver = SLEPcEigenSolver(self.condensed_A) @staticmethod @overload def _assemble_if_form(mat: Form): return assemble(mat, keep_diagonal=True) @staticmethod @overload def _assemble_if_form(mat: ParametrizedTensorFactory): return evaluate(mat) @staticmethod @overload def _assemble_if_form(mat: Matrix.Type()): return mat def _set_boundary_conditions(self, bcs): # List all local and constrained local dofs local_dofs = set() constrained_local_dofs = set() for bc in bcs: dofmap = bc.function_space().dofmap() local_range = dofmap.ownership_range() local_dofs.update(list(range(local_range[0], local_range[1]))) constrained_local_dofs.update([ dofmap.local_to_global_index(local_dof_index) for local_dof_index in bc.get_boundary_values().keys() ]) # List all unconstrained dofs unconstrained_local_dofs = local_dofs.difference( constrained_local_dofs) unconstrained_local_dofs = list(unconstrained_local_dofs) # Generate IS accordingly comm = bcs[0].function_space().mesh().mpi_comm() for bc in bcs: assert comm == bc.function_space().mesh().mpi_comm() self._is = PETSc.IS().createGeneral(unconstrained_local_dofs, comm) def _set_operators(self, A, B): if hasattr(self, "_is"): # there were Dirichlet BCs (self.A, self.condensed_A) = self._condense_matrix(A) if B is not None: (self.B, self.condensed_B) = self._condense_matrix(B) else: (self.B, self.condensed_B) = (None, None) else: (self.A, self.condensed_A) = (as_backend_type(A), as_backend_type(A)) if B is not None: (self.B, self.condensed_B) = (as_backend_type(B), as_backend_type(B)) else: (self.B, self.condensed_B) = (None, None) def _condense_matrix(self, mat): mat = as_backend_type(mat) petsc_version = PETSc.Sys().getVersionInfo() if petsc_version["major"] == 3 and petsc_version["minor"] <= 7: condensed_mat = mat.mat().getSubMatrix(self._is, self._is) else: condensed_mat = mat.mat().createSubMatrix(self._is, self._is) return mat, PETScMatrix(condensed_mat) def set_parameters(self, parameters): # Helper functions cpp_code = """ #include <pybind11/pybind11.h> #include <dolfin/la/PETScLUSolver.h> // defines PCFactorSetMatSolverType macro for PETSc <= 3.8 #include <dolfin/la/SLEPcEigenSolver.h> void throw_error(PetscErrorCode ierr, std::string reason); void set_linear_solver(std::shared_ptr<dolfin::SLEPcEigenSolver> eigen_solver, std::string lu_method) { ST st; KSP ksp; PC pc; PetscErrorCode ierr; ierr = EPSGetST(eigen_solver->eps(), &st); if (ierr != 0) throw_error(ierr, "EPSGetST"); ierr = STGetKSP(st, &ksp); if (ierr != 0) throw_error(ierr, "STGetKSP"); ierr = KSPGetPC(ksp, &pc); if (ierr != 0) throw_error(ierr, "KSPGetPC"); ierr = STSetType(st, STSINVERT); if (ierr != 0) throw_error(ierr, "STSetType"); ierr = KSPSetType(ksp, KSPPREONLY); if (ierr != 0) throw_error(ierr, "KSPSetType"); ierr = PCSetType(pc, PCLU); if (ierr != 0) throw_error(ierr, "PCSetType"); ierr = PCFactorSetMatSolverType(pc, lu_method.c_str()); if (ierr != 0) throw_error(ierr, "PCFactorSetMatSolverType"); } void throw_error(PetscErrorCode ierr, std::string reason) { throw std::runtime_error("Error in set_linear_solver: reason " + reason + ", error code " + std::to_string(ierr)); } PYBIND11_MODULE(SIGNATURE, m) { m.def("set_linear_solver", &set_linear_solver); } """ cpp_module = compile_cpp_code(cpp_code) set_linear_solver = cpp_module.set_linear_solver if "spectral_transform" in parameters and parameters[ "spectral_transform"] == "shift-and-invert": parameters["spectrum"] = "target real" if "linear_solver" in parameters: set_linear_solver(self.eigen_solver, parameters["linear_solver"]) parameters.pop("linear_solver") self.eigen_solver.parameters.update(parameters) def solve(self, n_eigs=None): assert n_eigs is not None self.eigen_solver.solve(n_eigs) assert self.eigen_solver.get_number_converged() >= n_eigs def get_eigenvalue(self, i): assert i < self.eigen_solver.get_number_converged() return self.eigen_solver.get_eigenvalue(i) def get_eigenvector(self, i): assert i < self.eigen_solver.get_number_converged() # Initialize eigenvectors real_vector = PETScVector() imag_vector = PETScVector() self.A.init_vector(real_vector, 0) self.A.init_vector(imag_vector, 0) # Condense input vectors if hasattr(self, "_is"): # there were Dirichlet BCs condensed_real_vector = PETScVector(real_vector.vec().getSubVector( self._is)) condensed_imag_vector = PETScVector(imag_vector.vec().getSubVector( self._is)) else: condensed_real_vector = real_vector condensed_imag_vector = imag_vector # Get eigenpairs if dolfin_version.startswith( "2018.1"): # TODO remove when 2018.2.0 is released # Helper functions cpp_code = """ #include <pybind11/pybind11.h> #include <dolfin/la/PETScVector.h> #include <dolfin/la/SLEPcEigenSolver.h> void get_eigen_pair(std::shared_ptr<dolfin::SLEPcEigenSolver> eigen_solver, std::shared_ptr<dolfin::PETScVector> condensed_real_vector, std::shared_ptr<dolfin::PETScVector> condensed_imag_vector, std::size_t i) { const PetscInt ii = static_cast<PetscInt>(i); double real_value; double imag_value; EPSGetEigenpair(eigen_solver->eps(), ii, &real_value, &imag_value, condensed_real_vector->vec(), condensed_imag_vector->vec()); } PYBIND11_MODULE(SIGNATURE, m) { m.def("get_eigen_pair", &get_eigen_pair); } """ get_eigen_pair = compile_cpp_code(cpp_code).get_eigen_pair get_eigen_pair(self.eigen_solver, condensed_real_vector, condensed_imag_vector, i) else: self.eigen_solver.get_eigenpair(condensed_real_vector, condensed_imag_vector, i) # Restore input vectors if hasattr(self, "_is"): # there were Dirichlet BCs real_vector.vec().restoreSubVector(self._is, condensed_real_vector.vec()) imag_vector.vec().restoreSubVector(self._is, condensed_imag_vector.vec()) # Return as Function return (Function(self.V, real_vector), Function(self.V, imag_vector))
class EigenSolver(AbstractEigenSolver): def __init__(self, V, A, B=None, bcs=None): self.V = V if bcs is not None: self._set_boundary_conditions(bcs) A = self._assemble_if_form(A) if B is not None: B = self._assemble_if_form(B) self._set_operators(A, B) if self.B is not None: self.eigen_solver = SLEPcEigenSolver(self.condensed_A, self.condensed_B) else: self.eigen_solver = SLEPcEigenSolver(self.condensed_A) @staticmethod @overload def _assemble_if_form(mat: Form): return assemble(mat, keep_diagonal=True) @staticmethod @overload def _assemble_if_form(mat: ParametrizedTensorFactory): return evaluate(mat) @staticmethod @overload def _assemble_if_form(mat: Matrix.Type()): return mat def _set_boundary_conditions(self, bcs): # List all local and constrained local dofs local_dofs = set() constrained_local_dofs = set() for bc in bcs: dofmap = bc.function_space().dofmap() local_range = dofmap.ownership_range() local_dofs.update(list(range(local_range[0], local_range[1]))) constrained_local_dofs.update([ dofmap.local_to_global_index(local_dof_index) for local_dof_index in bc.get_boundary_values().keys() ]) # List all unconstrained dofs unconstrained_local_dofs = local_dofs.difference( constrained_local_dofs) unconstrained_local_dofs = list(unconstrained_local_dofs) # Generate IS accordingly comm = bcs[0].function_space().mesh().mpi_comm() for bc in bcs: assert comm == bc.function_space().mesh().mpi_comm() self._is = PETSc.IS().createGeneral(unconstrained_local_dofs, comm) def _set_operators(self, A, B): if hasattr(self, "_is"): # there were Dirichlet BCs (self.A, self.condensed_A) = self._condense_matrix(A) if B is not None: (self.B, self.condensed_B) = self._condense_matrix(B) else: (self.B, self.condensed_B) = (None, None) else: (self.A, self.condensed_A) = (as_backend_type(A), as_backend_type(A)) if B is not None: (self.B, self.condensed_B) = (as_backend_type(B), as_backend_type(B)) else: (self.B, self.condensed_B) = (None, None) def _condense_matrix(self, mat): mat = as_backend_type(mat) petsc_version = PETSc.Sys().getVersionInfo() if petsc_version["major"] == 3 and petsc_version["minor"] <= 7: condensed_mat = mat.mat().getSubMatrix(self._is, self._is) else: condensed_mat = mat.mat().createSubMatrix(self._is, self._is) return mat, PETScMatrix(condensed_mat) def set_parameters(self, parameters): self.eigen_solver.parameters.update(parameters) def solve(self, n_eigs=None): assert n_eigs is not None self.eigen_solver.solve(n_eigs) def get_eigenvalue(self, i): return self.eigen_solver.get_eigenvalue(i) def get_eigenvector(self, i): # Helper functions if has_pybind11(): cpp_code = """ #include <pybind11/pybind11.h> #include <dolfin/la/PETScVector.h> #include <dolfin/la/SLEPcEigenSolver.h> PetscInt get_converged(std::shared_ptr<dolfin::SLEPcEigenSolver> eigen_solver) { PetscInt num_computed_eigenvalues; EPSGetConverged(eigen_solver->eps(), &num_computed_eigenvalues); return num_computed_eigenvalues; } void get_eigen_pair(std::shared_ptr<dolfin::SLEPcEigenSolver> eigen_solver, std::size_t i, std::shared_ptr<dolfin::PETScVector> condensed_real_vector, std::shared_ptr<dolfin::PETScVector> condensed_imag_vector) { const PetscInt ii = static_cast<PetscInt>(i); double real_value; double imag_value; EPSGetEigenpair(eigen_solver->eps(), ii, &real_value, &imag_value, condensed_real_vector->vec(), condensed_imag_vector->vec()); } PYBIND11_MODULE(SIGNATURE, m) { m.def("get_converged", &get_converged); m.def("get_eigen_pair", &get_eigen_pair); } """ cpp_module = compile_cpp_code(cpp_code) get_converged = cpp_module.get_converged get_eigen_pair = cpp_module.get_eigen_pair else: def get_converged(eigen_solver): return eigen_solver.eps().getConverged() def get_eigen_pair(eigen_solver, i, condensed_real_vector, condensed_imag_vector): eigen_solver.eps().getEigenpair(i, condensed_real_vector.vec(), condensed_imag_vector.vec()) # Get number of computed eigenvectors/values num_computed_eigenvalues = get_converged(self.eigen_solver) if (i < num_computed_eigenvalues): # Initialize eigenvectors real_vector = PETScVector() imag_vector = PETScVector() self.A.init_vector(real_vector, 0) self.A.init_vector(imag_vector, 0) # Condense input vectors if hasattr(self, "_is"): # there were Dirichlet BCs condensed_real_vector = PETScVector( real_vector.vec().getSubVector(self._is)) condensed_imag_vector = PETScVector( imag_vector.vec().getSubVector(self._is)) else: condensed_real_vector = real_vector condensed_imag_vector = imag_vector # Get eigenpairs get_eigen_pair(self.eigen_solver, i, condensed_real_vector, condensed_imag_vector) # Restore input vectors if hasattr(self, "_is"): # there were Dirichlet BCs real_vector.vec().restoreSubVector(self._is, condensed_real_vector.vec()) imag_vector.vec().restoreSubVector(self._is, condensed_imag_vector.vec()) # Return as Function return (Function(self.V, real_vector), Function(self.V, imag_vector)) else: raise RuntimeError("Requested eigenpair has not been computed")
class EMSolver(ModeSolver): """ electromagnetic mode solver """ def __init__(self,mesh,materials, wavelength): super(EMSolver, self).__init__(mesh, materials) self.wavelength = wavelength self.mesh_unit = 1e-6 self._combined_space = None self._finite_element = None self.nedelec_order = 1 self.lagrange_order = 1 self._k_o_squared = (2.0*pi/self.wavelength)**2 def assemble_matrices(self): nedelec = FiniteElement( "Nedelec 1st kind H(curl)", self.mesh.ufl_cell(), self.nedelec_order) lagrange = FiniteElement( "Lagrange", self.mesh.ufl_cell(), self.lagrange_order) self._finite_element = nedelec*lagrange self._combined_space = FunctionSpace(self.mesh, nedelec*lagrange) # define the test and trial functions from the combined space (N_i, L_i) = TestFunctions(self._combined_space) (N_j, L_j) = TrialFunctions(self._combined_space) # construct matrices for each domain if not isinstance(self.materials, collections.Iterable): material = self.materials u_r = material.em.u_r e_r = material.em.e_r DX = material.domain (A_ij, B_ij) = em_weak_form(N_i, N_j, L_i, L_j, u_r, e_r, self._k_o_squared,DX) else: counter = 0 # a work around for summing forms for material in self.materials: u_r = material.em.u_r e_r = material.em.e_r DX = material.domain if counter == 0: (A_ij, B_ij) = em_weak_form(N_i, N_j, L_i, L_j, u_r, e_r, self._k_o_squared, DX) counter += 1 else: (a_ij, b_ij) = em_weak_form(N_i, N_j, L_i, L_j, u_r, e_r, self._k_o_squared, DX) A_ij += a_ij B_ij += b_ij # assemble the matrices assemble(A_ij, tensor=self._A) assemble(B_ij, tensor=self._B) def setup_solver(self): self.esolver= SLEPcEigenSolver(self._A, self._B) shift = -(2.0*pi*self.eigenmode_guess/self.wavelength)**2 self.esolver.parameters["solver"] = "krylov-schur" self.esolver.parameters["tolerance"] = 1e-12 self.esolver.parameters["spectral_transform"] = "shift-and-invert" self.esolver.parameters["spectrum"] = "target magnitude" self.esolver.parameters["spectral_shift"] = shift def set_electric_walls(self): bcs = build_electric_walls(self._combined_space) bcs.apply(self._A) bcs.apply(self._B) def extract_normalized_field(self, eigenvalue_index): """ extract and rescale the E field from the solver note that Ex, Ey, are real, Ez is imaginary """ r, c, rx, cx = self.esolver.get_eigenpair(eigenvalue_index) e_raw = Function(self._combined_space, rx) n_eff = ((-r)**0.5)*self.wavelength/(2*pi) gamma = (abs(r))**0.5 E = as_vector([e_raw[0], e_raw[1], gamma*e_raw[2]]) return (E, n_eff) def calculate_power(self, eigenvalue_index, ng): """ 1. note the normalization of Ez in the equations 2. assume u_r = 1 everywhere """ r, c, rx, cx = self.esolver.get_eigenpair(eigenvalue_index) f = Function(self._combined_space, rx) gamma_sqrd = abs(r) (Et, Ez) = split(f) # construct matrices for each domain if not isinstance(self.materials, collections.Iterable): material = self.materials e_r = material.em.e_r DX = material.domain int_t = assemble(dot(Et,Et)*DX) int_z = assemble(Ez*Ez*DX)*gamma_sqrd power = 0.5*(c0/ng*eps0*e_r*(int_t+ int_z)) return power else: power = 0.0 for material in self.materials: e_r = material.em.e_r DX = material.domain int_t = assemble(dot(Et,Et)*DX) int_z = assemble(Ez*Ez*DX)*gamma_sqrd power += 0.5*(c0/ng*eps0*e_r*(int_t + int_z)) return power def compute_eigenvalues(self): self.esolver.solve(self.n_modes) if self.esolver.get_number_converged()==0: print('Eigensolver did not converge') for i in range(0,self.esolver.get_number_converged(),1): r, c = self.esolver.get_eigenvalue(i) try: f_b = ((-r)**0.5)*self.wavelength/(2*pi) print('Effective index: {} '.format(f_b)) if self.plot_eigenmodes == True: # TODO save plots to a folder r, c, xr, xi = self.esolver.get_eigenpair(i) e_raw = Function(self._combined_space, xr) plot_transverse_field(e_raw) except: print("Found negative frequency")