Ejemplo n.º 1
0
def test_ambient_relative_humidity(backend_class):
    # arrange
    n_sd = 1
    builder = Builder(n_sd, backend=backend_class())
    env = Parcel(dt=np.nan,
                 mixed_phase=True,
                 mass_of_dry_air=np.nan,
                 p0=1000 * si.hPa,
                 q0=1 * si.g / si.kg,
                 T0=260 * si.K,
                 w=np.nan)
    builder.set_environment(env)
    attributes = {'n': np.ones(n_sd), 'volume': np.ones(n_sd)}
    particulator = builder.build(
        attributes=attributes,
        products=(
            products.AmbientRelativeHumidity(name='RHw', var='RH'),
            products.AmbientRelativeHumidity(name='RHi', var='RH', ice=True),
        ))

    # act
    values = {}
    for name, product in particulator.products.items():
        values[name] = product.get()[0]

    # assert
    assert values['RHw'] < values['RHi']
Ejemplo n.º 2
0
    def __init__(self, settings, backend=CPU):
        t_half = settings.z_half / settings.w_avg

        dt_output = (2 * t_half) / settings.n_output
        self.n_substeps = 1
        while dt_output / self.n_substeps >= settings.dt_max:  # TODO #334 dt_max
            self.n_substeps += 1

        builder = Builder(backend=backend(formulae=settings.formulae), n_sd=1)
        builder.set_environment(
            Parcel(
                dt=dt_output / self.n_substeps,
                mass_of_dry_air=settings.mass_of_dry_air,
                p0=settings.p0,
                q0=settings.q0,
                T0=settings.T0,
                w=settings.w,
            ))

        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(
            Condensation(
                rtol_x=settings.rtol_x,
                rtol_thd=settings.rtol_thd,
                dt_cond_range=settings.dt_cond_range,
            ))
        attributes = {}
        r_dry = np.array([settings.r_dry])
        attributes["dry volume"] = settings.formulae.trivia.volume(
            radius=r_dry)
        attributes["kappa times dry volume"] = attributes[
            "dry volume"] * settings.kappa
        attributes["n"] = np.array([settings.n_in_dv], dtype=np.int64)
        environment = builder.particulator.environment
        r_wet = equilibrate_wet_radii(
            r_dry=r_dry,
            environment=environment,
            kappa_times_dry_volume=attributes["kappa times dry volume"],
        )
        attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet)
        products = [
            PySDM_products.MeanRadius(name="radius_m1", unit="um"),
            PySDM_products.CondensationTimestepMin(name="dt_cond_min"),
            PySDM_products.ParcelDisplacement(name="z"),
            PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"),
            PySDM_products.Time(name="t"),
            PySDM_products.ActivatingRate(unit="s^-1 mg^-1",
                                          name="activating_rate"),
            PySDM_products.DeactivatingRate(unit="s^-1 mg^-1",
                                            name="deactivating_rate"),
            PySDM_products.RipeningRate(unit="s^-1 mg^-1",
                                        name="ripening_rate"),
            PySDM_products.PeakSupersaturation(unit="%", name="S_max"),
        ]

        self.particulator = builder.build(attributes, products)

        self.n_output = settings.n_output
