def create_business(self, social_stratum=None):
     x, y = self.random_position()
     if social_stratum is None:
         social_stratum = int(np.random.rand(1) * 100 // 20)
     self.business.append(Business(x=x, y=y, status=Status.Susceptible, social_stratum=social_stratum,
                                   #fixed_expenses=(social_stratum+1)*self.minimum_expense
                                   #fixed_expenses=self.minimum_expense / (5 - social_stratum)
                                   environment=self
                                   ))
    def initialize(self):
        """
        Initializate the Simulation by creating its population of agents
        """
        x, y = self.random_position()
        self.healthcare = Business(x=x,
                                   y=y,
                                   status=Status.Susceptible,
                                   type=AgentType.Healthcare,
                                   environment=self)
        self.healthcare.fixed_expenses += self.minimum_expense * 3
        x, y = self.random_position()
        self.government = Business(x=x,
                                   y=y,
                                   status=Status.Susceptible,
                                   type=AgentType.Government,
                                   social_stratum=4,
                                   price=1.0,
                                   environment=self)
        self.government.fixed_expenses += self.population_size * (
            self.minimum_expense * 0.05)

        #number of houses
        for i in np.arange(0, int(self.population_size // self.homemates_avg)):
            self.create_house(social_stratum=i % 5)

        # number of business
        for i in np.arange(0, self.total_business):
            self.create_business(social_stratum=5 - (i % 5))

        # Initial infected population
        for i in np.arange(
                0, int(self.population_size * self.initial_infected_perc)):
            self.create_agent(Status.Infected, infected_time=5)

        # Initial immune population
        for i in np.arange(
                0, int(self.population_size * self.initial_immune_perc)):
            self.create_agent(Status.Recovered_Immune)

        # Initial susceptible population
        for i in np.arange(0, self.population_size - len(self.population)):
            self.create_agent(Status.Susceptible, social_stratum=i % 5)

        # Share the common wealth of 10^4 among the population, according each agent social stratum

        self.government.wealth = self.total_wealth * self.public_gdp_share

        for quintile in [0, 1, 2, 3, 4]:

            _houses = [
                x for x in filter(lambda x: x.social_stratum == quintile,
                                  self.houses)
            ]
            nhouses = len(_houses)

            if nhouses == 0:
                self.create_house(social_stratum=quintile)
                _houses = [self.houses[-1]]
                nhouses = 1

            if self.total_business > 5:
                btotal = lorenz_curve[quintile] * (self.total_wealth *
                                                   self.business_gdp_share)
                bqty = max(
                    1.0,
                    np.sum([
                        1.0 for a in self.business
                        if a.social_stratum == quintile
                    ]))
            else:
                btotal = self.total_wealth * self.business_gdp_share
                bqty = self.total_business

            ag_share = btotal / bqty
            for bus in filter(lambda x: x.social_stratum == quintile,
                              self.business):
                bus.wealth = ag_share

            ptotal = lorenz_curve[quintile] * self.total_wealth * (
                1 - (self.public_gdp_share + self.business_gdp_share))

            pqty = max(
                1.0,
                np.sum([
                    1 for a in self.population if a.social_stratum == quintile
                    and a.economical_status == EconomicalStatus.Active
                ]))
            ag_share = ptotal / pqty

            for agent in filter(lambda x: x.social_stratum == quintile,
                                self.population):

                # distribute wealth

                if agent.economical_status == EconomicalStatus.Active:
                    agent.wealth = ag_share
                    agent.incomes = basic_income[
                        agent.social_stratum] * self.minimum_income

                    # distribute employ

                    unemployed_test = np.random.rand()

                    if unemployed_test >= self.unemployment_rate:
                        ix = np.random.randint(0, self.total_business)
                        self.business[ix].hire(agent)

                agent.expenses = basic_income[
                    agent.social_stratum] * self.minimum_expense

                #distribute habitation

                homeless_test = np.random.rand()

                if not (quintile == 0 and homeless_test <= self.homeless_rate):
                    for kp in range(0, 5):
                        ix = np.random.randint(0, nhouses)
                        house = _houses[ix]
                        if house.size < self.homemates_avg + self.homemates_std:
                            house.append_mate(agent)
                            continue
                    if agent.house is None:
                        ix = np.random.randint(0, nhouses)
                        self.houses[ix].append_mate(agent)
class GraphSimulation(Simulation):
    def __init__(self, **kwargs):
        super(GraphSimulation, self).__init__(**kwargs)
        self.total_population = kwargs.get('total_population', 0)
        self.total_business = kwargs.get('total_business', 10)
        self.business_distance = kwargs.get('business_distance', 10)
        self.government = None
        self.business = []
        self.houses = []
        self.healthcare = None
        self.homeless_rate = kwargs.get("homeless_rate", 0.01)
        self.unemployment_rate = kwargs.get("unemployment_rate", 0.09)
        self.homemates_avg = kwargs.get("homemates_avg", 3)
        self.homemates_std = kwargs.get("homemates_std", 1)
        self.iteration = -1
        self.callbacks = kwargs.get('callbacks', {})
        self.public_gdp_share = kwargs.get('public_gdp_share', 0.1)
        self.business_gdp_share = kwargs.get('business_gdp_share', 0.5)
        self.incubation_time = kwargs.get('incubation_time', 5)
        self.contagion_time = kwargs.get('contagion_time', 10)
        self.recovering_time = kwargs.get('recovering_time', 20)

    def register_callback(self, event, action):
        self.callbacks[event] = action

    def callback(self, event, *args):
        if event in self.callbacks:
            return self.callbacks[event](*args)

        return False

    def get_unemployed(self):
        return [
            p for p in self.population
            if p.is_unemployed() and p.status != Status.Death
            and p.infected_status == InfectionSeverity.Asymptomatic
        ]

    def get_homeless(self):
        return [
            p for p in self.population
            if p.is_homeless() and p.status != Status.Death
            and p.infected_status == InfectionSeverity.Asymptomatic
        ]

    def create_business(self, social_stratum=None):
        x, y = self.random_position()
        if social_stratum is None:
            social_stratum = int(np.random.rand(1) * 100 // 20)
        self.business.append(
            Business(
                x=x,
                y=y,
                status=Status.Susceptible,
                social_stratum=social_stratum,
                #fixed_expenses=(social_stratum+1)*self.minimum_expense
                #fixed_expenses=self.minimum_expense / (5 - social_stratum)
                environment=self))

    def create_house(self, social_stratum=None):
        x, y = self.random_position()
        if social_stratum is None:
            social_stratum = int(np.random.rand(1) * 100 // 20)
        house = House(
            x=x,
            y=y,
            status=Status.Susceptible,
            social_stratum=social_stratum,
            #fixed_expenses=(social_stratum+1)*self.minimum_expense/(self.homemates_avg*10
            environment=self)
        self.callback('on_create_house', house)
        self.houses.append(house)

    def create_agent(self, status, social_stratum=None, infected_time=0):
        """
        Create a new agent with the given status

        :param infected_time:
        :param social_stratum:
        :param status: a value of agents.Status enum
        :return: the newly created agent
        """

        age = int(np.random.beta(2, 5, 1) * 100)
        if social_stratum is None:
            social_stratum = int(np.random.rand(1) * 100 // 20)
        person = Person(age=age,
                        status=status,
                        social_stratum=social_stratum,
                        infected_time=infected_time,
                        environment=self)
        self.callback('on_create_person', person)
        self.population.append(person)

    def initialize(self):
        """
        Initializate the Simulation by creating its population of agents
        """
        x, y = self.random_position()
        self.healthcare = Business(x=x,
                                   y=y,
                                   status=Status.Susceptible,
                                   type=AgentType.Healthcare,
                                   environment=self)
        self.healthcare.fixed_expenses += self.minimum_expense * 3
        x, y = self.random_position()
        self.government = Business(x=x,
                                   y=y,
                                   status=Status.Susceptible,
                                   type=AgentType.Government,
                                   social_stratum=4,
                                   price=1.0,
                                   environment=self)
        self.government.fixed_expenses += self.population_size * (
            self.minimum_expense * 0.05)

        #number of houses
        for i in np.arange(0, int(self.population_size // self.homemates_avg)):
            self.create_house(social_stratum=i % 5)

        # number of business
        for i in np.arange(0, self.total_business):
            self.create_business(social_stratum=5 - (i % 5))

        # Initial infected population
        for i in np.arange(
                0, int(self.population_size * self.initial_infected_perc)):
            self.create_agent(Status.Infected, infected_time=5)

        # Initial immune population
        for i in np.arange(
                0, int(self.population_size * self.initial_immune_perc)):
            self.create_agent(Status.Recovered_Immune)

        # Initial susceptible population
        for i in np.arange(0, self.population_size - len(self.population)):
            self.create_agent(Status.Susceptible, social_stratum=i % 5)

        # Share the common wealth of 10^4 among the population, according each agent social stratum

        self.government.wealth = self.total_wealth * self.public_gdp_share

        for quintile in [0, 1, 2, 3, 4]:

            _houses = [
                x for x in filter(lambda x: x.social_stratum == quintile,
                                  self.houses)
            ]
            nhouses = len(_houses)

            if nhouses == 0:
                self.create_house(social_stratum=quintile)
                _houses = [self.houses[-1]]
                nhouses = 1

            if self.total_business > 5:
                btotal = lorenz_curve[quintile] * (self.total_wealth *
                                                   self.business_gdp_share)
                bqty = max(
                    1.0,
                    np.sum([
                        1.0 for a in self.business
                        if a.social_stratum == quintile
                    ]))
            else:
                btotal = self.total_wealth * self.business_gdp_share
                bqty = self.total_business

            ag_share = btotal / bqty
            for bus in filter(lambda x: x.social_stratum == quintile,
                              self.business):
                bus.wealth = ag_share

            ptotal = lorenz_curve[quintile] * self.total_wealth * (
                1 - (self.public_gdp_share + self.business_gdp_share))

            pqty = max(
                1.0,
                np.sum([
                    1 for a in self.population if a.social_stratum == quintile
                    and a.economical_status == EconomicalStatus.Active
                ]))
            ag_share = ptotal / pqty

            for agent in filter(lambda x: x.social_stratum == quintile,
                                self.population):

                # distribute wealth

                if agent.economical_status == EconomicalStatus.Active:
                    agent.wealth = ag_share
                    agent.incomes = basic_income[
                        agent.social_stratum] * self.minimum_income

                    # distribute employ

                    unemployed_test = np.random.rand()

                    if unemployed_test >= self.unemployment_rate:
                        ix = np.random.randint(0, self.total_business)
                        self.business[ix].hire(agent)

                agent.expenses = basic_income[
                    agent.social_stratum] * self.minimum_expense

                #distribute habitation

                homeless_test = np.random.rand()

                if not (quintile == 0 and homeless_test <= self.homeless_rate):
                    for kp in range(0, 5):
                        ix = np.random.randint(0, nhouses)
                        house = _houses[ix]
                        if house.size < self.homemates_avg + self.homemates_std:
                            house.append_mate(agent)
                            continue
                    if agent.house is None:
                        ix = np.random.randint(0, nhouses)
                        self.houses[ix].append_mate(agent)

    def execute(self):

        self.iteration += 1

        if self.callback('on_execute', self):
            return

        #print(self.iteration)

        bed = bed_time(self.iteration)
        work = work_time(self.iteration)
        free = free_time(self.iteration)
        lunch = lunch_time(self.iteration)
        new_dy = new_day(self.iteration)
        work_dy = work_day(self.iteration)
        new_mth = new_month(self.iteration)

        #if new_dy:
        #    print("Day {}".format(self.iteration // 24))

        for agent in filter(lambda x: x.status != Status.Death,
                            self.population):

            if bed:
                agent.move_to_home()

            elif lunch or free or not work_dy:
                agent.move_freely()

            elif work_dy and work:
                agent.move_to_work()

            #agent.x = self._xclip(agent.x)
            #agent.y = self._yclip(agent.y)

            if new_dy:
                agent.update()

            if agent.infected_status == InfectionSeverity.Asymptomatic:
                for bus in filter(lambda x: x != agent.employer,
                                  self.business):
                    if distance(agent, bus) <= self.business_distance:
                        bus.supply(agent)

        for bus in filter(lambda b: b.open, self.business):
            if new_dy:
                bus.update()

            if self.iteration > 1 and new_mth:
                bus.accounting()

        for house in filter(lambda h: h.size > 0, self.houses):
            if new_dy:
                house.update()

            if self.iteration > 1 and new_mth:
                house.accounting()

        if new_dy:
            self.government.update()
            self.healthcare.update()

        if self.iteration > 1 and new_mth:
            self.government.accounting()

        contacts = []

        for i in np.arange(0, self.population_size):
            for j in np.arange(i + 1, self.population_size):
                ai = self.population[i]
                aj = self.population[j]
                if ai.status == Status.Death or ai.status == Status.Death:
                    continue

                if distance(ai, aj) <= self.contagion_distance:
                    contacts.append((i, j))

        for pair in contacts:
            ai = self.population[pair[0]]
            aj = self.population[pair[1]]
            self.contact(ai, aj)
            self.contact(aj, ai)

        self.statistics = None

    def contact(self, agent1, agent2):
        """
        Performs the actions needed when two agents get in touch.

        :param agent1: an instance of agents.Agent
        :param agent2: an instance of agents.Agent
        """

        if self.callback('on_contact', agent1, agent2):
            return

        if agent1.status == Status.Susceptible and agent2.status == Status.Infected:
            low = np.random.randint(-1, 1)
            up = np.random.randint(-1, 1)
            if agent2.infected_time >= self.incubation_time + low \
                    and agent2.infected_time <= self.contagion_time + up:
                contagion_test = np.random.random()
                #agent1.infection_status = InfectionSeverity.Exposed
                if contagion_test <= self.contagion_rate:
                    agent1.status = Status.Infected
                    agent1.infection_status = InfectionSeverity.Asymptomatic

    def get_statistics(self, kind='all'):
        if self.statistics is None:
            self.statistics = {}
            for quintile in [0, 1, 2, 3, 4]:
                #self.statistics['Q{}'.format(quintile + 1)] = np.sum(
                #    [a.wealth for a in self.population if a.social_stratum == quintile \
                #     and a.economical_status == EconomicalStatus.Active]) / self.total_wealth
                self.statistics['Q{}'.format(quintile + 1)] = np.sum(
                    h.wealth for h in self.houses
                    if h.social_stratum == quintile) / self.total_wealth
            self.statistics['Business'] = np.sum(
                [b.wealth for b in self.business]) / self.total_wealth
            self.statistics[
                'Government'] = self.government.wealth / self.total_wealth

            for status in Status:
                self.statistics[status.name] = np.sum([
                    1 for a in self.population if a.status == status
                ]) / self.population_size

            for infected_status in filter(
                    lambda x: x != InfectionSeverity.Exposed,
                    InfectionSeverity):
                self.statistics[infected_status.name] = np.sum([
                    1 for a in self.population if a.infected_status ==
                    infected_status and a.status != Status.Death
                ]) / self.population_size

        return self.filter_stats(kind)