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.0
glob_b = 4.0
glob_c = 7.5
n_x, n_t = 10, 10
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, a, b, 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, quality, rand_or_det):
global glob_k, glob_a, glob_b, glob_c, n_x, n_t
# Plot with more points than it was calculated
new_nx = 1 * n_x
new_nt = 1 * n_t
try:
loaded_values = np.load(
f"{approx_type}_m{m}_{str.lower(quality)}_{str.lower(rand_or_det)}.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, new_nx) # Spatial grid
t = np.linspace(0, 5, new_nt) # Temporal grid
X, T = np.meshgrid(x, t)
# Compute the real solution over the grid
U_real = complex_heat_eq_solution(X, T, glob_k, glob_a, glob_b, glob_c)
# Compute the selected approximation
# Compute the approximations as a single matrix multiplication
Phi_reshaped = Phi.reshape(n_t, n_x, -1)
# The result will be of shape (n_t, n_x), as U_approx should match U_real's shape
U_approx = np.einsum("ijk,k->ij", Phi_reshaped, 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(
scene=dict(
camera=dict(
eye=dict(x=0, y=-2, z=0), # 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",
],
legend=dict(yanchor="bottom", y=0.01, xanchor="left", x=0.01),
)
return fig
def plot_errors(m, approx_type, quality, rand_or_det):
global n_x, n_t
try:
loaded_values = np.load(
f"{approx_type}_m{m}_{str.lower(quality)}_{str.lower(rand_or_det)}.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, glob_k, glob_a, glob_b, glob_c)
# Compute the selected approximation
U_approx = np.zeros_like(U_real)
for i, t_val in enumerate(t):
Phi_at_t = Phi[i * n_x : (i + 1) * n_x]
U_approx[i, :] = np.dot(Phi_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(
scene=dict(
camera=dict(
eye=dict(x=0, y=-2, z=0), # 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",
],
legend=dict(yanchor="bottom", y=0.01, xanchor="left", x=0.01),
)
return fig
def generate_data():
global glob_k, glob_a, glob_b, glob_c, n_x, n_t
"""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], glob_k, glob_a, glob_b, glob_c
) # shape (n_x * n_t,)
return a_train, u_train, x, t
def features(a, theta_j, m, method="SINE", k=1, eps=1e-8):
"""Compute random features with adjustable method width."""
if method == "SINE":
return np.sin(k * np.linalg.norm(a - theta_j, axis=-1) + eps)
elif method == "GFF":
return np.log(np.linalg.norm(a - theta_j, axis=-1) + eps) / (2 * np.pi)
else:
raise ValueError("Unsupported method type!")
def design_matrix(a, theta, method):
"""Construct design matrix."""
return np.array(
[features(a, theta_j, theta.shape[0], method) 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, method):
"""Compute the approximation."""
Phi = design_matrix(a, theta, method)
return Phi @ alpha
def train_coefficients(m, method, quality, rand_or_det):
global glob_k, glob_a, glob_b, glob_c, n_x, n_t
# Start time for training
start_time = time.time()
# Generate data
a_train, u_train, x, t = generate_data()
# Define random features
if rand_or_det == "Random":
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]
)
)
else:
theta = np.column_stack(
(
np.linspace(-5, 5, m), # Linear spacing for x
np.linspace(-25, 25, m), # Linear spacing for y
)
)
# Construct design matrix and learn coefficients
Phi = design_matrix(a_train, theta, method)
alpha = learn_coefficients(Phi, u_train)
end_time = f"{time.time() - start_time:.2f}"
# Save values to the npz folder
np.savez(
f"{method}_m{m}_{str.lower(quality)}_{str.lower(rand_or_det)}.npz",
alpha=alpha,
method=method,
Phi=Phi,
theta=theta,
)
# Compute the average error
#
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, glob_k, glob_a, glob_b, glob_c)
# Compute the selected approximation
U_approx = np.zeros_like(U_real)
for i, t_val in enumerate(t):
Phi_at_t = Phi[i * n_x : (i + 1) * n_x]
U_approx[i, :] = np.dot(Phi_at_t, alpha)
# Compute average error
avg_err = np.mean(np.abs(U_real - U_approx))
return (
f"Training completed in {end_time} 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",
],
legend=dict(yanchor="bottom", y=0.01, xanchor="left", x=0.01),
)
return fig
def plot_all(m, method, quality, rand_or_det):
# Generate the plot content (replace this with your actual plot logic)
approx_fig = plot_heat_equation(
m, method, quality, rand_or_det
) # Replace with your function for approx_plot
error_fig = plot_errors(
m, method, quality, rand_or_det
) # 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),
)
def change_quality(quality):
global n_x, n_t
if quality == "Low":
n_x, n_t = 10, 10
elif quality == "Mid":
n_x, n_t = 20, 20
elif quality == "High":
n_x, n_t = 40, 40
# Gradio interface
def create_gradio_ui():
global glob_k, glob_a, glob_b, glob_c
markdown_content = r"""
## Goal function:
$$
\begin{alignat*}{5}
u(x, t)
\coloneqq &\exp(-\textcolor{magenta}{k}(&\textcolor{cyan}{a}&\pi)^2t)\sin(&\textcolor{cyan}{a}&\pi x) \\
+ &\exp(-\textcolor{magenta}{k}(&\textcolor{lime}{b}&\pi)^2t)\sin(&\textcolor{lime}{b}&\pi x) \\
+ &\exp(-\textcolor{magenta}{k}(&\textcolor{orange}{c}&\pi)^2t)\sin(&\textcolor{orange}{c}&\pi x)
\end{alignat*}
$$
Adjust the values for k, a, b and c with the sliders below.
Pressing "Train Coefficients" aims to solve
$$
\argmin_{\alpha\in\mathbb{R}^m}\|{\alpha\Phi-u}\|_2^2,
$$
where $\Phi$ contains the features depending on the method.
"""
# Get the initial available files
with gr.Blocks() as demo:
gr.Markdown("# Approximating a solution to the heat equation using RFM")
# Function parameter inputs
gr.Markdown(
markdown_content,
latex_delimiters=[
{"left": "$$", "right": "$$", "display": True},
{"left": "$", "right": "$", "display": False},
],
)
with gr.Row():
with gr.Column(min_width=500):
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"
)
with gr.Column(min_width=500):
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():
# method selection and slider for m
quality_dropdown = gr.Dropdown(
label="Choose Quality", choices=["Low", "Mid", "High"], value="Low"
)
quality_dropdown.change(
fn=change_quality, inputs=quality_dropdown, outputs=None
)
method_dropdown = gr.Dropdown(
label="Choose Method", choices=["SINE", "GFF"], value="SINE"
)
m_slider = gr.Dropdown(
label="Number of Random Features (m)",
choices=[50, 250, 1000, 5000, 10000, 25000],
value=1000,
)
rand_det_dropdown = gr.Dropdown(
label="Choose Random / Deterministic",
choices=["Deterministic", "Random"],
value="Deterministic",
)
# 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,
method_dropdown,
quality_dropdown,
rand_det_dropdown,
],
outputs=output,
)
approx_button = gr.Button("Plot Approximation")
with gr.Row():
with gr.Column(min_width=500):
approx_plot = gr.Plot(visible=False)
with gr.Column(min_width=500):
error_plot = gr.Plot(visible=False)
approx_button.click(
fn=plot_all,
inputs=[m_slider, method_dropdown, quality_dropdown, rand_det_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)