def __init__(
            self,
            supervisable_makers: Iterable[Union[str, Supervisable, Callable]],
            population_data: PopulationData,
            matrix_data: MatrixData,
            inital_agent_constraints: InitialAgentsConstraints,
            run_args,
            consts: Consts = Consts(),
    ):
        # setting logger
        self.logger = logging.getLogger("simulation")
        logging.basicConfig()
        self.logger.setLevel(logging.INFO)
        self.logger.info("Creating a new simulation.")

        # unpacking data from generation
        self.initial_agent_constraints = inital_agent_constraints
        self.agents = population_data.agents
        self.geographic_circles = population_data.geographic_circles
        self.social_circles_by_connection_type = population_data.social_circles_by_connection_type
        self.geographic_circle_by_agent_index = population_data.geographic_circle_by_agent_index
        self.social_circles_by_agent_index = population_data.social_circles_by_agent_index

        self.matrix_type = matrix_data.matrix_type
        self.matrix = matrix_data.matrix
        self.depth = matrix_data.depth

        self.run_args = run_args

        # setting up medical things
        self.consts = consts
        self.medical_machine = consts.medical_state_machine()
        initial_state = self.medical_machine.initial

        self.pending_transfers = PendingTransfers()

        # the manager holds the vector, but the agents update it
        self.contagiousness_vector = np.zeros(len(
            self.agents), dtype=float)  # how likely to infect others
        self.susceptible_vector = np.zeros(len(self.agents),
                                           dtype=bool)  # can get infected

        # healthcare related data
        self.living_agents_vector = np.ones(len(self.agents), dtype=bool)
        self.test_willingness_vector = np.zeros(len(self.agents), dtype=float)
        self.tested_vector = np.zeros(len(self.agents), dtype=bool)
        self.tested_positive_vector = np.zeros(len(self.agents), dtype=bool)
        self.ever_tested_positive_vector = np.zeros(len(self.agents),
                                                    dtype=bool)
        self.date_of_last_test = np.zeros(len(self.agents), dtype=int)
        self.pending_test_results = PendingTestResults()

        # initializing agents to current simulation
        for agent in self.agents:
            agent.add_to_simulation(self, initial_state)
        initial_state.add_many(self.agents)

        # initializing simulation modules
        self.simulation_progression = SimulationProgression(
            [Supervisable.coerce(a, self) for a in supervisable_makers], self)
        self.update_matrix_manager = update_matrix.UpdateMatrixManager(self)
        self.infection_manager = infection.InfectionManager(self)
        self.healthcare_manager = healthcare.HealthcareManager(self)
        self.medical_state_manager = MedicalStateManager(self)
        self.policy_manager = PolicyManager(self)

        self.current_step = 0

        # initializing data for supervising
        # dict(day:int -> message:string) saving policies messages
        self.policies_messages = defaultdict(str)
        self.sick_agents = SickAgents()

        self.new_sick_counter = 0
        self.new_detected_daily = 0

        self.logger.info("Created new simulation.")
        self.simulation_progression.snapshot(self)
def monte_carlo_state_machine_analysis(configuration: Dict) -> Dict:
    """

    :param configuration: Dictionary with configuration for the mc run
           configuration must contain:
           * population_size for the mc
           configuration might contain:
           * consts_file - For loading Consts()
           * circle_consts file - for loading CircleConsts
    :return: Dictionary with statistics of the run:
                * population_size
                * days_passed - time it took to all the agents to recover/die
                * time_in_each_state - for each state, total number of days all agents were in it
                * visitors_in_each_state - Number of agents that visited each state
                * average_duration_in_state - Empirical mean time to stay
                                              at state conditioned that we enter it
                * state_duration_expected_time - Empirical mean to stay in state, not conditioned
                * average_time_to_terminal -Empirical mean time until death/recovery

    """
    if 'consts_file' in configuration:
        consts = Consts.from_file(configuration['consts_file'])
    else:
        consts = Consts()

    if "circle_consts_file" in configuration:
        circle_const = CirclesConsts.from_file(
            configuration['circle_consts_file'])
    else:
        circle_const = CirclesConsts()

    population_size = configuration["monte_carlo_size"]

    medical_state_machine = consts.medical_state_machine()
    medical_machine_manager = MedicalStateManager(
        medical_state_machine=medical_state_machine)
    agents_list = _generate_agents_randomly(population_size=population_size,
                                            circle_consts=circle_const)
    _infect_all_agents(agents_list, medical_machine_manager,
                       medical_state_machine)
    medical_states = medical_state_machine.states
    terminal_states = list(filter(_is_terminal_state, medical_states))

    state_counter = Counter({m.name: m.agent_count for m in medical_states})
    sum_days_to_terminal = 0
    days_passed = 1
    number_terminals_agents = 0

    while number_terminals_agents != population_size:
        # No manager so we don't update it
        previous_terminal_agents = sum(
            [m.agent_count for m in terminal_states])
        medical_machine_manager.step(list())
        number_terminals_agents = sum([m.agent_count for m in terminal_states])
        new_terminals = number_terminals_agents - previous_terminal_agents
        for m in medical_states:
            state_counter[m.name] += m.agent_count
        days_passed += 1
        sum_days_to_terminal += days_passed * new_terminals

    average_state_time_duration, state_duration_expected_time = _get_empirical_state_times(
        medical_state_machine, population_size, state_counter)

    return dict(population_size=population_size,
                days_passed=days_passed,
                time_in_each_state=dict(state_counter),
                visitors_in_each_state={
                    m.name: len(m.ever_visited)
                    for m in medical_state_machine.states
                },
                average_duration_in_state=average_state_time_duration,
                state_duration_expected_time=state_duration_expected_time,
                average_time_to_terminal=sum_days_to_terminal /
                population_size)