Пример #1
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
Пример #2
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
Пример #3
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
Пример #4
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
Пример #5
0
def make_default_product_collection(settings):
    cloud_range = (settings.aerosol_radius_threshold, settings.drizzle_radius_threshold)
    products = [
        # Note: consider better radius_bins_edges
        PySDM_products.ParticleSizeSpectrumPerMass(
            name="Particles Wet Size Spectrum",
            unit="mg^-1 um^-1",
            radius_bins_edges=settings.r_bins_edges,
        ),
        PySDM_products.ParticleSizeSpectrumPerMass(
            name="Particles Dry Size Spectrum",
            unit="mg^-1 um^-1",
            radius_bins_edges=settings.r_bins_edges,
            dry=True,
        ),
        PySDM_products.TotalParticleConcentration(),
        PySDM_products.TotalParticleSpecificConcentration(),
        PySDM_products.ParticleConcentration(
            radius_range=(0, settings.aerosol_radius_threshold)
        ),
        PySDM_products.ParticleConcentration(
            name="n_c_cm3", unit="cm^-3", radius_range=cloud_range
        ),
        PySDM_products.WaterMixingRatio(name="qc", radius_range=cloud_range),
        PySDM_products.WaterMixingRatio(
            name="qr", radius_range=(settings.drizzle_radius_threshold, np.inf)
        ),
        PySDM_products.ParticleConcentration(
            name="drizzle concentration",
            radius_range=(settings.drizzle_radius_threshold, np.inf),
            unit="cm^-3",
        ),
        PySDM_products.ParticleSpecificConcentration(
            name="aerosol specific concentration",
            radius_range=(0, settings.aerosol_radius_threshold),
            unit="mg^-1",
        ),
        PySDM_products.MeanRadius(unit="um"),
        PySDM_products.SuperDropletCountPerGridbox(),
        PySDM_products.AmbientRelativeHumidity(name="RH_env", var="RH"),
        PySDM_products.AmbientPressure(name="p_env", var="p"),
        PySDM_products.AmbientTemperature(name="T_env", var="T"),
        PySDM_products.AmbientWaterVapourMixingRatio(name="qv_env", var="qv"),
        PySDM_products.AmbientDryAirDensity(name="rhod_env", var="rhod"),
        PySDM_products.AmbientDryAirPotentialTemperature(name="thd_env", var="thd"),
        PySDM_products.CPUTime(),
        PySDM_products.WallTime(),
        PySDM_products.EffectiveRadius(unit="um", radius_range=cloud_range),
        PySDM_products.RadiusBinnedNumberAveragedTerminalVelocity(
            radius_bin_edges=settings.terminal_velocity_radius_bin_edges
        ),
    ]

    if settings.processes["fluid advection"]:
        products.append(PySDM_products.MaxCourantNumber())
        products.append(PySDM_products.CoolingRate())
    if settings.processes["condensation"]:
        products.append(PySDM_products.CondensationTimestepMin(name="dt_cond_min"))
        products.append(PySDM_products.CondensationTimestepMax(name="dt_cond_max"))
        products.append(PySDM_products.PeakSupersaturation(unit="%", name="S_max"))
        products.append(PySDM_products.ActivatingRate())
        products.append(PySDM_products.DeactivatingRate())
        products.append(PySDM_products.RipeningRate())
    if settings.processes["particle advection"]:
        products.append(
            PySDM_products.SurfacePrecipitation(name="surf_precip", unit="mm/day")
        )
    if settings.processes["coalescence"]:
        products.append(PySDM_products.CollisionTimestepMean(name="dt_coal_avg"))
        products.append(PySDM_products.CollisionTimestepMin(name="dt_coal_min"))
        products.append(PySDM_products.CollisionRatePerGridbox())
        products.append(PySDM_products.CollisionRateDeficitPerGridbox())
    if settings.processes["freezing"]:
        products.append(PySDM_products.IceWaterContent())
        if settings.freezing_singular:
            products.append(
                PySDM_products.FreezableSpecificConcentration(settings.T_bins_edges)
            )
        else:
            products.append(PySDM_products.TotalUnfrozenImmersedSurfaceArea())
            # TODO #599 immersed surf spec
        products.append(
            PySDM_products.ParticleSpecificConcentration(
                radius_range=(-np.inf, 0), name="n_ice"
            )
        )

    return products
