DataGeneration Strategies

Generating data is the most important step when creating a DigitalEcho of your system. It is crucial that the way you intend to use your surrogate is captured by the dataset in order to ensure optimal performance. In practice there are two data generation schemes to consider. The first involves full knowledge of all the different configurations you will run your simulator with. The second is when you only have partial knowledge, in particular not knowing the form of your inputs to your system and only knowing its qualitative behaviour. This is a comprehensive walkthrough of implementing both scenarios using the API to demonstrate how the DataGeneration module makes it easy to generate data for either of these cases in order to generate a high-performance DigitalEcho of your system.

Full knowledge of the Configuration

To know the configurations of the system is to know what kind of parameters you will run the system with, also known as the ParameterSpace, and if it has inputs, what form of inputs, as well as its possible parameters, also known as the CtrlSpace. Configuring both the ParameterSpace and the CtrlSpace can be captured by the following code:

using DataGeneration, Controllers, Layer

Since we have the prior knowledge of the parameters, we will define the lower bound and upper bound of our parameter space.

p_lb = [0.19, 0.39, 0.1]
p_ub = [0.21, 0.41, 0.1]
3-element Vector{Float64}:
 0.21
 0.41
 0.1

Now we define the number of samples given these bounds

n_samples_p = 10
10

Now we define the ParameterSpace as:

param_space = ParameterSpace(p_lb, p_ub, n_samples_p);

We can optionally provide labels for our parameters as a keyword argument:

param_labels = ["p1", "p2", "p3"]
param_space = ParameterSpace(p_lb, p_ub, n_samples_p; labels = param_labels)
 3 dimensional ParameterSpace with 10 samples 
 ╭──────────────────┬──────────────────────────────────────────────┬──────────...
─────────╮...
    Space Type                      Statistics                    Number...
of Samples  ...
├──────────────────┼──────────────────────────────────────────────┼──────────...
─────────┤...
                    ╭──────────┬──────────────┬──────────────╮  ...
                      Labels    LowerBound    UpperBound    ...
                    ├──────────┼──────────────┼──────────────┤  ...
                        p1         0.19          0.21       ...
  ParameterSpace    ├──────────┼──────────────┼──────────────┤  ...
                      ...
                      ...
                    ├──────────┼──────────────┼──────────────┤  ...
                        p3         0.1           0.1        ...
                    ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────────┴──────────────────────────────────────────────┴──────────...
─────────╯...

Similarly we can define a CtrlSpace to sample inputs that drive the system. Since the knowledge of the nature of the inputs form is known we can use parameterized functions. As with the ParameterSpace, we define the lower bound, upper bound and the number of samples. We start by defining the lower bound and upper bound of our control parameters, and the number of samples that we want.

ctrl_lb = [0.0]
ctrl_ub = [0.2]
n_samples_ctrl = 2
2

We now define a parameterized input function, that will take in the states(u), control parameters(ctrl_p) and time(t)

ctrl_func(u, ctrl_p, t) = ctrl_p*exp(-t)
ctrl_func (generic function with 1 method)

The above function is an example of a parameterized open loop controller.

Finally putting it all together:

ctrl_space = CtrlSpace(ctrl_lb, ctrl_ub, ctrl_func, n_samples_ctrl);

Similar to the ParameterSpace we can optionally provide labels for our controls as:

ctrl_space = CtrlSpace(ctrl_lb, ctrl_ub, ctrl_func, n_samples_ctrl; labels = ["input_1"])
 1 dimensional CtrlSpace with 2 samples 
 With controls : input_1 
╭──────────────┬──────────────────────────────────────────────┬──────────────...
─────╮...
  Space Type                    Statistics                    Number...
Samples  ...
├──────────────┼──────────────────────────────────────────────┼──────────────...
─────┤...
                ╭──────────┬──────────────┬──────────────╮  ...
                  Labels    LowerBound    UpperBound    ...
  CtrlSpace     ├──────────┼──────────────┼──────────────┤  ...
                   x_1          0            0.2        ...
                ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────┴──────────────────────────────────────────────┴──────────────...
─────╯...

