import CSV import TypedTables: Table, columnnames import Printf: @sprintf, @printf # For converting numbers to strings import LinearAlgebra: BLAS BLAS.set_num_threads(1) # BFGS uses BLAS operations, but they are relatively small so it's actually more efficient to run BLAS single-threaded. table = CSV.read("../../../../Work/Resources/isochrones/parsec/jwst/nircam_nov22/table.dat", Table; comment="#", delim=' ', ignorerepeated=true, header=["Zini", "MH", "logAge", "Mini", "int_IMF", "Mass", "logL", "logTe", "logg", "label", "McoreTP", "C_O", "period0", "period1", "period2", "period3", "period4", "pmode", "Mloss", "tau1m", "X", "Y", "Xc", "Xn", "Xo", "Cexcess", "Z", "mbolmag", "F070W", "F090W", "F115W", "F150W", "F200W", "F277W", "F356W", "F444W", "F150W2", "F322W2", "F140M", "F162M", "F182M", "F210M", "F250M", "F300M", "F335M", "F360M", "F410M", "F430M", "F460M", "F480M"]) columnnames(table) unique(table.logAge) # These are `log10(age)`, where the age is in years. unique(table.MH) # These are metallicities, [M/H] unique(table.Zini) import StarFormationHistories: MH_from_Z # Rewrite [M/H] column. for i in eachindex(table) table.MH[i] = MH_from_Z(table.Zini[i], 0.01471) end unique(table.MH) import StarFormationHistories: Martin2016_complete, exp_photerr distmod = 25.0 # Distance modulus F090W_complete(m) = Martin2016_complete(m,1.0,28.5,0.7) F150W_complete(m) = Martin2016_complete(m,1.0,27.5,0.7) F090W_error(m) = min( exp_photerr(m, 1.03, 15.0, 36.0, 0.02), 0.4 ) F150W_error(m) = min( exp_photerr(m, 1.03, 15.0, 35.0, 0.02), 0.4 ); import PyPlot as plt import PyPlot: @L_str # For LatexStrings import PyCall: @pyimport plt.rc("text", usetex=true) plt.rc("text.latex", preamble="\\usepackage{amsmath}") # for \text plt.rc("font", family="serif", serif=["Computer Modern"], size=14) plt.rc("figure", figsize=(5,5)) plt.rc("patch", linewidth=1, edgecolor="k", force_edgecolor=true) # https://matplotlib.org/stable/gallery/images_contours_and_fields/interpolation_methods.html plt.rc("image", interpolation="none") plotmags=20.0:0.05:33.0 fig,ax1=plt.subplots() ax1.plot(plotmags,F090W_complete.(plotmags),label="F090W") ax1.plot(plotmags,F150W_complete.(plotmags),label="F150W") ax1.set_xlabel("Apparent Magnitude") ax1.set_ylabel("Completeness") ax1.legend(loc="lower left") plotmags=20.0:0.05:33.0 fig,ax1=plt.subplots() ax1.plot(plotmags,F090W_error.(plotmags),label="F090W") ax1.plot(plotmags,F150W_error.(plotmags),label="F150W") ax1.set_xlabel("Apparent Magnitude") ax1.set_ylabel("Photometric Error") ax1.legend(loc="upper left") ax1.set_ylim([0.0,ax1.get_ylim()[2]]) import StarFormationHistories: partial_cmd_smooth import InitialMassFunctions: Kroupa2001 # Some additional setup edges = (range(-0.65, 1.75, length=100), range(distmod-7.0, distmod+3.0, length=100)) # The bin edges for the Hess diagrams (17,28) imf = Kroupa2001(0.08, 100.0) # Initial mass function model unique_logAge = unique(table.logAge) unique_MH = unique(table.MH) template_norm = 1e3 # The stellar mass of the populations in each template # Now define the subset of unique logAges for which isochrones are available # that we actually want to use for our example. # As stated above, the isochrone file we read in has a uniform grid spacing of # `ΔlogAge=0.05`. We will only use a spacing of 0.1 dex for logAge < 9 if isochrone_cut is true. isochrone_cut::Bool = false if isochrone_cut logAge_cutval = 9 selection = vcat( findall( <=(logAge_cutval), unique_logAge)[begin:2:end], findall( >(logAge_cutval), unique_logAge) ) unique_logAge = unique_logAge[selection] end @printf("Number of final isochrones is: %i",length(unique_logAge) * length(unique_MH)) # # Constructing the templates; single-threaded with push! # templates = Vector{Matrix{Float64}}(undef,0) # template_logAge = Vector{Float64}(undef,0) # template_MH = Vector{Float64}(undef,0) # # We don't strictly need to save the initial masses (m_ini) or the isochrone magnitudes (iso_mags), # # but we'll use them later in this example so we'll save them now. # template_mini = Vector{Vector{Float64}}(undef,0) # template_isomags = Vector{Vector{Vector{Float64}}}(undef,0) # for logage in unique_logAge # for mh in unique_MH # # Combination of a logage and an MH defines a unique isochrone. # # Pick out all entries in `table` that match this combination; these will be row indices # # into the table of the stars in this specific isochrone. # local good = findall( (table.MH .== mh) .& (table.logAge .== logage) ) # # Chop off the last entry in the isochrone because its the 30 mag weird thing that parsec does. # local m_ini = table.Mini[good][begin:end-1] # These are the initial masses of the stars in the isochrone, in solar masses. # push!(template_mini, m_ini) # local iso_mags = [table.F090W[good][begin:end-1], table.F150W[good][begin:end-1]] # These are the absolute magnitudes we want from the isochrone. # push!(template_isomags, iso_mags) # # Create template and push # push!(templates, partial_cmd_smooth( m_ini, iso_mags, [F090W_error, F150W_error], 2, [1,2], imf, [F090W_complete, F150W_complete]; # dmod=distmod, normalize_value=template_norm, edges=edges).weights ) # push!(template_logAge, logage) # push!(template_MH, mh) # end # end # Constructing the templates; multi-threaded with pre-allocated arrays templates = Vector{Matrix{Float64}}(undef, length(unique_logAge) * length(unique_MH)) template_logAge = Vector{Float64}(undef, length(templates)) template_MH = Vector{Float64}(undef, length(templates)) # We don't strictly need to save the initial masses (m_ini) or the isochrone magnitudes (iso_mags), # but we'll use them later in this example so we'll save them now. template_mini = Vector{Vector{Float64}}(undef, length(templates)) template_isomags = Vector{Vector{Vector{Float64}}}(undef, length(templates)) # Multi-threaded, nested loop iterating over unique_MH (outer loop) and unique_logAge (inner loop). # Using `enumerate` also gets us index `i` of iteration. Base.Threads.@threads for (i, (mh, logage)) in collect(enumerate(Iterators.product(unique_MH, unique_logAge))) # Combination of a logage and an MH defines a unique isochrone. # Pick out all entries in `table` that match this combination; these will be row indices # into the table of the stars in this specific isochrone. local good = findall( (table.MH .== mh) .& (table.logAge .== logage) ) # Chop off the last star because its the 30 mag weird thing that parsec does. local m_ini = table.Mini[good][begin:end-1] # These are the initial masses of the stars in the isochrone, in solar masses. template_mini[i] = m_ini local iso_mags = [table.F090W[good][begin:end-1], table.F150W[good][begin:end-1]] # These are the absolute magnitudes we want from the isochrone. template_isomags[i] = iso_mags # Create templates templates[i] = partial_cmd_smooth(m_ini, iso_mags, [F090W_error, F150W_error], 2, [1,2], imf, [F090W_complete, F150W_complete]; dmod=distmod, normalize_value=template_norm, edges=edges).weights template_logAge[i] = logage template_MH[i] = mh end # Write single isochrone to file # Some extra info here https://discourse.julialang.org/t/formatting-float64-output-with-csv-write/23479 # let idx = length(unique_MH)*70 + findfirst(==(first(unique_MH)), unique_MH) # 1821 let idx = length(unique_MH)*(findmin( x -> abs(exp10(x-9) - 12.59), unique_logAge)[2] - 1) + findfirst(==(first(unique_MH)), unique_MH) # 1821 logage = template_logAge[idx] mh = template_MH[idx] println(logage, " ", mh) # local good = findall((table.MH .== mh) .& (table.logAge .== logage)) # open("isochrone.txt","w") do file # print(file, "m_ini F090W F150W F277W\n") # for i in good[begin:end-1] # @printf(file, "%.10f %.3f %.3f %.3f\n", table.Mini[i], table.F090W[i], table.F150W[i], table.F277W[i]) # end # end end # Sort the template_logAge and template_MH so we have a guarantee for later permidx = sortperm(template_logAge) template_logAge = template_logAge[permidx] template_MH = template_MH[permidx] templates = templates[permidx]; # When displaying the templates we will divide them by template_norm so that that units of the templates are # [N / M_sun], or expected number of stars per bin per solar mass of stars formed. # Plotting in absolute magnitude here rather than apparent on y-axis # display_templates = (100,1495,1821) # Before option of using isochrone_cut display_templates = ( length(unique_MH)*(findmin( x -> abs(x - 6.8), unique_logAge)[2] - 1) + findmin( x -> abs(x - (-0.1)), unique_MH)[2], length(unique_MH)*(findmin( x -> abs(exp10(x-9) - 2.82), unique_logAge)[2] - 1) + findmin( x -> abs(x - (-1.0)), unique_MH)[2], length(unique_MH)*(findmin( x -> abs(exp10(x-9) - 12.59), unique_logAge)[2] - 1) + findmin( x -> abs(x - (-2.8)), unique_MH)[2] ) # findfirst(==(first(unique_MH)), unique_MH) ) fig,axs=plt.subplots(1,3,sharey=true,sharex=true,figsize=(15,5)) fig.subplots_adjust(hspace=0.0,wspace=0.075) im1 = axs[1].imshow(permutedims(templates[display_templates[1]]) ./ template_norm, origin="lower", extent=(extrema(edges[1])..., extrema(edges[2] .- distmod)...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=1e-8), rasterized=true) im2 = axs[2].imshow(permutedims(templates[display_templates[2]]) ./ template_norm, origin="lower", extent=(extrema(edges[1])..., extrema(edges[2] .- distmod)...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=1e-8), rasterized=true) im3 = axs[3].imshow(permutedims(templates[display_templates[3]]) ./ template_norm, origin="lower", extent=(extrema(edges[1])..., extrema(edges[2] .- distmod)...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=1e-8), rasterized=true) # im1.set_clim( [0.5, im1.get_clim()[2]] ) for ax in axs ax.set_xlabel(L"F090W$-$F150W") end axs[1].set_ylabel("F150W") axs[1].set_ylim(reverse(extrema(edges[2])).-distmod) axs[1].set_xlim(extrema(edges[1])) # Label the templates with their ages and metallicities axs[1].text(0.95,0.95,(@sprintf "Age: %.2f Gyr \n [M/H]: %.1f" exp10(template_logAge[display_templates[1]])/1e9 template_MH[display_templates[1]]), transform=axs[1].transAxes,ha="right",va="top") axs[2].text(0.1,0.95,(@sprintf "Age: %.2f Gyr \n [M/H]: %.1f" exp10(template_logAge[display_templates[2]])/1e9 template_MH[display_templates[2]]), transform=axs[2].transAxes,ha="left",va="top") axs[3].text(0.1,0.95,(@sprintf "Age: %.2f Gyr \n [M/H]: %.1f" exp10(template_logAge[display_templates[3]])/1e9 template_MH[display_templates[3]]), transform=axs[3].transAxes,ha="left",va="top") fig.colorbar(im1, ax=axs[1], pad=0.01) fig.colorbar(im2, ax=axs[2], pad=0.01) fig.colorbar(im3, ax=axs[3], pad=0.01) # Optionally, plot photometric error bars along the left. Errors are small in this model so they don't show very well. plot_errors::Bool = false if plot_errors # Set up points to show the photometric errors error_points = minimum(edges[2])+1.0:0.5:maximum(edges[2])-0.5 error_points_x = repeat([minimum(edges[1])+0.15], length(error_points)) # Plot the photometric error points for ax in axs ax.errorbar(error_points_x, error_points, yerr=F150W_error.(error_points), xerr=sqrt.(F090W_error.(error_points_x .+ error_points).^2 .+ F150W_error.(error_points).^2), ls="") # , fmt='', ecolor=None, elinewidth=None, capsize=None end end # Optionally, overplot the isochrones themselves for comparison plot_isochrones::Bool = true if plot_isochrones alphas = (0.5,0.5,0.5) for (i,t_i) in enumerate(display_templates) local good = findall( (table.MH .== template_MH[t_i]) .& (table.logAge .== template_logAge[t_i]) ) local iso_mags = [table.F090W[good][begin:end-1], table.F150W[good][begin:end-1]] # These are the absolute magnitudes we want from the isochrone. axs[i].scatter(iso_mags[1] .- iso_mags[2], iso_mags[2],marker=".", c="orange", s=1, alpha=alphas[i]) end end # plt.savefig("template_example.pdf",bbox_inches="tight") import StarFormationHistories: construct_x0_mdf, calculate_coeffs_mdf # Set overall stellar mass of complex population and metallicity evolution variables stellar_mass = 1e7 α, β, σ = 0.1, -1.87, 0.1 # -0.1, -0.5, 0.1 # max_logage sets the highest bin edge for the template_logAge # if template_logage was [6.6,6.7,6.8], I would set max_logAge=6.9, # representing the rightmost bin edge. In our case, our oldest template # is quite old, so we'll make it either the Hubble time or the maximum of # template_logAge plus the isochrone logage grid spacing, which is 0.05. # max_logAge = min(log10(13.7e9), maximum(template_logAge) + 0.05) T_max = min(13.7, exp10(maximum(unique_logAge)+ 0.05)/1e9) max_logAge = log10(T_max * 1e9) # Get initial guess for stellar mass coefficients. # We have to divide `stellar_mass` by `template_norm` here because `template_norm` is the total amount of stellar mass in each template, # since we passed `normalize_value=template_norm` when constructing the templates with `partial_cmd_smooth`. As such, when computing # coefficients, we need to normalize out the adopted `normalize_value` for the templates. x0_mdf = construct_x0_mdf(template_logAge, T_max; normalize_value=stellar_mass / template_norm) # Check sum of x0_mdf * template_norm; should equal stellar_mass sum(x0_mdf) * template_norm x0 = calculate_coeffs_mdf(x0_mdf, template_logAge, template_MH, T_max, α, β, σ) import StarFormationHistories: calculate_cum_sfr unique_template_logAge, cum_sfr_arr, sfr_arr, mean_mh_arr = calculate_cum_sfr(x0, template_logAge, template_MH, T_max; normalize_value=template_norm) fig,axs=plt.subplots(nrows=1,ncols=2,sharex=true,sharey=false,figsize=(10,5)) fig.subplots_adjust(hspace=0.0,wspace=0.35) axs[1].plot( vcat(exp10.(unique_template_logAge)./1e9, T_max), vcat(cum_sfr_arr, 0.0), label="Input SFH" ) axs[1].set_xlim([T_max,-0.1]) axs[1].set_ylim([0.0,1.1]) axs[1].set_xlabel("Lookback Time [Gyr]") axs[1].set_ylabel("Cumulative SF") axs[1].legend() axs[2].plot( vcat(exp10.(unique_template_logAge)./1e9, T_max), vcat(mean_mh_arr, β), label="Input SFH" ) axs[2].set_xlabel("Lookback Time [Gyr]") axs[2].set_ylabel(L"$\langle$[M/H]$\rangle$") # Look at the unique values of the SFRs unique(sfr_arr) fig,ax1 = plt.subplots() ax1.bar(unique_template_logAge, sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="Input SFH") ax1.set_xlabel(L"log$_{10}$(Age [yr])") ax1.set_ylabel(L"SFR [$10^{-3}$ M$_\odot$ / yr]") ax1.set_ylim([0.0, ax1.get_ylim()[2]]) ax1.legend() model1 = sum( x0 .* templates) model2 = similar(model1) import StarFormationHistories: composite! composite!(model2, x0, templates) model2 model1 ≈ model2 fig,ax1=plt.subplots() im1=ax1.imshow(permutedims(model2), origin="lower", extent=(extrema(edges[1])..., extrema(edges[2])...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=2.5), rasterized=true) ax1.set_xlabel(L"F090W$-$F150W") ax1.set_ylabel("F150W") ax1.set_ylim(reverse(extrema(edges[2]))) ax1.set_xlim(extrema(edges[1])) fig.colorbar(im1) import Distributions: Poisson import Random: AbstractRNG, default_rng function poisson_sample!(sample::T, model::S, rng::AbstractRNG=default_rng()) where {T <: AbstractArray{<:Number}, S <: AbstractArray{<:Number}} @assert axes(sample) == axes(model) for i in eachindex(sample, model) sample[i] = rand(rng,Poisson(model[i])) end end poisson_sample(model::AbstractArray{<:Number}, rng::AbstractRNG=default_rng()) = (sample = similar(model); poisson_sample!(sample, model, rng); return sample) model3 = poisson_sample(model2) fig,axs=plt.subplots(nrows=1,ncols=2,sharex=true,sharey=true,figsize=(10,5)) fig.subplots_adjust(hspace=0.0,wspace=0.0) im1=axs[1].imshow(permutedims(model2), origin="lower", extent=(extrema(edges[1])..., extrema(edges[2])...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=2.5), rasterized=true) axs[1].text(0.1,0.9,"Smooth",transform=axs[1].transAxes) axs[2].imshow(permutedims(model3), origin="lower", extent=(extrema(edges[1])..., extrema(edges[2])...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=2.5,vmax=im1.get_clim()[2]), rasterized=true) axs[2].text(0.1,0.9,"Poisson-Sampled",transform=axs[2].transAxes) axs[1].set_xlabel(L"F090W$-$F150W") axs[2].set_xlabel(L"F090W$-$F150W") axs[1].set_ylabel("F150W") axs[1].set_ylim(reverse(extrema(edges[2]))) axs[1].set_xlim(extrema(edges[1])) fig.colorbar(im1, ax=axs, pad=0.005) import StarFormationHistories: generate_stars_mass_composite, NoBinaries @time starcat = generate_stars_mass_composite(template_mini, template_isomags, ["F090W", "F150W"], stellar_mass, x0, imf; dist_mod=distmod, binary_model=NoBinaries()) # starcat_mags = reduce(hcat,reduce(vcat,starcat[2])) # To make a 2D matrix starcat_mags = reduce(vcat,starcat[2]) println(@sprintf("Number of sampled stars is %.2e",length(starcat_mags))) println(@sprintf("Stellar mass of sampled system at birth was %.2e",stellar_mass)) println(@sprintf("Present-day stellar mass of sampled system is %.2e",sum(reduce(vcat,starcat[1]))[1])) import StarFormationHistories: mag2flux, flux2mag println(@sprintf("Absolute magnitude of sampled system is M_{F090W} = %.2f",flux2mag( sum( x -> mag2flux(first(x)), starcat_mags) ) - distmod)) # Construct new / read cached catalog (for reproducibility) import StarFormationHistories: model_cmd read_obs_mags::Bool = true if read_obs_mags obs_mags = CSV.read(joinpath(@__DIR__,".ipynb_checkpoints/obs_mags.txt"), Table; delim=' ', header=1) obs_mags = permutedims([obs_mags.F090W obs_mags.F150W]) else # Apply observational effects to "pure" catalog obs_mags = model_cmd( starcat_mags, [F090W_error, F150W_error], [F090W_complete, F150W_complete]) # Concatenate into 2D matrix obs_mags = reduce(hcat,obs_mags) # Optionally write out to file; use .ipynb_checkpoints to prevent commit to github write_obs_mags::Bool = false if write_obs_mags CSV.write(joinpath(@__DIR__,".ipynb_checkpoints/obs_mags.txt"), Table(F090W=obs_mags[1,:], F150W=obs_mags[2,:]); delim=' ') end end display(obs_mags) # Total number of stars in the observed CMD space count( obs_mags[2,:] .< maximum(edges[2]) ) import StarFormationHistories: bin_cmd model4 = bin_cmd(view(obs_mags,1,:) .- view(obs_mags,2,:), view(obs_mags,2,:), edges=edges).weights fig,axs=plt.subplots(nrows=1,ncols=3,sharex=true,sharey=true,figsize=(15,5)) fig.subplots_adjust(hspace=0.0,wspace=0.0) im1=axs[3].imshow(permutedims(model2), origin="lower", extent=(extrema(edges[1])..., extrema(edges[2])...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=2.5), rasterized=true) axs[3].text(0.1,0.9,"c) Smooth Model",transform=axs[3].transAxes) axs[1].scatter(view(obs_mags,1,:) .- view(obs_mags,2,:), view(obs_mags,2,:), s=1, marker=".", c="k", alpha=0.05, rasterized=true, label="CMD-Sampled") axs[1].text(0.1,0.9,"a) Sampled CMD",transform=axs[1].transAxes) axs[2].imshow(permutedims(model4), origin="lower", extent=(extrema(edges[1])..., extrema(edges[2])...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=2.5,vmax=im1.get_clim()[2]), rasterized=true, label="CMD-Sampled") axs[2].text(0.1,0.9,"b) Sampled Hess Diagram",transform=axs[2].transAxes) axs[1].set_xlabel(L"F090W$-$F150W") axs[2].set_xlabel(L"F090W$-$F150W") axs[3].set_xlabel(L"F090W$-$F150W") axs[1].set_ylabel("F150W") axs[1].set_ylim(reverse(extrema(edges[2]))) axs[1].set_xlim(extrema(edges[1])) fig.colorbar(im1, ax=axs, pad=0.005) # plt.savefig("mcsample_example.pdf",bbox_inches="tight") # fig.colorbar(im1) fig,axs=plt.subplots(nrows=1,ncols=3,sharex=true,sharey=true,figsize=(15,5)) fig.subplots_adjust(hspace=0.0,wspace=0.0) im1=axs[1].imshow(permutedims(model2), origin="lower", extent=(extrema(edges[1])..., extrema(edges[2])...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=2.5), rasterized=true) axs[1].text(0.1,0.9,"Smooth Model",transform=axs[1].transAxes) axs[2].imshow(permutedims(model3), origin="lower", extent=(extrema(edges[1])..., extrema(edges[2])...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=2.5,vmax=im1.get_clim()[2]), rasterized=true) axs[2].text(0.1,0.9,"Poisson-Sampled",transform=axs[2].transAxes) axs[3].imshow(permutedims(model4), origin="lower", extent=(extrema(edges[1])..., extrema(edges[2])...), aspect="auto", cmap="Greys", norm=plt.matplotlib.colors.LogNorm(vmin=2.5,vmax=im1.get_clim()[2]), rasterized=true, label="CMD-Sampled") axs[3].text(0.1,0.9,"CMD-Sampled",transform=axs[3].transAxes) axs[1].set_xlabel(L"F090W$-$F150W") axs[2].set_xlabel(L"F090W$-$F150W") axs[3].set_xlabel(L"F090W$-$F150W") axs[1].set_ylabel("F150W") axs[1].set_ylim(reverse(extrema(edges[2]))) axs[1].set_xlim(extrema(edges[1])) fig.colorbar(im1, ax=axs, pad=0.005) # Choose which sampled Hess diagram to use for model fitting; choosing CMD-sampled instance here. data = model4 import StarFormationHistories: fit_templates_mdf mdf_α, mdf_β, mdf_σ = 0.15, -2.25, 0.2 # -0.15, -0.2, 0.2 # initial guess for the metallicity evolution parameters mdf_result = fit_templates_mdf(templates, data, template_logAge, template_MH, T_max; x0=vcat(construct_x0_mdf(template_logAge, T_max; normalize_value=1e4), [mdf_α,mdf_β,mdf_σ])) mdf_coeffs = calculate_coeffs_mdf(mdf_result.map.μ, template_logAge, template_MH, T_max) mdf_coeffs *= template_norm _, mdf_cum_sfr_arr, mdf_sfr_arr, mdf_mean_mh_arr = calculate_cum_sfr(mdf_coeffs, template_logAge, template_MH, T_max) import Measurements: uncertainty, ± mdf_result.map.μ .± mdf_result.map.σ import Statistics: mean, median, quantile # # This is effectively what rand(mdf_result.map, 10000) is doing. # mdf_dist = MvNormal(Optim.minimizer(mdf_result.map.result), # Hermitian(Optim.trace(mdf_result.map.result)[end].metadata["~inv(H)"])) # mdf_sample = rand(mdf_dist, 10000) # # Transform the variables, noting that the stellar mass coefficients `mdf_sample[begin:end-3,:]` # # and σ `mdf_sample[end,:]` are fit with logarithmic transformations so we have to transform them back. # # α and β are optimized directly, without a transformation. # @views mdf_sample[begin:end-3,:] .= exp.(mdf_sample[begin:end-3,:]) .* template_norm # @views mdf_sample[end,:] .= exp.(mdf_sample[end,:]) mdf_sample = rand(mdf_result.map, 10000) @views mdf_sample[begin:end-3,:] *= template_norm # Correct the stellar mass coefficients for the template normalization # Calculate the cumulative SFH for each point in the sample # and find the 1-σ range mdf_cum_sfr = Vector{Vector{Float64}}(undef,0) mdf_sfr = Vector{Vector{Float64}}(undef,0) mdf_mh = Vector{Vector{Float64}}(undef,0) for x in eachcol(mdf_sample) tmp_coeffs = calculate_coeffs_mdf(x, template_logAge, template_MH, T_max) _, mdf_1, mdf_2, mdf_3 = calculate_cum_sfr(tmp_coeffs, template_logAge, template_MH, T_max) push!(mdf_cum_sfr, mdf_1) push!(mdf_sfr, mdf_2) push!(mdf_mh, mdf_3) end mdf_cum_sfr = reduce(hcat, mdf_cum_sfr) mdf_sfr = reduce(hcat, mdf_sfr) mdf_mh = reduce(hcat, mdf_mh) # Now calculate quantiles mdf_cum_lower = quantile.(eachrow(mdf_cum_sfr), 0.16) mdf_cum_med = median.(eachrow(mdf_cum_sfr)) mdf_cum_upper = quantile.(eachrow(mdf_cum_sfr), 0.84) mdf_sfr_lower = quantile.(eachrow(mdf_sfr), 0.16) mdf_sfr_med = median.(eachrow(mdf_sfr)) mdf_sfr_upper = quantile.(eachrow(mdf_sfr), 0.84) mdf_mh_lower = quantile.(eachrow(mdf_mh), 0.16) mdf_mh_med = median.(eachrow(mdf_mh)) mdf_mh_upper = quantile.(eachrow(mdf_mh), 0.84); println((α, β, σ)) mdf_sample[end-2:end,:] @pyimport corner # Easy marginal distributions fig=plt.figure(figsize=(7,7)) # fig.suptitle("Linear M/H") corner.corner( permutedims(@view mdf_sample[end-2:end,:]), fig=fig, labels=[L"\alpha", L"\beta", L"\sigma"], quantiles=[0.16,0.5,0.84], max_n_ticks=4, show_titles=false, title_kwargs=Dict("fontsize"=>17), label_kwargs=Dict("fontsize"=>25)) fig.subplots_adjust(hspace=0.0, wspace=0.0) # Now plot cumulative SFH and MH evolution for the metallicity evolution model fig,axs=plt.subplots(nrows=1,ncols=2,sharex=true,sharey=false,figsize=(10,5)) fig.subplots_adjust(hspace=0.0,wspace=0.35) axs[1].plot( vcat(exp10.(unique_template_logAge)./1e9, T_max), vcat(mdf_cum_sfr_arr,0.0), label="Result" ) # This is MAP result # axs[1].plot( exp10.(unique_template_logAge)./1e9, mdf_cum_med, label="Result" ) # This is median of samples axs[1].plot( vcat(exp10.(unique_template_logAge)./1e9, T_max), vcat(cum_sfr_arr,0.0), label="Input SFH", ls="--" ) axs[1].fill_between( vcat(exp10.(unique_template_logAge)./1e9, T_max), vcat(mdf_cum_lower,0.0), vcat(mdf_cum_upper,0.0), alpha=0.3, fc="k") axs[1].set_xlim([T_max,0.0]) axs[1].set_ylim([0.0,1.05]) axs[1].set_xlabel("Lookback Time [Gyr]") axs[1].set_ylabel("Cumulative SF") axs[1].legend() axs[2].plot( vcat( exp10.(unique_template_logAge)./1e9, T_max), vcat(mdf_mh_med, mdf_result.map.μ[end-1]), label="Result" ) axs[2].fill_between( vcat(exp10.(unique_template_logAge)./1e9, T_max), vcat(mdf_mh_lower, mdf_result.map.μ[end-1]), vcat(mdf_mh_upper, mdf_result.map.μ[end-1]), alpha=0.3, fc="k") axs[2].plot( vcat(exp10.(unique_template_logAge)./1e9, T_max), vcat(mean_mh_arr, β), label="Input AMR", ls="--" ) axs[2].set_xlabel("Lookback Time [Gyr]") axs[2].set_ylabel(L"$\langle$[M/H]$\rangle$") axs[2].legend() # plt.savefig("example_cumsfh.pdf", bbox_inches="tight") [ vcat(exp10.(unique_template_logAge)./1e9, T_max) 1.0.-vcat(cum_sfr_arr,0.0) ] # Construct an interpolator that, given a fraction of total stellar mass `x`, will find the lookback time `t` # at which the fraction of stellar mass formed *more recently* than `t` is `x`. import Interpolations: extrapolate, interpolate, Gridded, Linear, Flat # recent_sfh_interp = extrapolate(interpolate((vcat(exp10.(unique_template_logAge)./1e9, T_max),), 1 .- vcat(cum_sfr_arr,0.0), Gridded(Linear())), Flat()) sfh_quantile = extrapolate(interpolate((1 .- vcat(cum_sfr_arr,0.0),), vcat(exp10.(unique_template_logAge)./1e9, T_max), Gridded(Linear())), Flat()); log10(sfh_quantile(0.001)) + 9 # Now plot the SFRs for the metallicity evolution model fig,ax1 = plt.subplots(figsize=(7,7)) ax1.tick_params(axis="both", which="both", labelsize=16) # ax1.bar(unique_template_logAge, mdf_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", ax1.bar(unique_template_logAge, mdf_sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", yerr = [(mdf_sfr_med .- mdf_sfr_lower) .* 1e3, (mdf_sfr_upper .- mdf_sfr_med) .* 1e3], capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="Result") ax1.bar(unique_template_logAge, sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="Input SFRs", alpha=0.5) ax1.set_xlabel("log(Age [yr])", fontsize=20) ax1.set_ylabel(L"SFR [$10^{-3}$ M$_\odot$ / yr]", fontsize=20) ax1.set_ylim([0.0, 9.0]) # ax1.set_ylim([0.0, ax1.get_ylim()[2]]) # Draw vertical lines to denote times at which `x` fraction of stellar mass was formed more recently for q in ("0.01", "0.001") laq = log10(sfh_quantile(parse(Float64, q))) + 9 println(laq) ax1.axvline(laq, ls="--", c="red") # ax1.text( log10(sfh_quantile(qn)) + 9, ax1.get_ylim()[2] - 1, L"$\leftarrow$ \n $ %$q \text{M}_*$", transform=ax1.transData, ha="right", va="top") t = ax1.text(laq - 0.02, ax1.get_ylim()[2] - 0.5, "\$\\leftarrow\$ \n \$ $q \\, \\text{M}_*\$", transform=ax1.transData, ha="right", va="top", fontsize=20) t.set_bbox(Dict("facecolor"=>"red", "alpha"=>0.25, "edgecolor"=>"red", "lw"=>2, "boxstyle"=>"round")) end ax1.legend(fontsize=20) # plt.savefig("example_sfrs_hires.pdf", bbox_inches="tight") import Statistics: cor cor(mdf_sample[1:5,:]; dims=2) # Young stellar pops show minimal correlation plt.imshow(cor(mdf_sample[1:10,:]; dims=2)) plt.colorbar() # Old stellar pops show greater correlations plt.imshow(cor(mdf_sample[end-20:end-3,:]; dims=2), clim=(-1,1)) plt.colorbar() fig=plt.figure(figsize=(7,7)) corner.corner( permutedims(@view mdf_sample[end-20:end-10,:]) .* 1e3, # corner.corner( permutedims(@view mdf_sfr_sample[1:5,:]) .* 1e3, fig=fig, quantiles=[0.16,0.5,0.84], max_n_ticks=0, # turn ticks off, illustrating shape only show_titles=false, title_kwargs=Dict("fontsize"=>17), label_kwargs=Dict("fontsize"=>25)) fig.subplots_adjust(hspace=0.0, wspace=0.0) # Make integrated MDF plot import StarFormationHistories: mdf_amr MDF_x, MDF = mdf_amr(mdf_coeffs, template_logAge, template_MH) fig, ax1 = plt.subplots() ax1.bar(MDF_x, MDF, -abs(MDF_x[2] - MDF_x[3]), align="edge", linewidth=1, edgecolor="k") ax1.set_xlabel("[M/H]") ax1.set_ylabel("Probability") # Equivalent to sum(mdf_coeffs[template_logAge .<= maximum(unique_template_logAge[1:5])]) # sum(mdf_result.map.μ[1:5] .* template_norm) sum(mdf_result.map.μ[findall(<(7), unique_logAge)] .* template_norm) sum(mdf_result.map.μ[findall(<(7), unique_logAge)] .* template_norm) / stellar_mass * 100 # Compute change in time [yr] between bins of logAge template_δt = diff(vcat(exp10.(unique_template_logAge), T_max * 1e9)) # exp10(max_logAge))) sfr_norm = 1e-3 # Star formation rate in solar masses / yr to normalize templates to # Now normalize the templates: # SFR = stellar mass / dt; sfr_norm = A * template_norm / dt; where A is a multiplicative constant # since we originally normalized all the templates to contain stellar mass = `template_norm` # Solve for A = sfr_norm * dt / template_norm sfr_templates = deepcopy(templates) for i in eachindex(sfr_templates) idx = findfirst(==(template_logAge[i]), unique_template_logAge) A = sfr_norm * template_δt[idx] / template_norm sfr_templates[i] .*= A end # And now fit mdf_sfr_result = fit_templates_mdf(sfr_templates, data, template_logAge, template_MH, T_max; x0=vcat(fill(1,length(unique_template_logAge)), [mdf_α, mdf_β, mdf_σ])) # # This is effectively what rand(mdf_sfr_result.map, 10000) is doing. # # Generate sample from the inverse Hessian matrix # mdf_sfr_dist = MvNormal(Optim.minimizer(mdf_sfr_result.map.result), # Hermitian(Optim.trace(mdf_sfr_result.map.result)[end].metadata["~inv(H)"])) # mdf_sfr_sample = rand(mdf_sfr_dist, 10000) # # Transform the variables, noting that the SFR coefficients `mdf_sfr_sample[begin:end-3,:]`, α `mdf_sfr_sample[end-2,:]`, # # and σ `mdf_sfr_sample[end,:]` are fit with logarithmic transformations so we have to transform them back. # # β is optimized directly, without a transformation, as it is allowed to be negative. # @views mdf_sfr_sample[begin:end-2,:] .= exp.(mdf_sfr_sample[begin:end-2,:]) .* sfr_norm # @views mdf_sfr_sample[end,:] .= exp.(mdf_sfr_sample[end,:]); mdf_sfr_sample = rand(mdf_sfr_result.map, 10000) @views mdf_sfr_sample[begin:end-3,:] *= sfr_norm # Correct the SFR coefficients for the template normalization # Now calculate quantiles mdf_sfr_norm_lower = quantile.(eachrow(@view mdf_sfr_sample[begin:end-3,:]), 0.16) mdf_sfr_norm_med = median.(eachrow(@view mdf_sfr_sample[begin:end-3,:])) mdf_sfr_norm_upper = quantile.(eachrow(@view mdf_sfr_sample[begin:end-3,:]), 0.84); fig,axs = plt.subplots(nrows=1,ncols=2,sharex=true,sharey=true,figsize=(14,7)) fig.subplots_adjust(hspace=0.0,wspace=0.1) axs[1].bar(unique_template_logAge, mdf_sfr_norm_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", # axs[1].bar(unique_template_logAge, mdf_sfr_result.map.μ[begin:end-3] .* 1e3 .* sfr_norm; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", # axs[1].bar(unique_template_logAge, mdf_sfr_result.mle.μ[begin:end-3] .* 1e3 .* sfr_norm; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", yerr = [(mdf_sfr_norm_med .- mdf_sfr_norm_lower) .* 1e3, (mdf_sfr_norm_upper .- mdf_sfr_norm_med) .* 1e3], capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="Result, SFR Normalized") axs[1].bar(unique_template_logAge, sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="Input SFRs", alpha=0.5) axs[1].set_xlabel("log(Age [yr])") axs[1].set_ylabel(L"SFR [$10^{-3}$ M$_\odot$ / yr]") axs[1].set_ylim([0.0, axs[1].get_ylim()[2]]) axs[1].legend() axs[2].bar(unique_template_logAge, mdf_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", yerr = [(mdf_sfr_med .- mdf_sfr_lower) .* 1e3, (mdf_sfr_upper .- mdf_sfr_med) .* 1e3], capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="Result, Stellar Mass Normalized") axs[2].bar(unique_template_logAge, sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="Input SFRs", alpha=0.5) axs[2].legend() axs[2].set_xlabel("log(Age [yr])") # new_α, new_β, new_σ = 0.15, -2.25, 0.2 # -0.15, -0.2, 0.2 new_α, new_β, new_σ = α, β, σ met_func(logAge) = (T_max - exp10(logAge)/1e9) * new_α + new_β met_func_true(logAge) = (T_max - exp10(logAge)/1e9) * α + β laxplot = 6.6:0.01:10.2 fig,ax1=plt.subplots() ax1.plot(exp10.(laxplot)./1e9, met_func.(laxplot),label="Guess",zorder=0) ax1.plot(exp10.(laxplot)./1e9, met_func_true.(laxplot),label="Input",ls="--",zorder=1) ax1.set_xlim([13.0,-0.1]) ax1.set_xlabel("Lookback Time [Gyr]") ax1.set_ylabel(L"$\langle$[M/H]$\rangle$") ax1.legend(loc="upper left") # Single-threaded with push!, easier with this example free_templates = Vector{Matrix{Float64}}(undef,0) free_template_logAge = Vector{Float64}(undef,0) free_template_MH = Vector{Float64}(undef,0) for logage in unique_logAge mean_met = met_func(logage) for mh in unique_MH # If the current mh is more than `new_σ` dex away from mean, skip template abs(mh - mean_met) > new_σ && continue local good = findall( (table.MH .== mh) .& (table.logAge .== logage) ) # Chop off the last star because its the 30 mag weird thing that parsec does. local m_ini = table.Mini[good][begin:end-1] local iso_mags = [table.F090W[good][begin:end-1], table.F150W[good][begin:end-1]] push!(free_templates, partial_cmd_smooth( m_ini, iso_mags, [F090W_error, F150W_error], 2, [1,2], imf, [F090W_complete, F150W_complete]; dmod=distmod, normalize_value=template_norm, edges=edges).weights ) push!(free_template_logAge, logage) push!(free_template_MH, mh) end end # `templates` has 1846 entries, free_templates has ~10% of that. length(free_templates) import StarFormationHistories: fit_templates_lbfgsb, construct_x0 lbfgsb_result = fit_templates_lbfgsb(free_templates, data; x0=construct_x0(free_template_logAge, T_max; normalize_value=template_norm)) # Calculate the cumulative SFH, SFRs, <[M/H]>(logAge) from the above result free_coeffs = lbfgsb_result[2] .* template_norm unique_free_template_logAge, free_cum_sfr_arr, free_sfr_arr, free_mean_mh_arr = calculate_cum_sfr(free_coeffs, free_template_logAge, free_template_MH, T_max) fig,axs=plt.subplots(nrows=1,ncols=2,sharex=true,sharey=false,figsize=(10,5)) fig.subplots_adjust(hspace=0.0,wspace=0.35) axs[1].plot( exp10.(unique_free_template_logAge)./1e9, free_cum_sfr_arr, label="LBFGSB Fit" ) axs[1].plot( exp10.(unique_template_logAge)./1e9, cum_sfr_arr, label="Input SFH", ls="--" ) axs[1].set_xlim([13.0,-0.1]) axs[1].set_ylim([0.0,1.1]) axs[1].set_xlabel("Lookback Time [Gyr]") axs[1].set_ylabel("Cumulative SF") axs[1].legend() axs[2].plot( exp10.(unique_free_template_logAge)./1e9, free_mean_mh_arr, label="LBFGSB Fit" ) axs[2].plot( exp10.(unique_template_logAge)./1e9, mean_mh_arr, label="Input SFH", ls="--" ) axs[2].set_xlabel("Lookback Time [Gyr]") axs[2].set_ylabel(L"$\langle$[M/H]$\rangle$") import StarFormationHistories: fit_templates bfgs_result = fit_templates(free_templates, data; x0=construct_x0(free_template_logAge, T_max; normalize_value=template_norm)) bfgs_result.mle.μ .- lbfgsb_result[2] bfgs_result.map.μ bfgs_result.map.σ hessian_sample = rand(bfgs_result.map, 10000) .* template_norm # The above is effectively doing # hessian_dist = Distributions.MvNormal(Optim.minimizer(bfgs_result.map.result), # LinearAlgebra.Hermitian(bfgs_result.map.invH)) # hessian_sample = exp.(rand(hessian_dist, 10000)) .* template_norm # Calculate the cumulative SFH for each sample # and find the 1-σ quantile range for both the cumulative SFH and the SFRs hessian_cum_sfr = Vector{Vector{Float64}}(undef,0) hessian_sfr = Vector{Vector{Float64}}(undef,0) for x in eachcol(hessian_sample) _, hessian_1, hessian_2, hessian_mh = calculate_cum_sfr(x, free_template_logAge, free_template_MH, T_max) push!(hessian_cum_sfr, hessian_1) push!(hessian_sfr, hessian_2) end hessian_cum_sfr = reduce(hcat, hessian_cum_sfr) hessian_sfr = reduce(hcat, hessian_sfr) # Now calculate quantiles hessian_cum_lower = quantile.(eachrow(hessian_cum_sfr), 0.16) hessian_cum_med = median.(eachrow(hessian_cum_sfr)) hessian_cum_upper = quantile.(eachrow(hessian_cum_sfr), 0.84) hessian_sfr_lower = quantile.(eachrow(hessian_sfr), 0.16) hessian_sfr_med = median.(eachrow(hessian_sfr)) hessian_sfr_upper = quantile.(eachrow(hessian_sfr), 0.84) # Now plot cumulative SFH and MH evolution fig,axs=plt.subplots(nrows=1,ncols=2,sharex=true,sharey=false,figsize=(10,6)) fig.subplots_adjust(hspace=0.0,wspace=0.35) # axs[1].plot( exp10.(unique_free_template_logAge)./1e9, cum_sfr_arr, label="Result" ) axs[1].plot( exp10.(unique_free_template_logAge)./1e9, hessian_cum_med, label="Result" ) axs[1].plot( exp10.(unique_template_logAge)./1e9, cum_sfr_arr, label="Input SFH", ls="--" ) axs[1].fill_between( exp10.(unique_free_template_logAge)./1e9, hessian_cum_lower, hessian_cum_upper, alpha=0.3, fc="k") axs[1].set_xlim([13.0,-0.1]) axs[1].set_ylim([0.0,1.1]) axs[1].set_xlabel("Lookback Time [Gyr]") axs[1].set_ylabel("Cumulative SF") axs[1].legend() axs[2].plot( exp10.(unique_free_template_logAge)./1e9, free_mean_mh_arr, label="Result" ) axs[2].plot( exp10.(unique_template_logAge)./1e9, mean_mh_arr, label="Input", ls="--" ) # axs[2].plot( exp10.(unique_template_logAge)./1e9, met_func.(unique_template_logAge), c="k", label="Guess",ls="--") axs[2].set_xlabel("Lookback Time [Gyr]") axs[2].set_ylabel(L"$\langle$[M/H]$\rangle$") axs[2].legend() import Measurements: uncertainty, ± hessian_sfrerr = uncertainty.(calculate_cum_sfr(bfgs_result.map.μ .± bfgs_result.map.σ, free_template_logAge, free_template_MH, T_max; normalize_value=template_norm)[3]) .* 1e3 fig,ax1 = plt.subplots(figsize=(7,7)) # ax1.bar(unique_free_template_logAge[begin:end-1], free_sfr_arr[begin:end-1] .* 1e3; width=diff(unique_free_template_logAge), align="edge", yerr = hessian_sfrerr[begin:end-1], capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="LBFGS Fit") ax1.bar(unique_free_template_logAge, hessian_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", yerr = hessian_sfrerr, capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="BFGS Fit") ax1.bar(unique_template_logAge, sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="Input SFRs", alpha=0.5) ax1.set_xlabel("log(Age [yr])") ax1.set_ylabel(L"SFR [$10^{-3}$ M$_\odot$ / yr]") ax1.set_ylim([0.0, ax1.get_ylim()[2]]) ax1.legend() fig,ax1 = plt.subplots(figsize=(7,7)) ax1.bar(unique_free_template_logAge, hessian_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", yerr = [(hessian_sfr_med .- hessian_sfr_lower) .* 1e3, (hessian_sfr_upper .- hessian_sfr_med) .* 1e3], capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="BFGS Fit") ax1.bar(unique_template_logAge, sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="Input SFH", alpha=0.5) ax1.set_xlabel("log(Age [yr])") ax1.set_ylabel(L"SFR [$10^{-3}$ M$_\odot$ / yr]") ax1.set_ylim([0.0, ax1.get_ylim()[2]]) ax1.legend() import StarFormationHistories: mcmc_sample import Distributions: MvNormal import LinearAlgebra: I nwalkers = 1000 nsteps = 2000 burn_idx = 200 # Walker initial positions sampled from a multivariate normal distribution with means equal to the BFGS MLE # and identity covariance matrix. The `max` call makes sure none of the cofficients have initial conditions less than 0. mcmc_x0 = [max.(0.0, rand(MvNormal(bfgs_result.mle.μ, I))) for i in 1:nwalkers] mcmc_result = mcmc_sample(free_templates, data, mcmc_x0, nwalkers, nsteps; nthin=5); # nthin=5 to only save every 5 steps import StatsBase: fit, Histogram, normalize fig, (ax0, ax1) = plt.subplots(nrows=1,ncols=2,sharex=false,sharey=false,figsize=(10,6)) fig.subplots_adjust(hspace=0.0,wspace=0.2) ax0.axvline(burn_idx) for i in 1:size(mcmc_result,3) ax0.plot(first(axes(mcmc_result)), mcmc_result[:,end,i], alpha=0.1, rasterized=true) end ax1_hist = normalize(fit(Histogram, vec(mcmc_result[burn_idx:end,end,:]); nbins=100, closed=:left); mode=:probability) #:pdf, :density, :probability, :none ax1.bar(first(ax1_hist.edges)[2:end], ax1_hist.weights, width=step(first(ax1_hist.edges)), ec="k"); # fc="k" import DynamicHMC import StarFormationHistories: hmc_sample mc_result = hmc_sample(free_templates, data, 1000; reporter=DynamicHMC.ProgressMeterReport()); DynamicHMC.Diagnostics.summarize_tree_statistics(mc_result.tree_statistics) mc_matrix = exp.(mc_result.posterior_matrix) .* template_norm # Calculate the cumulative SFH for each sample in the chain # and find the 1-σ range mc_cum_sfr = Vector{Vector{Float64}}(undef,0) mc_sfr = Vector{Vector{Float64}}(undef,0) for x in eachcol(mc_matrix) _, mc_1, mc_2, mc_mh = calculate_cum_sfr(x, free_template_logAge, free_template_MH, T_max) push!(mc_cum_sfr, mc_1) push!(mc_sfr, mc_2) end mc_cum_sfr = reduce(hcat, mc_cum_sfr) # hcat( mc_cum_sfr... ) mc_sfr = reduce(hcat, mc_sfr) # hcat( mc_sfr... ) # Calculate quantiles mc_cum_lower = quantile.(eachrow(mc_cum_sfr), 0.16) mc_cum_med = median.(eachrow(mc_cum_sfr)) mc_cum_upper = quantile.(eachrow(mc_cum_sfr), 0.84) mc_sfr_lower = quantile.(eachrow(mc_sfr), 0.16) mc_sfr_med = median.(eachrow(mc_sfr)) mc_sfr_upper = quantile.(eachrow(mc_sfr), 0.84) fig,axs=plt.subplots(nrows=1,ncols=2,sharex=true,sharey=true,figsize=(10,6)) fig.subplots_adjust(hspace=0.0,wspace=0.1) axs[1].plot( exp10.(unique_free_template_logAge)./1e9, mc_cum_med, label="HMC Median" ) axs[1].plot( exp10.(unique_template_logAge)./1e9, cum_sfr_arr, label="Input SFH", ls="--" ) axs[1].fill_between( exp10.(unique_free_template_logAge)./1e9, mc_cum_lower, mc_cum_upper, alpha=0.3, fc="k") axs[1].set_xlim([13.0,-0.1]) axs[1].set_ylim([0.0,1.1]) axs[1].set_xlabel("Lookback Time [Gyr]") axs[1].set_ylabel("Cumulative SF") axs[1].legend() axs[2].plot( exp10.(unique_free_template_logAge)./1e9, hessian_cum_med, label="BFGS Result" ) axs[2].plot( exp10.(unique_template_logAge)./1e9, cum_sfr_arr, label="Input SFH", ls="--" ) axs[2].fill_between( exp10.(unique_free_template_logAge)./1e9, hessian_cum_lower, hessian_cum_upper, alpha=0.3, fc="k") axs[2].set_xlabel("Lookback Time [Gyr]") axs[2].legend() fig,axs = plt.subplots(nrows=1,ncols=2,sharex=true,sharey=true,figsize=(14,7)) fig.subplots_adjust(hspace=0.0,wspace=0.1) axs[1].bar(unique_free_template_logAge, mc_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", yerr = [(mc_sfr_med .- mc_sfr_lower) .* 1e3, (mc_sfr_upper .- mc_sfr_med) .* 1e3], capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="HMC Result") axs[1].bar(unique_template_logAge, sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="Input SFRs", alpha=0.5) axs[1].set_xlabel("log(Age [yr])") axs[1].set_ylabel(L"SFR [$10^{-3}$ M$_\odot$ / yr]") axs[1].legend() axs[2].bar(unique_free_template_logAge, hessian_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", yerr = [(hessian_sfr_med .- hessian_sfr_lower) .* 1e3, (hessian_sfr_upper .- hessian_sfr_med) .* 1e3], capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="BFGS Fit") axs[2].bar(unique_template_logAge, sfr_arr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="Input SFRs", alpha=0.5) axs[2].legend() axs[2].set_xlabel("log(Age [yr])") # _, _, tmp_sfr, _ = calculate_cum_sfr(bfgs_result.map.μ .* template_norm, free_template_logAge, free_template_MH) fig,ax1 = plt.subplots(figsize=(7,7)) ax1.bar(unique_free_template_logAge, mc_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="HMC Result") # ax1.bar(unique_free_template_logAge, hessian_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="BFGS Fit", alpha=0.5) ax1.bar(unique_free_template_logAge, hessian_sfr_med .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", yerr = [(hessian_sfr_med .- hessian_sfr_lower) .* 1e3, (hessian_sfr_upper .- hessian_sfr_med) .* 1e3], capsize=3, error_kw=Dict("elinewidth"=>1,"capthick"=>1), label="BFGS Fit", alpha=0.5) # ax1.bar(unique_free_template_logAge, tmp_sfr .* 1e3; width=diff(vcat(unique_template_logAge,max_logAge)), align="edge", label="BFGS Fit", alpha=0.5) ax1.set_xlabel("log(Age [yr])") ax1.set_ylabel(L"SFR [$10^{-3}$ M$_\odot$ / yr]") ax1.set_ylim([0.0, ax1.get_ylim()[2]]) ax1.legend() GC.gc() # Explicit garbage collection when finished