Example #1
0
def test_story_autoreset_true_not_dropping_rows_should_reset_all_timers():
    # in case an story is configured to perform an auto-reset, after one
    # execution,
    #  - all executed rows should have a timer back to some positive value
    #  - all non executed rows should have gone down one tick

    population = Population(circus=None,
                            size=10,
                            ids_gen=SequencialGenerator(prefix="ac_",
                                                        max_length=1))

    # 5 populations should trigger in 2 ticks, and 5 more
    init_timers = pd.Series([2] * 5 + [1] * 5, index=population.ids)
    timers_gen = MockTimerGenerator(init_timers)

    story = Story(
        name="tested",
        initiating_population=population,
        member_id_field="ac_id",

        # forcing the timer of all populations to be initialized to 0
        timer_gen=timers_gen,
        auto_reset_timer=True)

    # empty operation list as initialization
    story.set_operations(Operation())

    # initial timers should be those provided by the generator
    assert story.timer["remaining"].equals(init_timers)

    # after one execution, no population id has been selected and all counters
    # are decreased by 1
    story.execute()
    assert story.timer["remaining"].equals(init_timers - 1)

    # this time, the last 5 should have executed => go back up to 1. The
    # other 5 should now be at 0, ready to execute at next step
    story.execute()
    expected_timers = pd.Series([0] * 5 + [1] * 5, index=population.ids)
    assert story.timer["remaining"].equals(expected_timers)
Example #2
0
def _init_pos_product(circus, product, description):
    """
    Initialize the required stock and generators for this
    """

    logging.info(
        "Building a generator of {} POS bulk purchase size".format(product))
    bulk_size_gen = NumpyRandomGenerator(
        method="choice",
        a=description["pos_bulk_purchase_sizes"],
        p=description["pos_bulk_purchase_sizes_dist"],
        seed=next(circus.seeder))
    circus.attach_generator("pos_{}_bulk_size_gen".format(product),
                            bulk_size_gen)

    logging.info(
        "Building a generators of {} POS initial stock size".format(product))
    if "pos_init_distro" in description:
        logging.info(" using pre-defined initial distribution")
        gen_namespace, gen_id = description["pos_init_distro"].split("/")

        # TODO: with the new save/load, this is now a mere numpyGenerator
        init_stock_size_gen = db.load_empirical_discrete_generator(
            namespace=gen_namespace, gen_id=gen_id, seed=next(circus.seeder))
    else:
        logging.info(" using bulk size distribution")
        init_stock_size_gen = bulk_size_gen
    circus.attach_generator("pos_{}_init_stock_size_gen".format(product),
                            init_stock_size_gen)

    logging.info("Building a generator of {} ids".format(product))
    product_id_gen = SequencialGenerator(prefix="{}_".format(product))
    circus.attach_generator("{}_id_gen".format(product), product_id_gen)

    logging.info("Initializing POS {} stock".format(product))
    stock_gen = init_stock_size_gen.flatmap(
        DependentBulkGenerator(element_generator=product_id_gen))

    circus.actors["pos"].create_stock_relationship_grp(
        name=product, stock_bulk_gen=stock_gen)
Example #3
0
    def _add_person_population(self):

        id_gen = SequencialGenerator(prefix="PERSON_")
        age_gen = NumpyRandomGenerator(method="normal", loc=3, scale=5,
                                       seed=next(self.seeder))
        name_gen = FakerGenerator(method="name", seed=next(self.seeder))

        person = self.create_population(name="person", size=1000, ids_gen=id_gen)
        person.create_attribute("NAME", init_gen=name_gen)
        person.create_attribute("AGE", init_gen=age_gen)

        quote_generator = FakerGenerator(method="sentence", nb_words=6, variable_nb_words=True,
                                         seed=next(self.seeder))

        quotes_rel = self.populations["person"].create_relationship("quotes")

        for w in range(4):
            quotes_rel.add_relations(
                from_ids=person.ids,
                to_ids=quote_generator.generate(size=person.size),
                weights=w
            )
