Source code for phasefieldx.Element.Allen_Cahn.solver.solver_static

'''
Solver: Free Energy (Allen-Cahn)
================================

Static

'''

# Libraries ############################################################
########################################################################
import os
import time
import dolfinx
import ufl

from phasefieldx.files import prepare_simulation, append_results_to_file
from phasefieldx.solvers.newton import NewtonSolver
from phasefieldx.Logger.library_versions import set_logger, log_library_versions, log_system_info, log_end_analysis, log_model_information
from phasefieldx.Element.Allen_Cahn.potential import potential_function, potential_function_derivative, potential_coefficient
from phasefieldx.Element.Allen_Cahn.energy import calculate_potential_energy


[docs] def solve(Data, msh, final_time, V_Φ, bc_list_phi=[], update_boundary_conditions=None, update_loading=None, initial_condition=None, ds_bound=None, dt=1.0, path=None, quadrature_degree=2, case="DOUBLE", V_gradient_Φ=None): """ Solver for the Free Energy (Allen-Cahn) equation. Parameters ---------- Data : phasefieldx.Data Data object containing simulation parameters. msh : dolfinx.cpp.mesh.Mesh Mesh object defining the computational domain. final_time : float Final pseudo time for the simulation. V_phi : dolfinx.fem.FunctionSpace Function space for the phase-field variable phi. bc_list_phi : list of dolfinx.fem.DirichletBC, optional List of Dirichlet boundary conditions for phi (default is []). update_boundary_conditions : function, optional Function to update boundary conditions based on time (default is None). update_loading : function, optional Function to update external loading conditions based on time (default is None). initial_condition : dolfinx.fem.Function, optional Initial condition for phi (default is None). ds_bound : numpy.ndarray, optional Array containing boundary descriptions for reaction forces (default is None). dt : float, optional Time step size (default is 1.0). path : str, optional Path to store simulation results (default is current working directory). Returns ------- None Notes ----- This function initializes and solves the Allen-Cahn equation for the phase-field variable phi, which represents a free energy minimization problem. It uses a Newton-type solver to update phi over the specified time period. Simulation progress is logged, and results are saved using Paraview-compatible formats. Examples -------- # Initialize Data, msh, V_phi, and optionally update functions solve(Data, msh, 10.0, V_phi, bc_list_phi, update_boundary_conditions, update_loading) # This will simulate the free energy minimization problem using Allen-Cahn equation, # saving results in the specified directory. """ # Get MPI communicator info comm = msh.comm rank = comm.Get_rank() if path is None: path = os.getcwd() # Common - Only rank 0 handles file operations ###################################################################### result_folder_name = Data.results_folder_name if rank == 0: prepare_simulation(path, result_folder_name) logger = set_logger(result_folder_name) log_system_info(logger) # log system information log_library_versions(logger) # log Library versions Data.save_log_info(logger) # log Simulation input data Data.save_parameters_to_csv(os.path.join(result_folder_name, "parameters.input")) log_model_information(msh, logger) else: logger = None # Synchronize all processes comm.Barrier() # Dolfinx cpp logger - all processes dolfinx.log.set_log_level(dolfinx.log.LogLevel.INFO) if rank == 0: dolfinx.cpp.log.set_output_file( os.path.join(result_folder_name, "dolfinx.log")) # Formulation ########################################################## ######################################################################## # Phase-field ------------------------- Φ = dolfinx.fem.Function(V_Φ, name="phi") δΦ = ufl.TestFunction(V_Φ) metadata = {"quadrature_degree": quadrature_degree} dx = ufl.Measure("dx", domain=msh, metadata=metadata) # Phase-field ------------------------------------------------------------ c0 = potential_coefficient(case) F_phi = (1.0/(c0*Data.l)*potential_function_derivative(Φ, case)*δΦ + Data.l * 2/c0*ufl.inner(ufl.grad(Φ), ufl.grad(δΦ)))*dx J_phi = ufl.derivative(F_phi, Φ) problem = dolfinx.fem.petsc.NewtonSolverNonlinearProblem( F_phi, Φ, bcs=bc_list_phi, J=J_phi) solver_phi = NewtonSolver(problem) if rank == 0 and logger: solver_phi.save_log_info(logger) # Solve ################################################################ ######################################################################## start = time.perf_counter() if rank == 0 and logger: logger.info(f" start time: {start}") # Paraview files - DOLFINx handles parallel I/O automatically if Data.save_solution_xdmf: paraview_solution_folder_name_xdmf = os.path.join( result_folder_name, "paraview-solutions_xdmf") # Create directory only on rank 0 if rank == 0: os.makedirs(paraview_solution_folder_name_xdmf, exist_ok=True) comm.Barrier() # Wait for directory creation xdmf_phi = dolfinx.io.XDMFFile(msh.comm, os.path.join( paraview_solution_folder_name_xdmf, "phi.xdmf"), "w") xdmf_phi.write_mesh(msh) if V_gradient_Φ is not None: gradient_Φ = dolfinx.fem.Function(V_gradient_Φ, name="gradient_phi") if Data.save_solution_vtu: paraview_solution_folder_name_vtu = os.path.join( result_folder_name, "paraview-solutions_vtu") # Create directory only on rank 0 if rank == 0: os.makedirs(paraview_solution_folder_name_vtu, exist_ok=True) comm.Barrier() # Wait for directory creation vtk_sol = dolfinx.io.VTKFile(msh.comm, os.path.join( paraview_solution_folder_name_vtu, "phasefieldx.pvd"), "w") if rank == 0 and logger: logger.info(f" S t a r t i n g A n a l y s i s ") logger.info(f" ---------------------------------- ") logger.info(f" ---------------------------------- ") t = 0 step = 0 while t < final_time: if rank == 0 and logger: logger.info( f"\n\nSolution at (pseudo) time = {t}, dt = {dt}, Step = {step} ") logger.info( f"===========================================================================") if update_boundary_conditions is not None: bc_ux = update_boundary_conditions(bc_list_phi, t) # if update_loading is not None: # f, grad_f = update_loading(x, 0) # Phase-field solution - all processes participate if rank == 0 and logger: logger.info(f">>> Solving phase for dofs: Φ ") phi_iterations, _ = solver_phi.solver.solve(Φ) if rank == 0 and logger: logger.info(f" Newton iterations: {phi_iterations}") residual_Φ = solver_phi.ksp.getResidualNorm() logger.info(f" Residual norm Φ: {residual_Φ}") # Save results - Only rank 0 writes text files ###################################################################### if rank == 0: if logger: logger.info(f"\n\n Saving results: ") # conv --------------------------------------------------------------- append_results_to_file(os.path.join( result_folder_name, "phasefieldx.conv"), '#step\titerations', step, phi_iterations) # Energy ------------------------------------------------------------- gamma, gamma_phi, gamma_gradphi = calculate_potential_energy(Φ, Data.l, comm, case, dx) # Only rank 0 writes energy results if rank == 0: append_results_to_file(os.path.join(result_folder_name, "total.energy"), '#step\tgamma\tgamma_phi\tgamma_gradphi', step, gamma, gamma_phi, gamma_gradphi) if V_gradient_Φ is not None: # Compute gradient of Φ and save to gradient_Φ function gradient_expr = dolfinx.fem.Expression(ufl.grad(Φ), V_gradient_Φ.element.interpolation_points()) gradient_Φ.interpolate(gradient_expr) # Paraview ----------------------------------------------------------- if Data.save_solution_xdmf: xdmf_phi.write_function(Φ, step) if V_gradient_Φ is not None: xdmf_phi.write_function(gradient_Φ, step) if Data.save_solution_vtu: if V_gradient_Φ is not None: vtk_sol.write_function([Φ, gradient_Φ], step) else: vtk_sol.write_function([Φ], step) t += dt step += 1 # Cleanup - all processes if Data.save_solution_xdmf: xdmf_phi.close() if Data.save_solution_vtu: vtk_sol.close() if rank == 0: end = time.perf_counter() if logger: log_end_analysis(logger, end - start)