Once we have defined our spaces for the parameters and inputs, we can define a SimulatorConfig. A SimulatorConfig takes in the above defined spaces, and use them to carry out the simulations.

simconfig = SimulatorConfig(param_space, ctrl_space)
 Simulator Config with :   CtrlSpace  ParameterSpace
  Statistics for all Spaces  

  1 dimensional CtrlSpace with 2 samples 
 With controls : input_1 
╭──────────────┬──────────────────────────────────────────────┬──────────────...
─────╮...
  Space Type                    Statistics                    Number...
Samples  ...
├──────────────┼──────────────────────────────────────────────┼──────────────...
─────┤...
                ╭──────────┬──────────────┬──────────────╮  ...
                  Labels    LowerBound    UpperBound    ...
  CtrlSpace     ├──────────┼──────────────┼──────────────┤  ...
                   x_1          0            0.2        ...
                ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────┴──────────────────────────────────────────────┴──────────────...
─────╯...
 3 dimensional ParameterSpace with 10 samples 
 ╭──────────────────┬──────────────────────────────────────────────┬──────────...
─────────╮...
    Space Type                      Statistics                    Number...
of Samples  ...
├──────────────────┼──────────────────────────────────────────────┼──────────...
─────────┤...
                    ╭──────────┬──────────────┬──────────────╮  ...
                      Labels    LowerBound    UpperBound    ...
                    ├──────────┼──────────────┼──────────────┤  ...
                        p1         0.19          0.21       ...
  ParameterSpace    ├──────────┼──────────────┼──────────────┤  ...
                      ...
                      ...
                    ├──────────┼──────────────┼──────────────┤  ...
                        p3         0.1           0.1        ...
                    ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────────┴──────────────────────────────────────────────┴──────────...
─────────╯...

Once we have defined our SimulatorConfig, it can be called on the problem that we want to generate data for. This problem can be an ODEProblem or a FMU.

Partial Knowledge of the Configuration

In the previous section, we configured our SimulatorConfig with full knowledge of all the different kinds of parameters and inputs we wanted to simulate our model with. However, it is often the case that one does not have knowledge about the form of their inputs and therefore cannot configure their CtrlSpace exactly. In such a scenario, JuliaSimSurrogates provides a tuning app that allows one to provide what they do know about their inputs from a qualitative perspective in order to identify a useful parameterized form to sample from.

The dashboard allows one to interactively tune a controller that may represent what an input could typically look like for the system without knowledge of understanding what the different controllers mean, as well as their parameters. Simply move and adjust the sliders accordingly until the example controller being displayed matches visually in a qualitative manner to what you expect the system to see.

The dashboard can be started within the JuliaSim session by executing the datagen_tuning_app function. A user has a choice between RandomNeuralController, and SIRENController as the basic parameterized form, and does not need to understand or know what they are, simply toggle either one and see which gives you the closest fit to the qualitative nature of the input that your system expects.

img

1. RandomNeuralController

Based on the above procedure, if you found that the RandomNeuralController fit your expected input qualitatively better, translating what you see on the app to the API is as simple as copying the field value and names as demonstrated below:

Starting off by defining the number of inputs.

n_inputs = 2
2

We then define a RandomNeuralController as:

random_controller = RandomNeuralController(n_inputs)
RandomNeuralController{Int64, Int64, Int64, Controllers.var"#8#10", typeof(sin), Nothing}(2, 5, 0, Controllers.var"#8#10"(), sin, nothing)

We can then provide the controller config parameters we identified using the tuning app:

random_controller = RandomNeuralController(
    n_inputs = n_inputs,
    n_hidden = 5,
    n_hidden_layers = 2,
    out_omega = 2pi,
    out_activation = sin,
    hidden_omega = 1.0,
    hidden_activation = sin,
    scale = nothing
)
RandomNeuralController{Int64, Int64, Int64, Controllers.var"#14#16"{Float64, typeof(sin)}, Controllers.var"#13#15"{Float64, typeof(sin)}, Nothing}(2, 5, 2, Controllers.var"#14#16"{Float64, typeof(sin)}(6.283185307179586, sin), Controllers.var"#13#15"{Float64, typeof(sin)}(1.0, sin), nothing)

2. SIRENController

