ID_Mat_PINNs / utils.py
ktongue's picture
Update files
a93948a verified
import os
"""
This module provides utility functions for solving and identifying parameters in a 2D linear elasticity PDE using DeepXDE and PyTorch. The main functionalities include defining the exact solution, constructing the PDE, creating and training PINN models, saving/loading models, and making predictions.
Functions:
- exact_solution(points, lam, mu, Q=4.0):
Computes the exact analytical solution for displacement, stress, and source terms at given points for a 2D elasticity problem with specified Lamé parameters (lambda, mu) and a source parameter Q.
- build_pde(lmbd, mu):
Constructs the PDE residuals for the 2D linear elasticity problem in terms of DeepXDE's autodiff backend, parameterized by trainable Lamé parameters (lmbd, mu). Returns a function suitable for DeepXDE's PDE definition.
- create_model(true_lam, true_mu, init_lam=None, init_mu=None, n_points=5000):
Sets up the DeepXDE model for the elasticity problem, including geometry, boundary conditions, observation points, and the neural network. Returns the model and trainable Lamé variables.
- train_model(model, lmbd, mu, iterations_adam=5000, verbose=True):
Trains the DeepXDE model using Adam and L-BFGS optimizers, updating the Lamé parameters. Returns the estimated parameters, loss history, and training state.
- save_model(model, lmbd, mu, filepath):
Saves the trained model's state dictionary and identified Lamé parameters to a file using PyTorch.
- load_model_for_prediction(filepath):
Loads a saved model checkpoint and returns the neural network and identified Lamé parameters for prediction.
- predict(net, n_points=50):
Uses the trained neural network to predict displacement and stress fields on a regular grid over the domain. Returns results as a dictionary containing grid coordinates and predicted fields.
The module is designed for parameter identification and field prediction in 2D elasticity using physics-informed neural networks (PINNs).
"""
# Exemple d'usage pour chaque fonction du module
os.environ["DDE_BACKEND"] = "pytorch"
import numpy as np
import deepxde as dde
import torch
def exact_solution(points, lam, mu, Q=4.0):
x = points[:, 0:1]
y = points[:, 1:2]
ux = np.cos(2 * np.pi * x) * np.sin(np.pi * y)
uy = np.sin(np.pi * x) * (Q / 4) * y**4
exx = -2 * np.pi * np.sin(2 * np.pi * x) * np.sin(np.pi * y)
eyy = np.sin(np.pi * x) * Q * y**3
exy = 0.5 * (
np.pi * np.cos(2 * np.pi * x) * np.cos(np.pi * y)
+ np.pi * np.cos(np.pi * x) * (Q / 4) * y**4
)
sxx = (lam + 2 * mu) * exx + lam * eyy
syy = (lam + 2 * mu) * eyy + lam * exx
sxy = 2 * mu * exy
fx = lam * (
4 * np.pi**2 * np.cos(2 * np.pi * x) * np.sin(np.pi * y)
- np.pi * np.cos(np.pi * x) * Q * y**3
) + mu * (
9 * np.pi**2 * np.cos(2 * np.pi * x) * np.sin(np.pi * y)
- np.pi * np.cos(np.pi * x) * Q * y**3
)
fy = lam * (
-3 * np.sin(np.pi * x) * Q * y**2
+ 2 * np.pi**2 * np.sin(2 * np.pi * x) * np.cos(np.pi * y)
) + mu * (
-6 * np.sin(np.pi * x) * Q * y**2
+ 2 * np.pi**2 * np.sin(2 * np.pi * x) * np.cos(np.pi * y)
+ np.pi**2 * np.sin(np.pi * x) * Q * y**4 / 4
)
return ux, uy, sxx, syy, sxy, fx, fy
def build_pde(lmbd, mu):
"""
Construit une fonction représentant le système d'équations aux dérivées partielles (EDP) pour l'élasticité linéaire en 2D, basé sur les paramètres de Lamé.
Paramètres
----------
lmbd : float
Premier paramètre de Lamé (lambda), caractérisant le matériau.
mu : float
Deuxième paramètre de Lamé (mu), également appelé module de cisaillement.
Retourne
-------
pde : function
Fonction prenant en entrée deux arguments :
- x : ndarray, coordonnées spatiales (N, 2)
- y : ndarray, variables dépendantes (N, 5) [u_x, u_y, σ_xx, σ_yy, σ_xy]
et retournant une liste de résidus des équations :
- Équilibre des forces (momentum_x, momentum_y)
- Loi de Hooke (hooke_xx, hooke_yy, hooke_xy)
Notes
-----
Cette fonction est conçue pour être utilisée avec DeepXDE ou des frameworks similaires pour la résolution de PINNs (Physics-Informed Neural Networks).
Les équations implémentées incluent :
- Les équations de l'équilibre mécanique (momentum)
- Les relations constitutives de Hooke pour un matériau isotrope (hooke)
- Un terme de force volumique analytique (fx, fy) dépendant de x et y
"""
def pde(x, y):
ux = y[:, 0:1]
uy = y[:, 1:2]
sxx = y[:, 2:3]
syy = y[:, 3:4]
sxy = y[:, 4:5]
exx = dde.grad.jacobian(ux, x, i=0, j=0)
eyy = dde.grad.jacobian(uy, x, i=0, j=1)
exy = 0.5 * (
dde.grad.jacobian(ux, x, i=0, j=1) + dde.grad.jacobian(uy, x, i=0, j=0)
)
Q = 4.0
pi = np.pi
cos = dde.backend.cos
sin = dde.backend.sin
fx = lmbd * (
4 * pi**2 * cos(2 * pi * x[:, 0:1]) * sin(pi * x[:, 1:2])
- pi * cos(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 3
) + mu * (
9 * pi**2 * cos(2 * pi * x[:, 0:1]) * sin(pi * x[:, 1:2])
- pi * cos(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 3
)
fy = lmbd * (
-3 * sin(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 2
+ 2 * pi**2 * sin(2 * pi * x[:, 0:1]) * cos(pi * x[:, 1:2])
) + mu * (
-6 * sin(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 2
+ 2 * pi**2 * sin(2 * pi * x[:, 0:1]) * cos(pi * x[:, 1:2])
+ pi**2 * sin(pi * x[:, 0:1]) * Q * x[:, 1:2] ** 4 / 4
)
sxx_x = dde.grad.jacobian(sxx, x, i=0, j=0)
sxy_x = dde.grad.jacobian(sxy, x, i=0, j=0)
sxy_y = dde.grad.jacobian(sxy, x, i=0, j=1)
syy_y = dde.grad.jacobian(syy, x, i=0, j=1)
momentum_x = sxx_x + sxy_y + fx
momentum_y = sxy_x + syy_y + fy
hooke_xx = (lmbd + 2 * mu) * exx + lmbd * eyy - sxx
hooke_yy = (lmbd + 2 * mu) * eyy + lmbd * exx - syy
hooke_xy = 2 * mu * exy - sxy
return [momentum_x, momentum_y, hooke_xx, hooke_yy, hooke_xy]
return pde
def create_model(true_lam, true_mu, init_lam=None, init_mu=None, n_points=5000):
"""
Crée et retourne un modèle DeepXDE pour résoudre un problème d'élasticité linéaire 2D avec conditions aux limites et observations.
Args:
true_lam (float): Valeur réelle du paramètre de Lamé λ (lambda) pour la solution exacte.
true_mu (float): Valeur réelle du paramètre de Lamé μ (mu) pour la solution exacte.
init_lam (float, optional): Valeur initiale pour λ utilisée dans l'entraînement du modèle. Si None, utilise 2 * true_lam.
init_mu (float, optional): Valeur initiale pour μ utilisée dans l'entraînement du modèle. Si None, utilise 2 * true_mu.
n_points (int, optional): Nombre de points d'observation générés aléatoirement dans le domaine pour les conditions initiales. Par défaut à 5000.
Returns:
tuple: (model, lmbd, mu)
- model (dde.Model): Le modèle DeepXDE construit avec les conditions aux limites, observations et réseau de neurones.
- lmbd (dde.Variable): Variable entraînable représentant λ (lambda).
- mu (dde.Variable): Variable entraînable représentant μ (mu).
Remarques:
- Le domaine géométrique est un carré [0, 1] x [0, 1].
- Les conditions de Dirichlet sont appliquées sur le bord gauche (ux=0) et le bord inférieur (uy=0).
- Les observations sont générées à partir de la solution exacte fournie par la fonction `exact_solution`.
- Le réseau de neurones utilisé est un FNN à 4 couches cachées de 50 neurones chacune, avec activation "tanh".
"""
if init_lam is None:
init_lam = true_lam * 2.0
if init_mu is None:
init_mu = true_mu * 2.0
lmbd = dde.Variable(init_lam)
mu = dde.Variable(init_mu)
geom = dde.geometry.Rectangle([0, 0], [1, 1])
def boundary_left(x, on_boundary):
return on_boundary and np.isclose(x[0], 0)
def boundary_bottom(x, on_boundary):
return on_boundary and np.isclose(x[1], 0)
bc_ux = dde.icbc.DirichletBC(geom, lambda x: 0.0, boundary_left, component=0)
bc_uy = dde.icbc.DirichletBC(geom, lambda x: 0.0, boundary_bottom, component=1)
pde_func = build_pde(lmbd, mu)
X_train = geom.random_points(n_points)
u_ex, v_ex, sxx_ex, syy_ex, sxy_ex, _, _ = exact_solution(
X_train, true_lam, true_mu
)
obs_values = np.hstack([u_ex, v_ex, sxx_ex, syy_ex, sxy_ex])
observations = dde.icbc.PointSetBC(X_train, obs_values, component=[0, 1, 2, 3, 4])
data = dde.data.PDE(
geom,
pde_func,
[bc_ux, bc_uy, observations],
num_domain=2000,
num_boundary=500,
num_test=5000,
)
net = dde.nn.FNN([2] + [50] * 4 + [5], "tanh", "Glorot uniform")
model = dde.Model(data, net)
return model, lmbd, mu
def train_model(model, lmbd, mu, iterations_adam=5000, verbose=True):
model.compile("adam", lr=0.001, external_trainable_variables=[lmbd, mu])
losshistory, train_state = model.train(
iterations=iterations_adam, display_every=1000 if verbose else 10000
)
model.compile("L-BFGS", external_trainable_variables=[lmbd, mu])
model.train(display_every=1000 if verbose else 10000)
try:
lambda_est = lmbd.item()
mu_est = mu.item()
except:
lambda_est = float(lmbd.detach().cpu().numpy())
mu_est = float(mu.detach().cpu().numpy())
return lambda_est, mu_est, losshistory, train_state
def save_model(model, lmbd, mu, filepath):
torch.save(
{
"model_state_dict": model.net.state_dict(),
"lambda_identified": lmbd.item() if hasattr(lmbd, "item") else float(lmbd),
"mu_identified": mu.item() if hasattr(mu, "item") else float(mu),
},
filepath,
)
def load_model_for_prediction(filepath):
net = dde.nn.FNN([2] + [50] * 4 + [5], "tanh", "Glorot uniform")
checkpoint = torch.load(filepath, map_location="cpu")
net.load_state_dict(checkpoint["model_state_dict"])
lambda_id = checkpoint.get("lambda_identified", None)
mu_id = checkpoint.get("mu_identified", None)
return net, lambda_id, mu_id
def predict(net, n_points=50):
x_lin = np.linspace(0, 1, n_points)
y_lin = np.linspace(0, 1, n_points)
X_grid, Y_grid = np.meshgrid(x_lin, y_lin)
X_plot = np.hstack((X_grid.flatten()[:, None], Y_grid.flatten()[:, None]))
X_tensor = torch.tensor(X_plot, dtype=torch.float32)
with torch.no_grad():
y_pred = net(X_tensor).numpy()
return {
"X_grid": X_grid,
"Y_grid": Y_grid,
"u_x": y_pred[:, 0].reshape(n_points, n_points),
"u_y": y_pred[:, 1].reshape(n_points, n_points),
"s_xx": y_pred[:, 2].reshape(n_points, n_points),
"s_yy": y_pred[:, 3].reshape(n_points, n_points),
"s_xy": y_pred[:, 4].reshape(n_points, n_points),
}
def generate_noisy_fem_data(points, lam, mu, noise_level=0.01, seed=None):
"""
Génère des données simulées avec bruit gaussien pour simuler des mesures FEM ou expérimentales.
Cette fonction est utilisée pour:
- Simuler des données expérimentales réalistes
- Tester la robustesse du PINN face au bruit
- Générer des datasets pour l'identification de matériaux inconnus
Args:
points: Points d'observation (N, 2)
lam: Paramètre λ (lambda)
mu: Paramètre μ (mu)
noise_level: Niveau de bruit relatif (défaut: 1%)
seed: Graine aléatoire pour reproductibilité
Returns:
tuple: (ux, uy, sxx, syy, sxy, fx, fy) avec bruit ajouté
"""
if seed is not None:
np.random.seed(seed)
ux, uy, sxx, syy, sxy, fx, fy = exact_solution(points, lam, mu)
ux_noisy = ux + np.random.normal(0, noise_level * np.abs(ux).mean(), ux.shape)
uy_noisy = uy + np.random.normal(0, noise_level * np.abs(uy).mean(), uy.shape)
sxx_noisy = sxx + np.random.normal(0, noise_level * np.abs(sxx).mean(), sxx.shape)
syy_noisy = syy + np.random.normal(0, noise_level * np.abs(syy).mean(), syy.shape)
sxy_noisy = sxy + np.random.normal(0, noise_level * np.abs(sxy).mean(), sxy.shape)
return ux_noisy, uy_noisy, sxx_noisy, syy_noisy, sxy_noisy, fx, fy
def create_model_with_noisy_data(
true_lam,
true_mu,
noise_level=0.01,
init_lam=None,
init_mu=None,
n_points=5000,
seed=None,
):
"""
Crée un modèle avec des données bruitées (simulation FEM/expérimentales).
Args:
true_lam: Valeur réelle de λ
true_mu: Valeur réelle de μ
noise_level: Niveau de bruit (défaut: 1%)
init_lam: Valeur initiale pour l'identification
init_mu: Valeur initiale pour l'identification
n_points: Nombre de points d'observation
seed: Graine aléatoire
Returns:
tuple: (model, lmbd, mu)
"""
if init_lam is None:
init_lam = true_lam * 2.0
if init_mu is None:
init_mu = true_mu * 2.0
lmbd = dde.Variable(init_lam)
mu = dde.Variable(init_mu)
geom = dde.geometry.Rectangle([0, 0], [1, 1])
def boundary_left(x, on_boundary):
return on_boundary and np.isclose(x[0], 0)
def boundary_bottom(x, on_boundary):
return on_boundary and np.isclose(x[1], 0)
bc_ux = dde.icbc.DirichletBC(geom, lambda x: 0.0, boundary_left, component=0)
bc_uy = dde.icbc.DirichletBC(geom, lambda x: 0.0, boundary_bottom, component=1)
pde_func = build_pde(lmbd, mu)
X_train = geom.random_points(n_points)
u_ex, v_ex, sxx_ex, syy_ex, sxy_ex, _, _ = generate_noisy_fem_data(
X_train, true_lam, true_mu, noise_level=noise_level, seed=seed
)
obs_values = np.hstack([u_ex, v_ex, sxx_ex, syy_ex, sxy_ex])
observations = dde.icbc.PointSetBC(X_train, obs_values, component=[0, 1, 2, 3, 4])
data = dde.data.PDE(
geom,
pde_func,
[bc_ux, bc_uy, observations],
num_domain=2000,
num_boundary=500,
num_test=5000,
)
net = dde.nn.FNN([2] + [50] * 4 + [5], "tanh", "Glorot uniform")
model = dde.Model(data, net)
return model, lmbd, mu
if __name__ == "__main__":
# Paramètres de Lamé vrais
true_lam = 2.0
true_mu = 1.0
# 1. Exemple pour exact_solution
points = np.array([[0.5, 0.5], [0.1, 0.9]])
ux, uy, sxx, syy, sxy, fx, fy = exact_solution(points, true_lam, true_mu)
print("Exemple exact_solution:")
print("ux:", ux)
print("uy:", uy)
# 2. Exemple pour build_pde
# (Utilisé dans create_model, voir ci-dessous)
# 3. Exemple pour create_model
model, lmbd, mu = create_model(true_lam, true_mu)
print("Exemple create_model:")
print("Modèle créé avec lmbd initial:", lmbd.item(), "et mu initial:", mu.item())
# 4. Exemple pour train_model
lambda_est, mu_est, losshistory, train_state = train_model(
model, lmbd, mu, iterations_adam=100
)
print("Exemple train_model:")
print("Paramètres identifiés:", lambda_est, mu_est)
# 5. Exemple pour save_model
save_path = "modele_elasticite.pth"
save_model(model, lmbd, mu, save_path)
print(f"Exemple save_model: modèle sauvegardé dans {save_path}")
# 6. Exemple pour load_model_for_prediction
net_loaded, lambda_id, mu_id = load_model_for_prediction(save_path)
print("Exemple load_model_for_prediction:")
print("lambda identifié:", lambda_id, "mu identifié:", mu_id)
# 7. Exemple pour predict
results = predict(net_loaded, n_points=10)
print("Exemple predict:")
print("u_x shape:", results["u_x"].shape)
print("u_y shape:", results["u_y"].shape)