Beispiel #1
0
def step1():

    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_"))

    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(
        ConstantGenerator(value="hello world").ops.generate(named_as="HELLO"),
        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))
Beispiel #2
0
def test_scenario_transiting_to_state_with_1_back_to_default_prob_should_go_back_to_normal(
):
    """
    similar test to above, though this time we are using
    back_to_normal_prob = 1 => all populations should be back to "normal" state
    at the end of the execution
    """

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

    # this one is slightly tricky: populations
    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)

    # this time we're forcing to stay in the transited state
    back_to_normal_prob = ConstantGenerator(value=1)

    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 == {}

    # this time, all populations should have transited back to "normal" at the end
    print(story.timer["state"].tolist())
    assert ["default"] * 10 == story.timer["state"].tolist()
Beispiel #3
0
def step4():
    """
    Woah, this got drastically slower
    """

    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")
    for i in range(5):
        allowed_sites \
            .add_relations(from_ids=person.ids,
                           to_ids=random_site_gen.generate(person.size))

    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))
Beispiel #4
0
def add_agent_stock_log_action(circus, params):
    """
    Adds an action per agent, recording the daily stock level and value of pos,
    dist_l1 and dist_l2.

    All those actions contribute to the same log_id
    """

    for agent_name in ["pos", "dist_l1", "dist_l2"]:

        agent = circus.actors[agent_name]

        for product in params["products"].keys():

            product_actor = circus.actors[product]
            stock_ratio = 1 / product_actor.size
            mean_price = np.mean(params["products"][product]["item_prices"])

            stock_levels_logs = circus.create_story(
                name="{}_{}_stock_log".format(agent_name, product),
                initiating_actor=agent,
                actorid_field="agent_id",
                timer_gen=ConstantDependentGenerator(
                    value=circus.clock.n_iterations(
                        duration=pd.Timedelta("24h")) - 1))

            stock_levels_logs.append_operations(
                circus.clock.ops.timestamp(named_as="TIME"),

                # We're supposed to report the stock level of every product id
                # => what we actually do is counting the full stock accross
                # all products and report randomly on one product id, scaling
                # down the stock volume.
                # It's ok if not all product id get reported every day
                product_actor.ops.select_one(named_as="product_id"),
                agent.get_relationship(product).ops.get_neighbourhood_size(
                    from_field="agent_id", named_as="full_stock_volume"),
                Apply(source_fields="full_stock_volume",
                      named_as="stock_volume",
                      f=lambda v: (v * stock_ratio).astype(np.int),
                      f_args="series"),

                # estimate stock value based on stock volume
                Apply(source_fields="stock_volume",
                      named_as="stock_value",
                      f=lambda v: v * mean_price,
                      f_args="series"),

                # The log_id (=> the resulting file name) is the same for all
                # actions => we just merge the stock level of all populations as
                # we go. I dare to find that pretty neat ^^
                FieldLogger(log_id="agent_stock_log",
                            cols=[
                                "TIME", "product_id", "agent_id",
                                "stock_volume", "stock_value"
                            ]))
Beispiel #5
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()
Beispiel #6
0
    person.create_attribute("age",
                            init_gen=NumpyRandomGenerator(
                                method="normal",
                                loc=3,
                                scale=5,
                                seed=next(example_circus.seeder)))

    return example_circus


example = create_circus_with_population()

hello_world = example.create_story(
    name="hello_world",
    initiating_population=example.populations["person"],
    member_id_field="PERSON_ID",
    timer_gen=ConstantDependentGenerator(value=1))

hello_world.set_operations(
    ConstantGenerator(value="hello world").ops.generate(named_as="MESSAGE"),
    FieldLogger(log_id="hello"))

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

with open("output/example3/hello.csv") as log:
    logging.info("some produced logs: \n\n" +
                 "".join(log.readlines(1000)[:10]))