Example #4
0
def step2():

    example1 = circus.Circus(name="example1",
                             master_seed=12345,
                             start=pd.Timestamp("1 Jan 2017 00:00"),
                             step_duration=pd.Timedelta("1h"))

    person = example1.create_population(
        name="person",
        size=1000,
        ids_gen=SequencialGenerator(prefix="PERSON_"))

    person.create_attribute("NAME",
                            init_gen=FakerGenerator(method="name",
                                                    seed=next(
                                                        example1.seeder)))

    hello_world = example1.create_story(
        name="hello_world",
        initiating_population=person,
        member_id_field="PERSON_ID",

        # after each story, reset the timer to 0, so that it will get
        # executed again at the next clock tick (next hour)
        timer_gen=ConstantDependentGenerator(value=0))

    hello_world.set_operations(
        person.ops.lookup(id_field="PERSON_ID", select={"NAME": "NAME"}),
        ConstantGenerator(value="hello world").ops.generate(named_as="HELLO"),
        example1.clock.ops.timestamp(named_as="TIME"),
        FieldLogger(log_id="hello"))

    example1.run(duration=pd.Timedelta("48h"),
                 log_output_folder="output/example1",
                 delete_existing_logs=True)

    with open("output/example1/hello.csv") as f:
        print("Logged {} lines".format(len(f.readlines()) - 1))
Example #5
0
def test_insert_population_value_for_existing_and_new_populations_should_update_and_add_values(
):

    # copy of dummy population that will be updated
    tested_population = Population(circus=None,
                                   size=10,
                                   ids_gen=SequencialGenerator(max_length=1,
                                                               prefix="a_"))
    ages = [10, 20, 40, 10, 100, 98, 12, 39, 76, 23]
    tested_population.create_attribute("age", init_values=ages)
    city = ["a", "b", "b", "a", "d", "e", "r", "a", "z", "c"]
    tested_population.create_attribute("city", init_values=city)

    current = tested_population.get_attribute_values("age",
                                                     ["a_0", "a_7", "a_9"])
    assert current.tolist() == [10, 39, 23]

    update = pd.DataFrame(
        {
            "age": [139, 123, 54, 25],
            "city": ["city_7", "city_9", "city_11", "city_10"]
        },
        index=["a_7", "a_9", "a_11", "a_10"])

    tested_population.update(update)

    # we should have 2 new populations
    assert tested_population.ids.shape[0] == 12

    updated_age = tested_population.get_attribute_values(
        "age", ["a_0", "a_7", "a_9", "a_10", "a_11"])
    updated_city = tested_population.get_attribute_values(
        "city", ["a_0", "a_7", "a_9", "a_10", "a_11"])

    assert updated_age.tolist() == [10, 139, 123, 25, 54]
    assert updated_city.tolist() == [
        "a", "city_7", "city_9", "city_10", "city_11"
    ]
Example #6
0
def create_circus_with_population():
    example_circus = circus.Circus(
        name="example",
        master_seed=12345,
        start=pd.Timestamp("1 Jan 2017 00:00"),
        step_duration=pd.Timedelta("1h"))

    person = example_circus.create_population(
        name="person", size=1000,
        ids_gen=SequencialGenerator(prefix="PERSON_"))

    person.create_attribute(
        "NAME",
        init_gen=FakerGenerator(method="name",
                                seed=next(example_circus.seeder)))

    person.create_attribute(
        "age",
        init_gen=NumpyRandomGenerator(
            method="normal", loc=35, scale=5,
            seed=next(example_circus.seeder)))

    return example_circus