Пример #6
0
    def __init__(self, settings, backend=CPU):
        self.nt = settings.nt

        builder = Builder(backend=backend,
                          n_sd=settings.n_sd,
                          formulae=settings.formulae)
        mesh = Mesh(grid=(settings.nz, ), size=(settings.z_max, ))
        env = Kinematic1D(dt=settings.dt,
                          mesh=mesh,
                          thd_of_z=settings.thd,
                          rhod_of_z=settings.rhod)

        mpdata = MPDATA_1D(
            nz=settings.nz,
            dt=settings.dt,
            mpdata_settings=settings.mpdata_settings,
            advector_of_t=lambda t: settings.w(t) * settings.dt / settings.dz,
            advectee_of_zZ_at_t0=lambda zZ: settings.qv(zZ * settings.dz),
            g_factor_of_zZ=lambda zZ: settings.rhod(zZ * settings.dz))

        builder.set_environment(env)
        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(
            Condensation(adaptive=settings.condensation_adaptive,
                         rtol_thd=settings.condensation_rtol_thd,
                         rtol_x=settings.condensation_rtol_x,
                         kappa=settings.kappa))
        builder.add_dynamic(EulerianAdvection(mpdata))
        if settings.precip:
            builder.add_dynamic(
                Coalescence(kernel=Geometric(collection_efficiency=1),
                            adaptive=settings.coalescence_adaptive))
            builder.add_dynamic(
                Displacement(enable_sedimentation=True,
                             courant_field=(np.zeros(settings.nz +
                                                     1), )))  # TODO #414
        attributes = env.init_attributes(
            spatial_discretisation=spatial_sampling.Pseudorandom(),
            spectral_discretisation=spectral_sampling.ConstantMultiplicity(
                spectrum=settings.wet_radius_spectrum_per_mass_of_dry_air),
            kappa=settings.kappa)
        products = [
            PySDM_products.RelativeHumidity(),
            PySDM_products.Pressure(),
            PySDM_products.Temperature(),
            PySDM_products.WaterVapourMixingRatio(),
            PySDM_products.WaterMixingRatio(
                name='ql',
                description_prefix='cloud',
                radius_range=settings.cloud_water_radius_range),
            PySDM_products.WaterMixingRatio(
                name='qr',
                description_prefix='rain',
                radius_range=settings.rain_water_radius_range),
            PySDM_products.DryAirDensity(),
            PySDM_products.DryAirPotentialTemperature(),
            PySDM_products.ParticlesDrySizeSpectrum(
                v_bins=settings.v_bin_edges),
            PySDM_products.ParticlesWetSizeSpectrum(
                v_bins=settings.v_bin_edges),
            PySDM_products.CloudDropletConcentration(
                radius_range=settings.cloud_water_radius_range),
            PySDM_products.AerosolConcentration(
                radius_threshold=settings.cloud_water_radius_range[0]),
            PySDM_products.ParticleMeanRadius(),
            PySDM_products.RipeningRate(),
            PySDM_products.ActivatingRate(),
            PySDM_products.DeactivatingRate(),
            PySDM_products.CloudDropletEffectiveRadius(
                radius_range=settings.cloud_water_radius_range),
            PySDM_products.PeakSupersaturation()
        ]
        self.core = builder.build(attributes=attributes, products=products)
