SupplyNetPy in 10 Minutes
Installation
SupplyNetPy can be installed using pip.
pip install supplynetpy
Quick Start, Building a Simple Supply Chain with Three Nodes
Follow these steps to create and simulate a basic supply chain with an infinite supplier, a distributor and a customer demand.

Import the Library
import SupplyNetPy.Components as scm
The Components module gives you the building blocks of a supply chain. These are the nodes (raw-material suppliers, manufacturers, distributors, warehouses and retailers), products and inventories, customer demands, and the links that connect them. You assemble them into a model that fits your scenario. The words used on this page are explained in the Glossary.
Create Nodes
Let us create an infinite supplier, a supplier that never runs out and so can supply any number of units to its buyers. It needs an ID, a name and a node type. To make it an infinite supplier, set the node type to infinite_supplier.
supplier1 = {'ID': 'S1', 'name': 'Supplier1', 'node_type': 'infinite_supplier'}
Next, a distributor that buys units from the supplier. Its settings include the ID, the name, the capacity, the replenishment policy and its parameters, and the buy and sell prices.
distributor1 = {'ID': 'D1', 'name': 'Distributor1', 'node_type': 'distributor',
'capacity': 150, 'initial_level': 50, 'inventory_holding_cost': 0.2,
'replenishment_policy': scm.SSReplenishment, 'policy_param': {'s':100,'S':150},
'product_buy_price': 100,'product_sell_price': 105}
For a manufacturer, distributor, warehouse or retailer, we give its replenishment policy (the rule it follows to decide when to place a replenishment order and how many units to order) and the policy's parameters.
The policies are subclasses of the InventoryReplenishment class, which you can also subclass to define your own. SupplyNetPy has the following replenishment policies.
-
The (s, S) policy. When the inventory position falls to s or below, order enough units to bring it back up to S. Parameters {s, S}, class
SSReplenishment. -
The (s, S) policy with a safety stock. The same, with the safety stock added to both s and S. Parameters {s, S, safety_stock}, class
SSReplenishment. -
The (R, Q) policy. When the inventory position falls to R or below, order a fixed Q units. Parameters {R, Q}, class
RQReplenishment. -
The periodic policy. Every T time units, order Q units. Parameters {T, Q}, class
PeriodicReplenishment. -
The periodic policy with a safety stock. The same, but if the inventory level is below the safety stock, the order also includes the units needed to bring it back up to the safety stock. Parameters {T, Q, safety_stock}, class
PeriodicReplenishment.
The inventory position is the inventory level, plus the units in transit, minus the units still owed. By default, the (s, S) and (R, Q) policies check it each time the inventory drops.
Create a Link
A link is created as shown below. Its settings include the transportation cost and the lead time. The lead time can be a number, or a function with no arguments that returns one lead time each time it is called, for example a random one drawn from a distribution of your choice. For a constant lead time, a Python lambda such as lambda: 2 is enough.
link1 = {'ID': 'L1', 'source': 'S1', 'sink': 'D1', 'cost': 5, 'lead_time': lambda: 2}
Specify Demand
A customer demand is created by giving an ID, a name, the site the customers order from, how often orders arrive, and how many units each order is for. The "how often" and "how many" values are each given as a small function with no arguments that returns a number. If you want randomness, for example customer orders arriving at times drawn from an exponential distribution, write a function that samples from that distribution and returns one number per call. If you want a fixed value, just use a lambda such as lambda: 1. A customer demand can be at any site except a raw-material supplier, usually a retailer or a distributor. The example below sets up a steady demand of 10 units every time unit (read here as a day) at distributor D1.
demand1 = {'ID': 'd1', 'name': 'Demand1', 'order_arrival_model': lambda: 1,
'order_quantity_model': lambda: 10, 'demand_node': 'D1'}
Assemble and Simulate the Network
To create and simulate the supply chain, use the create_sc_net function to create the nodes and assemble them into a network. This function also records information about the network, such as the number of nodes, and returns a Python dictionary that holds all the parts of the network. The simulate_sc_net function then simulates the network up to a given time and writes a log of the run. It also calculates results for the whole network, such as the total cost, the revenue and the profit. Let's use these functions to build and simulate our supply chain.
# create a supply chain network
supplychainnet = scm.create_sc_net(nodes=[supplier1, distributor1], links=[link1], demands=[demand1])
# simulate for 20 days
supplychainnet = scm.simulate_sc_net(supplychainnet, sim_time=20, logging=True)
Review Results
After the simulation, look at supplychainnet to see the results. When simulate_sc_net runs with logging=True (the default), the simulation log is shown in the console and saved to a file named simulation_trace.log in the folder you run the script from. Each node has its own logger, and you can switch it on or off with logging=True or logging=False when you create the node. To switch all simulation logs on or off at once, call scm.global_logger.enable_logging() or scm.global_logger.disable_logging().
Repeating the same run. Some parts of the simulation can be random, for example disruptions that start at random (failure_p on a node, and the similar settings on a Link). By default, every run draws different random numbers, so two runs in a row may give slightly different results. If you want to repeat exactly the same run, for instance to compare two policies under the same sequence of disruptions, call scm.set_seed(n) once before you build the network. For even finer control, you can also create your own random number generator with random.Random() and pass it as the rng argument when you create a node or a link. That node or link then uses its own random numbers, independent of the rest.
Below is an example of a simulation log from this program. At the end of the log, the results for the whole network are printed. They are added up from every node and include the following.
- Holding cost (
inventory_carry_cost) - What the sites paid for the units they received (
inventory_spend_cost) - Transportation cost
- Total cost
- Revenue
- Profit
What each result means is listed under simulate_sc_net in the API reference.
INFO D1 - 0.0000:D1: Inventory levels:50, on hand:50
INFO D1 - 0.0000:D1:Replenishing inventory from supplier:Supplier1, order placed for 100 units.
INFO D1 - 0.0000:D1:shipment in transit from supplier:Supplier1.
INFO d1 - 0.0000:d1:Customer1:Order quantity:10, available.
INFO D1 - 0.0000:D1: Inventory levels:40, on hand:140
INFO d1 - 1.0000:d1:Customer2:Order quantity:10, available.
INFO D1 - 1.0000:D1: Inventory levels:30, on hand:130
INFO D1 - 2.0000:D1:Inventory replenished. reorder_quantity=100, Inventory levels:130
INFO d1 - 2.0000:d1:Customer3:Order quantity:10, available.
INFO D1 - 2.0000:D1: Inventory levels:120, on hand:120
INFO d1 - 3.0000:d1:Customer4:Order quantity:10, available.
INFO D1 - 3.0000:D1: Inventory levels:110, on hand:110
INFO d1 - 4.0000:d1:Customer5:Order quantity:10, available.
INFO D1 - 4.0000:D1: Inventory levels:100, on hand:100
INFO D1 - 4.0000:D1:Replenishing inventory from supplier:Supplier1, order placed for 50 units.
INFO D1 - 4.0000:D1:shipment in transit from supplier:Supplier1.
INFO d1 - 5.0000:d1:Customer6:Order quantity:10, available.
INFO D1 - 5.0000:D1: Inventory levels:90, on hand:140
INFO D1 - 6.0000:D1:Inventory replenished. reorder_quantity=50, Inventory levels:140
INFO d1 - 6.0000:d1:Customer7:Order quantity:10, available.
INFO D1 - 6.0000:D1: Inventory levels:130, on hand:130
INFO d1 - 7.0000:d1:Customer8:Order quantity:10, available.
INFO D1 - 7.0000:D1: Inventory levels:120, on hand:120
INFO d1 - 8.0000:d1:Customer9:Order quantity:10, available.
INFO D1 - 8.0000:D1: Inventory levels:110, on hand:110
INFO d1 - 9.0000:d1:Customer10:Order quantity:10, available.
INFO D1 - 9.0000:D1: Inventory levels:100, on hand:100
INFO D1 - 9.0000:D1:Replenishing inventory from supplier:Supplier1, order placed for 50 units.
INFO D1 - 9.0000:D1:shipment in transit from supplier:Supplier1.
INFO d1 - 10.0000:d1:Customer11:Order quantity:10, available.
INFO D1 - 10.0000:D1: Inventory levels:90, on hand:140
INFO D1 - 11.0000:D1:Inventory replenished. reorder_quantity=50, Inventory levels:140
INFO d1 - 11.0000:d1:Customer12:Order quantity:10, available.
INFO D1 - 11.0000:D1: Inventory levels:130, on hand:130
INFO d1 - 12.0000:d1:Customer13:Order quantity:10, available.
INFO D1 - 12.0000:D1: Inventory levels:120, on hand:120
INFO d1 - 13.0000:d1:Customer14:Order quantity:10, available.
INFO D1 - 13.0000:D1: Inventory levels:110, on hand:110
INFO d1 - 14.0000:d1:Customer15:Order quantity:10, available.
INFO D1 - 14.0000:D1: Inventory levels:100, on hand:100
INFO D1 - 14.0000:D1:Replenishing inventory from supplier:Supplier1, order placed for 50 units.
INFO D1 - 14.0000:D1:shipment in transit from supplier:Supplier1.
INFO d1 - 15.0000:d1:Customer16:Order quantity:10, available.
INFO D1 - 15.0000:D1: Inventory levels:90, on hand:140
INFO D1 - 16.0000:D1:Inventory replenished. reorder_quantity=50, Inventory levels:140
INFO d1 - 16.0000:d1:Customer17:Order quantity:10, available.
INFO D1 - 16.0000:D1: Inventory levels:130, on hand:130
INFO d1 - 17.0000:d1:Customer18:Order quantity:10, available.
INFO D1 - 17.0000:D1: Inventory levels:120, on hand:120
INFO d1 - 18.0000:d1:Customer19:Order quantity:10, available.
INFO D1 - 18.0000:D1: Inventory levels:110, on hand:110
INFO d1 - 19.0000:d1:Customer20:Order quantity:10, available.
INFO D1 - 19.0000:D1: Inventory levels:100, on hand:100
INFO D1 - 19.0000:D1:Replenishing inventory from supplier:Supplier1, order placed for 50 units.
INFO D1 - 19.0000:D1:shipment in transit from supplier:Supplier1.
INFO sim_trace - Supply chain info:
INFO sim_trace - available_inv : 100
INFO sim_trace - avg_available_inv : 112.5
INFO sim_trace - avg_cost_per_item : 50.87
INFO sim_trace - avg_cost_per_order : 1017.4
INFO sim_trace - backorders : [0, 0]
INFO sim_trace - demand_by_customers : [20, 200]
INFO sim_trace - demand_by_site : [5, 300]
INFO sim_trace - demands : {'d1': Demand1}
INFO sim_trace - disruption_lost_sales : [0, 0]
INFO sim_trace - env : <simpy.core.Environment object at 0x0000028D55F67C10>
INFO sim_trace - fulfillment_received_by_customers : [20, 200]
INFO sim_trace - fulfillment_received_by_site : [4, 250]
INFO sim_trace - inventory_carry_cost : 410.0
INFO sim_trace - inventory_spend_cost : 25000
INFO sim_trace - inventory_waste : 0
INFO sim_trace - links : {'L1': S1 to D1}
INFO sim_trace - lost_sales : [0, 0]
INFO sim_trace - nodes : {'S1': Supplier1, 'D1': Distributor1}
INFO sim_trace - num_distributors : 1
INFO sim_trace - num_manufacturers : 0
INFO sim_trace - num_of_links : 1
INFO sim_trace - num_of_nodes : 2
INFO sim_trace - num_retailers : 0
INFO sim_trace - num_suppliers : 1
INFO sim_trace - profit : -4435.0
INFO sim_trace - revenue : 21000
INFO sim_trace - shortage : [0, 0]
INFO sim_trace - total_cost : 25435.0
INFO sim_trace - total_demand : [25, 500]
INFO sim_trace - total_fulfillment_received : [24, 450]
INFO sim_trace - transportation_cost : 25
To see the statistics of one node, call node.stats.get_statistics(). In this example, the statistics of node D1 can be read with the code below.
D1_node = supplychainnet["nodes"]["D1"] # Get D1 node
stats = D1_node.stats.get_statistics() # Get D1_node statistics
print(stats) # print
Here is the output produced by the code above.
{'name': 'D1 statistics',
'start_time': 0,
'elapsed_time': 20,
'demand_placed': [5, 300],
'fulfillment_received': [4, 250],
'demand_received': [20, 200],
'demand_fulfilled': [20, 200],
'shortage': [0, 0],
'lost_sales': [0, 0],
'disruption_lost_sales': [0, 0],
'backorder': [0, 0],
'waited': [0, 0],
'waiting_time': 0,
'avg_waiting_time': 0.0,
'blocked': [0, 0],
'inventory_level': 100,
'inventory_waste': 0,
'dropped_qty': 0,
'inventory_level_time': 2050,
'avg_inventory_level': 102.5,
'zero_inventory_time': 0,
'zero_inventory_share': 0.0,
'inventory_carry_cost': 410.0,
'inventory_spend_cost': 25000,
'transportation_cost': 25,
'destroyed_qty': 0,
'destroyed_value': 0,
'disruptions': 0,
'disrupted_time': 0,
'availability': 1.0,
'node_cost': 25435.0,
'revenue': 21000,
'profit': -4435.0}
Another Approach, Creating the Objects Yourself
This approach builds and simulates the same supply chain by creating each node, link and customer demand as a Python object yourself, instead of passing dictionaries to the utility functions. This gives more flexibility. The simulation runs in a SimPy environment that you create, which makes it easy to combine with your own SimPy processes.
import simpy # importing simpy to create a simpy environment
env = simpy.Environment() # create a simpy environment
# create an infinite supplier
supplier1 = scm.Supplier(env=env, ID='S1', name='Supplier', node_type="infinite_supplier")
# create a distributor node
distributor1 = scm.InventoryNode(env=env, ID='D1', name='Distributor1', node_type='distributor',
capacity=150, initial_level=50, inventory_holding_cost=0.2,
replenishment_policy=scm.SSReplenishment,
policy_param={'s':100, 'S':150}, product_buy_price=100,
product_sell_price=105)
# create a link for distributor
link1 = scm.Link(env=env, ID='L1', source=supplier1, sink=distributor1, cost=5, lead_time=lambda: 2)
# create demand at distributor1
demand1 = scm.Demand(env=env, ID='d1', name='Demand1', order_arrival_model=lambda: 1,
order_quantity_model=lambda:10, demand_node=distributor1)
# we can simulate the supply chain
env.run(until=20)
This script produces the same simulation log, because the network and its demand are deterministic. The results for the whole network are not in the log, because they are calculated by the function simulate_sc_net. The statistics of each node are still available, as shown earlier. We can also create and simulate the supply chain network with the same functions, create_sc_net and simulate_sc_net, as shown below.
# create a supply chain network
supplychainnet = scm.create_sc_net(env=env, nodes=[supplier1, distributor1],
links=[link1], demands=[demand1])
# simulate
supplychainnet = scm.simulate_sc_net(supplychainnet, sim_time=20, logging=True)
Note that an extra parameter, env, is passed to create_sc_net. It is needed because we created the SimPy environment (env) ourselves, and the same environment must be used to create the supply chain network and to run the simulation.