APMonitor Tank Level Control StudioInstructions All apps Open the app →
Lesson · When P-only has no offset, when it does, and what integral action is really for

Tank Level Control Studio

Gravity-drained tank · manual and PID level control

A gravity-drained tank with an inlet valve you can drive by hand or hand to a PID controller. With the outlet closed it is a pure integrator with a fill-only actuator. Open the outlet and Bernoulli's square-root law makes it self-regulating. The app ships as the assignment asks (proportional only, Ki and Kd at zero) so the first thing you meet is offset.

ControlPIDLevelSimulation

1. Overview

This guide covers the Tank Level Control Studio: the mass balance and its two operating regimes, the velocity-form PID the app shares byte-for-byte with the TCLab app, why a P-only loop parks the level away from setpoint and exactly where, what the outlet valve does as a disturbance, and the two behaviors (a small noise-driven offset, and honest negative readings on an empty tank) that look like bugs and are deliberate.

For faculty

Section 2 is lecture background, section 3 is what to demonstrate live, and the scenario table in section 3 is assignable as-is. Section 5 says what not to claim.

For students

Start at section 3, run the app while you read, then come back to section 4 to connect what you saw to the equations. Nothing here is graded. The app is.

For practitioners

Section 2 places Tank Level Control Studio against the real unit operation, section 4 gives the model you would have to replace, and section 5 is the honest gap list.

The one-paragraph version

The tank obeys ρA·dh/dt = c·uin − ρcout(uout/100)√h with ρ = 1000 kg/m³, A = 1 m² and c = 50 kg/s per %, so dh/dt = 0.05uin with the outlet shut. The controller is the velocity form, seeded when auto is engaged at the positional law u = ubias + Kce, so it stays equal to that law for constant gains. Set ubias to the valve position the tank needs at steady state and a P-only loop lands on setpoint. With the outlet shut that value is zero, which is the shipped default, so P-only is offset free there. Open the outlet and the tank needs a nonzero valve, no single bias is right at every level, and the familiar proportional offset appears. Ki is what removes it then.

2. Background

The engineering problem

A cylindrical tank is filled through an inlet valve and drained through an outlet valve. The controlled variable is the liquid level. The manipulated variable is the inlet valve position, 0–100%. The outlet valve is adjustable in both modes, so it doubles as a drain disturbance.

The tank has two quite different characters depending on that outlet valve, and noticing which one you are in explains most of what the app does:

  • Outlet closed. Inflow is constant for a given valve position and nothing drains, so the level is a pure integral of the inlet valve. Worse, the actuator can only add water: there is no way at all to lower the level. A step in valve position gives a ramp in level, and the ramp only stops at the tank height.
  • Outlet open. Outflow follows Bernoulli's principle, ∝ √h, so a rising level drains faster. That negative feedback is internal to the process: the tank now finds its own equilibrium at √h = c·uin/(ρcout(uout/100)), and it is a self-regulating plant.

The engineering problem is to hold a commanded level, 10 m in the assignment, against changes in the outlet valve, with a measurement that carries noise, and with an actuator that saturates at 0% and 100%.

Where it sits in control theory

This app is the collection's introduction to the two most basic ideas in feedback control, and it is deliberately arranged so that both are met as results rather than as claims:

  • Whether proportional-only control leaves offset depends on the plant. The app ships with Ki = Kd = 0 because that is what the assignment asks for. With the outlet shut the tank is an integrator, the steady-state valve it needs is zero, the shipped ubias = 0 is therefore exactly right, and the loop lands on setpoint with no offset. Open the outlet and holding 10 m needs 11.07 %. Leaving the bias at zero then costs (uss − ubias)/Kc = 1.05 m of offset, which you can remove by typing the right bias or by adding Ki. Being able to predict the offset before running is the exercise.
  • Integrating against self-regulating plants. The outlet valve switches the tank between the two, and everything about the loop changes with it: whether a constant valve position has an equilibrium at all, whether the level can be lowered, whether proportional action alone can hold a setpoint. Learning to ask "does this plant self-regulate?" first is worth more than any tuning rule.
  • A nonlinear process gain. Because outflow goes as √h, the incremental gain dh/du depends on the operating level. A tuning that suits a nearly empty tank is different from one that suits a nearly full one. The same operating-point dependence that the speed, reactor and distillation apps show in other units.
  • Three velocity-form PID types, side by side. Type A takes both proportional and derivative action on the error, Type B takes derivative on the measurement, and Type C takes both on the measurement. A setpoint change shows derivative kick on Type A and none on Type B or C, and Type C is the industrial preference. The compute() step is byte-identical to the TCLab app's, so the comparison is genuinely about the algorithm.
  • Inherent anti-windup, for a structural reason. The velocity form clips uk to 0–100% and each step is an increment from the clipped value, so there is no accumulated quantity to unwind. That is a real advantage of the incremental form and it is worth understanding as a design choice rather than a detail.
  • Measurement noise acting on an actuator that cannot undo. With the outlet shut, the valve cracks open whenever the reading falls below the one taken at transfer, and the water that admits cannot drain, so the level rests a few standard deviations above where the noise-free calculation puts it. It is bounded and it settles, because a reading that low stops occurring once the level has risen. This is what a one-way actuator does with a symmetric measurement, not a bug, and opening the outlet even slightly shrinks it. It is the clearest small example in the collection of noise plus a rectifying nonlinearity producing a systematic offset.
  • An instrument model that is honest about signs. The reported level is not clipped at zero. Half-rectifying the noise would bias an empty tank's reading by σ/√(2π) = 0.399σ, an error introduced by the instrument model rather than by the instrument. A real transmitter reads below zero on an empty tank, and the app shows that.