Example #7
0
def test_populations_with_zero_activity_should_never_have_positive_timer():

    population = Population(circus=None,
                            size=10,
                            ids_gen=SequencialGenerator(prefix="ac_",
                                                        max_length=1))

    story = Story(
        name="tested",
        initiating_population=population,
        # fake generator that assign zero activity to 3 populations
        activity_gen=ConstantsMockGenerator([1, 1, 1, 1, 0, 1, 1, 0, 0, 1]),
        timer_gen=ConstantDependentGenerator(value=10),
        member_id_field="")

    story.reset_timers()

    # all non zero populations should have been through the profiler => timer to 10
    # all others should be locked to -1, to reflect that activity 0 never
    # triggers anything
    expected_timers = [10, 10, 10, 10, -1, 10, 10, -1, -1, 10]

    assert expected_timers == story.timer["remaining"].tolist()
Example #8
0
def run_test_scenario_1(clock_step, simulation_duration, n_stories, per,
                        log_folder):

    circus = Circus(name="tested_circus",
                    master_seed=1,
                    start=pd.Timestamp("8 June 2016"),
                    step_duration=pd.Timedelta(clock_step))

    population = circus.create_population(name="a",
                                          size=1000,
                                          ids_gen=SequencialGenerator(
                                              max_length=3, prefix="id_"))

    daily_profile = CyclicTimerGenerator(
        clock=circus.clock,
        config=CyclicTimerProfile(profile=[1] * 24,
                                  profile_time_steps="1h",
                                  start_date=pd.Timestamp("8 June 2016")),
        seed=1234)

    # each of the 500 populations have a constant 12 logs per day rate
    activity_gen = ConstantGenerator(
        value=daily_profile.activity(n=n_stories, per=per))

    # just a dummy operation to produce some logs
    story = circus.create_story(name="test_story",
                                initiating_population=population,
                                member_id_field="some_id",
                                timer_gen=daily_profile,
                                activity_gen=activity_gen)

    story.set_operations(circus.clock.ops.timestamp(named_as="TIME"),
                         FieldLogger(log_id="the_logs"))

    circus.run(duration=pd.Timedelta(simulation_duration),
               log_output_folder=log_folder)
Example #9
0
def create_field_agents(circus, params):

    logging.info(" adding {} field agents".format(params["n_field_agents"]))

    field_agents = circus.create_population(
        name="field_agents",
        size=params["n_field_agents"],
        ids_gen=SequencialGenerator(prefix="FA_"))

    logging.info(" adding mobility relationships to field agents")

    mobility_rel = field_agents.create_relationship("POSSIBLE_SITES")

    # TODO: make sure the number of sites per field agent is "reasonable"
    mobility_df = pd.DataFrame.from_records(make_random_bipartite_data(
        field_agents.ids,
        circus.actors["sites"].ids,
        0.4,
        seed=next(circus.seeder)),
                                            columns=["FA_ID", "SID"])

    mobility_weight_gen = NumpyRandomGenerator(method="exponential",
                                               scale=1.,
                                               seed=next(circus.seeder))

    mobility_rel.add_relations(from_ids=mobility_df["FA_ID"],
                               to_ids=mobility_df["SID"],
                               weights=mobility_weight_gen.generate(
                                   mobility_df.shape[0]))

    # Initialize the mobility by allocating one first random site to each
    # field agent among its network
    field_agents.create_attribute(name="CURRENT_SITE",
                                  init_relationship="POSSIBLE_SITES")

    return field_agents
