Skip to main content

Pool Temperature Physics

·2618 words·13 mins
By 
DrProton
Table of Contents
Backyard Data Science - This article is part of a series.
Part 3: This Article

The previous posts in this series covered acquiring and cross-checking some temperature and weather data that pertained to my backyard swimming pool. You might say those posts established a pool data data pool. [Sorry…]. In this post, I will use that data to help build a physical and mathematical model of the heat flows into and out of my backyard pool.

The Players

Just from what I know about physics (and backed up by AI and some web searches), I would expect several different physical effects to contribute to how much heat is gained or lost by the pool, i.e., the heat flux \(Q\) into or out of the pool.1 Let’s measure \(Q\) in ‘standard’ units - watts per square meter of pool surface [W/m^2]. A watt is a measure of energy flow per unit time; it is a joule per second. \(Q\) will be positive when heat energy is flowing into the pool and negative when heat is leaving.

Solar Heating

During the day, sunlight hitting the pool warms it up. I have hour-by-hour data from Visual Crossing for the solar heat flux that is being delivered to each square meter of flat, unshaded ground near me. But not all of the solar heat energy being delivered is reaching and being absorbed by the pool. Let’s call the fraction that does reach the pool \(\alpha_{solar}\). There are at least two reasons that \(\alpha_{solar}\) would be less than 1.0:

  1. The pool is partially in shade, mostly during the morning and late afternoon, and

  2. When the pool is not shaded, some amount of the solar energy is being reflected away instead of being absorbed.

The second factor is rather small (probably ~5%), but the first factor is larger and complicated because houses, trees, and fences cast shadows onto the pool that vary throughout the day. While I could try to get sophisticated and model the fraction of the pool that is in shade as a function of the time of day, I decided just to lump that complexity into \(\alpha_{solar}\) by making it a ‘daily average’. My guess is that, over the course of a day, probably about half of the sunlight that would fall on a completely flat, unshaded stretch of ground actually reaches and is absorbed by the pool.

So the heat flux reaching the pool is

\[ Q_{solar} = \alpha_{solar} \cdot Q_{VCsolar} \]

where \(Q_{VCsolar}\) is the measured solar heat flux from Visual Crossing and \(\alpha_{solar}\) was estimated at 0.5. The solar heat flux from Visual Crossing was already in our preferred units of Watts per square meter.

Convection

If the pool water temperature is different from the air temperature, heat will flow in or out of the pool just from convection at the pool surface. Wind will result in more convection. Using a common empirical relation for this dependence on wind (called the McAdams convective heat loss approximation):

\[ Q_{conv} = ( 5.7 + 3.8 \cdot v) \cdot (T_{air} - T_{pool}) \]

where \(v\) is the wind speed at the surface of the pool in m/s and the pool and air temperatures are in degC. If the pool temperature is less than the air temperature, \(Q_{conv}\) will be positive, which means that heat is flowing into the pool from the air.

Longwave Cooling

All objects at a temperature above absolute zero radiate heat energy, in the form of infrared radiation at the temperature ranges that concern us. According to Boltzmann’s equation, the amount of heat energy radiated is a strong function of temperature. So the pool surface is radiating away heat according to its temperature, but it is also absorbing infrared radiation from the sky and clouds. So the important thing is the difference between the amount of radiative heat being emitted from the pool and the amount being absorbed from the sky. This difference tends to be negative and largest at night, especially on cloudless nights. In meteorological circles, this is known as longwave cooling. A good estimate for this is

\[ Q_{\text{longwave}} = \sigma \cdot \left[\varepsilon_{\text{sky}}\cdot T_{\text{air}}^4 - \varepsilon_{\text{pool}} \cdot T_{\text{pool}}^4\right] \]

where

  • \(T_{\text{air}}\) and \(T_{\text{pool}}\) are the air and pool temperatures, in Kelvin

  • \(\varepsilon_{\text{sky}} = 0.741 + 0.0062 \cdot T_{\text{air,°C}} \cdot (1 + 0.17 \cdot f_{\text{cloud}}^2)\) is a commonly-used formula for the emissivity of the sky, where \(f_{cloud}\) is the cloud cover fraction, for air temperature in degC.

  • \(\varepsilon_{\text{pool}} = 0.96\) is the emissivity of the pool surface

  • \(\sigma = 5.67 \times 10^{-8}\) is the Stefan-Boltzmann constant

So the amount of longwave cooling for the pool is a function of air and pool temperatures and cloud cover.

Evaporative Cooling

As mentioned in the previous post, the rate of evaporation of water from a pool surface \(R_{evap}\) can be estimated as a function of wind, water and air temperatures, barometric pressure, and relative humidity in the air. This amount of evaporation can be converted to a heat flux by multiplying by the latent heat of evaporation for water:

\(L_v \approx 8100\) BTUs/gallon = \(2.26 \times 10^{6}\) J/kg

This is a large number; it takes a large amount of heat energy to turn water from a liquid to a vapor. In the previous post, on the worst days the pool would lose about 233 gallons per day. This means evaporation was removing

