Exercise: Solving a Forward Problem with PiTGWFlow¶
Welcome to this exercise on using the Physics-Informed Transient
Groundwater Flow model, PiTGWFlow,
available in fusionlab-learn. This model is a
Physics-Informed Neural Network (PINN) that learns by satisfying
the governing physical equations rather than by fitting to labeled
data.
We will perform a forward problem simulation. This means we will provide the model with known physical parameters and a domain, and its task will be to discover the hydraulic head field, \(h(t, x, y)\), that satisfies the groundwater flow PDE.
Learning Objectives:
Generate a set of “collocation points” within a defined spatio-temporal domain.
Understand how to configure
PiTGWFlowwith both fixed and learnable physical parameters.Create a
tf.data.Datasetsuitable for a PINN, including the use of dummy targets for Keras API compatibility.Define, compile, and train the
PiTGWFlowmodel using its custom, physics-based training loop.Visualize the training progress by plotting the PDE loss.
Visualize the final, continuous solution learned by the network.
Let’s get started!
Prerequisites¶
Ensure you have fusionlab-learn and its common dependencies
installed. For visualizations, matplotlib is also needed.
pip install fusionlab-learn matplotlib
Step 1: Imports and Setup¶
First, we import all necessary libraries and set up our environment for reproducibility and cleaner output.
1import os
2import numpy as np
3import tensorflow as tf
4import matplotlib.pyplot as plt
5import warnings
6
7# FusionLab imports
8from fusionlab.nn.pinn import PiTGWFlow
9from fusionlab.params import LearnableK
10
11# Suppress warnings and TF logs for cleaner output
12warnings.filterwarnings('ignore')
13tf.get_logger().setLevel('ERROR')
14os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
15
16# Directory for saving any output images from this exercise
17EXERCISE_OUTPUT_DIR = "./pitgwflow_exercise_outputs"
18os.makedirs(EXERCISE_OUTPUT_DIR, exist_ok=True)
19
20print("Libraries imported and setup complete for PiTGWFlow exercise.")
Expected Output:
Libraries imported and setup complete for PiTGWFlow exercise.
Step 2: Generate Collocation Points¶
Unlike traditional models, PINNs are not trained on labeled data. Instead, they are trained on “collocation points”—randomly sampled points in the time and space domain where the governing PDE is enforced.
1N_POINTS = 5000
2SEED = 42
3tf.random.set_seed(SEED)
4
5# Define the domain boundaries [t_min, t_max], [x_min, x_max], etc.
6t_bounds, x_bounds, y_bounds = [0., 1.], [-1., 1.], [-1., 1.]
7
8# Generate random points within the domain
9coords = {
10 't': tf.random.uniform((N_POINTS, 1), *t_bounds),
11 'x': tf.random.uniform((N_POINTS, 1), *x_bounds),
12 'y': tf.random.uniform((N_POINTS, 1), *y_bounds),
13}
14
15print(f"Generated {N_POINTS} random collocation points.")
16print(f"Shape of 't' tensor: {coords['t'].shape}")
Expected Output:
Generated 5000 random collocation points.
Shape of 't' tensor: (5000, 1)
Step 3: Prepare the Dataset for Training¶
We package our collocation points into a tf.data.Dataset for
efficient training. For compatibility with the standard Keras
.fit() API, we must provide a “dummy” target tensor. This
target is completely ignored by PiTGWFlow’s custom training
logic, as the loss is calculated from the PDE residual, not from a
data-driven error.
1BATCH_SIZE = 128
2
3# Create dummy targets (an array of zeros)
4dummy_targets = tf.zeros_like(coords['t'])
5
6# Create the dataset
7dataset = tf.data.Dataset.from_tensor_slices(
8 (coords, dummy_targets)
9).shuffle(buffer_size=N_POINTS).batch(BATCH_SIZE)
10
11print(f"Dataset created with batch size {BATCH_SIZE}.")
12print(f"Dataset element spec: {dataset.element_spec}")
Expected Output:
Dataset created with batch size 128.
Dataset element spec: ({'t': TensorSpec(shape=(None, 1), dtype=tf.float32, name=None), 'x': TensorSpec(shape=(None, 1), dtype=tf.float32, name=None), 'y': TensorSpec(shape=(None, 1), dtype=tf.float32, name=None)}, TensorSpec(shape=(None, 1), dtype=tf.float32, name=None))
Step 4: Define, Compile, and Train PiTGWFlow¶
Now we instantiate PiTGWFlow. We will set most physical
parameters as fixed constants but define hydraulic conductivity \(K\)
as a LearnableK object. This demonstrates how the model can be
used to infer physical parameters. We then compile and train the model.
1# Instantiate the PINN model
2pinn_model = PiTGWFlow(
3 hidden_units=[50, 50, 50],
4 activation='tanh',
5 K=LearnableK(initial_value=0.5), # Start with a guess for K
6 Ss=1e-4, # This is a fixed value
7 Q=0.1 # A constant source term
8)
9
10# Compile the model (no loss needed, it's handled internally)
11pinn_model.compile()
12
13# Train the model
14print("\nStarting PiTGWFlow model training...")
15history = pinn_model.fit(
16 dataset,
17 epochs=20,
18 verbose=1
19)
20print("Training complete.")
Expected Output:
Starting PiTGWFlow model training...
Epoch 1/20
40/40 [==============================] - 3s 4ms/step - pde_loss: 0.0125
Epoch 2/20
40/40 [==============================] - 0s 4ms/step - pde_loss: 5.123e-04
...
Epoch 20/20
40/40 [==============================] - 0s 4ms/step - pde_loss: 8.910e-06
Training complete.
Step 5: Visualize Training History¶
We can plot the pde_loss from the training history to confirm
that the model successfully learned to minimize the PDE residual. A
log scale on the y-axis is helpful to see the rapid decrease in loss.
1print("\nPlotting training history...")
2plt.figure(figsize=(10, 6))
3plt.plot(history.history['pde_loss'], label='PDE Loss')
4plt.yscale('log')
5plt.title('PiTGWFlow Training History')
6plt.xlabel('Epoch')
7plt.ylabel('Log PDE Loss')
8plt.legend()
9plt.grid(True, which="both", ls="--")
10fig_path = os.path.join(EXERCISE_OUTPUT_DIR, "pitgwflow_exercise_loss.png")
11plt.savefig(fig_path)
12plt.show()
Example Output Plot:
An example plot showing the PDE loss decreasing over epochs. This demonstrates that the neural network is successfully learning a solution that conforms to the governing physics.¶
Step 6: Visualize the Learned Solution¶
The great advantage of a PINN is that it represents a continuous solution. We can evaluate the trained model on a regular grid of points to visualize the hydraulic head field \(h(t, x, y)\) at a specific moment in time.
1# Create a meshgrid for visualization at a specific time t
2t_slice = 0.5
3x_range = np.linspace(x_bounds[0], x_bounds[1], 100)
4y_range = np.linspace(y_bounds[0], y_bounds[1], 100)
5X, Y = np.meshgrid(x_range, y_range)
6
7# --- FIX: Prepare grid points for prediction ---
8# The model expects a batch of points, not a grid. We need to
9# flatten the X and Y grids into a list of (x, y) coordinates.
10# The shape for each input tensor must be (N, 1).
11x_flat = tf.convert_to_tensor(X.ravel(), dtype=tf.float32)
12y_flat = tf.convert_to_tensor(Y.ravel(), dtype=tf.float32)
13
14# Create the corresponding 't' tensor for each point
15t_flat = tf.fill(x_flat.shape, t_slice)
16
17# Reshape all to be column vectors (N, 1)
18grid_coords = {
19 't': tf.reshape(t_flat, (-1, 1)),
20 'x': tf.reshape(x_flat, (-1, 1)),
21 'y': tf.reshape(y_flat, (-1, 1))
22}
23
24# Predict the hydraulic head 'h' on the flattened grid
25h_pred_flat = pinn_model.predict(grid_coords)
26
27# --- FIX: Reshape the flat predictions back to the grid shape ---
28# The output prediction will be flat, so we reshape it to the
29# original grid's shape (100x100) for plotting with contourf.
30h_pred_grid = tf.reshape(h_pred_flat, X.shape)
31
32# Plot the contour of the solution
33plt.figure(figsize=(9, 7))
34contour = plt.contourf(X, Y, h_pred_grid, 100, cmap='viridis')
35plt.colorbar(contour, label='Hydraulic Head (h)')
36plt.title(f'Learned Hydraulic Head Solution at t = {t_slice}')
37plt.xlabel('x-coordinate')
38plt.ylabel('y-coordinate')
39plt.axis('equal')
40fig_path = os.path.join(EXERCISE_OUTPUT_DIR, "pitgwflow_exercise_solution.png")
41plt.savefig(fig_path)
42plt.show()
43
44# Or use the utility function to easily visualize the result.
45
46# from fusionlab.nn.pinn.utils import plot_hydraulic_head
47
48# plot_hydraulic_head(
49# model=pinn_model,
50# t_slice=0.5,
51# x_bounds=(-1.0, 1.0),
52# y_bounds=(-1.0, 1.0),
53# resolution=100,
54# save_path=os.path.join(EXERCISE_OUTPUT_DIR, "pitgwflow_exercise_solution.png"),
55# show_plot=True
56# )
Expected Plot:
Visualization of the continuous hydraulic head field \(h(x, y)\)
at a fixed time, as learned by the PiTGWFlow model. The plot
shows the model’s ability to generate a complete solution over the
entire domain.¶
Discussion of Exercise¶
Congratulations! In this exercise, you have successfully used the
PiTGWFlow model to solve a forward physics problem:
You correctly generated collocation points to define the problem domain instead of using labeled data.
You prepared a
tf.data.Datasetcompatible with the Keras API for an unsupervised, physics-driven task.You instantiated, trained, and evaluated the
PiTGWFlowmodel, observing the decrease in the physics-based PDE loss.You visualized the final output, demonstrating that the model learned a continuous solution to the governing equation across the entire domain.
This exercise provides a solid foundation for using PINNs to tackle more complex scientific and engineering problems.