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

Fixes to chemistry following thermo changes #54

Merged
merged 9 commits into from
Jul 1, 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
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors:
given-names: "Harrison"
orcid: "https://orcid.org/0000-0002-8368-4641"
title: "AGNI"
version: 0.5.1
version: 0.5.2
doi: 10.xx/xx.xx
date-released: 2024-06-25
url: "https://github.com/nichollsh/AGNI"
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "AGNI"
uuid = "ede838c1-9ec3-4ebe-8ae8-da4091b3f21c"
authors = ["Harrison Nicholls <[email protected]>"]
version = "0.5.1"
version = "0.5.2"

[deps]
ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63"
Expand Down
86 changes: 43 additions & 43 deletions res/config/chemistry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,58 @@
title = "Equilibrium chemistry demo" # Name for this configuration file

[planet]
tmp_surf = 2200.0 # Surface temperature [kelvin]
instellation = 44000.0 # Stellar flux at planet's orbital distance [W m-2]
albedo_b = 0.18 # Pseudo bond-albedo which downscales the stellar flux by 1-this_value
s0_fact = 0.375 # Stellar flux scale factor which accounts for planetary rotation (c.f. Cronin+13)
zenith_angle = 48.19 # Characteristic zenith angle for incoming stellar radiation [degrees]
albedo_s = 0.0 # Surface albedo
radius = 6.37e6 # Planet radius at the surface [m]
gravity = 9.81 # Gravitational acceleration at the surface [m s-2]
p_surf = 270.0 # Total surface pressure [bar]
p_top = 1e-5 # Total top-of-atmosphere pressure [bar]
vmr = { H2O = 0.6, CO2=0.1, N2=0.1, SO2=0.1, O2=0.05, Fe=0.05} # Volatile volume mixing ratios
tmp_surf = 2200.0
instellation = 10000.0
albedo_b = 0.0
s0_fact = 0.375
zenith_angle = 48.19
albedo_s = 0.0
radius = 6.37e6
gravity = 9.81
p_surf = 270.0
p_top = 1e-5
vmr = { H2O = 0.3, CO2=0.1, N2=0.1, SO2=0.05, O2=0.05, FeO2=0.05, OH=0.0, H=0.0, CH4=0.1, CO=0.5, H2=0.5, SO=0.0, NH3=0.0, NH2=0.0, H2S=0.0, H2SO4=0.0} # Volatile volume mixing ratios
condensates = []
tmp_int = 0.0 # Effective temperature

tmp_int = 0.0
[files]
clean_output = true
input_sf = "res/spectral_files/Dayspring/48/Dayspring.sf" # Path to SOCRATES spectral file.
input_sf = "res/spectral_files/nogit/Dayspring/48/Dayspring.sf" # Path to SOCRATES spectral file.
input_star = "res/stellar_spectra/sun.txt" # Path to stellar spectrum.
output_dir = "out/" # Path to output directory.
fastchem_path = "/Users/nichollsh/Projects/fastchem/"

[execution]
verbosity = 1 # Log level (0: none, 1: normal, 2: debug)
max_steps = 200 # Maximum number of solver steps.
max_runtime = 400 # Maximum wall-clock solver runtime [s].
num_levels = 40 # Number of model levels.
continua = true # Include absorption from continua?
rayleigh = false # Include rayleigh scattering?
cloud = false # Include water cloud radiative properties?
aerosol = false # Include aerosol radiative properties?
overlap_method = 4 # Method for treating line overlap.
thermo_funct = false # Use temperature-dependent thermodynamic properties?
sensible_heat = false # Include sensible heat transport at the surface?
latent_heat = false
convection_type = "mlt" # Convection type ("" = no convection, "mlt" = use mixing length theory).
chemistry = 1 # Chemistry type (see wiki)
solution_type = 3 # Solution type (see wiki).
solvers = ["newton"] # Ordered list of solvers to apply (see wiki).
dx_max = 200.0 # Maximum step size [Kelvin], when using nonlinear solvers
initial_state = ["iso","2000"] # Ordered list of requests describing the initial state of the atmosphere (see wiki).
linesearch = false # Use linesearch?
converge_atol = 1.0e-2 # Convergence criterion on absolute flux lost [W m-2].
converge_rtol = 1.0e-4 # Convergence criterion on relative flux lost [dimensionless].
verbosity = 1
max_steps = 200
max_runtime = 400
num_levels = 45
continua = true
rayleigh = true
cloud = false
aerosol = false
overlap_method = 4
thermo_funct = true
sensible_heat = false
latent_heat = false
convection_type = "mlt"
chemistry = 1
solution_type = 3
solvers = ["newton"]
dx_max = 200.0
initial_state = ["iso","2000"]
linesearch = false
converge_atol = 1.0e-2
converge_rtol = 1.0e-4