What this app teaches

  • Why proportional-only control leaves a steady-state offset, and how to compute exactly where a velocity-form P-only loop will park.
  • How to tell an integrating plant from a self-regulating one, and why that distinction comes before any tuning decision.
  • That a square-root outflow law makes the process gain depend on the operating level, so one tuning is not right everywhere.
  • What the three velocity-form PID types do differently, demonstrated on a setpoint change.
  • Why the incremental (velocity) form has inherent anti-windup, and what seeding it at a positional law buys and costs.
  • How measurement noise plus a one-directional actuator produces a bounded systematic offset that neither term is at fault for.
  • Why an instrument model should not clip its own noise, and how much bias clipping would introduce.
  • How to use a disturbance (the outlet valve) deliberately, and why the same valve is both a process parameter and an upset.

The application in practice

Level control is the most common loop in the process industries:

  • Surge drums and buffer tanks throughout refining and chemicals, where the level's job is to absorb variation rather than to be held tightly, which is why averaging level control exists and why the cooling-tower app in this collection argues for a deliberately loose tuning.
  • Boiler drum level, the classic hard case: an integrating plant with inverse response (shrink and swell) and a three-element cascade to handle it.
  • Storage tanks, sumps, distillation column bases and reflux drums, all of which are the same mass balance with different fluids.
  • Any inventory: a hopper of solids, a battery's state of charge, a warehouse stock level, a queue. The mathematics is identical and so is the tuning argument.
  • Gravity-drained vessels specifically are worth knowing because the square-root law appears wherever flow is driven by head (a drain, an overflow weir, an orifice) and it is the reason such vessels are self-regulating at all.

This app is built for the APMonitor Tank Level exercise, and its defaults are the assignment's: ρ = 1000 kg/m³, A = 1 m², c = 50 kg/s per percent, and a P-only controller with Kc = 10.

Automation and its broader implications

A level loop is the least glamorous automation in a plant and a good place to see what automation is actually doing.

  • It removes a task humans do badly for boring reasons. Holding a level by hand is not difficult. It is relentless. Try it in MANUAL with the outlet valve moving and the tedium is the point. Almost all industrial automation started here, with tasks that are easy and endless, and the value is attention freed rather than performance gained.
  • The default tuning encodes a decision nobody restated. The app ships P-only because the assignment asks for it, and a P-only level loop will sit off setpoint. On a real plant that is sometimes exactly right (a surge drum should drift, so that flow variation is absorbed rather than passed on) and sometimes a fault. The distinction is a plant decision, and it lives in a number in a configuration file where nobody will look at it again. A great deal of industrial control performance is decided by defaults of this kind.
  • Automation changes what a failure looks like. The noise-driven offset with the outlet shut is small, slow and entirely explicable, and no alarm would catch it and no operator watching a fast trend would notice it. Automated loops fail like this far more often than they fail dramatically: a slow bias, a gradual drift, a small persistent error that nobody owns. Building the habit of asking "what would a slow failure of this loop look like, and who would see it?" is worth more than another tuning rule.
  • Instrumentation honesty is an engineering obligation. The choice not to clip the level reading at zero costs nothing and prevents a 0.4σ bias. Small decisions in a measurement chain create errors that look like process behavior, and once they are in the historian nobody can separate them again. The app exports both the transmitter reading and the true state so an offline identification can tell them apart, which is a courtesy a real plant does not extend, and a reason to think carefully about what gets recorded.
  • Overflow is the automation's real failure mode. A tank that spills sends product, and in a real plant chemistry or hydrocarbon, to a drain or a bund. The consequence of a level loop failing is environmental and financial rather than a control statistic, which is why level loops carry independent high-level alarms and trips, and why "the controller was in auto" is never a sufficient answer.

3. Operating the app