Beispiel #7
0
    def __init__(self,
                 name,
                 initiating_population,
                 member_id_field,
                 activity_gen=ConstantGenerator(value=1.),
                 states=None,
                 timer_gen=ConstantDependentGenerator(value=-1),
                 auto_reset_timer=True):
        """
        :param name: name of this story

        :param initiating_population: population from which the operations of
        this story are started

        :param member_id_field: when building the story data, a field will be
            automatically inserted containing the member ids, with this name

        :param activity_gen: generator for the default activity levels of the
            population members for this story. Default: same level for
            everybody

        :param states: dictionary of states providing activity level for
            other states of the population + a probability level to transit
            back to the default state after each execution (NOT after each clock
            tick). Default: no supplementary states.

        :param timer_gen: timer generator: this must be a generator able to
            generate new timer values based on the activity level. Default:
            no such generator, in which case the timer never triggers this
            story.

        :param auto_reset_timer: if True, we automatically re-schedule a new
            execution for the same member id after at the end of the previous
            ont, by resetting the timer.
        """

        self.name = name
        self.triggering_population = initiating_population
        self.member_id_field = member_id_field
        self.size = initiating_population.size
        self.time_generator = timer_gen
        self.auto_reset_timer = auto_reset_timer
        self.forced_to_act_next = pd.Series()

        # activity and transition probability parameters, for each state
        self.params = pd.DataFrame({("default", "activity"): 0},
                                   index=initiating_population.ids)

        default_state = {
            "default": {
                "activity": activity_gen,
                "back_to_default_probability": ConstantGenerator(value=1.),
            }
        }
        for state, state_gens in merge_2_dicts(default_state, states).items():
            activity_vals = state_gens["activity"].generate(size=self.size)
            probs_vals = state_gens["back_to_default_probability"].generate(
                size=self.size)

            self.params[("activity", state)] = activity_vals
            self.params[("back_to_default_probability", state)] = probs_vals

        # current state and timer value for each population member id
        self.timer = pd.DataFrame({
            "state": "default",
            "remaining": -1
        },
                                  index=self.params.index)
        if self.auto_reset_timer:
            self.reset_timers()

        self.ops = self.StoryOps(self)

        # in case self.operations is not called, at least we have a basic
        # selection
        self.operation_chain = Chain()
Beispiel #8
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))
Beispiel #9
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()
Beispiel #10
0
def add_attractiveness_evolution_action(circus):

    pos = circus.actors["pos"]

    # once per day the attractiveness of each POS evolves according to the delta
    attractiveness_evolution = circus.create_story(
        name="attractiveness_evolution",
        initiating_actor=pos,
        actorid_field="POS_ID",

        # exactly one attractiveness evolution per day
        # caveat: all at the same time for now
        timer_gen=ConstantDependentGenerator(
            value=circus.clock.n_iterations(pd.Timedelta("1 day"))))

    def update_attract_base(df):
        base = df.apply(lambda row: row["ATTRACT_BASE"] + row["ATTRACT_DELTA"],
                        axis=1)
        base = base.mask(base > 50, 50).mask(base < -50, -50)
        return pd.DataFrame(base, columns=["result"])

    attractiveness_evolution.set_operations(
        pos.ops.lookup(id_field="POS_ID",
                       select={
                           "ATTRACT_BASE": "ATTRACT_BASE",
                           "ATTRACT_DELTA": "ATTRACT_DELTA",
                       }),
        Apply(
            source_fields=["ATTRACT_BASE", "ATTRACT_DELTA"],
            named_as="NEW_ATTRACT_BASE",
            f=update_attract_base,
        ),
        pos.get_attribute("ATTRACT_BASE").ops.update(
            id_field="POS_ID", copy_from_field="NEW_ATTRACT_BASE"),
        Apply(source_fields=["ATTRACT_BASE"],
              named_as="NEW_ATTRACTIVENESS",
              f=_attractiveness_sigmoid(),
              f_args="series"),
        pos.get_attribute("ATTRACTIVENESS").ops.update(
            id_field="POS_ID", copy_from_field="NEW_ATTRACTIVENESS"),

        # TODO: remove this (currently there just for debugs)
        circus.clock.ops.timestamp(named_as="TIME"),
        FieldLogger(log_id="att_updates"))

    delta_updater = NumpyRandomGenerator(method="choice",
                                         a=[-1, 0, 1],
                                         seed=next(circus.seeder))

    # random walk around of the attractiveness delta, once per week
    attractiveness_delta_evolution = circus.create_story(
        name="attractiveness_delta_evolution",
        initiating_actor=pos,
        actorid_field="POS_ID",
        timer_gen=ConstantDependentGenerator(
            value=circus.clock.n_iterations(pd.Timedelta("7 days"))))

    attractiveness_delta_evolution.set_operations(
        pos.ops.lookup(id_field="POS_ID",
                       select={"ATTRACT_DELTA": "ATTRACT_DELTA"}),
        delta_updater.ops.generate(named_as="DELTA_UPDATE"),
        Apply(source_fields=["ATTRACT_DELTA", "DELTA_UPDATE"],
              named_as="NEW_ATTRACT_DELTA",
              f=np.add,
              f_args="series"),
        pos.get_attribute("ATTRACT_DELTA").ops.update(
            id_field="POS_ID", copy_from_field="NEW_ATTRACT_DELTA"),

        # TODO: remove this (currently there just for debugs)
        circus.clock.ops.timestamp(named_as="TIME"),
        FieldLogger(log_id="att_delta_updates"))