Overview
Input-output data of a scalar discrete-time Lur'e system with a dead-zone nonlinearity. The system is regionally but not globally stable: inside the dead zone it is a stable linear system, outside it is unstable. The dataset has two parts: converging trajectories that stay bounded over the whole horizon, and diverging trajectories that start at zero and are driven out of the stable region.
The system is small enough that its stable region is known in closed form. Use the training sets to fit model parameters, the validation sets for hyperparameter selection, and the test sets only for the final evaluation.
System
x_(k+1) = A x_k + B u_k + B2 dzn(C2 x_k + D21 u_k)
y_k = C x_k + D u_k + D12 dzn(C2 x_k + D21 u_k)
A = 0.9, B = 1.0, B2 = 1.1, C = C2 = 1.0, D = D12 = D21 = 0
dzn(z) = max(|z| - 1, 0) sign(z)
For |x| ≤ 1 the map is x+ = 0.9 x + u. For |x| > 1 it is x+ = 2.0 x ∓ 1.1 + u, which is unstable, so for u = 0 the basin of attraction of the origin is |x| < 1.1. The sampling time is T_s = 1 (dimensionless) and y = x.
For reference, id/params.json records a regional stability certificate of the true system (which is not unique): at contraction rate alpha = 0.99 the certified invariant set is |x| ≤ 1.0, and inputs with amplitude up to sigma = 0.0588 keep the state in it. This set coincides with the linear region. The converging trajectories start inside it and their inputs stay below sigma, so they never excite the nonlinearity (max|x| = 0.295). Only the diverging trajectories do.
Input generation
Each input trajectory is white Gaussian noise filtered with a zero-phase 4th-order Butterworth low-pass (cutoff 0.1 / T_s, scipy.signal.filtfilt, the first 16 samples discarded). It is then multiplied by the envelope exp(-0.9 t), with t running from 0 to 1 over the trajectory, and scaled so that max|u| = a, with a ~ U(0, a_max) drawn per trajectory: a_max = 0.04 (≈ 0.68 sigma*) for the converging and a_max = 0.6 for the diverging group.
Initial states and labelling
Trajectories are rejection-sampled with the divergence threshold |x| > 5:
rand_conv_NNN.csv: x_0 ~ U(-0.3, 0.3), kept if |x| ≤ 5 for all 500 samples.
zero_div_NNN.csv: x_0 = 0, kept if |x| exceeds 5 within 500 samples and the trajectory is at least 20 samples long. The trajectory ends with the last sample before the threshold is crossed, so these files are ragged.
Noise
None. The data is noise free.
Folders and statistics
Folder Content Trajectories Length (min / median / max) Samples
--------------- ---------------- ------------ --------------------------- -------
raw/ all trajectories 150 21 / 500 / 500 52,576
train/ converging 60 500 / 500 / 500 30,000
validation/ converging 10 500 / 500 / 500 5,000
test/ converging 30 500 / 500 / 500 15,000
train_div/ diverging 30 21 / 37 / 187 1,585
validation_div/ diverging 5 24 / 52 / 59 230
test_div/ diverging 15 21 / 43 / 126 761
The split is a random permutation (seed 42), 60/10/30 per group. The split folders contain copies of the files in raw/.
Value ranges over raw/:
Group Column min max std
--------- ------ ------- ------- --------
rand_conv u -0.039 0.03897 0.007052
rand_conv x -0.2904 0.2953 0.0398
zero_div u -0.5637 0.5017 0.1465
zero_div x -4.993 4.595 0.8755
File format
One CSV per trajectory, comma separated, header u,x, values rounded to 8 decimals. Row k holds the input u_k and the state x_k = y_k; x_(k+1) is the state after applying u_k. id/params.json holds the system, the generation parameters and the reference certificate in machine-readable form.
In the accompanying code the input is u, the output x, and x the state used for initial conditions.
Reproduction
code/ contains the generator. With Python 3.10.18, NumPy 1.26.4, SciPy 1.15.3 it reproduces every file in id/ byte for byte:
pip install -r code/requirements.txt
python code/generate_one_d_dataset.py --out-dir regenerated/id
The generator is also part of the genSecSysId repository (https://github.com/Dany-L/genSecSysId), commit aa168f6.
License
CC BY 4.0