コード例 #1
0
ファイル: example2.py プロジェクト: ztilottama/trumania
def update_purchase_story(the_circus):
    """
    Adds some operations to the existing customer purchase story in order to
    trigger a POS restock if their stock level gets low
    """

    purchase_story = the_circus.get_story("purchase")
    pos = the_circus.populations["point_of_sale"]

    # trigger_prop_func(level) specifies the probability of re-stocking as a
    # function the stock level
    trigger_prop_func = ops.bounded_sigmoid(

        # below x_min, probability is one, and decrements as x increases
        incrementing=False,

        # probability is 1 when level=2, 0 when level 10 and after
        x_min=2, x_max=10,

        # this controls the shape of the S curve in between
        shape=10)

    # Wraps the sigmoid into a dependent trigger, i.e.:
    #   - a generator, i.e producing random values
    #   - of booleans, hence the name "trigger"
    #   - dependent, i.e. as a function of story_data field at execution time
    trigger_gen = gen.DependentTriggerGenerator(
        value_to_proba_mapper=trigger_prop_func,
        seed=next(the_circus.seeder))

    # since those operations are added after the FieldLogger, the fields they
    # create will not be appended to the story_data
    purchase_story.append_operations(

        pos.get_relationship("items").ops.get_neighbourhood_size(
            from_field="POS_ID",
            named_as="POS_STOCK"),

        # generates random booleans with probability related to the stock level
        trigger_gen.ops.generate(
            observed_field="POS_STOCK",
            named_as="SHOULD_RESTOCK"),

        # trigger the restock story of the POS whose SHOULD_RESTOCK field is
        # now true
        the_circus.get_story("restock").ops.force_act_next(
            member_id_field="POS_ID",
            condition_field="SHOULD_RESTOCK")
    )
コード例 #2
0
def test_bounded_sigmoid_should_broadcast_as_a_ufunc():

    freud = operations.bounded_sigmoid(x_min=2,
                                       x_max=15,
                                       shape=5,
                                       incrementing=True)

    # passing a range of x should yield a range of y's
    for y in freud(np.linspace(-100, 2, 200)):
        assert y == 0

    # all values after x_max should be 1
    for y in freud(np.linspace(15, 100, 200)):
        assert y == 1

    # all values in between should be in [0,1 ]
    for y in freud(np.linspace(0, 1, 200)):
        assert 0 <= y <= 1
コード例 #3
0
def test_decreasing_bounded_sigmoid_must_reach_min_and_max_at_boundaries():

    freud = operations.bounded_sigmoid(x_min=2,
                                       x_max=15,
                                       shape=5,
                                       incrementing=False)

    # all values before x_min should be 1
    for x in np.linspace(-100, 2, 200):
        assert freud(x) == 1

    # all values after x_max should be 0
    for x in np.linspace(15, 100, 200):
        assert freud(x) == 0

    # all values in between should be in [0,1 ]
    for x in np.linspace(0, 1, 200):
        assert 0 <= freud(x) <= 1
