Torricelli’s law describes the speed of fluid leaving an opening. For a tank draining through a hole at its base, it provides a rate-of-change equation for the water height. Solving that ordinary differential equation (ODE) turns an initial water level into a predicted drainage curve and an estimated emptying time. section 9.2 section 9.2.2
A simple model combines tank area, hole area, and gravity into k = (hole area / tank area) × √(2g). While water remains, the height changes at dh/dt = −k√h; at or below zero, the model stops the outflow. The negative sign means the level falls, and the square-root term makes the modeled rate depend on the current height. section 9.2.2
Estimate when a tank will empty
With a 1.5 m tank diameter, a 1 cm opening, and an initial water height of 3 m, a Python model passes the height-rate function to SciPy’s solve_ivp. Its simulated curve puts emptying at approximately four hours. An ultrasonic level-sensor reading can replace the assumed initial height when making a prediction for the tank’s current state. section 9.2.2
Listing 9.1 Using a SciPy integrator to simulate tank drainage
import numpy as np
from scipy.integrate import solve_ivp
D_tank = 1.5 # Tank diameter (m)
R_tank = D_tank / 2 # Tank radius (m)
A_tank = np.pi * R_tank**2 # Tank cross-sectional area (m²)
D_hole = 0.01 # Leak diameter (m) - 1 cm
R_hole = D_hole / 2 # Leak radius (m)
A_hole = np.pi * R_hole**2 # Leak area (m²)
g = 9.81 # Gravity (m/s²)
k = (A_hole / A_tank) * np.sqrt(2 * g) #1
def leaking_tank(t, h): #2
if h <= 0:
return 0 # No more water to leak
return -k * np.sqrt(h)
h0 = 3.0 # Start with 3 meters of water
hours = 5
t_span = (0, hours * 3600) # 0 to 1.5 hours in seconds
t_eval = np.linspace(0, hours * 3600, 1000)
solution = solve_ivp( #3
leaking_tank, t_span, [h0], t_eval=t_eval, method="RK45",
dense_output=True
)
The listing calculates k from the tank and opening dimensions, defines the ODE, and integrates it from a specified starting height. section 9.2.2
The same drainage question can be modeled in OpenModelica by defining a tank, its outlet, the initial level, and the connected components. This component-based approach avoids importing an integration solver or specifying time steps in the model. Check initial conditions before comparing runs: the Python example starts at 3 m, while the shown Modelica tank starts at 2 m. section 9.2.2 section 9.3.2
Listing 9.2 Modelica tank model with 1 cm outlet hole
model WaterTankDrainage
import Modelica.Constants.pi;
inner Modelica.Fluid.System system #1
annotation (Placement(transformation(extent={{60,60},{80,80}})));
Modelica.Fluid.Vessels.OpenTank tank( #2
redeclare package Medium = Modelica.Media.Water.
ConstantPropertyLiquidWater,
crossArea = pi * (1.5/2)^2, // 1.767 m²
height = 3.0, // Max tank height
level_start = 2.0, // Initial water level
nPorts = 1, // Number of ports
portsData = {Modelica.Fluid.Vessels.BaseClasses.VesselPortsData(
diameter = 0.01, // Hole diameter
height = 0.0)}) // Port at bottom of tank
annotation (Placement(transformation(extent={{-20,20},{20,60}})));
Modelica.Fluid.Sources.Boundary_pT atmosphere( #3
redeclare package Medium = Modelica.Media.Water.
ConstantPropertyLiquidWater,
nPorts = 1,
p = 101325, // Atmospheric pressure (Pa)
T = 293.15) // Temperature (K)
annotation (Placement(transformation(extent={{60,-20},{40,0}})));
equation
connect(tank.ports[1], atmosphere.ports[1]) #4
annotation (Line(points={{0,20},{0,-10},{50,-10}}, color={0,127,255}));
end WaterTankDrainage;
The Modelica listing shows how tank geometry, a bottom outlet, and a starting level are specified as model parameters. section 9.3.2