Пример #7
0
    def reinit(self, products=None):
        builder = Builder(n_sd=self.settings.n_sd,
                          backend=self.backend,
                          formulae=self.settings.formulae)
        environment = Kinematic2D(dt=self.settings.dt,
                                  grid=self.settings.grid,
                                  size=self.settings.size,
                                  rhod_of=self.settings.rhod,
                                  field_values=self.settings.field_values)
        builder.set_environment(environment)

        cloud_range = (self.settings.aerosol_radius_threshold,
                       self.settings.drizzle_radius_threshold)
        if products is not None:
            products = list(products)
        products = products or [
            PySDM_products.ParticlesWetSizeSpectrum(
                v_bins=self.settings.v_bins, normalise_by_dv=True),
            PySDM_products.ParticlesDrySizeSpectrum(
                v_bins=self.settings.v_bins,
                normalise_by_dv=True),  # Note: better v_bins
            PySDM_products.TotalParticleConcentration(),
            PySDM_products.TotalParticleSpecificConcentration(),
            PySDM_products.AerosolConcentration(
                radius_threshold=self.settings.aerosol_radius_threshold),
            PySDM_products.CloudDropletConcentration(radius_range=cloud_range),
            PySDM_products.WaterMixingRatio(name='qc',
                                            description_prefix='Cloud',
                                            radius_range=cloud_range),
            PySDM_products.WaterMixingRatio(
                name='qr',
                description_prefix='Rain',
                radius_range=(self.settings.drizzle_radius_threshold, np.inf)),
            PySDM_products.DrizzleConcentration(
                radius_threshold=self.settings.drizzle_radius_threshold),
            PySDM_products.AerosolSpecificConcentration(
                radius_threshold=self.settings.aerosol_radius_threshold),
            PySDM_products.ParticleMeanRadius(),
            PySDM_products.SuperDropletCount(),
            PySDM_products.RelativeHumidity(),
            PySDM_products.Pressure(),
            PySDM_products.Temperature(),
            PySDM_products.WaterVapourMixingRatio(),
            PySDM_products.DryAirDensity(),
            PySDM_products.DryAirPotentialTemperature(),
            PySDM_products.CPUTime(),
            PySDM_products.WallTime(),
            PySDM_products.CloudDropletEffectiveRadius(
                radius_range=cloud_range),
            PySDM_products.PeakSupersaturation(),
            PySDM_products.ActivatingRate(),
            PySDM_products.DeactivatingRate(),
            PySDM_products.RipeningRate()
        ]

        fields = Fields(environment, self.settings.stream_function)
        if self.settings.processes[
                'fluid advection']:  # TODO #37 ambient thermodynamics checkbox
            builder.add_dynamic(AmbientThermodynamics())
        if self.settings.processes["condensation"]:
            condensation = Condensation(
                kappa=self.settings.kappa,
                rtol_x=self.settings.condensation_rtol_x,
                rtol_thd=self.settings.condensation_rtol_thd,
                adaptive=self.settings.condensation_adaptive,
                substeps=self.settings.condensation_substeps,
                dt_cond_range=self.settings.condensation_dt_cond_range,
                schedule=self.settings.condensation_schedule)
            builder.add_dynamic(condensation)
            products.append(PySDM_products.CondensationTimestepMin()
                            )  # TODO #37 and what if a user doesn't want it?
            products.append(PySDM_products.CondensationTimestepMax())
        if self.settings.processes['fluid advection']:
            solver = MPDATA(fields=fields,
                            n_iters=self.settings.mpdata_iters,
                            infinite_gauge=self.settings.mpdata_iga,
                            flux_corrected_transport=self.settings.mpdata_fct,
                            third_order_terms=self.settings.mpdata_tot)
            builder.add_dynamic(EulerianAdvection(solver))
        if self.settings.processes["particle advection"]:
            displacement = Displacement(
                courant_field=fields.courant_field,
                enable_sedimentation=self.settings.processes["sedimentation"])
            builder.add_dynamic(displacement)
            products.append(
                PySDM_products.SurfacePrecipitation())  # TODO #37 ditto
        if self.settings.processes["coalescence"]:
            builder.add_dynamic(
                Coalescence(
                    kernel=self.settings.kernel,
                    adaptive=self.settings.coalescence_adaptive,
                    dt_coal_range=self.settings.coalescence_dt_coal_range,
                    substeps=self.settings.coalescence_substeps,
                    optimized_random=self.settings.coalescence_optimized_random
                ))
            products.append(PySDM_products.CoalescenceTimestepMean())
            products.append(PySDM_products.CoalescenceTimestepMin())
            products.append(PySDM_products.CollisionRate())
            products.append(PySDM_products.CollisionRateDeficit())

        attributes = environment.init_attributes(
            spatial_discretisation=spatial_sampling.Pseudorandom(),
            spectral_discretisation=spectral_sampling.ConstantMultiplicity(
                spectrum=self.settings.spectrum_per_mass_of_dry_air),
            kappa=self.settings.kappa)

        self.core = builder.build(attributes, products)
        SpinUp(self.core, self.settings.n_spin_up)
        if self.storage is not None:
            self.storage.init(self.settings)