コード例 #4
0
def add_bulk_restock_actions(circus, params, buyer_actor_name,
                             seller_actor_name):

    buyer = circus.actors[buyer_actor_name]
    seller = circus.actors[seller_actor_name]
    pos_per_buyer = circus.actors["pos"].size / buyer.size

    for product, description in params["products"].items():
        action_name = "{}_{}_bulk_purchase".format(buyer_actor_name, product)
        upper_level_restock_action_name = "{}_{}_bulk_purchase".format(
            seller_actor_name, product)

        logging.info("creating {} action".format(action_name))

        # generator of item prices and type
        item_price_gen = random_generators.NumpyRandomGenerator(
            method="choice",
            a=description["item_prices"],
            seed=next(circus.seeder))

        item_prices_gen = random_generators.DependentBulkGenerator(
            element_generator=item_price_gen)

        item_type_gen = random_generators.NumpyRandomGenerator(
            method="choice",
            a=circus.actors[product].ids,
            seed=next(circus.seeder))

        item_types_gen = random_generators.DependentBulkGenerator(
            element_generator=item_type_gen)

        tx_gen = random_generators.SequencialGenerator(
            prefix="_".join(["TX", buyer_actor_name, product]))

        tx_seq_gen = random_generators.DependentBulkGenerator(
            element_generator=tx_gen)

        # trigger for another bulk purchase done by the seller if their own
        # stock get low
        seller_low_stock_bulk_purchase_trigger = random_generators.DependentTriggerGenerator(
            value_to_proba_mapper=operations.bounded_sigmoid(
                x_min=pos_per_buyer,
                x_max=description["max_pos_stock_triggering_pos_restock"] *
                pos_per_buyer,
                shape=description["restock_sigmoid_shape"],
                incrementing=False))

        # bulk size distribution is a scaled version of POS bulk size distribution
        bulk_size_gen = scale_quantity_gen(stock_size_gen=circus.generators[
            "pos_{}_bulk_size_gen".format(product)],
                                           scale_factor=pos_per_buyer)

        build_purchase_action = circus.create_story(
            name=action_name,
            initiating_actor=buyer,
            actorid_field="BUYER_ID",

            # no timer or activity: dealers bulk purchases are triggered externally
        )

        build_purchase_action.set_operations(
            circus.clock.ops.timestamp(named_as="TIME"),

            buyer.get_relationship("{}__provider".format(product))
                 .ops.select_one(from_field="BUYER_ID",
                                 named_as="SELLER_ID"),

            bulk_size_gen.ops.generate(named_as="REQUESTED_BULK_SIZE"),

            buyer.get_relationship(product).ops
                 .get_neighbourhood_size(
                     from_field="BUYER_ID",
                     named_as="OLD_BUYER_STOCK"),

            # TODO: the perfect case would prevent to go over max_stock at this point

            # selecting and removing Sims from dealers
            seller.get_relationship(product).ops \
                  .select_many(
                       from_field="SELLER_ID",
                       named_as="ITEM_IDS",
                       quantity_field="REQUESTED_BULK_SIZE",

                       # if an item is selected, it is removed from the dealer's stock
                       pop=True,

                       # TODO: put this back to False and log the failed purchases
                       discard_missing=True),

            # and adding them to the buyer
            buyer.get_relationship(product).ops.add_grouped(
                from_field="BUYER_ID",
                grouped_items_field="ITEM_IDS"),

            # We do not track the old and new stock of the dealer since the result
            # is misleading: since all purchases are performed in parallel,
            # if a dealer is selected several times, its stock level after the
            # select_many() is the level _after_ all purchases are done, which is
            # typically not what we want to include in the log.
            buyer.get_relationship(product).ops \
                 .get_neighbourhood_size(
                     from_field="BUYER_ID",
                     named_as="NEW_BUYER_STOCK"),

            # actual number of bought items might be different due to out of stock
            operations.Apply(source_fields="ITEM_IDS",
                             named_as="BULK_SIZE",
                             f=lambda s: s.map(len), f_args="series"),

            # Generate some item prices. Note that the same items will have a
            # different price through the whole distribution chain
            item_prices_gen.ops.generate(
                named_as="ITEM_PRICES",
                observed_field="BULK_SIZE"
            ),

            item_types_gen.ops.generate(
                named_as="ITEM_TYPES",
                observed_field="BULK_SIZE"
            ),

            tx_seq_gen.ops.generate(
                named_as="TX_IDS",
                observed_field="BULK_SIZE"
            ),

            operations.FieldLogger(log_id="{}_stock".format(action_name),
                                   cols=["TIME", "BUYER_ID", "SELLER_ID",
                                         "OLD_BUYER_STOCK", "NEW_BUYER_STOCK",
                                         "BULK_SIZE"]),

            operations.FieldLogger(log_id=action_name,
                                   cols=["TIME", "BUYER_ID", "SELLER_ID"],
                                   exploded_cols=["TX_IDS", "ITEM_IDS",
                                                  "ITEM_PRICES", "ITEM_TYPES"]),

            trigger_action_if_low_stock(
                circus,
                stock_relationship=seller.get_relationship(product),
                actor_id_field="SELLER_ID",
                restock_trigger=seller_low_stock_bulk_purchase_trigger,
                triggered_action_name=upper_level_restock_action_name
            )
        )
コード例 #5
0
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)),
        )