The app runs entirely in your browser. It opens in Manual with an empty tank, the setpoint at 10 m and the manual outlet valve 10% open. Simulation speed runs 1× to 10×, and, importantly, a run gives the same trajectory at 1× as at 10×, including the noise, because the physics and the controller run on the simulated clock rather than on browser timers.

Interface anatomy

The studio apps share a common layout:

  • Header: the chart time window (30 s / 1 min / 5 min / All), the simulation-speed slider, then Stop/Play, Reset, Settings, this Instructions page, and Results (the CSV download), with the session clock.
  • Tank visualization (left): the liquid level, the setpoint marker, the two valve positions, the flow streams, and an overflow indication. The level axis carries whole-meter ticks, so the 10 m setpoint is always labeled.
  • Controls (left, below the tank): Manual / PID auto, the setpoint, the primary control for the current mode, the outlet valve, and the PID tuning with the Type A/B/C switch.
  • Charts and status cards (right): level with its setpoint, and the valve positions, above four status cards (level, control mode, inlet, outlet).

Stop ends the session: time, sampling and charts freeze and the Results download stays available. Play then starts a clean session. Reset restarts the run, including the noise realization, so the same experiment really is the same experiment.

Tank Level Control Studio tagline t = 0.00 s ▶ Run ↺ Reset Manual|Auto Instructions Settings Results 1 Transport, mode switch, and the Instructions / Show Model / Export CSV buttons ANIMATION Animated cylindrical tank with wavy water, LT and LC connected to the inlet valve, manual outlet valve, flow streams and setpoint marker badges: PV · MV · status PV MV OK 2 Animation: the physical picture, with status badges CONTROL Beginner Advanced Scenario 1 · start here ▾ Setpoint (m) Inlet valve (%) Kp Ki Kd Outlet valve (%) 3 Scenario list, setpoint, manual actuator, tuning and disturbances TRENDS Level SP / PV Valve positions 4 Synchronized trends: one time axis, with limits and event marks SCORE IAE MAX ERR SETTLING SATURATED 5 Scorecard tiles
The layout. The tank artwork carries the setpoint marker and both valve positions, so the mass balance is visible without reading numbers. The Instructions button in the header opens this page. Settings opens the five model parameters.
Diagram of the layout rather than a screen capture, so that it follows this page's light or dark theme. The regions and the button names are the ones the app actually shows.

First run, step by step

  1. Fill it by hand. With the outlet closed, set the inlet valve to 20% and press Play. The level ramps at dh/dt = 0.05 × 20 = 1 m/s. Check it against the chart. Note that nothing you do to the inlet valve can bring the level back down.

  2. Make it self-regulating. Open the outlet valve to 50% and watch the level settle. Compute the equilibrium yourself: c·uin/ρ = cout(uout/100)√h. Now the plant has an equilibrium for every valve position.

  3. An integrator needs no integral. Fill partway in Manual with the outlet closed, then switch to PID auto with the shipped P-only tuning (Kc = 10, ubias = 0). The level goes to setpoint and stays, with no integral action at all. Change the setpoint and it follows.

  4. Meet the offset. Now open the outlet to 50% and hold 10 m. The tank needs 11.07% of inlet valve to balance the drain, ubias is still 0, and the loop parks 1.05 m low, (uss − ubias)/Kc. Predict that number before you look.

  5. Remove it twice, two different ways. First type 11.07 into ubias and watch the level rise to setpoint with no integral action. Then set the bias back to 0, add Ki = 1, and watch integral action find the same valve position by itself. That is what integral action is: an automatic search for the bias.

  6. Use the outlet as a disturbance. With the loop in auto and holding 10 m, step the outlet valve from 30% to 60%. Watch the level dip and the inlet valve open. With Ki = 0 the level does not come back. With Ki > 0 it does.

  7. Compare the three PID types. Set Kd = 5 and step the setpoint from 8 m to 10 m under Type A, then Type B, then Type C. Type A kicks the valve hard at the instant of the change. The other two do not, because their derivative acts on the measurement.

  8. Find the actuator limits. Set the setpoint to 12 m (the tank height) with the outlet at 80%. The inlet valve pins at 100% and the level settles wherever the balance puts it. Note there is no windup to unwind when you back the setpoint off. That is the velocity form.

  9. Watch the noise offset. With the outlet fully closed, a P-only loop and the default 0.05 m noise, run for five minutes. The level settles a little above where the noise-free calculation puts it, then holds. Then open the outlet 5% and watch the offset shrink. Section 3.4 explains why.

  10. Keep the data. Press Results to download the CSV. It carries both level_m (what the transmitter reported) and true_level_m (the state), plus the gains and PID type on every row, so an offline identification can separate signal from noise without turning the noise off.

The velocity-form PID, and the bias it is seeded at