[plots]
at_runtime = true # Make some plots at runtime?
temperature = true # Plot temperature profile?
fluxes = true # Plot fluxes?
contribution = true # Plot spectral contribution function?
emission = true # Plot emission spectrum?
albedo = true # Plot spectral albedo?
mixing_ratios = true # Plot mixing ratios?
animate = true # Make animation from runtime plots?
at_runtime = true
temperature = true
fluxes = true
contribution = true
emission = true
albedo = true
mixing_ratios = true
animate = true

Binary file added res/thermo/C2H4.nc
Binary file not shown.
Binary file added res/thermo/FeS.nc
Binary file not shown.
Binary file added res/thermo/OCS.nc
Binary file not shown.
Binary file added res/thermo/S2.nc
Binary file not shown.
Binary file added res/thermo/SO.nc
Binary file not shown.
115 changes: 92 additions & 23 deletions src/atmosphere.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ module atmosphere
gas_sat::Dict{String, Array{Bool, 1}} # Layer is saturated or cold-trapped
gas_dat::Dict{String, phys.Gas_t} # struct variables containing thermodynamic data
gas_yield::Dict{String, Array{Float64,1}} # condensate yield [kg/m^2] at each level (can be negative, representing evaporation)
condensates::Array{String, 1} # List of condensing gases (strings)
single_component::Bool # Does a single gas make up 100% of layer at any point in the column?
condensates::Array{String, 1} # List of condensing gases (strings)
condense_any::Bool # length(condensates)>0 ?
single_component::Bool # Does a single gas make up 100% of layer at any point in the column?

# Gases (only those in spectralfile)
gas_soc_num::Int
Expand Down Expand Up @@ -301,7 +302,7 @@ module atmosphere
end

# Code versions
atmos.AGNI_VERSION = "0.5.1"
atmos.AGNI_VERSION = "0.5.2"
atmos.SOCRATES_VERSION = readchomp(joinpath(ENV["RAD_DIR"],"version"))
@debug "AGNI VERSION = $(atmos.AGNI_VERSION)"
@debug "Using SOCRATES at $(ENV["RAD_DIR"])"
Expand Down Expand Up @@ -523,7 +524,9 @@ module atmosphere
end

# Validate condensate names
atmos.condense_any = false
if length(condensates) > 0
atmos.condense_any = true
for c in condensates
if !(c in atmos.gas_names)
@error "Invalid condensate '$c'"
Expand Down Expand Up @@ -1081,7 +1084,7 @@ module atmosphere
if !isempty(gas_flags)
gas_flags = "($(gas_flags[1:end-1]))"
end
@info @sprintf(" %6.2e %-5s %s", atmos.gas_vmr[g][end], g, gas_flags)
@info @sprintf(" %6.2e %-6s %s", atmos.gas_vmr[g][end], g, gas_flags)
end


Expand Down Expand Up @@ -1211,12 +1214,14 @@ module atmosphere
- `atmos::Atmos_t` the atmosphere struct instance to be used.
- `chem_type::Int` chemistry type (see wiki)
- `write_cfg::Bool` write config and elements
- `tmp_floor::Float64=500.0` temperature floor for T(p) provided to FastChem
- `tmp_floor::Float64` temperature floor for T(p) provided to FastChem

