Add your plant
A plant is one Julia file. Adding a model to the app means writing that file: nothing else in the app has to change, and the plant's form in the interface is generated from what the file declares.
The contract
| Command | u between -1 and +1: +1 is filtration, -1 is backwash, held constant over each sampling period |
| Dynamics | you write two vector fields, F₊ for filtration and F₋ for backwash; the app integrates dx/dt = ½(1+u)·F₊(x) + ½(1-u)·F₋(x) |
| State | x[1] is the cost, x[2] is x1 (hidden, typically the fouling), x[3] is x2; any number of further states may follow |
| Measured | the cost and x2, with Gaussian noise |
| Shown live | the first three states only |
| Time | in the unit you declare; the interface can display another one |
What a plant file declares
- Parameters and initial conditions. Each has a name, a default value, a label, a unit and a group. You choose which ones appear in the interface and which ones stay fixed in the file.
- The dynamics: the two vector fields.
- The initial state, built from the initial conditions and the parameters.
- The solver: the integration algorithm and its tolerances. It is never shown in the interface.
- Suggested controller settings (optional): warm-up and refit durations in your time unit, and a starting point for the controllers' internal model.
Parameters are declared like this:
const PARAMS = [
PlantParam(:r0, 1.0, label = "Clean-membrane resistance", unit = "-", group = "Membrane"),
PlantParam(:k_foul, 0.8, label = "Fouling rate", unit = "1/d", group = "Membrane"),
PlantParam(:k_clean, 6.0, label = "Cleaning rate", unit = "1/d", group = "Backwash"),
PlantParam(:rho, 0.5, label = "Backwash water cost", group = "Backwash", ui = false),
]and the dynamics as two functions returning the derivative of the state, here for a toy model whose state is (cost, x1, x2):
Q(x1, p) = 1 / (p.r0 + x1)
make_fields(p) = (
# filtration: the membrane produces and fouls
(x, mem) -> [Q(x[2], p), p.k_foul * Q(x[2], p), 1.0],
# backwash: it uses water and cleans the membrane
(x, mem) -> [-p.rho * Q(x[2], p), -p.k_clean * x[2], 1.0],
)Steps
- Copy the template plant shipped with the package: a complete, commented, working example.
- Rename it and replace its model with yours.
- Check it.
check_plantruns the plant exactly the way a live run does and reports what is wrong:using FiltrationSimulation check_plant(:my_plant) - Restart the server. The plant is in the list.
A plant that fails its check at start-up is reported and left out of the list; the server and the other plants are not affected.
Large or stiff models
A model with dozens of states is fine: put the cost, x1 and x2 first and everything else after. If your second state is not the quantity you want to compare with a controller's fouling estimate, declare how to compute the displayed one; the simulated physics is not changed.
For a stiff model, declare an implicit solver and tight tolerances. check_plant reports the time per step.