Insoluble aerosol affecting condensation though kappa#493
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Lagrangian microphysics initialization/diagnostics so that insoluble aerosol no longer directly affects condensation via a separate rd_insol term; instead, insoluble material reduces the effective hygroscopicity (kappa) while contributing to the dry radius.
Changes:
- Remove
rd3_insolfrom condensation/Köhler calculations and apply insoluble effects by reducingkappabased on insoluble volume fraction. - Introduce/rewire initialization steps to incorporate insoluble aerosol into dry size (
add_insol_to_dry) and update kappa initialization accordingly. - Update ice nucleation to use insoluble particle surface area (
rd2_insol) and adjust Python bindings + unit tests.
Reviewed changes
Copilot reviewed 33 out of 34 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/python/unit/diag_incloud_time.py | Simplifies dry distro keys (no explicit insoluble usage in this test). |
| tests/python/unit/api_lgrngn.py | Updates dry-radius diagnostics and adds assertions for effective kappa with insoluble aerosol. |
| tests/python/unit/api_common.py | Updates rw3_cr / S_cr calls to new signatures. |
| src/particles.tpp | Includes new initialization helper for insoluble aerosol handling. |
| src/particles_diag.ipp | Drops rd3_insol from diagnostic transforms calling Köhler helpers. |
| src/impl/sources_and_relaxation_of_SDs/particles_impl_src_dry_sizes.ipp | Adjusts dry-size init to include insoluble radius and new init_kappa(kappa, rd_insol). |
| src/impl/sources_and_relaxation_of_SDs/particles_impl_src_dry_distros_simple.ipp | Inserts insoluble contribution into dry size during source creation and updates kappa init signature. |
| src/impl/sources_and_relaxation_of_SDs/particles_impl_src_dry_distros_matching.ipp | Adds a guard for unsupported rd_insol in matching sources and updates kappa init signature. |
| src/impl/sources_and_relaxation_of_SDs/particles_impl_rlx_dry_distros.ipp | Modernizes iterator loops; clarifies assumptions about produced aerosol and rd_insol. |
| src/impl/particles_impl.ipp | Renames/removes insoluble state from rd3_insol to rd2_insol (ice-only) and updates init method signatures. |
| src/impl/initialization/particles_impl_reserve_hskpng_npart.ipp | Reserves rd2_insol only when ice is enabled; removes rd3_insol reserve. |
| src/impl/initialization/particles_impl_init_wet.ipp | Removes rd3_insol from wet-radius equilibrium calculation path. |
| src/impl/initialization/particles_impl_init_T_freeze.ipp | Updates freezing init to use rd2_insol (surface area proxy). |
| src/impl/initialization/particles_impl_init_SD_with_sizes.ipp | Uses rd_sol + rd_insol in dry size init; updates kappa + insol init sequencing. |
| src/impl/initialization/particles_impl_init_SD_with_distros.ipp | Adds insoluble contribution to dry size and updates kappa initialization sequencing. |
| src/impl/initialization/particles_impl_init_sanity_check.ipp | Disallows insoluble aerosol initialization when chemistry is enabled. |
| src/impl/initialization/particles_impl_init_kappa.ipp | Computes effective kappa after insoluble addition (volume-fraction reduction). |
| src/impl/initialization/particles_impl_init_insol_dry_sizes.ipp | Stores insoluble size as rd2_insol (radius squared). |
| src/impl/initialization/particles_impl_init_dry_const_multi.ipp | Whitespace-only tweak in signature line. |
| src/impl/initialization/particles_impl_add_insol_to_dry.ipp | New helper for adding insoluble aerosol contribution into dry size. |
| src/impl/ice/particles_impl_ice_nucl_melt.ipp | Switches ice nucleation inputs from rd3_insol to rd2_insol. |
| src/impl/ice/particles_impl_ice_dep.ipp | Removes unused tuple inputs (rd3/kpa) from deposition helper zip iterator. |
| src/impl/housekeeping/particles_impl_hskpng_rc2.ipp | Removes rd3_insol from rc2 approximation inputs. |
| src/impl/diagnose_SD_attributes/particles_impl_update_incloud_time.ipp | Removes rd3_insol from incloud-time diagnostic transform inputs. |
| src/impl/diagnose_SD_attributes/particles_impl_fill_outbuf.ipp | Renames exposed attribute from rd3_insol to rd2_insol and gates it behind ice_switch. |
| src/impl/condensation/perparticle/perparticle_nomixing_adaptive_sstp_cond.ipp | Removes rd3_insol from condensation substepping tuple payloads. |
| src/impl/condensation/perparticle/perparticle_advance_rw2.ipp | Removes rd3_insol from advance tuple payloads. |
| src/impl/condensation/percell/particles_impl_cond.ipp | Removes rd3_insol from per-cell condensation tuple payloads. |
| src/impl/condensation/common/particles_impl_cond_common.ipp | Removes rd3_insol from activity-of-water usage and tuple parsing/diagnostics. |
| include/libcloudph++/lgrngn/opts_init.hpp | Switches rlx_dry_distros to a shared alias type. |
| include/libcloudph++/lgrngn/distro_t.hpp | Adds rlx_dry_distros_t alias definition. |
| include/libcloudph++/common/kappa_koehler.hpp | Removes insoluble term from Köhler helpers (activity/equilibrium/critical values). |
| include/libcloudph++/common/ice_nucleation.hpp | Updates ice nucleation API to use insoluble surface area (rd2_insol). |
| bindings/python/common.hpp | Updates Python binding signatures for rw3_cr and S_cr. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| thrust::transform( | ||
| thrust::make_zip_iterator(thrust::make_tuple(rd3_insol.begin() + n_part_old, u01.begin())), | ||
| thrust::make_zip_iterator(thrust::make_tuple(rd3_insol.begin() + n_part_old, u01.begin())) + n_part_to_init, | ||
| thrust::make_zip_iterator(thrust::make_tuple(rd2_insol.begin() + n_part_old, u01.begin() + n_part_old)), | ||
| thrust::make_zip_iterator(thrust::make_tuple(rd2_insol.end(), u01.end())), | ||
| T_freeze.begin() + n_part_old, | ||
| T_freeze_CDF_inv_functor<real_t>(opts_init.inp_type) | ||
| ); |
|
|
||
| // add the insoluble aerosol | ||
| const real_t rd3_insol = rd_insol * rd_insol * rd_insol; | ||
| thrust::transform( | ||
| rd3.begin()+n_part_old, | ||
| rd3.end(), | ||
| thrust::make_constant_iterator<real_t>(rd3_insol), | ||
| rd3.begin()+n_part_old, | ||
| // arg::_1 + arg::_2 | ||
| thrust::plus<real_t>() | ||
| ); |
|
|
||
| debug::print(kpa); | ||
|
|
| auto p_sdd = sdd.cbegin(); | ||
| assert(p_sdd->first.rd_insol == 0); // rd insol in matching source not implemented yet | ||
|
|
| assert (n == 20 ).all() | ||
| assert isclose(k, n * kappa2, rtol=1e-20) | ||
| for rd_sol, kappa, n_stp in [(1e-6, kappa1, 30), (15.e-6, kappa1, 10), (1.2e-6, kappa2, 20), (12.e-6, kappa2, 15)]: | ||
| print("testig rd_sol: ", rd_sol, " kappa: ", kappa, " n_stp: ", n_stp) |
| rd3.begin() + n_part_old, rd3.end(), | ||
| kpa.begin() + n_part_old, | ||
| (arg::_1 - rd3_insol) / arg::_1 * kappa | ||
| // assuming that soluble and insoluble densities are the same (hence using volume fraction mixing, not mass fraction as should (?) be done) |
There was a problem hiding this comment.
I think it's ok following Petters and Kreidenweis:
"ε is defined as the volume fraction of a model salt in a dry particle consisting of the model salt (κ_m) and an insoluble
core. Two-component system of a model salt and an insoluble species (κ=0) gives κ=ε × κ_m ."
| thrust::make_zip_iterator(thrust::make_tuple(rd2_insol.end(), u01.end())), | ||
| T_freeze.begin() + n_part_old, | ||
| T_freeze_CDF_inv_functor<real_t>(opts_init.inp_type) | ||
| ); |
There was a problem hiding this comment.
that problem with u01 for T_freeze was fixed in my PR, so we need to keep changes from my PR here
…into rd_insol_in_kappa
| { | ||
| assert(kappa > 0); | ||
| if ((rw3 - rd3 - rd3_insol) / si::cubic_meters <= std::nextafter(real_t(0),real_t(1))) | ||
| return real_t(0.); | ||
| return (rw3 - rd3 - rd3_insol) / (rw3 - rd3 * (real_t(1) - kappa) - rd3_insol); | ||
| return (rw3 - rd3) / (rw3 - rd3 * (real_t(1) - kappa)); | ||
| } |
| BOOST_GPU_ENABLED | ||
| real_t operator()(real_t rd3_val) const | ||
| { | ||
| return pow(insol_fraction * rd3_val, real_t(2) / real_t(3)); | ||
| } |
| const real_t &kappa(dsi->first.kappa); | ||
| const real_t &rd_insol(dsi->first.rd_insol); | ||
| const real_t &soluble_fraction(dsi->first.soluble_fraction); | ||
| const auto &size_number_map(dsi->second); |
| // init other properties of SDs | ||
| init_kappa( | ||
| p_sdd->first.kappa | ||
| p_sdd->first.kappa, | ||
| p_sdd->first.soluble_fraction | ||
| ); |
| // if any SDs with dry radius similar to the one to be added are present, | ||
| // we increase their multiplicity instead of adding new SDs | ||
| // TODO: make it work for sdd.size()>1 | ||
| // -------- TODO: match not only sizes of old particles, but also kappas, rd_insol, chemical composition... -------- |
| // analyze distribution to get rd_min and max needed for bin sizes | ||
| // TODO: this could be done once at the beginning of the simulation | ||
| // TODO: take rd_insol into account here? | ||
| init_dist_analysis_sd_conc( |
| assert (n == 20 ).all() | ||
| assert isclose(k, n * kappa2, rtol=1e-20) | ||
| for rd, kappa, n_stp in [(1e-6, kappa1, 30), (15.e-6, kappa1, 10), (1.2e-6, kappa2, 20), (12.e-6, kappa2, 15)]: | ||
| print("testig rd: ", rd, " kappa: ", kappa, " n_stp: ", n_stp) |
no rd_insol in condensation, instead kappa is reduced if theres insoluble aerosol (Petters & Kreidenweis 2007)