Ejemplo n.º 3
0
    def __init__(self, settings, backend=CPU):
        dt_output = (settings.total_time / settings.n_steps
                     )  # TODO #334 overwritten in notebook
        self.n_substeps = 1  # TODO #334 use condensation substeps
        while dt_output / self.n_substeps >= settings.dt_max:
            self.n_substeps += 1
        self.formulae = Formulae(
            condensation_coordinate=settings.coord,
            saturation_vapour_pressure="AugustRocheMagnus",
        )
        self.bins_edges = self.formulae.trivia.volume(settings.r_bins_edges)
        builder = Builder(backend=backend(formulae=self.formulae),
                          n_sd=settings.n_sd)
        builder.set_environment(
            Parcel(
                dt=dt_output / self.n_substeps,
                mass_of_dry_air=settings.mass_of_dry_air,
                p0=settings.p0,
                q0=settings.q0,
                T0=settings.T0,
                w=settings.w,
                z0=settings.z0,
            ))

        environment = builder.particulator.environment
        builder.add_dynamic(AmbientThermodynamics())
        condensation = Condensation(
            adaptive=settings.adaptive,
            rtol_x=settings.rtol_x,
            rtol_thd=settings.rtol_thd,
            dt_cond_range=settings.dt_cond_range,
        )
        builder.add_dynamic(condensation)

        products = [
            PySDM_products.ParticleSizeSpectrumPerVolume(
                name="Particles Wet Size Spectrum",
                radius_bins_edges=settings.r_bins_edges,
            ),
            PySDM_products.CondensationTimestepMin(name="dt_cond_min"),
            PySDM_products.CondensationTimestepMax(name="dt_cond_max"),
            PySDM_products.RipeningRate(),
        ]

        attributes = environment.init_attributes(n_in_dv=settings.n,
                                                 kappa=settings.kappa,
                                                 r_dry=settings.r_dry)

        self.particulator = builder.build(attributes, products)

        self.n_steps = settings.n_steps
Ejemplo n.º 4
0
    def __init__(
        self, settings, products=None, scipy_solver=False, rtol_thd=1e-10, rtol_x=1e-10
    ):
        env = Parcel(
            dt=settings.timestep,
            p0=settings.initial_pressure,
            q0=settings.initial_vapour_mixing_ratio,
            T0=settings.initial_temperature,
            w=settings.vertical_velocity,
            mass_of_dry_air=44 * si.kg,
        )
        n_sd = sum(settings.n_sd_per_mode)
        builder = Builder(n_sd=n_sd, backend=CPU(formulae=settings.formulae))
        builder.set_environment(env)
        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(Condensation(rtol_thd=rtol_thd, rtol_x=rtol_x))

        volume = env.mass_of_dry_air / settings.initial_air_density
        attributes = {
            k: np.empty(0) for k in ("dry volume", "kappa times dry volume", "n")
        }
        for i, (kappa, spectrum) in enumerate(settings.aerosol_modes_by_kappa.items()):
            sampling = ConstantMultiplicity(spectrum)
            r_dry, n_per_volume = sampling.sample(settings.n_sd_per_mode[i])
            v_dry = settings.formulae.trivia.volume(radius=r_dry)
            attributes["n"] = np.append(attributes["n"], n_per_volume * volume)
            attributes["dry volume"] = np.append(attributes["dry volume"], v_dry)
            attributes["kappa times dry volume"] = np.append(
                attributes["kappa times dry volume"], v_dry * kappa
            )
        r_wet = equilibrate_wet_radii(
            r_dry=settings.formulae.trivia.radius(volume=attributes["dry volume"]),
            environment=env,
            kappa_times_dry_volume=attributes["kappa times dry volume"],
        )
        attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet)

        super().__init__(
            particulator=builder.build(attributes=attributes, products=products)
        )
        if scipy_solver:
            bdf.patch_particulator(self.particulator)

        self.output_attributes = {
            "volume": tuple([] for _ in range(self.particulator.n_sd))
        }
        self.settings = settings

        self.__sanity_checks(attributes, volume)
