Skip to content

Commit 1d53e33

Browse files
Merge pull request #576 from ChrisRackauckas-Claude/docs-real-data-controls-368
Add documentation for using real data with time-varying controls
2 parents 4a6e6df + d9f36f3 commit 1d53e33

File tree

2 files changed

+217
-0
lines changed

2 files changed

+217
-0
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# # [Using Real Data with Time-Varying Controls](@id real_data_controls)
2+
#
3+
# This example demonstrates how to use DataDrivenDiffEq with real experimental data,
4+
# particularly when you have time-varying control inputs stored in data files.
5+
# This is a common scenario when working with physical systems like RC circuits,
6+
# mechanical systems, or any controlled experiment where inputs vary over time.
7+
#
8+
# ## The Problem Setup
9+
#
10+
# Consider a linear system with controls of the form:
11+
# ```math
12+
# \frac{dx}{dt} = A x + B u
13+
# ```
14+
# where `x` is the state vector, `u` is a time-varying control input, and
15+
# `A` and `B` are constant matrices we want to identify.
16+
#
17+
# In practice, both states `x(t)` and controls `u(t)` are measured at discrete
18+
# time points and stored in data files (e.g., CSV).
19+
#
20+
# ## Simulating Real Data
21+
#
22+
# First, let's create some synthetic "experimental" data that mimics what you might
23+
# load from a CSV file. In a real application, you would load this from your data file
24+
# using packages like CSV.jl and DataFrames.jl.
25+
26+
using DataDrivenDiffEq
27+
using DataDrivenDMD
28+
using LinearAlgebra
29+
using OrdinaryDiffEq
30+
#md using Plots
31+
32+
# Define the true system (unknown in practice)
33+
A_true = [-0.5 0.1; 0.0 -0.3]
34+
B_true = [1.0; 0.5]
35+
36+
# Time-varying control: a combination of sinusoids (simulating real measured control)
37+
function control_signal(t)
38+
return sin(0.5 * t) + 0.3 * cos(1.2 * t)
39+
end
40+
41+
# System dynamics
42+
function controlled_system!(du, u, p, t)
43+
ctrl = control_signal(t)
44+
du .= A_true * u .+ B_true .* ctrl
45+
end
46+
47+
# Generate "experimental" data
48+
u0 = [1.0, -0.5]
49+
tspan = (0.0, 20.0)
50+
dt = 0.1 # Sampling interval
51+
52+
prob = ODEProblem(controlled_system!, u0, tspan)
53+
sol = solve(prob, Tsit5(), saveat = dt)
54+
55+
# ## Working with Real Data Format
56+
#
57+
# In practice, your data might come from a CSV file with columns like:
58+
# `time, state1, state2, control1`
59+
#
60+
# Here we simulate that data format:
61+
62+
# Time points (like loading from CSV column "time")
63+
t_data = sol.t
64+
65+
# State measurements (like loading from CSV columns "state1", "state2")
66+
# Note: Each column should be a time point, rows are state variables
67+
X_data = Array(sol)
68+
69+
# Control measurements (like loading from CSV column "control1")
70+
# Note: Control values at each time point, must match dimensions
71+
U_data = [control_signal(ti) for ti in t_data]
72+
U_data = reshape(U_data, 1, :) # Shape: (n_controls, n_timepoints)
73+
74+
# ```julia
75+
# # In practice, you would load data like this:
76+
# using CSV, DataFrames
77+
#
78+
# df = CSV.read("experimental_data.csv", DataFrame)
79+
# t_data = df.time
80+
# X_data = permutedims(Matrix(df[:, [:state1, :state2]])) # (n_states, n_timepoints)
81+
# U_data = permutedims(Matrix(df[:, [:control1]])) # (n_controls, n_timepoints)
82+
# ```
83+
84+
# ## Creating the DataDrivenProblem
85+
#
86+
# Now we can create the problem using the data arrays directly.
87+
# The key insight is that `U` can be passed as a matrix of measured values,
88+
# not just as a function!
89+
90+
ddprob = ContinuousDataDrivenProblem(X_data, t_data, U = U_data)
91+
92+
#md plot(ddprob, title = "Data-Driven Problem with Measured Controls")
93+
94+
# ## Solving the Problem
95+
#
96+
# We use DMD with SVD to identify the system dynamics:
97+
98+
res = solve(ddprob, DMDSVD(), digits = 2)
99+
100+
#md println(res) #hide
101+
102+
# ## Examining the Results
103+
#
104+
# The recovered system should approximate our original dynamics:
105+
106+
#md get_basis(res)
107+
#md println(get_basis(res)) #hide
108+
109+
# Let's visualize how well the identified model matches the data:
110+
111+
#md plot(res, title = "Identified System vs Data")
112+
113+
# ## Alternative: Using Control Functions with Interpolation
114+
#
115+
# If you prefer to use a continuous function for controls (e.g., for prediction
116+
# at arbitrary time points), you can interpolate your measured control data.
117+
# The `DataInterpolations.jl` package is useful for this:
118+
#
119+
# ```julia
120+
# using DataInterpolations
121+
#
122+
# # Create an interpolation from your measured control data
123+
# u_interp = LinearInterpolation(vec(U_data), t_data)
124+
#
125+
# # Now you can use it as a control function
126+
# control_func(x, p, t) = [u_interp(t)]
127+
#
128+
# ddprob_interp = ContinuousDataDrivenProblem(X_data, t_data, U = control_func)
129+
# ```
130+
#
131+
# This is particularly useful when:
132+
# - Your control and state measurements are at different time points
133+
# - You want to evaluate the model at times not in your dataset
134+
# - You need smooth derivatives of the control signal
135+
136+
# ## Summary
137+
#
138+
# When working with real experimental data containing time-varying controls:
139+
#
140+
# 1. **Load your data** from CSV or other formats using CSV.jl, DataFrames.jl, etc.
141+
#
142+
# 2. **Format your data** correctly:
143+
# - States `X`: Matrix of shape `(n_states, n_timepoints)`
144+
# - Times `t`: Vector of length `n_timepoints`
145+
# - Controls `U`: Matrix of shape `(n_controls, n_timepoints)`
146+
#
147+
# 3. **Create the problem** using measured control values:
148+
# ```julia
149+
# prob = ContinuousDataDrivenProblem(X, t, U = U)
150+
# ```
151+
#
152+
# 4. **Optionally interpolate** controls if you need a continuous function:
153+
# ```julia
154+
# using DataInterpolations
155+
# u_interp = LinearInterpolation(vec(U), t)
156+
# control_func(x, p, t) = [u_interp(t)]
157+
# prob = ContinuousDataDrivenProblem(X, t, U = control_func)
158+
# ```
159+
#
160+
# 5. **Solve** using your preferred method (DMD, sparse regression, etc.)
161+
162+
#md # ## [Copy-Pasteable Code](@id real_data_controls_copy_paste)
163+
#md #
164+
#md # ```julia
165+
#md # @__CODE__
166+
#md # ```

