├── .binder ├── apt.txt ├── postBuild └── requirements.txt ├── .codecov.yml ├── .github ├── dependabot.yml └── workflows │ ├── cancel.yml │ ├── joss.yml │ ├── joss_paper.yml │ ├── pdoc.yml │ ├── precommit.yml │ ├── pypi.yml │ ├── readme_snippets.yml │ ├── stale.yml │ └── tests.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .zenodo.json ├── LICENSE ├── PySDM ├── __init__.py ├── attributes │ ├── __init__.py │ ├── chemistry │ │ ├── __init__.py │ │ ├── acidity.py │ │ ├── concentration.py │ │ └── hydrogen_ion_concentration.py │ ├── ice │ │ ├── __init__.py │ │ ├── cooling_rate.py │ │ ├── freezing_temperature.py │ │ └── immersed_surface_area.py │ ├── impl │ │ ├── __init__.py │ │ ├── attribute.py │ │ ├── attribute_registry.py │ │ ├── base_attribute.py │ │ ├── cell_attribute.py │ │ ├── derived_attribute.py │ │ ├── dummy_attribute.py │ │ ├── extensive_attribute.py │ │ ├── intensive_attribute.py │ │ ├── maximum_attribute.py │ │ └── mole_amount.py │ ├── isotopes │ │ ├── __init__.py │ │ ├── delta.py │ │ └── moles.py │ ├── numerics │ │ ├── __init__.py │ │ ├── cell_id.py │ │ ├── cell_origin.py │ │ └── position_in_cell.py │ └── physics │ │ ├── __init__.py │ │ ├── area.py │ │ ├── critical_supersaturation.py │ │ ├── critical_volume.py │ │ ├── dry_radius.py │ │ ├── dry_volume.py │ │ ├── equilibrium_supersaturation.py │ │ ├── heat.py │ │ ├── hygroscopicity.py │ │ ├── multiplicity.py │ │ ├── radius.py │ │ ├── relative_fall_velocity.py │ │ ├── reynolds_number.py │ │ ├── temperature.py │ │ ├── terminal_velocity.py │ │ ├── volume.py │ │ └── water_mass.py ├── backends │ ├── __init__.py │ ├── impl_common │ │ ├── __init__.py │ │ ├── backend_methods.py │ │ ├── freezing_attributes.py │ │ ├── index.py │ │ ├── indexed_storage.py │ │ ├── pair_indicator.py │ │ ├── pairwise_storage.py │ │ ├── random_common.py │ │ └── storage_utils.py │ ├── impl_numba │ │ ├── __init__.py │ │ ├── atomic_operations.py │ │ ├── conf.py │ │ ├── methods │ │ │ ├── __init__.py │ │ │ ├── chemistry_methods.py │ │ │ ├── collisions_methods.py │ │ │ ├── condensation_methods.py │ │ │ ├── deposition_methods.py │ │ │ ├── displacement_methods.py │ │ │ ├── fragmentation_methods.py │ │ │ ├── freezing_methods.py │ │ │ ├── index_methods.py │ │ │ ├── isotope_methods.py │ │ │ ├── moments_methods.py │ │ │ ├── pair_methods.py │ │ │ ├── physics_methods.py │ │ │ ├── seeding_methods.py │ │ │ └── terminal_velocity_methods.py │ │ ├── random.py │ │ ├── storage.py │ │ ├── storage_impl.py │ │ ├── test_helpers │ │ │ ├── __init__.py │ │ │ └── scipy_ode_condensation_solver.py │ │ ├── toms748.py │ │ └── warnings.py │ ├── impl_thrust_rtc │ │ ├── __init__.py │ │ ├── bisection.py │ │ ├── conf.py │ │ ├── methods │ │ │ ├── __init__.py │ │ │ ├── collisions_methods.py │ │ │ ├── condensation_methods.py │ │ │ ├── displacement_methods.py │ │ │ ├── freezing_methods.py │ │ │ ├── index_methods.py │ │ │ ├── isotope_methods.py │ │ │ ├── moments_methods.py │ │ │ ├── pair_methods.py │ │ │ ├── physics_methods.py │ │ │ ├── terminal_velocity_methods.py │ │ │ └── thrust_rtc_backend_methods.py │ │ ├── nice_thrust.py │ │ ├── random.py │ │ ├── storage.py │ │ └── test_helpers │ │ │ ├── __init__.py │ │ │ ├── cpp2python.py │ │ │ ├── fake_thrust_rtc.py │ │ │ └── flag.py │ ├── numba.py │ └── thrust_rtc.py ├── builder.py ├── dynamics │ ├── __init__.py │ ├── ambient_thermodynamics.py │ ├── aqueous_chemistry.py │ ├── collisions │ │ ├── __init__.py │ │ ├── breakup_efficiencies │ │ │ ├── __init__.py │ │ │ └── constEb.py │ │ ├── breakup_fragmentations │ │ │ ├── __init__.py │ │ │ ├── always_n.py │ │ │ ├── constant_mass.py │ │ │ ├── expon_frag.py │ │ │ ├── exponential.py │ │ │ ├── feingold1988.py │ │ │ ├── gaussian.py │ │ │ ├── impl │ │ │ │ ├── __init__.py │ │ │ │ └── volume_based.py │ │ │ ├── lowlist82.py │ │ │ ├── slams.py │ │ │ └── straub2010.py │ │ ├── coalescence_efficiencies │ │ │ ├── __init__.py │ │ │ ├── _gravitational.py │ │ │ ├── _parameterized.py │ │ │ ├── berry1967.py │ │ │ ├── constEc.py │ │ │ ├── lowlist1982.py │ │ │ ├── specified_eff.py │ │ │ └── straub2010.py │ │ ├── collision.py │ │ └── collision_kernels │ │ │ ├── __init__.py │ │ │ ├── constantK.py │ │ │ ├── electric.py │ │ │ ├── geometric.py │ │ │ ├── golovin.py │ │ │ ├── hydrodynamic.py │ │ │ ├── impl │ │ │ ├── __init__.py │ │ │ ├── gravitational.py │ │ │ └── parameterized.py │ │ │ ├── linear.py │ │ │ └── simple_geometric.py │ ├── condensation.py │ ├── displacement.py │ ├── eulerian_advection.py │ ├── freezing.py │ ├── impl │ │ ├── __init__.py │ │ ├── chemistry_utils.py │ │ ├── random_generator_optimizer.py │ │ ├── random_generator_optimizer_nopair.py │ │ └── register_dynamic.py │ ├── isotopic_fractionation.py │ ├── relaxed_velocity.py │ ├── seeding.py │ ├── terminal_velocity │ │ ├── __init__.py │ │ ├── gunn_and_kinzer.py │ │ ├── power_series.py │ │ └── rogers_and_yau.py │ └── vapour_deposition_on_ice.py ├── environments │ ├── __init__.py │ ├── box.py │ ├── impl │ │ ├── __init__.py │ │ ├── moist.py │ │ └── register_environment.py │ ├── kinematic_1d.py │ ├── kinematic_2d.py │ └── parcel.py ├── exporters │ ├── __init__.py │ ├── netcdf_exporter.py │ ├── netcdf_exporter_1d.py │ ├── vtk_exporter.py │ └── vtk_exporter_1d.py ├── formulae.py ├── impl │ ├── __init__.py │ ├── arakawa_c.py │ ├── camel_case.py │ ├── mesh.py │ ├── null_physics_class.py │ ├── particle_attributes.py │ ├── particle_attributes_factory.py │ └── wall_timer.py ├── initialisation │ ├── __init__.py │ ├── aerosol_composition │ │ ├── __init__.py │ │ └── dry_aerosol.py │ ├── discretise_multiplicities.py │ ├── equilibrate_wet_radii.py │ ├── impl │ │ ├── __init__.py │ │ └── spectrum.py │ ├── init_fall_momenta.py │ ├── sampling │ │ ├── __init__.py │ │ ├── spatial_sampling.py │ │ ├── spectral_sampling.py │ │ └── spectro_glacial_sampling.py │ └── spectra │ │ ├── __init__.py │ │ ├── exponential.py │ │ ├── gamma.py │ │ ├── gaussian.py │ │ ├── lognormal.py │ │ ├── sum.py │ │ └── top_hat.py ├── particulator.py ├── physics │ ├── __init__.py │ ├── air_dynamic_viscosity │ │ ├── __init__.py │ │ └── zografos_et_al_1987.py │ ├── bulk_phase_partitioning │ │ ├── __init__.py │ │ └── kaul_et_al_2015.py │ ├── constants.py │ ├── constants_defaults.py │ ├── diffusion_coordinate │ │ ├── __init__.py │ │ ├── water_mass.py │ │ └── water_mass_logarithm.py │ ├── diffusion_ice_capacity │ │ ├── __init__.py │ │ └── spherical.py │ ├── diffusion_ice_kinetics │ │ ├── __init__.py │ │ ├── neglect.py │ │ └── standard.py │ ├── diffusion_kinetics │ │ ├── __init__.py │ │ ├── fuchs_sutugin.py │ │ ├── grabowski_et_al_2011.py │ │ ├── lowe_et_al_2019.py │ │ ├── neglect.py │ │ └── pruppacher_and_klett_2005.py │ ├── diffusion_thermics │ │ ├── __init__.py │ │ ├── grabowski_et_al_2011.py │ │ ├── lowe_et_al_2019.py │ │ ├── neglect.py │ │ ├── seinfeld_and_pandis_2010.py │ │ └── tracy_welch_porter.py │ ├── dimensional_analysis.py │ ├── drop_growth │ │ ├── __init__.py │ │ ├── fick.py │ │ ├── howell_1949.py │ │ └── mason_1971.py │ ├── fragmentation_function │ │ ├── __init__.py │ │ ├── always_n.py │ │ ├── constant_mass.py │ │ ├── expon_frag.py │ │ ├── exponential.py │ │ ├── feingold1988.py │ │ ├── gaussian.py │ │ ├── lowlist82.py │ │ ├── slams.py │ │ └── straub2010nf.py │ ├── freezing_temperature_spectrum │ │ ├── __init__.py │ │ ├── bigg_1953.py │ │ ├── niemand_et_al_2012.py │ │ └── null.py │ ├── heterogeneous_ice_nucleation_rate │ │ ├── __init__.py │ │ ├── abifm.py │ │ ├── constant.py │ │ └── null.py │ ├── hydrostatics │ │ ├── __init__.py │ │ ├── constant_g_vapour_mixing_ratio_and_theta_std.py │ │ └── variable_g_isothermal.py │ ├── hygroscopicity │ │ ├── __init__.py │ │ ├── kappa_koehler.py │ │ └── kappa_koehler_leading_terms.py │ ├── impl │ │ ├── __init__.py │ │ ├── fake_unit_registry.py │ │ └── flag.py │ ├── isotope_diffusivity_ratios │ │ ├── __init__.py │ │ ├── grahams_law.py │ │ ├── hellmann_and_harvey_2020.py │ │ └── stewart_1975.py │ ├── isotope_equilibrium_fractionation_factors │ │ ├── __init__.py │ │ ├── barkan_and_luz_2005.py │ │ ├── ellehoj_et_al_2013.py │ │ ├── horita_and_wesolowski_1994.py │ │ ├── lamb_et_al_2017.py │ │ ├── majoube_1970.py │ │ ├── majoube_1971.py │ │ ├── merlivat_and_nief_1967.py │ │ └── van_hook_1968.py │ ├── isotope_kinetic_fractionation_factors │ │ ├── __init__.py │ │ ├── craig_gordon.py │ │ └── jouzel_and_merlivat_1984.py │ ├── isotope_meteoric_water_line │ │ ├── __init__.py │ │ ├── barkan_and_luz_2007.py │ │ ├── dansgaard_1964.py │ │ └── picciotto_et_al_1960.py │ ├── isotope_ratio_evolution │ │ ├── __init__.py │ │ ├── gedzelman_and_arnold_1994.py │ │ ├── merlivat_and_jouzel_1979.py │ │ └── rayleigh_distillation.py │ ├── isotope_relaxation_timescale │ │ ├── __init__.py │ │ ├── bolin_1958.py │ │ └── miyake_et_al_1968.py │ ├── isotope_temperature_inference │ │ ├── __init__.py │ │ └── picciotto_et_al_1960.py │ ├── isotope_ventilation_ratio │ │ ├── __init__.py │ │ ├── brutsaert_1982.py │ │ └── neglect.py │ ├── latent_heat_sublimation │ │ ├── __init__.py │ │ └── murphy_koop_2005.py │ ├── latent_heat_vapourisation │ │ ├── __init__.py │ │ ├── constant.py │ │ ├── kirchhoff.py │ │ ├── lowe2019.py │ │ └── seinfeld_and_pandis_2010.py │ ├── optical_albedo │ │ ├── __init__.py │ │ └── bohren1987.py │ ├── optical_depth │ │ ├── __init__.py │ │ └── stephens_1978.py │ ├── particle_advection │ │ ├── __init__.py │ │ ├── explicit_in_space.py │ │ └── implicit_in_space.py │ ├── particle_shape_and_density │ │ ├── __init__.py │ │ ├── liquid_spheres.py │ │ ├── mixed_phase_spheres.py │ │ └── porous_spheroids.py │ ├── saturation_vapour_pressure │ │ ├── __init__.py │ │ ├── august_roche_magnus.py │ │ ├── bolton_1980.py │ │ ├── flatau_walko_cotton.py │ │ ├── lowe1977.py │ │ ├── murphy_koop_2005.py │ │ └── wexler_1976.py │ ├── state_variable_triplet │ │ ├── __init__.py │ │ └── libcloudphplusplus.py │ ├── surface_tension │ │ ├── __init__.py │ │ ├── compressed_film_ovadnevaite.py │ │ ├── compressed_film_ruehl.py │ │ ├── constant.py │ │ └── szyszkowski_langmuir.py │ ├── terminal_velocity │ │ ├── __init__.py │ │ ├── gunn_kinzer_1949.py │ │ ├── power_series.py │ │ └── rogers_yau.py │ ├── trivia.py │ └── ventilation │ │ ├── __init__.py │ │ ├── froessling_1938.py │ │ ├── neglect.py │ │ └── pruppacher_rasmussen_1979.py └── products │ ├── __init__.py │ ├── ambient_thermodynamics │ ├── __init__.py │ ├── ambient_dry_air_density.py │ ├── ambient_dry_air_potential_temperature.py │ ├── ambient_pressure.py │ ├── ambient_relative_humidity.py │ ├── ambient_temperature.py │ └── ambient_water_vapour_mixing_ratio.py │ ├── aqueous_chemistry │ ├── __init__.py │ ├── acidity.py │ ├── aqueous_mass_spectrum.py │ ├── aqueous_mole_fraction.py │ ├── gaseous_mole_fraction.py │ └── total_dry_mass_mixing_ratio.py │ ├── collision │ ├── __init__.py │ ├── collision_rates.py │ ├── collision_timestep_mean.py │ └── collision_timestep_min.py │ ├── condensation │ ├── __init__.py │ ├── activable_fraction.py │ ├── condensation_timestep.py │ ├── event_rates.py │ └── peak_supersaturation.py │ ├── displacement │ ├── __init__.py │ ├── averaged_terminal_velocity.py │ ├── flow_velocity_component.py │ ├── max_courant_number.py │ └── surface_precipitation.py │ ├── freezing │ ├── __init__.py │ ├── cooling_rate.py │ ├── freezable_specific_concentration.py │ ├── frozen_particle_concentration.py │ ├── ice_nuclei_concentration.py │ └── total_unfrozen_immersed_surface_area.py │ ├── housekeeping │ ├── __init__.py │ ├── dynamic_wall_time.py │ ├── super_droplet_count_per_gridbox.py │ ├── time.py │ └── timers.py │ ├── impl │ ├── __init__.py │ ├── activation_filtered_product.py │ ├── concentration_product.py │ ├── moist_environment_product.py │ ├── moment_product.py │ ├── product.py │ ├── rate_product.py │ ├── register_product.py │ └── spectrum_moment_product.py │ ├── optical │ ├── __init__.py │ ├── cloud_albedo.py │ └── cloud_optical_depth.py │ ├── parcel │ ├── __init__.py │ ├── cloud_water_path.py │ └── parcel_displacement.py │ └── size_spectral │ ├── __init__.py │ ├── arbitrary_moment.py │ ├── cloud_water_content.py │ ├── effective_radius.py │ ├── effective_radius_activated.py │ ├── mean_radius.py │ ├── mean_radius_activated.py │ ├── mean_volume_radius.py │ ├── number_size_spectrum.py │ ├── particle_concentration.py │ ├── particle_concentration_activated.py │ ├── particle_size_spectrum.py │ ├── particle_volume_versus_radius_logarithm_spectrum.py │ ├── radius_binned_number_averaged_terminal_velocity.py │ ├── size_standard_deviation.py │ ├── total_particle_concentration.py │ ├── total_particle_specific_concentration.py │ └── water_mixing_ratio.py ├── README.md ├── docs ├── bibliography.json ├── generate_html.py ├── logos │ ├── pysdm_logo.png │ └── pysdm_logo.svg ├── markdown │ └── pysdm_landing.md └── templates │ ├── README.md │ ├── custom.css │ ├── index.html.jinja2 │ ├── syntax-highlighting.css │ └── theme.css ├── examples ├── MANIFEST.in ├── PySDM_examples │ ├── Abade_and_Albuquerque_2024 │ │ ├── __init__.py │ │ ├── fig_2.ipynb │ │ ├── settings.py │ │ └── simulation.py │ ├── Abdul_Razzak_Ghan_2000 │ │ ├── __init__.py │ │ ├── aerosol.py │ │ ├── data_from_ARG2000_paper.py │ │ ├── data_from_CloudMicrophysics_ARG.py │ │ ├── fig_4_kinetic_limitations.ipynb │ │ ├── figs1-5.ipynb │ │ └── run_ARG_parcel.py │ ├── Alpert_and_Knopf_2016 │ │ ├── __init__.py │ │ ├── fig_1.ipynb │ │ ├── fig_2.ipynb │ │ ├── fig_3.ipynb │ │ ├── fig_4.ipynb │ │ ├── fig_5.ipynb │ │ ├── simulation.py │ │ ├── table.py │ │ ├── table_1.py │ │ └── table_2.py │ ├── Arabas_and_Shima_2017 │ │ ├── __init__.py │ │ ├── example.py │ │ ├── fig_5.ipynb │ │ ├── settings.py │ │ └── simulation.py │ ├── Arabas_et_al_2015 │ │ ├── __init__.py │ │ ├── example.py │ │ ├── example_benchmark.py │ │ ├── gui.ipynb │ │ ├── settings.py │ │ └── spin_up.py │ ├── Arabas_et_al_2025 │ │ ├── __init__.py │ │ ├── aida.ipynb │ │ ├── commons.py │ │ ├── copula_hello.ipynb │ │ ├── curved_text.py │ │ ├── fig_2.ipynb │ │ ├── fig_A2.ipynb │ │ ├── figs_10_and_11_and_animations.ipynb │ │ ├── figs_3_and_7_and_8.ipynb │ │ ├── figs_5_and_6.ipynb │ │ ├── frozen_fraction.py │ │ ├── make_particulator.py │ │ ├── plots.py │ │ └── run_simulation.py │ ├── Bartman_2020_MasterThesis │ │ ├── __init__.py │ │ ├── fig_4_adaptive_sdm.py │ │ └── fig_5_SCIPY_VS_ADAPTIVE.py │ ├── Bartman_et_al_2021 │ │ ├── __init__.py │ │ ├── demo.ipynb │ │ ├── demo_fig2.ipynb │ │ ├── demo_fig3.ipynb │ │ └── label.py │ ├── Berry_1967 │ │ ├── __init__.py │ │ ├── example.py │ │ ├── example_fig_6.py │ │ ├── figs_5_8_10.ipynb │ │ ├── settings.py │ │ └── spectrum_plotter.py │ ├── Bieli_et_al_2022 │ │ ├── __init__.py │ │ ├── make_fig_3.ipynb │ │ ├── settings.py │ │ └── simulation.py │ ├── Bolin_1958 │ │ ├── __init__.py │ │ └── table_1.ipynb │ ├── Bolot_et_al_2013 │ │ ├── __init__.py │ │ └── fig_1.ipynb │ ├── Bulenok_2023_MasterThesis │ │ ├── __init__.py │ │ ├── performance_comparison_Srivastava_Setup.py │ │ ├── setups.py │ │ └── utils.py │ ├── Ervens_and_Feingold_2012 │ │ ├── __init__.py │ │ └── settings.py │ ├── Fisher_1991 │ │ ├── __init__.py │ │ └── fig_2.ipynb │ ├── Gedzelman_and_Arnold_1994 │ │ ├── __init__.py │ │ └── fig_2.ipynb │ ├── Gonfiantini_1986 │ │ ├── __init__.py │ │ └── fig_3_1.ipynb │ ├── Grabowski_and_Pawlowska_2023 │ │ ├── __init__.py │ │ ├── figure_1.ipynb │ │ ├── figure_2.ipynb │ │ ├── figure_3.ipynb │ │ ├── figure_4.ipynb │ │ ├── figure_ripening_rate.ipynb │ │ ├── settings.py │ │ └── simulation.py │ ├── Graf_et_al_2019 │ │ ├── Table_1.ipynb │ │ ├── __init__.py │ │ └── figure_4.ipynb │ ├── Jaruga_and_Pawlowska_2018 │ │ ├── __init__.py │ │ ├── fig_2.ipynb │ │ └── fig_3.ipynb │ ├── Jensen_and_Nugent_2017 │ │ ├── Fig_1.ipynb │ │ ├── Fig_3_and_Tab_4_upper_rows.ipynb │ │ ├── Fig_4_and_7_and_Tab_4_bottom_rows.ipynb │ │ ├── Fig_5.ipynb │ │ ├── Fig_6.ipynb │ │ ├── Fig_8.ipynb │ │ ├── __init__.py │ │ ├── plotting.py │ │ ├── settings.py │ │ ├── simulation.py │ │ └── table_3.py │ ├── Jouzel_and_Merlivat_1984 │ │ ├── __init__.py │ │ ├── fig_8_9.ipynb │ │ └── thermodynamic_profiles.py │ ├── Kinzer_And_Gunn_1951 │ │ ├── __init__.py │ │ └── table_1_and_2.py │ ├── Kreidenweis_et_al_2003 │ │ ├── __init__.py │ │ ├── fig_1.ipynb │ │ ├── settings.py │ │ └── simulation.py │ ├── Lamb_et_al_2017 │ │ ├── __init__.py │ │ └── fig_4.ipynb │ ├── Lowe_et_al_2019 │ │ ├── __init__.py │ │ ├── aerosol.py │ │ ├── aerosol_code.py │ │ ├── constants_def.py │ │ ├── fig_1.ipynb │ │ ├── fig_2.ipynb │ │ ├── fig_3.ipynb │ │ ├── fig_s2.ipynb │ │ ├── plot_helper.py │ │ ├── settings.py │ │ └── simulation.py │ ├── Merlivat_and_Nief_1967 │ │ ├── __init__.py │ │ └── fig_2.ipynb │ ├── Miyake_et_al_1968 │ │ ├── __init__.py │ │ └── fig_19.ipynb │ ├── Morrison_and_Grabowski_2007 │ │ ├── __init__.py │ │ ├── common.py │ │ ├── fig_1.ipynb │ │ └── strato_cumulus.py │ ├── Niedermeier_et_al_2014 │ │ ├── __init__.py │ │ ├── fig_2.ipynb │ │ ├── settings.py │ │ └── simulation.py │ ├── Pierchala_et_al_2022 │ │ ├── __init__.py │ │ ├── commons.py │ │ ├── fig_3.ipynb │ │ └── fig_4.ipynb │ ├── Pruppacher_and_Rasmussen_1979 │ │ ├── __init__.py │ │ └── fig_1.ipynb │ ├── Pyrcel │ │ ├── __init__.py │ │ ├── example_basic_run.ipynb │ │ ├── profile_plotter.py │ │ ├── settings.py │ │ └── simulation.py │ ├── Rogers_1975 │ │ ├── __init__.py │ │ └── fig_1.ipynb │ ├── Rozanski_and_Sonntag_1982 │ │ ├── __init__.py │ │ ├── figs_4_5_6.ipynb │ │ └── multibox.py │ ├── Shima_et_al_2009 │ │ ├── __init__.py │ │ ├── error_measure.py │ │ ├── example.py │ │ ├── example_timing.py │ │ ├── fig_2.ipynb │ │ ├── settings.py │ │ ├── spectrum_plotter.py │ │ ├── tutorial_example.py │ │ ├── tutorial_plotter.py │ │ └── tutorial_settings.py │ ├── Shipway_and_Hill_2012 │ │ ├── __init__.py │ │ ├── fig_1.ipynb │ │ ├── mpdata_1d.py │ │ ├── plot.py │ │ ├── settings.py │ │ └── simulation.py │ ├── Singer_Ward │ │ ├── MWE_joss_paper.ipynb │ │ ├── __init__.py │ │ ├── aerosol.py │ │ └── kohler.ipynb │ ├── Srivastava_1982 │ │ ├── __init__.py │ │ ├── equations.py │ │ ├── example.py │ │ ├── figures.ipynb │ │ ├── settings.py │ │ └── simulation.py │ ├── Stewart_1975 │ │ ├── __init__.py │ │ └── fig_1.ipynb │ ├── Toon_et_al_1980 │ │ ├── __init__.py │ │ └── fig_1.ipynb │ ├── Van_Hook_1968 │ │ ├── __init__.py │ │ └── fig_1.ipynb │ ├── Yang_et_al_2018 │ │ ├── __init__.py │ │ ├── fig_2.ipynb │ │ ├── settings.py │ │ └── simulation.py │ ├── Zaba_et_al_2025 │ │ ├── __init__.py │ │ └── global_meteoric_water_line.ipynb │ ├── _HOWTOs │ │ ├── __init__.py │ │ ├── dimensional_analysis.ipynb │ │ └── paraview_hello_world.ipynb │ ├── __init__.py │ ├── deJong_Azimi │ │ ├── __init__.py │ │ ├── box.ipynb │ │ ├── cloudy_data.py │ │ ├── cloudy_data_0d.py │ │ ├── rainshaft.ipynb │ │ ├── settings1D.py │ │ └── simulation_0D.py │ ├── deJong_Mackay_et_al_2023 │ │ ├── __init__.py │ │ ├── data │ │ │ └── straub_fig10.csv │ │ ├── fig_9.ipynb │ │ ├── figs_10_11_12_13.ipynb │ │ ├── figs_3_4_5.ipynb │ │ ├── figs_6_7_8.ipynb │ │ ├── plot_rates.py │ │ ├── settings1D.py │ │ ├── settings_0D.py │ │ ├── simulation1D.py │ │ ├── simulation_0D.py │ │ └── simulation_ss.py │ ├── seeding │ │ ├── __init__.py │ │ ├── hello_world.ipynb │ │ ├── seeding_no_collisions.ipynb │ │ ├── settings.py │ │ └── simulation.py │ └── utils │ │ ├── __init__.py │ │ ├── basic_simulation.py │ │ ├── dummy_controller.py │ │ ├── kinematic_2d │ │ ├── __init__.py │ │ ├── fields.py │ │ ├── gui.py │ │ ├── gui_controller.py │ │ ├── gui_settings.py │ │ ├── gui_viewer.py │ │ ├── make_default_product_collection.py │ │ ├── mpdata_2d.py │ │ ├── plots.py │ │ ├── simulation.py │ │ └── storage.py │ │ ├── progbar_controller.py │ │ ├── pvanim.py │ │ ├── read_vtk_1d.py │ │ └── widgets │ │ ├── __init__.py │ │ ├── freezer.py │ │ └── progbar_updater.py ├── README.md ├── docs │ └── pysdm_examples_landing.md ├── pyproject.toml └── setup.py ├── paper ├── joss-ARG-fig_1.pdf ├── paper.bib ├── paper.md ├── paperv1.md ├── readme.pdf └── test.pdf ├── pyproject.toml ├── setup.py ├── tests ├── __init__.py ├── examples_tests │ ├── __init__.py │ ├── conftest.py │ ├── test_run_examples.py │ ├── test_run_notebooks.py │ └── test_tests_completeness.py ├── smoke_tests │ ├── __init__.py │ ├── box │ │ ├── alpert_and_knopf_2016 │ │ │ ├── __init__.py │ │ │ ├── test_ak16_fig_1.py │ │ │ └── test_frozen_fraction.py │ │ ├── berry_1967 │ │ │ ├── __init__.py │ │ │ └── test_coalescence.py │ │ ├── bieli_et_al_2022 │ │ │ ├── __init__.py │ │ │ └── test_moments.py │ │ ├── dejong_and_mackay_et_al_2023 │ │ │ ├── __init__.py │ │ │ ├── test_collision.py │ │ │ ├── test_fig_6.py │ │ │ ├── test_fig_7.py │ │ │ └── test_fig_8.py │ │ ├── dejong_azimi │ │ │ └── test_box.py │ │ ├── partmc │ │ │ ├── __init__.py │ │ │ └── test_dry_wet_equilibration.py │ │ ├── shima_et_al_2009 │ │ │ ├── __init__.py │ │ │ ├── test_convergence.py │ │ │ └── test_lwc_constant.py │ │ └── srivastava_1982 │ │ │ ├── __init__.py │ │ │ ├── test_eq_10.py │ │ │ ├── test_eq_13.py │ │ │ └── test_equations.py │ ├── conftest.py │ ├── kinematic_1d │ │ ├── deJong_Azimi │ │ │ ├── __init__.py │ │ │ ├── test_few_steps.py │ │ │ └── test_initial_condition.py │ │ └── shipway_and_hill_2012 │ │ │ ├── __init__.py │ │ │ ├── test_1d_exporters.py │ │ │ ├── test_few_steps.py │ │ │ ├── test_initial_condition.py │ │ │ └── test_settings.py │ ├── kinematic_2d │ │ └── arabas_et_al_2015 │ │ │ ├── __init__.py │ │ │ ├── dummy_storage.py │ │ │ ├── test_adaptive_displacement.py │ │ │ ├── test_environment.py │ │ │ ├── test_export.py │ │ │ ├── test_freezing.py │ │ │ ├── test_gui_settings.py │ │ │ ├── test_initialisation.py │ │ │ └── test_spin_up.py │ ├── no_env │ │ ├── __init__.py │ │ ├── bolin_1958 │ │ │ ├── __init__.py │ │ │ └── test_table_1.py │ │ ├── gedzelman_and_arnold_1994 │ │ │ ├── __init__.py │ │ │ └── test_fig_2.py │ │ ├── gonfiantini_1986 │ │ │ ├── __init__.py │ │ │ └── test_fig_3_1.py │ │ ├── jouzel_and_merlivat_1984 │ │ │ ├── __init__.py │ │ │ └── test_thermodynamic_profiles.py │ │ ├── kinzer_and_gunn_1951 │ │ │ ├── __init__.py │ │ │ └── test_table_1_and_2.py │ │ ├── lamb_et_al_2017 │ │ │ ├── __init__.py │ │ │ └── test_fig_4.py │ │ ├── miyake_et_al_1968 │ │ │ ├── __init__.py │ │ │ └── test_fig_19.py │ │ ├── pierchala_et_al_2022 │ │ │ ├── __init__.py │ │ │ ├── test_fig_3.py │ │ │ ├── test_fig_4.py │ │ │ └── test_supplement.py │ │ ├── pruppacher_and_rasmussen_1979 │ │ │ ├── __init__.py │ │ │ └── test_fig_1.py │ │ ├── stewart_1975 │ │ │ ├── __init__.py │ │ │ └── test_fig_1.py │ │ ├── toon_et_al_1980 │ │ │ ├── __init__.py │ │ │ └── test_fig_1.py │ │ └── zaba_et_al_2025 │ │ │ ├── __init__.py │ │ │ └── test_global_meteoric_water_line.py │ ├── parcel_a │ │ ├── lowe_et_al_2019 │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── test_dz_sensitivity.py │ │ │ ├── test_fig_1.py │ │ │ ├── test_fig_2.py │ │ │ ├── test_fig_s2.py │ │ │ ├── test_surface_tension_models.py │ │ │ └── test_zero_forg.py │ │ └── pyrcel │ │ │ ├── __init__.py │ │ │ └── test_parcel_example.py │ ├── parcel_b │ │ ├── __init__.py │ │ └── arabas_and_shima_2017 │ │ │ ├── __init__.py │ │ │ ├── test_conservation.py │ │ │ ├── test_displacement.py │ │ │ ├── test_event_rates.py │ │ │ ├── test_initialisation.py │ │ │ └── test_vs_scipy.py │ ├── parcel_c │ │ ├── abade_and_albuquerque_2024 │ │ │ ├── __init__.py │ │ │ └── test_fig_2.py │ │ ├── abdul_razzak_ghan_2000 │ │ │ ├── __init__.py │ │ │ ├── test_ARG_example.py │ │ │ ├── test_just_do_it.py │ │ │ └── test_single_supersaturation_peak.py │ │ └── grabowski_and_pawlowska_2023 │ │ │ ├── __init__.py │ │ │ ├── test_condensation_tolerance.py │ │ │ ├── test_figure_1_and_2.py │ │ │ ├── test_figure_3.py │ │ │ ├── test_figure_4.py │ │ │ └── test_ripening_rate.py │ └── parcel_d │ │ ├── __init__.py │ │ ├── graf_et_al_2019 │ │ ├── __init__.py │ │ ├── test_fig_4.py │ │ └── test_table_1.py │ │ ├── jensen_and_nugent_2017 │ │ ├── __init__.py │ │ ├── test_fig_1.py │ │ ├── test_fig_3_and_tab_4_upper_rows.py │ │ ├── test_fig_4_and_7_and_tab_4_bottom_rows.py │ │ ├── test_fig_5.py │ │ ├── test_fig_6.py │ │ └── test_table_3.py │ │ ├── kreidenweis_et_al_2003 │ │ ├── __init__.py │ │ ├── test_fig_1.py │ │ ├── test_ionic_strength.py │ │ ├── test_spectrum_at_t_0.py │ │ └── test_table_3.py │ │ ├── niedermeier_et_al_2013 │ │ ├── __init__.py │ │ └── test_temperature_profile.py │ │ ├── rogers_1975 │ │ └── test_fig_1.py │ │ ├── rozanski_and_sonntag_1982 │ │ ├── __init__.py │ │ └── test_figs_4_5_6.py │ │ ├── seeding │ │ ├── __init__.py │ │ ├── test_hello_world.py │ │ └── test_seeding_no_collisions.py │ │ └── yang_et_al_2018 │ │ ├── __init__.py │ │ ├── test_displacement.py │ │ ├── test_initialisation.py │ │ └── test_just_do_it.py ├── tutorials_tests │ ├── __init__.py │ ├── conftest.py │ └── test_run_notebooks.py └── unit_tests │ ├── __init__.py │ ├── attributes │ ├── __init__.py │ ├── test_acidity.py │ ├── test_area_radius.py │ ├── test_critical_supersaturation.py │ ├── test_fall_velocity.py │ ├── test_impl_attribute_registry.py │ ├── test_isotopes.py │ ├── test_multiplicities.py │ └── test_reynolds_number.py │ ├── backends │ ├── __init__.py │ ├── storage │ │ ├── __init__.py │ │ ├── test_basic_ops.py │ │ ├── test_index.py │ │ └── test_setitem.py │ ├── test_collisions_methods.py │ ├── test_ctor_defaults.py │ ├── test_fake_thrust.py │ ├── test_isotope_methods.py │ ├── test_moments_methods.py │ ├── test_oxidation.py │ ├── test_pair_methods.py │ ├── test_physics_methods.py │ ├── test_seeding_methods.py │ └── test_toms748.py │ ├── conftest.py │ ├── dummy_environment.py │ ├── dummy_particulator.py │ ├── dynamics │ ├── __init__.py │ ├── collisions │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_croupiers.py │ │ ├── test_defaults.py │ │ ├── test_efficiencies.py │ │ ├── test_fragmentations.py │ │ ├── test_kernels.py │ │ ├── test_sdm_breakup.py │ │ ├── test_sdm_multi_cell.py │ │ └── test_sdm_single_cell.py │ ├── condensation │ │ ├── test_diagnostics.py │ │ ├── test_parcel_sanity_checks.py │ │ └── test_ventilation.py │ ├── displacement │ │ ├── __init__.py │ │ ├── displacement_settings.py │ │ ├── test_advection.py │ │ ├── test_courant_product.py │ │ └── test_sedimentation.py │ ├── test_eulerian_advection.py │ ├── test_immersion_freezing.py │ ├── test_impl_register_dynamic.py │ ├── test_isotopic_fractionation.py │ ├── test_relaxed_velocity.py │ ├── test_seeding.py │ ├── test_terminal_velocity.py │ └── test_vapour_deposition_on_ice.py │ ├── environments │ ├── __init__.py │ ├── test_impl.py │ └── test_moist.py │ ├── exporters │ ├── __init__.py │ └── test_vtk_exporter.py │ ├── impl │ ├── __init__.py │ ├── test_camel_case.py │ ├── test_mesh.py │ ├── test_moments.py │ └── test_particle_attributes.py │ ├── initialisation │ ├── __init__.py │ ├── test_aerosol_init.py │ ├── test_discretise_multiplicities.py │ ├── test_equilibrate_wet_radii.py │ ├── test_init_fall_momenta.py │ ├── test_spatial_discretisation.py │ ├── test_spectra_lognormal.py │ ├── test_spectral_discretisation.py │ └── test_spectro_glacial_discretisation.py │ ├── physics │ ├── __init__.py │ ├── test_accommodation_coefficients.py │ ├── test_air_dynamic_viscosity.py │ ├── test_bulk_phase_partitioning.py │ ├── test_constants.py │ ├── test_dimensional_analysis.py │ ├── test_drop_growth.py │ ├── test_fake_unit_registry.py │ ├── test_formulae.py │ ├── test_fragmentation_functions.py │ ├── test_freezing_temperature_spectra.py │ ├── test_hydrostatics_var_g.py │ ├── test_hygroscopicity_fierce_diagrams.py │ ├── test_isotope_diffusivity_ratios.py │ ├── test_isotope_equilibrium_fractionation_factors.py │ ├── test_isotope_kinetic_fractionation_factors.py │ ├── test_isotope_meteoric_water_line.py │ ├── test_isotope_ratio_evolution.py │ ├── test_isotope_relaxation_timescale.py │ ├── test_isotope_temperature_inference.py │ ├── test_isotope_ventilation_ratio.py │ ├── test_latent_heat.py │ ├── test_optical.py │ ├── test_particle_shape_and_density.py │ ├── test_saturation_vapour_pressure.py │ ├── test_spectra.py │ ├── test_spectra_top_hat.py │ ├── test_surface_tension.py │ ├── test_thermal_conductivity.py │ ├── test_trivia.py │ └── test_ventilation_coefficient.py │ ├── products │ ├── __init__.py │ ├── test_ambient_relative_humidity.py │ ├── test_arbitrary_moment.py │ ├── test_averaged_terminal_velocity.py │ ├── test_collision_rates.py │ ├── test_concentration_product.py │ ├── test_cooling_rate.py │ ├── test_effective_radii.py │ ├── test_impl.py │ ├── test_mixed_phase_moments.py │ ├── test_parcel_liquid_water_path.py │ ├── test_particle_size_product.py │ ├── test_particle_size_spectrum.py │ └── test_surface_precipitation.py │ ├── test_builder.py │ ├── test_formulae.py │ ├── test_imports.py │ └── test_particulator.py └── tutorials ├── README.md ├── collisions ├── collection_droplet.svg └── collisions_playground.ipynb ├── condensation ├── condensation_playground.ipynb └── kohler_curve.svg └── wikipedia └── sdm.ipynb /.binder/apt.txt: -------------------------------------------------------------------------------- 1 | libgl1 2 | -------------------------------------------------------------------------------- /.binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # mimicking what happens on Colab: packages are fetched from PyPI, only notebooks from the repo 4 | 5 | set -e 6 | shopt -s extglob 7 | rm -rfv !("examples"|"tutorials") 8 | -------------------------------------------------------------------------------- /.binder/requirements.txt: -------------------------------------------------------------------------------- 1 | PySDM-examples 2 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 0.1% 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: pip 8 | directory: "/examples" 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/cancel.yml: -------------------------------------------------------------------------------- 1 | name: Cancel 2 | on: 3 | workflow_run: 4 | workflows: ["PySDM"] 5 | types: 6 | - requested 7 | jobs: 8 | cancel: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: styfle/cancel-workflow-action@0.12.1 12 | with: 13 | all_but_latest: true 14 | workflow_id: ${{ github.event.workflow.id }} 15 | -------------------------------------------------------------------------------- /.github/workflows/joss.yml: -------------------------------------------------------------------------------- 1 | name: Build JOSS paper draft 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | paper: 11 | runs-on: ubuntu-latest 12 | name: Paper Draft 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4.1.6 16 | - name: TeX and PDF 17 | uses: docker://openjournals/paperdraft:latest 18 | with: 19 | args: '-k paper/paper.md' 20 | env: 21 | GIT_SHA: $GITHUB_SHA 22 | JOURNAL: joss 23 | - name: Upload 24 | uses: actions/upload-artifact@v4.3.1 25 | with: 26 | name: paper 27 | path: paper/ 28 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '45 13 * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v9.0.0 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | stale-issue-message: 'Stale issue message' 25 | stale-pr-message: 'Stale pull request message' 26 | stale-issue-label: 'no-activity' 27 | stale-pr-label: 'no-activity' 28 | operations-per-run: 3 29 | days-before-stale: 90 30 | days-before-close: 30 31 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/devops_tests"] 2 | path = tests/devops_tests 3 | url = https://github.com/open-atmos/devops_tests 4 | shallow = true 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | files: '.py|.json' 2 | exclude: '.git' 3 | default_stages: [pre-commit] 4 | 5 | repos: 6 | - repo: https://github.com/psf/black 7 | rev: 25.1.0 8 | hooks: 9 | - id: black 10 | 11 | # - repo: https://github.com/timothycrosley/isort 12 | # rev: 5.13.2 13 | # hooks: 14 | # - id: isort 15 | 16 | - repo: https://github.com/pre-commit/pre-commit-hooks 17 | rev: v5.0.0 18 | hooks: 19 | - id: trailing-whitespace 20 | - id: end-of-file-fixer 21 | - id: debug-statements 22 | - id: check-json 23 | files: '.json' 24 | -------------------------------------------------------------------------------- /PySDM/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint:disable=invalid-name 2 | """ 3 | .. include:: ../docs/markdown/pysdm_landing.md 4 | """ 5 | 6 | from importlib.metadata import PackageNotFoundError, version 7 | 8 | from PySDM.attributes.impl.attribute_registry import register_attribute 9 | 10 | from . import attributes 11 | from . import environments, exporters, products 12 | from .builder import Builder 13 | from .formulae import Formulae 14 | from .particulator import Particulator 15 | 16 | try: 17 | __version__ = version(__name__) 18 | except PackageNotFoundError: 19 | # package is not installed 20 | pass 21 | -------------------------------------------------------------------------------- /PySDM/attributes/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Classes representing super-particle attributes 3 | """ 4 | 5 | from .physics import * 6 | from .numerics import * 7 | from .ice import * 8 | from .chemistry import * 9 | from .isotopes import * 10 | -------------------------------------------------------------------------------- /PySDM/attributes/chemistry/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | attributes used by the `PySDM.dynamics.aqueous_chemistry` dynamic 3 | """ 4 | 5 | from .acidity import Acidity 6 | from .concentration import make_concentration_factory 7 | from .hydrogen_ion_concentration import HydrogenIonConcentration 8 | -------------------------------------------------------------------------------- /PySDM/attributes/chemistry/concentration.py: -------------------------------------------------------------------------------- 1 | """ 2 | concentrations (intensive, derived attributes) 3 | """ 4 | 5 | from PySDM.attributes.impl import IntensiveAttribute, register_attribute 6 | from PySDM.attributes.impl.mole_amount import make_mole_amount_factory 7 | from PySDM.dynamics.impl.chemistry_utils import AQUEOUS_COMPOUNDS 8 | 9 | 10 | class ConcentrationImpl(IntensiveAttribute): 11 | def __init__(self, builder, *, what): 12 | super().__init__(builder, name="conc_" + what, base="moles_" + what) 13 | 14 | 15 | def make_concentration_factory(what): 16 | def _factory(builder): 17 | return ConcentrationImpl(builder, what=what) 18 | 19 | return _factory 20 | 21 | 22 | for compound in AQUEOUS_COMPOUNDS: 23 | register_attribute(name=f"conc_{compound}")(make_concentration_factory(compound)) 24 | 25 | register_attribute(name=f"moles_{compound}")(make_mole_amount_factory(compound)) 26 | -------------------------------------------------------------------------------- /PySDM/attributes/chemistry/hydrogen_ion_concentration.py: -------------------------------------------------------------------------------- 1 | """ 2 | hydrogen ion concentration derived from pH 3 | """ 4 | 5 | from PySDM.attributes.impl import DerivedAttribute, register_attribute 6 | 7 | 8 | @register_attribute(name="conc_H") 9 | class HydrogenIonConcentration(DerivedAttribute): 10 | def __init__(self, builder): 11 | self.acidity = builder.get_attribute("pH") 12 | super().__init__(builder, name="conc_H", dependencies=(self.acidity,)) 13 | 14 | def recalculate(self): 15 | self.data[:] = self.formulae.trivia.pH2H(self.acidity.get().data) 16 | -------------------------------------------------------------------------------- /PySDM/attributes/ice/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | attributes used by the `PySDM.dynamics.freezing.Freezing` dynamic 3 | """ 4 | 5 | from .cooling_rate import CoolingRate 6 | from .freezing_temperature import FreezingTemperature 7 | from .immersed_surface_area import ImmersedSurfaceArea 8 | -------------------------------------------------------------------------------- /PySDM/attributes/ice/immersed_surface_area.py: -------------------------------------------------------------------------------- 1 | """ 2 | immersed INP surface area (assigned at initialisation, modified through collisions only, 3 | used in time-dependent regime) 4 | """ 5 | 6 | from ..impl import ExtensiveAttribute, register_attribute 7 | 8 | 9 | @register_attribute() 10 | class ImmersedSurfaceArea(ExtensiveAttribute): 11 | def __init__(self, particles_builder): 12 | super().__init__(particles_builder, name="immersed surface area") 13 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | common code intended for use from within attribute classes (not in user code) 3 | """ 4 | 5 | from .attribute import Attribute 6 | from .base_attribute import BaseAttribute 7 | from .cell_attribute import CellAttribute 8 | from .derived_attribute import DerivedAttribute 9 | from .dummy_attribute import DummyAttribute 10 | from .extensive_attribute import ExtensiveAttribute 11 | from .maximum_attribute import MaximumAttribute 12 | from .attribute_registry import register_attribute, get_attribute_class 13 | from .intensive_attribute import IntensiveAttribute 14 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/base_attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | logic around `PySDM.attributes.impl.base_attribute.BaseAttribute` - the parent class 3 | for non-derived attributes 4 | """ 5 | 6 | from .attribute import Attribute 7 | 8 | 9 | class BaseAttribute(Attribute): 10 | def __init__(self, builder, name, dtype=float, n_vector_components=0): 11 | super().__init__( 12 | builder, name=name, dtype=dtype, n_vector_components=n_vector_components 13 | ) 14 | 15 | def init(self, data): 16 | self.data.upload(data) 17 | self.mark_updated() 18 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/cell_attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | logic around `PySDM.attributes.impl.cell_attribute.CellAttribute` - the parent class 3 | for grid-particle mapping attributes 4 | """ 5 | 6 | from .base_attribute import BaseAttribute 7 | 8 | 9 | class CellAttribute(BaseAttribute): 10 | pass 11 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/derived_attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | logic around `PySDM.attributes.impl.derived_attribute.DerivedAttribute` - the parent class 3 | for all derived attributes 4 | """ 5 | 6 | from .attribute import Attribute 7 | 8 | 9 | class DerivedAttribute(Attribute): 10 | def __init__(self, builder, name, dependencies): 11 | assert len(dependencies) > 0 12 | super().__init__(builder, name) 13 | self.dependencies = dependencies 14 | 15 | def update(self): 16 | for dependency in self.dependencies: 17 | dependency.update() 18 | dependencies_timestamp = sum( 19 | dependency.timestamp for dependency in self.dependencies 20 | ) 21 | if self.timestamp < dependencies_timestamp: 22 | self.timestamp = dependencies_timestamp 23 | self.recalculate() 24 | 25 | def recalculate(self): 26 | raise NotImplementedError() 27 | 28 | def mark_updated(self): 29 | raise AssertionError() 30 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/dummy_attribute.py: -------------------------------------------------------------------------------- 1 | """logic around `PySDM.attributes.impl.dummy_attribute.DummyAttribute` - parent class 2 | for do-nothing attributes""" 3 | 4 | import numpy as np 5 | 6 | from .attribute import Attribute 7 | 8 | 9 | class DummyAttribute(Attribute): 10 | def __init__(self, builder, name): 11 | super().__init__(builder, name) 12 | 13 | def allocate(self, idx): 14 | super().allocate(idx) 15 | self.data[:] = np.nan 16 | 17 | def get(self): 18 | return self.data 19 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/extensive_attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | logic around `PySDM.attributes.impl.extensive_attribute.ExtensiveAttribute` - parent class 3 | for all extensive attributes 4 | """ 5 | 6 | from .base_attribute import BaseAttribute 7 | 8 | 9 | class ExtensiveAttribute(BaseAttribute): 10 | pass 11 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/intensive_attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | logic around `PySDM.attributes.impl.intensive_attribute.IntensiveAttribute` - parent class 3 | for all intensive attributes 4 | """ 5 | 6 | from .derived_attribute import DerivedAttribute 7 | 8 | 9 | class IntensiveAttribute(DerivedAttribute): 10 | def __init__(self, builder, name: str, base: str): 11 | self.volume = builder.get_attribute("volume") 12 | self.base = builder.get_attribute(base) 13 | super().__init__(builder, name, dependencies=(self.volume, self.base)) 14 | 15 | def recalculate(self): 16 | self.data.ratio(self.base.get(), self.volume.get()) 17 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/maximum_attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | logic around `PySDM.attributes.impl.maximum_attribute.MaximumAttribute` - parent class 3 | for attributes for which under coalescence the newly collided particle's attribute 4 | value is set to maximum of values of colliding particle (e.g., freezing temperature 5 | in singular immersion freezing) 6 | """ 7 | 8 | from .base_attribute import BaseAttribute 9 | 10 | 11 | class MaximumAttribute(BaseAttribute): 12 | pass 13 | -------------------------------------------------------------------------------- /PySDM/attributes/impl/mole_amount.py: -------------------------------------------------------------------------------- 1 | """ 2 | mole amounts (extensive, base attributes) 3 | """ 4 | 5 | from PySDM.attributes.impl.extensive_attribute import ExtensiveAttribute 6 | 7 | 8 | class MoleAmountImpl(ExtensiveAttribute): 9 | def __init__(self, builder, *, name): 10 | super().__init__(builder, name=name) 11 | 12 | 13 | def make_mole_amount_factory(compound): 14 | def _factory(builder): 15 | return MoleAmountImpl(builder, name="moles_" + compound) 16 | 17 | return _factory 18 | -------------------------------------------------------------------------------- /PySDM/attributes/isotopes/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | isotopic fractionation related attributes 3 | """ 4 | 5 | from .moles import Moles1H, Moles16O, MolesLightWater 6 | from ..isotopes import delta 7 | -------------------------------------------------------------------------------- /PySDM/attributes/numerics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | attributes used for tracking cell-particle mapping in multi-dimensional simulations 3 | """ 4 | 5 | from .cell_id import CellId 6 | from .cell_origin import CellOrigin 7 | from .position_in_cell import PositionInCell 8 | -------------------------------------------------------------------------------- /PySDM/attributes/numerics/cell_id.py: -------------------------------------------------------------------------------- 1 | """ 2 | grid cell id attribute 3 | """ 4 | 5 | from PySDM.attributes.impl import CellAttribute, register_attribute 6 | 7 | 8 | @register_attribute() 9 | class CellId(CellAttribute): 10 | def __init__(self, builder): 11 | super().__init__(builder, name="cell id", dtype=int) 12 | 13 | def recalculate(self): 14 | pass 15 | -------------------------------------------------------------------------------- /PySDM/attributes/numerics/cell_origin.py: -------------------------------------------------------------------------------- 1 | """ 2 | grid-cell origin (multi-dimensional) 3 | """ 4 | 5 | from PySDM.attributes.impl import CellAttribute, register_attribute 6 | 7 | 8 | @register_attribute() 9 | class CellOrigin(CellAttribute): 10 | def __init__(self, builder): 11 | super().__init__( 12 | builder, 13 | name="cell origin", 14 | dtype=int, 15 | n_vector_components=builder.particulator.mesh.dim, 16 | ) 17 | -------------------------------------------------------------------------------- /PySDM/attributes/numerics/position_in_cell.py: -------------------------------------------------------------------------------- 1 | """ 2 | position-within-cell attribute (multi-dimensional, values normalised to one) 3 | """ 4 | 5 | from PySDM.attributes.impl import CellAttribute, register_attribute 6 | 7 | 8 | @register_attribute() 9 | class PositionInCell(CellAttribute): 10 | def __init__(self, builder): 11 | super().__init__( 12 | builder, 13 | name="position in cell", 14 | n_vector_components=builder.particulator.mesh.dim, 15 | ) 16 | -------------------------------------------------------------------------------- /PySDM/attributes/physics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | attributes carrying information on particle physical properties 3 | """ 4 | 5 | from .area import Area 6 | from .critical_supersaturation import CriticalSupersaturation 7 | from .critical_volume import CriticalVolume, WetToCriticalVolumeRatio 8 | from .dry_radius import DryRadius 9 | from .dry_volume import DryVolume 10 | from .equilibrium_supersaturation import EquilibriumSupersaturation 11 | from .heat import Heat 12 | from .hygroscopicity import Kappa, KappaTimesDryVolume 13 | from .water_mass import SignedWaterMass 14 | from .multiplicity import Multiplicity 15 | from .radius import Radius, SquareRootOfRadius 16 | from .relative_fall_velocity import RelativeFallMomentum, RelativeFallVelocity 17 | from .temperature import Temperature 18 | from .terminal_velocity import TerminalVelocity 19 | from .volume import Volume 20 | from .reynolds_number import ReynoldsNumber 21 | -------------------------------------------------------------------------------- /PySDM/attributes/physics/area.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle wet radius (calculated from the volume) 3 | """ 4 | 5 | from PySDM.attributes.impl import DerivedAttribute, register_attribute 6 | 7 | 8 | @register_attribute() 9 | class Area(DerivedAttribute): 10 | def __init__(self, builder): 11 | self.volume = builder.get_attribute("volume") 12 | dependencies = [self.volume] 13 | super().__init__(builder, name="area", dependencies=dependencies) 14 | 15 | def recalculate(self): 16 | self.data.product(self.volume.get(), 1 / self.formulae.constants.PI_4_3) 17 | self.data **= 2 / 3 18 | self.data *= self.formulae.constants.PI_4_3 * 3 19 | -------------------------------------------------------------------------------- /PySDM/attributes/physics/dry_radius.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle dry radius computed from dry volume 3 | """ 4 | 5 | from PySDM.attributes.impl import DerivedAttribute, register_attribute 6 | 7 | 8 | @register_attribute() 9 | class DryRadius(DerivedAttribute): 10 | def __init__(self, builder): 11 | self.volume_dry = builder.get_attribute("dry volume") 12 | dependencies = [self.volume_dry] 13 | super().__init__(builder, name="dry radius", dependencies=dependencies) 14 | 15 | def recalculate(self): 16 | self.data.product(self.volume_dry.get(), 1 / self.formulae.constants.PI_4_3) 17 | self.data **= 1 / 3 18 | -------------------------------------------------------------------------------- /PySDM/attributes/physics/heat.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle heat content (test-use only for now, exemplifying intensive/extensive attribute logic) 3 | """ 4 | 5 | from PySDM.attributes.impl import ExtensiveAttribute, register_attribute 6 | 7 | 8 | @register_attribute() 9 | class Heat(ExtensiveAttribute): 10 | def __init__(self, builder): 11 | super().__init__(builder, name="heat") 12 | -------------------------------------------------------------------------------- /PySDM/attributes/physics/multiplicity.py: -------------------------------------------------------------------------------- 1 | """ 2 | super-particle multiplicity (aka weighting factor) - the number of real-world particles 3 | represented in the simulation with a given super particle 4 | """ 5 | 6 | import numpy as np 7 | from PySDM.attributes.impl import BaseAttribute, register_attribute 8 | 9 | 10 | @register_attribute() 11 | class Multiplicity(BaseAttribute): 12 | TYPE = np.int64 13 | MAX_VALUE = np.iinfo(TYPE).max 14 | 15 | def __init__(self, builder): 16 | super().__init__(builder, name="multiplicity", dtype=Multiplicity.TYPE) 17 | -------------------------------------------------------------------------------- /PySDM/attributes/physics/temperature.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle temperature (test-use only for now, exemplifying intensive/extensive attribute logic) 3 | """ 4 | 5 | from PySDM.attributes.impl import IntensiveAttribute, register_attribute 6 | 7 | 8 | @register_attribute() 9 | class Temperature(IntensiveAttribute): 10 | def __init__(self, builder): 11 | super().__init__(builder, base="heat", name="temperature") 12 | -------------------------------------------------------------------------------- /PySDM/attributes/physics/terminal_velocity.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle terminal velocity (used for collision probability and particle displacement) 3 | """ 4 | 5 | from PySDM.attributes.impl import DerivedAttribute, register_attribute 6 | 7 | 8 | @register_attribute( 9 | name="relative fall velocity", 10 | variant=lambda dynamics, _: "RelaxedVelocity" not in dynamics, 11 | ) 12 | @register_attribute() 13 | class TerminalVelocity(DerivedAttribute): 14 | def __init__(self, builder): 15 | self.radius = builder.get_attribute("radius") 16 | dependencies = [self.radius] 17 | super().__init__(builder, name="terminal velocity", dependencies=dependencies) 18 | 19 | self.approximation = builder.formulae.terminal_velocity_class( 20 | builder.particulator 21 | ) 22 | 23 | def recalculate(self): 24 | self.approximation(self.data, self.radius.get()) 25 | -------------------------------------------------------------------------------- /PySDM/attributes/physics/volume.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle volume (derived from water mass); 3 | in simulation involving mixed-phase clouds, positive values correspond to 4 | liquid water and negative values to ice 5 | """ 6 | 7 | from PySDM.attributes.impl import DerivedAttribute, register_attribute 8 | 9 | 10 | @register_attribute() 11 | class Volume(DerivedAttribute): 12 | def __init__(self, builder): 13 | self.water_mass = builder.get_attribute("water mass") 14 | super().__init__(builder, name="volume", dependencies=(self.water_mass,)) 15 | 16 | def recalculate(self): 17 | self.particulator.backend.volume_of_water_mass(self.data, self.water_mass.get()) 18 | -------------------------------------------------------------------------------- /PySDM/backends/impl_common/__init__.py: -------------------------------------------------------------------------------- 1 | """common code for all backends""" 2 | -------------------------------------------------------------------------------- /PySDM/backends/impl_common/backend_methods.py: -------------------------------------------------------------------------------- 1 | """ 2 | logic around the `PySDM.backends.impl_common.backend_methods.BackendMethods` - the parent 3 | class for all backend methods classes 4 | """ 5 | 6 | 7 | # pylint: disable=too-few-public-methods 8 | class BackendMethods: 9 | def __init__(self): 10 | if not hasattr(self, "formulae"): 11 | self.formulae = None 12 | if not hasattr(self, "formulae_flattened"): 13 | self.formulae_flattened = None 14 | if not hasattr(self, "Storage"): 15 | self.Storage = None 16 | if not hasattr(self, "default_jit_flags"): 17 | self.default_jit_flags = {} 18 | -------------------------------------------------------------------------------- /PySDM/backends/impl_common/freezing_attributes.py: -------------------------------------------------------------------------------- 1 | """ 2 | groups of attributes used in either singular or time-dependent immersion freezing regimes 3 | """ 4 | 5 | from collections import namedtuple 6 | 7 | 8 | class SingularAttributes( 9 | namedtuple( 10 | typename="SingularAttributes", 11 | field_names=("freezing_temperature", "signed_water_mass"), 12 | ) 13 | ): 14 | """groups attributes required in singular regime""" 15 | 16 | __slots__ = () 17 | 18 | 19 | class TimeDependentAttributes( 20 | namedtuple( 21 | typename="TimeDependentAttributes", 22 | field_names=("immersed_surface_area", "signed_water_mass"), 23 | ) 24 | ): 25 | """groups attributes required in time-dependent regime""" 26 | 27 | __slots__ = () 28 | -------------------------------------------------------------------------------- /PySDM/backends/impl_common/pair_indicator.py: -------------------------------------------------------------------------------- 1 | """ 2 | storage abstraction layer facilitating pairwise operations (for use with PairwiseStorage class) 3 | """ 4 | 5 | 6 | def make_PairIndicator(backend): 7 | class PairIndicator: 8 | def __init__(self, length): 9 | self.indicator = backend.Storage.empty(length, dtype=bool) 10 | self.length = length 11 | 12 | def __len__(self): 13 | return self.length 14 | 15 | def update(self, cell_start, cell_idx, cell_id): 16 | backend.find_pairs(cell_start, self, cell_id, cell_idx, cell_id.idx) 17 | self.length = len(cell_id) 18 | 19 | return PairIndicator 20 | -------------------------------------------------------------------------------- /PySDM/backends/impl_common/random_common.py: -------------------------------------------------------------------------------- 1 | """ 2 | common base class for random number generation abstraction layer 3 | """ 4 | 5 | 6 | class RandomCommon: # pylint: disable=too-few-public-methods 7 | def __init__(self, size: int, seed: int): 8 | assert isinstance(size, int) 9 | assert isinstance(seed, int) 10 | self.size = size 11 | -------------------------------------------------------------------------------- /PySDM/backends/impl_numba/__init__.py: -------------------------------------------------------------------------------- 1 | """the guts of the CPU backend""" 2 | -------------------------------------------------------------------------------- /PySDM/backends/impl_numba/conf.py: -------------------------------------------------------------------------------- 1 | """ 2 | default settings for Numba just-in-time compilation 3 | """ 4 | 5 | JIT_FLAGS = { 6 | "parallel": False, 7 | "fastmath": True, 8 | "error_model": "numpy", 9 | "cache": False, # https://github.com/numba/numba/issues/2956 10 | } 11 | -------------------------------------------------------------------------------- /PySDM/backends/impl_numba/methods/__init__.py: -------------------------------------------------------------------------------- 1 | """method classes of the CPU backend""" 2 | 3 | from .chemistry_methods import ChemistryMethods 4 | from .collisions_methods import CollisionsMethods 5 | from .condensation_methods import CondensationMethods 6 | from .displacement_methods import DisplacementMethods 7 | from .fragmentation_methods import FragmentationMethods 8 | from .freezing_methods import FreezingMethods 9 | from .index_methods import IndexMethods 10 | from .isotope_methods import IsotopeMethods 11 | from .moments_methods import MomentsMethods 12 | from .pair_methods import PairMethods 13 | from .physics_methods import PhysicsMethods 14 | from .terminal_velocity_methods import TerminalVelocityMethods 15 | from .seeding_methods import SeedingMethods 16 | from .deposition_methods import DepositionMethods 17 | -------------------------------------------------------------------------------- /PySDM/backends/impl_numba/methods/isotope_methods.py: -------------------------------------------------------------------------------- 1 | """ 2 | CPU implementation of isotope-relates backend methods 3 | """ 4 | 5 | from functools import cached_property 6 | 7 | import numba 8 | 9 | from PySDM.backends.impl_common.backend_methods import BackendMethods 10 | 11 | 12 | class IsotopeMethods(BackendMethods): 13 | @cached_property 14 | def _isotopic_delta_body(self): 15 | ff = self.formulae_flattened 16 | 17 | @numba.njit(**self.default_jit_flags) 18 | def body(output, ratio, reference_ratio): 19 | for i in numba.prange(output.shape[0]): # pylint: disable=not-an-iterable 20 | output[i] = ff.trivia__isotopic_ratio_2_delta(ratio[i], reference_ratio) 21 | 22 | return body 23 | 24 | def isotopic_delta(self, output, ratio, reference_ratio): 25 | self._isotopic_delta_body(output.data, ratio.data, reference_ratio) 26 | 27 | def isotopic_fractionation(self): 28 | pass 29 | -------------------------------------------------------------------------------- /PySDM/backends/impl_numba/random.py: -------------------------------------------------------------------------------- 1 | """ 2 | random number generator class for Numba backend 3 | """ 4 | 5 | import numpy as np 6 | 7 | from ..impl_common.random_common import RandomCommon 8 | 9 | # TIP: can be called asynchronously 10 | # TIP: sometimes only half array is needed 11 | 12 | 13 | class Random(RandomCommon): # pylint: disable=too-few-public-methods 14 | def __init__(self, size, seed): 15 | super().__init__(size, seed) 16 | self.generator = np.random.default_rng(seed) 17 | 18 | def __call__(self, storage): 19 | storage.data[:] = self.generator.uniform(0, 1, storage.shape) 20 | -------------------------------------------------------------------------------- /PySDM/backends/impl_numba/test_helpers/__init__.py: -------------------------------------------------------------------------------- 1 | """logic intended to be used in tests only including the SciPy-based condensation solver""" 2 | -------------------------------------------------------------------------------- /PySDM/backends/impl_numba/warnings.py: -------------------------------------------------------------------------------- 1 | """ 2 | warning reporting logic for use whithin Numba njitted code (printing to standard 3 | error using numba.objmode() allowing to capture the output from Python tests 4 | """ 5 | 6 | import sys 7 | 8 | import numba 9 | 10 | from PySDM.backends.impl_numba import conf 11 | 12 | 13 | @numba.njit(**{**conf.JIT_FLAGS, **{"parallel": False}}) 14 | def warn(msg, file, context=None, return_value=None): 15 | with numba.objmode(): 16 | print(msg, file=sys.stderr) 17 | print("\tfile:", file, file=sys.stderr) 18 | if context is not None: 19 | print("\tcontext:", file=sys.stderr) 20 | for var in context: 21 | print("\t\t", var, file=sys.stderr) 22 | return return_value 23 | -------------------------------------------------------------------------------- /PySDM/backends/impl_thrust_rtc/__init__.py: -------------------------------------------------------------------------------- 1 | """the guts of the GPU backend""" 2 | -------------------------------------------------------------------------------- /PySDM/backends/impl_thrust_rtc/methods/__init__.py: -------------------------------------------------------------------------------- 1 | """method classes of the GPU backend""" 2 | -------------------------------------------------------------------------------- /PySDM/backends/impl_thrust_rtc/methods/thrust_rtc_backend_methods.py: -------------------------------------------------------------------------------- 1 | """ 2 | common parent class for all ThrustRTC backend methods classes 3 | """ 4 | 5 | from typing import Callable, Optional 6 | 7 | from ...impl_common.backend_methods import BackendMethods 8 | 9 | 10 | class ThrustRTCBackendMethods(BackendMethods): # pylint: disable=too-few-public-methods 11 | def __init__(self): 12 | super().__init__() 13 | if not hasattr(self, "_conv_function"): 14 | self._conv_function: Optional[Callable] = None 15 | if not hasattr(self, "_real_type"): 16 | self._real_type = None 17 | if not hasattr(self, "_np_dtype"): 18 | self._np_dtype = None 19 | 20 | def _get_floating_point(self, number): 21 | return self._conv_function(number) # pylint: disable=not-callable 22 | 23 | def _get_c_type(self): 24 | return self._real_type 25 | 26 | def _get_np_dtype(self): 27 | return self._np_dtype 28 | -------------------------------------------------------------------------------- /PySDM/backends/impl_thrust_rtc/nice_thrust.py: -------------------------------------------------------------------------------- 1 | """ 2 | a decorator triggering ThrustRTC.Wait() after each function call 3 | """ 4 | 5 | from PySDM.backends.impl_thrust_rtc.conf import trtc 6 | 7 | 8 | def nice_thrust(*, wait=False, debug_print=False): 9 | def decorator(func): 10 | def wrapper(*args, **kwargs): 11 | if debug_print: 12 | print(func.__name__) 13 | result = func(*args, **kwargs) 14 | if wait: 15 | trtc.Wait() 16 | return result 17 | 18 | return wrapper 19 | 20 | return decorator 21 | -------------------------------------------------------------------------------- /PySDM/backends/impl_thrust_rtc/test_helpers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | logic intended to be used in tests only including the 3 | `PySDM.backends.impl_thrust_rtc.test_helpers.fake_thrust_rtc.FakeThrustRTC` magick 4 | """ 5 | -------------------------------------------------------------------------------- /PySDM/backends/impl_thrust_rtc/test_helpers/flag.py: -------------------------------------------------------------------------------- 1 | """ 2 | flag enabling `PySDM.backends.impl_thrust_rtc.test_helpers.fake_thrust_rtc.FakeThrustRTC` 3 | (for tests of GPU code on machines with no GPU) 4 | """ 5 | 6 | fakeThrustRTC = False 7 | -------------------------------------------------------------------------------- /PySDM/dynamics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Classes representing physicochemical processes: 3 | `PySDM.dynamics.collisions.collision.Collision`, 4 | `PySDM.dynamics.condensation.Condensation`, ... 5 | """ 6 | 7 | from PySDM.dynamics.isotopic_fractionation import IsotopicFractionation 8 | 9 | # isort: split 10 | from PySDM.dynamics.ambient_thermodynamics import AmbientThermodynamics 11 | from PySDM.dynamics.aqueous_chemistry import AqueousChemistry 12 | from PySDM.dynamics.collisions import Breakup, Coalescence, Collision 13 | from PySDM.dynamics.condensation import Condensation 14 | from PySDM.dynamics.displacement import Displacement 15 | from PySDM.dynamics.eulerian_advection import EulerianAdvection 16 | from PySDM.dynamics.freezing import Freezing 17 | from PySDM.dynamics.relaxed_velocity import RelaxedVelocity 18 | from PySDM.dynamics.seeding import Seeding 19 | from PySDM.dynamics.vapour_deposition_on_ice import VapourDepositionOnIce 20 | -------------------------------------------------------------------------------- /PySDM/dynamics/ambient_thermodynamics.py: -------------------------------------------------------------------------------- 1 | """ 2 | environment-sync triggering class 3 | """ 4 | 5 | from PySDM.dynamics.impl import register_dynamic 6 | 7 | 8 | @register_dynamic() 9 | class AmbientThermodynamics: 10 | def __init__(self): 11 | self.particulator = None 12 | 13 | def register(self, builder): 14 | self.particulator = builder.particulator 15 | 16 | def __call__(self): 17 | self.particulator.environment.sync() 18 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | collisions-related logic including the `PySDM.dynamics.collisions.collision.Collision` 3 | dynamic and coalescence. 4 | Includes collision kernels, ``PySDM.dynamics.collisions.collision_kernels`, 5 | as well as coalescence efficiencies, `PySDM.dynamics.collisions.coalescence_efficiencies`, 6 | and breakup efficiencies `PySDM.dynamics.collisions.breakup_efficiencies`, and 7 | breakup fragmentations `PySDM.dynamics.collisions.breakup_fragmentations` 8 | """ 9 | 10 | from PySDM.dynamics.collisions.collision import Breakup, Coalescence, Collision 11 | 12 | from . import collision_kernels 13 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/breakup_efficiencies/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Breakup efficiencies 3 | """ 4 | 5 | from .constEb import ConstEb 6 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/breakup_efficiencies/constEb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Specifies constant breakup efficiency. 3 | """ 4 | 5 | 6 | class ConstEb: 7 | def __init__(self, Eb=1.0): 8 | self.Eb = Eb 9 | self.particulator = None 10 | 11 | def register(self, builder): 12 | self.particulator = builder.particulator 13 | 14 | def __call__(self, output, is_first_in_pair): 15 | output.fill(self.Eb) 16 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/breakup_fragmentations/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO #744 3 | """ 4 | 5 | from .always_n import AlwaysN 6 | from .constant_mass import ConstantMass 7 | from .expon_frag import ExponFrag 8 | from .exponential import Exponential 9 | from .feingold1988 import Feingold1988 10 | from .gaussian import Gaussian 11 | from .lowlist82 import LowList1982Nf 12 | from .slams import SLAMS 13 | from .straub2010 import Straub2010Nf 14 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/breakup_fragmentations/always_n.py: -------------------------------------------------------------------------------- 1 | """ 2 | Always produces N fragments in a given collisional breakup 3 | """ 4 | 5 | 6 | class AlwaysN: # pylint: disable=too-many-instance-attributes 7 | def __init__(self, n): 8 | self.particulator = None 9 | self.N = n 10 | 11 | def __call__(self, nf, frag_mass, u01, is_first_in_pair): 12 | nf.fill(self.N) 13 | frag_mass.sum(self.particulator.attributes["water mass"], is_first_in_pair) 14 | frag_mass /= self.N 15 | 16 | def register(self, builder): 17 | self.particulator = builder.particulator 18 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/breakup_fragmentations/constant_mass.py: -------------------------------------------------------------------------------- 1 | """ 2 | Always produces fragments of mass c in a given collisional breakup 3 | """ 4 | 5 | 6 | class ConstantMass: # pylint: disable=too-many-instance-attributes 7 | def __init__(self, c): 8 | self.particulator = None 9 | self.C = c 10 | 11 | def __call__(self, nf, frag_mass, u01, is_first_in_pair): 12 | frag_mass[:] = self.C 13 | nf.sum(self.particulator.attributes["water mass"], is_first_in_pair) 14 | nf /= self.C 15 | 16 | def register(self, builder): 17 | self.particulator = builder.particulator 18 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/breakup_fragmentations/expon_frag.py: -------------------------------------------------------------------------------- 1 | """ 2 | DEPRECATED 3 | P(x) = exp(-x / lambda); lambda specified in volume units 4 | """ 5 | 6 | import warnings 7 | 8 | from .exponential import Exponential 9 | 10 | 11 | class ExponFrag(Exponential): # pylint: disable=too-few-public-methods 12 | def __init_subclass__(cls): 13 | warnings.warn("Class has been renamed", DeprecationWarning) 14 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/breakup_fragmentations/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Abstractions and common code for fragmentation functions 3 | """ 4 | 5 | from .volume_based import VolumeBasedFragmentationFunction 6 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/breakup_fragmentations/impl/volume_based.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base class for volume-based fragmentation functions 3 | """ 4 | 5 | 6 | class VolumeBasedFragmentationFunction: 7 | def __init__(self): 8 | self.particulator = None 9 | 10 | def __call__(self, nf, frag_mass, u01, is_first_in_pair): 11 | frag_volume_aliased_to_mass = frag_mass 12 | self.compute_fragment_number_and_volumes( 13 | nf, frag_volume_aliased_to_mass, u01, is_first_in_pair 14 | ) 15 | self.particulator.backend.mass_of_water_volume( 16 | frag_mass, frag_volume_aliased_to_mass 17 | ) 18 | 19 | def compute_fragment_number_and_volumes( 20 | self, nf, frag_volume, u01, is_first_in_pair 21 | ): 22 | raise NotImplementedError() 23 | 24 | def register(self, builder): 25 | self.particulator = builder.particulator 26 | builder.request_attribute("volume") 27 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/coalescence_efficiencies/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Coalescence efficiencies for the overall collision dynamic 3 | """ 4 | 5 | from .berry1967 import Berry1967 6 | from .constEc import ConstEc 7 | from .lowlist1982 import LowList1982Ec 8 | from .specified_eff import SpecifiedEff 9 | from .straub2010 import Straub2010Ec 10 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/coalescence_efficiencies/_gravitational.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO #744 3 | """ 4 | 5 | 6 | class Gravitational: # pylint: disable=too-few-public-methods 7 | def __init__(self): 8 | self.particulator = None 9 | self.pair_tmp = None 10 | 11 | def register(self, builder): 12 | self.particulator = builder.particulator 13 | builder.request_attribute("radius") 14 | builder.request_attribute("relative fall velocity") 15 | self.pair_tmp = self.particulator.PairwiseStorage.empty( 16 | self.particulator.n_sd // 2, dtype=float 17 | ) 18 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/coalescence_efficiencies/_parameterized.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO #744 3 | """ 4 | 5 | from PySDM.physics import constants as const 6 | 7 | 8 | class Parameterized: 9 | def __init__(self, params): 10 | self.particulator = None 11 | self.params = params 12 | 13 | def register(self, builder): 14 | self.particulator = builder.particulator 15 | builder.request_attribute("radius") 16 | 17 | def __call__(self, output, is_first_in_pair): 18 | self.particulator.backend.linear_collection_efficiency( 19 | params=self.params, 20 | output=output, 21 | radii=self.particulator.attributes["radius"], 22 | is_first_in_pair=is_first_in_pair, 23 | unit=const.si.um, 24 | ) 25 | output **= 2 26 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/coalescence_efficiencies/berry1967.py: -------------------------------------------------------------------------------- 1 | """ 2 | E.X. Berry 1967 3 | Cloud Droplet Growth by Collection 4 | """ 5 | 6 | from ._parameterized import Parameterized 7 | 8 | 9 | class Berry1967(Parameterized): # pylint: disable=too-few-public-methods 10 | def __init__(self): 11 | super().__init__((1, 1, -27, 1.65, -58, 1.9, 15, 1.13, 16.7, 1, 0.004, 4, 8)) 12 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/coalescence_efficiencies/constEc.py: -------------------------------------------------------------------------------- 1 | """constant value""" 2 | 3 | 4 | class ConstEc: 5 | def __init__(self, Ec=1.0): 6 | self.Ec = Ec 7 | self.particulator = None 8 | 9 | def register(self, builder): 10 | self.particulator = builder.particulator 11 | 12 | def __call__(self, output, is_first_in_pair): 13 | output.fill(self.Ec) 14 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/coalescence_efficiencies/specified_eff.py: -------------------------------------------------------------------------------- 1 | """ 2 | Taking the form of Berry 1967 3 | Cloud Droplet Growth by Collection 4 | but with user-specified collection efficiency constants 5 | """ 6 | 7 | from ._parameterized import Parameterized 8 | 9 | 10 | class SpecifiedEff(Parameterized): # pylint: disable=too-few-public-methods 11 | def __init__( 12 | self, 13 | *, 14 | A=1, 15 | B=1, 16 | D1=-27, 17 | D2=1.65, 18 | E1=-58, 19 | E2=1.9, 20 | F1=15, 21 | F2=1.13, 22 | G1=16.7, 23 | G2=1, 24 | G3=0.004, 25 | Mf=4, 26 | Mg=8, 27 | ): 28 | super().__init__((A, B, D1, D2, E1, E2, F1, F2, G1, G2, G3, Mf, Mg)) 29 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Collision kernels including 3 | [Golovin](https://open-atmos.github.io/PySDM/physics/collisions/kernels/golovin.html), 4 | [Geometric](https://open-atmos.github.io/PySDM/physics/collisions/kernels/geometric.html) 5 | and other... 6 | """ 7 | 8 | from .constantK import ConstantK 9 | from .electric import Electric 10 | from .geometric import Geometric 11 | from .golovin import Golovin 12 | from .hydrodynamic import Hydrodynamic 13 | from .linear import Linear 14 | from .simple_geometric import SimpleGeometric 15 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/constantK.py: -------------------------------------------------------------------------------- 1 | """ 2 | #TODO #744 3 | """ 4 | 5 | 6 | class ConstantK: 7 | def __init__(self, a): 8 | self.a = a 9 | self.particulator = None 10 | 11 | def __call__(self, output, is_first_in_pair): 12 | output.fill(self.a) 13 | 14 | def register(self, builder): 15 | self.particulator = builder.particulator 16 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/electric.py: -------------------------------------------------------------------------------- 1 | """ 2 | kernel modelling influence of electric field of 3000V/cm 3 | as in [Berry 1967](https://doi.org/10.1175/1520-0469(1967)024%3C0688:CDGBC%3E2.0.CO;2) 4 | """ 5 | 6 | from PySDM.dynamics.collisions.collision_kernels.impl.parameterized import Parameterized 7 | 8 | 9 | class Electric(Parameterized): # pylint: disable=too-few-public-methods 10 | def __init__(self): 11 | super().__init__( 12 | (1, 1, -7, 1.78, -20.5, 1.73, 0.26, 1.47, 1, 0.82, -0.003, 4.4, 8), 13 | ) 14 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/geometric.py: -------------------------------------------------------------------------------- 1 | """ 2 | basic geometric kernel 3 | """ 4 | 5 | from PySDM.dynamics.collisions.collision_kernels.impl.gravitational import Gravitational 6 | from PySDM.physics import constants as const 7 | 8 | 9 | class Geometric(Gravitational): 10 | def __init__(self, collection_efficiency=1.0, x="volume"): 11 | super().__init__() 12 | self.collection_efficiency = collection_efficiency 13 | self.x = x 14 | 15 | def __call__(self, output, is_first_in_pair): 16 | output.sum(self.particulator.attributes["radius"], is_first_in_pair) 17 | output **= 2 18 | output *= const.PI * self.collection_efficiency 19 | self.pair_tmp.distance( 20 | self.particulator.attributes["relative fall velocity"], is_first_in_pair 21 | ) 22 | output *= self.pair_tmp 23 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/hydrodynamic.py: -------------------------------------------------------------------------------- 1 | """ 2 | hydrodynamic kernel using 3 | [Berry 1967](https://doi.org/10.1175/1520-0469(1967)024%3C0688:CDGBC%3E2.0.CO;2) parameterization 4 | """ 5 | 6 | from PySDM.dynamics.collisions.collision_kernels.impl.parameterized import Parameterized 7 | 8 | 9 | class Hydrodynamic(Parameterized): # pylint: disable=too-few-public-methods 10 | def __init__(self): 11 | super().__init__( 12 | (1, 1, -27, 1.65, -58, 1.9, 15, 1.13, 16.7, 1, 0.004, 4, 8), 13 | ) 14 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """common code for implementing collision kernels""" 2 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/impl/gravitational.py: -------------------------------------------------------------------------------- 1 | """common parent class for gravitational collision kernels""" 2 | 3 | 4 | class Gravitational: # pylint: disable=too-few-public-methods 5 | def __init__(self): 6 | self.particulator = None 7 | self.pair_tmp = None 8 | 9 | def register(self, builder): 10 | self.particulator = builder.particulator 11 | builder.request_attribute("radius") 12 | builder.request_attribute("relative fall velocity") 13 | self.pair_tmp = self.particulator.PairwiseStorage.empty( 14 | self.particulator.n_sd // 2, dtype=float 15 | ) 16 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/linear.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO #744 3 | """ 4 | 5 | 6 | class Linear: 7 | def __init__(self, a, b): 8 | self.a = a 9 | self.b = b 10 | self.particulator = None 11 | 12 | def __call__(self, output, is_first_in_pair): 13 | output.sum_pair(self.particulator.attributes["volume"], is_first_in_pair) 14 | output *= self.b 15 | output += self.a 16 | 17 | def register(self, builder): 18 | self.particulator = builder.particulator 19 | builder.request_attribute("volume") 20 | -------------------------------------------------------------------------------- /PySDM/dynamics/collisions/collision_kernels/simple_geometric.py: -------------------------------------------------------------------------------- 1 | """ 2 | basic geometric kernel (not taking fall velocity into account) 3 | """ 4 | 5 | 6 | class SimpleGeometric: 7 | def __init__(self, C): 8 | self.particulator = None 9 | self.pair_tmp = None 10 | self.C = C 11 | 12 | def register(self, builder): 13 | self.particulator = builder.particulator 14 | builder.request_attribute("radius") 15 | builder.request_attribute("area") 16 | self.pair_tmp = self.particulator.PairwiseStorage.empty( 17 | self.particulator.n_sd // 2, dtype=float 18 | ) 19 | 20 | def __call__(self, output, is_first_in_pair): 21 | output[:] = self.C 22 | self.pair_tmp.sum(self.particulator.attributes["radius"], is_first_in_pair) 23 | self.pair_tmp **= 2 24 | output *= self.pair_tmp 25 | self.pair_tmp.distance(self.particulator.attributes["area"], is_first_in_pair) 26 | output *= self.pair_tmp 27 | -------------------------------------------------------------------------------- /PySDM/dynamics/eulerian_advection.py: -------------------------------------------------------------------------------- 1 | """ 2 | wrapper class for triggering integration in the Eulerian advection solver 3 | """ 4 | 5 | from PySDM.dynamics.impl import register_dynamic 6 | 7 | 8 | @register_dynamic() 9 | class EulerianAdvection: 10 | def __init__(self, solvers): 11 | self.solvers = solvers 12 | self.particulator = None 13 | 14 | def register(self, builder): 15 | self.particulator = builder.particulator 16 | 17 | def __call__(self): 18 | for field in ("water_vapour_mixing_ratio", "thd"): 19 | self.particulator.environment.get_predicted(field).download( 20 | getattr(self.particulator.environment, f"get_{field}")(), reshape=True 21 | ) 22 | self.solvers(self.particulator.dynamics["Displacement"]) 23 | -------------------------------------------------------------------------------- /PySDM/dynamics/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """stuff not intended to be imported from user code""" 2 | 3 | from .register_dynamic import register_dynamic 4 | -------------------------------------------------------------------------------- /PySDM/dynamics/impl/register_dynamic.py: -------------------------------------------------------------------------------- 1 | """decorator for dynamics classes 2 | ensuring that their instances can be re-used with multiple builders""" 3 | 4 | from copy import deepcopy 5 | 6 | 7 | def _instantiate(self, *, builder): 8 | copy = deepcopy(self) 9 | copy.register(builder=builder) 10 | return copy 11 | 12 | 13 | def register_dynamic(): 14 | def decorator(cls): 15 | if hasattr(cls, "instantiate"): 16 | assert cls.instantiate is _instantiate 17 | else: 18 | setattr(cls, "instantiate", _instantiate) 19 | return cls 20 | 21 | return decorator 22 | -------------------------------------------------------------------------------- /PySDM/dynamics/terminal_velocity/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle terminal velocity formulae 3 | """ 4 | 5 | from PySDM.dynamics.terminal_velocity.gunn_and_kinzer import GunnKinzer1949 6 | from PySDM.dynamics.terminal_velocity.power_series import PowerSeries 7 | from PySDM.dynamics.terminal_velocity.rogers_and_yau import RogersYau 8 | -------------------------------------------------------------------------------- /PySDM/dynamics/terminal_velocity/rogers_and_yau.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rogers & Yau, equations: (8.5), (8.6), (8.8) 3 | """ 4 | 5 | 6 | class RogersYau: # pylint: disable=too-few-public-methods 7 | def __init__(self, particulator): 8 | self.particulator = particulator 9 | 10 | def __call__(self, output, radius): 11 | self.particulator.backend.terminal_velocity( 12 | values=output.data, 13 | radius=radius.data, 14 | ) 15 | -------------------------------------------------------------------------------- /PySDM/dynamics/vapour_deposition_on_ice.py: -------------------------------------------------------------------------------- 1 | """basic water vapor deposition on ice""" 2 | 3 | from PySDM.dynamics.impl import register_dynamic 4 | 5 | 6 | @register_dynamic() 7 | class VapourDepositionOnIce: 8 | def __init__(self): 9 | """called by the user while building a particulator""" 10 | self.particulator = None 11 | 12 | def register(self, *, builder): 13 | """called by the builder""" 14 | self.particulator = builder.particulator 15 | assert builder.formulae.particle_shape_and_density.supports_mixed_phase() 16 | builder.request_attribute("Reynolds number") 17 | 18 | def __call__(self): 19 | """called by the particulator during simulation""" 20 | self.particulator.deposition() 21 | -------------------------------------------------------------------------------- /PySDM/environments/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Classes representing particle environment: 3 | `PySDM.environments.box.Box`, 4 | `PySDM.environments.parcel.Parcel`, ... 5 | """ 6 | 7 | from .box import Box 8 | from .kinematic_1d import Kinematic1D 9 | from .kinematic_2d import Kinematic2D 10 | from .parcel import Parcel 11 | -------------------------------------------------------------------------------- /PySDM/environments/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """common internals not intended to be imported from user code""" 2 | 3 | from PySDM.environments.impl.register_environment import register_environment 4 | -------------------------------------------------------------------------------- /PySDM/environments/impl/register_environment.py: -------------------------------------------------------------------------------- 1 | """decorator for environment classes 2 | ensuring that their instances can be re-used with multiple builders""" 3 | 4 | from copy import deepcopy 5 | 6 | 7 | def _instantiate(self, *, builder): 8 | copy = deepcopy(self) 9 | copy.register(builder=builder) 10 | return copy 11 | 12 | 13 | def register_environment(): 14 | def decorator(cls): 15 | if hasattr(cls, "instantiate"): 16 | if cls.instantiate is not _instantiate: 17 | raise AttributeError( 18 | "decorated class has a different instantiate method" 19 | ) 20 | else: 21 | setattr(cls, "instantiate", _instantiate) 22 | return cls 23 | 24 | return decorator 25 | -------------------------------------------------------------------------------- /PySDM/exporters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exporters handling output to metadata-rich file formats incl. netCDF and VTK 3 | """ 4 | 5 | from .netcdf_exporter import NetCDFExporter 6 | from .netcdf_exporter_1d import NetCDFExporter_1d, readNetCDF_1d 7 | from .vtk_exporter import VTKExporter 8 | from .vtk_exporter_1d import VTKExporter_1d 9 | -------------------------------------------------------------------------------- /PySDM/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """stuff not intended to be used from user code""" 2 | -------------------------------------------------------------------------------- /PySDM/impl/arakawa_c.py: -------------------------------------------------------------------------------- 1 | """ 2 | Arakawa-C staggered spatial grid helper utils 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | def z_scalar_coord(grid): 9 | return np.linspace(1 / 2, grid[-1] - 1 / 2, grid[-1]) 10 | 11 | 12 | def make_rhod(grid, rhod_of_zZ): 13 | return np.repeat( 14 | rhod_of_zZ(z_scalar_coord(grid) / grid[-1]).reshape((1, grid[1])), 15 | grid[0], 16 | axis=0, 17 | ) 18 | -------------------------------------------------------------------------------- /PySDM/impl/camel_case.py: -------------------------------------------------------------------------------- 1 | """ 2 | utility routine converting "CamelCase" strings into space-separated ones (i.e., "camel case") 3 | """ 4 | 5 | import re 6 | 7 | CAMEL_CASE_PATTERN = re.compile(r"[A-Z]?[a-z]+|[A-Z]+(?![^A-Z])") 8 | 9 | 10 | def camel_case_to_words(string: str): 11 | words = CAMEL_CASE_PATTERN.findall(string) 12 | words = (word if word.isupper() else word.lower() for word in words) 13 | return " ".join(words) 14 | -------------------------------------------------------------------------------- /PySDM/impl/null_physics_class.py: -------------------------------------------------------------------------------- 1 | """ 2 | do-nothing null default 3 | """ 4 | 5 | 6 | class Null: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | -------------------------------------------------------------------------------- /PySDM/impl/wall_timer.py: -------------------------------------------------------------------------------- 1 | """ 2 | context manager automating wall-time counting using Python's basic 3 | [time.perf_counter()](https://docs.python.org/3/library/time.html#time.perf_counter) 4 | """ 5 | 6 | import time 7 | 8 | 9 | class WallTimer: 10 | @staticmethod 11 | def __clock(): 12 | return time.perf_counter() 13 | 14 | def __init__(self): 15 | self.time = None 16 | 17 | def __enter__(self): 18 | self.time = self.__clock() 19 | 20 | def __exit__(self, *_): 21 | self.time *= -1 22 | self.time += self.__clock() 23 | -------------------------------------------------------------------------------- /PySDM/initialisation/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | initialisation logic, particle size spectra, sampling methods and 3 | wet radii equilibration 4 | """ 5 | 6 | from . import sampling, spectra 7 | from .discretise_multiplicities import discretise_multiplicities 8 | from .equilibrate_wet_radii import equilibrate_wet_radii 9 | from .init_fall_momenta import init_fall_momenta 10 | 11 | from . import aerosol_composition # isort:skip 12 | -------------------------------------------------------------------------------- /PySDM/initialisation/aerosol_composition/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | classes defining structure for specifying interal and external mixtures of aerosols 3 | and functions for computing bulk properties of the aerosol like kappa 4 | """ 5 | 6 | from .dry_aerosol import DryAerosolMixture 7 | -------------------------------------------------------------------------------- /PySDM/initialisation/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """commons (incl. `PySDM.initialisation.impl.spectrum.Spectrum` base class) 2 | not intended to be imported from user code""" 3 | -------------------------------------------------------------------------------- /PySDM/initialisation/sampling/__init__.py: -------------------------------------------------------------------------------- 1 | """particle attribute sampling logic""" 2 | -------------------------------------------------------------------------------- /PySDM/initialisation/spectra/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | classes representing logic around size spectra and more generally 3 | probability density functions (based on SciPy.stats logic) 4 | """ 5 | 6 | from .exponential import Exponential 7 | from .gamma import Gamma 8 | from .gaussian import Gaussian 9 | from .lognormal import Lognormal 10 | from .sum import Sum 11 | from .top_hat import TopHat 12 | -------------------------------------------------------------------------------- /PySDM/initialisation/spectra/exponential.py: -------------------------------------------------------------------------------- 1 | """ 2 | exponential spectrum implemented using 3 | [SciPy.stats](https://docs.scipy.org/doc/scipy/reference/stats.html) 4 | """ 5 | 6 | from scipy.stats import expon 7 | 8 | from PySDM.initialisation.impl.spectrum import Spectrum 9 | 10 | 11 | class Exponential(Spectrum): 12 | def __init__(self, norm_factor, scale): 13 | super().__init__(expon, (0, scale), norm_factor) # loc # scale = 1/lambda 14 | -------------------------------------------------------------------------------- /PySDM/initialisation/spectra/gamma.py: -------------------------------------------------------------------------------- 1 | """ 2 | gamma spectrum implemented using 3 | [SciPy.stats](https://docs.scipy.org/doc/scipy/reference/stats.html) 4 | """ 5 | 6 | from scipy.stats import gamma 7 | 8 | from PySDM.initialisation.impl.spectrum import Spectrum 9 | 10 | 11 | class Gamma(Spectrum): 12 | def __init__(self, norm_factor, k, theta): 13 | super().__init__( 14 | gamma, (k, 0, theta), norm_factor # shape factor # loc # scale 15 | ) 16 | -------------------------------------------------------------------------------- /PySDM/initialisation/spectra/gaussian.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gaussian/normal spectrum implemented using 3 | [SciPy.stats](https://docs.scipy.org/doc/scipy/reference/stats.html) 4 | """ 5 | 6 | from scipy.stats import norm 7 | 8 | from PySDM.initialisation.impl.spectrum import Spectrum 9 | 10 | 11 | class Gaussian(Spectrum): 12 | def __init__(self, norm_factor, loc, scale): 13 | super().__init__(norm, (loc, scale), norm_factor) # mean # std dev 14 | -------------------------------------------------------------------------------- /PySDM/initialisation/spectra/top_hat.py: -------------------------------------------------------------------------------- 1 | """ 2 | top-hat spectrum 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class TopHat: 9 | def __init__(self, norm_factor, endpoints): 10 | self.norm_factor = norm_factor 11 | self.endpoints = endpoints 12 | self._mn = endpoints[0] 13 | self._mx = endpoints[1] 14 | 15 | def cumulative(self, arg): 16 | cdf = np.minimum(1, np.maximum(0, (arg - self._mn) / (self._mx - self._mn))) 17 | return self.norm_factor * cdf 18 | 19 | def percentiles(self, cdf_values): 20 | return (self._mx - self._mn) * ( 21 | np.asarray(cdf_values) + self._mn / (self._mx - self._mn) 22 | ) 23 | -------------------------------------------------------------------------------- /PySDM/physics/air_dynamic_viscosity/__init__.py: -------------------------------------------------------------------------------- 1 | """air dynamic viscosity formulae""" 2 | 3 | from .zografos_et_al_1987 import ZografosEtAl1987 4 | -------------------------------------------------------------------------------- /PySDM/physics/air_dynamic_viscosity/zografos_et_al_1987.py: -------------------------------------------------------------------------------- 1 | """ 2 | calculate dynamic viscosity of Earth air 3 | from [Zografos et al. (1987)](doi:10.1016/0045-7825(87)90003-X) Table 1 4 | (note labeled as μ not η there) 5 | fit for T ∈ [100-3000] K 6 | neglects effects of pressure 7 | """ 8 | 9 | 10 | class ZografosEtAl1987: # pylint: disable=too-few-public-methods 11 | def __init__(self, _): 12 | pass 13 | 14 | @staticmethod 15 | def eta_air(const, temperature): 16 | return ( 17 | const.ZOGRAFOS_1987_COEFF_T3 * temperature**3 18 | + const.ZOGRAFOS_1987_COEFF_T2 * temperature**2 19 | + const.ZOGRAFOS_1987_COEFF_T1 * temperature 20 | + const.ZOGRAFOS_1987_COEFF_T0 21 | ) 22 | -------------------------------------------------------------------------------- /PySDM/physics/bulk_phase_partitioning/__init__.py: -------------------------------------------------------------------------------- 1 | """phase partitioning formulae for bulk description of cloud water""" 2 | 3 | from PySDM.impl.null_physics_class import Null 4 | from .kaul_et_al_2015 import KaulEtAl2015 5 | -------------------------------------------------------------------------------- /PySDM/physics/bulk_phase_partitioning/kaul_et_al_2015.py: -------------------------------------------------------------------------------- 1 | """ 2 | Eq. 1 in [Kaul et al. 2015](https://doi.org/10.1175/MWR-D-14-00319.1) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class KaulEtAl2015: # pylint: disable=too-few-public-methods 9 | def __init__(self, const): 10 | assert np.isfinite(const.bulk_phase_partitioning_exponent) 11 | 12 | @staticmethod 13 | def liquid_fraction(const, T): 14 | return np.minimum( 15 | 1, 16 | np.power( 17 | np.maximum( 18 | 0, 19 | (T - const.bulk_phase_partitioning_T_cold) 20 | / ( 21 | const.bulk_phase_partitioning_T_warm 22 | - const.bulk_phase_partitioning_T_cold 23 | ), 24 | ), 25 | const.bulk_phase_partitioning_exponent, 26 | ), 27 | ) 28 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_coordinate/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | definitions of particle-size coordinates for the condensation solver 3 | """ 4 | 5 | from .water_mass import WaterMass 6 | from .water_mass_logarithm import WaterMassLogarithm 7 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_coordinate/water_mass.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle water mass as condensation coordinate (i.e. no transformation) 3 | """ 4 | 5 | 6 | class WaterMass: 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def dx_dt(m, dm_dt): # pylint: disable=unused-argument 12 | return dm_dt 13 | 14 | @staticmethod 15 | def mass(x): 16 | return x 17 | 18 | @staticmethod 19 | def x(mass): 20 | return mass 21 | 22 | @staticmethod 23 | def x_max(const): 24 | """1 kg droplet!""" 25 | return const.ONE 26 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_coordinate/water_mass_logarithm.py: -------------------------------------------------------------------------------- 1 | """ 2 | logarithm of particle mass as coordinate (ensures non-negative values) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class WaterMassLogarithm: 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def dx_dt(m, dm_dt): 14 | """ 15 | x = ln(m/m0) 16 | m0 = 1 kg 17 | dx_dt = 1/m(x) dm_dt 18 | """ 19 | return dm_dt / m 20 | 21 | @staticmethod 22 | def mass(x): 23 | return np.exp(x) 24 | 25 | @staticmethod 26 | def x(mass): 27 | return np.log(mass) 28 | 29 | @staticmethod 30 | def x_max(const): 31 | """corresponds to 1 kg droplet!""" 32 | return const.ZERO 33 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_ice_capacity/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae for capacity in diffusional growh/evaporation of ice 3 | """ 4 | 5 | from .spherical import Spherical 6 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_ice_capacity/spherical.py: -------------------------------------------------------------------------------- 1 | """ 2 | capacity for approximation of ice crystals as spheres 3 | """ 4 | 5 | 6 | class Spherical: # pylint: disable=too-few-public-methods 7 | 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def capacity(const, diameter): 13 | return diameter / const.TWO 14 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_ice_kinetics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae for handling transition-régime corrections in diffusional growh/evaporation of ice 3 | """ 4 | 5 | from .neglect import Neglect 6 | from .standard import Standard 7 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_ice_kinetics/neglect.py: -------------------------------------------------------------------------------- 1 | """ 2 | no transition-regime corrections formulation 3 | """ 4 | 5 | 6 | class Neglect: 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def lambdaD(_, T, p): # pylint: disable=unused-argument 12 | return -1 13 | 14 | @staticmethod 15 | def lambdaK(_, T, p): # pylint: disable=unused-argument 16 | return -1 17 | 18 | @staticmethod 19 | def D(_, D, r, lmbd, T): # pylint: disable=unused-argument 20 | return D 21 | 22 | @staticmethod 23 | def K(_, K, r, lmbd, T, rho): # pylint: disable=unused-argument 24 | return K 25 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_kinetics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae for handling transition-régime corrections in condensational growth/evaporation 3 | """ 4 | 5 | from .fuchs_sutugin import FuchsSutugin 6 | from .lowe_et_al_2019 import LoweEtAl2019 7 | from .neglect import Neglect 8 | from .grabowski_et_al_2011 import GrabowskiEtAl2011 9 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_kinetics/grabowski_et_al_2011.py: -------------------------------------------------------------------------------- 1 | """ 2 | as in [Grabowski et al. (2011)](https://doi.org/10.1016/j.atmosres.2010.10.020) 3 | """ 4 | 5 | from .pruppacher_and_klett_2005 import PruppacherKlett 6 | 7 | 8 | class GrabowskiEtAl2011(PruppacherKlett): 9 | pass 10 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_kinetics/lowe_et_al_2019.py: -------------------------------------------------------------------------------- 1 | """ 2 | as in [Lowe et al. 2019](https://doi.org/10.1038/s41467-019-12982-0) 3 | uses eq. 13-14 in Pruppacher & Klett 2005 with Delta v = 0 4 | and no corrections for thermal conductivity 5 | """ 6 | 7 | from PySDM.physics.diffusion_kinetics.pruppacher_and_klett_2005 import PruppacherKlett 8 | 9 | 10 | class LoweEtAl2019(PruppacherKlett): 11 | def __init__(self, const): 12 | assert const.dv_pk05 == 0 13 | PruppacherKlett.__init__(self, const) 14 | 15 | @staticmethod 16 | def lambdaK(const, T, p): # pylint: disable=unused-argument 17 | return -1 18 | 19 | @staticmethod 20 | def K(const, K, r, lmbd): # pylint: disable=unused-argument 21 | return K 22 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_kinetics/neglect.py: -------------------------------------------------------------------------------- 1 | """ 2 | no transition-regime corrections formulation 3 | """ 4 | 5 | 6 | class Neglect: 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def lambdaD(_, D, T): # pylint: disable=unused-argument 12 | return -1 13 | 14 | @staticmethod 15 | def lambdaK(_, T, p): # pylint: disable=unused-argument 16 | return -1 17 | 18 | @staticmethod 19 | def D(_, D, r, lmbd): # pylint: disable=unused-argument 20 | return D 21 | 22 | @staticmethod 23 | def K(_, K, r, lmbd): # pylint: disable=unused-argument 24 | return K 25 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_kinetics/pruppacher_and_klett_2005.py: -------------------------------------------------------------------------------- 1 | """ 2 | as in Pruppacher and Klett 2005 (eq. 13-14) 3 | with reference to [Okuyama and Zung 1967](https://doi.org/10.1063/1.1840906) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class PruppacherKlett: 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def lambdaD(const, D, T): 15 | return D / np.sqrt(2 * const.Rv * T) 16 | 17 | @staticmethod 18 | def D(const, D, r, lmbd): 19 | return D / ( 20 | (r / (r + const.dv_pk05)) + 2 * np.sqrt(const.PI) * lmbd / r / const.MAC 21 | ) 22 | 23 | @staticmethod 24 | def lambdaK(_, T, p): # pylint: disable=unused-argument 25 | return -1 26 | 27 | @staticmethod 28 | def K(const, K, r, lmbd): # pylint: disable=unused-argument 29 | return K # TODO #1266 30 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_thermics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae for representing the temperature and pressure dependence of vapour diffusion coefficient 3 | """ 4 | 5 | from .lowe_et_al_2019 import LoweEtAl2019 6 | from .neglect import Neglect 7 | from .tracy_welch_porter import TracyWelchPorter 8 | from .grabowski_et_al_2011 import GrabowskiEtAl2011 9 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_thermics/grabowski_et_al_2011.py: -------------------------------------------------------------------------------- 1 | """ 2 | as in [Grabowski et al. (2011)](https://doi.org/10.1016/j.atmosres.2010.10.020) 3 | """ 4 | 5 | 6 | class GrabowskiEtAl2011: 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def D(const, T, p): # pylint: disable=unused-argument 12 | """eq (10)""" 13 | return const.diffusion_thermics_D_G11_A * ( 14 | const.diffusion_thermics_D_G11_B * T + const.diffusion_thermics_D_G11_C 15 | ) 16 | 17 | @staticmethod 18 | def K(const, T, p): # pylint: disable=unused-argument 19 | """eq (12)""" 20 | return ( 21 | const.diffusion_thermics_K_G11_A * T**3 22 | + const.diffusion_thermics_K_G11_B * T**2 23 | + const.diffusion_thermics_K_G11_C * T 24 | + const.diffusion_thermics_K_G11_D 25 | ) 26 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_thermics/lowe_et_al_2019.py: -------------------------------------------------------------------------------- 1 | """ 2 | as in [Lowe et al. 2019](https://doi.org/10.1038/s41467-019-12982-0) 3 | """ 4 | 5 | from PySDM.physics.diffusion_thermics.seinfeld_and_pandis_2010 import ( 6 | SeinfeldAndPandis2010, 7 | ) 8 | 9 | 10 | class LoweEtAl2019(SeinfeldAndPandis2010): 11 | def __init__(self, const): 12 | SeinfeldAndPandis2010.__init__(self, const) 13 | 14 | @staticmethod 15 | def K(const, T, p): # pylint: disable=unused-argument 16 | return const.k_l19_a * (const.k_l19_b + const.k_l19_c * T) 17 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_thermics/neglect.py: -------------------------------------------------------------------------------- 1 | """ 2 | constant diffusion coefficient formulation 3 | """ 4 | 5 | 6 | class Neglect: 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def D(const, T, p): # pylint: disable=unused-argument 12 | return const.D0 13 | 14 | @staticmethod 15 | def K(const, T, p): # pylint: disable=unused-argument 16 | return const.K0 17 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_thermics/seinfeld_and_pandis_2010.py: -------------------------------------------------------------------------------- 1 | """ 2 | as in Seinfeld and Pandis 2010 (eq. 15.65) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class SeinfeldAndPandis2010: # pylint: disable=too-few-public-methods 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def D(const, T, p): 14 | return const.d_l19_a * (const.p_STP / p) * np.power(T / const.T0, const.d_l19_b) 15 | -------------------------------------------------------------------------------- /PySDM/physics/diffusion_thermics/tracy_welch_porter.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on "PROPERTIES OF AIR: A Manual for Use in Biophysical Ecology" 3 | (Fourth Edition - 2010, page 22) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class TracyWelchPorter: 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def D(const, T, p): 15 | return const.D0 * np.power(T / const.T0, const.D_exp) * (const.p1000 / p) 16 | 17 | @staticmethod 18 | def K(const, T, p): # pylint: disable=unused-argument 19 | return const.K0 20 | -------------------------------------------------------------------------------- /PySDM/physics/dimensional_analysis.py: -------------------------------------------------------------------------------- 1 | """ 2 | A context manager (for use with the `with` statement) which 3 | enables Pint physical-units checks and disables Numba in `PySDM.formulae.Formulae` 4 | """ 5 | 6 | from importlib import reload 7 | 8 | from PySDM import formulae 9 | from PySDM import physics 10 | from . import constants, constants_defaults 11 | from .impl import flag 12 | 13 | 14 | class DimensionalAnalysis: 15 | def __enter__(*_): # pylint: disable=no-method-argument,no-self-argument 16 | flag.DIMENSIONAL_ANALYSIS = True 17 | reload(constants) 18 | reload(constants_defaults) 19 | reload(formulae) 20 | reload(physics) 21 | 22 | def __exit__(*_): # pylint: disable=no-method-argument,no-self-argument 23 | flag.DIMENSIONAL_ANALYSIS = False 24 | reload(constants) 25 | reload(constants_defaults) 26 | reload(formulae) 27 | reload(physics) 28 | -------------------------------------------------------------------------------- /PySDM/physics/drop_growth/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulation of the coupled heat-moisture diffusion problem 3 | """ 4 | 5 | from .fick import Fick 6 | from .howell_1949 import Howell1949 7 | from .mason_1971 import Mason1971 8 | -------------------------------------------------------------------------------- /PySDM/physics/drop_growth/mason_1971.py: -------------------------------------------------------------------------------- 1 | """ 2 | single-equation approximation of the vapour and heat diffusion problem 3 | as given in eq. 3.11 in [Mason 1971](https://archive.org/details/physicsofclouds0000maso) 4 | (see also discussion of the ventilation effect on page 125). 5 | The difference between Howell'49 and Mason'71 is `-1` in the `Fk` definition. 6 | 7 | The notation for terms associated with heat conduction and diffusion are from 8 | [Rogers & Yau 1989](https://archive.org/details/shortcourseinclo0000roge_m3k2). 9 | """ 10 | 11 | from .howell_1949 import Howell1949 12 | 13 | 14 | class Mason1971(Howell1949): # pylint: disable=too-few-public-methods 15 | 16 | @staticmethod 17 | def Fk(const, T, K, lv): 18 | """thermodynamic term associated with heat conduction""" 19 | return const.rho_w * lv / T / K * (lv / T / const.Rv - 1) 20 | -------------------------------------------------------------------------------- /PySDM/physics/fragmentation_function/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | fragmentation functions for use with breakup 3 | """ 4 | 5 | from .always_n import AlwaysN 6 | from .constant_mass import ConstantMass 7 | from .expon_frag import ExponFrag 8 | from .exponential import Exponential 9 | from .feingold1988 import Feingold1988 10 | from .gaussian import Gaussian 11 | from .lowlist82 import LowList1982Nf 12 | from .slams import SLAMS 13 | from .straub2010nf import Straub2010Nf 14 | -------------------------------------------------------------------------------- /PySDM/physics/fragmentation_function/always_n.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae supporting `PySDM.dynamics.collisions.breakup_fragmentations.always_n` 3 | """ 4 | 5 | 6 | class AlwaysN: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | -------------------------------------------------------------------------------- /PySDM/physics/fragmentation_function/constant_mass.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae supporting `PySDM.dynamics.collisions.breakup_fragmentations.constant_mass` 3 | """ 4 | 5 | 6 | class ConstantMass: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | -------------------------------------------------------------------------------- /PySDM/physics/fragmentation_function/expon_frag.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae supporting `PySDM.dynamics.collisions.breakup_fragmentations.expon_frag` 3 | """ 4 | 5 | import warnings 6 | 7 | from .exponential import Exponential 8 | 9 | 10 | class ExponFrag(Exponential): # pylint: disable=too-few-public-methods 11 | def __init_subclass__(cls): 12 | warnings.warn("Class has been renamed", DeprecationWarning) 13 | -------------------------------------------------------------------------------- /PySDM/physics/fragmentation_function/exponential.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae supporting `PySDM.dynamics.collisions.breakup_fragmentations.exponential` 3 | """ 4 | 5 | 6 | class Exponential: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | -------------------------------------------------------------------------------- /PySDM/physics/fragmentation_function/feingold1988.py: -------------------------------------------------------------------------------- 1 | """ 2 | Scaled exponential PDF 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class Feingold1988: # pylint: disable=too-few-public-methods 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def frag_volume(_, scale, rand, x_plus_y, fragtol): 14 | return -scale * np.log(max(1 - rand * scale / x_plus_y, fragtol)) 15 | -------------------------------------------------------------------------------- /PySDM/physics/fragmentation_function/gaussian.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gaussian PDF 3 | """ 4 | 5 | 6 | class Gaussian: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | -------------------------------------------------------------------------------- /PySDM/physics/fragmentation_function/slams.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae supporting `PySDM.dynamics.collisions.breakup_fragmentations.slams` 3 | """ 4 | 5 | 6 | class SLAMS: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | -------------------------------------------------------------------------------- /PySDM/physics/freezing_temperature_spectrum/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | freezing temperature spectra for use with singular immersion freezing representations 3 | """ 4 | 5 | from .bigg_1953 import Bigg_1953 6 | from .niemand_et_al_2012 import Niemand_et_al_2012 7 | from .null import Null 8 | -------------------------------------------------------------------------------- /PySDM/physics/freezing_temperature_spectrum/bigg_1953.py: -------------------------------------------------------------------------------- 1 | """ 2 | freezing temperature spectrum based on [Bigg 1953](https://doi.org/10.1088/0370-1301/66/8/309) 3 | formulae (i.e. immersed surface independent) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class Bigg_1953: 10 | def __init__(self, const): 11 | assert np.isfinite(const.BIGG_DT_MEDIAN) 12 | 13 | @staticmethod 14 | def pdf(const, T, A_insol): # pylint: disable=unused-argument 15 | A = np.log(1 - 0.5) 16 | B = const.BIGG_DT_MEDIAN - const.T0 17 | return -A * np.exp(A * np.exp(B + T) + B + T) 18 | 19 | @staticmethod 20 | def cdf(const, T, A_insol): # pylint: disable=unused-argument 21 | return np.exp(np.log(1 - 0.5) * np.exp(const.BIGG_DT_MEDIAN - (const.T0 - T))) 22 | 23 | @staticmethod 24 | def median(const): 25 | return const.T0 - const.BIGG_DT_median 26 | -------------------------------------------------------------------------------- /PySDM/physics/freezing_temperature_spectrum/null.py: -------------------------------------------------------------------------------- 1 | """ 2 | null spectrum (needed as other formulations require parameters 3 | to be set before instantiation of Formulae) 4 | """ 5 | 6 | 7 | class Null: # pylint: disable=too-few-public-methods 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def cdf(const, T): 13 | pass 14 | -------------------------------------------------------------------------------- /PySDM/physics/heterogeneous_ice_nucleation_rate/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | immersion-freezing rate (aka J_het) formulations 3 | """ 4 | 5 | from .abifm import ABIFM 6 | from .constant import Constant 7 | from .null import Null 8 | -------------------------------------------------------------------------------- /PySDM/physics/heterogeneous_ice_nucleation_rate/abifm.py: -------------------------------------------------------------------------------- 1 | """ 2 | ABIFM heterogeneous freezing rate parameterization 3 | ([Knopf & Alpert 2013](https://doi.org/10.1039/C3FD00035D)) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class ABIFM: # pylint: disable=too-few-public-methods 10 | def __init__(self, const): 11 | assert np.isfinite(const.ABIFM_M) 12 | assert np.isfinite(const.ABIFM_C) 13 | 14 | @staticmethod 15 | def j_het(const, a_w_ice): 16 | return 10 ** (const.ABIFM_M * (1 - a_w_ice) + const.ABIFM_C) * const.ABIFM_UNIT 17 | -------------------------------------------------------------------------------- /PySDM/physics/heterogeneous_ice_nucleation_rate/constant.py: -------------------------------------------------------------------------------- 1 | """ 2 | constant rate formulation (for tests) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class Constant: # pylint: disable=too-few-public-methods 9 | def __init__(self, const): 10 | assert np.isfinite(const.J_HET) 11 | 12 | @staticmethod 13 | def j_het(const, a_w_ice): # pylint: disable=unused-argument 14 | return const.J_HET 15 | -------------------------------------------------------------------------------- /PySDM/physics/heterogeneous_ice_nucleation_rate/null.py: -------------------------------------------------------------------------------- 1 | """ 2 | do-nothing null formulation (needed as other formulations require parameters 3 | to be set before instantiation of Formulae) 4 | """ 5 | 6 | 7 | class Null: # pylint: disable=too-few-public-methods,unused-argument 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def j_het(const, a_w_ice): 13 | return 0 14 | -------------------------------------------------------------------------------- /PySDM/physics/hydrostatics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper routines for hydrostatic pressure and density profiles 3 | """ 4 | 5 | from .constant_g_vapour_mixing_ratio_and_theta_std import ( 6 | ConstantGVapourMixingRatioAndThetaStd, 7 | ) 8 | from .variable_g_isothermal import VariableGIsothermal 9 | -------------------------------------------------------------------------------- /PySDM/physics/hydrostatics/variable_g_isothermal.py: -------------------------------------------------------------------------------- 1 | """ 2 | assuming constant temperature and variable 3 | gravitational acceleration g(z) = g0 * R^2 / (R+z)^2 4 | as in [Toon et al. 1980](https://doi.org/10.1016/0019-1035(80)90173-6) 5 | """ 6 | 7 | import numpy as np 8 | from PySDM.physics import constants_defaults 9 | 10 | 11 | class VariableGIsothermal: # pylint: disable=too-few-public-methods 12 | def __init__(self, const): 13 | assert np.isfinite(const.celestial_body_radius) 14 | assert const.g_std != constants_defaults.g_std 15 | 16 | @staticmethod 17 | def pressure(const, z, p0, temperature, molar_mass): 18 | return p0 * np.exp( 19 | -const.g_std 20 | / const.R_str 21 | * molar_mass 22 | / temperature 23 | * z 24 | / (1 + z / const.celestial_body_radius) 25 | ) 26 | -------------------------------------------------------------------------------- /PySDM/physics/hygroscopicity/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Alternative formulations of particle hygroscopicity for condensational drop growth/evaporation 3 | """ 4 | 5 | from .kappa_koehler import KappaKoehler 6 | from .kappa_koehler_leading_terms import KappaKoehlerLeadingTerms 7 | -------------------------------------------------------------------------------- /PySDM/physics/hygroscopicity/kappa_koehler.py: -------------------------------------------------------------------------------- 1 | """ 2 | kappa-Koehler parameterization 3 | ([Petters & Kreidenweis 2007](https://doi.org/10.5194/acp-7-1961-2007)) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class KappaKoehler: 10 | def __init__(self, _): 11 | pass 12 | 13 | # pylint: disable=too-many-arguments 14 | @staticmethod 15 | def RH_eq(const, r, T, kp, rd3, sgm): 16 | return ( 17 | np.exp((2 * sgm / const.Rv / T / const.rho_w) / r) 18 | * (r**3 - rd3) 19 | / (r**3 - rd3 * (1 - kp)) 20 | ) 21 | 22 | @staticmethod 23 | def r_cr(const, kp, rd3, T, sgm): 24 | # TODO #493 25 | return np.sqrt(3 * kp * rd3 / (2 * sgm / const.Rv / T / const.rho_w)) 26 | -------------------------------------------------------------------------------- /PySDM/physics/hygroscopicity/kappa_koehler_leading_terms.py: -------------------------------------------------------------------------------- 1 | """ 2 | leading-terms of the kappa-Koehler parameterization resulting in classic 3 | two-term formulation with 1/r and 1/r^3 terms corresponding to surface-tension 4 | (Kelvin) and soluble substance (Raoult/Wüllner) effects, respectively 5 | """ 6 | 7 | import numpy as np 8 | 9 | 10 | class KappaKoehlerLeadingTerms: 11 | def __init__(self, _): 12 | pass 13 | 14 | # pylint: disable=too-many-arguments 15 | @staticmethod 16 | def RH_eq(const, r, T, kp, rd3, sgm): 17 | return ( 18 | 1 19 | + (2 * sgm / const.Rv / T / const.rho_w) / r 20 | - kp * rd3 / np.power(r, const.THREE) 21 | ) 22 | 23 | @staticmethod 24 | def r_cr(const, kp, rd3, T, sgm): 25 | return np.sqrt(3 * kp * rd3 / (2 * sgm / const.Rv / T / const.rho_w)) 26 | -------------------------------------------------------------------------------- /PySDM/physics/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | physics stuff not intended to be imported from user code 3 | (incl. the `PySDM.physics.impl.fake_unit_registry.FakeUnitRegistry`) 4 | """ 5 | 6 | from . import flag 7 | -------------------------------------------------------------------------------- /PySDM/physics/impl/flag.py: -------------------------------------------------------------------------------- 1 | """flag disabling `PySDM.physics.impl.fake_unit_registry.FakeUnitRegistry` within 2 | `PySDM.physics.dimensional_analysis.DimensionalAnalysis` 3 | context manager 4 | """ 5 | 6 | DIMENSIONAL_ANALYSIS = False 7 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_diffusivity_ratios/__init__.py: -------------------------------------------------------------------------------- 1 | """heavy-to-light diffusivity ratios for water molecules""" 2 | 3 | from PySDM.impl.null_physics_class import Null 4 | from .grahams_law import GrahamsLaw 5 | from .stewart_1975 import Stewart1975 6 | from .hellmann_and_harvey_2020 import HellmannAndHarvey2020 7 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_diffusivity_ratios/grahams_law.py: -------------------------------------------------------------------------------- 1 | """[Graham's law](https://en.wikipedia.org/wiki/Graham%27s_law) 2 | see also eq. (21) in [Horita et al. 2008](https://doi.org/10.1080/10256010801887174) 3 | """ 4 | 5 | 6 | class GrahamsLaw: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def ratio_2H_heavy_to_light(const, temperature): # pylint: disable=unused-argument 12 | return ( 13 | (2 * const.M_1H + const.M_16O) / (const.M_2H + const.M_1H + const.M_16O) 14 | ) ** const.ONE_HALF 15 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_equilibrium_fractionation_factors/__init__.py: -------------------------------------------------------------------------------- 1 | """factors describing partitioning of water isotopologues under thermodynamic 2 | phase equilibrium, defined as ratios alpha of isotopic ratios R""" 3 | 4 | from PySDM.impl.null_physics_class import Null 5 | from .barkan_and_luz_2005 import BarkanAndLuz2005 6 | from .horita_and_wesolowski_1994 import HoritaAndWesolowski1994 7 | from .majoube_1970 import Majoube1970 8 | from .majoube_1971 import Majoube1971 9 | from .merlivat_and_nief_1967 import MerlivatAndNief1967 10 | from .van_hook_1968 import VanHook1968 11 | from .lamb_et_al_2017 import LambEtAl2017 12 | from .ellehoj_et_al_2013 import EllehojEtAl2013 13 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_equilibrium_fractionation_factors/barkan_and_luz_2005.py: -------------------------------------------------------------------------------- 1 | """ 2 | Equilibrium fractionation factor for Oxygen-17 from 3 | [Barkan and Luz 2005](https://doi.org/10.1002/rcm.2250) 4 | """ 5 | 6 | 7 | class BarkanAndLuz2005: # pylint: disable=too-few-public-methods 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def alpha_l_17O(const, _, alpha_l_18O): 13 | return alpha_l_18O**const.BARKAN_AND_LUZ_2005_EXPONENT 14 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_equilibrium_fractionation_factors/ellehoj_et_al_2013.py: -------------------------------------------------------------------------------- 1 | """ 2 | Equilibrium fractionation factors from 3 | [Ellehoj et al. 2013](https://doi.org/10.1002/rcm.6668) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class EllehojEtAl2013: # pylint: disable=too-few-public-methods 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def alpha_i_2H(const, T): 15 | return np.exp( 16 | const.ELLEHOJ_ET_AL_2013_ALPHA_I_2H_T2 / T**2 17 | + const.ELLEHOJ_ET_AL_2013_ALPHA_I_2H_T1 / T 18 | + const.ELLEHOJ_ET_AL_2013_ALPHA_I_2H_T0 19 | ) 20 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_equilibrium_fractionation_factors/lamb_et_al_2017.py: -------------------------------------------------------------------------------- 1 | """ 2 | Equilibrium fractionation factors from 3 | [Lamb et al. 2017](https://doi.org/10.1073/pnas.1618374114) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class LambEtAl2017: # pylint: disable=too-few-public-methods 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def alpha_i_2H(const, T): 15 | return np.exp( 16 | const.LAMB_ET_AL_2017_ALPHA_I_2H_T2 / T**2 17 | + const.LAMB_ET_AL_2017_ALPHA_I_2H_T1 / T 18 | + const.LAMB_ET_AL_2017_ALPHA_I_2H_T0 19 | ) 20 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_equilibrium_fractionation_factors/majoube_1970.py: -------------------------------------------------------------------------------- 1 | """ 2 | Equilibrium fractionation factors from [Majoube 1970](https://doi.org/10.1038/2261242a0) 3 | (also published by the same author in [Majoube 1971](https://doi.org/10.1051/jcp/1971680625)) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class Majoube1970: # pylint: disable=too-few-public-methods 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def alpha_i_18O(const, T): 15 | return np.exp( 16 | const.MAJOUBE_1970_ALPHA_I_18O_T2 / T**2 17 | + const.MAJOUBE_1970_ALPHA_I_18O_T1 / T 18 | + const.MAJOUBE_1970_ALPHA_I_18O_T0 19 | ) 20 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_equilibrium_fractionation_factors/majoube_1971.py: -------------------------------------------------------------------------------- 1 | """ 2 | Equilibrium fractionation factors from [Majoube 1971](https://doi.org/10.1051/jcp/1971681423) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class Majoube1971: # pylint: disable=too-few-public-methods 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def alpha_l_18O(const, T): 14 | return np.exp( 15 | const.MAJOUBE_1971_ALPHA_L_18O_T2 / T**2 16 | + const.MAJOUBE_1971_ALPHA_L_18O_T1 / T 17 | + const.MAJOUBE_1971_ALPHA_L_18O_T0 18 | ) 19 | 20 | @staticmethod 21 | def alpha_l_2H(const, T): 22 | return np.exp( 23 | const.MAJOUBE_1971_ALPHA_L_2H_T2 / T**2 24 | + const.MAJOUBE_1971_ALPHA_L_2H_T1 / T 25 | + const.MAJOUBE_1971_ALPHA_L_2H_T0 26 | ) 27 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_equilibrium_fractionation_factors/merlivat_and_nief_1967.py: -------------------------------------------------------------------------------- 1 | """ 2 | Equilibrium fractionation factors from 3 | [Merlivat and Nief 1967](https://doi.org/10.3402/tellusa.v19i1.9756) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class MerlivatAndNief1967: 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def alpha_l_2H(const, T): 15 | return np.exp( 16 | const.MERLIVAT_NIEF_1967_ALPHA_L_2H_T2 / T**2 17 | + const.MERLIVAT_NIEF_1967_ALPHA_L_2H_T1 / T 18 | + const.MERLIVAT_NIEF_1967_ALPHA_L_2H_T0 19 | ) 20 | 21 | @staticmethod 22 | def alpha_i_2H(const, T): 23 | return np.exp( 24 | const.MERLIVAT_NIEF_1967_ALPHA_I_2H_T2 / T**2 25 | + const.MERLIVAT_NIEF_1967_ALPHA_I_2H_T1 / T 26 | + const.MERLIVAT_NIEF_1967_ALPHA_I_2H_T0 27 | ) 28 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_kinetic_fractionation_factors/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | kinetic fractionation factors for water isotopologues 3 | """ 4 | 5 | from PySDM.impl.null_physics_class import Null 6 | from .craig_gordon import CraigGordon 7 | from .jouzel_and_merlivat_1984 import JouzelAndMerlivat1984 8 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_kinetic_fractionation_factors/craig_gordon.py: -------------------------------------------------------------------------------- 1 | """ 2 | kinetic fractionation factor in the framework of the Craig-Gordon model 3 | as given in eq. 1.5 in 4 | [Rozanski_et_al_2001 (UNESCO, ed. Mook) 5 | ](https://web.archive.org/web/20160322221332/https://hydrology.nl/images/docs/ihp/Mook_III.pdf) 6 | and as used in [Pierchala et al. 2022](https://doi.org/10.1016/j.gca.2022.01.020) 7 | """ 8 | 9 | 10 | class CraigGordon: # pylint: disable=too-few-public-methods 11 | def __init__(self, _): 12 | pass 13 | 14 | @staticmethod 15 | def alpha_kinetic(*, relative_humidity, turbulence_parameter_n, delta_diff, theta): 16 | """delta_diff = 1 - diffusivity_ratio_heavy_to_light""" 17 | return 1 + theta * turbulence_parameter_n * delta_diff * (1 - relative_humidity) 18 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_kinetic_fractionation_factors/jouzel_and_merlivat_1984.py: -------------------------------------------------------------------------------- 1 | """ 2 | kinetic fractionation factor from [Jouzel & Merlivat 1984](https://doi.org/10.1029/JD089iD07p11749) 3 | (as eq. 3e for n=1 in [Stewart 1975](https://doi.org/10.1029/JC080i009p01133)) 4 | """ 5 | 6 | 7 | class JouzelAndMerlivat1984: # pylint: disable=too-few-public-methods 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def alpha_kinetic( 13 | alpha_equilibrium, saturation_over_ice, diffusivity_ratio_heavy_to_light 14 | ): 15 | """eq. (11)""" 16 | return saturation_over_ice / ( 17 | alpha_equilibrium 18 | / diffusivity_ratio_heavy_to_light 19 | * (saturation_over_ice - 1) 20 | + 1 21 | ) 22 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_meteoric_water_line/__init__.py: -------------------------------------------------------------------------------- 1 | """definitions of meteoric water line parameters 2 | and definitions of the excess quantities""" 3 | 4 | from PySDM.impl.null_physics_class import Null 5 | from .barkan_and_luz_2007 import BarkanAndLuz2007 6 | from .dansgaard_1964 import Dansgaard1964 7 | from .picciotto_et_al_1960 import PicciottoEtAl1960 8 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_meteoric_water_line/barkan_and_luz_2007.py: -------------------------------------------------------------------------------- 1 | """ 2 | Water isotopic line excess parameters defined in 3 | [Barkan and Luz 2007](https://doi.org/10.1002/rcm.3180) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class BarkanAndLuz2007: # pylint: disable=too-few-public-methods 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def excess_17O(const, delta_17O, delta_18O): 15 | return np.log( 16 | delta_17O + 1 17 | ) - const.BARKAN_AND_LUZ_2007_EXCESS_18O_COEFF * np.log(delta_18O + 1) 18 | 19 | @staticmethod 20 | def d17O_of_d18O(const, delta_18O): 21 | return ( 22 | np.exp(const.BARKAN_AND_LUZ_2007_EXCESS_18O_COEFF * np.log(delta_18O + 1)) 23 | - 1 24 | ) 25 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_meteoric_water_line/dansgaard_1964.py: -------------------------------------------------------------------------------- 1 | """ 2 | Water isotopic line excess parameters defined in 3 | [Dansgaard 1964](https://doi.org/10.3402/tellusa.v16i4.8993) 4 | for Northern hemisphere continental stations, except African and Near East 5 | (weighted means) - see, e.g., abstract and Fig 10 6 | """ 7 | 8 | 9 | class Dansgaard1964: # pylint: disable=too-few-public-methods 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def excess_d(const, delta_2H, delta_18O): 15 | return delta_2H - const.CRAIG_1961_SLOPE_COEFF * delta_18O 16 | 17 | @staticmethod 18 | def d18O_of_d2H(const, delta_2H): 19 | return ( 20 | delta_2H - const.CRAIG_1961_INTERCEPT_COEFF 21 | ) / const.CRAIG_1961_SLOPE_COEFF 22 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_meteoric_water_line/picciotto_et_al_1960.py: -------------------------------------------------------------------------------- 1 | """based on [Picciotto et al. 1960](https://doi.org/10.1038/187857a0) 2 | where delta(2H)= slope_coeff * delta(18O) + intercept_coeff formulae is given 3 | with swapped 2H and 18O in a paper 4 | """ 5 | 6 | 7 | class PicciottoEtAl1960: # pylint: disable=too-few-public-methods 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def d18O_of_d2H(const, delta_2H): 13 | return ( 14 | delta_2H - const.PICCIOTTO_18O_TO_2H_INTERCEPT_COEFF 15 | ) / const.PICCIOTTO_18O_TO_2H_SLOPE_COEFF 16 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_ratio_evolution/__init__.py: -------------------------------------------------------------------------------- 1 | """isotope-ratio evolution formulae (incl. Rayleigh distillation)""" 2 | 3 | from PySDM.impl.null_physics_class import Null 4 | from .merlivat_and_jouzel_1979 import MerlivatAndJouzel1979 5 | from .rayleigh_distillation import RayleighDistillation 6 | from .gedzelman_and_arnold_1994 import GedzelmanAndArnold1994 7 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_ratio_evolution/gedzelman_and_arnold_1994.py: -------------------------------------------------------------------------------- 1 | """ 2 | eqs. (22) and (23) in [Gedzelman & Arnold 1994](https://doi.org/10.1029/93JD03518) 3 | """ 4 | 5 | 6 | # pylint: disable=too-few-public-methods 7 | class GedzelmanAndArnold1994: 8 | def __init__(self, _): 9 | pass 10 | 11 | # pylint: disable=too-many-arguments 12 | @staticmethod 13 | def zero_dR_condition( 14 | _, diff_rat, iso_ratio_x, iso_ratio_r, iso_ratio_v, b, alpha_w 15 | ): 16 | return (diff_rat * iso_ratio_x - iso_ratio_r / alpha_w) / ( 17 | diff_rat * iso_ratio_x - (1 + b) * iso_ratio_v + b * iso_ratio_r / alpha_w 18 | ) 19 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_ratio_evolution/merlivat_and_jouzel_1979.py: -------------------------------------------------------------------------------- 1 | """ 2 | see derivation of eq. (12) in [Merlivat and Jouzel 1979](https://doi.org/10.1029/JC084iC08p05029) 3 | (for constant alpha leads to eq. (13) in 4 | [Gedzelman & Arnold 1994](https://doi.org/10.1029/93JD03518)) 5 | """ 6 | 7 | 8 | class MerlivatAndJouzel1979: # pylint: disable=too-few-public-methods 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def d_Rv_over_Rv(_, alpha, d_alpha, n_vapour, d_n_vapour, n_liquid): 14 | return ((alpha - 1) * d_n_vapour - n_liquid * d_alpha) / ( 15 | n_vapour + alpha * n_liquid 16 | ) 17 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_ratio_evolution/rayleigh_distillation.py: -------------------------------------------------------------------------------- 1 | """Fractional distillation assuming continuous removal under equilibrium""" 2 | 3 | 4 | class RayleighDistillation: # pylint:disable=too-few-public-methods 5 | """https://en.wikipedia.org/wiki/Rayleigh_fractionation""" 6 | 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def R_over_R0(_, X_over_X0, a): 12 | return X_over_X0 ** (a - 1) 13 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_relaxation_timescale/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | isotope relaxation timescale formulae 3 | """ 4 | 5 | from PySDM.impl.null_physics_class import Null 6 | from .miyake_et_al_1968 import MiyakeEtAl1968 7 | from .bolin_1958 import Bolin1958 8 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_relaxation_timescale/bolin_1958.py: -------------------------------------------------------------------------------- 1 | """Timescale derived for tritium with assumption of zero ambient concentration - see text above 2 | Table 1 [Bolin 1958](https://digitallibrary.un.org/record/3892725)""" 3 | 4 | import numpy as np 5 | 6 | 7 | class Bolin1958: # pylint: disable=too-few-public-methods 8 | """Implementation of timescale used by Bolin 1958""" 9 | 10 | def __init__(self, const): 11 | assert np.isfinite(const.BOLIN_ISOTOPE_TIMESCALE_COEFF_C1) 12 | 13 | @staticmethod 14 | # pylint: disable=too-many-arguments 15 | def tau(const, radius, r_dr_dt): 16 | """timescale for evaporation of a falling drop with tritium""" 17 | return (-3 / radius**2 * r_dr_dt * const.BOLIN_ISOTOPE_TIMESCALE_COEFF_C1) ** -1 18 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_relaxation_timescale/miyake_et_al_1968.py: -------------------------------------------------------------------------------- 1 | """eq. 28 in [Miyake et al. 1968](https://doi.org/10.2467/mripapers1950.19.2_243)""" 2 | 3 | 4 | class MiyakeEtAl1968: # pylint: disable=too-few-public-methods 5 | def __init__(self, _): 6 | pass 7 | 8 | @staticmethod 9 | # pylint: disable=too-many-arguments 10 | def tau(const, e_s, D, M, vent_coeff, radius, alpha, temperature): 11 | return (radius**2 * alpha * const.rho_w * const.R_str * temperature) / ( 12 | 3 * e_s * D * M * vent_coeff 13 | ) 14 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_temperature_inference/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | formulae for inferring atmospheric temperatures from (ice core) water isotopic composition 3 | """ 4 | 5 | from PySDM.impl.null_physics_class import Null 6 | from PySDM.physics.isotope_temperature_inference.picciotto_et_al_1960 import ( 7 | PicciottoEtAl1960, 8 | ) 9 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_temperature_inference/picciotto_et_al_1960.py: -------------------------------------------------------------------------------- 1 | """based on [Picciotto et al. 1960](https://doi.org/10.1038/187857a0) 2 | where delta(T)=-(a*T + b) formulae given, here cast as T(delta)=(-delta-b)/a""" 3 | 4 | 5 | class PicciottoEtAl1960: # pylint: disable=too-few-public-methods 6 | def __init__(self, _): 7 | pass 8 | 9 | @staticmethod 10 | def temperature_from_delta_18O(const, delta_18O): 11 | return const.T0 + (-delta_18O - const.PICCIOTTO_18O_B) / const.PICCIOTTO_18O_A 12 | 13 | @staticmethod 14 | def temperature_from_delta_2H(const, delta_2H): 15 | return const.T0 + (-delta_2H - const.PICCIOTTO_2H_B) / const.PICCIOTTO_2H_A 16 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_ventilation_ratio/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Isotope ventilation coefficient formulae 3 | """ 4 | 5 | from PySDM.impl.null_physics_class import Null 6 | from .neglect import Neglect 7 | from .brutsaert_1982 import Brutsaert1982 8 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_ventilation_ratio/brutsaert_1982.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on [Brutsaert 1982](https://doi.org/10.1007/978-94-017-1497-6) Springer Netherlands 3 | statement about ventilation coefficient for heavy isotopes on pp. 92-93. 4 | """ 5 | 6 | 7 | class Brutsaert1982: # pylint: disable=too-few-public-methods 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def ratio_heavy_to_light(ventilation_coefficient, diffusivity_ratio_heavy_to_light): 13 | """heavy to light isotope ventilation ratio""" 14 | return ( 15 | 1 - diffusivity_ratio_heavy_to_light ** (1 / 3) 16 | ) / ventilation_coefficient + diffusivity_ratio_heavy_to_light ** (1 / 3) 17 | -------------------------------------------------------------------------------- /PySDM/physics/isotope_ventilation_ratio/neglect.py: -------------------------------------------------------------------------------- 1 | """assuming f'/f equals 1""" 2 | 3 | 4 | class Neglect: # pylint: disable=too-few-public-methods 5 | def __init__(self, _): 6 | pass 7 | 8 | @staticmethod 9 | def ratio_heavy_to_light( 10 | ventilation_coefficient, diffusivity_ratio_heavy_to_light 11 | ): # pylint: disable=unused-argument 12 | return 1 13 | -------------------------------------------------------------------------------- /PySDM/physics/latent_heat_sublimation/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | formulations of latent heat of sublimation temperature dependence (or lack thereof) 3 | """ 4 | 5 | from .murphy_koop_2005 import MurphyKoop2005 6 | -------------------------------------------------------------------------------- /PySDM/physics/latent_heat_sublimation/murphy_koop_2005.py: -------------------------------------------------------------------------------- 1 | """ 2 | temperature-dependent latent heat of sublimation from Murphy and Koop (2005) Eq. (5) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class MurphyKoop2005: # pylint: disable=too-few-public-methods 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def ls(const, T): 14 | return ( 15 | const.MK05_SUB_C1 16 | + const.MK05_SUB_C2 * T 17 | - const.MK05_SUB_C3 * T**2.0 18 | + const.MK05_SUB_C4 * np.exp(-((T / const.MK05_SUB_C5) ** 2.0)) 19 | ) / const.Mv 20 | -------------------------------------------------------------------------------- /PySDM/physics/latent_heat_vapourisation/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | alternative formulations of latent heat temperature dependence (or lack thereof) 3 | """ 4 | 5 | from .constant import Constant 6 | from .kirchhoff import Kirchhoff 7 | from .lowe2019 import Lowe2019 8 | -------------------------------------------------------------------------------- /PySDM/physics/latent_heat_vapourisation/constant.py: -------------------------------------------------------------------------------- 1 | """ 2 | temperature-independent latent heat of vaporization 3 | """ 4 | 5 | 6 | class Constant: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def lv(const, T): # pylint: disable=unused-argument 12 | return const.l_tri 13 | -------------------------------------------------------------------------------- /PySDM/physics/latent_heat_vapourisation/kirchhoff.py: -------------------------------------------------------------------------------- 1 | """ 2 | [Kirchhoff's 3 | law](https://en.wikipedia.org/wiki/Gustav_Kirchhoff#Kirchhoff's_law_of_thermochemistry) 4 | based temperature-dependent latent heat of vaporization 5 | """ 6 | 7 | 8 | class Kirchhoff: # pylint: disable=too-few-public-methods 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def lv(const, T): 14 | return const.l_tri + (const.c_pv - const.c_pw) * (T - const.T_tri) 15 | -------------------------------------------------------------------------------- /PySDM/physics/latent_heat_vapourisation/lowe2019.py: -------------------------------------------------------------------------------- 1 | """ 2 | temperature-dependent latent heat of vaporization used in Lowe et al. 2019 3 | using equation form from Seinfeld and Pandis, with constant values from ICPM code 4 | """ 5 | 6 | from PySDM.physics import constants_defaults 7 | from PySDM.physics.latent_heat_vapourisation.seinfeld_and_pandis_2010 import ( 8 | SeinfeldPandis, 9 | ) 10 | 11 | 12 | class Lowe2019(SeinfeldPandis): # pylint: disable=too-few-public-methods 13 | def __init__(self, const): 14 | assert const.l_l19_a == constants_defaults.l_l19_a 15 | assert const.l_l19_b == constants_defaults.l_l19_b 16 | SeinfeldPandis.__init__(self, const) 17 | -------------------------------------------------------------------------------- /PySDM/physics/latent_heat_vapourisation/seinfeld_and_pandis_2010.py: -------------------------------------------------------------------------------- 1 | """ 2 | temperature-dependent latent heat of vaporization from Seinfeld and Pandis 3 | """ 4 | 5 | 6 | class SeinfeldPandis: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def lv(const, T): 12 | return const.l_tri * (const.T_tri / T) ** (const.l_l19_a + const.l_l19_b * T) 13 | -------------------------------------------------------------------------------- /PySDM/physics/optical_albedo/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | alternative formulations of cloud albedo 3 | """ 4 | 5 | from PySDM.impl.null_physics_class import Null 6 | from .bohren1987 import Bohren1987 7 | -------------------------------------------------------------------------------- /PySDM/physics/optical_albedo/bohren1987.py: -------------------------------------------------------------------------------- 1 | """ 2 | [Bohren 1987](https://doi.org/10.1119/1.15109) Eq. 14 3 | """ 4 | 5 | 6 | class Bohren1987: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def albedo(const, tau): 12 | return ((const.ONE - const.asymmetry_g) * tau) / ( 13 | const.TWO + (const.ONE - const.asymmetry_g) * tau 14 | ) 15 | -------------------------------------------------------------------------------- /PySDM/physics/optical_depth/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | alternative formulations of optical depth 3 | """ 4 | 5 | from PySDM.impl.null_physics_class import Null 6 | from .stephens_1978 import Stephens1978 7 | -------------------------------------------------------------------------------- /PySDM/physics/optical_depth/stephens_1978.py: -------------------------------------------------------------------------------- 1 | """ 2 | [Stephens 1978](https://doi.org/10.1175/1520-0469(1978)035%3C2123:RPIEWC%3E2.0.CO;2) 3 | Eq. 7 for optical depth, where LWP is in g/m^2 and reff is in um. 4 | """ 5 | 6 | 7 | class Stephens1978: # pylint: disable=too-few-public-methods 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def tau(const, LWP, reff): 13 | return (const.ONE_AND_A_HALF * LWP) / (const.rho_w * reff) 14 | -------------------------------------------------------------------------------- /PySDM/physics/particle_advection/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implicit (default) and explicit particle displacement integrators 3 | """ 4 | 5 | from .explicit_in_space import ExplicitInSpace 6 | from .implicit_in_space import ImplicitInSpace 7 | -------------------------------------------------------------------------------- /PySDM/physics/particle_advection/explicit_in_space.py: -------------------------------------------------------------------------------- 1 | """ 2 | basic explicit-in-space Euler scheme 3 | """ 4 | 5 | 6 | class ExplicitInSpace: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def displacement(_, position_in_cell, c_l, c_r): 12 | return c_l * (1 - position_in_cell) + c_r * position_in_cell 13 | -------------------------------------------------------------------------------- /PySDM/physics/particle_advection/implicit_in_space.py: -------------------------------------------------------------------------------- 1 | """ 2 | eqs. 14-16 in [Arabas et al. 2015](https://doi.org/10.5194/gmd-8-1677-2015) 3 | """ 4 | 5 | 6 | class ImplicitInSpace: # pylint: disable=too-few-public-methods 7 | def __init__(self, _): 8 | pass 9 | 10 | @staticmethod 11 | def displacement(_, position_in_cell, c_l, c_r): 12 | return (c_l * (1 - position_in_cell) + c_r * position_in_cell) / (1 - c_r + c_l) 13 | -------------------------------------------------------------------------------- /PySDM/physics/particle_shape_and_density/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle shape and density relationships 3 | """ 4 | 5 | from .liquid_spheres import LiquidSpheres 6 | from .mixed_phase_spheres import MixedPhaseSpheres 7 | from .porous_spheroids import PorousSpheroid 8 | -------------------------------------------------------------------------------- /PySDM/physics/particle_shape_and_density/porous_spheroids.py: -------------------------------------------------------------------------------- 1 | """ 2 | for mixed-phase microphysics as in 3 | [Shima et al. 2020](https://doi.org/10.5194/gmd-13-4107-2020) 4 | """ 5 | 6 | 7 | class PorousSpheroid: # pylint: disable=too-few-public-methods 8 | @staticmethod 9 | def supports_mixed_phase(_=None): 10 | return True 11 | -------------------------------------------------------------------------------- /PySDM/physics/saturation_vapour_pressure/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Alternative formulations of saturation vapour pressure temperature dependence approximations 3 | """ 4 | 5 | from .august_roche_magnus import AugustRocheMagnus 6 | from .flatau_walko_cotton import FlatauWalkoCotton 7 | from .lowe1977 import Lowe1977 8 | from .murphy_koop_2005 import MurphyKoop2005 9 | from .wexler_1976 import Wexler1976 10 | from .bolton_1980 import Bolton1980 11 | -------------------------------------------------------------------------------- /PySDM/physics/saturation_vapour_pressure/august_roche_magnus.py: -------------------------------------------------------------------------------- 1 | """ 2 | August-Roche-Magnus formula (see, e.g., 3 | [Wikipedia](https://en.wikipedia.org/wiki/Clausius–Clapeyron_relation#August–Roche–Magnus_formula) 4 | and references therein) 5 | """ 6 | 7 | import numpy as np 8 | 9 | 10 | class AugustRocheMagnus: 11 | def __init__(self, _): 12 | pass 13 | 14 | @staticmethod 15 | def pvs_water(const, T): 16 | return const.ARM_C1 * np.exp( 17 | (const.ARM_C2 * (T - const.T0)) / ((T - const.T0) + const.ARM_C3) 18 | ) 19 | 20 | @staticmethod 21 | def pvs_ice(const, T): 22 | """NaN with unit of pressure and correct dimension""" 23 | return np.nan * (T - const.T0) / const.ARM_C3 * const.ARM_C1 24 | -------------------------------------------------------------------------------- /PySDM/physics/saturation_vapour_pressure/bolton_1980.py: -------------------------------------------------------------------------------- 1 | """ 2 | [Bolton 1980](https://doi.org/10.1175/1520-0493(1980)108%3C1046:TCOEPT%3E2.0.CO;2) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class Bolton1980: 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def pvs_water(const, T): 14 | """valid for 243.15(-30) <= T <= 308.15(35) K(C), eq. (10)""" 15 | return const.B80W_G0 * np.exp( 16 | (const.B80W_G1 * (T - const.T0)) / ((T - const.T0) + const.B80W_G2) 17 | ) 18 | 19 | @staticmethod 20 | def pvs_ice(const, T): 21 | """NaN with unit of pressure and correct dimension""" 22 | return np.nan * (T - const.T0) / const.B80W_G2 * const.B80W_G0 23 | -------------------------------------------------------------------------------- /PySDM/physics/saturation_vapour_pressure/wexler_1976.py: -------------------------------------------------------------------------------- 1 | """ 2 | [Wexler 1976](https://doi.org/10.6028/jres.080A.071) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class Wexler1976: 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def pvs_water(const, T): 14 | return ( 15 | np.exp( 16 | const.W76W_G0 / T**2 17 | + const.W76W_G1 / T 18 | + const.W76W_G2 19 | + const.W76W_G3 * T 20 | + const.W76W_G4 * T**2 21 | + const.W76W_G5 * T**3 22 | + const.W76W_G6 * T**4 23 | + const.W76W_G7 * np.log(T / const.one_kelvin) 24 | ) 25 | * const.W76W_G8 26 | ) 27 | 28 | @staticmethod 29 | def pvs_ice(const, T): 30 | """NaN with unit of pressure and correct dimension""" 31 | return np.nan * (T - const.T0) / const.B80W_G2 * const.B80W_G0 32 | -------------------------------------------------------------------------------- /PySDM/physics/state_variable_triplet/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Formulae pertaining to the choice of state variables 3 | """ 4 | 5 | from .libcloudphplusplus import LibcloudphPlusPlus 6 | -------------------------------------------------------------------------------- /PySDM/physics/surface_tension/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Particle surface tension formulae 3 | """ 4 | 5 | from .compressed_film_ovadnevaite import CompressedFilmOvadnevaite 6 | from .compressed_film_ruehl import CompressedFilmRuehl 7 | from .constant import Constant 8 | from .szyszkowski_langmuir import SzyszkowskiLangmuir 9 | -------------------------------------------------------------------------------- /PySDM/physics/surface_tension/constant.py: -------------------------------------------------------------------------------- 1 | """ 2 | constant surface tension coefficient 3 | """ 4 | 5 | 6 | class Constant: # pylint: disable=too-few-public-methods 7 | """ 8 | Assumes aerosol is dissolved in the bulk of the droplet, and the 9 | droplet surface is composed of pure water with constant surface 10 | tension `sgm_w`. 11 | """ 12 | 13 | def __init__(self, _): 14 | pass 15 | 16 | @staticmethod 17 | def sigma(const, T, v_wet, v_dry, f_org): # pylint: disable=unused-argument 18 | return const.sgm_w 19 | -------------------------------------------------------------------------------- /PySDM/physics/terminal_velocity/__init__.py: -------------------------------------------------------------------------------- 1 | """terminal velocity formulae""" 2 | 3 | from .rogers_yau import RogersYau 4 | from .gunn_kinzer_1949 import GunnKinzer1949 5 | from .power_series import PowerSeries 6 | -------------------------------------------------------------------------------- /PySDM/physics/terminal_velocity/gunn_kinzer_1949.py: -------------------------------------------------------------------------------- 1 | """[Gunn & Kinzer 1949](https://doi.org/10.1175/1520-0469(1949)006%3C0243:TTVOFF%3E2.0.CO;2)""" 2 | 3 | 4 | class GunnKinzer1949: # pylint: disable=too-few-public-methods 5 | def __init__(self, _): 6 | pass 7 | -------------------------------------------------------------------------------- /PySDM/physics/terminal_velocity/power_series.py: -------------------------------------------------------------------------------- 1 | """power-series formulation""" 2 | 3 | 4 | class PowerSeries: # pylint: disable=too-few-public-methods 5 | def __init__(self, _): 6 | pass 7 | -------------------------------------------------------------------------------- /PySDM/physics/terminal_velocity/rogers_yau.py: -------------------------------------------------------------------------------- 1 | """ 2 | equations 8.5, 8.6, 8.8 in 3 | [Rogers & Yau 1989](https://archive.org/details/shortcourseinclo0000roge_m3k2) 4 | """ 5 | 6 | import numpy as np 7 | 8 | 9 | class RogersYau: # pylint: disable=too-few-public-methods 10 | def __init__(self, _): 11 | pass 12 | 13 | @staticmethod 14 | def v_term(const, radius): 15 | return np.where( 16 | radius < const.ROGERS_YAU_TERM_VEL_SMALL_R_LIMIT, 17 | const.ROGERS_YAU_TERM_VEL_SMALL_K * radius**const.TWO, 18 | np.where( 19 | radius < const.ROGERS_YAU_TERM_VEL_MEDIUM_R_LIMIT, 20 | const.ROGERS_YAU_TERM_VEL_MEDIUM_K * radius, 21 | const.ROGERS_YAU_TERM_VEL_LARGE_K * radius**const.ONE_HALF, 22 | ), 23 | ) 24 | -------------------------------------------------------------------------------- /PySDM/physics/ventilation/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Ventilation coefficient formulae 3 | """ 4 | 5 | from .neglect import Neglect 6 | from .pruppacher_rasmussen_1979 import PruppacherAndRasmussen1979 7 | from .froessling_1938 import Froessling1938 8 | -------------------------------------------------------------------------------- /PySDM/physics/ventilation/froessling_1938.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on Froessling ,N. (1938) Beitr. Geophys. 52 pp. 170-216 3 | as referenced in [Squires 1952](https://doi.org/10.1071/CH9520059) 4 | """ 5 | 6 | 7 | class Froessling1938: # pylint: disable=too-few-public-methods 8 | def __init__(self, _): 9 | pass 10 | 11 | @staticmethod 12 | def ventilation_coefficient(const, sqrt_re_times_cbrt_sc): 13 | return const.FROESSLING_1938_A + const.FROESSLING_1938_B * sqrt_re_times_cbrt_sc 14 | -------------------------------------------------------------------------------- /PySDM/physics/ventilation/neglect.py: -------------------------------------------------------------------------------- 1 | """ 2 | constant ventilation coefficient of unity (i.e., neglect ventilation effects) 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class Neglect: # pylint: disable=too-few-public-methods 9 | def __init__(self, _): 10 | pass 11 | 12 | @staticmethod 13 | def ventilation_coefficient(sqrt_re_times_cbrt_sc): 14 | return np.power(sqrt_re_times_cbrt_sc, 0) 15 | -------------------------------------------------------------------------------- /PySDM/products/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simulation output products 3 | """ 4 | 5 | from .ambient_thermodynamics import * 6 | from .aqueous_chemistry import * 7 | from .collision import * 8 | from .condensation import * 9 | from .displacement import * 10 | from .freezing import * 11 | from .housekeeping import * 12 | from .size_spectral import * 13 | from .optical import * 14 | from .parcel import * 15 | -------------------------------------------------------------------------------- /PySDM/products/ambient_thermodynamics/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | products offering access to environment variables (ambient temperature, pressure, ...) 3 | """ 4 | 5 | from .ambient_dry_air_density import AmbientDryAirDensity 6 | from .ambient_dry_air_potential_temperature import AmbientDryAirPotentialTemperature 7 | from .ambient_pressure import AmbientPressure 8 | from .ambient_relative_humidity import AmbientRelativeHumidity 9 | from .ambient_temperature import AmbientTemperature 10 | from .ambient_water_vapour_mixing_ratio import AmbientWaterVapourMixingRatio 11 | -------------------------------------------------------------------------------- /PySDM/products/ambient_thermodynamics/ambient_dry_air_density.py: -------------------------------------------------------------------------------- 1 | """ 2 | ambient dry-air density 3 | """ 4 | 5 | from PySDM.products.impl import MoistEnvironmentProduct, register_product 6 | 7 | 8 | @register_product() 9 | class AmbientDryAirDensity(MoistEnvironmentProduct): 10 | def __init__(self, name="rhod", unit="kg/m^3", var=None): 11 | super().__init__(name=name, unit=unit, var=var) 12 | -------------------------------------------------------------------------------- /PySDM/products/ambient_thermodynamics/ambient_dry_air_potential_temperature.py: -------------------------------------------------------------------------------- 1 | """ 2 | ambient dry-air potential temperature (computed using dry air partial pressure) 3 | """ 4 | 5 | from PySDM.products.impl import MoistEnvironmentProduct, register_product 6 | 7 | 8 | @register_product() 9 | class AmbientDryAirPotentialTemperature(MoistEnvironmentProduct): 10 | def __init__(self, unit="K", name=None, var=None): 11 | super().__init__(unit=unit, name=name, var=var) 12 | -------------------------------------------------------------------------------- /PySDM/products/ambient_thermodynamics/ambient_pressure.py: -------------------------------------------------------------------------------- 1 | """ 2 | ambient pressure 3 | """ 4 | 5 | from PySDM.products.impl import MoistEnvironmentProduct, register_product 6 | 7 | 8 | @register_product() 9 | class AmbientPressure(MoistEnvironmentProduct): 10 | def __init__(self, name=None, unit="Pa", var=None): 11 | super().__init__(name=name, unit=unit, var=var) 12 | -------------------------------------------------------------------------------- /PySDM/products/ambient_thermodynamics/ambient_relative_humidity.py: -------------------------------------------------------------------------------- 1 | """ 2 | ambient relative humidity (wrt water or ice) 3 | """ 4 | 5 | from PySDM.products.impl import MoistEnvironmentProduct, register_product 6 | 7 | 8 | @register_product() 9 | class AmbientRelativeHumidity(MoistEnvironmentProduct): 10 | def __init__(self, name=None, unit="dimensionless", var=None, ice=False): 11 | super().__init__(name=name, unit=unit, var=var) 12 | self.ice = ice 13 | 14 | def _impl(self, **kwargs): 15 | super()._impl() 16 | if self.ice: 17 | RHw = self.buffer.copy() 18 | self._download_to_buffer(self.environment["a_w_ice"]) 19 | self.buffer[:] = RHw / self.buffer[:] 20 | return self.buffer 21 | -------------------------------------------------------------------------------- /PySDM/products/ambient_thermodynamics/ambient_temperature.py: -------------------------------------------------------------------------------- 1 | """ 2 | ambient temperature 3 | """ 4 | 5 | from PySDM.products.impl import MoistEnvironmentProduct, register_product 6 | 7 | 8 | @register_product() 9 | class AmbientTemperature(MoistEnvironmentProduct): 10 | def __init__(self, name=None, unit="K", var=None): 11 | super().__init__(name=name, unit=unit, var=var) 12 | -------------------------------------------------------------------------------- /PySDM/products/ambient_thermodynamics/ambient_water_vapour_mixing_ratio.py: -------------------------------------------------------------------------------- 1 | """ 2 | ambient water vapour mixing ratio (mass of water vapour per mass of dry air) 3 | """ 4 | 5 | from PySDM.products.impl import MoistEnvironmentProduct, register_product 6 | 7 | 8 | @register_product() 9 | class AmbientWaterVapourMixingRatio(MoistEnvironmentProduct): 10 | def __init__(self, name=None, unit="dimensionless", var=None): 11 | super().__init__(unit=unit, name=name, var=var) 12 | -------------------------------------------------------------------------------- /PySDM/products/aqueous_chemistry/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | products pertinent to the `PySDM.dynamics.aqueous_chemistry.AqueousChemistry` dynamic 3 | """ 4 | 5 | from .acidity import Acidity 6 | from .aqueous_mass_spectrum import AqueousMassSpectrum 7 | from .aqueous_mole_fraction import AqueousMoleFraction 8 | from .gaseous_mole_fraction import GaseousMoleFraction 9 | from .total_dry_mass_mixing_ratio import TotalDryMassMixingRatio 10 | -------------------------------------------------------------------------------- /PySDM/products/aqueous_chemistry/total_dry_mass_mixing_ratio.py: -------------------------------------------------------------------------------- 1 | """ 2 | dry aerosol mass summed over all particles in a grid box per mass of dry air 3 | """ 4 | 5 | import numpy as np 6 | 7 | from PySDM.products.impl import MomentProduct, register_product 8 | 9 | 10 | @register_product() 11 | class TotalDryMassMixingRatio(MomentProduct): 12 | def __init__(self, density, name=None, unit="kg/kg"): 13 | super().__init__(unit=unit, name=name) 14 | self.density = density 15 | 16 | def _impl(self, **kwargs): 17 | self._download_moment_to_buffer(attr="dry volume", rank=1) 18 | self.buffer[:] *= self.density 19 | result = np.copy(self.buffer) 20 | self._download_moment_to_buffer(attr="dry volume", rank=0) 21 | result[:] *= self.buffer 22 | self._download_to_buffer(self.particulator.environment["rhod"]) 23 | result[:] /= self.particulator.mesh.dv 24 | result[:] /= self.buffer 25 | return result 26 | -------------------------------------------------------------------------------- /PySDM/products/collision/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Collision rate products for breakup, coalescence, and collisions 3 | """ 4 | 5 | from .collision_rates import ( # BreakupOnlyRatePerGridbox,; CoalescenceOnlyRatePerGridbox, 6 | BreakupRateDeficitPerGridbox, 7 | BreakupRatePerGridbox, 8 | CoalescenceRatePerGridbox, 9 | CollisionRateDeficitPerGridbox, 10 | CollisionRatePerGridbox, 11 | ) 12 | from .collision_timestep_mean import CollisionTimestepMean 13 | from .collision_timestep_min import CollisionTimestepMin 14 | -------------------------------------------------------------------------------- /PySDM/products/collision/collision_timestep_min.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimal collision timestep used when adaptive timestepping is enabled in the 3 | `PySDM.dynamics.collisions.collision.Collision` dynamic (fetching a value resets the counter) 4 | """ 5 | 6 | from PySDM.products.impl import Product, register_product 7 | 8 | 9 | @register_product() 10 | class CollisionTimestepMin(Product): 11 | def __init__(self, unit="s", name=None): 12 | super().__init__(unit=unit, name=name) 13 | self.collision = None 14 | self.range = None 15 | 16 | def register(self, builder): 17 | super().register(builder) 18 | self.collision = self.particulator.dynamics["Collision"] 19 | self.range = self.collision.dt_coal_range 20 | 21 | def _impl(self, **kwargs): 22 | self._download_to_buffer(self.collision.stats_dt_min) 23 | self.collision.stats_dt_min[:] = self.collision.particulator.dt 24 | return self.buffer 25 | -------------------------------------------------------------------------------- /PySDM/products/condensation/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | products pertinent to the `PySDM.dynamics.condensation.Condensation` dynamic 3 | """ 4 | 5 | from .activable_fraction import ActivableFraction 6 | from .condensation_timestep import CondensationTimestepMax, CondensationTimestepMin 7 | from .event_rates import ActivatingRate, DeactivatingRate, RipeningRate 8 | from .peak_supersaturation import PeakSupersaturation 9 | -------------------------------------------------------------------------------- /PySDM/products/displacement/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | products pertinent to the `PySDM.dynamics.displacement.Displacement` dynamic 3 | """ 4 | 5 | from .averaged_terminal_velocity import AveragedTerminalVelocity 6 | from .flow_velocity_component import FlowVelocityComponent 7 | from .max_courant_number import MaxCourantNumber 8 | from .surface_precipitation import SurfacePrecipitation 9 | -------------------------------------------------------------------------------- /PySDM/products/freezing/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | products pertinent to the `PySDM.dynamics.freezing.Freezing` dynamic 3 | """ 4 | 5 | from .cooling_rate import CoolingRate 6 | from .freezable_specific_concentration import FreezableSpecificConcentration 7 | from .frozen_particle_concentration import ( 8 | FrozenParticleConcentration, 9 | FrozenParticleSpecificConcentration, 10 | ) 11 | from .ice_nuclei_concentration import ( 12 | IceNucleiConcentration, 13 | SpecificIceNucleiConcentration, 14 | ) 15 | from .total_unfrozen_immersed_surface_area import TotalUnfrozenImmersedSurfaceArea 16 | -------------------------------------------------------------------------------- /PySDM/products/freezing/cooling_rate.py: -------------------------------------------------------------------------------- 1 | """ 2 | number-averaged cooling rate 3 | """ 4 | 5 | from PySDM.products.impl import MomentProduct, register_product 6 | 7 | 8 | @register_product() 9 | class CoolingRate(MomentProduct): 10 | def __init__(self, unit="K/s", name=None): 11 | super().__init__(unit=unit, name=name) 12 | 13 | def register(self, builder): 14 | builder.request_attribute("cooling rate") 15 | super().register(builder) 16 | 17 | def _impl(self, **kwargs): 18 | self._download_moment_to_buffer(attr="cooling rate", rank=1) 19 | return self.buffer 20 | -------------------------------------------------------------------------------- /PySDM/products/freezing/total_unfrozen_immersed_surface_area.py: -------------------------------------------------------------------------------- 1 | """ 2 | mean immersed surface area per particle for unfrozen particles 3 | """ 4 | 5 | import numpy as np 6 | 7 | from PySDM.products.impl import MomentProduct, register_product 8 | 9 | 10 | @register_product() 11 | class TotalUnfrozenImmersedSurfaceArea(MomentProduct): 12 | def __init__(self, unit="m^2", name=None): 13 | super().__init__(unit=unit, name=name) 14 | 15 | def _impl(self, **kwargs): 16 | params = { 17 | "attr": "immersed surface area", 18 | "filter_attr": "volume", 19 | "filter_range": (0, np.inf), 20 | } 21 | self._download_moment_to_buffer(**params, rank=1) 22 | result = np.copy(self.buffer) 23 | self._download_moment_to_buffer(**params, rank=0) 24 | result[:] *= self.buffer 25 | # TODO #599 per volume / per gridbox ? 26 | return result 27 | -------------------------------------------------------------------------------- /PySDM/products/housekeeping/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Housekeeping products: time, super-particle count, wall-time timers... 3 | """ 4 | 5 | from .dynamic_wall_time import DynamicWallTime 6 | from .super_droplet_count_per_gridbox import SuperDropletCountPerGridbox 7 | from .time import Time 8 | from .timers import CPUTime, WallTime 9 | -------------------------------------------------------------------------------- /PySDM/products/housekeeping/dynamic_wall_time.py: -------------------------------------------------------------------------------- 1 | """ 2 | wall-time for a given dynamic (fetching a value resets the counter) 3 | """ 4 | 5 | from PySDM.products.impl import Product, register_product 6 | 7 | 8 | @register_product() 9 | class DynamicWallTime(Product): 10 | def __init__(self, dynamic, name=None, unit="s"): 11 | super().__init__(name=name, unit=unit) 12 | self.value = 0 13 | self.dynamic = dynamic 14 | 15 | def register(self, builder): 16 | super().register(builder) 17 | self.particulator.observers.append(self) 18 | self.shape = () 19 | 20 | def _impl(self, **kwargs): 21 | tmp = self.value 22 | self.value = 0 23 | return tmp 24 | 25 | def notify(self): 26 | self.value += self.particulator.timers[self.dynamic].time 27 | -------------------------------------------------------------------------------- /PySDM/products/housekeeping/time.py: -------------------------------------------------------------------------------- 1 | """ 2 | physical time (in dt increments) 3 | """ 4 | 5 | from PySDM.products.impl import Product, register_product 6 | 7 | 8 | @register_product() 9 | class Time(Product): 10 | def __init__(self, name=None, unit="s"): 11 | super().__init__(name=name, unit=unit) 12 | self.t = 0 13 | 14 | def register(self, builder): 15 | super().register(builder) 16 | self.particulator.observers.append(self) 17 | 18 | def _impl(self, **kwargs): 19 | return self.t 20 | 21 | def notify(self): 22 | self.t += self.particulator.dt 23 | -------------------------------------------------------------------------------- /PySDM/products/impl/__init__.py: -------------------------------------------------------------------------------- 1 | """commons, not intended to be imported from user code""" 2 | 3 | from PySDM.products.impl.moist_environment_product import MoistEnvironmentProduct 4 | from PySDM.products.impl.moment_product import MomentProduct 5 | from PySDM.products.impl.product import Product 6 | from PySDM.products.impl.rate_product import RateProduct 7 | from PySDM.products.impl.spectrum_moment_product import SpectrumMomentProduct 8 | from PySDM.products.impl.concentration_product import ConcentrationProduct 9 | from PySDM.products.impl.activation_filtered_product import ActivationFilteredProduct 10 | from PySDM.products.impl.register_product import register_product 11 | -------------------------------------------------------------------------------- /PySDM/products/impl/register_product.py: -------------------------------------------------------------------------------- 1 | """decorator for product classes 2 | ensuring that their instances can be re-used with multiple builders""" 3 | 4 | from copy import deepcopy 5 | 6 | 7 | def _instantiate(self, *, builder, buffer): 8 | copy = deepcopy(self) 9 | copy.set_buffer(buffer) 10 | copy.register(builder) 11 | return copy 12 | 13 | 14 | def register_product(): 15 | def decorator(cls): 16 | if hasattr(cls, "instantiate"): 17 | assert cls.instantiate is _instantiate 18 | else: 19 | setattr(cls, "instantiate", _instantiate) 20 | return cls 21 | 22 | return decorator 23 | -------------------------------------------------------------------------------- /PySDM/products/optical/__init__.py: -------------------------------------------------------------------------------- 1 | """cloud optical properties""" 2 | 3 | from .cloud_optical_depth import CloudOpticalDepth 4 | from .cloud_albedo import CloudAlbedo 5 | -------------------------------------------------------------------------------- /PySDM/products/optical/cloud_albedo.py: -------------------------------------------------------------------------------- 1 | """ 2 | cloud albedo 3 | """ 4 | 5 | from PySDM.products.impl import Product, register_product 6 | 7 | 8 | @register_product() 9 | class CloudAlbedo(Product): 10 | def __init__(self, *, unit="dimensionless", name=None): 11 | super().__init__(name=name, unit=unit) 12 | 13 | def _impl(self, **kwargs): 14 | return self.formulae.optical_albedo.albedo(kwargs["optical_depth"]) 15 | -------------------------------------------------------------------------------- /PySDM/products/optical/cloud_optical_depth.py: -------------------------------------------------------------------------------- 1 | """ 2 | cloud optical depth 3 | """ 4 | 5 | from PySDM.products.impl import Product, register_product 6 | 7 | 8 | @register_product() 9 | class CloudOpticalDepth(Product): 10 | def __init__(self, *, unit="dimensionless", name=None): 11 | super().__init__(name=name, unit=unit) 12 | 13 | def _impl(self, **kwargs): 14 | return self.formulae.optical_depth.tau( 15 | kwargs["liquid_water_path"], 16 | kwargs["effective_radius"], 17 | ) 18 | -------------------------------------------------------------------------------- /PySDM/products/parcel/__init__.py: -------------------------------------------------------------------------------- 1 | """products specific to the parcel environment""" 2 | 3 | from .cloud_water_path import ParcelLiquidWaterPath 4 | from .parcel_displacement import ParcelDisplacement 5 | -------------------------------------------------------------------------------- /PySDM/products/parcel/parcel_displacement.py: -------------------------------------------------------------------------------- 1 | """ 2 | parcel displacement, for use with `PySDM.environments.parcel.Parcel` environment only 3 | """ 4 | 5 | from PySDM.environments import Parcel 6 | from PySDM.products.impl import Product, register_product 7 | 8 | 9 | @register_product() 10 | class ParcelDisplacement(Product): 11 | def __init__(self, unit="m", name=None): 12 | super().__init__(unit=unit, name=name) 13 | self.environment = None 14 | 15 | def register(self, builder): 16 | super().register(builder) 17 | assert isinstance(builder.particulator.environment, Parcel) 18 | self.environment = builder.particulator.environment 19 | 20 | def _impl(self, **kwargs): 21 | self._download_to_buffer(self.environment["z"]) 22 | return self.buffer 23 | -------------------------------------------------------------------------------- /PySDM/products/size_spectral/total_particle_concentration.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle concentration (per volume of air) 3 | """ 4 | 5 | from PySDM.products.impl import ConcentrationProduct, register_product 6 | 7 | 8 | @register_product() 9 | class TotalParticleConcentration(ConcentrationProduct): 10 | def __init__(self, name=None, unit="m^-3", stp=False): 11 | super().__init__(name=name, unit=unit, specific=False, stp=stp) 12 | 13 | def _impl(self, **kwargs): 14 | self._download_moment_to_buffer(attr="water mass", rank=0) 15 | return super()._impl(**kwargs) 16 | -------------------------------------------------------------------------------- /PySDM/products/size_spectral/total_particle_specific_concentration.py: -------------------------------------------------------------------------------- 1 | """ 2 | particle specific concentration (per mass of dry air) 3 | """ 4 | 5 | from PySDM.products.impl import ConcentrationProduct, register_product 6 | 7 | 8 | @register_product() 9 | class TotalParticleSpecificConcentration(ConcentrationProduct): 10 | def __init__(self, name=None, unit="kg^-1"): 11 | super().__init__(name=name, unit=unit, stp=False, specific=True) 12 | 13 | def _impl(self, **kwargs): 14 | self._download_moment_to_buffer(attr="volume", rank=0) 15 | return super()._impl(**kwargs) 16 | -------------------------------------------------------------------------------- /docs/logos/pysdm_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/docs/logos/pysdm_logo.png -------------------------------------------------------------------------------- /docs/templates/README.md: -------------------------------------------------------------------------------- 1 | dark mode .css from https://github.com/mitmproxy/pdoc/tree/main/examples/dark-mode 2 | -------------------------------------------------------------------------------- /docs/templates/custom.css: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | } 4 | 5 | mark { 6 | background-color: #859bed; 7 | color: black; 8 | } 9 | 10 | ul { 11 | margin-left: 2em; 12 | } 13 | -------------------------------------------------------------------------------- /docs/templates/theme.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --pdoc-background: #212529; 3 | } 4 | 5 | .pdoc { 6 | --text: #f7f7f7; 7 | --muted: #9d9d9d; 8 | --link: #58a6ff; 9 | --link-hover: #3989ff; 10 | --code: #333; 11 | --active: #555; 12 | 13 | --accent: #343434; 14 | --accent2: #555; 15 | 16 | --nav-hover: rgba(0, 0, 0, 0.1); 17 | --name: #77C1FF; 18 | --def: #0cdd0c; 19 | --annotation: #00c037; 20 | } 21 | -------------------------------------------------------------------------------- /examples/MANIFEST.in: -------------------------------------------------------------------------------- 1 | global-exclude *.ipynb 2 | global-exclude *.csv 3 | include docs/*.md -------------------------------------------------------------------------------- /examples/PySDM_examples/Abade_and_Albuquerque_2024/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | mixed-phase example using parcel environment based on 4 | [Abade & Albuquerque 2024 (QJRMS)](https://doi.org/10.1002/qj.4775) 5 | 6 | fig_2.ipynb: 7 | .. include:: ./fig_2.ipynb.badges.md 8 | """ 9 | 10 | from .simulation import Simulation 11 | from .settings import Settings 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Abdul_Razzak_Ghan_2000/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | condensation example using parcel environment based on 4 | [Abdul-Razzak & Ghan 2000 (JGR)](https://doi.org/10.1029/1999JD901161) 5 | 6 | figs1-5.ipynb: 7 | .. include:: ./figs1-5.ipynb.badges.md 8 | 9 | fig_4_kinetic_limitations.ipynb: 10 | .. include:: ./fig_4_kinetic_limitations.ipynb.badges.md 11 | """ 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Alpert_and_Knopf_2016/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | box-environment example based on 3 | [Alpert & Knopf 2016 (Atmos. Chem. Phys. 16)](https://doi.org/10.5194/acp-16-2083-2016) 4 | 5 | fig_1.ipynb: 6 | .. include:: ./fig_1.ipynb.badges.md 7 | 8 | fig_2.ipynb: 9 | .. include:: ./fig_2.ipynb.badges.md 10 | 11 | fig_3.ipynb: 12 | .. include:: ./fig_3.ipynb.badges.md 13 | 14 | fig_4.ipynb: 15 | .. include:: ./fig_4.ipynb.badges.md 16 | 17 | fig_5.ipynb: 18 | .. include:: ./fig_5.ipynb.badges.md 19 | """ 20 | 21 | # pylint: disable=invalid-name 22 | from .simulation import Simulation, simulation 23 | from .table_1 import Table1 24 | from .table_2 import Table2 25 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Alpert_and_Knopf_2016/table.py: -------------------------------------------------------------------------------- 1 | class Table: 2 | def __getitem__(self, item): 3 | return self._data[item] 4 | 5 | def items(self): 6 | return self._data.items() 7 | 8 | def __init__(self, volume, data): 9 | self._data = data 10 | self.volume = volume 11 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Arabas_and_Shima_2017/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | condensation-evaportaion parcel example based on 3 | [Arabas and Shima 2017 (Nonlin. Processes Geophys. 24)](https://doi.org/10.5194/npg-24-535-2017) 4 | 5 | fig_5.ipynb: 6 | .. include:: ./fig_5.ipynb.badges.md 7 | """ 8 | 9 | # pylint: disable=invalid-name 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Arabas_and_Shima_2017/example.py: -------------------------------------------------------------------------------- 1 | from PySDM_examples.Arabas_and_Shima_2017.settings import setups 2 | from PySDM_examples.Arabas_and_Shima_2017.simulation import Simulation 3 | 4 | 5 | def main(): 6 | for settings in setups: 7 | Simulation(settings).run() 8 | 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Arabas_et_al_2015/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | 2D prescribed-flow case extended with Paraview visualisation with spin-up logic from 4 | [Arabas et al. 2015](http://doi.org/10.5194/gmd-8-1677-2015) 5 | 6 | gui.ipynb: 7 | .. include:: ./gui.ipynb.badges.md 8 | """ 9 | from .settings import Settings 10 | from .spin_up import SpinUp 11 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Arabas_et_al_2015/example.py: -------------------------------------------------------------------------------- 1 | from open_atmos_jupyter_utils import TemporaryFile 2 | from PySDM_examples.Arabas_et_al_2015 import Settings, SpinUp 3 | from PySDM_examples.utils.kinematic_2d import Simulation, Storage 4 | from PySDM_examples.utils import DummyController 5 | 6 | from PySDM import Formulae 7 | from PySDM.exporters import NetCDFExporter 8 | from PySDM.physics import si 9 | 10 | 11 | def main(): 12 | settings = Settings(Formulae()) 13 | 14 | settings.n_sd_per_gridbox = 25 15 | settings.grid = (25, 25) 16 | settings.simulation_time = 5400 * si.second 17 | 18 | storage = Storage() 19 | simulation = Simulation(settings, storage, SpinUp) 20 | simulation.reinit() 21 | simulation.run() 22 | temp_file = TemporaryFile(".nc") 23 | exporter = NetCDFExporter(storage, settings, simulation, temp_file.absolute_path) 24 | exporter.run(controller=DummyController()) 25 | 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Arabas_et_al_2015/spin_up.py: -------------------------------------------------------------------------------- 1 | from PySDM.dynamics import Collision, Displacement 2 | 3 | 4 | class SpinUp: 5 | def __init__(self, particulator, spin_up_steps): 6 | self.spin_up_steps = spin_up_steps 7 | particulator.observers.append(self) 8 | self.particulator = particulator 9 | self.set(Collision, "enable", False) 10 | self.set(Displacement, "enable_sedimentation", False) 11 | 12 | def notify(self): 13 | if self.particulator.n_steps == self.spin_up_steps: 14 | self.set(Collision, "enable", True) 15 | self.set(Displacement, "enable_sedimentation", True) 16 | 17 | def set(self, dynamic, attr, value): 18 | key = dynamic.__name__ 19 | if key in self.particulator.dynamics: 20 | setattr(self.particulator.dynamics[key], attr, value) 21 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Arabas_et_al_2025/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | box-model and 2D prescribed-flow immersion-freezing examples based on 3 | [Arabas et al. 2025](https://doi.org/10.1029/2024MS004770) 4 | 5 | aida.ipynb: 6 | .. include:: ./aida.ipynb.badges.md 7 | 8 | copula_hello.ipynb: 9 | .. include:: ./copula_hello.ipynb.badges.md 10 | 11 | fig_2.ipynb: 12 | .. include:: ./fig_2.ipynb.badges.md 13 | 14 | figs_10_and_11_and_animations.ipynb: 15 | .. include:: ./figs_10_and_11_and_animations.ipynb.badges.md 16 | 17 | fig_A2.ipynb: 18 | .. include:: ./fig_A2.ipynb.badges.md 19 | 20 | figs_3_and_7_and_8.ipynb: 21 | .. include:: ./figs_3_and_7_and_8.ipynb.badges.md 22 | 23 | figs_5_and_6.ipynb: 24 | .. include:: ./figs_5_and_6.ipynb.badges.md 25 | """ 26 | 27 | from .make_particulator import make_particulator 28 | from .plots import make_freezing_spec_plot, make_pdf_plot, make_temperature_plot 29 | from .run_simulation import run_simulation 30 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Arabas_et_al_2025/commons.py: -------------------------------------------------------------------------------- 1 | """ABIFM and INAS parameters and other constants""" 2 | 3 | from PySDM import Formulae 4 | from PySDM.physics import si 5 | 6 | FREEZING_CONSTANTS = { 7 | "dust": { 8 | "NIEMAND_A": -0.517, 9 | "NIEMAND_B": 8.934, 10 | "ABIFM_M": 22.62, 11 | "ABIFM_C": -1.35, 12 | }, 13 | "illite": {"ABIFM_M": 54.48, "ABIFM_C": -10.67}, 14 | } 15 | 16 | COOLING_RATES = (-3.75 * si.K / si.min, -0.75 * si.K / si.min, -0.15 * si.K / si.min) 17 | 18 | BEST_FIT_LN_S_GEOM = 0.25 19 | 20 | LOGNORMAL_MODE_SURF_A = Formulae().trivia.sphere_surface(diameter=0.74 * si.um) 21 | LOGNORMAL_SGM_G = 2.55 22 | 23 | TEMP_RANGE = (250 * si.K, 230 * si.K) 24 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Arabas_et_al_2025/frozen_fraction.py: -------------------------------------------------------------------------------- 1 | class FrozenFraction: 2 | def __init__(self, *, volume, droplet_volume, total_particle_number, rho_w): 3 | self.volume = volume 4 | self.rho_w = rho_w 5 | self.droplet_volume = droplet_volume 6 | self.total_particle_number = total_particle_number 7 | 8 | def qi2ff(self, ice_mass_per_volume): 9 | ice_mass = ice_mass_per_volume * self.volume 10 | ice_number = ice_mass / (self.rho_w * self.droplet_volume) 11 | frozen_fraction = ice_number / self.total_particle_number 12 | return frozen_fraction 13 | 14 | def ff2qi(self, frozen_fraction): 15 | ice_number = frozen_fraction * self.total_particle_number 16 | ice_mass = ice_number * (self.rho_w * self.droplet_volume) 17 | ice_mass_per_volume = ice_mass / self.volume 18 | return ice_mass_per_volume 19 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Bartman_2020_MasterThesis/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | Box and parcel examples from [Bartman 2020 MSc thesis](https://www.ap.uj.edu.pl/diplomas/141204) 4 | """ 5 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Bartman_et_al_2021/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | condensation and coalescence adaptivity examples 4 | 5 | demo.ipynb: 6 | .. include:: ./demo.ipynb.badges.md 7 | 8 | demo_fig2.ipynb: 9 | .. include:: ./demo_fig2.ipynb.badges.md 10 | 11 | demo_fig3.ipynb: 12 | .. include:: ./demo_fig3.ipynb.badges.md 13 | """ 14 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Bartman_et_al_2021/label.py: -------------------------------------------------------------------------------- 1 | def label(settings): 2 | lbl = str( 3 | { 4 | k.replace("condensation_", ""): ( 5 | f"{v:.1e}" 6 | if isinstance(v, float) 7 | else str(v).zfill(2) if isinstance(v, int) else v 8 | ) 9 | for k, v in settings.items() 10 | } 11 | ) 12 | return ( 13 | lbl.replace("{", "") 14 | .replace("}", "") 15 | .replace("'", "") 16 | .replace("True", "T") 17 | .replace("False", "F") 18 | .replace("_thd", "$_{th}$") 19 | .replace("e-0", "e-") 20 | ) 21 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Berry_1967/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | box-model coalescence-only example based on 3 | [Berry 1967 (J. Atmos. Sci. 24)](https://doi.org/10.1175/1520-0469(1967)024%3C0688:CDGBC%3E2.0.CO;2) 4 | 5 | figs_5_8_10.ipynb: 6 | .. include:: ./figs_5_8_10.ipynb.badges.md 7 | """ 8 | 9 | # pylint: disable=invalid-name 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Bieli_et_al_2022/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | collision-only box-model example from [Bieli et al. 2022](https://doi.org/10.1029/2022MS002994) 3 | 4 | make_fig_3.ipynb: 5 | .. include:: ./make_fig_3.ipynb.badges.md 6 | """ 7 | 8 | # pylint: disable=invalid-name 9 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Bolin_1958/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Table 1 from [Bolin 1958](https://digitallibrary.un.org/record/3892725) 3 | 4 | table_1.ipynb: 5 | .. include:: ./table_1.ipynb.badges.md 6 | """ 7 | 8 | # pylint: disable=invalid-name 9 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Bolot_et_al_2013/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | figures from Bolot et al. 2013 (Atmos. Chem. Phys. 13) 3 | https://doi.org/10.5194/acp-13-7903-2013 4 | 5 | fig_1.ipynb: 6 | .. include:: ./fig_1.ipynb.badges.md 7 | """ 8 | 9 | # pylint: disable=invalid-name 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Bulenok_2023_MasterThesis/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | Box-model coalescence-breakup performance benchmark from 4 | [Bulenok 2023 MSc thesis](https://www.ap.uj.edu.pl/diplomas/166879) 5 | """ 6 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Ervens_and_Feingold_2012/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | aerosol distribution from 3 | [Ervens and Feingold 2012 (Atmos. Chem. Phys. 12)](https://doi.org/10.5194/acp-12-5807-2012) 4 | """ 5 | 6 | # pylint: disable=invalid-name 7 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Ervens_and_Feingold_2012/settings.py: -------------------------------------------------------------------------------- 1 | from PySDM.initialisation.sampling.spectral_sampling import Logarithmic 2 | from PySDM.initialisation.spectra import Lognormal 3 | from PySDM.physics import si 4 | 5 | 6 | def sampled_ccn_diameter_number_concentration_spectrum( 7 | n_sd: int = 11, size_range: tuple = (0.02 * si.um, 2 * si.um) 8 | ): 9 | return Logarithmic( 10 | spectrum=Lognormal(s_geom=1.4, m_mode=0.04 * si.um, norm_factor=100 / si.cm**3), 11 | size_range=size_range, 12 | ).sample(n_sd) 13 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Fisher_1991/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | based on the [Jouzel and Merlivat 1984](https://doi.org/10.1029/JD089iD07p11749) calculations 4 | of delta_{18O} and delta_D used in d-excess formula and solved 5 | by integration of the eq. (1) in 6 | [Fisher 1991](https://doi.org/10.3402/tellusb.v43i5.15414) and eq. (3) in Jouzel and Merlivat 1984 7 | 8 | 9 | fig_2.ipynb: 10 | .. include:: ./fig_2.ipynb.badges.md 11 | """ 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Gedzelman_and_Arnold_1994/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | Formulae-only example depicting changes in drop and ambient vapour isotopic composition 4 | upon evaporation of rain in subsaturated environment based on 5 | [Gedzelman and Arnold 1994](https://doi.org/10.1029/93JD03518) 6 | 7 | fig_2.ipynb: 8 | .. include:: ./fig_2.ipynb.badges.md 9 | """ 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Gonfiantini_1986/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | Formulae-only example depicting Rayleigh fractionation from a flat surface of water 4 | at different humidities for Oxygen-18 and Deuterium, based on 5 | [Gonfiantini 1986](https://doi.org/10.1016/B978-0-444-42225-5.50008-5) 6 | 7 | fig_3_1.ipynb: 8 | .. include:: ./fig_3_1.ipynb.badges.md 9 | """ 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Grabowski_and_Pawlowska_2023/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | ripening-focused parcel-model example based on 4 | [Grabowski & Pawlowska 2023 (GRL)](https://doi.org/10.1029/2022GL101917) 5 | 6 | figure_1.ipynb: 7 | .. include:: ./figure_1.ipynb.badges.md 8 | 9 | figure_2.ipynb: 10 | .. include:: ./figure_2.ipynb.badges.md 11 | 12 | figure_3.ipynb: 13 | .. include:: ./figure_3.ipynb.badges.md 14 | 15 | figure_4.ipynb: 16 | .. include:: ./figure_4.ipynb.badges.md 17 | 18 | figure_ripening_rate.ipynb: 19 | .. include:: ./figure_ripening_rate.ipynb.badges.md 20 | """ 21 | from .settings import Settings 22 | from .simulation import Simulation 23 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Graf_et_al_2019/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | Parcel-model Condensation,Isotope example 4 | [Graf.et.al 2019](https://doi.org/10.5194/acp-19-747-2019) 5 | 6 | figure_4.ipynb: 7 | .. include:: ./figure_4.ipynb.badges.md 8 | 9 | Table_1.ipynb: 10 | .. include:: ./Table_1.ipynb.badges.md 11 | """ 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Jaruga_and_Pawlowska_2018/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | aqueous-chemistry parcel-model example based on 4 | [Jaruga & Pawlowska 2018 (GMD)](https://doi.org/10.5194/gmd-11-3623-2018) 5 | 6 | fig_2.ipynb: 7 | .. include:: ./fig_2.ipynb.badges.md 8 | 9 | fig_3.ipynb: 10 | .. include:: ./fig_3.ipynb.badges.md 11 | """ 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Jensen_and_Nugent_2017/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fig_1.ipynb: 3 | .. include:: ./Fig_1.ipynb.badges.md 4 | 5 | Fig_3_and_Tab_4_upper_rows.ipynb: 6 | .. include:: ./Fig_3_and_Tab_4_upper_rows.ipynb.badges.md 7 | 8 | Fig_4_and_7_and_Tab_4_bottom_rows.ipynb: 9 | .. include:: ./Fig_4_and_7_and_Tab_4_bottom_rows.ipynb.badges.md 10 | 11 | Fig_5.ipynb: 12 | .. include:: ./Fig_5.ipynb.badges.md 13 | 14 | Fig_6.ipynb: 15 | .. include:: ./Fig_6.ipynb.badges.md 16 | 17 | Fig_8.ipynb: 18 | .. include:: ./Fig_8.ipynb.badges.md 19 | """ 20 | 21 | from .settings import Settings 22 | from .simulation import Simulation 23 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Jouzel_and_Merlivat_1984/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | kinetic fractionation factors used in modelling isotopic effect 4 | in snow formation, replacing saturation fractionation factors with 5 | alpha_{saturation} alpha_{kinetic} 6 | (see [Jouzel and Merlivat 1984](https://doi.org/10.1029/JD089iD07p11749)) 7 | 8 | 9 | fig_8_9.ipynb: 10 | .. include:: ./fig_8_9.ipynb.badges.md 11 | """ 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Kinzer_And_Gunn_1951/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Table 1 and 2 from [Kinzer & Gunn 1951] 3 | (https://doi.org/10.1175/1520-0469(1951)008%3C0071:TETATR%3E2.0.CO;2) 4 | """ 5 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Kreidenweis_et_al_2003/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | aqueous-phase chemistry parcel-model example from 4 | [Kreidenweis et al. 2003 (JGR)](https://doi.org/10.1029/2002JD002697) 5 | 6 | fig_1.ipynb: 7 | .. include:: ./fig_1.ipynb.badges.md 8 | """ 9 | from .settings import Settings 10 | from .simulation import Simulation 11 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Lamb_et_al_2017/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | Formulae-only example depicting equilibrium isotopic fractionation over ice based on 4 | [Lamb et al. 2017](https://doi.org/10.1073/pnas.1618374114) 5 | 6 | fig_4.ipynb: 7 | .. include:: ./fig_4.ipynb.badges.md 8 | """ 9 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Lowe_et_al_2019/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | parcel-model example focused on surfactants based on 4 | [Lowe et al. 2019 (Nature Comm.)](https://doi.org/10.1038/s41467-019-12982-0) 5 | 6 | fig_1.ipynb: 7 | .. include:: ./fig_1.ipynb.badges.md 8 | 9 | fig_2.ipynb: 10 | .. include:: ./fig_2.ipynb.badges.md 11 | 12 | fig_3.ipynb: 13 | .. include:: ./fig_3.ipynb.badges.md 14 | 15 | fig_s2.ipynb: 16 | .. include:: ./fig_s2.ipynb.badges.md 17 | """ 18 | from .settings import Settings 19 | from .simulation import Simulation 20 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Lowe_et_al_2019/constants_def.py: -------------------------------------------------------------------------------- 1 | from PySDM.physics import constants_defaults, si 2 | 3 | LOWE_CONSTS = { 4 | "sgm_org": 40 * si.mN / si.m, 5 | "delta_min": 0.1 6 | * si.nm, # 0.2 in the paper, but 0.1 matches the paper plot fig 1c and 1d 7 | "MAC": 1, 8 | "HAC": 1, 9 | "c_pd": 1006 * si.joule / si.kilogram / si.kelvin, 10 | "g_std": 9.81 * si.metre / si.second**2, 11 | "Md": constants_defaults.R_str / 287.058 * si.joule / si.kelvin / si.kg, 12 | "Mv": constants_defaults.R_str / 461 * si.joule / si.kelvin / si.kg, 13 | } 14 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Merlivat_and_Nief_1967/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | figure from Merlivat and Nief 1967 (Tellus) 3 | https://doi.org/10.3402/tellusa.v19i1.9756 4 | 5 | fig_2.ipynb: 6 | .. include:: ./fig_2.ipynb.badges.md 7 | """ 8 | 9 | # pylint: disable=invalid-name 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Miyake_et_al_1968/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | isotopic adjustment time plot following 4 | [Friedman et al. 1962 (JGR)](https://doi.org/10.1029/JZ067i007p02761) 5 | 6 | fig_19.ipynb: 7 | .. include:: ./fig_19.ipynb.badges.md 8 | """ 9 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Morrison_and_Grabowski_2007/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | prescribed-flow simulation settings data from 4 | [Morrison & Grabowski 2007 (JAS)](https://doi.org/10.1175/JAS3980) 5 | 6 | fig_1.ipynb: 7 | .. include:: ./fig_1.ipynb.badges.md 8 | """ 9 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Niedermeier_et_al_2014/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on [Niedermeier et al. 2014](https://doi.org/10.1002/2013GL058684) 3 | 4 | fig_2.ipynb: 5 | .. include:: ./fig_2.ipynb.badges.md 6 | """ 7 | 8 | from .settings import Settings 9 | from .simulation import Simulation 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Pierchala_et_al_2022/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on Pierchala et al. 2022 (Geochim. Cosmochim. Acta) 3 | https://doi.org/10.1016/j.gca.2022.01.020 4 | 5 | fig_3.ipynb: 6 | .. include:: ./fig_3.ipynb.badges.md 7 | 8 | fig_4.ipynb: 9 | .. include:: ./fig_4.ipynb.badges.md 10 | """ 11 | 12 | # pylint: disable=invalid-name 13 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Pruppacher_and_Rasmussen_1979/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on Pruppacher and Rasmussen 1979 (J. Atmos. Sci.) 3 | https://doi.org/10.1175/1520-0469%281979%29036%3C1255:AWTIOT%3E2.0.CO;2 4 | 5 | fig_1.ipynb: 6 | .. include:: ./fig_1.ipynb.badges.md 7 | 8 | """ 9 | 10 | # pylint: disable=invalid-name 11 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Pyrcel/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | parcel-model example based on the test case from 4 | [Pyrcel package docs](https://pyrcel.readthedocs.io/) 5 | 6 | example_basic_run.ipynb: 7 | .. include:: ./example_basic_run.ipynb.badges.md 8 | """ 9 | from .settings import Settings 10 | from .simulation import Simulation 11 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Rogers_1975/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on Rogers 1975 (Atmosphere) 3 | https://doi.org/10.1080/00046973.1975.9648397 4 | 5 | fig_1.ipynb: 6 | .. include:: ./fig_1.ipynb.badges.md 7 | """ 8 | 9 | # pylint: disable=invalid-name 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Rozanski_and_Sonntag_1982/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on Rozanski and Sonntag 1982 (Tellus) 3 | https://doi.org/10.3402/tellusa.v34i2.10795 4 | 5 | figs_4_5_6.ipynb: 6 | .. include:: ./figs_4_5_6.ipynb.badges.md 7 | """ 8 | 9 | # pylint: disable=invalid-name 10 | from PySDM_examples.Rozanski_and_Sonntag_1982.multibox import MultiBox 11 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Shima_et_al_2009/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | box-model coalescence-only example featuring Golovin analytic solution following setup from 4 | [Shima et al. 2009](https://doi.org/10.1002/qj.441) 5 | 6 | fig_2.ipynb: 7 | .. include:: ./fig_2.ipynb.badges.md 8 | """ 9 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Shima_et_al_2009/error_measure.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def error_measure(y, y_true, x): 5 | errors = y_true - y 6 | errors = errors[0:-1] + errors[1:] 7 | dx = np.diff(x) 8 | errors *= dx 9 | errors /= 2 10 | error = np.sum(np.abs(errors)) 11 | return error 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Shipway_and_Hill_2012/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | single-column prescribed-flow constant-temperature example from 4 | [Shipway & Hill 2012](https://doi.org/10.1002/qj.1913) 5 | 6 | fig_1.ipynb: 7 | .. include:: ./fig_1.ipynb.badges.md 8 | """ 9 | from .plot import plot 10 | from .settings import Settings 11 | from .simulation import Simulation 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Singer_Ward/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | kohler.ipynb: 3 | .. include:: ./kohler.ipynb.badges.md 4 | 5 | MWE_joss_paper.ipynb: 6 | .. include:: ./MWE_joss_paper.ipynb.badges.md 7 | """ 8 | 9 | # pylint: disable=invalid-name 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Srivastava_1982/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | box-model examples feat. analytic solution for breakup from 3 | [Srivastava 1982 (JAS)](https://doi.org/10.1175/1520-0469(1982)039%3C1317:ASMOPC%3E2.0.CO;2) 4 | 5 | figures.ipynb: 6 | .. include:: ./figures.ipynb.badges.md 7 | """ 8 | 9 | from .equations import Equations, EquationsHelpers 10 | from .example import ( 11 | add_to_plot_simulation_results, 12 | coalescence_and_breakup_eq13, 13 | compute_log_space, 14 | get_coalescence_analytic_results, 15 | get_processed_results, 16 | get_pysdm_secondary_products, 17 | ) 18 | from .settings import Settings, SimProducts 19 | from .simulation import Simulation 20 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Stewart_1975/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | based on Stewart 1975 (J. Geophys. Res.) 3 | https://doi.org/10.1029/JC080i009p01133 4 | 5 | fig_1.ipynb: 6 | .. include:: ./fig_1.ipynb.badges.md 7 | """ 8 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Toon_et_al_1980/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/examples/PySDM_examples/Toon_et_al_1980/__init__.py -------------------------------------------------------------------------------- /examples/PySDM_examples/Van_Hook_1968/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | figure from Van Hook 1968 (J. Phys. Chem.) 3 | https://doi.org/10.1021/j100850a028 4 | 5 | fig_1.ipynb: 6 | .. include:: ./fig_1.ipynb.badges.md 7 | """ 8 | 9 | # pylint: disable=invalid-name 10 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Yang_et_al_2018/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | parcel-model condensation-evaporation example based on 4 | [Yang et al. 2018 (ACP)](https://doi.org/10.5194/acp-18-7313-2018) 5 | 6 | fig_2.ipynb: 7 | .. include:: ./fig_2.ipynb.badges.md 8 | """ 9 | from .settings import Settings 10 | from .simulation import Simulation 11 | -------------------------------------------------------------------------------- /examples/PySDM_examples/Zaba_et_al_2025/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/examples/PySDM_examples/Zaba_et_al_2025/__init__.py -------------------------------------------------------------------------------- /examples/PySDM_examples/_HOWTOs/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | paraview_hello_world.ipynb: 3 | .. include:: ./paraview_hello_world.ipynb.badges.md 4 | 5 | dimensional_analysis.ipynb: 6 | .. include:: ./dimensional_analysis.ipynb.badges.md 7 | """ 8 | -------------------------------------------------------------------------------- /examples/PySDM_examples/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. include:: ../docs/pysdm_examples_landing.md 3 | """ 4 | 5 | from importlib.metadata import PackageNotFoundError, version 6 | 7 | try: 8 | __version__ = version(__name__) 9 | except PackageNotFoundError: 10 | # package is not installed 11 | pass 12 | -------------------------------------------------------------------------------- /examples/PySDM_examples/deJong_Azimi/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | box- and single-column coalescence-focused examples used to test new 3 | moment-based microphysics in (Cloudy.jl)[https://github.com/CliMA/Cloudy.jl] 4 | 5 | box.ipynb: 6 | .. include:: ./box.ipynb.badges.md 7 | 8 | rainshaft.ipynb: 9 | .. include:: ./rainshaft.ipynb.badges.md 10 | """ 11 | 12 | # pylint: disable=invalid-name 13 | from .settings1D import Settings1D 14 | from .simulation_0D import run_box 15 | -------------------------------------------------------------------------------- /examples/PySDM_examples/deJong_Mackay_et_al_2023/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | box- and single-column breakup-focused examples from 3 | [de Jong et al. 2023](https://doi.org/10.5194/gmd-16-4193-2023) 4 | 5 | fig_9.ipynb: 6 | .. include:: ./fig_9.ipynb.badges.md 7 | 8 | figs_3_4_5.ipynb: 9 | .. include:: ./figs_3_4_5.ipynb.badges.md 10 | 11 | figs_6_7_8.ipynb: 12 | .. include:: ./figs_6_7_8.ipynb.badges.md 13 | 14 | figs_10_11_12_13.ipynb: 15 | .. include:: ./figs_10_11_12_13.ipynb.badges.md 16 | """ 17 | 18 | # pylint: disable=invalid-name 19 | from .plot_rates import plot_ax, plot_zeros_ax 20 | from .settings1D import Settings1D 21 | from .settings_0D import Settings0D 22 | from .simulation1D import Simulation1D 23 | from .simulation_0D import run_box_breakup, run_box_NObreakup 24 | from .simulation_ss import ( 25 | get_straub_fig10_data, 26 | get_straub_fig10_init, 27 | run_to_steady_state, 28 | ) 29 | -------------------------------------------------------------------------------- /examples/PySDM_examples/seeding/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | hello_world.ipynb: 3 | .. include:: ./hello_world.ipynb.badges.md 4 | 5 | seeding_no_collisions.ipynb: 6 | .. include:: ./seeding_no_collisions.ipynb.badges.md 7 | """ 8 | 9 | from .settings import Settings 10 | from .simulation import Simulation 11 | -------------------------------------------------------------------------------- /examples/PySDM_examples/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | reusable commons for examples 3 | """ 4 | 5 | from .basic_simulation import BasicSimulation 6 | from .dummy_controller import DummyController 7 | from .progbar_controller import ProgBarController 8 | from .read_vtk_1d import readVTK_1d 9 | -------------------------------------------------------------------------------- /examples/PySDM_examples/utils/basic_simulation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class BasicSimulation: 5 | def __init__(self, particulator): 6 | self.particulator = particulator 7 | 8 | def _save(self, output): 9 | for k, v in self.particulator.products.items(): 10 | value = v.get() 11 | if isinstance(value, np.ndarray) and value.shape[0] == 1: 12 | value = value[0] 13 | output[k].append(value) 14 | 15 | def _run(self, nt, steps_per_output_interval): 16 | output = {k: [] for k in self.particulator.products} 17 | self._save(output) 18 | for _ in range(0, nt + 1, steps_per_output_interval): 19 | self.particulator.run(steps=steps_per_output_interval) 20 | self._save(output) 21 | return output 22 | -------------------------------------------------------------------------------- /examples/PySDM_examples/utils/dummy_controller.py: -------------------------------------------------------------------------------- 1 | from contextlib import AbstractContextManager 2 | 3 | from PySDM.products import CPUTime, WallTime 4 | 5 | 6 | class DummyController(AbstractContextManager): 7 | def __init__(self): 8 | self.panic = False 9 | self.t_last = self.__times() 10 | 11 | @staticmethod 12 | def __times(): 13 | return WallTime.clock(), CPUTime.clock() 14 | 15 | def set_percent(self, value): 16 | t_curr = self.__times() 17 | wall_time = t_curr[0] - self.t_last[0] 18 | cpu_time = t_curr[1] - self.t_last[1] 19 | print( 20 | f"{100 * value:.1f}%" 21 | f" (times since last print: cpu={cpu_time:.1f}s wall={wall_time:.1f}s)" 22 | ) 23 | self.t_last = self.__times() 24 | 25 | def __enter__(self, *_): 26 | pass 27 | 28 | def __exit__(self, *_): 29 | pass 30 | -------------------------------------------------------------------------------- /examples/PySDM_examples/utils/progbar_controller.py: -------------------------------------------------------------------------------- 1 | from PySDM_examples.utils.widgets import FloatProgress, display 2 | 3 | 4 | class ProgBarController: 5 | def __init__(self, description=""): 6 | self.progress = FloatProgress( 7 | value=0.0, min=0.0, max=1.0, description=description 8 | ) 9 | self.panic = False 10 | 11 | def __enter__(self): 12 | self.set_percent(0) 13 | display(self.progress) 14 | 15 | def __exit__(self, *_): 16 | pass 17 | 18 | def set_percent(self, value): 19 | self.progress.value = value 20 | -------------------------------------------------------------------------------- /examples/PySDM_examples/utils/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from IPython.display import FileLink, clear_output, display 4 | from ipywidgets import ( 5 | HTML, 6 | Accordion, 7 | Box, 8 | Button, 9 | Checkbox, 10 | Dropdown, 11 | FloatProgress, 12 | FloatSlider, 13 | HBox, 14 | IntProgress, 15 | IntRangeSlider, 16 | IntSlider, 17 | Layout, 18 | Output, 19 | Play, 20 | RadioButtons, 21 | Select, 22 | Tab, 23 | VBox, 24 | interactive_output, 25 | jslink, 26 | ) 27 | from PySDM_examples.utils.widgets.freezer import Freezer 28 | from PySDM_examples.utils.widgets.progbar_updater import ProgbarUpdater 29 | -------------------------------------------------------------------------------- /examples/PySDM_examples/utils/widgets/freezer.py: -------------------------------------------------------------------------------- 1 | class Freezer: 2 | def __init__(self, widgets): 3 | self.widgets = widgets 4 | 5 | def observe(self, *_): 6 | pass 7 | 8 | @property 9 | def value(self): 10 | return self 11 | 12 | def __enter__(self): 13 | for widget in self.widgets: 14 | widget.disabled = True 15 | return self 16 | 17 | def __exit__(self, *args, **kwargs): 18 | for widget in self.widgets: 19 | widget.disabled = False 20 | -------------------------------------------------------------------------------- /examples/PySDM_examples/utils/widgets/progbar_updater.py: -------------------------------------------------------------------------------- 1 | class ProgbarUpdater: 2 | def __init__(self, progbar, max_steps): 3 | self.max_steps = max_steps 4 | self.steps = 0 5 | self.progbar = progbar 6 | 7 | def notify(self): 8 | self.steps += 1 9 | self.progbar.value = 100 * (self.steps / self.max_steps) 10 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.html) 2 | [![DOI](https://zenodo.org/badge/199064632.svg)](https://zenodo.org/badge/latestdoi/199064632) 3 | 4 | [![PyPI version](https://badge.fury.io/py/PySDM-examples.svg)](https://pypi.org/project/PySDM-examples) 5 | [![API docs](https://img.shields.io/badge/API_docs-pdoc3-blue.svg)](https://open-atmos.github.io/PySDM-examples/) 6 | 7 | For a list of examples, see [PySDM-examples documentation](https://open-atmos.github.io/PySDM/PySDM_examples.html). 8 | 9 | For information on package development, see [PySDM README](https://github.com/open-atmos/PySDM/blob/main/README.md). 10 | 11 | -------------------------------------------------------------------------------- /examples/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.setuptools_scm] 2 | root = ".." 3 | local_scheme = "no-local-version" 4 | version_scheme = "post-release" 5 | 6 | [build-system] 7 | requires = ['setuptools==80.9.0', 'setuptools-scm==8.3.1'] 8 | -------------------------------------------------------------------------------- /paper/joss-ARG-fig_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/paper/joss-ARG-fig_1.pdf -------------------------------------------------------------------------------- /paper/readme.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/paper/readme.pdf -------------------------------------------------------------------------------- /paper/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/paper/test.pdf -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/__init__.py -------------------------------------------------------------------------------- /tests/examples_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/examples_tests/__init__.py -------------------------------------------------------------------------------- /tests/examples_tests/test_run_examples.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring 2 | import pathlib 3 | 4 | 5 | def test_run_examples(example_filename): 6 | if pathlib.Path(example_filename).name == "__init__.py": 7 | return 8 | with open(example_filename, encoding="utf8") as f: 9 | code = f.read() 10 | if not code.startswith("#!/usr/bin/env pvpython"): 11 | exec(code, {"__name__": "__main__"}) # pylint: disable=exec-used 12 | -------------------------------------------------------------------------------- /tests/examples_tests/test_run_notebooks.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring 2 | from ..devops_tests.test_notebooks import test_run_notebooks as _impl 3 | 4 | 5 | def test_run_notebooks(notebook_filename, tmp_path): 6 | _impl(notebook_filename, tmp_path) 7 | -------------------------------------------------------------------------------- /tests/smoke_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/box/alpert_and_knopf_2016/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/box/alpert_and_knopf_2016/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/box/alpert_and_knopf_2016/test_frozen_fraction.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | import numpy as np 3 | from PySDM_examples.Arabas_et_al_2025.frozen_fraction import FrozenFraction 4 | 5 | from PySDM.physics import constants_defaults as const 6 | 7 | TOTAL_PARTICLE_NUMBER = 1 8 | DROPLET_VOLUME = 1 9 | VOLUME = 1 10 | FF = FrozenFraction( 11 | volume=VOLUME, 12 | total_particle_number=TOTAL_PARTICLE_NUMBER, 13 | droplet_volume=DROPLET_VOLUME, 14 | rho_w=const.rho_w, 15 | ) 16 | 17 | 18 | class TestFrozenFraction: 19 | @staticmethod 20 | def test_qi2ff(): 21 | all_frozen = FF.qi2ff( 22 | TOTAL_PARTICLE_NUMBER * DROPLET_VOLUME * const.rho_w / VOLUME 23 | ) 24 | np.testing.assert_almost_equal(all_frozen, 1) 25 | 26 | @staticmethod 27 | def test_ff2qi(): 28 | assert FF.qi2ff(FF.ff2qi(1)) == 1 29 | -------------------------------------------------------------------------------- /tests/smoke_tests/box/berry_1967/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/box/berry_1967/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/box/bieli_et_al_2022/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/box/bieli_et_al_2022/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/box/dejong_and_mackay_et_al_2023/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/box/dejong_and_mackay_et_al_2023/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/box/partmc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/box/partmc/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/box/shima_et_al_2009/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/box/shima_et_al_2009/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/box/srivastava_1982/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/box/srivastava_1982/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/conftest.py: -------------------------------------------------------------------------------- 1 | """borrowing everything from unit_tests (this cannot be one level up due to devops_tests)""" 2 | 3 | import pytest 4 | 5 | from ..unit_tests.conftest import backend_class 6 | 7 | pytest.fixture(backend_class.__wrapped__) 8 | -------------------------------------------------------------------------------- /tests/smoke_tests/kinematic_1d/deJong_Azimi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/kinematic_1d/deJong_Azimi/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/kinematic_1d/shipway_and_hill_2012/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/kinematic_1d/shipway_and_hill_2012/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/kinematic_2d/arabas_et_al_2015/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/kinematic_2d/arabas_et_al_2015/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/kinematic_2d/arabas_et_al_2015/dummy_storage.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | import numpy as np 3 | 4 | 5 | class DummyStorage: 6 | def __init__(self): 7 | self.profiles = [] 8 | 9 | def init(*_): # pylint: disable=no-method-argument,no-self-argument 10 | pass 11 | 12 | def save( 13 | self, data: np.ndarray, step: int, name: str 14 | ): # pylint: disable=unused-argument 15 | if name == "water_vapour_mixing_ratio_env": 16 | self.profiles.append( 17 | {"water_vapour_mixing_ratio_env": np.mean(data, axis=0)} 18 | ) 19 | -------------------------------------------------------------------------------- /tests/smoke_tests/kinematic_2d/arabas_et_al_2015/test_gui_settings.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | from PySDM_examples.Arabas_et_al_2015 import Settings 3 | from PySDM_examples.utils.kinematic_2d.gui_settings import GUISettings 4 | 5 | 6 | class TestGUISettings: 7 | @staticmethod 8 | def test_instantiate(): 9 | _ = GUISettings(Settings()) 10 | 11 | @staticmethod 12 | def test_stream_function(): 13 | # arrange 14 | gui_settings = GUISettings(Settings()) 15 | gui_settings.ui_rhod_w_max = None 16 | failed = False 17 | 18 | # act 19 | try: 20 | _ = gui_settings.stream_function(0, 0, 0) 21 | except AttributeError: 22 | failed = True 23 | 24 | # assert 25 | assert failed 26 | -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/bolin_1958/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/bolin_1958/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/gedzelman_and_arnold_1994/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/gedzelman_and_arnold_1994/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/gonfiantini_1986/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/gonfiantini_1986/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/jouzel_and_merlivat_1984/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/jouzel_and_merlivat_1984/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/kinzer_and_gunn_1951/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/kinzer_and_gunn_1951/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/kinzer_and_gunn_1951/test_table_1_and_2.py: -------------------------------------------------------------------------------- 1 | """ 2 | test if values in table1 (table2) are increasing (decreasing) in each column 3 | """ 4 | 5 | import numpy as np 6 | import pytest 7 | 8 | from PySDM_examples.Kinzer_And_Gunn_1951.table_1_and_2 import table1, table2 9 | 10 | 11 | @pytest.mark.parametrize("temperature", (0, 20, 30, 40)) 12 | @pytest.mark.parametrize("table, slope_sign", ((table1, 1), (table2, -1))) 13 | def test_table_1_monotonicity(table, temperature, slope_sign): 14 | # Arrange 15 | values = np.array([x for x in table[f"{temperature} [deg C]"] if x != 0]) 16 | 17 | # Act 18 | differences = values[1:] - values[:-1] 19 | signs = np.sign(differences) 20 | 21 | # Assert 22 | np.testing.assert_equal(actual=signs, desired=slope_sign) 23 | -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/lamb_et_al_2017/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/lamb_et_al_2017/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/miyake_et_al_1968/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/miyake_et_al_1968/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/pierchala_et_al_2022/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/pierchala_et_al_2022/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/pruppacher_and_rasmussen_1979/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/pruppacher_and_rasmussen_1979/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/stewart_1975/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/stewart_1975/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/toon_et_al_1980/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/toon_et_al_1980/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/no_env/zaba_et_al_2025/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/no_env/zaba_et_al_2025/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_a/lowe_et_al_2019/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_a/lowe_et_al_2019/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_a/lowe_et_al_2019/conftest.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | import numpy as np 3 | import pytest 4 | 5 | from PySDM.physics import si 6 | from PySDM.physics.surface_tension import compressed_film_ovadnevaite 7 | 8 | 9 | @pytest.fixture(name="constants") 10 | def constants_fixture(): 11 | compressed_film_ovadnevaite.sgm_org = 40 * si.mN / si.m 12 | # TODO #1247 0.2 in the paper, but 0.1 matches the paper plots 13 | compressed_film_ovadnevaite.delta_min = 0.1 * si.nm 14 | 15 | yield 16 | 17 | compressed_film_ovadnevaite.sgm_org = np.nan 18 | compressed_film_ovadnevaite.delta_min = np.nan 19 | -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_a/pyrcel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_a/pyrcel/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_b/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_b/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_b/arabas_and_shima_2017/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_b/arabas_and_shima_2017/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_b/arabas_and_shima_2017/test_displacement.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | import numpy as np 3 | import pytest 4 | from PySDM_examples.Arabas_and_Shima_2017.settings import Settings, w_avgs 5 | from PySDM_examples.Arabas_and_Shima_2017.simulation import Simulation 6 | from PySDM.physics import si 7 | 8 | 9 | @pytest.mark.parametrize("w_idx", range(len(w_avgs))) 10 | def test_displacement(w_idx): 11 | # Arrange 12 | settings = Settings( 13 | w_avg=w_avgs[w_idx], 14 | N_STP=44 / si.cm**3, 15 | r_dry=0.1 * si.um, 16 | mass_of_dry_air=1 * si.kg, 17 | ) 18 | settings.n_output = 50 19 | simulation = Simulation(settings) 20 | 21 | # Act 22 | output = simulation.run() 23 | 24 | # Assert 25 | np.testing.assert_almost_equal(min(output["z"]), 0, decimal=1) 26 | np.testing.assert_almost_equal(max(output["z"]), settings.z_half, decimal=1) 27 | -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_c/abade_and_albuquerque_2024/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_c/abade_and_albuquerque_2024/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_c/abdul_razzak_ghan_2000/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_c/abdul_razzak_ghan_2000/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_c/abdul_razzak_ghan_2000/test_just_do_it.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | from PySDM_examples.Abdul_Razzak_Ghan_2000.run_ARG_parcel import run_parcel 3 | 4 | from PySDM.physics import si 5 | 6 | 7 | def test_just_do_it(): 8 | # act 9 | output = run_parcel( 10 | w=1 * si.m / si.s, 11 | sol2=0.5, 12 | N2=100 / si.cm**3, 13 | rad2=100 * si.nm, 14 | n_sd_per_mode=10, 15 | ) 16 | 17 | # assert 18 | assert (output.activated_fraction_S[:] <= 1).all() 19 | -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_c/grabowski_and_pawlowska_2023/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_c/grabowski_and_pawlowska_2023/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_d/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/graf_et_al_2019/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_d/graf_et_al_2019/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/graf_et_al_2019/test_fig_4.py: -------------------------------------------------------------------------------- 1 | """tests ensuring values on plots match those in the paper""" 2 | 3 | from pathlib import Path 4 | 5 | import pytest 6 | 7 | from open_atmos_jupyter_utils import notebook_vars 8 | from PySDM_examples import Graf_et_al_2019 9 | 10 | from PySDM.physics import si 11 | 12 | PLOT = False 13 | 14 | 15 | @pytest.fixture(scope="session", name="variables") 16 | def variables_fixture(): 17 | return notebook_vars( 18 | file=Path(Graf_et_al_2019.__file__).parent / "figure_4.ipynb", plot=PLOT 19 | ) 20 | 21 | 22 | @pytest.mark.parametrize( 23 | "level_name, expected_height", (("CB", 1 * si.km), ("0C", 2.24 * si.km)) 24 | ) 25 | def test_fig_4(variables, level_name, expected_height): 26 | # arrange 27 | tolerance = 50 * si.m 28 | 29 | # act 30 | actual = variables["levels"][level_name] + variables["alt_initial"] 31 | 32 | # assert 33 | assert abs(expected_height - actual) < tolerance 34 | -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/jensen_and_nugent_2017/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_d/jensen_and_nugent_2017/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/jensen_and_nugent_2017/test_table_3.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | 3 | from PySDM_examples.Jensen_and_Nugent_2017.table_3 import RD, NA 4 | import numpy as np 5 | from PySDM.physics import si 6 | from PySDM import Formulae 7 | 8 | 9 | TRIVIA = Formulae().trivia 10 | 11 | # from Wikipedia 12 | RHO_S = 2.17 * si.g / si.cm**3 13 | 14 | 15 | class TestTable3: 16 | @staticmethod 17 | def test_number_integral(): 18 | np.testing.assert_approx_equal( 19 | actual=np.sum(NA), desired=281700 / si.m**3, significant=4 20 | ) 21 | 22 | @staticmethod 23 | def test_mass_integral(): 24 | total_mass_concentration = np.dot(TRIVIA.volume(radius=RD), NA) * RHO_S 25 | np.testing.assert_approx_equal( 26 | actual=total_mass_concentration, 27 | desired=7.3 * si.ug / si.m**3, 28 | significant=2, 29 | ) 30 | -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/kreidenweis_et_al_2003/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_d/kreidenweis_et_al_2003/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/niedermeier_et_al_2013/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_d/niedermeier_et_al_2013/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/rozanski_and_sonntag_1982/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_d/rozanski_and_sonntag_1982/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/seeding/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_d/seeding/__init__.py -------------------------------------------------------------------------------- /tests/smoke_tests/parcel_d/yang_et_al_2018/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/smoke_tests/parcel_d/yang_et_al_2018/__init__.py -------------------------------------------------------------------------------- /tests/tutorials_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/tutorials_tests/__init__.py -------------------------------------------------------------------------------- /tests/tutorials_tests/conftest.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring 2 | import pathlib 3 | 4 | import pytest 5 | 6 | from ..examples_tests.conftest import findfiles 7 | 8 | PYSDM_TUTORIALS_ABS_PATH = ( 9 | pathlib.Path(__file__).parent.parent.parent.absolute().joinpath("tutorials") 10 | ) 11 | 12 | 13 | @pytest.fixture( 14 | params=(path for path in findfiles(PYSDM_TUTORIALS_ABS_PATH, r".*\.(ipynb)$")), 15 | ) 16 | def notebook_filename(request): 17 | return request.param 18 | -------------------------------------------------------------------------------- /tests/tutorials_tests/test_run_notebooks.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring 2 | from ..devops_tests.test_notebooks import test_run_notebooks as _impl 3 | 4 | 5 | def test_run_notebooks(notebook_filename, tmp_path): 6 | _impl(notebook_filename, tmp_path) 7 | -------------------------------------------------------------------------------- /tests/unit_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/attributes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/attributes/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/backends/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/backends/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/backends/storage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/backends/storage/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/backends/storage/test_setitem.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | import numpy as np 3 | import pytest 4 | 5 | from PySDM.backends import CPU, GPU 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "backend", (pytest.param(GPU, marks=pytest.mark.xfail(strict=True)), CPU) 10 | ) 11 | def test_setitem(backend): 12 | # arrange 13 | arr = backend.Storage.from_ndarray(np.zeros(3)) 14 | 15 | # act 16 | arr[1] = 1 17 | 18 | # assert 19 | assert arr[1] == 1 20 | assert arr[0] == arr[2] == 0 21 | -------------------------------------------------------------------------------- /tests/unit_tests/backends/test_ctor_defaults.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | import inspect 3 | 4 | from PySDM.backends import CPU, GPU 5 | 6 | 7 | class TestCtorDefaults: 8 | @staticmethod 9 | def test_gpu_ctor_defaults(): 10 | signature = inspect.signature(GPU.__init__) 11 | assert signature.parameters["verbose"].default is False 12 | assert signature.parameters["debug"].default is False 13 | assert signature.parameters["double_precision"].default is False 14 | assert signature.parameters["formulae"].default is None 15 | 16 | @staticmethod 17 | def test_cpu_ctor_defaults(): 18 | signature = inspect.signature(CPU.__init__) 19 | assert signature.parameters["formulae"].default is None 20 | -------------------------------------------------------------------------------- /tests/unit_tests/backends/test_fake_thrust.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | from PySDM.backends.impl_thrust_rtc.test_helpers.fake_thrust_rtc import FakeThrustRTC 3 | 4 | 5 | def test_device_vector_fails_on_zero_size(): 6 | # arrange 7 | sut = FakeThrustRTC.device_vector 8 | 9 | # act 10 | exception = None 11 | try: 12 | sut("float", size=0) 13 | except ValueError as caught: 14 | exception = caught 15 | 16 | # assert 17 | assert exception is not None 18 | -------------------------------------------------------------------------------- /tests/unit_tests/backends/test_isotope_methods.py: -------------------------------------------------------------------------------- 1 | """ 2 | unit tests for backend isotope-related routines 3 | """ 4 | 5 | import numpy as np 6 | 7 | 8 | class TestIsotopeMethods: 9 | @staticmethod 10 | def test_isotopic_fractionation(backend_instance): 11 | # arrange 12 | backend = backend_instance 13 | 14 | # act 15 | backend.isotopic_fractionation() 16 | 17 | @staticmethod 18 | def test_isotopic_delta(backend_instance): 19 | # arrange 20 | backend = backend_instance 21 | arr2storage = backend.Storage.from_ndarray 22 | n_sd = 10 23 | output = arr2storage(np.empty(n_sd)) 24 | ratio = arr2storage(np.zeros(n_sd)) 25 | 26 | # act 27 | backend.isotopic_delta(output=output, ratio=ratio, reference_ratio=0.0001) 28 | 29 | # assert 30 | assert (output.to_ndarray() == -1).all() 31 | -------------------------------------------------------------------------------- /tests/unit_tests/conftest.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | import pytest 3 | 4 | from PySDM.backends import CPU, GPU 5 | 6 | 7 | @pytest.fixture(params=(CPU, GPU)) 8 | def backend_class(request): 9 | return request.param 10 | 11 | 12 | @pytest.fixture( 13 | params=(pytest.param(CPU(), id="CPU"), pytest.param(GPU(), id="GPU")), 14 | scope="session", 15 | ) 16 | def backend_instance(request): 17 | return request.param 18 | -------------------------------------------------------------------------------- /tests/unit_tests/dummy_particulator.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | from PySDM.builder import Builder 3 | from PySDM.particulator import Particulator 4 | 5 | from .dummy_environment import DummyEnvironment 6 | 7 | 8 | class DummyParticulator(Builder, Particulator): 9 | def __init__(self, backend_class, n_sd=0, formulae=None, grid=None): 10 | backend = backend_class(formulae, double_precision=True) 11 | env = DummyEnvironment(grid=grid) 12 | Builder.__init__(self, n_sd, backend, env) 13 | Particulator.__init__(self, n_sd, backend) 14 | self.environment = env.instantiate(builder=self) # pylint: disable=no-member 15 | self.particulator = self 16 | self.req_attr_names = ["multiplicity", "cell id"] 17 | self.attributes = None 18 | -------------------------------------------------------------------------------- /tests/unit_tests/dynamics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/dynamics/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/dynamics/collisions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/dynamics/collisions/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/dynamics/collisions/test_defaults.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring 2 | import inspect 3 | 4 | import pytest 5 | 6 | from PySDM.dynamics.collisions import Breakup, Coalescence, Collision 7 | 8 | 9 | def get_default_args(func): 10 | signature = inspect.signature(func) 11 | return { 12 | k: v.default 13 | for k, v in signature.parameters.items() 14 | if v.default is not inspect.Parameter.empty 15 | } 16 | 17 | 18 | class TestDefaults: # pylint: disable=too-few-public-methods 19 | @staticmethod 20 | @pytest.mark.parametrize("dynamic_class", (Collision, Breakup, Coalescence)) 21 | def test_collision_adaptive_default(dynamic_class): 22 | assert get_default_args(dynamic_class.__init__)["adaptive"] is True 23 | -------------------------------------------------------------------------------- /tests/unit_tests/dynamics/displacement/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/dynamics/displacement/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/environments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/environments/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/exporters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/exporters/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/impl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/impl/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/impl/test_camel_case.py: -------------------------------------------------------------------------------- 1 | """ 2 | test checking CamelCase conversion routine 3 | """ 4 | 5 | from typing import Tuple 6 | import pytest 7 | from PySDM.impl.camel_case import camel_case_to_words 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "in_out_pair", (("CPUTime", "CPU time"), ("WallTime", "wall time")) 12 | ) 13 | def test_camel_case_to_words(in_out_pair: Tuple[str, str]): 14 | # arrange 15 | test_input, expected_output = in_out_pair 16 | 17 | # act 18 | actual_output = camel_case_to_words(test_input) 19 | 20 | # assert 21 | assert actual_output == expected_output 22 | -------------------------------------------------------------------------------- /tests/unit_tests/initialisation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/initialisation/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/physics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/physics/__init__.py -------------------------------------------------------------------------------- /tests/unit_tests/physics/test_fake_unit_registry.py: -------------------------------------------------------------------------------- 1 | """checks for SI units conversions in FakeUnitRegistry""" 2 | 3 | from PySDM.physics import si 4 | 5 | 6 | class TestFakeUnitRegistry: 7 | @staticmethod 8 | def test_d(): 9 | """a check for the 'd' prefix""" 10 | assert 44 * si.dm == 440 * si.cm 11 | 12 | @staticmethod 13 | def test_deci(): 14 | """a check for the 'deci' prefix""" 15 | assert 44 * si.decimetre == 440 * si.centimetre 16 | -------------------------------------------------------------------------------- /tests/unit_tests/products/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-atmos/PySDM/c256737f19eeb34f642e6fb85e54b5f87f346831/tests/unit_tests/products/__init__.py --------------------------------------------------------------------------------