Soil Temperatures on the Moon

Model Parameters

Component Fluxes and Temperatures

Temperature vs Depth


This is a very simple model of soil temperatures on the Moon’s Equator, intended to be used for teaching and learning about weather and climate. It’s just one-dimensional heat transfer through the soil forced by incoming solar radiation that heats the soil and outgoing thermal radiation that cools it at the surface.

The Moon is “tidally locked” to the Earth so that the time required to rotate once on its axis is exactly the same as the period of its orbit around the Earth (that is, the length of the Moon’s “day” is the same as the length of a Lunar “month:” 29 Earth days, 12 hours, 44 minutes).

The Moon has no atmosphere, and the “tilt” of its axis relative to the orbital plane of the Earth-Moon system around the Sun is negligible. Therefore the calculation of insolation at the Lunar surface is much simpler than the one you did for the Earth in problem 1 above. Assuming that the tilt of the Moon’s axis is zero, declination = 0. So at the Moon’s equator the cosine of the solar zenith angle is just equal to the cosine of the hour angle. Much simpler than the Earth!

On Earth the surface energy budget is given by Bonan’s equation 13.1:

\[ \begin{aligned} \rho c \frac{\Delta T}{\Delta t}\Delta z = (S\downarrow - S\uparrow + L\downarrow - L\uparrow) - H - \lambda E = G \end{aligned} \]

On the left-hand side, \(\rho\) is the density of the soil (kg m-3), \(c\) is the heat capacity of the soil (J m-3 K-1), \(\Delta T\) is the change in soil temperature (Kelvin) over a time step (\(\Delta t\), seconds) for each soil layer whose thickness is \(\Delta z\) (meters). On the right-hand side the four terms inside the parentheses are the fluxes of shortwave radiation down and up, and longwave radiation down and up. \(H\) is the turbulent flux of sensible heat, \(\lambda\) is the latent heat of evaporation of water, \(E\) is the rate of evaporation of water from the surface, and \(G\) is the change in ground heat storage. All terms in this equation have the units of W m-2.

The Moon’s surface energy budget is much simpler! There’s no water to evaporate and no turbulent air to carry sensible heat. There’s a little bit of longwave radiation emitted by the Earth that heats the Moon’s surface, but on the far side (which we never see) this is zero so we can neglect this. All of the upward flux of shortwave radiation is just the incoming solar radiation times the Moon’s albedo. To calculate upward longwave emission we can assume the surface emits as a blackbody. So the Moon’s surface energy budget is

\[ \begin{aligned} \rho c \frac{\Delta T}{\Delta t}\Delta z = S\downarrow (1-albedo) - \sigma T_S^4 \end{aligned} \]

In visible wavelengths, the Moon is surprisingly dark, with an albedo of just 7% (like asphalt!). Across the whole Solar spectrum it’s a little brighter, so please use the broadband albedo of 11%.

To calculate the temperature, divide the Lunar “soil” into 15 layers. Make the top layer thinnest (0.02 m = 2 cm), and then make each layer underneath 1.5 times as thick as the one above. This will get you 15 lunar soil temperatures down to about 17.5 meters depth.

The downward flux of heat at the top of each layer is proportional to the difference in temperature between adjacent layers according to Bonan’s equation 9.1:

\[ \begin{aligned} \rho c \frac{\Delta T}{\Delta t}\Delta z = -k \frac{\Delta T}{\Delta z} \end{aligned} \]

The proportionality constant \(k\) is called the “thermal conductivity.” The negative sign is there because the layers are counted down from the surface but the heat flux is defined as positive upward, away from the surface.

Changes in soil temperature are determined by the difference in the heat flux into and out of each layer. This is called the “heat flux divergence,” given by Bonan’s equation 9.2:

\[ \begin{aligned} F \uparrow = -k \frac{\Delta T}{\Delta z} = -(F_Z - F_{z+\Delta z}) \end{aligned} \]

There’s a nice worked example of this on Bonan page 133. The book also gives typical values of heat capacity and thermal conductivity for different kinds of soil.

The Lunar “soil” is actually made up of mineral grains blasted to smithereens by billions of years of impact bombardment. Unlike Earth soils, the pore space is not filled with water or air. It’s just vacuum, so the heat flow through the lunar soil is much slower than on Earth, depending on conductance and thermal radiation between adjacent grains!

According to Colozza (1991), the volumetric heat capacity of lunar regolith is about 4.4 million J m-3 K-1. Use this value for \(ρc\) in the equation above. The thermal conductivity of lunar “soil” is only about 0.025 W m-1 K-1. This is about 100 times less conductive than soils on Earth!