\(233 \cdot 8100 / 24 \approx 88,000\) BTUs/hr

from the pool. This is a fair fraction of what my pool heater can deliver into the pool. With the estimated thermal mass of the pool from a previous post (about 200,000 BTUs/degF), that means that the pool is cooling by about 0.44 degF/hr from evaporative cooling alone on the worst days.

So,

\[ Q_{evap} = - L_v \cdot R_{evap}\]

where \(R_{evap}\) is the evaporation rate as calculated in the previous post, and all the appropriate unit conversions are done to get the heat flux to be in Watts per square meter. Evaporative cooling can only remove heat from the pool, so \(Q_{evap}\) will be negative.

Ignored Factors

There are at least two other heat flows that probably affect the pool to some degree, but that have been ignored here:

  1. Conduction through the walls of the pool between the pool water and the surrounding ground. I did not have a good way to measure or obtain ground temperature data, so I excluded this from the analysis. The concrete and plaster in the pool walls are probably poor conductors of heat, so I would not expect this to be a major factor.

  2. Because of evaporation, water has to occasionally be added to the pool when the water level gets too low. This involves turning on a valve for 30-60 minutes and letting tap water flow into the pool. I expect that the incoming water would be at a different temperature than the pool water, and so adding water is also adding or removing heat to/from the pool. But I did not keep track of when water was added to the pool, so I can’t really analyze this. There are no obvious blips in temperature that I can attribute to adding pool water, so this probably is not a big effect either.

The Physics Model

So the model of heat flux of the pool is the sum of the fluxes from each contributing factor:

\[Q_{total} = Q_{solar} + Q_{conv} + Q_{longwave} + Q_{evap}\]

Which is a function of pool and air temperatures, wind speed, solar radiation, humidity, barometric pressure, and cloud cover.

Since the heat flux is per square meter of pool surface, multiply by the surface area to get the total heat energy entering or leaving the pool. And from a previous post, the thermal mass \(m_{th}\) of the pool is about 200,000 BTUs/degF. That is how much heat energy has to be added or removed from the entire pool to raise the pool temperature by 1 degF. So, putting it together, the \(\frac{dT}{dt}\) expected for a given total heat flux is

\[ \frac{dT}{dt} = \frac{Q_{total} \cdot A}{m_{th}} \approx Q_{total} \cdot 1.29 \times 10^{-7}\]

degF/hr

where \(A \approx 49\) square meters is the surface area of the pool, \(m_{th}\) is the thermal mass of the pool, and all the appropriate unit conversions have been done. This equation can be used in both directions - to convert a pool \(\frac{dT}{dt}\) to a heat flux, or a heat flux to a \(\frac{dT}{dt}\).

Enter the Data

As pointed out in the first post in this series, the pool temperature data and therefore the pool \(\frac{dT}{dt}\) data had a few glitches. I did spend some effort coming up with some elaborate mechanisms to flag and remove these glitches from the data, but for simplicity I decided to just use a span of data from Oct 25 to Dec 16 2025 that did not contain any major glitches. So the analysis from here on out will just use this range of contiguous data.

Here then is the observed \(\frac{dT}{dt}\) for the pool, converted to an observed heat flux, plotted along with the \(Q_{total}\) heat flux calculated from the weather conditions:

Calculated and observed heat flux

If our model were perfect, the lines would be on top of each other. They are not. The calculated model underestimates both the amount of heating during the day and especially the amount of cooling at night. This plot shows the same calculated heat flux, but also plots the contributions to the calculated heat flux from each of the individual terms in \(Q_{total}\), zoomed in to a stretch of time where the discrepancy was large:

Calculated heat flux contributors, with observed heat flux

From this plot, we can see that \(Q_{evap}\) is too large (and negative) on some days, and those days turn out to be particularly windy days. So the model is overestimating the evaporative heat losses from the pool. And this is after we have already tried to account for nearby obstructions reducing the measured wind speed near the surface of the pool by introducing the estimated wind-shielding factor \(\alpha_{wind} = 0.3\) from the previous post.

Predicting Pool Temperature

We can take the calculated total heat flux from the model and convert it to a calculated \(\frac{dT}{dt}\), which is plotted here:

Calculated and observed dT/dt

which looks identical to the above plot of the heat flux; only the vertical scale has changed. And from this calculated hour-by-hour \(\frac{dT}{dt}\), we can calculate an hour-by-hour pool temperature just by ‘integrating’ the \(\frac{dT}{dt}\) at each step. That is, the temperature at hour \(i+1\) is just the temperature at hour \(i\) plus the \(\frac{dT}{dt}\) at hour \(i\). We start this procedure with a measured starting pool temperature.

Here is the reconstructed pool temperature from the calculated \(\frac{dT}{dt}\), starting with the measured pool temperature on Oct 25:

Calculated and observed pool temperature

Obviously this is a pretty bad prediction. The prediction matched the observed temperature at the start because we forced it to match, but then the prediction got worse and worse. This happened because the calculation systematically under-predicted the heat gain during the day and over-predicted the heat loss at night, so the calculated pool temperature just kept dropping. Any errors in the predicted \(\frac{dT}{dt}\) just kept compounding since they were always wrong in the same direction.