Ejemplo n.º 5
0
    def __init__(self, settings, backend=CPU):
        t_half = settings.z_half / settings.w_avg

        dt_output = (2 * t_half) / settings.n_output
        self.n_substeps = 1
        while dt_output / self.n_substeps >= settings.dt_max:  # TODO #334 dt_max
            self.n_substeps += 1

        builder = Builder(backend=backend, n_sd=1, formulae=settings.formulae)
        builder.set_environment(Parcel(
            dt=dt_output / self.n_substeps,
            mass_of_dry_air=settings.mass_of_dry_air,
            p0=settings.p0,
            q0=settings.q0,
            T0=settings.T0,
            w=settings.w
        ))

        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(Condensation(
            kappa=settings.kappa,
            rtol_x=settings.rtol_x,
            rtol_thd=settings.rtol_thd,
            dt_cond_range=settings.dt_cond_range
        ))
        attributes = {}
        r_dry = np.array([settings.r_dry])
        attributes['dry volume'] = settings.formulae.trivia.volume(radius=r_dry)
        attributes['n'] = np.array([settings.n_in_dv], dtype=np.int64)
        environment = builder.core.environment
        r_wet = r_wet_init(r_dry, environment, kappa=settings.kappa)
        attributes['volume'] = settings.formulae.trivia.volume(radius=r_wet)
        products = [
            PySDM_products.ParticleMeanRadius(),
            PySDM_products.CondensationTimestepMin(),
            PySDM_products.ParcelDisplacement(),
            PySDM_products.RelativeHumidity(),
            PySDM_products.Time(),
            PySDM_products.ActivatingRate(),
            PySDM_products.DeactivatingRate(),
            PySDM_products.RipeningRate(),
            PySDM_products.PeakSupersaturation()
        ]

        self.core = builder.build(attributes, products)

        self.n_output = settings.n_output
Ejemplo n.º 6
0
    def __init__(self, settings, backend=CPU):
        dt_output = settings.total_time / settings.n_steps  # TODO #334 overwritten in jupyter example
        self.n_substeps = 1  # TODO #334 use condensation substeps
        while (dt_output / self.n_substeps >= settings.dt_max):
            self.n_substeps += 1
        self.formulae = Formulae(condensation_coordinate=settings.coord, saturation_vapour_pressure='AugustRocheMagnus')
        self.bins_edges = self.formulae.trivia.volume(settings.r_bins_edges)
        builder = Builder(backend=backend, n_sd=settings.n_sd, formulae=self.formulae)
        builder.set_environment(Parcel(
            dt=dt_output / self.n_substeps,
            mass_of_dry_air=settings.mass_of_dry_air,
            p0=settings.p0,
            q0=settings.q0,
            T0=settings.T0,
            w=settings.w,
            z0=settings.z0
        ))

        environment = builder.core.environment
        builder.add_dynamic(AmbientThermodynamics())
        condensation = Condensation(
            kappa=settings.kappa,
            adaptive=settings.adaptive,
            rtol_x=settings.rtol_x,
            rtol_thd=settings.rtol_thd,
            dt_cond_range=settings.dt_cond_range
        )
        builder.add_dynamic(condensation)

        products = [
            PySDM_products.ParticlesWetSizeSpectrum(v_bins=self.formulae.trivia.volume(settings.r_bins_edges)),
            PySDM_products.CondensationTimestepMin(),
            PySDM_products.CondensationTimestepMax(),
            PySDM_products.RipeningRate()
        ]

        attributes = environment.init_attributes(
            n_in_dv=settings.n,
            kappa=settings.kappa,
            r_dry=settings.r_dry
        )

        self.core = builder.build(attributes, products)

        self.n_steps = settings.n_steps
Ejemplo n.º 7
0
    def __init__(self, settings, backend=CPU):

        dt_output = settings.total_time / settings.n_steps  # TODO: overwritten in jupyter example
        self.n_substeps = 1  # TODO
        while (dt_output / self.n_substeps >= settings.dt_max):
            self.n_substeps += 1
        self.bins_edges = phys.volume(settings.r_bins_edges)
        builder = Builder(backend=backend, n_sd=settings.n_sd)
        builder.set_environment(
            Parcel(dt=dt_output / self.n_substeps,
                   mass_of_dry_air=settings.mass_of_dry_air,
                   p0=settings.p0,
                   q0=settings.q0,
                   T0=settings.T0,
                   w=settings.w,
                   z0=settings.z0))

        environment = builder.core.environment
        builder.add_dynamic(AmbientThermodynamics())
        condensation = Condensation(kappa=settings.kappa,
                                    coord=settings.coord,
                                    adaptive=settings.adaptive,
                                    rtol_x=settings.rtol_x,
                                    rtol_thd=settings.rtol_thd)
        builder.add_dynamic(condensation)

        products = [
            ParticlesWetSizeSpectrum(
                v_bins=phys.volume(settings.r_bins_edges)),
            CondensationTimestep(),
            RipeningRate()
        ]

        attributes = environment.init_attributes(n_in_dv=settings.n,
                                                 kappa=settings.kappa,
                                                 r_dry=settings.r_dry)

        self.core = builder.build(attributes, products)

        self.n_steps = settings.n_steps
