EpisimModule.java

/*-
 * #%L
 * MATSim Episim
 * %%
 * Copyright (C) 2020 matsim-org
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.matsim.episim;

import com.google.inject.AbstractModule;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.multibindings.Multibinder;
import org.matsim.api.core.v01.Scenario;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.config.groups.VspExperimentalConfigGroup;
import org.matsim.core.events.EventsUtils;
import org.matsim.core.scenario.ScenarioUtils;
import org.matsim.episim.model.*;
import org.matsim.episim.model.activity.ActivityParticipationModel;
import org.matsim.episim.model.activity.AllParticipationModel;
import org.matsim.episim.model.progression.DefaultDiseaseStatusTransitionModel;
import org.matsim.episim.model.progression.DiseaseStatusTransitionModel;
import org.matsim.episim.model.testing.DefaultTestingModel;
import org.matsim.episim.model.testing.TestingModel;
import org.matsim.episim.model.vaccination.RandomVaccination;
import org.matsim.episim.model.vaccination.VaccinationModel;
import org.matsim.episim.policy.FixedPolicy;
import org.matsim.episim.policy.ShutdownPolicy;
import org.matsim.episim.reporting.EpisimWriter;

import jakarta.inject.Named;
import org.matsim.episim.util.EpisimSplittableRandom;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Provides the default bindings needed for Episim.
 */
public class EpisimModule extends AbstractModule {


	@Override
	protected void configure() {

		binder().requireExplicitBindings();

		// Main model classes regarding progression / infection etc..
		bind(ContactModel.class).to(DefaultContactModel.class).in(Singleton.class);
		bind(InfectionModel.class).to(DefaultInfectionModel.class).in(Singleton.class);
		bind(ProgressionModel.class).to(ConfigurableProgressionModel.class).in(Singleton.class);
		// which of the two a run uses is decided by the configuration, see antibodyModel() below
		bind(DefaultAntibodyModel.class).in(Singleton.class);
		bind(NoAntibodyModel.class).in(Singleton.class);
		// antibody parameters are calibrated for SARS-CoV-2 and not a config group; scenarios bind their own instance
		bind(AntibodyModel.Config.class).toInstance(new AntibodyModel.Config());
		// Both sources are bound explicitly because LegacySplitImmunityModel injects them and this injector
		// requires explicit bindings. The legacy default is the curve-based source, which is what
		// DefaultDiseaseStatusTransitionModel used through the former default methods of its interface.
		// Modules that bind AgeDependentDiseaseStatusTransitionModel took severity from antibodies instead and
		// therefore override the @Legacy binding with LegacySplitImmunityModel. Which of the two, legacy or
		// explicit, the run uses is decided by the configuration, see immunityModel() below.
		bind(LegacyCurveImmunityModel.class).in(Singleton.class);
		bind(LegacyAntibodyImmunityModel.class).in(Singleton.class);
		bind(ExplicitImmunityModel.class).in(Singleton.class);
		bind(ImmunityModel.class).annotatedWith(Legacy.class).to(LegacyCurveImmunityModel.class).in(Singleton.class);
		bind(DiseaseStatusTransitionModel.class).to(DefaultDiseaseStatusTransitionModel.class).in(Singleton.class);
		bind(FaceMaskModel.class).to(DefaultFaceMaskModel.class).in(Singleton.class);
		bind(ShutdownPolicy.class).to(FixedPolicy.class).in(Singleton.class);
		bind(InitialInfectionHandler.class).to(RandomInitialInfections.class).in(Singleton.class);
		bind(VaccinationModel.class).to(RandomVaccination.class).in(Singleton.class);
		bind(TestingModel.class).to(DefaultTestingModel.class).in(Singleton.class);
		bind(ActivityParticipationModel.class).to(AllParticipationModel.class).in(Singleton.class);

		// Internal classes, should rarely be needed to be reconfigured
		bind(EpisimRunner.class).in(Singleton.class);
		bind(ReplayHandler.class).in(Singleton.class);
		bind(InfectionEventHandler.class).in(Singleton.class);
		bind(EpisimReporting.class).in(Singleton.class);

		Multibinder.newSetBinder(binder(), SimulationListener.class);
		Multibinder.newSetBinder(binder(), VaccinationModel.class);
	}

	@Provides
	@Singleton
	public Scenario scenario(Config config) {

		// guice will use no args constructor by default, we check if this config was initialized
		// this is only the case when no explicit binding are required
		if (config.getModules().size() == 0)
			throw new IllegalArgumentException("Please provide a config module or binding.");

		config.vspExperimental().setVspDefaultsCheckingLevel(VspExperimentalConfigGroup.VspDefaultsCheckingLevel.ignore);

		// save some time for not needed inputs
//		config.facilities().setInputFile(null); // facilities are needed for location-based-restrictions

		return ScenarioUtils.loadScenario(config);
	}

