import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import os
from enrichedfem.problem.utils import create_tree,get_random_params,compute_slope
from enrichedfem.error_estimations.utils import get_solver_type
[docs]
class GainsEnhancedFEM:
"""Calculate and analyze the gains of enhanced FEM methods.
This class calculates and analyzes the gains obtained by using enhanced
FEM methods (additive and multiplicative corrections) compared to standard
FEM and PINNs. It computes errors for different methods, degrees, and
mesh sizes, and provides functionalities to save and analyze the results.
Args:
n_params (int): The number of parameter samples to consider.
pb_considered: The problem being considered.
repo_dir (str): The repository directory. Defaults to "./".
high_degree (int): The highest degree of the finite element solution. Defaults to 10.
error_degree (int): The degree of the error space. Defaults to 4.
tab_nb_vert (list): A list of number of vertices to consider. Defaults to [20, 40].
tab_degree (list): A list of degrees to consider. Defaults to [1, 2, 3].
"""
def __init__(self, n_params, pb_considered, **kwargs):
# define the problem
self.n_params = n_params
self.pb_considered = pb_considered
self.__infos_from_problem()
repo_dir = kwargs.get('repo_dir', "./")
version_str = f"version{self.version}"
self.results_dir = repo_dir + f"/results/fenics/test_{self.dim}D/testcase{self.testcase}/{version_str}/gains/"
create_tree(self.results_dir)
print(f"## Results directory: {self.results_dir}")
# define the degrees and the number of vertices
self.high_degree = kwargs.get('high_degree', 10)
self.error_degree = kwargs.get('error_degree', 4)
self.tab_nb_vert = kwargs.get('tab_nb_vert', [20,40])
self.tab_degree = kwargs.get('tab_degree', [1,2,3])
# define if the reference solution is an analytical solution
self.save_uref = None
if not self.pb_considered.ana_sol:
savedir = self.results_dir + "u_ref/"
create_tree(savedir)
self.save_uref = [savedir + f"u_ref_{param_num}.npy" for param_num in range(self.n_params)]
def __infos_from_problem(self):
"""Extract problem information.
This method extracts information about the problem, such as dimension,
testcase, version, parameters, and solver type, from the
`pb_considered` attribute and stores them as attributes of the class.
It also generates random parameters within the problem's parameter
domain.
"""
self.dim = self.pb_considered.dim
self.testcase = self.pb_considered.testcase
self.version = self.pb_considered.version
parameter_domain = self.pb_considered.parameter_domain
self.params = get_random_params(self.n_params,parameter_domain)
self.solver_type = get_solver_type(self.dim,self.testcase,self.version)
[docs]
def read_csv(self, csv_file):
"""Read error data from a CSV file.
This method reads error data from a CSV file generated by the
`run_errors_deg` method. It extracts the mesh sizes (h), errors for
each parameter sample, and number of vertices.
Args:
csv_file (str): The path to the CSV file.
Returns:
tuple: A tuple containing the DataFrame, a list of mesh sizes (h),
and a NumPy array of errors for each parameter sample.
"""
df_method = pd.read_csv(csv_file)
tab_h_method = df_method.values[1,1:]
tab_err_method = df_method.values[2:,1:]
tab_nb_vert_method = df_method.values[0,1:]
assert all(tab_nb_vert_method) == all(self.tab_nb_vert)
return df_method,tab_h_method,tab_err_method
[docs]
def run_errors_deg(self, method, degree, **kwargs):
"""Run error calculations for a given method and degree.
This method calculates the L2 errors for a given method (FEM, PINNs,
additive correction "Corr", or multiplicative correction "Mult") and
degree, for sets of parameter and given mesh sizes. The results
are saved to a CSV file.
Args:
method (str): The error estimation method. Should be one of "FEM", "PINNs", "Corr", or "Mult".
degree (int): The degree of the finite element solution.
**kwargs: Additional keyword arguments. These may include:
new_run (bool): Whether to force a new run even if a CSV file exists. Defaults to False.
u_theta: The predicted solution (required for "Corr" and "Mult").
M (float): Lifting constant (required for "Mult").
impose_bc (bool): Whether to impose boundary conditions (required for "Mult").
Returns:
tuple: A tuple containing the DataFrame of errors, a list of mesh
sizes (h), and a NumPy array of errors for each parameter sample.
"""
new_run = kwargs.get('new_run', False)
assert method in ["FEM","PINNs","Corr","Mult"], f"method={method} is not implemented"
assert "u_theta" in kwargs if method in ["Corr","Mult"] else True, f"u_theta is required for {method}"
u_theta = kwargs.get('u_theta')
if method == "Mult":
assert 'M' in kwargs and 'impose_bc' in kwargs, f"M and impose_bc are required for {method}"
M = kwargs.get('M')
impose_bc = kwargs.get('impose_bc')
else:
assert not 'M' in kwargs and not 'impose_bc' in kwargs, f"M and impose_bc are not required for {method}"
# Define the csv_file
csv_file = self.results_dir+f"{method}_errors_case{self.testcase}_v{self.version}_degree{degree}"
if method == "Mult":
csv_file += f"_M{M}"
csv_file += '_weak' if not impose_bc else ''
csv_file += ".csv"
# Read the csv file if it exists (or if not new_run)
if not new_run and os.path.exists(csv_file):
print(f"## Read csv file : {csv_file}")
return self.read_csv(csv_file)
# Run the error estimation
print(f"## Run errrors with {method} for degree={degree}")
# tab_h_method = []
# tab_err_method = []
tab_h_method = []
tab_err_method = np.zeros((self.n_params,len(self.tab_nb_vert)))
solver = self.solver_type(params=self.params, problem=self.pb_considered, degree=degree, error_degree=self.error_degree, high_degree=self.high_degree, save_uref=self.save_uref)
for (j,nb_vert) in enumerate(self.tab_nb_vert):
print(f"nb_vert={nb_vert}")
solver.set_meshsize(nb_cell=nb_vert-1)
tab_h_method.append(np.round(solver.h,3))
for i in range(self.n_params):
print(i,end=" ")
if method == "FEM":
_,norme_L2 = solver.fem(i)
elif method == "PINNs":
norme_L2 = solver.pinns(i,u_theta)
elif method == "Corr":
_,_,norme_L2 = solver.corr_add(i,u_theta)
else:
_,_,norme_L2 = solver.corr_mult(i,u_theta,M=M,impose_bc=impose_bc)
tab_err_method[i,j] = norme_L2
col_names = [(method,str(self.tab_nb_vert[i]),tab_h_method[i]) for i in range(len(self.tab_nb_vert))]
mi = pd.MultiIndex.from_tuples(col_names, names=["method","n_vert","h"])
df_method = pd.DataFrame(tab_err_method,columns=mi)
df_method.to_csv(csv_file)
df_method = pd.DataFrame(tab_err_method,columns=mi)
df_method.to_csv(csv_file)
return df_method, tab_h_method, tab_err_method
[docs]
def run_errors_alldeg(self, method, **kwargs):
"""Run error calculations for a given method and all degrees.
This method calculates the L2 errors for a given method (FEM, PINNs,
"Corr", or "Mult") and all degrees specified in `self.tab_degree`,
for multiple parameter samples and mesh sizes. It calls the
`run_errors_deg` method for each degree.
Args:
method (str): The error estimation method. Should be one of "FEM", "PINNs", "Corr", or "Mult".
**kwargs: Additional keyword arguments. These may include:
new_run (bool): Whether to force a new run even if a CSV file exists. Defaults to False.
u_theta: The predicted solution (required for "Corr" and "Mult").
M (float): Lifting constant (required for "Mult").
impose_bc (bool): Whether to impose boundary conditions (required for "Mult").
Returns: None
"""
for degree in self.tab_degree:
self.run_errors_deg(method,degree,**kwargs)
[docs]
def run_fem_deg(self, degree, new_run=False):
"""Run FEM error calculations for a given degree.
This method runs the error calculations for the standard FEM for a
given degree, using the `run_errors_deg` method.
Args:
degree (int): The degree of the finite element solution.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns:
tuple: A tuple containing the DataFrame, a list of mesh sizes (h), and a NumPy array of errors for each parameter sample.
"""
return self.run_errors_deg("FEM",degree,new_run=new_run)
[docs]
def run_pinns_deg(self, degree, u_theta, new_run=False):
"""Run PINNs error calculations for a given degree.
This method runs the error calculations for the PINNs method for a
given degree, using the `run_errors_deg` method.
Args:
degree (int): The degree of the finite element solution.
u_theta: The predicted solution from the PINNs model.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns:
tuple: A tuple containing the DataFrame, a list of mesh sizes (h), and a NumPy array of errors for each parameter sample.
"""
return self.run_errors_deg("PINNs",degree,u_theta=u_theta,new_run=new_run)
[docs]
def run_corr_deg(self, degree, u_theta, new_run=False):
"""Run additive correction error calculations for a given degree.
This method runs the error calculations for the additive correction
method ("Corr") for a given degree, using the `run_errors_deg` method.
Args:
degree (int): The degree of the finite element solution.
u_theta: The predicted solution from the PINNs model.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns:
tuple: A tuple containing the DataFrame, a list of mesh sizes (h), and a NumPy array of errors for each parameter sample.
"""
return self.run_errors_deg("Corr",degree,u_theta=u_theta,new_run=new_run)
[docs]
def run_mult_deg_M(self, degree, u_theta, M=0.0, impose_bc=True, new_run=False):
"""Run multiplicative correction error calculations for a given degree and M value.
This method runs the error calculations for the multiplicative
correction method ("Mult") for a given degree and M value, using the
`run_errors_deg` method.
Args:
degree (int): The degree of the finite element solution.
u_theta: The predicted solution from the PINNs model.
M (float, optional): Lifting constant. Defaults to 0.0.
impose_bc (bool, optional): Whether to impose boundary conditions. Defaults to True.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns:
tuple: A tuple containing the DataFrame, a list of mesh sizes (h), and a NumPy array of errors for each parameter sample.
"""
return self.run_errors_deg("Mult",degree,u_theta=u_theta,M=M,impose_bc=impose_bc,new_run=new_run)
[docs]
def run_fem_alldeg(self, new_run=False):
"""Run FEM error calculations for all degrees.
This method runs the error calculations for the standard FEM for all
degrees specified in `self.tab_degree`, using the `run_errors_alldeg`
method.
Args:
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns: None
"""
self.run_errors_alldeg("FEM",new_run=new_run)
[docs]
def run_pinns_alldeg(self, u_theta, new_run=False):
"""Run PINNs error calculations for all degrees.
This method runs the error calculations for the PINNs method for all
degrees specified in `self.tab_degree`, using the
`run_errors_alldeg` method.
Args:
u_theta: The predicted solution from the PINNs model.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns: None
"""
self.run_errors_alldeg("PINNs",u_theta=u_theta,new_run=new_run)
[docs]
def run_corr_alldeg(self, u_theta, new_run=False):
"""Run additive correction error calculations for all degrees.
This method runs the error calculations for the additive correction
method ("Corr") for all degrees specified in `self.tab_degree`, using
the `run_errors_alldeg` method.
Args:
u_theta: The predicted solution from the PINNs model.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns: None
"""
self.run_errors_alldeg("Corr",u_theta=u_theta,new_run=new_run)
[docs]
def run_mult_alldeg_M(self, u_theta, M=0.0, impose_bc=True, new_run=False):
"""Run multiplicative correction error calculations for all degrees and a given M value.
This method runs the error calculations for the multiplicative
correction method ("Mult") for all degrees specified in
`self.tab_degree` and a given M value, using the `run_errors_alldeg`
method.
Args:
u_theta: The predicted solution from the PINNs model.
M (float, optional): Lifting constant. Defaults to 0.0.
impose_bc (bool, optional): Whether to impose boundary conditions. Defaults to True.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns: None
"""
self.run_errors_alldeg("Mult",u_theta=u_theta,M=M,impose_bc=impose_bc,new_run=new_run)
[docs]
def run_mult_deg_allM(self, degree, u_theta, tab_M, impose_bc=True, new_run=False):
"""Run multiplicative correction error calculations for a given degree and multiple M values.
This method runs the error calculations for the multiplicative
correction method ("Mult") for a given degree and a list of M values.
It calls the `run_mult_deg_M` method for each M value.
Args:
degree (int): The degree of the finite element solution.
u_theta: The predicted solution from the PINNs model.
tab_M (list): A list of M values to consider.
impose_bc (bool, optional): Whether to impose boundary conditions. Defaults to True.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns: None
"""
for M in tab_M:
self.run_mult_deg_M(degree,u_theta,M=M,impose_bc=impose_bc,new_run=new_run)
[docs]
def run_mult_alldeg_allM(self, u_theta, tab_M, impose_bc=True, new_run=False):
"""Run multiplicative correction error calculations for all degrees and multiple M values.
This method runs the error calculations for the multiplicative
correction method ("Mult") for all degrees specified in
`self.tab_degree` and a list of M values. It calls the
`run_mult_alldeg_M` method for each M value.
Args:
u_theta: The predicted solution from the PINNs model.
tab_M (list): A list of M values to consider.
impose_bc (bool, optional): Whether to impose boundary conditions. Defaults to True.
new_run (bool, optional): Whether to force a new run. Defaults to False.
Returns: None
"""
for M in tab_M:
self.run_mult_alldeg_M(u_theta,M=M,impose_bc=impose_bc,new_run=new_run)