Ejemplo n.º 8
0
    def __init__(self, settings, backend=CPU):
        t_half = settings.z_half / settings.w_avg

        dt_output = (2 * t_half) / settings.n_output
        self.n_substeps = 1
        while dt_output / self.n_substeps >= settings.dt_max:  # TODO dt_max
            self.n_substeps += 1

        builder = Builder(backend=backend, n_sd=1)
        builder.set_environment(Parcel(
            dt=dt_output / self.n_substeps,
            mass_of_dry_air=settings.mass_of_dry_air,
            p0=settings.p0,
            q0=settings.q0,
            T0=settings.T0,
            w=settings.w
        ))

        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(Condensation(
            kappa=settings.kappa,
            rtol_x=settings.rtol_x,
            rtol_thd=settings.rtol_thd,
        ))
        attributes = {}
        r_dry = np.array([settings.r_dry])
        attributes['dry volume'] = phys.volume(radius=r_dry)
        attributes['n'] = np.array([settings.n_in_dv], dtype=np.int64)
        environment = builder.core.environment
        r_wet = r_wet_init(r_dry, environment, np.zeros_like(attributes['n']), settings.kappa)
        attributes['volume'] = phys.volume(radius=r_wet)
        products = [ParticleMeanRadius(), CondensationTimestep()]

        self.core = builder.build(attributes, products)

        self.n_output = settings.n_output
Ejemplo n.º 9
0
    def __init__(self, settings, products=None):
        env = Parcel(
            dt=settings.dt,
            mass_of_dry_air=settings.mass_of_dry_air,
            p0=settings.p0,
            q0=settings.q0,
            T0=settings.T0,
            w=settings.w,
        )
        n_sd = settings.n_sd_per_mode * len(settings.aerosol.modes)
        builder = Builder(n_sd=n_sd, backend=CPU(formulae=settings.formulae))
        builder.set_environment(env)

        attributes = {
            "dry volume": np.empty(0),
            "dry volume organic": np.empty(0),
            "kappa times dry volume": np.empty(0),
            "n": np.ndarray(0),
        }
        for mode in settings.aerosol.modes:
            r_dry, n_in_dv = settings.spectral_sampling(
                spectrum=mode["spectrum"]).sample(settings.n_sd_per_mode)
            V = settings.mass_of_dry_air / settings.rho0
            N = n_in_dv * V
            v_dry = settings.formulae.trivia.volume(radius=r_dry)
            attributes["n"] = np.append(attributes["n"], N)
            attributes["dry volume"] = np.append(attributes["dry volume"],
                                                 v_dry)
            attributes["dry volume organic"] = np.append(
                attributes["dry volume organic"], mode["f_org"] * v_dry)
            attributes["kappa times dry volume"] = np.append(
                attributes["kappa times dry volume"],
                v_dry * mode["kappa"][settings.model],
            )
        for attribute in attributes.values():
            assert attribute.shape[0] == n_sd

        np.testing.assert_approx_equal(
            np.sum(attributes["n"]) / V,
            Sum(
                tuple(
                    settings.aerosol.modes[i]["spectrum"]
                    for i in range(len(settings.aerosol.modes)))).norm_factor,
            significant=5,
        )
        r_wet = equilibrate_wet_radii(
            r_dry=settings.formulae.trivia.radius(
                volume=attributes["dry volume"]),
            environment=env,
            kappa_times_dry_volume=attributes["kappa times dry volume"],
            f_org=attributes["dry volume organic"] / attributes["dry volume"],
        )
        attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet)

        if settings.model == "Constant":
            del attributes["dry volume organic"]

        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(Condensation())

        products = products or (
            PySDM_products.ParcelDisplacement(name="z"),
            PySDM_products.Time(name="t"),
            PySDM_products.PeakSupersaturation(unit="%", name="S_max"),
            PySDM_products.AmbientRelativeHumidity(unit="%", name="RH"),
            PySDM_products.ParticleConcentration(
                name="n_c_cm3",
                unit="cm^-3",
                radius_range=settings.cloud_radius_range),
            PySDM_products.ParticleSizeSpectrumPerVolume(
                radius_bins_edges=settings.wet_radius_bins_edges),
            PySDM_products.ActivableFraction(),
        )

        particulator = builder.build(attributes=attributes, products=products)
        if settings.BDF:
            bdf.patch_particulator(particulator)
        self.settings = settings
        super().__init__(particulator=particulator)