Пример #8
0
    def __init__(self, settings, backend=CPU):
        self.nt = settings.nt
        self.z0 = -settings.particle_reservoir_depth

        builder = Builder(n_sd=settings.n_sd,
                          backend=backend(formulae=settings.formulae))
        mesh = Mesh(
            grid=(settings.nz, ),
            size=(settings.z_max + settings.particle_reservoir_depth, ),
        )
        env = Kinematic1D(
            dt=settings.dt,
            mesh=mesh,
            thd_of_z=settings.thd,
            rhod_of_z=settings.rhod,
            z0=-settings.particle_reservoir_depth,
        )

        def zZ_to_z_above_reservoir(zZ):
            z_above_reservoir = zZ * (settings.nz * settings.dz) + self.z0
            return z_above_reservoir

        self.mpdata = MPDATA_1D(
            nz=settings.nz,
            dt=settings.dt,
            mpdata_settings=settings.mpdata_settings,
            advector_of_t=lambda t: settings.rho_times_w(t) * settings.dt /
            settings.dz,
            advectee_of_zZ_at_t0=lambda zZ: settings.qv(
                zZ_to_z_above_reservoir(zZ)),
            g_factor_of_zZ=lambda zZ: settings.rhod(zZ_to_z_above_reservoir(zZ)
                                                    ),
        )

        _extra_nz = settings.particle_reservoir_depth // settings.dz
        _z_vec = settings.dz * np.linspace(-_extra_nz, settings.nz - _extra_nz,
                                           settings.nz + 1)
        self.g_factor_vec = settings.rhod(_z_vec)

        builder.set_environment(env)
        builder.add_dynamic(AmbientThermodynamics())
        builder.add_dynamic(
            Condensation(
                adaptive=settings.condensation_adaptive,
                rtol_thd=settings.condensation_rtol_thd,
                rtol_x=settings.condensation_rtol_x,
            ))
        builder.add_dynamic(EulerianAdvection(self.mpdata))
        if settings.precip:
            if settings.breakup:
                builder.add_dynamic(
                    Collision(
                        collision_kernel=Geometric(collection_efficiency=1),
                        coalescence_efficiency=ConstEc(Ec=0.95),
                        breakup_efficiency=ConstEb(Eb=1.0),
                        fragmentation_function=ExponFrag(scale=100 * si.um),
                        adaptive=settings.coalescence_adaptive,
                    ))
            else:
                builder.add_dynamic(
                    Coalescence(
                        collision_kernel=Geometric(collection_efficiency=1),
                        adaptive=settings.coalescence_adaptive,
                    ))
        displacement = Displacement(
            enable_sedimentation=settings.precip,
            precipitation_counting_level_index=int(
                settings.particle_reservoir_depth / settings.dz),
        )
        builder.add_dynamic(displacement)
        attributes = env.init_attributes(
            spatial_discretisation=spatial_sampling.Pseudorandom(),
            spectral_discretisation=spectral_sampling.ConstantMultiplicity(
                spectrum=settings.wet_radius_spectrum_per_mass_of_dry_air),
            kappa=settings.kappa,
        )
        products = [
            PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"),
            PySDM_products.AmbientPressure(name="p"),
            PySDM_products.AmbientTemperature(name="T"),
            PySDM_products.AmbientWaterVapourMixingRatio(name="qv"),
            PySDM_products.WaterMixingRatio(
                name="ql",
                unit="g/kg",
                radius_range=settings.cloud_water_radius_range),
            PySDM_products.WaterMixingRatio(
                name="qr",
                unit="g/kg",
                radius_range=settings.rain_water_radius_range),
            PySDM_products.AmbientDryAirDensity(name="rhod"),
            PySDM_products.AmbientDryAirPotentialTemperature(name="thd"),
            PySDM_products.ParticleSizeSpectrumPerVolume(
                name="dry spectrum",
                radius_bins_edges=settings.r_bins_edges,
                dry=True),
            PySDM_products.ParticleSizeSpectrumPerVolume(
                name="wet spectrum", radius_bins_edges=settings.r_bins_edges),
            PySDM_products.ParticleConcentration(
                name="nc", radius_range=settings.cloud_water_radius_range),
            PySDM_products.ParticleConcentration(
                name="na",
                radius_range=(0, settings.cloud_water_radius_range[0])),
            PySDM_products.MeanRadius(),
            PySDM_products.RipeningRate(),
            PySDM_products.ActivatingRate(),
            PySDM_products.DeactivatingRate(),
            PySDM_products.EffectiveRadius(
                radius_range=settings.cloud_water_radius_range),
            PySDM_products.PeakSupersaturation(unit="%"),
            PySDM_products.SuperDropletCountPerGridbox(),
        ]
        self.particulator = builder.build(attributes=attributes,
                                          products=products)