Example #10
0
    def create_subs_and_sims(self):
        """
        Creates the subs and sims + a relationship between them + an agent
        relationship.

        We have at least one sim per subs: sims.size >= subs.size

        The sims population contains the "OPERATOR", "MAIN_ACCT" and "MSISDN" attributes.

        The subs population has a "SIMS" relationship that points to the sims owned by
        each subs.

        The sims population also has a relationship to the set of agents where this sim
        can be topped up.
        """

        npgen = RandomState(seed=next(self.seeder))

        # subs are empty here but will receive a "CELLS" and "EXCITABILITY"
        # attributes later on
        subs = self.create_population(
            name="subs",
            size=self.params["n_subscribers"],
            ids_gen=SequencialGenerator(prefix="SUBS_"))

        number_of_operators = npgen.choice(a=range(1, 5), size=subs.size)
        operator_ids = build_ids(size=4, prefix="OPERATOR_", max_length=1)

        def pick_operators(qty):
            """
            randomly choose a set of unique operators of specified size
            """
            return npgen.choice(a=operator_ids,
                                p=[.8, .05, .1, .05],
                                size=qty,
                                replace=False).tolist()

        # set of operators of each subs
        subs_operators_list = map(pick_operators, number_of_operators)

        # Dataframe with 4 columns for the 1rst, 2nd,... operator of each subs.
        # Since subs_operators_list don't all have the size, some entries of this
        # dataframe contains None, which are just discarded by the stack() below
        subs_operators_df = pd.DataFrame(data=list(subs_operators_list),
                                         index=subs.ids)

        # same info, vertically: the index contains the sub id (with duplicates)
        # and "operator" one of the operators of this subs
        subs_ops_mapping = subs_operators_df.stack()
        subs_ops_mapping.index = subs_ops_mapping.index.droplevel(level=1)

        # SIM population, each with an OPERATOR and MAIN_ACCT attributes
        sims = self.create_population(
            name="sims",
            size=subs_ops_mapping.size,
            ids_gen=SequencialGenerator(prefix="SIMS_"))
        sims.create_attribute("OPERATOR", init_values=subs_ops_mapping.values)
        recharge_gen = ConstantGenerator(value=1000.)
        sims.create_attribute(name="MAIN_ACCT", init_gen=recharge_gen)

        # keeping track of the link between population and sims as a relationship
        sims_of_subs = subs.create_relationship("SIMS")
        sims_of_subs.add_relations(from_ids=subs_ops_mapping.index,
                                   to_ids=sims.ids)

        msisdn_gen = MSISDNGenerator(
            countrycode="0032",
            prefix_list=["472", "473", "475", "476", "477", "478", "479"],
            length=6,
            seed=next(self.seeder))
        sims.create_attribute(name="MSISDN", init_gen=msisdn_gen)

        # Finally, adding one more relationship that defines the set of possible
        # shops where we can topup each SIM.
        # TODO: to make this a bit more realistic, we should probably generate
        # such relationship first from the subs to their favourite shops, and then
        # copy that info to each SIM, maybe with some fluctuations to account
        # for the fact that not all shops provide topups of all operators.
        agents = build_ids(self.params["n_agents"],
                           prefix="AGENT_",
                           max_length=3)

        agent_df = pd.DataFrame.from_records(make_random_bipartite_data(
            sims.ids, agents, 0.3, seed=next(self.seeder)),
                                             columns=["SIM_ID", "AGENT"])

        logging.info(" creating random sim/agent relationship ")
        sims_agents_rel = sims.create_relationship("POSSIBLE_AGENTS")

        agent_weight_gen = NumpyRandomGenerator(method="exponential",
                                                scale=1.,
                                                seed=next(self.seeder))

        sims_agents_rel.add_relations(from_ids=agent_df["SIM_ID"],
                                      to_ids=agent_df["AGENT"],
                                      weights=agent_weight_gen.generate(
                                          agent_df.shape[0]))

        return subs, sims, recharge_gen