Ejemplo n.º 10
0
    def __init__(self, settings, products=None):
        env = Parcel(
            dt=settings.dt,
            mass_of_dry_air=settings.mass_of_dry_air,
            p0=settings.p0,
            q0=settings.q0,
            T0=settings.T0,
            w=settings.w,
        )

        builder = Builder(n_sd=settings.n_sd,
                          backend=CPU(formulae=settings.formulae))
        builder.set_environment(env)

        attributes = env.init_attributes(n_in_dv=settings.n_in_dv,
                                         kappa=settings.kappa,
                                         r_dry=settings.r_dry)
        attributes = {
            **attributes,
            **settings.starting_amounts,
            "pH": np.zeros(settings.n_sd),
        }

        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(Condensation())
        builder.add_dynamic(
            AqueousChemistry(
                environment_mole_fractions=settings.ENVIRONMENT_MOLE_FRACTIONS,
                system_type=settings.system_type,
                n_substep=settings.n_substep,
                dry_rho=settings.DRY_RHO,
                dry_molar_mass=settings.dry_molar_mass,
            ))

        products = products or (
            PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"),
            PySDM_products.WaterMixingRatio(
                name="ql", radius_range=[1 * si.um, np.inf], unit="g/kg"),
            PySDM_products.ParcelDisplacement(name="z"),
            PySDM_products.AmbientPressure(name="p"),
            PySDM_products.AmbientTemperature(name="T"),
            PySDM_products.AmbientDryAirDensity(name="rhod"),
            PySDM_products.AmbientWaterVapourMixingRatio(name="qv",
                                                         unit="g/kg"),
            PySDM_products.Time(name="t"),
            *(PySDM_products.AqueousMoleFraction(
                comp, unit="ppb", name=f"aq_{comp}_ppb")
              for comp in AQUEOUS_COMPOUNDS),
            *(PySDM_products.GaseousMoleFraction(
                comp, unit="ppb", name=f"gas_{comp}_ppb")
              for comp in GASEOUS_COMPOUNDS),
            PySDM_products.Acidity(
                name="pH_pH_number_weighted",
                radius_range=settings.cloud_radius_range,
                weighting="number",
                attr="pH",
            ),
            PySDM_products.Acidity(
                name="pH_pH_volume_weighted",
                radius_range=settings.cloud_radius_range,
                weighting="volume",
                attr="pH",
            ),
            PySDM_products.Acidity(
                name="pH_conc_H_number_weighted",
                radius_range=settings.cloud_radius_range,
                weighting="number",
                attr="conc_H",
            ),
            PySDM_products.Acidity(
                name="pH_conc_H_volume_weighted",
                radius_range=settings.cloud_radius_range,
                weighting="volume",
                attr="conc_H",
            ),
            PySDM_products.TotalDryMassMixingRatio(
                settings.DRY_RHO, name="q_dry", unit="ug/kg"),
            PySDM_products.PeakSupersaturation(unit="%", name="S_max"),
            PySDM_products.ParticleSpecificConcentration(
                radius_range=settings.cloud_radius_range,
                name="n_c_mg",
                unit="mg^-1"),
            PySDM_products.AqueousMassSpectrum(
                key="S_VI",
                dry_radius_bins_edges=settings.dry_radius_bins_edges,
                name="dm_S_VI/dlog_10(dry diameter)",
                unit='ug / m^3"',
            ),
        )

        particulator = builder.build(attributes=attributes, products=products)
        self.settings = settings
        super().__init__(particulator=particulator)