The controller is the velocity (incremental) form, and its compute() step is byte-for-byte identical to the TCLab Control Studio app's, so anything you learn about it here transfers directly.

Each cycle adds one increment to the previous output:

TypeIncrement
AKP(ek − ek−1) + KIekdt + KD(ek − 2ek−1 + ek−2)/dt
BKP(ek − ek−1) + KIekdt − KD(PVk − 2PVk−1 + PVk−2)/dt
C−KP(PVk − PVk−1) + KIekdt − KD(PVk − 2PVk−1 + PVk−2)/dt

with e = SP − PV, PV the noisy measured level, and u clamped to 0–100%. Type B is the default. Type A shows derivative kick on a setpoint change. Type C is the industrial preference because neither its proportional nor its derivative term reacts to a setpoint step.

The recursion is seeded at a positional law. Engaging auto sets the output to ubias + Kce and fills the error and measurement history from the current sample. Because the velocity form is the positional form differenced, the output then stays equal to ubias + Kce for constant gains, through later setpoint moves, without being re-seeded. Two consequences worth stating:

  • The bias field is a real bias. It is the valve position the tank needs at steady state. Editing it mid-run re-seeds the recursion so it takes effect immediately. Editing a setpoint or a gain deliberately does not re-seed, because the running output carries the integral history and re-seeding there would throw it away.
  • Transfer is not bumpless, on purpose. The valve steps to ubias + Kce the moment auto is engaged. A bumpless start would pin the output at whatever the manual valve happened to be, and a P-only loop would then rest at h₀ + u₀/Kc. Tracking a setpoint move one for one but never reaching it. Going back to manual is still bumpless.
  • Anti-windup is inherent. The output is clipped to 0–100% and only the integral absorbs the clip, so there is no reset state left over to unwind.

The controller runs at a fixed 0.1 s sample time on the simulated clock, the same time base as the assignment's ODEINT interval, inside the same loop that integrates the tank in 0.05 s substeps. Browser timer jitter cannot change the closed-loop response.

Predicting the P-only offset, and when there is none

This is the app's central quantitative result and it is worth deriving rather than observing. The derivation is one line, and it says something different about each of the tank's two regimes.

A P-only loop holds u = ubias + Kce. At steady state the level must stop moving, so u has to equal whatever valve position uss the plant needs to hold still. Setting the two equal and solving for the error gives the offset directly:

e∞ = (uss − ubias)/Kc

Outlet shut, the integrating case. A tank with no discharge only holds still with the inlet closed, so uss = 0. The shipped ubias is also 0, so e∞ = 0: proportional control alone lands on setpoint, exactly, and lands on the new one after a setpoint move. This is the property that makes P-only respectable for level control, and it is why averaging level control on a surge drum is usually P-only with a deliberately low gain.

Outlet open, the self-regulating case. Holding 10 m against a half-open outlet needs uss = 11.07%. With the bias still at 0 the offset is 11.07/10 = 1.11 m by the formula, and the app measures 1.05 m. The small difference is the square-root outflow law moving uss as the level falls. Now no single bias is right at every level, which is precisely the gap integral action fills.

Three experiments follow. Type the required bias into ubias with the outlet open and watch the offset vanish without integral action. Raise Kc instead and watch it shrink as 1/Kc and never reach zero. Then move the outlet valve and see the bias you typed go stale, which is the argument for Ki in one move.

Two behaviors that look like bugs and are not

Both are documented deliberately, and both teach something.

1 · The small upward offset with the outlet closed. The plant is a pure integrator driven by a fill-only actuator, so nothing can lower the level. With the outlet shut and ubias = 0 a P-only loop holds u = Kce, so the valve cracks open on any sample that reads below setpoint, and the water that admits cannot drain. The level rises until a reading that low has become rare, which happens about 3.2σ above setpoint. The approach is asymptotic rather than a plateau, because a strictly positive mean inflow never stops: 10.12 m after five minutes and 10.16 m after fifty at the default σ = 0.05 m, and 10.24 m then 10.32 m at σ = 0.1 m, the fifty-minute value repeating to within a centimetre across noise seeds. This is what a one-way actuator does with a symmetric measurement, not an algorithm defect, and it is the one place where the offset-free integrating result above is spoiled, by the instrument rather than by the controller. Opening the outlet removes it, because the tank can give the water back.

2 · Negative level readings on an empty tank. The reported level is true level + noise and is not clipped at zero. Clipping it would half-rectify the noise and bias an empty tank's reading by E[max(0, X)] = σ/√(2π) = 0.399σ, which is +0.12 m at the maximum noise setting, a systematic error introduced by the instrument model rather than by the instrument. A real transmitter reads below zero on an empty tank, and the app shows that.