Example #11
0
def step7():

    example1 = circus.Circus(name="example1",
                             master_seed=12345,
                             start=pd.Timestamp("1 Jan 2017 00:00"),
                             step_duration=pd.Timedelta("1h"))

    person = example1.create_population(
        name="person",
        size=1000,
        ids_gen=SequencialGenerator(prefix="PERSON_"))

    person.create_attribute("NAME",
                            init_gen=FakerGenerator(method="name",
                                                    seed=next(
                                                        example1.seeder)))
    person.create_attribute("POPULARITY",
                            init_gen=NumpyRandomGenerator(
                                method="uniform",
                                low=0,
                                high=1,
                                seed=next(example1.seeder)))

    sites = SequencialGenerator(prefix="SITE_").generate(1000)
    random_site_gen = NumpyRandomGenerator(method="choice",
                                           a=sites,
                                           seed=next(example1.seeder))

    allowed_sites = person.create_relationship(name="sites")

    # SITES ------------------

    # Add HOME sites
    allowed_sites.add_relations(from_ids=person.ids,
                                to_ids=random_site_gen.generate(person.size),
                                weights=0.4)

    # Add WORK sites
    allowed_sites.add_relations(from_ids=person.ids,
                                to_ids=random_site_gen.generate(person.size),
                                weights=0.3)

    # Add OTHER sites
    for i in range(3):
        allowed_sites \
            .add_relations(from_ids=person.ids,
                           to_ids=random_site_gen.generate(person.size),
                           weights=0.1)

    # FRIENDS ------------------

    friends = person.create_relationship(name="friends")

    friends_df = pd.DataFrame.from_records(
        make_random_bipartite_data(
            person.ids,
            person.ids,
            p=0.005,  # probability for a node to be connected to
            # another one : 5 friends on average = 5/1000
            seed=next(example1.seeder)),
        columns=["A", "B"])

    friends.add_relations(from_ids=friends_df["A"], to_ids=friends_df["B"])

    # PRICE ------------------

    def price(story_data):

        result = pd.DataFrame(index=story_data.index)

        result["PRICE"] = story_data["DURATION"] * 0.05
        result["CURRENCY"] = "EUR"

        return result

    # STORIES ------------------

    hello_world = example1.create_story(
        name="hello_world",
        initiating_population=person,
        member_id_field="PERSON_ID",

        # after each story, reset the timer to 0, so that it will get
        # executed again at the next clock tick (next hour)
        timer_gen=ConstantDependentGenerator(value=0))

    duration_gen = NumpyRandomGenerator(method="exponential",
                                        scale=60,
                                        seed=next(example1.seeder))

    hello_world.set_operations(
        person.ops.lookup(id_field="PERSON_ID", select={"NAME": "NAME"}),
        ConstantGenerator(value="hello world").ops.generate(named_as="HELLO"),
        duration_gen.ops.generate(named_as="DURATION"),
        friends.ops.select_one(
            from_field="PERSON_ID",
            named_as="COUNTERPART_ID",
            weight=person.get_attribute_values("POPULARITY"),
            # For people that do not have friends, it will try to find
            # the POPULARITY attribute of a None and crash miserably
            # Adding this flag will discard people that do not have friends
            discard_empty=True),
        person.ops.lookup(id_field="COUNTERPART_ID",
                          select={"NAME": "COUNTER_PART_NAME"}),
        allowed_sites.ops.select_one(from_field="PERSON_ID", named_as="SITE"),
        allowed_sites.ops.select_one(from_field="COUNTERPART_ID",
                                     named_as="COUNTERPART_SITE"),
        Apply(source_fields=["DURATION", "SITE", "COUNTERPART_SITE"],
              named_as=["PRICE", "CURRENCY"],
              f=price,
              f_args="dataframe"),
        example1.clock.ops.timestamp(named_as="TIME"),
        FieldLogger(log_id="hello"))

    example1.run(duration=pd.Timedelta("48h"),
                 log_output_folder="output/example1",
                 delete_existing_logs=True)

    with open("output/example1/hello.csv") as f:
        print("Logged {} lines".format(len(f.readlines()) - 1))