We can compute some numbers showing just how bad the model is at predicting temperatures:

Metric Value
RMSE 76.87 °F
MAE 68.54 °F

Focus on the RMSE, which is a measure of the mismatch between the predicted and observed pool temperature. Smaller numbers are better. The prediction was off from the measured temperature by about 77 degF on average (in some sense) over the course of the simulation. MAE is a similar measure of mismatch that perhaps softens the influence of data outliers a bit.

Fitting the model

So the model so far is terrible. I spent considerable effort looking for errors in the calculations, but found no smoking guns. But recall that along the way I had to estimate a few things, like the \(\alpha_{solar} = 0.5\) solar-radiation absorption reduction (because the pool is sometimes shaded during the day, and it reflects some sunlight) and the wind-shielding factor \(\alpha_{wind} = 0.3\). Because these are just estimates, they could be off. Or there could be systematic differences between the weather data supplied by Visual Crossing and the actual weather conditions near the pool. Or perhaps the ignored factors mentioned above are more important than I thought.

We can acknowledge these model inaccuracies by building some term-by-term ‘fudge factors’ into our heat-flux model. Each component gets a multiplicative factor to scale it appropriately:

\[ Q_{total} = C_{solar} \cdot Q_{solar} + C_{conv} \cdot Q_{conv} + C_{longwave} \cdot Q_{longwave} + C_{evap} \cdot Q_{evap} \]

Then we can ’tune’ all these coefficients so that the calculated heat flux more closely matches the observed heat flux. We are fitting the model to the observed data. We can do this because each of the factors has a different dependence on the weather variables - we can find the fit coefficients that produce the best match between the calculated and observed heat flux given the weather variables. For this fit, the SciPy minimize function was used. It works by searching for a minimum RMSE between the calculated and observed heat fluxes. The fit coefficients it found were:

Coefficient Value
\(C_{solar}\) 1.1054
\(C_{evap}\) 0.47
\(C_{conv}\) 0.49
\(C_{longwave}\) 0.58

Note what these numbers are saying: making the solar heat flux larger by about 10% produced the best fit to the observed heat flux data. This could be because the estimated \(\alpha_{solar}\) was too small. Likewise, reducing the other contributors by about half produced the best fit. Here is the plot showing how the scaled optimized flux components combine together to the total flux, for the same problematic dates plotted above:

Optimized heat flux contributors

Note how the reduced evaporative flux in particular brought the calculated total flux more in line with the observed flux.

Fitting the model to the data produced a much better match to the observed \(\frac{dT}{dt}\):

Modeled and observed dT/dt

And using the fitted model to predict the pool temperature likewise did much better:

Predicted temperature from the optimized physics model

Calculating the same metrics, they now show much better agreement:

Metric Value
RMSE 2.06 °F
MAE 1.79 °F

as the fitting decreased the RMSE measure from about 77 degF down to about 2 degF.

Astute readers may be saying: Waitaminute - one of the inputs used in calculating the heat flux was the pool temperature itself. You fed the model ’the answer’ as an input! No wonder the model worked well. Well, yes and no. The model is designed to calculate heat flux, and it makes good sense for the heat flux to depend on the current pool temperature; e.g., warmer water evaporates more than cooler water. The calculated heat flux is converted to \(\frac{dT}{dt}\), and that \(\frac{dT}{dt}\) is used to predict temperature, hour by hour. So at each hour, the pool temperature is only used to figure out how much the pool temperature will change for the next hour. Using the pool temperature as an input would be a clear foul if the model were designed to predict temperature instead of \(\frac{dT}{dt}\).

Summary

So by adding coefficients to our physical model and fitting them to the observed data, we were able to reproduce the observed pool temperature data quite well. It is difficult to determine exactly why the calculated model without fitting to the observed data was so bad at predicting the pool temperature. It probably had to do with the ‘microclimate’ near the pool not being accurately represented by the weather variables obtained from Visual Crossing.

Are there better ways to model the thermodynamics of my pool? Stay tuned…


  1. Notice how using the word ‘flux’ instead of just ‘flow’ or ‘change’ makes things sound seriously scientific. Don’t be fooled. ↩︎

Author
DrProton
Retired software developer, ex-physicist, wannabe musician and lifelong learner.

Tags

Projects Coding Technology Physics Atmospheric Science Computational Physics Cooling Data Analysis Physics Simulation Python
Backyard Data Science - This article is part of a series.
Part 3: This Article

Related

Weather Data Science
·2228 words·11 mins
By 
DrProton
Projects Coding Technology Physics
Backyard Data Science
·3017 words·15 mins
By 
DrProton
Projects Coding Technology Physics
Stretching a Rope
·1248 words·6 mins
By 
DrProton
Coding Projects Physics AI
The Earth Moved, And So Did the Code (to the Web!)
··1980 words·10 mins
By 
DrProton
Coding Projects Physics
Simulating Retirement
·3471 words·17 mins
By 
DrProton
Retirement Coding Projects