docs/src/problems.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,57 @@ problem = DirectDataDrivenProblem(X, t, Y, p = p)
5151
problem = DirectDataDrivenProblem(X, t, Y, (x, p, t) -> u(x, p, t), p = p)
5252
```
5353

54+
## Working with Real Data
55+
56+
When working with experimental data from files (e.g., CSV), the data must be formatted correctly before creating a problem. The key points are:
57+
58+
- **States `X`**: A matrix of shape `(n_states, n_timepoints)` where each column is a measurement at a time point
59+
- **Times `t`**: A vector of length `n_timepoints`
60+
- **Controls `U`**: Either a matrix of shape `(n_controls, n_timepoints)` or a function `(x, p, t) -> u_vector`
61+
62+
### Loading Data from CSV
63+
64+
```julia
65+
using CSV, DataFrames
66+
67+
# Load your experimental data
68+
df = CSV.read("experiment.csv", DataFrame)
69+
70+
# Extract time points
71+
t = Vector(df.time)
72+
73+
# Extract state measurements (transpose so columns are time points)
74+
X = permutedims(Matrix(df[:, [:x1, :x2]]))
75+
76+
# Extract control measurements
77+
U = permutedims(Matrix(df[:, [:u1]]))
78+
79+
# Create the problem
80+
prob = ContinuousDataDrivenProblem(X, t, U = U)
81+
```
82+
83+
### Time-Varying Controls from Data
84+
85+
Control inputs can be specified in two ways:
86+
87+
1. **As measured data** (matrix): Use this when you have control values recorded at each time point
88+
```julia
89+
U = [u1_at_t1 u1_at_t2 ... u1_at_tn;
90+
u2_at_t1 u2_at_t2 ... u2_at_tn] # Shape: (n_controls, n_timepoints)
91+
prob = ContinuousDataDrivenProblem(X, t, U = U)
92+
```
93+
94+
2. **As a function**: Use this when controls can be computed analytically or when you want to interpolate measured data
95+
```julia
96+
# Using DataInterpolations.jl to create a continuous function from discrete data
97+
using DataInterpolations
98+
u_interp = LinearInterpolation(vec(U), t)
99+
control_func(x, p, t) = [u_interp(t)]
100+
prob = ContinuousDataDrivenProblem(X, t, U = control_func)
101+
```
102+
103+
For a complete example, see [Using Real Data with Time-Varying Controls](@ref real_data_controls).
104+
54105
## Concrete Types
55106

56107
```@docs

0 commit comments

Comments
 (0)