Example #12
0
def step5():

    example1 = circus.Circus(name="example1",
                             master_seed=12345,
                             start=pd.Timestamp("1 Jan 2017 00:00"),
                             step_duration=pd.Timedelta("1h"))

    person = example1.create_population(
        name="person",
        size=1000,
        ids_gen=SequencialGenerator(prefix="PERSON_"))

    person.create_attribute("NAME",
                            init_gen=FakerGenerator(method="name",
                                                    seed=next(
                                                        example1.seeder)))

    sites = SequencialGenerator(prefix="SITE_").generate(1000)
    random_site_gen = NumpyRandomGenerator(method="choice",
                                           a=sites,
                                           seed=next(example1.seeder))

    allowed_sites = person.create_relationship(name="sites")

    # Add HOME sites
    allowed_sites.add_relations(from_ids=person.ids,
                                to_ids=random_site_gen.generate(person.size),
                                weights=0.4)

    # Add WORK sites
    allowed_sites.add_relations(from_ids=person.ids,
                                to_ids=random_site_gen.generate(person.size),
                                weights=0.3)

    # Add OTHER sites
    for i in range(3):
        allowed_sites \
            .add_relations(from_ids=person.ids,
                           to_ids=random_site_gen.generate(person.size),
                           weights=0.1)

    hello_world = example1.create_story(
        name="hello_world",
        initiating_population=person,
        member_id_field="PERSON_ID",

        # after each story, reset the timer to 0, so that it will get
        # executed again at the next clock tick (next hour)
        timer_gen=ConstantDependentGenerator(value=0))

    duration_gen = NumpyRandomGenerator(method="exponential",
                                        scale=60,
                                        seed=next(example1.seeder))

    hello_world.set_operations(
        person.ops.lookup(id_field="PERSON_ID", select={"NAME": "NAME"}),
        ConstantGenerator(value="hello world").ops.generate(named_as="HELLO"),
        duration_gen.ops.generate(named_as="DURATION"),
        allowed_sites.ops.select_one(from_field="PERSON_ID", named_as="SITE"),
        example1.clock.ops.timestamp(named_as="TIME"),
        FieldLogger(log_id="hello"))

    example1.run(duration=pd.Timedelta("48h"),
                 log_output_folder="output/example1",
                 delete_existing_logs=True)

    with open("output/example1/hello.csv") as f:
        print("Logged {} lines".format(len(f.readlines()) - 1))
Example #13
0
import path
import pandas as pd
import os
import pytest

from trumania.core.random_generators import SequencialGenerator
from trumania.core.population import Population

dummy_population = Population(circus=None,
                              size=10,
                              ids_gen=SequencialGenerator(max_length=1,
                                                          prefix="id_"))

ages = [10, 20, 40, 10, 100, 98, 12, 39, 76, 23]
dummy_population.create_attribute("age", init_values=ages)

city = ["a", "b", "b", "a", "d", "e", "r", "a", "z", "c"]
dummy_population.create_attribute("city", init_values=city)