To calculate the changes in temperature with depth and time you’ll need to add solar radiation to the surface, subtract upward longwave, and step the heat flux equations forward in time. You’ll find that if you use a time step that’s too long, the numerical solution will become unstable and you’ll get crazy values (for example negative temperatures Kelvin). I find I can get away with a time step of a few hours.

The program that does the calculation is very simple, and the code that controls this website is surprisingly simple too! It is all written in the programming language R. You can read all about it on the “Website Code” tab to the right.

How this website works (including all the code!)

This website is controlled using the R package “shiny.” There are four important components:

  • The lunar soil temperature model itself (moon.R)
  • A program that plots timeseries of the radiation, fluxes, and temps (plot.moon.R)
  • A program that plots soil temperatures as a funciton of depth and time (plot.soilT.image.R)
  • The user interface (ui.R) that controls the sliders and passes inputs to the model
  • The “server” (server.R) that responds to user interface events in the web browser

Scroll down or click links in the list above to read all about it!

This is the module that calculates soil temperatures on the Moon


moon <- function(time.step=3600, albedo=0.08){

  # Calculate temperatures in lunar soil ("regolith") for 10 lunar days  
  # resulting from diurnal cycles of solar heating and longwave cooling
  # Assume no tilt of lunar axis, ignore seasonal changes in Moon-Sun distance

  # A lunar day (or month!) is 29 Earth days plus 12.75 hours <- 24 * 29 + 12.75

  # Simulate 10 lunar days
  n.lunar.days <- 10

  # Figure out how many time steps to take to do n lunar days
  n.steps <- n.lunar.days * * 3600  / time.step

  # Parameters  
  heat.capacity <- 4.4e6 # (J/m3 per Kelvin, from Colozza 1991, pages 2-3)
  thermal.conductivity <- 0.025    # (W/m per Kelvin, also from Colozza 1991)    
  sigma <- 5.67e-8 # (W/m2/K4, Stefan-Boltzmann coefficient) 
  solar.constant <- 1367. # Watts/m2

  # Set depths and thicknesses of each soil layer
  n.layers <- 15
  dz <- rep(NA, n.layers)  # layer thickness (meters)
  depth <- dz
  dz[1] <- 0.02 # Top layer is thinnest (2 cm)
  depth[1] <- dz[1] / 2
  layer.mult <- 1.5
  for (L in 2:n.layers) {
    dz[L] <- dz[L-1] * layer.mult
    depth[L] <- sum(dz[1:(L-1)]) + dz[L] / 2

  # Set up arrays for later plotting
  empty <- rep(NA, n.steps-1) <- empty
  SW.down <- empty
  SW.up <- empty
  LW.down <- empty
  LW.up <- empty <- empty

  # Initial soil temperature
  soilT <- matrix(NA, nrow=n.layers, ncol=n.steps)  # array to hold soil temps (Kelvin)
  soilT[ , 1] <- 273.   # Initial soil temps = 298 K = 25 C

  # Array to hold heat fluxes
  flux <- matrix(NA, nrow=n.layers+1, ncol=n.steps-1)  # array to hold heat fluxes (W/m2)
  flux[ ,1] <- NA    # upward flux at top of layer L

  # Set elapsed time to zero at beginning of simulation
  elapsed <- 0.

  # Time step the model
  for (i in 1:(n.steps-1)) {

    # Calculate date and time (lunar solar time = LST)
    elapsed <- elapsed + time.step
    hours <- elapsed / 3600.[i] <- hours/
    day <- floor([i])
    LST <- hours - ( * day) 

    # Calculate position of Sun in Lunar sky
    hour.angle <- -pi + (LST / * 2 * pi
    cos.z <- max(cos(hour.angle), 0.)

    # Calculate SW gain using Bonan (Chapter 4)
    SW.down[i] <- solar.constant * cos.z  
    SW.up[i] <- SW.down[i] * albedo

    # Calculate LW using sky temp = 5 Kelvin
    LW.down[i] <- sigma * 5^4

    # Upward longwave from surface assumes blackbody (emissivity = 1)
    LW.up[i] <- sigma * soilT[1,i] ^ 4

    # Net radiation       [i] <- SW.down[i] - SW.up[i] + LW.down[i] - LW.up[i]      

    # Soil temperature 

    # Compute upward flux at top of each layer
    flux[1,i] <-[i]     # top flux boundary condition
    for (L in 2:n.layers){
      flux[L,i] <- -thermal.conductivity * (soilT[L-1,i] - soilT[L,i]) / ((dz[L-1]+dz[L])/2)
    flux[n.layers+1,i] <- 0.  # bottom flux boundary condition

    # Compute new temperatures for each layer
    for (L in 1:n.layers) {
      soilT[L,i+1]  <- soilT[L,i] - (flux[L,i] - flux[L+1,i]) * time.step /
                                         (heat.capacity * dz[L])


  return(list(SW.down=SW.down, SW.up=SW.up, LW.down=LW.down, LW.up=LW.up,,
              flux=flux, soilT=soilT[,1:(n.steps-1)],,

This is module that plots the timeseries of the radiation, fluxes, and temperatures


plot.moon <- function(output){
  # ladder plot of radiation, turbulent fluxes, and temperatures

  # Format is a "grid" of three timeseries panels stacked vertically on one page
  # Set plot margins
  bottomMargin <- 4
  leftMargin <- 5
  topMargin <- 1
  rightMargin <- .5

  orig.par <- par(no.readonly=TRUE)  # Remember changable parameters to reset later
  par(mfcol=c(3,1), mar=c(bottomMargin,leftMargin,topMargin,rightMargin),oma=c(0,2.,1.,0))



  # Top panel shows radiation components
  plot.min<- min(c(SW.down-SW.up, LW.down-LW.up,
  plot.max <- max(c(SW.down-SW.up, LW.down-LW.up,

  plot(, SW.down-SW.up, typ='l', col='yellow', lwd=2, ylim=c(plot.min, plot.max), 
       main='Radiation', ylab=expression(W ~~ m^-2), xlab='',
       cex.main=1.3, cex.lab=1.3, cex.axis=1.3)
  lines(, LW.down-LW.up, col='red',lwd=2)
  lines(,, col='black',lwd=2)

  legend('topright', c('SW','LW','Net'),
         col=c('darkgoldenrod2','red','black'), lwd=c(2,2,2))


  # Middle panel shows fluxes
  plot.min<- min(flux)
  plot.max <- max(flux) 

  colors <- c('darkred','darkorange2','darkgoldenrod2','green','blue')

  plot(, flux[1,], typ='l', col='red', lwd=2, ylim=c(-100,100), 
       main='Heat Fluxes', ylab=expression(W ~~ m^-2), xlab='',
       cex.main=1.3, cex.lab=1.3, cex.axis=1.3)
  for (L in 2:5) {
    lines(, flux[L,], col=colors[L],lwd=1)


  # Bottom panel shows temperatures
  plot.min <- min(soilT)
  plot.max <- max(soilT)

  plot(, soilT[1,], typ='l', col='red', lwd=2, ylim=c(plot.min, plot.max),
       main='Temperatures', ylab='Kelvin', xlab='Lunar Month', 
       cex.main=1.3, cex.lab=1.3, cex.axis=1.3)
  for (L in 2:5) {
    lines(, soilT[L,], col=colors[L],lwd=1)



This is module that plots the temperatures as a function of depth and time


plot.soilT.image <- function(output){

  # Make a color-filled contour plot of lunar soil temperature by depth and time

  library(fields)  # Need this to make the image.plot

  # Draw an image using colors to represent the insolation
  image.plot(output$,100*output$depth,t(output$soilT), ylim=c(50,0),
             xlab='Lunar Days', ylab='Depth (cm)',
             main='Lunar Soil Temperature (Kelvin)',
             cex.main=1.5, cex.axis=1.2, cex.lab=1.3)  

This is the user interface that actually controls this website



# Define UI for calculation of lunar temperatures

  #  Application title
  headerPanel("Soil Temperatures on the Moon"),

  # Sidebar with sliders that control various available options
    h4('Model Parameters'),            

    sliderInput("albedo", "Albedo (percent)", 
                min = 5, max = 30, value = 11, step=1),

    sliderInput("time.step", "Time Step (hours):", 
                min = 0.5, max = 12, value = 1, step=0.5)


   # Main panel consists of a tabset displays model output or model description
                h3("Component Fluxes and Temperatures"),
                tags$style(type="text/css", ".tab-content { overflow: visible; }")), 
                h3("Temperature vs Depth"),
                tags$style(type="text/css", ".tab-content { overflow: visible; }")), 
       tabPanel("Website Code",

Here is the server module that responds to user interface events



# Source required R scripts

shinyServer(function(input, output) {

  # time-depth plot of soil temperatures
  output$soil <- renderPlot(
    plot.soilT.image(moon(albedo=input$albedo/100, time.step=input$time.step*3600))

  # Stacked timeseries of component fluxes and temperatures
  output$fluxes <- renderPlot(
    plot.moon(moon(albedo=input$albedo/100, time.step=input$time.step*3600))