Based on the above procedure, if you found that the SIRENController fit your expected input qualitatively better, translating what you see on the app to the API is as simple as copying the field value and names as demonstrated below:

n_inputs = 2
siren_controller = SIRENController(n_inputs)
SIRENController{Int64, Int64, Int64, Controllers.var"#22#24", typeof(Layer.σ_siren), Bool, Float64, Nothing}(2, 64, 3, Controllers.var"#22#24"(), Layer.σ_siren, true, 0.1, nothing)

We can then provide the controller config parameters we identified using the tuning app:

siren_controller = SIRENController(
    n_inputs = n_inputs,
    n_hidden = 64,
    n_hidden_layers = 3,
    out_omega = 1 / 150.0,
    out_activation = tanh,
    hidden_activation = Layer.σ_siren,
    bias = true,
    omega = 1.0 / 10.0,
    scale = nothing
)
SIRENController{Int64, Int64, Int64, Controllers.var"#27#28"{Float64, typeof(tanh)}, typeof(Layer.σ_siren), Bool, Float64, Nothing}(2, 64, 3, Controllers.var"#27#28"{Float64, typeof(tanh)}(0.006666666666666667, tanh), Layer.σ_siren, true, 0.1, nothing)

In either case of the kind of controller basis you decide on, the next step is to decide how you want the inputs to be connected to the simulations.

lb = [0.0, 0.5]
ub = [2.5, 2.6]
n_samples_ctrl = 10
10
ctrl_space = CtrlSpace(lb, ub, simple_open_loop_f, n_samples_ctrl, random_controller);
ctrl_space = CtrlSpace(lb, ub, simple_open_loop_f, n_samples_ctrl, siren_controller);
 2 dimensional CtrlSpace with 10 samples 
 ╭──────────────┬──────────────────────────────────────────────┬──────────────...
─────╮...
  Space Type                    Statistics                    Number...
Samples  ...
├──────────────┼──────────────────────────────────────────────┼──────────────...
─────┤...
                ╭──────────┬──────────────┬──────────────╮  ...
                  Labels    LowerBound    UpperBound    ...
                ├──────────┼──────────────┼──────────────┤  ...
                   x_1          0            2.5        ...
  CtrlSpace     ├──────────┼──────────────┼──────────────┤  ...
                  ...
                  ...
                ├──────────┼──────────────┼──────────────┤  ...
                   x_2         0.5           2.6        ...
                ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────┴──────────────────────────────────────────────┴──────────────...
─────╯...
ctrl_space = CtrlSpace(lb, ub, simple_closed_loop_f, n_samples_ctrl, random_controller);
ctrl_space = CtrlSpace(lb, ub, simple_closed_loop_f, n_samples_ctrl, siren_controller);
 2 dimensional CtrlSpace with 10 samples 
 ╭──────────────┬──────────────────────────────────────────────┬──────────────...
─────╮...
  Space Type                    Statistics                    Number...
Samples  ...
├──────────────┼──────────────────────────────────────────────┼──────────────...
─────┤...
                ╭──────────┬──────────────┬──────────────╮  ...
                  Labels    LowerBound    UpperBound    ...
                ├──────────┼──────────────┼──────────────┤  ...
                   x_1          0            2.5        ...
  CtrlSpace     ├──────────┼──────────────┼──────────────┤  ...
                  ...
                  ...
                ├──────────┼──────────────┼──────────────┤  ...
                   x_2         0.5           2.6        ...
                ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────┴──────────────────────────────────────────────┴──────────────...
─────╯...

Notice how configuring the CtrlSpace here is very similar to the previous example, with the exception that a user specifies whether or not the controllers should be run in open or closed loop instead of writing out the actual evaluation function (u,p,t) -> ctrl(u,p,t). simple_open_loop_f refers to open loop control whereas simple_closed_loop_f refers to closed loop control.

FixedICController

It is often required that the initial condition of the system should be fixed. In order to define controls for fixed initial conditions we use FixedICController. We wrap the above mentioned controls inside a FixedICController and provide an initial condition u0.