Both are worth pointing out to students explicitly, because the instinct in each case is to assume the simulation is wrong, and the more valuable habit is to ask what mechanism would produce the observation.

Controls reference

Everything below is live in both modes except the mode's own primary control. The outlet valve in particular stays adjustable in auto, which is what makes it usable as a disturbance.

ControlSymbolUnitsRangeDefaultWhat it does
Mode and setpoint
Control mode——Manual · PID autoManualManual drives the inlet valve directly, PID auto hands it to the controller. Transfer is bumpless in both directions.
Level setpointSPm0 – tank height10The assignment value. Only used in auto.
Actuators
Inlet valveuin%0 – 1000The manipulated variable. Manual only. In auto it shows the controller output.
Outlet valveuout%0 – 10010Manual in both control modes. It starts 10% open. Set it to 0% to make the tank an integrator.
Controller tuning
Proportional gainKp% per m0 – 10010The assignment default. With the outlet open it divides the P-only offset (uss − ubias)/Kp.
Biasubias% valve0 – 1000Bias of the positional law u = ubias + Kpe that the recursion is seeded at. Zero is correct with the outlet shut. With the outlet open the required value is the one that balances the drain.
Integral gainKi% per m·s0 – 200Zero by default for the P-only assignment. Accumulates error to remove offset.
Derivative gainKd% per m/s0 – 500Zero by default. Responds to the measurement rate. On a noisy level it needs care.
PID form——A · B · CBWhich velocity-form variant runs. A shows derivative kick on a setpoint change, C is the industrial preference.
Session
Simulation speed—×1 – 101Accelerates physics, control, chart time and sampling together. The trajectory is identical at every speed.
Chart time window——30 s · 1 min · 5 min · All1 minDisplay only.

Disturbances

Disturbances are what the controller has to reject. Sliders are persistent conditions and buttons are one-off events. Every change is stamped on the trends so cause and effect stay visible.

DisturbanceKindUnitsRangeDefaultEffect
Outlet valve openingSlider%0 – 10010The app's principal disturbance, and also the switch between an integrating and a self-regulating plant. Step it while the loop is in auto to see load rejection, or its absence with Ki = 0.
Sensor noiseSettingm (1σ)0 – 0.30.05Zero-mean Gaussian, seeded. Set it to zero to separate the loop's behavior from the instrument's.
Model parametersSettingvarioussee section 4.4—Tank area, inlet and outlet coefficients and tank height are all editable under Settings, so a plant can be changed mid-course to test whether a tuning travels.

Scenarios

This app has no scenario selector. These are experiments to set up by hand, in this order. Each takes a minute or two and each has a checkable result.

#ScenarioWhat it sets up and what to look for
1Fill by hand, outlet closedSets: MANUAL, outlet 0%.
Look for: a ramp, not a settling curve, and the fact that nothing you do to the inlet lowers the level.
2Fill by hand, outlet openSets: MANUAL, outlet 50%.
Look for: an equilibrium for every inlet position. Compute it from the mass balance and check.
3P-only with no offset at allSets: fill partway in MANUAL, outlet 0%, then switch to AUTO with the shipped tuning and ubias = 0.
Look for: the level reaching setpoint and holding with Ki = 0, then following a setpoint move. The bias is right because an integrating tank needs a shut inlet to hold still.
4The offset appears, and two ways to remove itSets: as scenario 3, then open the outlet to 50%.
Look for: the level dropping about 1.05 m low. Type 11.07 into ubias and it returns with no integral action. Set the bias back to 0 and add Ki = 1 and integral action finds the same valve position by itself.
5Load disturbanceSets: AUTO holding 10 m, outlet stepped 30% → 60%.
Look for: the dip, the inlet valve opening, and whether the level returns, which depends entirely on Ki.
6Derivative kickSets: Kd = 5, setpoint stepped 8 → 10 m, under each PID type.
Look for: the valve spike on Type A and its absence on B and C.
7Actuator saturationSets: setpoint 12 m with the outlet at 80%.
Look for: the inlet pinned at 100%, and no windup to unwind when you back the setpoint off.
8Noise offset on the integratorSets: outlet 0%, P-only, noise 0.05 m, five minutes.
Look for: the level resting about 3.2σ above setpoint (10.16 m at σ = 0.05 m) and then holding, because a shut drain cannot give back the water the noise let in. Open the outlet and it goes.
9Nonlinear gainSets: outlet 50%, one tuning, setpoints of 2 m and 10 m.
Look for: a different response at each level, because h ∝ uin².

Reading the charts and the scorecard