Ejemplo n.º 11
0
    def __init__(self, settings, products=None):
        env = Parcel(dt=settings.dt,
                     mass_of_dry_air=settings.mass_of_dry_air,
                     p0=settings.p0,
                     q0=settings.q0,
                     T0=settings.T0,
                     w=settings.w,
                     g=settings.g)

        builder = Builder(n_sd=settings.n_sd,
                          backend=CPU,
                          formulae=settings.formulae)
        builder.set_environment(env)

        attributes = env.init_attributes(n_in_dv=settings.n_in_dv,
                                         kappa=settings.kappa,
                                         r_dry=settings.r_dry)
        attributes = {
            **attributes,
            **settings.starting_amounts, 'pH': np.zeros(settings.n_sd)
        }

        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(Condensation(kappa=settings.kappa))
        builder.add_dynamic(
            AqueousChemistry(settings.ENVIRONMENT_MOLE_FRACTIONS,
                             system_type=settings.system_type,
                             n_substep=settings.n_substep,
                             dry_rho=settings.DRY_RHO,
                             dry_molar_mass=settings.dry_molar_mass))

        products = products or (
            PySDM_products.RelativeHumidity(),
            PySDM_products.WaterMixingRatio(name='ql',
                                            description_prefix='liquid',
                                            radius_range=[1 * si.um, np.inf]),
            PySDM_products.ParcelDisplacement(), PySDM_products.Pressure(),
            PySDM_products.Temperature(), PySDM_products.DryAirDensity(),
            PySDM_products.WaterVapourMixingRatio(), PySDM_products.Time(), *[
                PySDM_products.AqueousMoleFraction(compound)
                for compound in AQUEOUS_COMPOUNDS.keys()
            ], *[
                PySDM_products.GaseousMoleFraction(compound)
                for compound in GASEOUS_COMPOUNDS.keys()
            ],
            PySDM_products.pH(radius_range=settings.cloud_radius_range,
                              weighting='number',
                              attr='pH'),
            PySDM_products.pH(radius_range=settings.cloud_radius_range,
                              weighting='volume',
                              attr='pH'),
            PySDM_products.pH(radius_range=settings.cloud_radius_range,
                              weighting='number',
                              attr='conc_H'),
            PySDM_products.pH(radius_range=settings.cloud_radius_range,
                              weighting='volume',
                              attr='conc_H'),
            PySDM_products.TotalDryMassMixingRatio(
                settings.DRY_RHO), PySDM_products.PeakSupersaturation(),
            PySDM_products.CloudDropletConcentration(
                radius_range=settings.cloud_radius_range),
            PySDM_products.AqueousMassSpectrum("S_VI",
                                               settings.dry_radius_bins_edges))

        self.core = builder.build(attributes=attributes, products=products)
        self.settings = settings