	@Provides
	@Singleton
	public EpisimConfigGroup episimConfigGroup(Config config) {
		return ConfigUtils.addOrGetModule(config, EpisimConfigGroup.class);
	}

	@Provides
	@Singleton
	public TracingConfigGroup tracingConfigGroup(Config config) {
		return ConfigUtils.addOrGetModule(config, TracingConfigGroup.class);
	}

	@Provides
	@Singleton
	public VaccinationConfigGroup vaccinationConfigGroup(Config config) {
		return ConfigUtils.addOrGetModule(config, VaccinationConfigGroup.class);
	}

	@Provides
	@Singleton
	public TestingConfigGroup testingConfigGroup(Config config) {
		return ConfigUtils.addOrGetModule(config, TestingConfigGroup.class);
	}

	@Provides
	@Singleton
	public ImmunityConfigGroup immunityConfigGroup(Config config) {
		return ConfigUtils.addOrGetModule(config, ImmunityConfigGroup.class);
	}

	/**
	 * Antibodies are only evolved when something reads them, which is the legacy immunity model. A scenario that
	 * needs them for its own reason binds {@link AntibodyModel} itself and overrides this.
	 */
	@Provides
	@Singleton
	public AntibodyModel antibodyModel(ImmunityConfigGroup immunityConfig,
									   Provider<DefaultAntibodyModel> antibodies, Provider<NoAntibodyModel> none) {
		return immunityConfig.getModel() == ImmunityConfigGroup.Model.legacyCovid ? antibodies.get() : none.get();
	}

	/**
	 * The immunity of the whole run, chosen by {@link ImmunityConfigGroup#getModel()}. The legacy implementation is
	 * whatever the scenario bound as {@code @Legacy}; providers are injected so that the unused one is never built.
	 */
	@Provides
	@Singleton
	public ImmunityModel immunityModel(ImmunityConfigGroup immunityConfig,
									   @Legacy Provider<ImmunityModel> legacy, Provider<ExplicitImmunityModel> explicit) {
		switch (immunityConfig.getModel()) {
			case legacyCovid:
				return legacy.get();
			case explicit:
				return explicit.get();
			default:
				throw new IllegalStateException("Unknown immunity model: " + immunityConfig.getModel());
		}
	}

	@Provides
	@Singleton
	public VirusStrainConfigGroup strainConfigGroup(Config config) {
		return ConfigUtils.addOrGetModule(config, VirusStrainConfigGroup.class);
	}

	@Provides
	@Singleton
	public PathogenConfigGroup pathogenConfigGroup(Config config) {
		return ConfigUtils.addOrGetModule(config, PathogenConfigGroup.class);
	}

	/** Provides the contact-transmission configuration. */
	@Provides
	@Singleton
	public ContactTransmissionConfigGroup contactTransmissionConfigGroup(Config config) {
		ContactTransmissionConfigGroup group = ConfigUtils.addOrGetModule(config, ContactTransmissionConfigGroup.class);
		group.setContext(config.getContext());
		return group;
	}

	@Provides
	@Singleton
	public EpisimWriter episimWriter(EpisimConfigGroup episimConfig) {

		// TODO: needs to be fixed for single event files, synchronization after each iteration would be needed
		// Async writer is used for huge event number
		//if (Runtime.getRuntime().availableProcessors() > 1 && episimConfig.getWriteEvents() != EpisimConfigGroup.WriteEvents.episim)
			// by default only one episim simulation is running
		//	return new AsyncEpisimWriter(1);

		return new EpisimWriter();
	}

	@Provides
	@Singleton
	public EventsManager eventsManager() {
		return EventsUtils.createEventsManager();
	}

	@Provides
	@Singleton
	public EpisimSplittableRandom splittableRandom(Config config) {
		return new EpisimSplittableRandom(config.global().getRandomSeed());
	}

	@Provides
	@Named("policy")
	@Singleton
	public com.typesafe.config.Config policyConfig(EpisimConfigGroup config) {
		return config.getPolicy();
	}


	@Provides
	@Singleton
	public ExecutorService executorService(EpisimConfigGroup episimConfig) {

		if (episimConfig.getThreads() > 1)
			return Executors.newFixedThreadPool(episimConfig.getThreads());
		else
			return Executors.newSingleThreadScheduledExecutor();
	}

}