Both charts share one time axis, and the window selector covers 30 s, 1 min, 5 min and All.

  • Level: the measured level with the setpoint. The level axis uses whole-meter ticks (0, 2, … 10, 12 m) so the 10 m assignment setpoint is always labeled and no tick is clipped.
  • Valve positions: inlet and outlet, 0–100%. The inlet trace is the controller's output in auto and your hand in manual. A flat trace at 0% or 100% is saturation.

The four status cards report the level (with an overflow warning), the control mode and gains, and the two valve positions with their volumetric flows. The flow numbers are the quickest way to check the mass balance by hand: at steady state the two must be equal.

The Results CSV carries thirteen columns including both the transmitter reading and the true level, so an offline identification can separate the noise without turning it off. The gains and PID type are repeated on every row, which means the file alone reproduces the experiment.

Logged data and CSV export

The Results button downloads every logged sample. The time column's unit adapts to the run length, and both the measured and the true level are recorded.

ColumnMeaningUnits
time_sElapsed session time. The unit adapts to the run length.s
inlet_valve_pctInlet valve position: the manipulated variable.%
level_mMeasured level, including noise. What the controller acted on.m
true_level_mThe actual state, noise-free. Having both is what lets an offline identification separate them.m
outlet_valve_pctOutlet valve position.%
modemanual or auto at that sample.text
setpoint_mLevel setpoint.m
kp, ki, kdThe gains in force at that sample, repeated on every row so the file alone reproduces the run.—
pid_typeA, B or C.text
simulation_speed_xSpeed factor, for the record. It does not affect the trajectory.×
overflowtrue while the tank was spilling.true/false

If something looks wrong

SymptomMost likely causeWhat to do
The level settles below setpoint and stays thereProportional-only control against an outlet that is open, so the tank needs a valve position the bias does not supplyThe offset is (uss − ubias)/Kc. Predict it, then remove it either by typing the required bias or by adding Ki. With the outlet shut there is no offset to remove.
The level will not come downOutlet valve closed: the actuator can only add waterOpen the outlet valve. With it shut the tank is an integrator with a fill-only actuator and no controller can lower the level.
The level rests a few centimeters above setpoint, with the outlet shutMeasurement noise cracking the valve open on a plant that cannot drainExpected, and bounded: it settles within minutes at about 3.2σ above setpoint, which is 0.16 m at the default noise. Open the outlet and it goes. Section 3.4.
The level reading goes negative on an empty tankThe instrument model does not clip its noise, deliberatelyCorrect behavior. Clipping would bias an empty tank by 0.399σ. Compare level_m with true_level_m in the CSV.
The valve kicks hard when I move the setpointPID Type A takes derivative action on the errorSwitch to Type B or C, whose derivative acts on the measurement. Type C also removes the proportional kick.
The tank overflowsThe level saturates at the tank height and spillsReduce the setpoint, open the outlet, or lower Kc. On a real plant this is the loop's consequential failure mode.
Two runs of the same experiment give different numbersThey should notThe noise is seeded from a fixed constant and Reset restarts the realization. If they differ, check that you pressed Reset rather than changing a parameter mid-run.
The response looks different at 10× speedIt should notThe physics and controller run on the simulated clock, so 1× and 10× produce identical trajectories. Check that the time window did not change what you are looking at.

4. Model

One state, the liquid level, and one algebraic instrument model. Integration is explicit Euler with 0.05 s substeps. The controller samples every 0.1 s. Both run on the simulated clock, so a run replays exactly.

Modeling assumptions

Every one of these is a deliberate simplification. Knowing which one you are standing on is the difference between a model you can teach with and a model you can design with.

  • Tank cross section and water density are constant.
  • Valve flow responds instantly to the command.
  • Overflow is clamped at the tank height.

Equations

Mass balance on the gravity-drained tank

With the outlet closed the second term vanishes and the tank is a pure integrator: dh/dt = 0.05·uin at the default parameters. With the outlet open, the square-root term is negative feedback internal to the process, and the tank becomes self-regulating.

Equilibrium level with the outlet open

The level goes as the square of the inlet valve position, so the incremental process gain dh/du grows with the operating level. One tuning is not right at every level.

Draining time from full

At the defaults this is 2√12/0.35 ≈ 20 s for a full 12 m tank at 100% outlet, which is where the 0.35 coefficient comes from.

Velocity-form PID (Type B, the default)

Each cycle adds an increment, and only the integral absorbs the output clip, which is what gives the incremental form its inherent anti-windup. Engaging auto seeds the recursion at ubias + Kce, so the running output stays equal to that positional law for constant gains.

Steady-state offset of a proportional loop

The whole assignment in one line, and it says something different about each regime. With the outlet shut the tank holds still only with the inlet closed, so uss = 0, the shipped bias is 0, and the offset is zero. With the outlet open uss is whatever balances the drain, no single bias is right at every level, and the offset is what integral action exists to remove.

Instrument model