Ejemplo n.º 12
0
def run_parcel(
    w,
    sol2,
    N2,
    rad2,
    n_sd_per_mode,
    RH0=1.0,
    T0=294 * si.K,
    p0=1e5 * si.Pa,
    n_steps=50,
    mass_of_dry_air=1e3 * si.kg,
    dt=2 * si.s,
):
    products = (
        PySDM_products.WaterMixingRatio(unit="g/kg", name="ql"),
        PySDM_products.PeakSupersaturation(name="S max"),
        PySDM_products.AmbientRelativeHumidity(name="RH"),
        PySDM_products.ParcelDisplacement(name="z"),
    )

    formulae = Formulae()
    const = formulae.constants
    pv0 = RH0 * formulae.saturation_vapour_pressure.pvs_Celsius(T0 - const.T0)
    q0 = const.eps * pv0 / (p0 - pv0)

    env = Parcel(dt=dt,
                 mass_of_dry_air=mass_of_dry_air,
                 p0=p0,
                 q0=q0,
                 w=w,
                 T0=T0)

    aerosol = AerosolARG(M2_sol=sol2, M2_N=N2, M2_rad=rad2)
    n_sd = n_sd_per_mode * len(aerosol.modes)

    builder = Builder(backend=CPU(), n_sd=n_sd)
    builder.set_environment(env)
    builder.add_dynamic(AmbientThermodynamics())
    builder.add_dynamic(Condensation())
    builder.add_dynamic(Magick())

    attributes = {
        k: np.empty(0)
        for k in ("dry volume", "kappa times dry volume", "n")
    }
    for i, mode in enumerate(aerosol.modes):
        kappa, spectrum = mode["kappa"]["CompressedFilmOvadnevaite"], mode[
            "spectrum"]
        r_dry, concentration = ConstantMultiplicity(spectrum).sample(
            n_sd_per_mode)
        v_dry = builder.formulae.trivia.volume(radius=r_dry)
        specific_concentration = concentration / builder.formulae.constants.rho_STP
        attributes["n"] = np.append(
            attributes["n"], specific_concentration * env.mass_of_dry_air)
        attributes["dry volume"] = np.append(attributes["dry volume"], v_dry)
        attributes["kappa times dry volume"] = np.append(
            attributes["kappa times dry volume"], v_dry * kappa)

    r_wet = equilibrate_wet_radii(
        r_dry=builder.formulae.trivia.radius(volume=attributes["dry volume"]),
        environment=env,
        kappa_times_dry_volume=attributes["kappa times dry volume"],
    )
    attributes["volume"] = builder.formulae.trivia.volume(radius=r_wet)

    particulator = builder.build(attributes, products=products)
    bdf.patch_particulator(particulator)

    output = {product.name: [] for product in particulator.products.values()}
    output_attributes = {
        "n": tuple([] for _ in range(particulator.n_sd)),
        "volume": tuple([] for _ in range(particulator.n_sd)),
        "critical volume": tuple([] for _ in range(particulator.n_sd)),
        "critical supersaturation":
        tuple([] for _ in range(particulator.n_sd)),
    }

    for _ in range(n_steps):
        particulator.run(steps=1)
        for product in particulator.products.values():
            value = product.get()
            output[product.name].append(value[0])
        for key, attr in output_attributes.items():
            attr_data = particulator.attributes[key].to_ndarray()
            for drop_id in range(particulator.n_sd):
                attr[drop_id].append(attr_data[drop_id])

    error = np.zeros(len(aerosol.modes))
    activated_fraction_S = np.zeros(len(aerosol.modes))
    activated_fraction_V = np.zeros(len(aerosol.modes))
    for j, mode in enumerate(aerosol.modes):
        activated_drops_j_S = 0
        activated_drops_j_V = 0
        RHmax = np.nanmax(np.asarray(output["RH"]))
        for i, volume in enumerate(output_attributes["volume"]):
            if j * n_sd_per_mode <= i < (j + 1) * n_sd_per_mode:
                if output_attributes["critical supersaturation"][i][-1] < RHmax:
                    activated_drops_j_S += output_attributes["n"][i][-1]
                if output_attributes["critical volume"][i][-1] < volume[-1]:
                    activated_drops_j_V += output_attributes["n"][i][-1]
        Nj = np.asarray(output_attributes["n"])[j * n_sd_per_mode:(j + 1) *
                                                n_sd_per_mode, -1]
        max_multiplicity_j = np.max(Nj)
        sum_multiplicity_j = np.sum(Nj)
        error[j] = max_multiplicity_j / sum_multiplicity_j
        activated_fraction_S[j] = activated_drops_j_S / sum_multiplicity_j
        activated_fraction_V[j] = activated_drops_j_V / sum_multiplicity_j

    Output = namedtuple(
        "Output",
        [
            "profile",
            "attributes",
            "aerosol",
            "activated_fraction_S",
            "activated_fraction_V",
            "error",
        ],
    )
    return Output(
        profile=output,
        attributes=output_attributes,
        aerosol=aerosol,
        activated_fraction_S=activated_fraction_S,
        activated_fraction_V=activated_fraction_V,
        error=error,
    )