Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add capability for user-defined parameter sets, with an example (take 2) #210

Merged
merged 19 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dev/clima_formatter_options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ clima_formatter_options = (
whitespace_typedefs = true,
whitespace_ops_in_indices = true,
remove_extra_newlines = false,
ignore = ["examples"],
)
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
!docs/src/assets/*.svg
docs/build/
docs/site/
docs/src/literated/

# Deps
Manifest.toml

*.jld2
6 changes: 5 additions & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
[deps]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
RootSolvers = "7181ea78-2dcb-4de3-ab41-2b8ab5a31e74"
Thermodynamics = "b60c26fb-14c3-4610-9d3e-2d17fe7ff00c"

[compat]
CairoMakie = "0.11"
Documenter = "1"
julia = "1.7"
65 changes: 63 additions & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,53 @@
using Thermodynamics, Documenter
using DocumenterCitations
using Thermodynamics
using Documenter, DocumenterCitations, Literate, Printf

# https://github.com/jheinen/GR.jl/issues/278#issuecomment-587090846
ENV["GKSwstype"] = "nul"

bib = CitationBibliography(joinpath(@__DIR__, "bibliography.bib"))

const EXAMPLES_DIR = joinpath(@__DIR__, "..", "examples")
const OUTPUT_DIR = joinpath(@__DIR__, "src/literated")

example_scripts = ["density_from_temperature_pressure_humidity.jl"]

if isdir(OUTPUT_DIR)
rm(OUTPUT_DIR)
end

mkdir(OUTPUT_DIR)

cp(
joinpath(EXAMPLES_DIR, "JRA55_atmospheric_state_Jan_1_1991.jld2"),
joinpath(OUTPUT_DIR, "JRA55_atmospheric_state_Jan_1_1991.jld2"),
)

for example in example_scripts
example_filepath = joinpath(EXAMPLES_DIR, example)

withenv("JULIA_DEBUG" => "Literate") do
start_time = time_ns()
Literate.markdown(
example_filepath,
OUTPUT_DIR;
flavor = Literate.DocumenterFlavor(),
execute = true,
)
elapsed = 1e-9 * (time_ns() - start_time)
@info @sprintf("%s example took %s seconds to build.", example, elapsed)
end
end

example_pages = [
"Density from temperature, pressure, and humidity" => "literated/density_from_temperature_pressure_humidity.md",
]

pages = Any[
"Home" => "index.md",
"Installation" => "Installation.md",
"API" => "API.md",
"How-to-guide" => "HowToGuide.md",
"Examples" => example_pages,
"Tested profiles" => "TestedProfiles.md",
"Temperature profiles" => "TemperatureProfiles.md",
"Developer docs" => "DevDocs.md",
Expand All @@ -28,10 +65,13 @@ mathengine = MathJax(
),
)

const MiB = 2^20

format = Documenter.HTML(
prettyurls = get(ENV, "CI", nothing) == "true",
mathengine = mathengine,
collapselevel = 1,
size_threshold = 2MiB,
)

makedocs(;
Expand All @@ -45,6 +85,27 @@ makedocs(;
pages = pages,
)

@info "Clean up temporary .jld2 and .nc output created by doctests or literated examples..."

"""
recursive_find(directory, pattern)

Return list of filepaths within `directory` that contains the `pattern::Regex`.
"""
recursive_find(directory, pattern) =
mapreduce(vcat, walkdir(directory)) do (root, dirs, files)
joinpath.(root, filter(contains(pattern), files))
end

files = []
for pattern in [r"\.jld2", r"\.nc"]
global files = vcat(files, recursive_find(@__DIR__, pattern))
end

for file in files
rm(file)
end

deploydocs(
repo = "github.com/CliMA/Thermodynamics.jl.git",
target = "build",
Expand Down
Binary file added examples/JRA55_atmospheric_state_Jan_1_1991.jld2
Binary file not shown.
150 changes: 150 additions & 0 deletions examples/density_from_temperature_pressure_humidity.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# # Defining a simple parameter set and using it to compute density
#
# This script shows how to define a simple parameter set, and then using it to
# compute density as a function of pressure, temperature, and humidity.

# # Define parameters for computing the density of air
#
# First, we build a parameter set suitable for computing the density of
# _moist_ air --- that is, a mixture of dry air, water vapor, liquid droplets,
# and ice crystals (the latter two are called "condensates").

using Thermodynamics
using Thermodynamics.Parameters: AbstractThermodynamicsParameters

struct ConstitutiveParameters{FT} <: AbstractThermodynamicsParameters{FT}
gas_constant::FT
dry_air_molar_mass::FT
water_molar_mass::FT
end

"""
ConstitutiveParameters(FT; gas_constant = 8.3144598,
dry_air_molar_mass = 0.02897,
water_molar_mass = 0.018015)

Construct a set of parameters that define the density of moist air,
```math
ρ = p / Rᵐ(q) T,
```
where ``p`` is pressure, ``T`` is temperature, ``q`` defines the partition
of total mass into vapor, liqiud, and ice mass fractions, and
``Rᵐ`` is the effective specific gas constant for the mixture,
```math
Rᵐ(q) =
```
where
For more information see [reference docs].
"""
function ConstitutiveParameters(
FT = Float64;
gas_constant = 8.3144598,
dry_air_molar_mass = 0.02897,
water_molar_mass = 0.018015,
)

return ConstitutiveParameters(
convert(FT, gas_constant),
convert(FT, dry_air_molar_mass),
convert(FT, water_molar_mass),
)
end

# Next, we define functions that return:
# 1. The specific gas constant for dry air
# 2. The specific gas constant for water vapor
# 3. The ratio between the dry air molar mass and water molar mass

const VTP = ConstitutiveParameters
using Thermodynamics: Parameters

Parameters.R_d(p::VTP) = p.gas_constant / p.dry_air_molar_mass
Parameters.R_v(p::VTP) = p.gas_constant / p.water_molar_mass
Parameters.molmass_ratio(p::VTP) = p.dry_air_molar_mass / p.water_molar_mass

# # The density of dry air

import Thermodynamics as AtmosphericThermodynamics

# To compute the density of dry air, we first build a default
# parameter set,

parameters = ConstitutiveParameters()

# Next, we construct a phase partitioning representing dry air --- the trivial
# case where the all mass ratios are 0.
#
# Note that the syntax for `PhasePartition` is
#
# ```
# PhasePartition(q_total, q_liquid, q_ice)
# ```
# where the q's are mass fractions of total water components,
# liquid droplet condensate, and ice crystal condensate.

q_dry = AtmosphericThermodynamics.PhasePartition(0.0)

# Finally, we define the pressure and temperature, which constitute
# the "state" of our atmosphere,

p₀ = 101325.0 # sea level pressure in Pascals (here taken to be mean sea level pressure)
T₀ = 273.15 # temperature in Kelvin

# Note that the above must be defined with the same float point precision as
# used for the parameters and PhasePartition.
# We're now ready to compute the density of dry air,

ρ = air_density(parameters, T₀, p₀, q_dry)

@show ρ

# Note that the above must be defined with the same float point precision as
# used for the parameters and PhasePartition.
# We're now ready to compute the density of dry air,

using JLD2

# Next, we load an atmospheric state correpsonding to atmospheric surface
# variables substampled from the JRA55 dataset, from the date Jan 1, 1991:

@load "JRA55_atmospheric_state_Jan_1_1991.jld2" q T p

# The variables `q`, `T`, and `p` correspond to the total specific humidity
# (a mass fraction), temperature (Kelvin), and sea level pressure (Pa)
#
# We use `q` to build a vector of PhasePartition,

qp = PhasePartition.(q)

# And then compute the density using the same parameters as before:

ρ = air_density.(parameters, T, p, qp)

# Finally, we plot the density as a function of temperature and specific humidity,

using CairoMakie

## Pressure range, centered around the mean sea level pressure defined above
pmax = maximum(abs, p)
dp = 3 / 4 * (pmax - p₀)
prange = (p₀ - dp, p₀ + dp)
pmap = :balance

## Compute temperature range
Tmin = minimum(T)
Tmax = maximum(T)
Trange = (Tmin, Tmax)
Tmap = :viridis

fig = Figure(size = (1000, 450))

axρ = Axis(fig[2, 1], xlabel = "Temperature (K) ", ylabel = "Density (kg m⁻³)")
axq = Axis(fig[2, 2], xlabel = "Specific humidity", ylabel = "Density (kg m⁻³)")

scatter!(axρ, T[:], ρ[:], color = p[:], colorrange = prange, colormap = pmap, alpha = 0.1)
scatter!(axq, q[:], ρ[:], color = T[:], colorrange = Trange, colormap = Tmap, alpha = 0.1)

Colorbar(fig[1, 1], label = "Pressure (Pa)", vertical = false, colorrange = prange, colormap = pmap)
Colorbar(fig[1, 2], label = "Temperature (K)", vertical = false, colorrange = Trange, colormap = Tmap)

current_figure()
12 changes: 7 additions & 5 deletions src/Parameters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module Parameters

export ThermodynamicsParameters

abstract type AbstractThermodynamicsParameters{FT} end
const ATP = AbstractThermodynamicsParameters

"""
ThermodynamicsParameters

Expand All @@ -21,7 +24,8 @@ param_set = TP.ThermodynamicsParameters(toml_dict)

```
"""
Base.@kwdef struct ThermodynamicsParameters{FT}
Base.@kwdef struct ThermodynamicsParameters{FT} <:
AbstractThermodynamicsParameters{FT}
T_0::FT
MSLP::FT
p_ref_theta::FT
Expand Down Expand Up @@ -50,14 +54,12 @@ Base.@kwdef struct ThermodynamicsParameters{FT}
pow_icenuc::FT
end

const ATP = ThermodynamicsParameters

Base.broadcastable(ps::ATP) = tuple(ps)
Base.eltype(::ThermodynamicsParameters{FT}) where {FT} = FT

# wrappers
for fn in fieldnames(ATP)
@eval @inline $(fn)(ps::ATP) = ps.$(fn)
for fn in fieldnames(ThermodynamicsParameters)
@eval $(fn)(ps::ThermodynamicsParameters) = ps.$(fn)
end

# Derived parameters
Expand Down
2 changes: 1 addition & 1 deletion src/TemperatureProfiles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export TemperatureProfile,

import ..Parameters
const TP = Parameters
const APS = TP.ThermodynamicsParameters
const APS = TP.AbstractThermodynamicsParameters

"""
TemperatureProfile
Expand Down
2 changes: 1 addition & 1 deletion src/Thermodynamics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const KA = KernelAbstractions
include("Parameters.jl")
import .Parameters
const TP = Parameters
const APS = TP.ThermodynamicsParameters
const APS = TP.AbstractThermodynamicsParameters

# Allow users to skip error on non-convergence
# by importing:
Expand Down
22 changes: 10 additions & 12 deletions src/relations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1022,12 +1022,11 @@ end

Compute the saturation specific humidity over a plane surface of condensate, given

- `param_set` an `AbstractParameterSet`, see the [`Thermodynamics`](@ref) for more details
- `T` temperature
- `ρ` (moist-)air density
and, optionally,
- `Liquid()` indicating condensate is liquid
- `Ice()` indicating condensate is ice
- `param_set`: an `AbstractParameterSet`, see the [`Thermodynamics`](@ref) for more details
- `T`: temperature
- `ρ`: air density
- (optional) `Liquid()`: indicating condensate is liquid
- (optional) `Ice()`: indicating condensate is ice
"""
@inline function q_vap_saturation_generic(
param_set::APS,
Expand All @@ -1046,12 +1045,11 @@ q_vap_saturation_generic(param_set::APS, T, ρ, phase::Phase) =

Compute the saturation specific humidity, given

- `param_set` an `AbstractParameterSet`, see the [`Thermodynamics`](@ref) for more details
- `T` temperature
- `ρ` (moist-)air density
- `phase_type` a thermodynamic state type
and, optionally,
- `q` [`PhasePartition`](@ref)
- `param_set`: an `AbstractParameterSet`, see the [`Thermodynamics`](@ref) for more details
- `T`: temperature
- `ρ`: air density
- `phase_type`: a thermodynamic state type
- (optional) `q`: [`PhasePartition`](@ref)

If the `PhasePartition` `q` is given, the saturation specific humidity is that of a
mixture of liquid and ice, computed in a thermodynamically consistent way from the
Expand Down
Loading