u0 = [0.0, 1.0]
fixed_ic_controller_random = FixedICController(random_controller, u0)
fixed_ic_controller_siren = FixedICController(siren_controller, u0)
FixedICController{SIRENController{Int64, Int64, Int64, Controllers.var"#27#28"{Float64, typeof(tanh)}, typeof(Layer.σ_siren), Bool, Float64, Nothing}, Vector{Float64}}(SIRENController{Int64, Int64, Int64, Controllers.var"#27#28"{Float64, typeof(tanh)}, typeof(Layer.σ_siren), Bool, Float64, Nothing}(2, 64, 3, Controllers.var"#27#28"{Float64, typeof(tanh)}(0.006666666666666667, tanh), Layer.σ_siren, true, 0.1, nothing), [0.0, 1.0])

And similarly we define the CtrlSpace as:

ctrl_space = CtrlSpace(lb, ub, simple_open_loop_f, n_samples_ctrl, random_controller);
ctrl_space = CtrlSpace(lb, ub, simple_open_loop_f, n_samples_ctrl, siren_controller);
ctrl_space = CtrlSpace(lb, ub, simple_closed_loop_f, n_samples_ctrl, fixed_ic_controller_random);
ctrl_space = CtrlSpace(lb, ub, simple_closed_loop_f, n_samples_ctrl, fixed_ic_controller_siren)
 2 dimensional CtrlSpace with 10 samples 
 ╭──────────────┬──────────────────────────────────────────────┬──────────────...
─────╮...
  Space Type                    Statistics                    Number...
Samples  ...
├──────────────┼──────────────────────────────────────────────┼──────────────...
─────┤...
                ╭──────────┬──────────────┬──────────────╮  ...
                  Labels    LowerBound    UpperBound    ...
                ├──────────┼──────────────┼──────────────┤  ...
                   x_1          0            2.5        ...
  CtrlSpace     ├──────────┼──────────────┼──────────────┤  ...
                  ...
                  ...
                ├──────────┼──────────────┼──────────────┤  ...
                   x_2         0.5           2.6        ...
                ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────┴──────────────────────────────────────────────┴──────────────...
─────╯...
Note

In the previous section when we defined the lower bound and upper bound, it referred to the lower and upper bound of the control parameters. Here, we refer to the lower and upper bound of the input function.

Now that we have defined a CtrlSpace with partial knowledge about the configuration. We construct a SimulatorConfig with the CtrlSpace and ParameterSpace in the same way we did in the previous section.

simconfig = SimulatorConfig(ctrl_space, param_space)
 Simulator Config with :   CtrlSpace  ParameterSpace
  Statistics for all Spaces  

  2 dimensional CtrlSpace with 10 samples 
 ╭──────────────┬──────────────────────────────────────────────┬──────────────...
─────╮...
  Space Type                    Statistics                    Number...
Samples  ...
├──────────────┼──────────────────────────────────────────────┼──────────────...
─────┤...
                ╭──────────┬──────────────┬──────────────╮  ...
                  Labels    LowerBound    UpperBound    ...
                ├──────────┼──────────────┼──────────────┤  ...
                   x_1          0            2.5        ...
  CtrlSpace     ├──────────┼──────────────┼──────────────┤  ...
                  ...
                  ...
                ├──────────┼──────────────┼──────────────┤  ...
                   x_2         0.5           2.6        ...
                ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────┴──────────────────────────────────────────────┴──────────────...
─────╯...
 3 dimensional ParameterSpace with 10 samples 
 ╭──────────────────┬──────────────────────────────────────────────┬──────────...
─────────╮...
    Space Type                      Statistics                    Number...
of Samples  ...
├──────────────────┼──────────────────────────────────────────────┼──────────...
─────────┤...
                    ╭──────────┬──────────────┬──────────────╮  ...
                      Labels    LowerBound    UpperBound    ...
                    ├──────────┼──────────────┼──────────────┤  ...
                        p1         0.19          0.21       ...
  ParameterSpace    ├──────────┼──────────────┼──────────────┤  ...
                      ...
                      ...
                    ├──────────┼──────────────┼──────────────┤  ...
                        p3         0.1           0.1        ...
                    ╰──────────┴──────────────┴──────────────╯  ...
╰──────────────────┴──────────────────────────────────────────────┴──────────...
─────────╯...