diff --git a/docs/Project.toml b/docs/Project.toml index d4330d85e..cc62f8d8e 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,6 +1,6 @@ [deps] -ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c" CloudMicrophysics = "6a9e3e04-43cd-43ba-94b9-e8782df3c71b" Dierckx = "39dd38d3-220a-591b-8e3c-4c3a8c710a94" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" diff --git a/docs/src/AerosolActivation.md b/docs/src/AerosolActivation.md index a753f0da4..67edb9d9b 100644 --- a/docs/src/AerosolActivation.md +++ b/docs/src/AerosolActivation.md @@ -278,3 +278,8 @@ make_ARG_figX(5) ![](Abdul-Razzak_and_Ghan_fig_3.svg) ![](Abdul-Razzak_and_Ghan_fig_4.svg) ![](Abdul-Razzak_and_Ghan_fig_5.svg) + +## Aerosol activation prediction with ML emulators +The CloudMicrophysics package offers an advanced feature for predicting the aerosol activation fraction using machine-learned emulators. Users have access to a function that utilizes pre-trained models for the prediction of activation fraction. Additionally, the package includes tools for training machine learning models, tailored to specific datasets or needs. The training process can benefit from incorporating the ARG activation equation, enhancing the relevance of training data. Emulator training, covering neural networks, Gaussian processes, and EvoTrees, is showcased in `test/aerosol_activation_emulators.jl`. + +Using ML emulators for predicting aerosol activation is provided through an extension to the main package. This extension will be loaded with `CloudMicrophysics.jl` if both `MLJ.jl` and `DataFrames.jl` are loaded by the user as well. diff --git a/ext/Common.jl b/ext/Common.jl index b09274649..7c93cc21e 100644 --- a/ext/Common.jl +++ b/ext/Common.jl @@ -2,6 +2,10 @@ import CSV import DataFrames as DF using DataFramesMeta +import CloudMicrophysics.AerosolModel as AM +import CloudMicrophysics.AerosolActivation as AA +import Thermodynamics.Parameters as TDP + function get_num_modes(df::DataFrame) i = 1 while true @@ -68,6 +72,83 @@ function preprocess_aerosol_data(X::DataFrame) return X end +function get_ARG_act_frac( + data_row::NamedTuple, + ap::CMP.AerosolActivationParameters, + aip::CMP.AirProperties, + tps::TDP.ThermodynamicsParameters, + FT::DataType, +) + + num_modes = get_num_modes(data_row) + @assert num_modes > 0 + mode_Ns = [] + mode_means = [] + mode_stdevs = [] + mode_kappas = [] + w = data_row.velocity + T = data_row.initial_temperature + p = data_row.initial_pressure + for i in 1:num_modes + push!(mode_Ns, data_row[Symbol("mode_$(i)_N")]) + push!(mode_means, data_row[Symbol("mode_$(i)_mean")]) + push!(mode_stdevs, data_row[Symbol("mode_$(i)_stdev")]) + push!(mode_kappas, data_row[Symbol("mode_$(i)_kappa")]) + end + ad = AM.AerosolDistribution( + Tuple( + AM.Mode_κ( + mode_means[i], + mode_stdevs[i], + mode_Ns[i], + FT(1), + FT(1), + FT(0), + FT(mode_kappas[i]), + 1, + ) for i in 1:num_modes + ), + ) + pv0 = TD.saturation_vapor_pressure(tps, FT(T), TD.Liquid()) + vapor_mix_ratio = pv0 / TD.Parameters.molmass_ratio(tps) / (p - pv0) + q_vap = vapor_mix_ratio / (vapor_mix_ratio + 1) + q = TD.PhasePartition(FT(q_vap), FT(0), FT(0)) + + return collect( + AA.N_activated_per_mode(ap, ad, aip, tps, FT(T), FT(p), FT(w), q), + ) ./ mode_Ns +end + +function get_ARG_act_frac( + X::DataFrame, + ap::CMP.AerosolActivationParameters, + aip::CMP.AirProperties, + tps::TDP.ThermodynamicsParameters, + FT::DataType, +) + return transpose( + hcat(get_ARG_act_frac.(NamedTuple.(eachrow(X)), ap, aip, tps, FT)...), + ) +end + +function preprocess_aerosol_data_with_ARG_act_frac( + X::DataFrame, + ap::CMP.AerosolActivationParameters, + aip::CMP.AirProperties, + tps::TDP.ThermodynamicsParameters, + FT::DataType, +) + f(y) = get_ARG_act_frac(y, ap, aip, tps, FT) + num_modes = get_num_modes(X) + X = DF.transform( + X, + AsTable(All()) => + ByRow(x -> f(x)) => + [Symbol("mode_$(i)_ARG_act_frac") for i in 1:num_modes], + ) + return preprocess_aerosol_data(X) +end + function target_transform(act_frac) return @. atanh(2.0 * 0.99 * (act_frac - 0.5)) end diff --git a/ext/EmulatorModelsExt.jl b/ext/EmulatorModelsExt.jl index 4926667f4..1f093ae7e 100644 --- a/ext/EmulatorModelsExt.jl +++ b/ext/EmulatorModelsExt.jl @@ -9,6 +9,22 @@ import CloudMicrophysics.AerosolActivation as AA import CloudMicrophysics.AerosolModel as AM import CloudMicrophysics.Parameters as CMP +""" + N_activated_per_mode(machine, ap, ad, aip, tps, T, p, w, q) + + - `machine` - ML model + - `ap` - a struct with aerosol activation parameters + - `ad` - aerosol distribution struct + - `aip` - a struct with air parameters + - `tps` - a struct with thermodynamics parameters + - `T` - air temperature + - `p` - air pressure + - `w` - vertical velocity + - `q` - phase partition + +Returns the number of activated aerosol particles +in each aerosol size distribution mode by using a trained emulator. +""" function AA.N_activated_per_mode( machine::MLJ.Machine, ap::CMP.AerosolActivationParameters, diff --git a/test/Project.toml b/test/Project.toml index d1b797297..cf9d03732 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -8,13 +8,16 @@ ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c" CloudMicrophysics = "6a9e3e04-43cd-43ba-94b9-e8782df3c71b" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DataFramesMeta = "1313f7d8-7da2-5740-9ea0-a2ca25f37964" +EvoTrees = "f6006082-12f8-11e9-0c9c-0d5d367ab1e5" Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" +GaussianProcesses = "891a1506-143c-57d2-908e-e1f8e92e6de9" KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" MLJ = "add582a8-e3ab-11e8-2d5e-e98b27df1bc7" MLJFlux = "094fc8d1-fd35-5302-93ea-dabda2abf845" MLJModels = "d491faf4-2d78-11e9-2867-c94bc002c0b7" RootSolvers = "7181ea78-2dcb-4de3-ab41-2b8ab5a31e74" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Thermodynamics = "b60c26fb-14c3-4610-9d3e-2d17fe7ff00c" diff --git a/test/aerosol_activation_emulators.jl b/test/aerosol_activation_emulators.jl new file mode 100644 index 000000000..6c5db0472 --- /dev/null +++ b/test/aerosol_activation_emulators.jl @@ -0,0 +1,372 @@ +# Get ML packages +import MLJ +import Flux +import MLJModels +import MLJFlux +import GaussianProcesses +import StatsBase + +Standardizer = MLJ.@load Standardizer pkg = MLJModels +NeuralNetworkRegressor = MLJ.@load NeuralNetworkRegressor pkg = MLJFlux +EvoTreeRegressor = MLJ.@load EvoTreeRegressor pkg = EvoTrees + +# Get the testing package +import Test as TT + +# Get the CliMA packages +import CloudMicrophysics as CM +import ClimaParams as CP +import Thermodynamics as TD +import CloudMicrophysics.AerosolModel as AM +import CloudMicrophysics.AerosolActivation as AA +import CloudMicrophysics.Parameters as CMP + +# NN helpers +# Container for the NN we are about to build +struct NNBuilder <: MLJFlux.Builder + layer_sizes::Vector{Integer} + dropout::Vector +end + +# Define the NN structure +function MLJFlux.build(builder::NNBuilder, rng, n_in, n_out) + @assert length(builder.layer_sizes) == length(builder.dropout) + num_hidden_layers = length(builder.layer_sizes) + init = Flux.glorot_uniform(rng) + layers::Vector{Any} = [] + if num_hidden_layers == 0 + push!(layers, Flux.Dense(n_in => n_out, init = init)) + else + push!( + layers, + Flux.Dense( + n_in => builder.layer_sizes[1], + Flux.sigmoid_fast, + init = init, + ), + ) + end + for i in 1:num_hidden_layers + push!(layers, Flux.Dropout(builder.dropout[i])) + if i == num_hidden_layers + push!( + layers, + Flux.Dense(builder.layer_sizes[i] => n_out, init = init), + ) + else + push!( + layers, + Flux.Dense( + builder.layer_sizes[i] => builder.layer_sizes[i + 1], + Flux.sigmoid_fast, + init = init, + ), + ) + end + end + return Flux.Chain(layers...) +end + +# GP helpers +# Container for the GP we want to build +mutable struct GPRegressor <: MLJ.Deterministic + num_gps::Integer + sample_size::Integer + use_ARG_weights::Bool + use_DTC::Bool + sample_size_inducing::Integer +end + +# Define the function that fits the GP model +function MLJ.fit(model::GPRegressor, verbosity, X, y) + gps = [] + for i in 1:(model.num_gps) + if model.use_ARG_weights + weights = StatsBase.Weights([ + Distributions.pdf(Distributions.Normal(0.0, 0.5), x) for + x in X.mode_1_ARG_act_frac + ]) + inds = StatsBase.sample( + 1:DF.nrow(X), + weights, + model.sample_size, + replace = false, + ) + inds_inducing = StatsBase.sample( + 1:DF.nrow(X), + weights, + model.sample_size_inducing, + replace = false, + ) + else + inds = StatsBase.sample( + 1:DF.nrow(X), + model.sample_size, + replace = false, + ) + inds_inducing = StatsBase.sample( + 1:DF.nrow(X), + model.sample_size_inducing, + replace = false, + ) + end + if model.use_DTC + gp = GaussianProcesses.DTC( + Matrix(X[inds, :])', + Matrix(X[inds_inducing, :])', + y[inds], + GaussianProcesses.MeanConst(StatsBase.mean(y)), + GaussianProcesses.SEArd(fill(4.0, DF.ncol(X)), 0.0), + 2.0, + ) + else + gp = GaussianProcesses.GPA( + Matrix(X[inds, :])', + y[inds], + GaussianProcesses.MeanConst(StatsBase.mean(y)), + GaussianProcesses.SEArd(fill(4.0, DF.ncol(X)), 0.0), + GaussianProcesses.GaussLik(2.0), + ) + end + GaussianProcesses.optimize!(gp) + push!(gps, gp) + end + return gps, nothing, nothing +end + +# Define the function that predicts by using the GP model +function MLJ.predict(::GPRegressor, fitresult, Xnew) + means = reduce( + hcat, + [GaussianProcesses.predict_f(gp, Matrix(Xnew)')[1] for gp in fitresult], + ) + variances = reduce( + hcat, + [GaussianProcesses.predict_f(gp, Matrix(Xnew)')[2] for gp in fitresult], + ) + return (sum( + means ./ variances, + dims = 2, + ) ./ sum(1.0 ./ variances, dims = 2))[ + :, + 1, + ] +end + +# Load aerosol data reading and preprocessing functions +include(joinpath(pkgdir(CM), "ext", "Common.jl")) + +function preprocess_aerosol_data_with_ARG_act_frac_FT32(x::DataFrame) + aip = CMP.AirProperties(Float32) + tps = TD.Parameters.ThermodynamicsParameters(Float32) + ap = CMP.AerosolActivationParameters(Float32) + return preprocess_aerosol_data_with_ARG_act_frac(x, ap, aip, tps, Float32) +end + +# Get the ML model +function get_2modal_model_FT32(; + ml_model = "NN", + with_ARG_act_frac = false, + machine_name = "2modal_nn_machine_naive.jls", +) + FT = Float32 + + # If the ML model already exists load it in. + # If it does not exist, train + fpath = joinpath(pkgdir(CM), "test") + if isfile(joinpath(fpath, machine_name)) + # Read-in the saved ML model + emulator_filepath = joinpath(fpath, machine_name) + return MLJ.machine(emulator_filepath) + else + # Download data files if you don't have them + # (not the most elegant way...) + fname = "2modal_dataset1_train.csv" + if !isfile(joinpath(fpath, "data", fname)) + # For reasons unknown to humankind uploading/downloading the file + # from Caltech box changes the file. We are using dropbox for now. + # In the end we should upload the files to Caltech Data + # (and pray they are not changed there...) + url = "https://www.dropbox.com/scl/fi/qgq6ujvqenebjkskqvht5/2modal_dataset1_train.csv?rlkey=53qtqz0mtce993gy5jtnpdfz5&dl=0" + download(url, fname) + mkdir(joinpath(fpath, "data")) + mv(fname, joinpath(fpath, "data", fname), force = true) + end + + # Read the aerosol data + X_train, Y_train, initial_data = + read_aerosol_dataset(joinpath(fpath, "data", fname)) + + # Define the training pipeline + if with_ARG_act_frac + _preprocess_aerosol_data = + preprocess_aerosol_data_with_ARG_act_frac_FT32 + else + _preprocess_aerosol_data = preprocess_aerosol_data + end + if ml_model == "NN" + pipeline = + _preprocess_aerosol_data |> + Standardizer() |> + MLJ.TransformedTargetModel( + NeuralNetworkRegressor( + builder = NNBuilder( + [250, 50, 5], + [FT(0.3), FT(0), FT(0)], + ), + optimiser = Flux.Optimise.Adam( + 0.001, + (0.9, 0.999), + 1.0e-8, + ), + epochs = 2000, + loss = Flux.mse, + batch_size = 1000, + ), + transformer = target_transform, + inverse = inverse_target_transform, + ) + elseif ml_model == "GP" + pipeline = + _preprocess_aerosol_data |> + Standardizer() |> + MLJ.TransformedTargetModel( + GPRegressor(5, 2000, false, true, 20), + transformer = target_transform, + inverse = inverse_target_transform, + ) + elseif ml_model == "ET" + pipeline = + _preprocess_aerosol_data |> + Standardizer() |> + MLJ.TransformedTargetModel( + MLJ.TunedModel( + tuning = MLJ.Grid(goal = 30), + model = EvoTreeRegressor(), + resampling = MLJ.CV(nfolds = 5), + range = [ + range( + EvoTreeRegressor(), + :eta, + lower = 0.05, + upper = 1, + scale = :log, + ), + range( + EvoTreeRegressor(), + :max_depth, + lower = 3, + upper = 15, + ), + ], + ), + transformer = target_transform, + inverse = inverse_target_transform, + ) + else + error("Unknown ML method!") + end + + # Create the untrained ML model + mach = MLJ.machine(pipeline, X_train, Y_train) + # Train a new ML model + @info( + "Training a new ML model ($(ml_model)). This may take a minute or two." + ) + MLJ.fit!(mach, verbosity = 0) + # Save the ML model to a binary file that can be re-used by CloudMicrophysics + MLJ.save(joinpath(fpath, machine_name), mach) + return mach + end +end + +function test_emulator( + FT; + ml_model = "NN", + with_ARG_act_frac = false, + machine_name = "2modal_nn_machine_naive.jls", + rtols = [1e-4, 1e-3], +) + + aip = CMP.AirProperties(FT) + tps = TD.Parameters.ThermodynamicsParameters(FT) + ap = CMP.AerosolActivationParameters(FT) + + # Atmospheric conditions + T = FT(294) # air temperature K + p = FT(1e5) # air pressure Pa + w = FT(0.5) # vertical velocity m/s + p_vs = TD.saturation_vapor_pressure(tps, T, TD.Liquid()) + q_vs = 1 / (1 - TD.Parameters.molmass_ratio(tps) * (p_vs - p) / p_vs) + q = TD.PhasePartition(q_vs) + + # Aerosol size distribution + salt = CMP.Seasalt(FT) + # Accumulation mode + r1 = FT(0.243 * 1e-6) # m + σ1 = FT(1.4) # - + N1 = FT(100 * 1e6) # 1/m3 + # Coarse Mode + r2 = FT(1.5 * 1e-6) # m + σ2 = FT(2.1) # - + N2 = FT(1e6) # 1/m3 + acc = AM.Mode_κ(r1, σ1, N1, (FT(1.0),), (FT(1.0),), (salt.M,), (salt.κ,), 1) + crs = AM.Mode_κ(r2, σ2, N2, (FT(1.0),), (FT(1.0),), (salt.M,), (salt.κ,), 1) + ad = AM.AerosolDistribution((crs, acc)) + + # Get the ML model + mach = get_2modal_model_FT32( + ml_model = ml_model, + with_ARG_act_frac = with_ARG_act_frac, + machine_name = machine_name, + ) + + TT.@test AA.N_activated_per_mode(mach, ap, ad, aip, tps, T, p, w, q)[1] ≈ + AA.N_activated_per_mode(ap, ad, aip, tps, T, p, w, q)[1] rtol = + rtols[1] + TT.@test AA.N_activated_per_mode(mach, ap, ad, aip, tps, T, p, w, q)[2] ≈ + AA.N_activated_per_mode(ap, ad, aip, tps, T, p, w, q)[2] rtol = + rtols[2] +end + +@info "Aerosol activation test" + +TT.@testset "Neural Network" begin + test_emulator( + Float32, + ml_model = "NN", + with_ARG_act_frac = false, + machine_name = "2modal_nn_machine_naive.jls", + rtols = [1e-4, 1e-3], + ) +end + +TT.@testset "Gaussian Processes" begin + test_emulator( + Float32, + ml_model = "GP", + with_ARG_act_frac = false, + machine_name = "2modal_gp_machine_naive.jls", + rtols = [1e-2, 5e-2], + ) +end + +TT.@testset "Evo Trees" begin + test_emulator( + Float32, + ml_model = "ET", + with_ARG_act_frac = false, + machine_name = "2modal_et_machine_naive.jls", + rtols = [5e-3, 5e-3], + ) +end + +TT.@testset "Neural Network with ARG-informed data" begin + test_emulator( + Float32, + ml_model = "NN", + with_ARG_act_frac = true, + machine_name = "2modal_nn_machine_naive_with_ARG_act_frac.jls", + rtols = [1e-4, 1e-3], + ) +end diff --git a/test/emulator_NN.jl b/test/emulator_NN.jl deleted file mode 100644 index e0ee5bed5..000000000 --- a/test/emulator_NN.jl +++ /dev/null @@ -1,165 +0,0 @@ -# Get ML packages -import MLJ -import Flux -import MLJModels -import MLJFlux -Standardizer = MLJ.@load Standardizer pkg = MLJModels -NeuralNetworkRegressor = MLJ.@load NeuralNetworkRegressor pkg = MLJFlux - -# Get the testing package -import Test as TT - -# Get the CliMA packages -import CloudMicrophysics as CM -import ClimaParams as CP -import Thermodynamics as TD -import CloudMicrophysics.AerosolModel as AM -import CloudMicrophysics.AerosolActivation as AA -import CloudMicrophysics.Parameters as CMP - -# Container for the NN we are about to build -struct NNBuilder <: MLJFlux.Builder - layer_sizes::Vector{Integer} - dropout::Vector -end - -# Define the NN structure -function MLJFlux.build(builder::NNBuilder, rng, n_in, n_out) - @assert length(builder.layer_sizes) == length(builder.dropout) - num_hidden_layers = length(builder.layer_sizes) - init = Flux.glorot_uniform(rng) - layers::Vector{Any} = [] - if num_hidden_layers == 0 - push!(layers, Flux.Dense(n_in => n_out, init = init)) - else - push!( - layers, - Flux.Dense( - n_in => builder.layer_sizes[1], - Flux.sigmoid_fast, - init = init, - ), - ) - end - for i in 1:num_hidden_layers - push!(layers, Flux.Dropout(builder.dropout[i])) - if i == num_hidden_layers - push!( - layers, - Flux.Dense(builder.layer_sizes[i] => n_out, init = init), - ) - else - push!( - layers, - Flux.Dense( - builder.layer_sizes[i] => builder.layer_sizes[i + 1], - Flux.sigmoid_fast, - init = init, - ), - ) - end - end - return Flux.Chain(layers...) -end - -# Load aerosol data reading and preprocessing functions -include(joinpath(pkgdir(CM), "ext", "Common.jl")) - -# Get the ML model -function get_2modal_NN_model_FT32() - FT = Float32 - machine_name = "2modal_nn_machine_naive.jls" - - # If the ML model already exists load it in. - # If it does not exist, train a NN - fpath = joinpath(pkgdir(CM), "test") - if isfile(joinpath(fpath, machine_name)) - # Read-in the saved ML model - emulator_filepath = joinpath(fpath, machine_name) - return MLJ.machine(emulator_filepath) - else - # Download data files if you don't have them - # (not the most elegant way...) - fname = "2modal_dataset1_train.csv" - if !isfile(joinpath(fpath, "data", fname)) - # For reasons unknown to humankind uploading/downloading the file - # from Caltech box changes the file. We are using dropbox for now. - # In the end we should upload the files to Caltech Data - # (and pray they are not changed there...) - url = "https://www.dropbox.com/scl/fi/qgq6ujvqenebjkskqvht5/2modal_dataset1_train.csv?rlkey=53qtqz0mtce993gy5jtnpdfz5&dl=0" - download(url, fname) - mkdir(joinpath(fpath, "data")) - mv(fname, joinpath(fpath, "data", fname), force = true) - end - - # Read the aerosol data - X_train, Y_train, initial_data = - read_aerosol_dataset(joinpath(fpath, "data", fname)) - - # Define the training pipeline - pipeline = - preprocess_aerosol_data |> - Standardizer() |> - MLJ.TransformedTargetModel( - NeuralNetworkRegressor( - builder = NNBuilder([250, 50, 5], [FT(0.3), FT(0), FT(0)]), - optimiser = Flux.Optimise.Adam(0.001, (0.9, 0.999), 1.0e-8), - epochs = 2000, - loss = Flux.mse, - batch_size = 1000, - ), - transformer = target_transform, - inverse = inverse_target_transform, - ) - # Create the untrained ML model - mach = MLJ.machine(pipeline, X_train, Y_train) - # Train a new ML model - @info("Training a new ML model. This may take a minute or two.") - MLJ.fit!(mach, verbosity = 0) - # Save the ML model to a binary file that can be re-used by CloudMicrophysics - MLJ.save(joinpath(fpath, machine_name), mach) - return mach - end -end - -function test_emulator_NN(FT) - - aip = CMP.AirProperties(FT) - tps = TD.Parameters.ThermodynamicsParameters(FT) - ap = CMP.AerosolActivationParameters(FT) - - # Atmospheric conditions - T = FT(294) # air temperature K - p = FT(1e5) # air pressure Pa - w = FT(0.5) # vertical velocity m/s - p_vs = TD.saturation_vapor_pressure(tps, T, TD.Liquid()) - q_vs = 1 / (1 - TD.Parameters.molmass_ratio(tps) * (p_vs - p) / p_vs) - q = TD.PhasePartition(q_vs) - - # Aerosol size distribution - salt = CMP.Seasalt(FT) - # Accumulation mode - r1 = FT(0.243 * 1e-6) # m - σ1 = FT(1.4) # - - N1 = FT(100 * 1e6) # 1/m3 - # Coarse Mode - r2 = FT(1.5 * 1e-6) # m - σ2 = FT(2.1) # - - N2 = FT(1e6) # 1/m3 - acc = AM.Mode_κ(r1, σ1, N1, (FT(1.0),), (FT(1.0),), (salt.M,), (salt.κ,), 1) - crs = AM.Mode_κ(r2, σ2, N2, (FT(1.0),), (FT(1.0),), (salt.M,), (salt.κ,), 1) - ad = AM.AerosolDistribution((crs, acc)) - - # Get the ML model - mach = get_2modal_NN_model_FT32() - - TT.@test AA.N_activated_per_mode(mach, ap, ad, aip, tps, T, p, w, q)[1] ≈ - AA.N_activated_per_mode(ap, ad, aip, tps, T, p, w, q)[1] rtol = - 1e-5 - TT.@test AA.N_activated_per_mode(mach, ap, ad, aip, tps, T, p, w, q)[2] ≈ - AA.N_activated_per_mode(ap, ad, aip, tps, T, p, w, q)[2] rtol = - 1e-3 -end - -@info "Aerosol activation NN test" -test_emulator_NN(Float32) diff --git a/test/runtests.jl b/test/runtests.jl index 13347a7d8..0b117ce2b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,5 +13,5 @@ include("common_types_tests.jl") include("nucleation_unit_tests.jl") include("precipitation_susceptibility_tests.jl") include("p3_tests.jl") -include("emulator_NN.jl") +include("aerosol_activation_emulators.jl") include("aqua.jl")