Zero-mean Gaussian noise, not clipped at zero. Clipping would bias an empty tank by σ/√(2π) = 0.399σ. The generator is a seeded mulberry32, so a noisy run replays exactly.

Variables

These are the quantities that move while the simulation runs. The app shows all of them live under Show Model → Variables.

SymbolMeaningUnitsWhere it appears
hTrue liquid levelmThe single integrated state, and what the tank visualization draws.
PVMeasured levelmTrue level plus noise. This is what the controller acts on, and it is the trace on the level chart. Exported separately from the true level.
SPLevel setpointmFrom the setpoint control. The assignment value is 10 m.
eError, SP − PVmNote it is computed on the noisy measurement.
uinInlet valve position%The manipulated variable: your hand in manual, the controller output in auto. Clamped 0–100%.
uoutOutlet valve position%A process parameter and a disturbance. Adjustable in both modes.
qinInlet volumetric flowm³/sc·uin/ρ. Shown on the inlet status card.
qoutOutlet volumetric flowm³/scout(uout/100)√h. At steady state it equals the inlet flow, the quickest hand check in the app.

Parameters and default values

Parameters are held constant while the simulation runs but can be edited under Show Model → Parameters. The default column is what the app loads with, and what every scenario in the table above is quoted against.

SymbolNameDefaultRangeUnitsMeaning
ATank cross-sectional area1.00.5 – 5m²The assignment default. A larger tank fills and drains more slowly. It divides the whole mass balance, so it is a pure time-scaling parameter.
cInlet valve coefficient5010 – 100kg/s per %Mass flow per percent open, from the assignment. With A = 1 m² and ρ = 1000 kg/m³ this gives dh/dt = 0.05u.
coutOutlet valve coefficient0.350.05 – 1m³/s per √mThe Bernoulli coefficient at 100% open. The default drains a full 12 m tank in about 20 s.
hmaxTank height125 – 20mOverflow height. The level saturates here and the tank spills.
σSensor noise (standard deviation)0.050 – 0.3mZero-mean Gaussian noise on the reported level, seeded so a run replays exactly. Set it to 0 to see the loop without it.
ρWater density1000fixedkg/m³Not user editable. Appears only in the conversion between the inlet mass coefficient and volumetric flow.
KpProportional gain100 – 100% per mThe assignment default. With the outlet open it divides the P-only offset: (uss − ubias)/Kp.
ubiasBias of the positional law the recursion is seeded at00 – 100% valveThe valve position the tank needs at steady state. Zero is exactly right with the outlet shut, which is why P-only is offset free there. Editing it re-seeds the controller, so it takes effect at once.
KiIntegral gain00 – 20% per m·sZero by default, because the assignment asks for P-only. Adding it is what removes the offset.
KdDerivative gain00 – 50% per m/sZero by default. Useful mainly for demonstrating the difference between the three PID types.
ΔtctlController sample time0.1fixedsThe assignment's ODEINT interval. Runs on the simulated clock.
ΔtsimIntegration substep0.05fixedsExplicit Euler. Fine against a tank whose fastest drain time is about 20 s.

Numerical method and timing

The tank is integrated with explicit Euler in 0.05 s substeps and the controller samples every 0.1 s, both on the simulated clock inside a single loop. Nothing is driven by wall-clock timers, which has three consequences worth relying on: a run gives the same trajectory at 1× as at 10×, the CSV time column steps by exactly 0.100 s, and browser timer jitter cannot change the closed-loop response.

The sensor noise is deterministic too. A mulberry32 generator seeded from a fixed constant rather than Math.random, so a noisy run replays exactly and two students can compare numbers. Reset restarts the noise realization along with the state.

Euler at 0.05 s is comfortable here: the fastest thing in the model is a full tank draining in about 20 s. It is worth knowing that the choice is Euler rather than a higher-order method because the assignment's reference solution uses a fixed-interval ODE solve, and matching it matters more than the extra accuracy would.

How the model is checked

npm run test runs this app through the shared framework checks for model behavior, controller limits, scenario stability, reproducible reset, and CSV consistency. npm run build creates the deployable app and instructions.

5. Limitations

A browser simulation is a teaching instrument, not a plant model. These are the places where this one stops describing the real thing. Read them before quoting a number from it in a design.

Process representation

  • One well-mixed lump with a single level state. No inlet jet momentum, no sloshing, no stratification, and no dynamics in the piping, the inlet flow responds to the valve instantly.
  • The valves are ideal. Inlet flow is exactly linear in position and outlet flow exactly follows √h, with no valve characteristic, no stiction, no hysteresis, no actuator lag and no stroking-rate limit. A real control valve has all five, and stiction in particular is the commonest cause of a cycling level loop in service.
  • No upstream or downstream pressure. The inlet coefficient does not depend on supply pressure and the outlet does not depend on backpressure, so neither is a disturbance here.
  • Constant density and temperature, and no composition. The tank holds one fluid at one condition.
  • Overflow and empty are hard clamps. The level saturates at the tank height and at zero with no spill dynamics, no weir behavior and no pump-suction consequence.