# some fake story data with an index corresponding to another population
# => simulates an story triggered by that other population
# the column "NEIGHBOUR" contains value that point to the dummy population, with
# a duplication (id2)
story_data = pd.DataFrame(
    {
        "A": ["a1", "a2", "a3", "a4"],
        "B": ["b1", "b2", "b3", "b4"],
        "NEIGHBOUR": ["id_2", "id_4", "id_7", "id_2"],
        "COUSINS": [
            ["id_2", "id_4", "id_7", "id_2"],
            ["id_3"],
Example #14
0
val = 1 / num_log_per_hr
step_dur = "{0}h".format(val)
#7am-7pm = 13 hrs
num_log = 13 * (num_log_per_hr)

setup_logging()
start_date = pd.Timestamp("09 Aug 2020 00:00:00")

example1 = circus.Circus(name="example1",
                         master_seed=123456,
                         start=start_date,
                         step_duration=pd.Timedelta(step_dur))

person = example1.create_population(name="person",
                                    size=num_user,
                                    ids_gen=SequencialGenerator(prefix=""))

person.create_attribute("NAME",
                        init_gen=FakerGenerator(method="name",
                                                seed=next(example1.seeder)))

activity = (100, 100, 100, 100, 54, 54, 54, 26, 20, 22)
normed_activity = [float(i) / sum(activity) for i in activity]
"""
sites = SequencialGenerator(prefix="").generate(num_sites)										
random_site_gen = NumpyRandomGenerator(method="choice", a=sites,
                                        seed=next(example1.seeder),
                                        p=normed_activity)

allowed_sites = person.create_relationship(name="sites")
Example #15
0
def test_scenario_transiting_to_state_with_0_back_to_default_prob_should_remain_there(
):
    """
    we create an story with a transit_to_state operation and 0 probability
    of going back to normal => after the execution, all triggered populations should
    still be in that starte
    """

    population = Population(circus=None,
                            size=10,
                            ids_gen=SequencialGenerator(prefix="ac_",
                                                        max_length=1))

    # here we are saying that some story on populations 5 to 9 is triggering a
    # state change on populations 0 to 4
    active_ids_gens = ConstantsMockGenerator(values=[np.nan] * 5 +
                                             population.ids[:5].tolist())

    excited_state_gens = ConstantsMockGenerator(values=[np.nan] * 5 +
                                                ["excited"] * 5)

    excited_call_activity = ConstantGenerator(value=10)

    # forcing to stay excited
    back_to_normal_prob = ConstantGenerator(value=0)

    story = Story(
        name="tested",
        initiating_population=population,
        member_id_field="ac_id",
        states={
            "excited": {
                "activity": excited_call_activity,
                "back_to_default_probability": back_to_normal_prob
            }
        },

        # forcing the timer of all populations to be initialized to 0
        timer_gen=ConstantDependentGenerator(value=0))

    story.set_operations(
        # first 5 population are "active"
        active_ids_gens.ops.generate(named_as="active_ids"),
        excited_state_gens.ops.generate(named_as="new_state"),

        # forcing a transition to "excited" state of the 5 populations
        story.ops.transit_to_state(member_id_field="active_ids",
                                   state_field="new_state"))

    # before any execution, the state should be default for all
    assert ["default"] * 10 == story.timer["state"].tolist()

    logs = story.execute()

    # no logs are expected as output
    assert logs == {}

    # the first 5 populations should still be in "excited", since
    # "back_to_normal_probability" is 0, the other 5 should not have
    # moved
    expected_state = ["excited"] * 5 + ["default"] * 5
    assert expected_state == story.timer["state"].tolist()
import logging
import pandas as pd
from tabulate import tabulate

from trumania.core import circus, operations
from trumania.core.random_generators import SequencialGenerator, FakerGenerator, NumpyRandomGenerator, ConstantDependentGenerator, ConstantGenerator
import trumania.core.util_functions as util_functions

util_functions.setup_logging()

example_circus = circus.Circus(name="example",
                               master_seed=12345,
                               start=pd.Timestamp("1 Jan 2017 00:00"),
                               step_duration=pd.Timedelta("1h"))

id_gen = SequencialGenerator(prefix="PERSON_")
age_gen = NumpyRandomGenerator(method="normal",
                               loc=3,
                               scale=5,
                               seed=next(example_circus.seeder))
name_gen = FakerGenerator(method="name", seed=next(example_circus.seeder))

person = example_circus.create_population(name="person",
                                          size=1000,
                                          ids_gen=id_gen)
person.create_attribute("NAME", init_gen=name_gen)
person.create_attribute("AGE", init_gen=age_gen)

hello_world = example_circus.create_story(
    name="hello_world",
    initiating_population=example_circus.populations["person"],
from trumania.core import circus
import trumania.core.util_functions as util_functions
from trumania.core.random_generators import SequencialGenerator, FakerGenerator, NumpyRandomGenerator

util_functions.setup_logging()

logging.info("building circus")

example = circus.Circus(name="example",
                        master_seed=12345,
                        start=pd.Timestamp("1 Jan 2017 00:00"),
                        step_duration=pd.Timedelta("1h"))

person = example.create_population(
    name="person", size=1000, ids_gen=SequencialGenerator(prefix="PERSON_"))

person.create_attribute("NAME",
                        init_gen=FakerGenerator(method="name",
                                                seed=next(example.seeder)))

person.create_attribute("age",
                        init_gen=NumpyRandomGenerator(method="normal",
                                                      loc=35,
                                                      scale=5,
                                                      seed=next(
                                                          example.seeder)))

example.run(duration=pd.Timedelta("48h"),
            log_output_folder="output/example2",
            delete_existing_logs=True)
def add_purchase_actions(circus, params):

    customers = circus.actors["customers"]
    pos = circus.actors["pos"]
    sites = circus.actors["sites"]

    for product, description in params["products"].items():

        logging.info("creating customer {} purchase action".format(product))
        purchase_timer_gen = DefaultDailyTimerGenerator(
            circus.clock, next(circus.seeder))

        max_activity = purchase_timer_gen.activity(
            n=1,
            per=pd.Timedelta(
                days=description["customer_purchase_min_period_days"]))

        min_activity = purchase_timer_gen.activity(
            n=1,
            per=pd.Timedelta(
                days=description["customer_purchase_max_period_days"]))

        purchase_activity_gen = NumpyRandomGenerator(
            method="uniform",
            low=1 / max_activity,
            high=1 / min_activity,
            seed=next(circus.seeder)).map(f=lambda per: 1 / per)

        low_stock_bulk_purchase_trigger = DependentTriggerGenerator(
            value_to_proba_mapper=bounded_sigmoid(
                x_min=1,
                x_max=description["max_pos_stock_triggering_pos_restock"],
                shape=description["restock_sigmoid_shape"],
                incrementing=False))

        item_price_gen = NumpyRandomGenerator(method="choice",
                                              a=description["item_prices"],
                                              seed=next(circus.seeder))

        action_name = "customer_{}_purchase".format(product)
        purchase_action = circus.create_story(
            name=action_name,
            initiating_actor=customers,
            actorid_field="CUST_ID",
            timer_gen=purchase_timer_gen,
            activity_gen=purchase_activity_gen)

        purchase_action.set_operations(
            customers.ops.lookup(id_field="CUST_ID",
                                 select={"CURRENT_SITE": "SITE"}),
            sites.get_relationship("POS").ops.select_one(
                from_field="SITE",
                named_as="POS",
                weight=pos.get_attribute_values("ATTRACTIVENESS"),

                # TODO: this means customer in a location without POS do not buy
                # anything => we could add a re-try mechanism here
                discard_empty=True),
            sites.get_relationship("CELLS").ops.select_one(from_field="SITE",
                                                           named_as="CELL_ID"),

            # injecting geo level 2 and distributor in purchase action:
            # this is only required for approximating targets of that
            # distributor
            sites.ops.lookup(id_field="SITE",
                             select={
                                 "GEO_LEVEL_2": "geo_level2_id",
                                 "{}__dist_l1".format(product):
                                 "distributor_l1"
                             }),
            pos.get_relationship(product).ops.select_one(
                from_field="POS",
                named_as="INSTANCE_ID",
                pop=True,
                discard_empty=False),
            circus.actors[product].ops.select_one(named_as="PRODUCT_ID"),
            Apply(source_fields="INSTANCE_ID",
                  named_as="FAILED_SALE_OUT_OF_STOCK",
                  f=pd.isnull,
                  f_args="series"),
            SequencialGenerator(
                prefix="TX_CUST_{}".format(product)).ops.generate(
                    named_as="TX_ID"),
            item_price_gen.ops.generate(named_as="VALUE"),
            circus.clock.ops.timestamp(named_as="TIME"),
            FieldLogger(log_id=action_name),
            patterns.trigger_action_if_low_stock(
                circus,
                stock_relationship=pos.get_relationship(product),
                actor_id_field="POS",
                restock_trigger=low_stock_bulk_purchase_trigger,
                triggered_action_name="pos_{}_bulk_purchase".format(product)),
        )