Control a Water Tank Level

Consider a cylindrical tank with no outlet flow and an adjustable inlet flow that is controlled by a valve. The inlet flow rate is not measured but there is a level measurement that shows how much fluid has been added to the tank. The objective of this exercise is to develop a controller that can maintain a certain water level by automatically adjusting the inlet flow rate.

Note: The symbol LT is an abbreviation for Level Transmitter. A concentration sensor is typically shown as CT for Concentration Transmitter or AT for Analyzer Transmitter. A temperature sensor such as a thermocouple is shown as TT which stands for Temperature Transmitter. If the second letter is C then it is a controller such as LC for Level Controller.

This example continues from the introduction to modeling where the process equation is derived and sample Python code is available.

$$ \rho \; A \; \frac{dh}{dt} = c \; u \quad \mathrm{with} \quad \dot m_{in} = c \; u$$

where c is a constant that relates valve opening to inlet flow.


Design a P-only controller for the tank to maintain a level set point of 10.0 m. Test the P-only controller with different values of Kc by integrating the mass balance equation for a period of 10 seconds. Use a value of 1000 kg/m3 for density and 1.0 m2 for the cross-sectional area of the tank. For the valve, assume a valve coefficient of c=50.0 (kg/s / percent open). Make sure that the valve does not exceed the limits of 0-100 percent by clipping the requested valve opening to an acceptable range. For example, if the P-only controller calculates a valve opening of 150 percent, use 100 percent instead.

Starting Python Code

Select "Get Code" from the link at the bottom. Fill in the areas labeled "TO DO" to implement the P-only controller.

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint

# define tank model
def tank(Level,time,c,valve):
    rho = 1000.0 # water density (kg/m^3)
    A = 1.0      # tank area (m^2)
    # calculate derivative of the Level
    dLevel_dt = (c/(rho*A)) * valve
    return dLevel_dt

# time span for the simulation for 10 sec, every 0.1 sec
ts = np.linspace(0,10,101)

# valve operation
c = 50.0          # valve coefficient (kg/s / %open)
u = np.zeros(101) # u = valve % open

# level initial condition
Level0 = 0

# initial valve position
valve = 0

# for storing the results
z = np.zeros(101)
es = np.zeros(101)

# TO DO: what is the value for ubias?
# ubias = ?

# TO DO: decide on a tuning value for Kc
# Kc = ?

# TO DO: record the desired level (set point)
# SP = ?

# simulate with ODEINT
for i in range(100):
    # calculate the error
    error = SP - Level0

    # TO DO: put P-only controller here
    # valve = ?

    u[i+1] = valve   # store the valve position
    es[i+1] = error  # store the error
    y = odeint(tank,Level0,[0,0.1],args=(c,valve))
    Level0 = y[-1] # take the last point
    z[i+1] = Level0 # store the level for plotting

# plot results
plt.ylabel('Tank Level')
plt.ylabel('Error = SP-PV')    
plt.xlabel('Time (sec)')