import os
import time
import shutil
import numpy as np
import gradio as gr
import plotly.graph_objs as go
glob_k = 0.0025
glob_a = -2.
glob_b = 4.
glob_c = 7.5
def clear_npz():
current_directory = os.getcwd() # Get the current working directory
for filename in os.listdir(current_directory):
if filename.endswith(".npz"): # Check if the file ends with .npz
file_path = os.path.join(current_directory, filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path) # Remove the file or symbolic link
else:
print(f"Skipping {file_path}, not a file or symbolic link.")
except Exception as e:
print(f"Failed to delete {file_path}. Reason: {e}")
def complex_heat_eq_solution(x, t, k=glob_k, a=glob_a, b=glob_b, c=glob_c):
global glob_k, glob_a, glob_b, glob_c
glob_k, glob_a, glob_b, glob_c = k, a, b, c
return (
np.exp(-glob_k * (glob_a * np.pi) ** 2 * t) * np.cos(glob_a * np.pi * x)
+ np.exp(-glob_k * (glob_b * np.pi) ** 2 * t) * np.sin(glob_b * np.pi * x)
+ np.exp(-glob_k * (glob_c * np.pi) ** 2 * t) * np.sin(glob_c * np.pi * x)
)
def plot_heat_equation(m, approx_type):
# Define grid dimensions
n_x = 32 # Fixed spatial grid resolution
n_t = 50
try:
loaded_values = np.load(f"{approx_type}_m{m}.npz")
except:
raise gr.Error(f"First train the coefficients for {approx_type} and m = {m}")
alpha = loaded_values["alpha"]
Phi = loaded_values["Phi"]
# Create grids for x and t
x = np.linspace(0, 1, n_x) # Spatial grid
t = np.linspace(0, 5, n_t) # Temporal grid
X, T = np.meshgrid(x, t)
# Compute the real solution over the grid
U_real = complex_heat_eq_solution(X, T)
# Compute the selected approximation
U_approx = np.zeros_like(U_real)
for i, t_val in enumerate(t):
Phi_gff_at_t = Phi[i * n_x : (i + 1) * n_x]
U_approx[i, :] = np.dot(Phi_gff_at_t, alpha)
# Create the 3D plot with Plotly
traces = []
# Real solution surface with a distinct color (e.g., 'Viridis')
traces.append(
go.Surface(
z=U_real,
x=X,
y=T,
colorscale="Blues",
showscale=False,
name="Real Solution",
showlegend=True,
)
)
# Approximation surface with a distinct color (e.g., 'Plasma')
traces.append(
go.Surface(
z=U_approx,
x=X,
y=T,
colorscale="Reds",
reversescale=True,
showscale=False,
name=f"{approx_type} Approximation",
showlegend=True,
)
)
# Layout for the Plotly plot without controls
layout = go.Layout(
title=f"Heat Equation Approximation | Kernel = {approx_type} | m = {m}",
scene=dict(
camera=dict(
eye=dict(x=0, y=-2, z=0), # Front view
),
xaxis_title="x",
yaxis_title="t",
zaxis_title="u",
),
)
# Config to remove modebar buttons except the save image button
config = {
"modeBarButtonsToRemove": [
"pan",
"resetCameraLastSave",
"hoverClosest3d",
"hoverCompareCartesian",
"zoomIn",
"zoomOut",
"select2d",
"lasso2d",
"zoomIn2d",
"zoomOut2d",
"sendDataToCloud",
"zoom3d",
"orbitRotation",
"tableRotation",
],
"displayModeBar": True, # Keep the modebar visible
"displaylogo": False, # Hide the Plotly logo
}
# Create the figure
fig = go.Figure(data=traces, layout=layout)
fig.update_layout(
modebar_remove=[
"pan",
"resetCameraLastSave",
"hoverClosest3d",
"hoverCompareCartesian",
"zoomIn",
"zoomOut",
"select2d",
"lasso2d",
"zoomIn2d",
"zoomOut2d",
"sendDataToCloud",
"zoom3d",
"orbitRotation",
"tableRotation",
"toImage",
"resetCameraDefault3d",
]
)
return fig
def plot_errors(m, approx_type):
# Define grid dimensions
n_x = 32 # Fixed spatial grid resolution
n_t = 50
try:
loaded_values = np.load(f"{approx_type}_m{m}.npz")
except:
raise gr.Error(f"First train the coefficients for {approx_type} and m = {m}")
alpha = loaded_values["alpha"]
Phi = loaded_values["Phi"]
# Create grids for x and t
x = np.linspace(0, 1, n_x) # Spatial grid
t = np.linspace(0, 5, n_t) # Temporal grid
X, T = np.meshgrid(x, t)
# Compute the real solution over the grid
U_real = complex_heat_eq_solution(X, T)
# Compute the selected approximation
U_approx = np.zeros_like(U_real)
for i, t_val in enumerate(t):
Phi_gff_at_t = Phi[i * n_x : (i + 1) * n_x]
U_approx[i, :] = np.dot(Phi_gff_at_t, alpha)
U_err = abs(U_approx - U_real)
# Create the 3D plot with Plotly
traces = []
# Real solution surface with a distinct color (e.g., 'Viridis')
traces.append(
go.Surface(
z=U_err,
x=X,
y=T,
colorscale="Viridis",
showscale=False,
name=f"Absolute Error",
showlegend=True,
)
)
# Layout for the Plotly plot without controls
layout = go.Layout(
title=f"Heat Equation Approximation Error | Kernel = {approx_type} | m = {m}",
scene=dict(
camera=dict(
eye=dict(x=0, y=-2, z=0), # Front view
),
xaxis_title="x",
yaxis_title="t",
zaxis_title="u",
),
)
# Config to remove modebar buttons except the save image button
config = {
"modeBarButtonsToRemove": [
"pan",
"resetCameraLastSave",
"hoverClosest3d",
"hoverCompareCartesian",
"zoomIn",
"zoomOut",
"select2d",
"lasso2d",
"zoomIn2d",
"zoomOut2d",
"sendDataToCloud",
"zoom3d",
"orbitRotation",
"tableRotation",
],
"displayModeBar": True, # Keep the modebar visible
"displaylogo": False, # Hide the Plotly logo
}
# Create the figure
fig = go.Figure(data=traces, layout=layout)
fig.update_layout(
modebar_remove=[
"pan",
"resetCameraLastSave",
"hoverClosest3d",
"hoverCompareCartesian",
"zoomIn",
"zoomOut",
"select2d",
"lasso2d",
"zoomIn2d",
"zoomOut2d",
"sendDataToCloud",
"zoom3d",
"orbitRotation",
"tableRotation",
"toImage",
"resetCameraDefault3d",
]
)
return fig
def generate_data(n_x=32, n_t=50):
"""Generate training data."""
x = np.linspace(0, 1, n_x) # spatial points
t = np.linspace(0, 5, n_t) # temporal points
X, T = np.meshgrid(x, t)
a_train = np.c_[X.ravel(), T.ravel()] # shape (n_x * n_t, 2)
u_train = complex_heat_eq_solution(
a_train[:, 0], a_train[:, 1]
) # shape (n_x * n_t,)
return a_train, u_train, x, t
def random_features(a, theta_j, kernel="SINE", k=0.5, t=1.0):
"""Compute random features with adjustable kernel width."""
if kernel == "SINE":
return np.sin(t * np.linalg.norm(a - theta_j, axis=-1))
elif kernel == "GFF":
return np.log(np.linalg.norm(a - theta_j, axis=-1)) / (2 * np.pi)
else:
raise ValueError("Unsupported kernel type!")
def design_matrix(a, theta, kernel):
"""Construct design matrix."""
return np.array([random_features(a, theta_j, kernel=kernel) for theta_j in theta]).T
def learn_coefficients(Phi, u):
"""Learn coefficients alpha via least squares."""
return np.linalg.lstsq(Phi, u, rcond=None)[0]
def approximate_solution(a, alpha, theta, kernel):
"""Compute the approximation."""
Phi = design_matrix(a, theta, kernel)
return Phi @ alpha
def polyfit2d(x, y, z, kx=3, ky=3, order=None):
# grid coords
x, y = np.meshgrid(x, y)
# coefficient array, up to x^kx, y^ky
coeffs = np.ones((kx + 1, ky + 1))
# solve array
a = np.zeros((coeffs.size, x.size))
# for each coefficient produce array x^i, y^j
for index, (j, i) in enumerate(np.ndindex(coeffs.shape)):
# do not include powers greater than order
if order is not None and i + j > order:
arr = np.zeros_like(x)
else:
arr = coeffs[i, j] * x**i * y**j
a[index] = arr.ravel()
# do leastsq fitting and return leastsq result
return np.linalg.lstsq(a.T, np.ravel(z), rcond=None)
def train_coefficients(m, kernel):
# Start time for training
start_time = time.time()
# Generate data
n_x, n_t = 32, 50
a_train, u_train, x, t = generate_data(n_x, n_t)
# Define random features
theta = np.column_stack(
(
np.random.uniform(-1, 1, size=m), # First dimension: [-1, 1]
np.random.uniform(-5, 5, size=m), # Second dimension: [-5, 5]
)
)
# Construct design matrix and learn coefficients
Phi = design_matrix(a_train, theta, kernel)
alpha = learn_coefficients(Phi, u_train)
# Validate and animate results
u_real = np.array([complex_heat_eq_solution(x, t_i) for t_i in t])
a_test = np.c_[np.meshgrid(x, t)[0].ravel(), np.meshgrid(x, t)[1].ravel()]
u_approx = approximate_solution(a_test, alpha, theta, kernel).reshape(n_t, n_x)
# Save values to the npz folder
np.savez(
f"{kernel}_m{m}.npz",
alpha=alpha,
kernel=kernel,
Phi=Phi,
theta=theta,
)
# Compute average error
avg_err = np.mean(np.abs(u_real - u_approx))
return f"Training completed in {time.time() - start_time:.2f} seconds. The average error is {avg_err}."
def plot_function(k, a, b, c):
global glob_k, glob_a, glob_b, glob_c
glob_k, glob_a, glob_b, glob_c = k, a, b, c
x = np.linspace(0, 1, 100)
t = np.linspace(0, 5, 500)
X, T = np.meshgrid(x, t) # Create the mesh grid
Z = complex_heat_eq_solution(X, T, glob_k, glob_a, glob_b, glob_c)
traces = []
traces.append(
go.Surface(
z=Z,
x=X,
y=T,
colorscale="Viridis",
showscale=False,
showlegend=False,
)
)
# Layout for the Plotly plot without controls
layout = go.Layout(
scene=dict(
camera=dict(
eye=dict(x=1.25, y=-1.75, z=0.3), # Front view
),
xaxis_title="x",
yaxis_title="t",
zaxis_title="u",
),
margin=dict(l=0, r=0, t=0, b=0), # Reduce margins
)
# Create the figure
fig = go.Figure(data=traces, layout=layout)
fig.update_layout(
modebar_remove=[
"pan",
"resetCameraLastSave",
"hoverClosest3d",
"hoverCompareCartesian",
"zoomIn",
"zoomOut",
"select2d",
"lasso2d",
"zoomIn2d",
"zoomOut2d",
"sendDataToCloud",
"zoom3d",
"orbitRotation",
"tableRotation",
"toImage",
"resetCameraDefault3d",
]
)
return fig
def plot_all(m, kernel):
# Generate the plot content (replace this with your actual plot logic)
approx_fig = plot_heat_equation(
m, kernel
) # Replace with your function for approx_plot
error_fig = plot_errors(m, kernel) # Replace with your function for error_plot
# Return the figures and make the plots visible
return (
gr.update(visible=True, value=approx_fig),
gr.update(visible=True, value=error_fig),
)
# Gradio interface
def create_gradio_ui():
global glob_k, glob_a, glob_b, glob_c
# Get the initial available files
with gr.Blocks() as demo:
gr.Markdown("# Learn the Coefficients for the Heat Equation using the RFM")
# Function parameter inputs
gr.Markdown(
"""
## Function: $$u(x, t)\\coloneqq\\exp(-\\textcolor{magenta}{k}(\\textcolor{cyan}{a}\\pi)^2t)\\sin(\\textcolor{cyan}{a}\\pi x)+0.5\\exp(-\\textcolor{magenta}{k}(\\textcolor{lime}{b}\\pi)^2t)\\sin(\\textcolor{lime}{b}\\pi x)+0.25\\exp(-\\textcolor{magenta}{k}(\\textcolor{orange}{c}\\pi)^2t)\\sin(\\textcolor{orange}{c}\\pi x)$$
Adjust the values for k, a, b and c with the sliders below.
"""
)
with gr.Row():
with gr.Column():
k_slider = gr.Slider(
minimum=0.0001, maximum=0.1, step=0.0001, value=glob_k, label="k"
)
a_slider = gr.Slider(
minimum=-10, maximum=10, step=0.1, value=glob_a, label="a"
)
b_slider = gr.Slider(
minimum=-10, maximum=10, step=0.1, value=glob_b, label="b"
)
c_slider = gr.Slider(
minimum=-10, maximum=10, step=0.1, value=glob_c, label="c"
)
plot_output = gr.Plot()
k_slider.change(
fn=plot_function,
inputs=[k_slider, a_slider, b_slider, c_slider],
outputs=[plot_output],
)
a_slider.change(
fn=plot_function,
inputs=[k_slider, a_slider, b_slider, c_slider],
outputs=[plot_output],
)
b_slider.change(
fn=plot_function,
inputs=[k_slider, a_slider, b_slider, c_slider],
outputs=[plot_output],
)
c_slider.change(
fn=plot_function,
inputs=[k_slider, a_slider, b_slider, c_slider],
outputs=[plot_output],
)
with gr.Column():
with gr.Row():
# Kernel selection and slider for m
kernel_dropdown = gr.Dropdown(
label="Choose Kernel", choices=["SINE", "GFF"], value="SINE"
)
m_slider = gr.Dropdown(
label="Number of Random Features (m)",
choices=[50, 250, 1000, 5000, 10000, 25000],
value=1000,
)
# Output to show status
output = gr.Textbox(label="Status", interactive=False)
with gr.Column():
# Button to train coefficients
train_button = gr.Button("Train Coefficients")
# Function to trigger training and update dropdown
train_button.click(
fn=train_coefficients,
inputs=[m_slider, kernel_dropdown],
outputs=output,
)
approx_button = gr.Button("Plot Approximation")
with gr.Row():
approx_plot = gr.Plot(visible=False)
error_plot = gr.Plot(visible=False)
approx_button.click(
fn=plot_all,
inputs=[m_slider, kernel_dropdown],
outputs=[approx_plot, error_plot],
)
demo.load(fn=clear_npz, inputs=None, outputs=None)
demo.load(
fn=plot_function,
inputs=[k_slider, a_slider, b_slider, c_slider],
outputs=[plot_output],
)
return demo
# Launch Gradio app
if __name__ == "__main__":
interface = create_gradio_ui()
interface.launch(share=False)