Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a601900
setup recipes
lazarusA Mar 24, 2026
eaf9331
docstrings
lazarusA Mar 24, 2026
57d6e4f
training recipe
lazarusA Mar 25, 2026
cd3a313
plot lines
lazarusA Apr 7, 2026
c3ca2ce
git fetch-depth 0 (#253)
lazarusA Mar 24, 2026
0e1bdca
CompatHelper: bump compat for MLJ to 0.23, (keep existing compat) (#254)
github-actions[bot] Mar 25, 2026
c0d908f
GPU (+ lstm) (#259)
BernhardAhrens Apr 22, 2026
102b913
Revert "GPU (+ lstm) (#259)" (#262)
lazarusA Apr 22, 2026
32363f1
gpu support (#263)
lazarusA Apr 22, 2026
e379dfe
update
lazarusA Apr 23, 2026
b872399
update working
lazarusA Apr 27, 2026
04872ff
Merge branch 'main' into la/ups_dashboard
lazarusA Apr 27, 2026
e11b8c2
no gl
lazarusA Apr 27, 2026
d03809a
no
lazarusA Apr 27, 2026
e7ee5df
update zoom
lazarusA Apr 29, 2026
a36ded0
adds zoomed rect2
lazarusA Apr 29, 2026
333c04b
sliding
lazarusA Apr 29, 2026
6a6f995
mv window
lazarusA Apr 29, 2026
7ebf055
see lstm plots
lazarusA Apr 30, 2026
b3c951a
include videos
lazarusA Apr 30, 2026
76bd14b
recipe monitor names
lazarusA May 5, 2026
7ee0bd5
monitor recipe
lazarusA May 11, 2026
e82ec4c
trimspine
lazarusA May 11, 2026
44604ef
multiple monitor names
lazarusA May 12, 2026
dd3ffe2
no gl
lazarusA May 12, 2026
3234954
no monitors, no history
lazarusA May 12, 2026
88ddcfa
start prediction plot
lazarusA May 26, 2026
5d47dfd
Merge branch 'main' into la/ups_dashboard
lazarusA Jun 15, 2026
a0ee612
prediction recipe, fix lstm sequences
lazarusA Jul 1, 2026
185ff4a
rm comments
lazarusA Jul 1, 2026
35400b8
time series, don't rand points but sequences
lazarusA Jul 1, 2026
5ced37a
fix tune
lazarusA Jul 1, 2026
0057002
add
lazarusA Jul 2, 2026
5c0ba18
first 5
lazarusA Jul 2, 2026
74ab33b
rm Observables approach
lazarusA Jul 2, 2026
9a8f105
lego-like assembly
lazarusA Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
DataFrameMacros = "75880514-38bc-4a95-a458-c2aea5a3a702"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
Expand Down Expand Up @@ -51,6 +52,7 @@ ChainRulesCore = "1.25.1"
ComponentArrays = "0.15.28"
DataFrameMacros = "0.4"
DataFrames = "1"
DataStructures = "0.19.4"
DimensionalData = "0.29.24, 0.30"
Downloads = "1.6.0"
ForwardDiff = "1"
Expand Down
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ AxisKeys = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Chain = "8be319e6-bccf-4806-a6f7-6fae938471bc"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365"
Expand Down
2 changes: 1 addition & 1 deletion docs/literate/research/synthetic_respiration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ single_nn_out = train(
single_nn_hybrid_model,
df,
();
nepochs = 10, # Number of training epochs
nepochs = 100, # Number of training epochs
batchsize = 512, # Batch size for training
opt = AdamW(0.1), # Optimizer and learning rate
monitor_names = [:rb, :Q10], # Parameters to monitor during training
Expand Down
26 changes: 17 additions & 9 deletions docs/literate/tutorials/example_synthetic_lstm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using EasyHybrid
using AxisKeys
using DimensionalData
using CairoMakie

# ## 2. Data Loading and Preprocessing

Expand Down Expand Up @@ -195,18 +196,22 @@ out_lstm = train(
opt = RMSProp(0.01), # Optimizer and learning rate
monitor_names = [:rb, :Q10], # Parameters to monitor during training
yscale = identity, # Scaling for outputs
shuffleobs = true,
shuffleobs = false,
training_loss = :nseLoss,
loss_types = [:nse],
loss_types = [:nse, :nseLoss],
sequence_kwargs = (; input_window = input_window, output_window = output_window, output_shift = output_shift, lead_time = 0),
plotting = false,
plotting = true,
show_progress = false,
input_batchnorm = false,
array_type = pref_array_type,
model_name = "RbQ10_synthetic_lstm"
);

out_lstm.val_obs_pred
# ```@raw html
# <video src="../training_history_RbQ10_synthetic_lstm.mp4" controls="controls" autoplay="autoplay"></video>
# ```

first(out_lstm.val_obs_pred, 5)

# ## 10. Train Single NN Hybrid Model (Optional)

Expand Down Expand Up @@ -234,15 +239,18 @@ single_nn_out = train(
opt = RMSProp(0.01), # Optimizer and learning rate
monitor_names = [:rb, :Q10], # Parameters to monitor during training
yscale = identity, # Scaling for outputs
shuffleobs = true,
shuffleobs = false,
training_loss = :nseLoss,
loss_types = [:nse],
loss_types = [:nse, :nseLoss],
array_type = :DimArray,
plotting = false,
plotting = true,
show_progress = false,
model_name = "RbQ10_synthetic_single_nn"
);

# ```@raw html
# <video src="../training_history_RbQ10_synthetic_single_nn.mp4" controls="controls" autoplay="autoplay"></video>
# ```

# Close enough
out_lstm.best_loss
single_nn_out.best_loss
out_lstm.best_loss, single_nn_out.best_loss
4 changes: 2 additions & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ makedocs(;
"Hyperparameter Tuning" => "tutorials/hyperparameter_tuning.md",
"GPU Acceleration" => "tutorials/gpu.md",
"Synthetic Respiration on GPU" => "tutorials/synthetic_respiration_gpu.md",
# "Slurm" => "tutorials/slurm.md",
"Slurm" => "tutorials/slurm.md",
"Cross-validation" => "tutorials/folds.md",
# "LSTM Hybrid Model" => "tutorials/example_synthetic_lstm.md",
"LSTM Hybrid Model" => "tutorials/example_synthetic_lstm.md",
"Loss Functions" => "tutorials/losses.md",
],
"Research" => [
Expand Down
2 changes: 1 addition & 1 deletion docs/src/tutorials/exponential_res.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ hybrid_model = constructHybridModel(

```@example expo
out = train(hybrid_model, df, (:k,); nepochs=300, batchsize=64,
opt=AdamW(0.01, (0.9, 0.999), 0.01), loss_types=[:mse, :nse],
opt=AdamW(0.01, (0.9, 0.999), 0.01), loss_types=[:mse, :nse, :nseLoss],
training_loss=:nseLoss, random_seed=123, yscale = identity,
monitor_names=[:Resp0, :k],
show_progress=false,
Expand Down
55 changes: 55 additions & 0 deletions docs/test_expo.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using EasyHybrid
using GLMakie

using Random
Random.seed!(2314)

T = rand(500) .* 40 .- 10 # Random temperature
SM = rand(500) .* 0.8 .+ 0.1 # Random soil moisture
SM_fac = exp.(-8.0 * (SM .- 0.6) .^ 2)
Resp0 = 1.1 .* SM_fac # Base respiration dependent on soil moisture
Resp = Resp0 .* exp.(0.07 .* T)
Resp_obs = Resp .+ randn(length(Resp)) .* 0.05 .* mean(Resp); # Add some noise

df = DataFrame(; T, SM, SM_fac, Resp0, Resp, Resp_obs);

function Expo_resp_model(; T, Resp0, k)
Resp_obs = Resp0 .* exp.(k .* T)
return (; Resp_obs, Resp0, k)
end;

parameters = (
# name: (default, lower_bound, upper_bound) # Description
k = (0.01f0, 0.0f0, 0.2f0), # Exponent
Resp0 = (2.0f0, 0.0f0, 8.0f0), # Basal respiration [μmol/m²/s]
);

targets = [:Resp_obs]
forcings = [:T]
predictors = (Resp0 = [:SM],);

global_param_names = [:k]

hybrid_model = constructHybridModel(
predictors,
forcings,
targets,
Expo_resp_model,
parameters,
global_param_names,
scale_nn_outputs = false, # TODO `true` also works with good lower and upper bounds
hidden_layers = [16, 16],
activation = sigmoid,
input_batchnorm = true
)

out = train(
hybrid_model, df, (:k,); nepochs = 500, batchsize = 64,
opt = AdamW(0.01, (0.9, 0.999), 0.01), loss_types = [:mse, :nse, :nseLoss],
training_loss = :nseLoss, random_seed = 123, yscale = identity,
monitor_names = [:Resp0, :k],
show_progress = false,
hybrid_name = "expo_response"
);

# predictionplot(out.train_obs_pred[!, :Resp_obs], out.train_obs_pred[!, :Resp_obs_pred]; color = :tomato)
127 changes: 127 additions & 0 deletions docs/test_extMakie.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using EasyHybrid
using GLMakie
using GLMakie.Makie.GeometryBasics: AbstractPoint
using DataStructures: CircularBuffer

n_epochs = [0.9]
t_arr = sin.(rand(1))
v_arr = cos.(rand(1))
fig, ax, plt = lossplot(n_epochs, t_arr, v_arr; axis = (; xlabel = "Epochs", ylabel = "Loss"))
# axislegend(ax, plt)

Legend(fig[1, 1, Top()], ax, plt)
hidespines!(ax, :r, :t)
fig

# ! do buffer!
zoom_epochs = 50
n_epochs_buffer = CircularBuffer{Int64}(zoom_epochs)
fill!(n_epochs_buffer, 0)
t_arr_buffer = CircularBuffer{Float64}(zoom_epochs)
fill!(t_arr_buffer, t_arr[1])
v_arr_buffer = CircularBuffer{Float64}(zoom_epochs)
fill!(v_arr_buffer, v_arr[1])

ax_z = Axis(
fig[1, 1],
width = Relative(0.35),
height = Relative(0.35),
halign = 0.95,
valign = 1,
xlabel = "",
ylabel = "",
rightspinecolor = :dodgerblue,
leftspinecolor = :dodgerblue,
topspinecolor = :dodgerblue,
bottomspinecolor = :dodgerblue,
title = "Zoomed View"
)

plt_z = lossplot!(ax_z, n_epochs_buffer, t_arr_buffer, v_arr_buffer)
# hidespines!(ax_z, :l, :t)
translate!(ax_z.blockscene, 0, 0, 150)
fig

o_ax_z = ax_z.scene.viewport[].origin

function current_rect2(n_epochs_buffer, t_arr_buffer, v_arr_buffer, zoom_epochs, epoch)
xzoom_rect = epoch < zoom_epochs ? epoch : zoom_epochs
mn_tv = minimum(map(minimum, [t_arr_buffer, v_arr_buffer]))
mx_tv = maximum(map(maximum, [t_arr_buffer, v_arr_buffer]))
z_rect = Rect2(minimum(n_epochs_buffer), 0.95 * mn_tv, xzoom_rect, 1.05 * (mx_tv - mn_tv))

return z_rect
end

z_rect = current_rect2(n_epochs_buffer, t_arr_buffer, v_arr_buffer, zoom_epochs, 0)

plt_b = lines!(ax, z_rect, color = :dodgerblue, linewidth = 1)
fig

# scatter!(fig.scene, Point2f(o_ax_z))
# scatter!(fig.scene, Point2f(o_ax_z) + Point2f(first(ax_z.scene.viewport[].widths), 0))

function _project_points_to_figure(ax, p::AbstractPoint)
return ax.scene.viewport[].origin + Makie.project(ax.scene, p)
end

function _axis_bottom_points(ax_z)
left_point = Point2f(ax_z.scene.viewport[].origin)
x_right = first(ax_z.scene.viewport[].widths)
right_point = left_point + Point2f(x_right, 0)
return [left_point, right_point]
end

_axis_bottom_points(ax_z)

Legend(fig[1, 1, Top()], ax, plt; nbanks = 3, framewidth = 0.25, halign = 0)
fig

for epoch in 1:1000
# push a new data point
n_tv = sin(rand()) / epoch
n_vv = cos(rand()) / epoch
push!(n_epochs, epoch)
push!(t_arr, n_tv)
push!(v_arr, n_vv)
#! now the buffers
push!(n_epochs_buffer, epoch)
push!(t_arr_buffer, n_tv)
push!(v_arr_buffer, n_vv)

new_z_rect = current_rect2(n_epochs_buffer, t_arr_buffer, v_arr_buffer, zoom_epochs, epoch)

#? now that all are updated and synchronized we can update the plot

update!(plt, n_epochs, t_arr, v_arr)
update!(plt_z, n_epochs_buffer, t_arr_buffer, v_arr_buffer)
update!(plt_b, arg1 = new_z_rect)
autolimits!(ax)
autolimits!(ax_z)
sleep(0.002)
end
fig


# oo = _project_points_to_figure(ax, Point2f(1000, 0.01))
# scatter!(fig.scene, Point2f(oo); color = :olive, markersize=15)
# fig

ax.yscale = log10

# oo2 = _project_points_to_figure(ax, Point2f(1000, 0.02))
# scatter!(fig.scene, Point2f(oo2); color = :orange, markersize=15)

ax.xscale = log10
fig


fig, ax, plt = lossplot(rand(10), rand(10))
scatter!(rand(10), label = "some dots")
Legend(fig[0, 1], ax, plt; position = :ct, nbanks = 3, tellheight = true, tellwidth = false)
fig

fig, ax, plt = lossplot(rand(10), rand(10); validation_label = "validate me")
scatter!(rand(10), label = "some dots")
Legend(fig[0, 1], ax, plt; position = :ct, nbanks = 3, tellheight = true, tellwidth = false)
fig
73 changes: 73 additions & 0 deletions docs/test_monitor.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using EasyHybrid
using GLMakie
using GLMakie.Makie.GeometryBasics: AbstractPoint
using DataStructures: CircularBuffer

epochs = 1:20
# Build fake monitor data matching the expected structure
# Scalar monitors are expected to be tuples of the form (scalar = <vector>,)
training_monitor = (
loss = (scalar = rand(20) .* 0.5 .+ 0.1,),
accuracy = (scalar = rand(20) .* 0.3 .+ 0.6,),
)
validation_monitor = (
loss = (scalar = rand(20) .* 0.5 .+ 0.2,),
accuracy = (scalar = rand(20) .* 0.3 .+ 0.5,),
)

# Quantile monitors are expected to be tuples of the form (quantile = (q25 = <vector>, q50 = <vector>, q75 = <vector>),)
training_monitor_q = (
loss = (
quantile = (
q25 = rand(20) .* 0.3 .+ 0.05,
q50 = rand(20) .* 0.3 .+ 0.15,
q75 = rand(20) .* 0.3 .+ 0.25,
),
),
)
validation_monitor_q = (
loss = (
quantile = (
q25 = rand(20) .* 0.3 .+ 0.1,
q50 = rand(20) .* 0.3 .+ 0.2,
q75 = rand(20) .* 0.3 .+ 0.3,
),
),
)

# 1. Scalar, standalone figure
fig1, ax1, plt1 = monitorplot(epochs, training_monitor, validation_monitor, :loss; axis = (xlabel = "Epoch", ylabel = "Loss"))
# axislegend(ax1, plt1)
hidespines!(ax1, :r, :t)
Legend(fig1[1, 1, Top()], ax1, plt1; orientation = :horizontal, titleposition = :left)
fig1

# 2. Quantile, standalone figure
fig2, ax2, plt2 = monitorplot(epochs, training_monitor_q, validation_monitor_q, :loss)
# axislegend(ax2, plt2)
hidespines!(ax2, :r, :t)
Legend(fig2[1, 1, Top()], ax2, plt2; orientation = :horizontal)
fig2

# 3. Mutating form + attribute overrides
fig3 = Figure()
ax3 = Axis(fig3[1, 1], title = "Loss (custom style)", xlabel = "Epoch", ylabel = "Loss")
plt3 = monitorplot!(
ax3, epochs, training_monitor, validation_monitor, :loss;
training_color = :steelblue,
validation_color = :crimson,
linewidth = 3,
training_label = "Train",
validation_label = "Val",
)
axislegend(ax3, plt3)
fig3

# 4. Multi-panel figure
fig4 = Figure(size = (900, 400))
for (col, name) in enumerate([:loss, :accuracy])
ax = Axis(fig4[1, col], title = string(name), xlabel = "Epoch")
plt = monitorplot!(ax, epochs, training_monitor, validation_monitor, name)
axislegend(ax, plt)
end
fig4
Loading
Loading