Returns:
- `state::Int` fastchem state (0: success, 1: critical_fail, 2: elem_fail, 3: conv_fail, 4: both_fail)
"""
function chemistry_eq!(atmos::atmosphere.Atmos_t, chem_type::Int, write_cfg::Bool; tmp_floor::Float64=500.0)::Int
function chemistry_eq!(atmos::atmosphere.Atmos_t, chem_type::Int, write_cfg::Bool; tmp_floor::Float64=700.0)::Int

@debug "Running equilibrium chemistry"

# Return code
state::Int = 0
Expand All @@ -1227,6 +1232,14 @@ module atmosphere
return 1
end

# Check minimum temperature
if maximum(atmos.tmpl) < tmp_floor
@warn "Temperature profile is entirely too cold for FastChem. Not doing chemistry."
return 1
end

count_elem_nonzero::Int = 0

# Paths
execpath::String = joinpath(atmos.fastchem_path,"fastchem") # Executable file
confpath::String = joinpath(atmos.fastchem_work,"config.input") # Configuration by AGNI
Expand Down Expand Up @@ -1263,16 +1276,16 @@ module atmosphere
write(f,joinpath(logK,"logK.dat")*" "*joinpath(logK,"logK_condensates.dat")*" \n\n")

write(f,"#Accuracy of chemistry iteration \n")
write(f,"1.0e-5 \n\n")
write(f,"1.0e-4 \n\n")

write(f,"#Accuracy of element conservation \n")
write(f,"1.0e-4 \n\n")

write(f,"#Max number of chemistry iterations \n")
write(f,"50000 \n\n")
write(f,"60000 \n\n")

write(f,"#Max number internal solver iterations \n")
write(f,"10000 \n\n")
write(f,"30000 \n\n")
end

# Calculate elemental abundances
Expand All @@ -1289,16 +1302,19 @@ module atmosphere
N_g[i] += d[e]
end
end
# will be normalised in later code
N_g *= get_x(atmos, gas, atmos.nlev_c) * atmos.p[end] / (phys.k_B * atmos.tmp[end]) # gas contribution
N_t += N_g # number/m^3
N_t += N_g
end

# Write elemental abundances
open(joinpath(atmos.fastchem_work,"elements.dat"),"w") do f
write(f,"# Elemental abundances derived from AGNI volatiles \n")
for (i,e) in enumerate(phys.elements_list)
if N_t[i] > 1.0e-30
# normalise relative to hydrogen
write(f, @sprintf("%s %.3f \n",e,log10(N_t[i]/N_t[1]) + 12.0))
count_elem_nonzero += 1
end
end
end
Expand All @@ -1309,7 +1325,7 @@ module atmosphere
write(f,"# AGNI temperature structure \n")
write(f,"# bar, kelvin \n")
for i in 1:atmos.nlev_c
write(f,@sprintf("%.6e %.6e \n", atmos.p[i], max(tmp_floor,atmos.tmp[i]) ))
write(f,@sprintf("%.6e %.6e \n", atmos.p[i]*1e-5, max(tmp_floor,atmos.tmp[i]) ))
end
end

Expand Down Expand Up @@ -1350,24 +1366,77 @@ module atmosphere
(data,head) = readdlm(chempath, '\t', Float64, header=true)
data = transpose(data) # convert to: gas, level

# Clear VMRs
for g in atmos.gas_names
fill!(atmos.gas_vmr[g], 0.0)
end

# Parse gas chemistry
g::String = ""
g_fc::String = "_unset"
d_fc::Dict = Dict{String, Int}()
g_in::String = "_unset"
match::Bool = false
N_t = data[4,:] # at each level: sum of gas number densities

for (i,h) in enumerate(head) # for each column (gas)
g = rstrip(lstrip(h))

# check if gas is included in the model
if g in keys(phys.map_fastchem_name)
g = phys.map_fastchem_name[g] # convert name
if g in atmos.gas_names
N_g = data[i,:] # number densities for this gas
atmos.gas_vmr[g][:] .= N_g[:] ./ N_t[:] # mole fraction (VMR) for this gas
end
end # not included => skip

# skip T and P
if i <= 5+count_elem_nonzero
continue
end

# parse name
g_fc = rstrip(lstrip(h))
if occursin("_", g_fc)
g_fc = split(g_fc, "_")[1]
end
g_fc = replace(g_fc, "cis"=>"", "trans"=>"")

match = false

# firstly, check if we have the FC name already stored
for g in atmos.gas_names
if atmos.gas_dat[g].fastchem_name == g_fc
match = true
g_in = g
break
end
end

# not stored => search based on atoms
if !match
d_fc = phys.count_atoms(g_fc) # get atoms dict from FC name

for g in atmos.gas_names
if phys.same_atoms(d_fc, atmos.gas_dat[g].atoms)
match = true
g_in = g
break
end
end
end

# matched?
if match
N_g = data[i,:] # number densities for this gas
atmos.gas_vmr[g_in][:] .+= N_g[:] ./ N_t[:] # mole fraction (VMR) for this gas
end
end

# Do not renormalise mixing ratios
# Do not renormalise mixing ratios, since this is done by fastchem
# If we are missing gases then that's okay.

# Find where we truncated the temperature profile, and make sure that regions above that use the same x_gas values
for i in range(start=atmos.nlev_c, stop=1, step=-1)
if atmos.tmp[i] < tmp_floor
for g in atmos.gas_names
atmos.gas_vmr[g][1:i] .= atmos.gas_vmr[g][i+1]
end
break
end
end

# See docstring for return codes
return state
end

Expand Down
6 changes: 3 additions & 3 deletions src/energy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ module energy
function condense_relax!(atmos::atmosphere.Atmos_t)

# Check if empty
if length(atmos.condensates) == 0
if !atmos.condense_any
return nothing
end

Expand Down Expand Up @@ -542,7 +542,7 @@ module energy
fill!(atmos.mask_l, 0)

# Check if empty
if length(atmos.condensates) == 0
if !atmos.condense_any
return nothing
end

Expand Down Expand Up @@ -600,7 +600,7 @@ module energy
fill!(atmos.flux_dif, 0.0)

# +Condensation energy flux
if latent
if latent || atmos.condense_any
if atmos.single_component
# does NOT modify VMRs
energy.condense_relax!(atmos)
Expand Down
Loading