Instrument and control scope

  • The level measurement has noise and nothing else. No transmitter lag, no calibration drift, no bias, no quantization and no failure mode. A real level transmitter (differential-pressure, radar or float) has its own dynamics and its own characteristic errors.
  • Only PID, in the velocity form. There is no cascade to a flow loop (which is how a real drum level is controlled), no feedforward from the outlet flow, no gain scheduling for the nonlinear gain, and no averaging-level or error-squared controller, all of which are standard answers to problems this app poses.
  • No alarms, trips or interlocks. Overflow is annunciated in the visualization and nothing happens. On a real tank the high-level trip is the protection that matters.
  • Explicit Euler at 0.05 s. Comfortable for this plant, and not a general-purpose choice: it was picked to match the assignment's reference solution rather than for accuracy.
  • Perfect reproducibility. A real plant does not replay, and a real DCS's sample time jitters. The determinism here is a teaching convenience.

What this app is, and is not, evidence for

It is a sound demonstration of proportional offset and its exact magnitude, of the difference between an integrating and a self-regulating plant, of the three velocity-form PID types, and of the inherent anti-windup of the incremental form. It is not a tank or valve sizing tool, and with ideal valves and an ideal transmitter it cannot show the mechanical and instrument faults that cause most real level-loop problems.

6. Classroom and practice

Each of these has a number you can mark, and most are answered from the Results CSV.

E1 · Verify the mass balance

With the outlet closed, hold the inlet valve at 10%, 20% and 40% and measure dh/dt from the chart in each case. Compare with c·u/(ρA). Then open the outlet to 100%, fill the tank, close the inlet, and time the drain from 12 m. Compare with 2A√h₀/cout.

E2 · Predict the offset, in both regimes

First with the outlet shut: hand the loop over to auto at three different (level, valve) pairs with ubias = 0 and predict where it lands. The answer is the setpoint every time, and the point of doing it three times is that the handover condition no longer matters.

Now open the outlet to 30, 50 and 80%. For each, compute the valve position uss that balances the drain at setpoint, predict (uss − ubias)/Kc with the bias still at 0, and compare with what the app settles at. Your prediction will be slightly large every time. Explain why using the square-root outflow law, and say which level uss should have been evaluated at.

Then repeat one of the open-outlet cases at Kc = 5, 10, 20 and 40 and confirm the offset scales as 1/Kc.

E3 · Remove the offset, and pay for it

With the outlet at 40% and the loop holding 10 m, sweep Ki over 0, 0.25, 0.5, 1, 2 and 4 and record, after a step in the outlet valve to 60%: the peak deviation, the time to return within 0.1 m, and the total inlet-valve travel. Plot deviation against travel and recommend a value.

E4 · Show the process gain moves

With the outlet at 50%, tune the loop so that a 1 m setpoint step around 3 m gives a well-damped response. Freeze that tuning and repeat the same 1 m step around 9 m. Report both responses, explain the difference from h ∝ uin², and propose a gain schedule in h that would fix it.

E5 · Compare the three PID types properly

With Kd = 5 and the outlet at 50%, run each PID type through (a) a 2 m setpoint step and (b) a 20-point step in the outlet valve. Tabulate peak valve movement and peak level deviation for all six runs. Then explain, from the increment expressions, which type is best for each of the two tests and why.

E6 · Quantify the noise offset

With the outlet closed, P-only, and the setpoint below the current level, run for five simulated minutes at noise levels of 0, 0.02, 0.05, 0.1 and 0.2 m and record the level rise in each case. Repeat each setting three times with a reset between runs, because the rise depends on the noise sample present at the moment of transfer and the scatter between repeats is as large as the mean itself. Plot mean rise against σ and confirm it is proportional, with a slope near 6. Then check that the rise stops rather than continuing: run σ = 0.2 m for five minutes and again for thirty, and compare. Finally repeat at σ = 0.05 m with the outlet at 2%, 5% and 10%, and report the opening at which the resting level falls below the noise-free prediction rather than above it.

E7 · For practitioners: specify the loop

Write the design basis you would hand over for this loop: the control objective (tight or averaging, and why), the tuning with its justification, the valve characteristic you would specify and why, the level-transmitter requirements, the high-level and low-level alarm and trip settings, and the behavior required when the inlet valve saturates. Then, from section 5, list what this model cannot tell you, particularly about valve stiction, and how you would check it on the real tank.

7. References and further reading