diff --git a/CITATION.cff b/CITATION.cff index e5a9921..7287e64 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -5,7 +5,7 @@ authors: given-names: "Harrison" orcid: "https://orcid.org/0000-0002-8368-4641" title: "AGNI" -version: 0.11.1 +version: 0.11.2 doi: 10.xx/xx.xx date-released: 2024-11-29 url: "https://github.com/nichollsh/AGNI" diff --git a/Project.toml b/Project.toml index 070d2f2..af3d728 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "AGNI" uuid = "ede838c1-9ec3-4ebe-8ae8-da4091b3f21c" authors = ["Harrison Nicholls "] -version = "0.11.1" +version = "0.11.2" [deps] ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" diff --git a/codemeta.json b/codemeta.json index a9df8ab..d4d961e 100644 --- a/codemeta.json +++ b/codemeta.json @@ -19,5 +19,5 @@ "keywords": "physics, radiative transfer, exoplanets, astronomy, convection, radiation, planets, atmospheres", "license": "GPL v3.0", "title": "AGNI", - "version": "0.11.1" + "version": "0.11.2" } diff --git a/src/AGNI.jl b/src/AGNI.jl index 71a9530..b1b6855 100755 --- a/src/AGNI.jl +++ b/src/AGNI.jl @@ -443,7 +443,7 @@ module AGNI @info prt_req[1:end-2] # Write initial state - dump.write_ptz(atmos, joinpath(atmos.OUT_DIR,"ptz_ini.csv")) + dump.write_ptz(atmos, joinpath(atmos.OUT_DIR,"ptz_initial.csv")) # Do chemistry on initial composition if chem_type in [1,2,3] @@ -525,8 +525,8 @@ module AGNI # Write arrays @info "Writing results" - dump.write_ptz(atmos, joinpath(atmos.OUT_DIR,"ptz.csv")) - dump.write_fluxes(atmos, joinpath(atmos.OUT_DIR,"fl.csv")) + # dump.write_ptz(atmos, joinpath(atmos.OUT_DIR,"ptz.csv")) + # dump.write_fluxes(atmos, joinpath(atmos.OUT_DIR,"fl.csv")) dump.write_ncdf(atmos, joinpath(atmos.OUT_DIR,"atm.nc")) # Save plots @@ -537,7 +537,6 @@ module AGNI plt_ani && plotting.animate(atmos) plt_vmr && plotting.plot_vmr(atmos, joinpath(atmos.OUT_DIR,"plot_vmrs.png"), size_x=600) plt_cff && plotting.plot_contfunc1(atmos, joinpath(atmos.OUT_DIR,"plot_contfunc1.png")) - plt_cff && plotting.plot_contfunc2(atmos, joinpath(atmos.OUT_DIR,"plot_contfunc2.png")) plt_tmp && plotting.plot_pt(atmos, joinpath(atmos.OUT_DIR,"plot_ptprofile.png"), incl_magma=(sol_type==2)) plt_flx && plotting.plot_fluxes(atmos, joinpath(atmos.OUT_DIR,"plot_fluxes.png"), incl_mlt=incl_convect, incl_eff=(sol_type==3), incl_cdct=incl_conduct, incl_latent=incl_latent) plt_ems && plotting.plot_emission(atmos, joinpath(atmos.OUT_DIR,"plot_emission.png")) diff --git a/src/atmosphere.jl b/src/atmosphere.jl index 1e7443a..6548bbf 100644 --- a/src/atmosphere.jl +++ b/src/atmosphere.jl @@ -13,7 +13,7 @@ module atmosphere using LinearAlgebra using Logging using LoopVectorization - # import Statistics + import Statistics import PCHIPInterpolation:Interpolator import DelimitedFiles:readdlm @@ -209,7 +209,7 @@ module atmosphere fastchem_work::String # Path to fastchem working directory # Observing properties - transspec_p::Float64 # (INPUT PARAMETER) pressure level probed in transmission [Pa] + transspec_p::Float64 # Pressure level probed in transmission [Pa] transspec_r::Float64 # planet radius probed in transmission [m] transspec_m::Float64 # mass [kg] enclosed by transspec_r transspec_rho::Float64 # bulk density [kg m-3] implied by r and m @@ -333,7 +333,7 @@ module atmosphere @info "Setting-up a new atmosphere struct" # Code versions - atmos.AGNI_VERSION = "0.11.1" + atmos.AGNI_VERSION = "0.11.2" atmos.SOCRATES_VERSION = readchomp(joinpath(ENV["RAD_DIR"],"version")) @debug "AGNI VERSION = "*atmos.AGNI_VERSION @debug "Using SOCRATES at $(ENV["RAD_DIR"])" @@ -717,10 +717,64 @@ module atmosphere return true end # end function setup + + """ + **Estimate photosphere.** + + Estimates the location of the photosphere by finding the median of the contribution + function in each band (0.2 um to 150 um), and then finding the pressure level at which + these median values are maximised. + + Arguments: + - `atmos::Atmos_t` the atmosphere struct instance to be used. + + Returns: + - `p_ref::Float64` pressure level of photosphere [Pa] + """ + function estimate_photosphere!(atmos::atmosphere.Atmos_t)::Float64 + + # Params + wl_min::Float64 = 0.2 * 1e-6 # 200 nm + wl_max::Float64 = 150 * 1e-6 # 150 um + p_min::Float64 = 10.0 # 1e-4 bar + + # tracking + cff_max::Float64 = 0.0 + cff_try::Float64 = 0.0 + atmos.transspec_p = p_min + + # get band indices + wl_imin = findmin(abs.(atmos.bands_cen .- wl_min))[2] + wl_imax = findmin(abs.(atmos.bands_cen .- wl_max))[2] + + # reversed? + if wl_imin > wl_imax + wl_imin, wl_imax = wl_imax, wl_imin + end + + # loop over levels + for i in 1:atmos.nlev_c + if atmos.p[i] < p_min + continue + end + + # maximum contfunc in this band + cff_try = Statistics.median(atmos.contfunc_band[i,wl_imin:wl_imax]) + + # is this more than the existing maximum? + if cff_try > cff_max + cff_max = cff_try + atmos.transspec_p = atmos.p[i] + end + end + + return atmos.transspec_p + end + """ **Calculate observed radius and bulk density.** - This is done at the layer probed in transmission, which is set at a fixed pressure. + This is done at the layer probed in transmission, which is set to a fixed pressure. Arguments: - `atmos::Atmos_t` the atmosphere struct instance to be used. @@ -729,15 +783,14 @@ module atmosphere Returns: - `transspec_rho::Float64` the bulk density observed in transmission """ - function calc_observed_rho!(atmos::atmosphere.Atmos_t, p_ref::Float64=100.0)::Float64 + function calc_observed_rho!(atmos::atmosphere.Atmos_t)::Float64 - # transspec_p::Float64 # (INPUT) level probed in transmission [Pa] # transspec_r::Float64 # planet radius probed in transmission [m] # transspec_m::Float64 # mass [kg] of atmosphere + interior # transspec_rho::Float64 # bulk density [kg m-3] implied by r and m # Store reference pressure in atmos struct - atmos.transspec_p = p_ref + estimate_photosphere!(atmos) # get the observed height idx::Int = findmin(abs.(atmos.p .- atmos.transspec_p))[2] diff --git a/src/dump.jl b/src/dump.jl index 1136938..8978787 100644 --- a/src/dump.jl +++ b/src/dump.jl @@ -125,6 +125,7 @@ module dump # ---------------------- # Scalar quantities # Create variables + var_max_cff_p = defVar(ds, "max_cff_p", Float64, (), attrib = OrderedDict("units" => "Pa")) # Pressure level at which CFF is max [Pa] var_tmp_surf = defVar(ds, "tmp_surf", Float64, (), attrib = OrderedDict("units" => "K")) # Surface brightness temperature [K] var_flux_int = defVar(ds, "flux_int", Float64, (), attrib = OrderedDict("units" => "W m-2")) # Internal flux [W m-2] var_inst = defVar(ds, "instellation", Float64, (), attrib = OrderedDict("units" => "W m-2")) # Solar flux at TOA @@ -149,6 +150,7 @@ module dump var_starfile = defVar(ds, "starfile" ,String, ()) # Path to star file when read # Store data + var_max_cff_p[1] = atmos.transspec_p var_tmp_surf[1] = atmos.tmp_surf var_flux_int[1] = atmos.flux_int var_inst[1] = atmos.instellation diff --git a/src/plotting.jl b/src/plotting.jl index 35c01e5..dd3b139 100644 --- a/src/plotting.jl +++ b/src/plotting.jl @@ -13,6 +13,7 @@ module plotting using LaTeXStrings using Printf using FFMPEG + using Statistics import Glob:glob import ..atmosphere @@ -20,7 +21,7 @@ module plotting # Default plotting configuration default(fontfamily="sans-serif", framestyle=:box, label=nothing, grid=true, - guidefontsize=9, titlefontsize=9) + guidefontsize=9, titlefontsize=9, dpi=280) # Symmetric log function _symlog(v::Float64)::Float64 @@ -39,7 +40,6 @@ module plotting Plot the temperature-pressure profile. """ function plot_pt(atmos::atmosphere.Atmos_t, fname::String; - dpi::Int=250, size_x::Int=500, size_y::Int=400, incl_magma::Bool=false, title::String="") @@ -48,8 +48,7 @@ module plotting yticks = 10.0 .^ round.(Int,range( log10(ylims[1]), stop=log10(ylims[2]), step=1)) # Create plot - plt = plot(ylims=ylims, yticks=yticks, legend=:outertopright, - dpi=dpi, size=(size_x,size_y)) + plt = plot(ylims=ylims, yticks=yticks, legend=:outertopright, size=(size_x,size_y)) # Plot phase boundary if atmos.condense_any @@ -104,17 +103,14 @@ module plotting Plot the height vs pressure profile. """ function plot_height(atmos::atmosphere.Atmos_t, fname::String; - dpi::Int=250, size_x::Int=500, size_y::Int=400, - incl_magma::Bool=false, title::String="") ylims = (1e-5*atmos.pl[1]/1.5, 1e-5*atmos.pl[end]*1.5) yticks = 10.0 .^ round.(Int,range( log10(ylims[1]), stop=log10(ylims[2]), step=1)) # Create plot - plt = plot(ylims=ylims, yticks=yticks, legend=:outertopright, - dpi=dpi, size=(size_x,size_y)) + plt = plot(ylims=ylims, yticks=yticks, legend=:outertopright, size=(size_x,size_y)) # Plot surface scatter!(plt, [0.0], [atmos.pl[end]*1e-5], color="brown3", label=L"P_s") @@ -142,7 +138,6 @@ module plotting Plot the cloud mass mixing ratio and area fraction. """ function plot_cloud(atmos::atmosphere.Atmos_t, fname::String; - dpi::Int=250, size_x::Int=500, size_y::Int=400, title::String="") @@ -155,7 +150,7 @@ module plotting # Create plot plt = plot( xlims=xlims, xticks=xticks, ylims=ylims, yticks=yticks, - legend=:outertopright, dpi=dpi, + legend=:outertopright, size=(size_x,size_y)) # Temperature profile for reference @@ -186,7 +181,6 @@ module plotting Plot the VMRs of the atmosphere at each cell-centre location. """ function plot_vmr(atmos::atmosphere.Atmos_t, fname::String; - dpi::Int=250, size_x::Int=500, size_y::Int=400) # X-axis minimum allowed left-hand-side limit (log units) @@ -197,8 +191,7 @@ module plotting yticks = 10.0 .^ round.(Int,range( log10(ylims[1]), stop=log10(ylims[2]), step=1)) # Create plot - plt = plot(ylims=ylims, yticks=yticks, dpi=dpi, - legend=:outertopright, size=(size_x,size_y)) + plt = plot(ylims=ylims, yticks=yticks, legend=:outertopright, size=(size_x,size_y)) # Plot log10 VMRs for each gas gas_xsurf::Array = zeros(Float64, atmos.gas_num) @@ -246,7 +239,7 @@ module plotting xticks = round.(Int,range( xlims[1], stop=0, step=1)) # Set figure properties - xlabel!(plt, "log Volume Mixing Ratio") + xlabel!(plt, "log₁₀ Volume Mixing Ratio") xaxis!(plt, xlims=xlims, xticks=xticks) ylabel!(plt, "Pressure [bar]") @@ -263,7 +256,7 @@ module plotting Plot the fluxes at each pressure level """ function plot_fluxes(atmos::atmosphere.Atmos_t, fname::String; - dpi::Int=250, size_x::Int=550, size_y::Int=400, + size_x::Int=550, size_y::Int=400, incl_eff::Bool=false, incl_mlt::Bool=true, incl_cdct::Bool=true, incl_latent::Bool=true, title::String="" @@ -280,8 +273,7 @@ module plotting xticklabels = _intstr.(round.(Int, abs.(xticks))) plt = plot(legend=:outertopright, ylims=ylims, yticks=yticks, - xticks=(xticks, xticklabels), xlims=xlims, - dpi=dpi, size=(size_x,size_y)) + xticks=(xticks, xticklabels), xlims=xlims, size=(size_x,size_y)) col_r::String = "#c0c0c0" col_n::String = "#000000" @@ -380,7 +372,7 @@ module plotting """ Plot emission spectrum at the TOA """ - function plot_emission(atmos::atmosphere.Atmos_t, fname::String; dpi::Int=250) + function plot_emission(atmos::atmosphere.Atmos_t, fname::String) # Check that we have data if !(atmos.is_out_lw && atmos.is_out_sw) @@ -415,7 +407,7 @@ module plotting @. yp = phys.evaluate_planck(xe, atmos.tmp_surf) * 1000.0 # Make plot - plt = plot(dpi=dpi) + plt = plot() plot!(plt, xe, yp, label=L"Planck @ $T_s$", color="green") plot!(plt, xe, ye, label="Surface LW+SW", color="green", ls=:dash) @@ -442,45 +434,76 @@ module plotting end """ - Plot normalised contribution function (bolometric) + Plot contribution function at different bands. """ - function plot_contfunc1(atmos::atmosphere.Atmos_t, fname::String; dpi::Int=250) + function plot_contfunc1(atmos::atmosphere.Atmos_t, fname::String; + size_x::Int=500, size_y::Int=400, + cf_min::Float64=1e-6) # Check that we have data if !atmos.is_out_lw - @error "Cannot plot contribution func because radiances have not been calculated" + @error "Cannot plot contrib func because radiances have not been calculated" return end + # Make plot + plt = plot(legend=:bottomleft, size=(size_x, size_y)) + x_min::Float64 = log10(cf_min) + x_max::Float64 = x_min + 0.5 + # Define arrays - x::Array{Float64, 1} = zeros(Float64, atmos.nlev_c) - y::Array{Float64, 1} = zeros(Float64, atmos.nlev_c) - w::Array{Float64, 1} = zeros(Float64, atmos.nbands) + cff::Array{Float64, 1} = zeros(Float64, atmos.nlev_c) # log10 cont func + prs::Array{Float64, 1} = zeros(Float64, atmos.nlev_c) # pressure [bar] + @. prs = atmos.p * 1.0e-5 - # band widths - for ba in 1:atmos.nbands - w[ba] = abs(atmos.bands_min[ba] - atmos.bands_max[ba]) + # Band limits + wl_min = 0.1 * 1e-6 # 100 nm + wl_imin = findmin(abs.(atmos.bands_cen .- wl_min))[2] + wl_max = 150 * 1e-6 # 150 um + wl_imax = findmin(abs.(atmos.bands_cen .- wl_max))[2] + + # reversed? + if wl_imin > wl_imax + wl_imin, wl_imax = wl_imax, wl_imin end - # store contribution - cf_min = 1.0e-9 + # plot statistical contributions at each level, from bands in given range for i in 1:atmos.nlev_c - x[i] = max(sum(atmos.contfunc_band[i,:] .* w[:]),cf_min) - y[i] = atmos.p[i] * 1.0e-5 + cff[i] = log10(max(maximum(atmos.contfunc_band[i,wl_imin:wl_imax]), cf_min)) end + x_max = max(x_max, maximum(cff)) + plot!(plt, cff, prs, c=:black, label="Maximum", ls=:solid) - # normalise contribution - x /= maximum(x) + for i in 1:atmos.nlev_c + cff[i] = log10(max(mean(atmos.contfunc_band[i,wl_imin:wl_imax]), cf_min)) + end + plot!(plt, cff, prs, c=:black, label="Mean", ls=:dash) - # Make plot - plt = plot(dpi=dpi, colorbar_title="log " * L"\widehat {cf}(\lambda, p)") + for i in 1:atmos.nlev_c + cff[i] = log10(max(median(atmos.contfunc_band[i,wl_imin:wl_imax]), cf_min)) + end + plot!(plt, cff, prs, c=:black, label="Median", ls=:dot) - plot!(plt, x,y, c=:black) + # plot per-band contributions [um] as their own lines + for wl_tgt in Float64[1.0, 5.0, 10.0, 15.0] + # find nearest band + wl_tgt *= 1e-6 + iband = findmin(abs.(atmos.bands_cen .- wl_tgt))[2] + wl_i = atmos.bands_cen[iband] * 1e6 - xlabel!(plt, "Normalised bolometric contribution") - xaxis!(plt, xscale=:log10, minorgrid=true) + # get contribution function + for i in 1:atmos.nlev_c + cff[i] = log10(max(atmos.contfunc_band[i,iband], cf_min)) + end - ylims = (y[1], y[end]) + # plot + plot!(plt, cff, prs, label=@sprintf("%.1f μm",wl_i)) + end + + xlabel!(plt, "log₁₀ Contribution function") + xaxis!(plt, xlims=(x_min+0.05, x_max+0.1)) + + ylims = (prs[1], prs[end]) yticks = 10.0 .^ round.(Int,range( log10(ylims[1]), stop=log10(ylims[2]), step=1)) ylabel!(plt, "Pressure [bar]") yflip!(plt) @@ -494,8 +517,11 @@ module plotting """ Plot normalised contribution function (per band) + + The data displayed in this plot are fine, but the x-axis ticks are labelled + incorrectly by the plotting library. I don't know why this is. """ - function plot_contfunc2(atmos::atmosphere.Atmos_t, fname::String; dpi::Int=250) + function plot_contfunc2(atmos::atmosphere.Atmos_t, fname::String) # Check that we have data if !atmos.is_out_lw @@ -503,6 +529,8 @@ module plotting return end + @warn "Contribution func 2D colormesh x-axis is incorrect!" + # Get data x::Array{Float64, 1} = zeros(Float64, atmos.nbands) # band centres (reverse order) y::Array{Float64, 1} = zeros(Float64, atmos.nlev_c) # pressure levels @@ -542,7 +570,7 @@ module plotting z[:] = log10.(z[:]) # Make plot - plt = plot(dpi=dpi, colorbar_title="log " * L"\widehat {cf}(\lambda, p)") + plt = plot(colorbar_title="log " * L"\widehat {cf}(\lambda, p)") heatmap!(plt, x,y,z, c=:devon) @@ -566,7 +594,7 @@ module plotting """ Plot spectral albedo (ratio of SW_UP to SW_DN) """ - function plot_albedo(atmos::atmosphere.Atmos_t, fname::String; dpi::Int=250) + function plot_albedo(atmos::atmosphere.Atmos_t, fname::String) # Check that we have data if !(atmos.is_out_lw && atmos.is_out_sw) @@ -580,7 +608,7 @@ module plotting # Make plot ylims = (0.0, 100.0) - plt = plot(dpi=dpi, ylims=ylims) + plt = plot(ylims=ylims) plot!(plt, atmos.bands_cen*1e9, y, color="black") @@ -640,13 +668,12 @@ module plotting Plot jacobian matrix """ function jacobian(b::Array{Float64,2}, fname::String; - perturb::Array{Bool,1}=Bool[], - dpi::Int=200, size_x::Int=600, size_y::Int=500) + perturb::Array{Bool,1}=Bool[], size_x::Int=600, size_y::Int=500) lim::Float64 = maximum(abs.(b)) # colourbar limits l::Int = length(perturb) # show perturbed levels? - plt = plot(dpi=dpi, size=(size_x, size_y), + plt = plot(size=(size_x, size_y), title="∂r/∂x [W m⁻² K⁻¹]", clim=(-lim,lim), yflip=true) diff --git a/src/solver.jl b/src/solver.jl index ed8a96f..a88bfda 100644 --- a/src/solver.jl +++ b/src/solver.jl @@ -149,7 +149,7 @@ module solver fdr::Float64 = 0.01 # Use forward difference if cost ratio is below this value perturb_trig::Float64 = 0.1 # Require full Jacobian update when cost*peturb_trig satisfies convergence perturb_crit::Float64 = 0.1 # Require Jacobian update at level i when r_i>perturb_crit - perturb_mod::Int = 10 # Do full jacobian at least this frequently + perturb_mod::Int = 5 # Do full jacobian at least this frequently # linesearch ls_tau::Float64 = 0.7 # backtracking downscale size @@ -540,7 +540,7 @@ module solver # Determine which parts of the Jacobian matrix need to be updated @debug " jacobian" - if (step == 1) || perturb_all || easy_step || + if (step <= 2) || perturb_all || easy_step || (c_cur*perturb_trig < conv_atol + conv_rtol * c_max) || mod(step,perturb_mod)==0 # Update whole matrix when any of these are true: @@ -778,6 +778,8 @@ module solver _fev!(x_cur, zeros(Float64, arr_len)) energy.calc_hrates!(atmos) + energy.radtrans!(atmos, true, calc_cf=true) # calculate LW radtrans with contfunc + atmosphere.calc_observed_rho!(atmos) # ---------------------------------------------------------- # Print info