moptipyapps.prodsched package¶
Examples of production scheduling.
In this package, we provide instance data structures, instance generators, and a simulator for production scheduling problems. The goal is to emulate the works of Thürer et al. [1] and beyond, in a way that can be plugged into optimization procedures.
Matthias Thürer, Nuno O. Fernandes, Hermann Lödding, and Mark Stevenson. Material Flow Control in Make-to-Stock Production Systems: An Assessment of Order Generation, Order Release and Production Authorization by Simulation Flexible Services and Manufacturing Journal. 37(1):1-37. March 2025. doi:https://doi.org/10.1007/s10696-024-09532-2
Subpackages¶
Submodules¶
moptipyapps.prodsched.instance module¶
A production scheduling instance.
Production instances have names Instance.name.
Notice that production times are used in a cycling fashion. The time when a certain product is finished can be computed via compute_finish_time() in an efficient way.
>>> name = "my_instance"
The number of products be 3.
>>> n_products = 3
The number of customers be 5.
>>> n_customers = 5
The number of stations be 4.
>>> n_stations = 4
There will be 6 customer demands.
>>> n_demands = 6
The end of the warmup period.
>>> time_end_warmup = 10
The end of the measurement period.
>>> time_end_measure = 10000
Each product may take a different route through different stations.
>>> route_p0 = [0, 3, 2]
>>> route_p1 = [0, 2, 1, 3]
>>> route_p2 = [1, 2, 3]
>>> routes = [route_p0, route_p1, route_p2]
Each demand is a tuple of demand_id, customer_id, product_id, amount, release time, and deadline.
>>> d0 = [0, 0, 1, 20, 1240, 3000]
>>> d1 = [1, 1, 0, 10, 2300, 4000]
>>> d2 = [2, 2, 2, 7, 8300, 11000]
>>> d3 = [3, 3, 1, 12, 7300, 9000]
>>> d4 = [4, 4, 2, 23, 5410, 16720]
>>> d5 = [5, 3, 0, 19, 4234, 27080]
>>> demands = [d0, d1, d2, d3, d4, d5]
There is a fixed amount of each product in the warehouse at time step 0.
>>> warehous_at_t0 = [10, 0, 6]
Each station requires a certain working time for each unit of each product. This production time may vary over time. For example, maybe station 0 needs 10 time units for 1 unit of product 0 from time step 0 to time step 19, then 11 time units from time step 20 to 39, then 8 time units from time step 40 to 59. These times are cyclic, meaning that at time step 60 to 79, it will again need 10 time units, and so on. Of course, production times are only specified for stations that a product is actually routed through.
>>> m0_p0 = [10.0, 20.0, 11.0, 40.0, 8.0, 60.0]
>>> m0_p1 = [12.0, 20.0, 7.0, 40.0, 11.0, 70.0]
>>> m0_p2 = []
>>> m1_p0 = []
>>> m1_p1 = [20.0, 50.0, 30.0, 120.0, 7.0, 200.0]
>>> m1_p2 = [21.0, 50.0, 29.0, 130.0, 8.0, 190.0]
>>> m2_p0 = [ 8.0, 20.0, 9.0, 60.0]
>>> m2_p1 = [10.0, 90.0]
>>> m2_p2 = [12.0, 70.0, 30.0, 120.0]
>>> m3_p0 = [70.0, 200.0, 3.0, 220.0]
>>> m3_p1 = [60.0, 220.0, 5.0, 260.0]
>>> m3_p2 = [30.0, 210.0, 10.0, 300.0]
>>> station_product_unit_times = [[m0_p0, m0_p1, m0_p2],
... [m1_p0, m1_p1, m1_p2],
... [m2_p0, m2_p1, m2_p2],
... [m3_p0, m3_p1, m3_p2]]
We can (but do not need to) provide additional information as key-value pairs.
>>> infos = {"source": "manually created",
... "creation_date": "2025-11-09"}
From all of this data, we can create the instance.
>>> instance = Instance(name, n_products, n_customers, n_stations, n_demands,
... time_end_warmup, time_end_measure,
... routes, demands, warehous_at_t0,
... station_product_unit_times, infos)
>>> instance.name
'my_instance'
>>> instance.n_customers
5
>>> instance.n_stations
4
>>> instance.n_demands
6
>>> instance.n_products
3
>>> instance.routes
((0, 3, 2), (0, 2, 1, 3), (1, 2, 3))
>>> instance.time_end_warmup
10.0
>>> instance.time_end_measure
10000.0
>>> instance.demands
(Demand(arrival=1240.0, deadline=3000.0, demand_id=0, customer_id=0, product_id=1, amount=20, measure=True), Demand(arrival=2300.0, deadline=4000.0, demand_id=1, customer_id=1, product_id=0, amount=10, measure=True), Demand(arrival=4234.0, deadline=27080.0, demand_id=5, customer_id=3, product_id=0, amount=19, measure=True), Demand(arrival=5410.0, deadline=16720.0, demand_id=4, customer_id=4, product_id=2, amount=23, measure=True), Demand(arrival=7300.0, deadline=9000.0, demand_id=3, customer_id=3, product_id=1, amount=12, measure=True), Demand(arrival=8300.0, deadline=11000.0, demand_id=2, customer_id=2, product_id=2, amount=7, measure=True))
>>> instance.warehous_at_t0
(10, 0, 6)
>>> instance.station_product_unit_times
((array([10., 20., 11., 40., 8., 60.]), array([12., 20., 7., 40., 11., 70.]), array([], dtype=float64)), (array([], dtype=float64), array([ 20., 50., 30., 120., 7., 200.]), array([ 21., 50., 29., 130., 8., 190.])), (array([ 8., 20., 9., 60.]), array([10., 90.]), array([ 12., 70., 30., 120.])), (array([ 70., 200., 3., 220.]), array([ 60., 220., 5., 260.]), array([ 30., 210., 10., 300.])))
>>> instance.n_measurable_demands
6
>>> instance.n_measurable_demands_per_product
(2, 2, 2)
>>> dict(instance.infos)
{'source': 'manually created', 'creation_date': '2025-11-09'}
We can serialize instances to a stream of strings and also load them back from a stream of strings. Here, we store instance to a stream. We then load the independent instance i2 from that stream.
>>> i2 = from_stream(to_stream(instance))
>>> i2 is instance
False
>>> i2 == instance
True
You can see that the loaded instance has the same data as the stored one.
>>> i2.name == instance.name
True
>>> i2.n_customers == instance.n_customers
True
>>> i2.n_stations == instance.n_stations
True
>>> i2.n_demands == instance.n_demands
True
>>> i2.n_products == instance.n_products
True
>>> i2.routes == instance.routes
True
>>> i2.demands == instance.demands
True
>>> i2.time_end_warmup == instance.time_end_warmup
True
>>> i2.time_end_measure == instance.time_end_measure
True
>>> i2.warehous_at_t0 == instance.warehous_at_t0
True
>>> eq: bool = True
>>> for i in range(i2.n_stations):
... ma1 = i2.station_product_unit_times[i]
... ma2 = instance.station_product_unit_times[i]
... for j in range(i2.n_products):
... pr1 = ma1[j]
... pr2 = ma2[j]
... if not np.array_equal(pr1, pr2):
... eq = False
>>> eq
True
True >>> i2.infos == instance.infos True
- class moptipyapps.prodsched.instance.Demand(arrival, deadline, demand_id, customer_id, product_id, amount, measure)[source]¶
-
The record for demands.
>>> Demand(arrival=0.6, deadline=0.8, demand_id=1, ... customer_id=2, product_id=6, amount=12, measure=True) Demand(arrival=0.6, deadline=0.8, demand_id=1, customer_id=2, product_id=6, amount=12, measure=True) >>> Demand(arrival=16, deadline=28, demand_id=1, ... customer_id=2, product_id=6, amount=12, measure=False) Demand(arrival=16.0, deadline=28.0, demand_id=1, customer_id=2, product_id=6, amount=12, measure=False)
- class moptipyapps.prodsched.instance.Instance(name, n_products, n_customers, n_stations, n_demands, time_end_warmup, time_end_measure, routes, demands, warehous_at_t0, station_product_unit_times, infos=None)[source]¶
Bases:
ComponentAn instance of the Production Scheduling Problem.
- demands:
Final[tuple[Demand,...]]¶ The demands: Each demand is a tuple of demand_id, customer_id, product_id, amount, release_time, and deadline. The customer makes their order at time step release_time. They expect to receive their product by the deadline. The demands are sorted by release time and then deadline. The release time is always > 0. The deadline is always >= release time. Demand ids are unique.
- infos:
Final[Mapping[str,str]]¶ Additional information about the nature of the instance can be stored here. This has no impact on the behavior of the instance, but it may explain, e.g., settings of an instance generator.
- n_measurable_demands:
Final[int]¶ the number of demands that actually fall into the time measured window
- n_measurable_demands_per_product:
Final[tuple[int,...]]¶ the measurable demands on a per-product basis
- routes:
Final[tuple[tuple[int,...],...]]¶ the product routes, i.e., the stations through which each product must pass
- station_product_unit_times:
Final[tuple[tuple[ndarray,...],...]]¶ The per-station unit production times for each product. Each station can have different production times per product. Let’s say that this is tuple A. For each product, it has a tuple B at the index of the product id. If the product does not pass through the station, B is empty. Otherwise, it holds one or multiple tuples C. Each tuple C consists of two numbers: A per-unit-production time for the product. An end time index for this production time. Once the real time surpasses the end time of the last of these production specs, the production specs are recycled and begin again.
- demands:
- moptipyapps.prodsched.instance.KEY_IN_WAREHOUSE: Final[str] = 'products_in_warehouse_at_t0'¶
The amount of products in the warehouse at time step 0.
- moptipyapps.prodsched.instance.KEY_N_CUSTOMERS: Final[str] = 'n_customers'¶
the key for the number of customers
- moptipyapps.prodsched.instance.KEY_N_DEMANDS: Final[str] = 'n_demands'¶
the number of demands in the scenario
- moptipyapps.prodsched.instance.KEY_N_PRODUCTS: Final[str] = 'n_products'¶
the key for the number of products
- moptipyapps.prodsched.instance.KEY_N_STATIONS: Final[str] = 'n_stations'¶
the key for the number of stations
- moptipyapps.prodsched.instance.KEY_PRODUCTION_TIME: Final[str] = 'production_time'¶
the first part of the production time
- moptipyapps.prodsched.instance.KEY_ROUTE: Final[str] = 'product_route'¶
the first part of the product route key
- moptipyapps.prodsched.instance.KEY_TIME_END_MEASURE: Final[str] = 'time_end_measure'¶
the end of the measure period
- moptipyapps.prodsched.instance.KEY_TIME_END_WARMUP: Final[str] = 'time_end_warmup'¶
the end of the warmup period
- moptipyapps.prodsched.instance.MAX_ID: Final[int] = 1000000000¶
The maximum for the number of stations, products, or customers.
- moptipyapps.prodsched.instance.MAX_VALUE: Final[int] = 2147483647¶
No value bigger than this is permitted in any tuple anywhere.
- moptipyapps.prodsched.instance.compute_finish_time(start_time, amount, production_times)[source]¶
Compute the time when one job is finished.
The production times are cyclic intervals of unit production times and interval ends.
- Parameters:
- Return type:
- Returns:
the end time
Here, the production time is 10 time units / 1 product unit, valid until end time 100.
>>> compute_finish_time(0.0, 1, np.array((10.0, 100.0), np.float64)) 10.0
Here, the production time is 10 time units / 1 product unit, valid until end time 100. We begin producing at time unit 250. Since the production periods are cyclic, this is OK: we would be halfway through the third production period when the request comes in. It will consume 10 time units and be done at time unit 260.
>>> compute_finish_time(250.0, 1, np.array((10.0, 100.0))) 260.0
Here, the end time of the production time validity is at time unit 100. However, we begin producing 1 product unit at time step 90. This unit will use 10 time units, meaning that its production is exactly finished when the production time validity ends. It will be finished at time step 100.
>>> compute_finish_time(90.0, 1, np.array((10.0, 100.0))) 100.0
Here, the end time of the production time validity is at time unit 100. However, we begin producing 1 product unit at time step 95. This unit would use 10 time units. It will use these units, even though this extends beyond the end of the production time window.
>>> compute_finish_time(95.0, 1, np.array((10.0, 100.0))) 105.0
Now we have two production periods. The production begins again at time step 95. It will use 10 time units, even though this extends into the second period.
>>> compute_finish_time(95.0, 1, np.array((10.0, 100.0, 20.0, 200.0))) 105.0
Now things get more complex. We want to do 10 units of product. We start in the first period, so one unit will be completed there. This takes the starting time for the next job to 105, which is in the second period. Here, one unit of product takes 20 time units. We can finish producing one unit until time 125 and start the production of a second one, taking until 145. Now the remaining three units are produced until time 495 >>> compute_finish_time(95.0, 10, np.array(( … 10.0, 100.0, 20.0, 140.0, 50.0, 5000.0))) 495.0 >>> 95 + (1*10 + 2*20 + 7*50) 495
We again produce 10 product units starting at time step 95. The first one takes 10 time units, taking us into the second production interval at time 105. Then we can again do two units here, which consume 40 time units, taking us over the edge into the third interval at time unit 145. Here we do two units using 50 time units. We ahen are at time 245, which wraps back to 45. So the remaining 5 units take 10 time units each.
>>> compute_finish_time(95.0, 10, np.array(( ... 10.0, 100.0, 20.0, 140.0, 50.0, 200.0))) 295.0 >>> 95 + (1*10 + 2*20 + 2*50 + 5*10) 295
This is the same as the last example, but this time, the last interval (3 time units until 207) is skipped over by the long production of the second 50-time-unit product.
>>> compute_finish_time(95.0, 10, np.array(( ... 10.0, 100.0, 20.0, 140.0, 50.0, 200.0, 3.0, 207.0))) 295.0 >>> 95 + (1*10 + 2*20 + 2*50 + 5*10) 295
Production unit times may extend beyond the intervals.
>>> compute_finish_time(0.0, 5, np.array((1000.0, 100.0, 10.0, 110.0))) 5000.0 >>> 5 * 1000
- moptipyapps.prodsched.instance.load_instances(source)[source]¶
Load the instances from a given irectory.
- Parameters:
source (
str) – the source directory- Return type:
- Returns:
the tuple of instances
>>> inst1 = Instance( ... name="test1", n_products=1, n_customers=1, n_stations=2, ... n_demands=1, time_end_warmup=10, time_end_measure=4000, ... routes=[[0, 1]], ... demands=[[0, 0, 0, 10, 20, 100]], ... warehous_at_t0=[0], ... station_product_unit_times=[[[10.0, 10000.0]], ... [[30.0, 10000.0]]])
>>> inst2 = Instance( ... name="test2", n_products=2, n_customers=1, n_stations=2, ... n_demands=3, time_end_warmup=21, time_end_measure=10000, ... routes=[[0, 1], [1, 0]], ... demands=[[0, 0, 1, 10, 20, 90], [1, 0, 0, 5, 22, 200], ... [2, 0, 1, 7, 30, 200]], ... warehous_at_t0=[2, 1], ... station_product_unit_times=[[[10.0, 50.0, 15.0, 100.0], ... [ 5.0, 20.0, 7.0, 35.0, 4.0, 50.0]], ... [[ 5.0, 24.0, 7.0, 80.0], ... [ 3.0, 21.0, 6.0, 50.0,]]])
>>> from pycommons.io.temp import temp_dir >>> with temp_dir() as td: ... store_instances(td, [inst2, inst1]) ... res = load_instances(td) >>> res == (inst1, inst2) True
moptipyapps.prodsched.instances module¶
Generate instances for training and testing.
In this package, we provide a function for generating instances in a deterministic fashion for training and testing of MFC scenarios.
The function get_instances() will return a fixed set of instances for a given instance number. It allows you to store and retrieve compatible instance sets of different sizes from a given directory. It is not very efficient, but it will do.
>>> from pycommons.io.temp import temp_dir
>>> with temp_dir() as td:
... inst_1 = get_instances(3, td)
... inst_2 = get_instances(1, td)
... inst_3 = get_instances(5, td)
>>> len(inst_1)
3
>>> len(inst_2)
1
>>> len(inst_3)
5
>>> all(ix in inst_1 for ix in inst_2)
True
>>> all(ix in inst_3 for ix in inst_1)
True
moptipyapps.prodsched.mfc_generator module¶
Methods for generating MFC instances.
Here we provide some basic utilities for generating deterministic variants of instances such as those used in the paper [1] by Thürer et al.
>>> from moptipyapps.utils.sampling import Gamma
>>> inst = sample_mfc_instance([
... Product(0, (0, 1), Gamma.from_alpha_beta(3, 0.26))], [
... Station(0, Gamma.from_k_and_mean(3, 10)),
... Station(1, Gamma.from_k_and_mean(2, 10))],
... time_end_measure=100, seed=123)
>>> inst.name
'mfc_1_2_100_0x7b'
>>> inst.n_demands
7
>>> inst.demands
(Demand(arrival=5.213885878801001, deadline=5.213885878801001, demand_id=0, customer_id=0, product_id=0, amount=1, measure=False), Demand(arrival=25.872387132411287, deadline=25.872387132411287, demand_id=1, customer_id=1, product_id=0, amount=1, measure=False), Demand(arrival=43.062182155666896, deadline=43.062182155666896, demand_id=2, customer_id=2, product_id=0, amount=1, measure=True), Demand(arrival=49.817978344678004, deadline=49.817978344678004, demand_id=3, customer_id=3, product_id=0, amount=1, measure=True), Demand(arrival=58.21166922638016, deadline=58.21166922638016, demand_id=4, customer_id=4, product_id=0, amount=1, measure=True), Demand(arrival=69.09054693162531, deadline=69.09054693162531, demand_id=5, customer_id=5, product_id=0, amount=1, measure=True), Demand(arrival=88.804579148131, deadline=88.804579148131, demand_id=6, customer_id=6, product_id=0, amount=1, measure=True))
>>> len(inst.station_product_unit_times[0][0])
1600
>>> len(inst.station_product_unit_times[1][0])
1600
>>> inst.time_end_measure
100.0
>>> inst.time_end_warmup
30.0
>>> d = dict(inst.infos)
>>> del d["info_generated_on"]
>>> del d["info_generator_version"]
>>> d
{'info_generator': 'moptipyapps.prodsched.mfc_generator', 'info_rand_seed_src': 'USER_PROVIDED', 'info_rand_seed': '0x7b', 'info_time_end_measure_src': 'USER_PROVIDED', 'info_time_end_measure': '100', 'info_time_end_warmup_src': 'SAMPLED', 'info_time_end_warmup': '30', 'info_name_src': 'SAMPLED', 'info_product_interarrival_times[0]': 'Erlang(k=3, theta=3.846153846153846)', 'info_product_route[0]': 'USER_PROVIDED', 'info_station_processing_time[0]': 'Erlang(k=3, theta=3.3333333333333335)', 'info_station_processing_time_window_length[0]': 'Const(v=0.125)', 'info_station_processing_time[1]': 'Erlang(k=2, theta=5)', 'info_station_processing_time_window_length[1]': 'Const(v=0.125)'}
>>> inst = sample_mfc_instance(seed=23445)
>>> inst.name
'mfc_10_13_10000_0x5b95'
>>> inst.n_demands
9922
>>> len([dem for dem in inst.demands if dem.product_id == 0])
959
>>> len([dem for dem in inst.demands if dem.product_id == 1])
1055
>>> [len(k[0]) for k in inst.station_product_unit_times]
[160000, 160000, 0, 160000, 0, 0, 0, 0, 160000, 160000, 160000, 0, 0]
Matthias Thürer, Nuno O. Fernandes, Hermann Lödding, and Mark Stevenson. Material Flow Control in Make-to-Stock Production Systems: An Assessment of Order Generation, Order Release and Production Authorization by Simulation Flexible Services and Manufacturing Journal. 37(1):1-37. March 2025. doi:https://doi.org/10.1007/s10696-024-09532-2
- moptipyapps.prodsched.mfc_generator.INFO_GENERATED_ON: Final[str] = 'info_generated_on'¶
When was the instance generated?
- moptipyapps.prodsched.mfc_generator.INFO_GENERATOR_VERSION: Final[str] = 'info_generator_version'¶
The generator version
- moptipyapps.prodsched.mfc_generator.INFO_PRODUCT_INTERARRIVAL_TIME_DIST: Final[str] = 'info_product_interarrival_times'¶
The information key for interarrival times
- moptipyapps.prodsched.mfc_generator.INFO_PRODUCT_ROUTE: Final[str] = 'info_product_route'¶
The information key for interarrival times
- moptipyapps.prodsched.mfc_generator.INFO_RAND_SEED_SRC: Final[str] = 'info_rand_seed_src'¶
the random seed source
- moptipyapps.prodsched.mfc_generator.INFO_STATION_PROCESSING_TIME: Final[str] = 'info_station_processing_time'¶
The information key for the processing time distribution
- moptipyapps.prodsched.mfc_generator.INFO_STATION_PROCESSING_WINDOW_LENGTH: Final[str] = 'info_station_processing_time_window_length'¶
The information key for the processing time window length distribution
- moptipyapps.prodsched.mfc_generator.INFO_TIME_END_MEASURE: Final[str] = 'info_time_end_measure'¶
the measurement time end
- moptipyapps.prodsched.mfc_generator.INFO_TIME_END_MEASURE_SRC: Final[str] = 'info_time_end_measure_src'¶
the source of the measurement time end
- moptipyapps.prodsched.mfc_generator.INFO_TIME_END_WARMUP: Final[str] = 'info_time_end_warmup'¶
the warmup time end
- moptipyapps.prodsched.mfc_generator.INFO_TIME_END_WARMUP_SRC: Final[str] = 'info_time_end_warmup_src'¶
the source of the warmup time end
- moptipyapps.prodsched.mfc_generator.INFO_USER_PROVIDED: Final[str] = 'USER_PROVIDED'¶
a fixed structure
- class moptipyapps.prodsched.mfc_generator.Product(product_id, routing, interarrival_times)[source]¶
Bases:
objectThe product sampling definition.
- interarrival_times:
Distribution¶ the interarrival distribution
- interarrival_times:
- class moptipyapps.prodsched.mfc_generator.Station(station_id, processing_time, processing_windows=None)[source]¶
Bases:
objectThe station sampling definition.
- processing_time:
Distribution¶ the processing time distribution
- processing_windows:
Distribution¶ the processing window distribution
- processing_time:
- moptipyapps.prodsched.mfc_generator.default_products()[source]¶
Create the default product sequence as used in [1].
>>> default_products() (Product(product_id=0, routing=(0, 1, 3, 1, 8, 9, 10), interarrival_times=Erlang(k=3, theta=3.3333333333333335)), Product(product_id=1, routing=(0, 1, 4, 1, 7, 8, 9, 10), interarrival_times=Erlang(k=2, theta=5)), Product(product_id=2, routing=(0, 1, 5, 3, 1, 8, 11, 10), interarrival_times=Uniform(low=5, high=15)), Product(product_id=3, routing=(0, 1, 6, 3, 1, 8, 9, 10), interarrival_times=Erlang(k=3, theta=3.3333333333333335)), Product(product_id=4, routing=(0, 1, 3, 11, 1, 8, 1, 12), interarrival_times=Erlang(k=4, theta=2.5)), Product(product_id=5, routing=(0, 1, 4, 11, 1, 8, 6, 12), interarrival_times=Erlang(k=2, theta=5)), Product(product_id=6, routing=(0, 1, 5, 11, 1, 7, 1, 12), interarrival_times=Erlang(k=4, theta=2.5)), Product(product_id=7, routing=(0, 1, 2, 6, 3, 11, 1, 7, 5, 8, 1, 12), interarrival_times=Uniform(low=5, high=15)), Product(product_id=8, routing=(0, 1, 2, 4, 3, 5, 11, 1, 7, 1, 9, 5, 12), interarrival_times=Erlang(k=4, theta=2.5)), Product(product_id=9, routing=(0, 1, 2, 5, 1, 3, 11, 6, 1, 8, 10, 4, 12), interarrival_times=Erlang(k=2, theta=5)))
Matthias Thürer, Nuno O. Fernandes, Hermann Lödding, and Mark Stevenson. Material Flow Control in Make-to-Stock Production Systems: An Assessment of Order Generation, Order Release and Production Authorization by Simulation Flexible Services and Manufacturing Journal. 37(1):1-37. March 2025. doi:https://doi.org/10.1007/s10696-024-09532-2
- moptipyapps.prodsched.mfc_generator.default_stations()[source]¶
Create the default station sequence as used in [1].
>>> default_stations() (Station(station_id=0, processing_time=Erlang(k=3, theta=0.26), processing_windows=Const(v=0.125)), Station(station_id=1, processing_time=Erlang(k=3, theta=0.12), processing_windows=Const(v=0.125)), Station(station_id=2, processing_time=Erlang(k=2, theta=1.33), processing_windows=Const(v=0.125)), Station(station_id=3, processing_time=AtLeast(lb=5e-324, d=Exponential(eta=1)), processing_windows=Const(v=0.125)), Station(station_id=4, processing_time=Erlang(k=3, theta=0.67), processing_windows=Const(v=0.125)), Station(station_id=5, processing_time=Erlang(k=4, theta=0.35), processing_windows=Const(v=0.125)), Station(station_id=6, processing_time=Erlang(k=3, theta=0.59), processing_windows=Const(v=0.125)), Station(station_id=7, processing_time=Erlang(k=3, theta=0.63), processing_windows=Const(v=0.125)), Station(station_id=8, processing_time=Erlang(k=2, theta=0.59), processing_windows=Const(v=0.125)), Station(station_id=9, processing_time=Erlang(k=3, theta=0.6), processing_windows=Const(v=0.125)), Station(station_id=10, processing_time=AtLeast(lb=5e-324, d=Exponential(eta=1)), processing_windows=Const(v=0.125)), Station(station_id=11, processing_time=Erlang(k=4, theta=0.29), processing_windows=Const(v=0.125)), Station(station_id=12, processing_time=Erlang(k=3, theta=0.48), processing_windows=Const(v=0.125)))
Matthias Thürer, Nuno O. Fernandes, Hermann Lödding, and Mark Stevenson. Material Flow Control in Make-to-Stock Production Systems: An Assessment of Order Generation, Order Release and Production Authorization by Simulation Flexible Services and Manufacturing Journal. 37(1):1-37. March 2025. doi:https://doi.org/10.1007/s10696-024-09532-2
- moptipyapps.prodsched.mfc_generator.sample_mfc_instance(products=None, stations=None, time_end_warmup=None, time_end_measure=None, name=None, seed=None)[source]¶
Sample an MFC instance.
- Parameters:
products (
Optional[Iterable[Product]], default:None) – the productsstations (
Optional[Iterable[Station]], default:None) – the work stationstime_end_warmup (
int|float|None, default:None) – the end of the warmup periodtime_end_measure (
int|float|None, default:None) – the end of the measurement period
- Return type:
- Returns:
the instance
moptipyapps.prodsched.multistatistics module¶
A statistics record for multiple simulations.
We use this record as the solution space when optimizing for the MFC scenario. It stores the statistics of several simulation runs. The objective functions can then access these statistics. A space instance is provided that can create, copy, and serialize these objects to text, so that they can appear in the log files.
- class moptipyapps.prodsched.multistatistics.MultiStatistics(instances)[source]¶
Bases:
objectA set of statistics gathered over multiple instances.
- per_instance:
tuple[Statistics,...]¶ the per-instance statistics
- per_instance:
- class moptipyapps.prodsched.multistatistics.MultiStatisticsSpace(instances)[source]¶
Bases:
SpaceAn implementation of the Space API of for multiple statistics.
- copy(dest, source)[source]¶
Copy one multi-statistics to another one.
- Parameters:
dest (
MultiStatistics) – the destination multi-statisticssource (
MultiStatistics) – the source multi-statistics
- Return type:
- create()[source]¶
Create an empty multi-statistics record.
- Return type:
- Returns:
the empty multi-statistics record
- from_str(text)[source]¶
Convert a string to a multi-statistics.
- Parameters:
text (
str) – the string- Return type:
- Returns:
the multi-statistics
- is_equal(x1, x2)[source]¶
Check if two multi-statistics have the same contents.
- Parameters:
x1 (
MultiStatistics) – the first multi-statisticsx2 (
MultiStatistics) – the second multi-statistics
- Return type:
- Returns:
True if both multi-statistics have the same content
- log_parameters_to(logger)[source]¶
Log the parameters of the space to the given logger.
- Parameters:
logger (
KeyValueLogSection) – the logger for the parameters- Return type:
- n_points()[source]¶
Get the number of possible multi-statistics.
- Return type:
- Returns:
just some arbitrary number
- to_str(x)[source]¶
Convert a multi-statistics to a string.
- Parameters:
x (
MultiStatistics) – the packing- Return type:
- Returns:
a string corresponding to the multi-statistics
- validate(x)[source]¶
Check if a multi-statistics is valid.
- Parameters:
x (
MultiStatistics) – the multi-statistics- Raises:
TypeError – if any component of the multi-statistics is of the wrong type
ValueError – if the multi-statistics is not feasible
- Return type:
moptipyapps.prodsched.rop_experiment module¶
A small template for ROP-based experiments.
This experiment uses the NSGA-II algorithm to optimize Re-Order-Points (ROPs) to achieve both a high worst-case fillrate and a low worst-case average stock level.
moptipyapps.prodsched.rop_multisimulation module¶
A simulator for multiple runs of the ROP scenario.
Re-Order-Point (ROP) scenarios are such that for each product, a value X is provided. Once there are no more than X elements of that product in the warehouse, one new unit is ordered to be produced. Therefore, we have n_products such X values.
This module provides the functionality to simulate this scenario over multiple instances.
moptipyapps.prodsched.rop_simulation module¶
A re-order-point-based simulation.
Re-Order-Point (ROP) scenarios are such that for each product, a value X is provided. Once there are no more than X elements of that product in the warehouse, one new unit is ordered to be produced. Therefore, we have n_products such X values.
ROP-based simulations extend the basic behavior of the class Simulation to re-order production based on ROPs.
>>> from moptipyapps.prodsched.simulation import PrintingListener
>>> instance = Instance(
... name="test1", n_products=2, n_customers=4, n_stations=2, n_demands=4,
... time_end_warmup=3000, time_end_measure=10000,
... routes=[[0, 1], [1, 0]],
... demands=[Demand(arrival=100, deadline=100, demand_id=0,
... customer_id=0, product_id=0, amount=1, measure=False),
... Demand(arrival=3100, deadline=3100, demand_id=1,
... customer_id=1, product_id=0, amount=1, measure=True),
... Demand(arrival=500, deadline=500, demand_id=2,
... customer_id=2, product_id=1, amount=1, measure=False),
... Demand(arrival=4000, deadline=5000, demand_id=3,
... customer_id=3, product_id=1, amount=1, measure=True)],
... warehous_at_t0=[0, 0],
... station_product_unit_times=[[[10.0, 50.0, 15.0, 100.0],
... [ 5.0, 20.0, 7.0, 35.0, 4.0, 50.0]],
... [[ 5.0, 24.0, 7.0, 80.0],
... [ 3.0, 21.0, 6.0, 50.0,]]])
>>> rop_sim = ROPSimulation(instance, PrintingListener(print_time=False))
>>> rop_sim.set_rop((10, 20))
>>> rop_sim.ctrl_run()
start
T=0.0: product=0, amount=0, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: station=0, 1 jobs queued
T=0.0: start j(id: 0, p: 0, am: 11, ar: 0, me: F, c: F, st: 0, sp: 0) at station 0
T=0.0: product=1, amount=0, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: station=1, 1 jobs queued
T=0.0: start j(id: 1, p: 1, am: 21, ar: 0, me: F, c: F, st: 0, sp: 0) at station 1
T=84.0: finished j(id: 1, p: 1, am: 21, ar: 0, me: F, c: F, st: 0, sp: 0) at station 1
T=100.0: product=0, amount=0, in_warehouse=0, in_production=11, 1 pending demands
T=130.0: finished j(id: 0, p: 0, am: 11, ar: 0, me: F, c: F, st: 0, sp: 0) at station 0
T=130.0: station=0, 2 jobs queued
T=130.0: start j(id: 1, p: 1, am: 21, ar: 0, me: F, c: F, st: 84, sp: 1) at station 0
T=130.0: station=1, 1 jobs queued
T=130.0: start j(id: 0, p: 0, am: 11, ar: 0, me: F, c: F, st: 130, sp: 1) at station 1
T=197.0: finished j(id: 0, p: 0, am: 11, ar: 0, me: F, c: T, st: 130, sp: 1) at station 1
T=197.0: product=0, amount=11, in_warehouse=0, in_production=1, 1 pending demands
T=197.0: d(id: 0, p: 0, c: 0, am: 1, ar: 100, dl: 100, me: F) statisfied
T=197.0: 10 units of product 0 in warehouse
T=240.0: finished j(id: 1, p: 1, am: 21, ar: 0, me: F, c: T, st: 84, sp: 1) at station 0
T=240.0: station=0, 1 jobs queued
T=240.0: start j(id: 2, p: 0, am: 1, ar: 100, me: F, c: F, st: 100, sp: 0) at station 0
T=240.0: product=1, amount=21, in_warehouse=0, in_production=0, 0 pending demands
T=240.0: 21 units of product 1 in warehouse
T=250.0: finished j(id: 2, p: 0, am: 1, ar: 100, me: F, c: F, st: 100, sp: 0) at station 0
T=250.0: station=1, 1 jobs queued
T=250.0: start j(id: 2, p: 0, am: 1, ar: 100, me: F, c: F, st: 250, sp: 1) at station 1
T=255.0: finished j(id: 2, p: 0, am: 1, ar: 100, me: F, c: T, st: 250, sp: 1) at station 1
T=255.0: product=0, amount=1, in_warehouse=10, in_production=0, 0 pending demands
T=255.0: 11 units of product 0 in warehouse
T=500.0: product=1, amount=0, in_warehouse=21, in_production=0, 1 pending demands
T=500.0: d(id: 2, p: 1, c: 2, am: 1, ar: 500, dl: 500, me: F) statisfied
T=500.0: 20 units of product 1 in warehouse
T=500.0: station=1, 1 jobs queued
T=500.0: start j(id: 3, p: 1, am: 1, ar: 500, me: F, c: F, st: 500, sp: 0) at station 1
T=503.0: finished j(id: 3, p: 1, am: 1, ar: 500, me: F, c: F, st: 500, sp: 0) at station 1
T=503.0: station=0, 1 jobs queued
T=503.0: start j(id: 3, p: 1, am: 1, ar: 500, me: F, c: F, st: 503, sp: 1) at station 0
T=508.0: finished j(id: 3, p: 1, am: 1, ar: 500, me: F, c: T, st: 503, sp: 1) at station 0
T=508.0: product=1, amount=1, in_warehouse=20, in_production=0, 0 pending demands
T=508.0: 21 units of product 1 in warehouse
T=3100.0! product=0, amount=0, in_warehouse=11, in_production=0, 1 pending demands
T=3100.0! d(id: 1, p: 0, c: 1, am: 1, ar: 3100, dl: 3100, me: T) statisfied
T=3100.0! 10 units of product 0 in warehouse
T=3100.0! station=0, 1 jobs queued
T=3100.0! start j(id: 4, p: 0, am: 1, ar: 3100, me: T, c: F, st: 3100, sp: 0) at station 0
T=3110.0! finished j(id: 4, p: 0, am: 1, ar: 3100, me: T, c: F, st: 3100, sp: 0) at station 0
T=3110.0! station=1, 1 jobs queued
T=3110.0! start j(id: 4, p: 0, am: 1, ar: 3100, me: T, c: F, st: 3110, sp: 1) at station 1
T=3117.0! finished j(id: 4, p: 0, am: 1, ar: 3100, me: T, c: T, st: 3110, sp: 1) at station 1
T=3117.0! product=0, amount=1, in_warehouse=10, in_production=0, 0 pending demands
T=3117.0! 11 units of product 0 in warehouse
T=4000.0! product=1, amount=0, in_warehouse=21, in_production=0, 1 pending demands
T=4000.0! d(id: 3, p: 1, c: 3, am: 1, ar: 4000, dl: 5000, me: T) statisfied
T=4000.0! 20 units of product 1 in warehouse
T=4000.0! station=1, 1 jobs queued
T=4000.0! start j(id: 5, p: 1, am: 1, ar: 4000, me: T, c: F, st: 4000, sp: 0) at station 1
T=4003.0! finished j(id: 5, p: 1, am: 1, ar: 4000, me: T, c: F, st: 4000, sp: 0) at station 1
T=4003.0! station=0, 1 jobs queued
T=4003.0! start j(id: 5, p: 1, am: 1, ar: 4000, me: T, c: F, st: 4003, sp: 1) at station 0
T=4008.0! finished j(id: 5, p: 1, am: 1, ar: 4000, me: T, c: T, st: 4003, sp: 1) at station 0
T=4008.0! product=1, amount=1, in_warehouse=20, in_production=0, 0 pending demands
T=4008.0! 21 units of product 1 in warehouse
T=4008.0 -- finished
>>> instance = Instance(
... name="test2", n_products=2, n_customers=1, n_stations=2, n_demands=5,
... time_end_warmup=21, time_end_measure=10000,
... routes=[[0, 1], [1, 0]],
... demands=[[0, 0, 1, 4, 20, 90], [1, 0, 0, 5, 22, 200],
... [2, 0, 1, 4, 30, 200], [3, 0, 1, 6, 60, 200],
... [4, 0, 0, 3, 5, 2000]],
... warehous_at_t0=[2, 1],
... station_product_unit_times=[[[10.0, 50.0, 15.0, 100.0],
... [ 5.0, 20.0, 7.0, 35.0, 4.0, 50.0]],
... [[ 5.0, 24.0, 7.0, 80.0],
... [ 3.0, 21.0, 6.0, 50.0,]]])
>>> rop_sim = ROPSimulation(instance, PrintingListener(print_time=False))
>>> rop_sim.set_rop((10, 10))
>>> rop_sim.ctrl_run()
start
T=0.0: product=0, amount=2, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 2 units of product 0 in warehouse
T=0.0: station=0, 1 jobs queued
T=0.0: start j(id: 0, p: 0, am: 9, ar: 0, me: F, c: F, st: 0, sp: 0) at station 0
T=0.0: product=1, amount=1, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 1 units of product 1 in warehouse
T=0.0: station=1, 1 jobs queued
T=0.0: start j(id: 1, p: 1, am: 10, ar: 0, me: F, c: F, st: 0, sp: 0) at station 1
T=5.0: product=0, amount=0, in_warehouse=2, in_production=9, 1 pending demands
T=20.0: product=1, amount=0, in_warehouse=1, in_production=10, 1 pending demands
T=22.0! product=0, amount=0, in_warehouse=2, in_production=12, 2 pending demands
T=30.0! product=1, amount=0, in_warehouse=1, in_production=14, 2 pending demands
T=39.0: finished j(id: 1, p: 1, am: 10, ar: 0, me: F, c: F, st: 0, sp: 0) at station 1
T=39.0! station=1, 2 jobs queued
T=39.0: start j(id: 3, p: 1, am: 4, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=57.0: finished j(id: 3, p: 1, am: 4, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=57.0! station=1, 1 jobs queued
T=57.0! start j(id: 5, p: 1, am: 4, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=60.0! product=1, amount=0, in_warehouse=1, in_production=18, 3 pending demands
T=69.0! finished j(id: 5, p: 1, am: 4, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=69.0! station=1, 1 jobs queued
T=69.0! start j(id: 6, p: 1, am: 6, ar: 60, me: T, c: F, st: 60, sp: 0) at station 1
T=102.0! finished j(id: 6, p: 1, am: 6, ar: 60, me: T, c: F, st: 60, sp: 0) at station 1
T=110.0: finished j(id: 0, p: 0, am: 9, ar: 0, me: F, c: F, st: 0, sp: 0) at station 0
T=110.0! station=0, 6 jobs queued
T=110.0: start j(id: 2, p: 0, am: 3, ar: 5, me: F, c: F, st: 5, sp: 0) at station 0
T=110.0! station=1, 1 jobs queued
T=110.0: start j(id: 0, p: 0, am: 9, ar: 0, me: F, c: F, st: 110, sp: 1) at station 1
T=140.0: finished j(id: 2, p: 0, am: 3, ar: 5, me: F, c: F, st: 5, sp: 0) at station 0
T=140.0! station=0, 5 jobs queued
T=140.0! start j(id: 4, p: 0, am: 5, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=171.0: finished j(id: 0, p: 0, am: 9, ar: 0, me: F, c: T, st: 110, sp: 1) at station 1
T=171.0! station=1, 1 jobs queued
T=171.0: start j(id: 2, p: 0, am: 3, ar: 5, me: F, c: F, st: 140, sp: 1) at station 1
T=171.0! product=0, amount=9, in_warehouse=2, in_production=8, 2 pending demands
T=171.0: d(id: 4, p: 0, c: 0, am: 3, ar: 5, dl: 2000, me: F) statisfied
T=171.0! d(id: 1, p: 0, c: 0, am: 5, ar: 22, dl: 200, me: T) statisfied
T=171.0! 3 units of product 0 in warehouse
T=186.0: finished j(id: 2, p: 0, am: 3, ar: 5, me: F, c: T, st: 140, sp: 1) at station 1
T=186.0! product=0, amount=3, in_warehouse=3, in_production=5, 0 pending demands
T=186.0! 6 units of product 0 in warehouse
T=210.0! finished j(id: 4, p: 0, am: 5, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=210.0! station=0, 4 jobs queued
T=210.0: start j(id: 1, p: 1, am: 10, ar: 0, me: F, c: F, st: 39, sp: 1) at station 0
T=210.0! station=1, 1 jobs queued
T=210.0! start j(id: 4, p: 0, am: 5, ar: 22, me: T, c: F, st: 210, sp: 1) at station 1
T=245.0! finished j(id: 4, p: 0, am: 5, ar: 22, me: T, c: T, st: 210, sp: 1) at station 1
T=245.0! product=0, amount=5, in_warehouse=6, in_production=0, 0 pending demands
T=245.0! 11 units of product 0 in warehouse
T=263.0: finished j(id: 1, p: 1, am: 10, ar: 0, me: F, c: T, st: 39, sp: 1) at station 0
T=263.0! station=0, 3 jobs queued
T=263.0: start j(id: 3, p: 1, am: 4, ar: 20, me: F, c: F, st: 57, sp: 1) at station 0
T=263.0! product=1, amount=10, in_warehouse=1, in_production=14, 3 pending demands
T=263.0: d(id: 0, p: 1, c: 0, am: 4, ar: 20, dl: 90, me: F) statisfied
T=263.0! d(id: 2, p: 1, c: 0, am: 4, ar: 30, dl: 200, me: T) statisfied
T=263.0! 3 units of product 1 in warehouse
T=287.0: finished j(id: 3, p: 1, am: 4, ar: 20, me: F, c: T, st: 57, sp: 1) at station 0
T=287.0! station=0, 2 jobs queued
T=287.0! start j(id: 5, p: 1, am: 4, ar: 30, me: T, c: F, st: 69, sp: 1) at station 0
T=287.0! product=1, amount=4, in_warehouse=3, in_production=10, 1 pending demands
T=287.0! d(id: 3, p: 1, c: 0, am: 6, ar: 60, dl: 200, me: T) statisfied
T=287.0! 1 units of product 1 in warehouse
T=303.0! finished j(id: 5, p: 1, am: 4, ar: 30, me: T, c: T, st: 69, sp: 1) at station 0
T=303.0! station=0, 1 jobs queued
T=303.0! start j(id: 6, p: 1, am: 6, ar: 60, me: T, c: F, st: 102, sp: 1) at station 0
T=303.0! product=1, amount=4, in_warehouse=1, in_production=6, 0 pending demands
T=303.0! 5 units of product 1 in warehouse
T=337.0! finished j(id: 6, p: 1, am: 6, ar: 60, me: T, c: T, st: 102, sp: 1) at station 0
T=337.0! product=1, amount=6, in_warehouse=5, in_production=0, 0 pending demands
T=337.0! 11 units of product 1 in warehouse
T=337.0 -- finished
- class moptipyapps.prodsched.rop_simulation.ROPSimulation(instance, listener)[source]¶
Bases:
SimulationCreate the re-order point-based simulation.
moptipyapps.prodsched.simulation module¶
A simulator for production scheduling.
For simulation a production system, we can build on the class Simulation. This base class offers the support to implement almost arbitrarily complex production system scheduling logic. The simulations here a fully deterministic and execute a given MFC scenario given as an Instance.
Simulations have three groups of methods:
Methods starting with ctrl_* are for starting and resetting the simulation so that it can be started again. You may override them if you have additional need for initialization or clean-up.
Methods that start with event_* are methods that are invoked by the simulator to notify you about an event in the simulation. You can overwrite these methods to implement the logic of your production scheduling method.
Methods that start with act_* are actions that you can invoke inside the event_* methods. The tell the simulator or stations what to do.
An example of such specialized simulations is the ROPSimulation, which simulates the behavior of a system that uses re-order points (ROPs) to decide what to produce and when. In such a simulation, the event_*-methods are overwritten to invoke the act_*-methods according to their needs. Here, in the base class Simulation, they are implemented such to order the production of product units directly upon the arrival of customer demands. In the ROPSimulation on the other hand, products are produced base on re-order points.
## We have the following ctrl_* methods:**
ctrl_run()runs the simulation.ctrl_reset()resets the simulator so that we can start it again. If you want to re-use a simulation, you need to first invokectrl_reset()to clear the internal state.
## We have the following event_* methods:**
event_product()is invoked by the simulation if one of the following three things happened:An amount of a product has been produced (amount > 0).
An amount of a product has been made available at the start of the simulation to form the initial amount in the warehouse (amount > 0).
A customer demand for the product has appeared in the system.
In this method, you can store product into the warehouse, remove product from the warehouse, and/or mark a demand as completed.
## Examples
Here we have a very easy production scheduling instance. There is 1 product that passes through 2 stations. First it passes through station 0, then through station 1. The per-unit production time is always 10 time units on station 0 and 30 time units on station 2. There is one customer demand, for 10 units of this product, which enters the system at time unit 20. The warehouse is initially empty.
>>> instance = Instance(
... name="test1", n_products=1, n_customers=1, n_stations=2, n_demands=1,
... time_end_warmup=10, time_end_measure=4000,
... routes=[[0, 1]],
... demands=[[0, 0, 0, 10, 20, 100]],
... warehous_at_t0=[0],
... station_product_unit_times=[[[10.0, 10000.0]],
... [[30.0, 10000.0]]])
The simulation will see that the customer demand for 10 units of product 0 appears at time unit 20. It will issue a production order for these 10 units at station 0. Since station 0 is not occupied, it can immediately begin with the production. It will finish the production after 10*10 time units, i.e., at time unit 120. The product is then routed to station 1, which is also idle and can immediately begin producing. It needs 10*30 time units, meaning that it finishes after 300 time units. The demanded product amount is completed after 420 time units and the demand 0 can be fulfilled.
>>> simulation = Simulation(instance, PrintingListener(print_time=False))
>>> simulation.ctrl_run()
start
T=0.0: product=0, amount=0, in_warehouse=0, in_production=0, 0 pending demands
T=20.0! product=0, amount=0, in_warehouse=0, in_production=0, 1 pending demands
T=20.0! station=0, 1 jobs queued
T=20.0! start j(id: 0, p: 0, am: 10, ar: 20, me: T, c: F, st: 20, sp: 0) at station 0
T=120.0! finished j(id: 0, p: 0, am: 10, ar: 20, me: T, c: F, st: 20, sp: 0) at station 0
T=120.0! station=1, 1 jobs queued
T=120.0! start j(id: 0, p: 0, am: 10, ar: 20, me: T, c: F, st: 120, sp: 1) at station 1
T=420.0! finished j(id: 0, p: 0, am: 10, ar: 20, me: T, c: T, st: 120, sp: 1) at station 1
T=420.0! product=0, amount=10, in_warehouse=0, in_production=0, 1 pending demands
T=420.0! d(id: 0, p: 0, c: 0, am: 10, ar: 20, dl: 100, me: T) statisfied
T=420.0 -- finished
>>> instance = Instance(
... name="test2", n_products=2, n_customers=1, n_stations=2, n_demands=3,
... time_end_warmup=21, time_end_measure=10000,
... routes=[[0, 1], [1, 0]],
... demands=[[0, 0, 1, 10, 20, 90], [1, 0, 0, 5, 22, 200],
... [2, 0, 1, 7, 30, 200]],
... warehous_at_t0=[2, 1],
... station_product_unit_times=[[[10.0, 50.0, 15.0, 100.0],
... [ 5.0, 20.0, 7.0, 35.0, 4.0, 50.0]],
... [[ 5.0, 24.0, 7.0, 80.0],
... [ 3.0, 21.0, 6.0, 50.0,]]])
>>> instance.name
'test2'
>>> simulation = Simulation(instance, PrintingListener(print_time=False))
>>> simulation.ctrl_run()
start
T=0.0: product=0, amount=2, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 2 units of product 0 in warehouse
T=0.0: product=1, amount=1, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 1 units of product 1 in warehouse
T=20.0: product=1, amount=0, in_warehouse=1, in_production=0, 1 pending demands
T=20.0: station=1, 1 jobs queued
T=20.0: start j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=22.0! product=0, amount=0, in_warehouse=2, in_production=0, 1 pending demands
T=22.0! station=0, 1 jobs queued
T=22.0! start j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=30.0! product=1, amount=0, in_warehouse=1, in_production=9, 2 pending demands
T=52.0! finished j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=62.0: finished j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=62.0! station=1, 2 jobs queued
T=62.0! start j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=62.0! station=0, 1 jobs queued
T=62.0: start j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 62, sp: 1) at station 0
T=95.0! finished j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=95.0! station=1, 1 jobs queued
T=95.0! start j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 52, sp: 1) at station 1
T=107.0: finished j(id: 0, p: 1, am: 9, ar: 20, me: F, c: T, st: 62, sp: 1) at station 0
T=107.0! station=0, 1 jobs queued
T=107.0! start j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 95, sp: 1) at station 0
T=107.0! product=1, amount=9, in_warehouse=1, in_production=7, 2 pending demands
T=107.0: d(id: 0, p: 1, c: 0, am: 10, ar: 20, dl: 90, me: F) statisfied
T=107.0! 0 units of product 1 in warehouse
T=112.0! finished j(id: 1, p: 0, am: 3, ar: 22, me: T, c: T, st: 52, sp: 1) at station 1
T=112.0! product=0, amount=3, in_warehouse=2, in_production=0, 1 pending demands
T=112.0! d(id: 1, p: 0, c: 0, am: 5, ar: 22, dl: 200, me: T) statisfied
T=112.0! 0 units of product 0 in warehouse
T=144.0! finished j(id: 2, p: 1, am: 7, ar: 30, me: T, c: T, st: 95, sp: 1) at station 0
T=144.0! product=1, amount=7, in_warehouse=0, in_production=0, 1 pending demands
T=144.0! d(id: 2, p: 1, c: 0, am: 7, ar: 30, dl: 200, me: T) statisfied
T=144.0 -- finished
>>> simulation.ctrl_reset()
>>> simulation.ctrl_run()
start
T=0.0: product=0, amount=2, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 2 units of product 0 in warehouse
T=0.0: product=1, amount=1, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 1 units of product 1 in warehouse
T=20.0: product=1, amount=0, in_warehouse=1, in_production=0, 1 pending demands
T=20.0: station=1, 1 jobs queued
T=20.0: start j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=22.0! product=0, amount=0, in_warehouse=2, in_production=0, 1 pending demands
T=22.0! station=0, 1 jobs queued
T=22.0! start j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=30.0! product=1, amount=0, in_warehouse=1, in_production=9, 2 pending demands
T=52.0! finished j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=62.0: finished j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=62.0! station=1, 2 jobs queued
T=62.0! start j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=62.0! station=0, 1 jobs queued
T=62.0: start j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 62, sp: 1) at station 0
T=95.0! finished j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=95.0! station=1, 1 jobs queued
T=95.0! start j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 52, sp: 1) at station 1
T=107.0: finished j(id: 0, p: 1, am: 9, ar: 20, me: F, c: T, st: 62, sp: 1) at station 0
T=107.0! station=0, 1 jobs queued
T=107.0! start j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 95, sp: 1) at station 0
T=107.0! product=1, amount=9, in_warehouse=1, in_production=7, 2 pending demands
T=107.0: d(id: 0, p: 1, c: 0, am: 10, ar: 20, dl: 90, me: F) statisfied
T=107.0! 0 units of product 1 in warehouse
T=112.0! finished j(id: 1, p: 0, am: 3, ar: 22, me: T, c: T, st: 52, sp: 1) at station 1
T=112.0! product=0, amount=3, in_warehouse=2, in_production=0, 1 pending demands
T=112.0! d(id: 1, p: 0, c: 0, am: 5, ar: 22, dl: 200, me: T) statisfied
T=112.0! 0 units of product 0 in warehouse
T=144.0! finished j(id: 2, p: 1, am: 7, ar: 30, me: T, c: T, st: 95, sp: 1) at station 0
T=144.0! product=1, amount=7, in_warehouse=0, in_production=0, 1 pending demands
T=144.0! d(id: 2, p: 1, c: 0, am: 7, ar: 30, dl: 200, me: T) statisfied
T=144.0 -- finished
Now we want to stop the simulation measurement period before the last job completes. Notice that the last production jobs after time unit 81.xxx are no longer performed, because their end falls outside of the measurement period.
>>> instance = Instance(
... name="test3", n_products=2, n_customers=1, n_stations=2, n_demands=3,
... time_end_warmup=21, time_end_measure=100,
... routes=[[0, 1], [1, 0]],
... demands=[[0, 0, 1, 10, 20, 90], [1, 0, 0, 5, 22, 200],
... [2, 0, 1, 7, 30, 200]],
... warehous_at_t0=[2, 1],
... station_product_unit_times=[[[10.0, 50.0, 15.0, 100.0],
... [ 5.0, 20.0, 7.0, 35.0, 4.0, 50.0]],
... [[ 5.0, 24.0, 7.0, 80.0],
... [ 3.0, 21.0, 6.0, 50.0,]]])
>>> instance.name
'test3'
>>> simulation = Simulation(instance, PrintingListener(print_time=False))
>>> simulation.ctrl_run()
start
T=0.0: product=0, amount=2, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 2 units of product 0 in warehouse
T=0.0: product=1, amount=1, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 1 units of product 1 in warehouse
T=20.0: product=1, amount=0, in_warehouse=1, in_production=0, 1 pending demands
T=20.0: station=1, 1 jobs queued
T=20.0: start j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=22.0! product=0, amount=0, in_warehouse=2, in_production=0, 1 pending demands
T=22.0! station=0, 1 jobs queued
T=22.0! start j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=30.0! product=1, amount=0, in_warehouse=1, in_production=9, 2 pending demands
T=52.0! finished j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=62.0: finished j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=62.0! station=1, 2 jobs queued
T=62.0! start j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=62.0! station=0, 1 jobs queued
T=62.0: start j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 62, sp: 1) at station 0
T=95.0! finished j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=95.0! station=1, 1 jobs queued
T=95.0! start j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 52, sp: 1) at station 1
T=95.0 -- finished
>>> simulation.ctrl_reset()
>>> simulation.ctrl_run()
start
T=0.0: product=0, amount=2, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 2 units of product 0 in warehouse
T=0.0: product=1, amount=1, in_warehouse=0, in_production=0, 0 pending demands
T=0.0: 1 units of product 1 in warehouse
T=20.0: product=1, amount=0, in_warehouse=1, in_production=0, 1 pending demands
T=20.0: station=1, 1 jobs queued
T=20.0: start j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=22.0! product=0, amount=0, in_warehouse=2, in_production=0, 1 pending demands
T=22.0! station=0, 1 jobs queued
T=22.0! start j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=30.0! product=1, amount=0, in_warehouse=1, in_production=9, 2 pending demands
T=52.0! finished j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 22, sp: 0) at station 0
T=62.0: finished j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 20, sp: 0) at station 1
T=62.0! station=1, 2 jobs queued
T=62.0! start j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=62.0! station=0, 1 jobs queued
T=62.0: start j(id: 0, p: 1, am: 9, ar: 20, me: F, c: F, st: 62, sp: 1) at station 0
T=95.0! finished j(id: 2, p: 1, am: 7, ar: 30, me: T, c: F, st: 30, sp: 0) at station 1
T=95.0! station=1, 1 jobs queued
T=95.0! start j(id: 1, p: 0, am: 3, ar: 22, me: T, c: F, st: 52, sp: 1) at station 1
T=95.0 -- finished
- class moptipyapps.prodsched.simulation.Job(job_id, product_id, amount, arrival, measure, completed=False, station_time=-1.0, step=-1)[source]¶
Bases:
objectThe record for a production job.
- class moptipyapps.prodsched.simulation.Listener[source]¶
Bases:
objectA listener for simulation events.
- event_product(time, product_id, amount, in_warehouse, in_production, pending_demands, is_in_measure_period)[source]¶
Get notified right before
Simulation.event_product().- Parameters:
time (
float) – the current system timeproduct_id (
int) – the id of the productamount (
int) – the amount of the product that appearsin_warehouse (
int) – the amount of the product currently in the warehousein_production (
int) – the amounf of product currently under productionpending_demands (
tuple[Demand,...]) – the pending orders for the productis_in_measure_period (
bool) – is this event inside the measurement period?
- Return type:
- event_station(time, station_id, queue, is_in_measure_period)[source]¶
Get notified right before
Simulation.event_station().If this event happens, the station is not busy. It could process a job and there is at least one job that it could process. You can now select the job to be executed from the queue and pass it to
act_exec_job().
- produce_at_begin(time, station_id, job)[source]¶
Report the start of the production of a certain product at a station.
- produce_at_end(time, station_id, job)[source]¶
Report the completion of the production of a product at a station.
- class moptipyapps.prodsched.simulation.PrintingListener(output=<built-in function print>, print_time=True)[source]¶
Bases:
ListenerA listener that just prints simulation events.
- event_product(time, product_id, amount, in_warehouse, in_production, pending_demands, is_in_measure_period)[source]¶
Print the prouct event.
- Return type:
- event_station(time, station_id, queue, is_in_measure_period)[source]¶
Print the station event.
- Return type:
- produce_at_begin(time, station_id, job)[source]¶
Print that the production at a given station begun.
- Return type:
- produce_at_end(time, station_id, job)[source]¶
Print that the production at a given station ended.
- Return type:
- class moptipyapps.prodsched.simulation.Simulation(instance, listener)[source]¶
Bases:
objectA simulator for production scheduling.
- act_store_in_warehouse(product_id, amount)[source]¶
Add a certain amount of product to the warehouse.
- act_take_from_warehouse(product_id, amount)[source]¶
Remove a certain amount of product to the warehouse.
- ctrl_reset()[source]¶
Reset the simulation.
This function sets the time to 0, clears the event queue, clears the pending orders list, clears the warehouse.
- Return type:
- ctrl_run()[source]¶
Run the simulation.
This function executes the main loop of the simulation. It runs the central event pump, which is a priority queue. It processes the simulation events one by one.
- Return type:
- event_product(time, product_id, amount, in_warehouse, in_production, pending_demands)[source]¶
Take actions when an event regarding a product or demand occurred.
The following events may have occurred:
An amount of a product has been produced (amount > 0).
An amount of a product has been made available at the start of the simulation to form the initial amount in the warehouse (amount > 0).
A customer demand for the product has appeared in the system. If there is any demand to be fulfilled, then pending_demands is not empty.
You can choose to execute one or multiple of the following actions:
act_store_in_warehouse()to store a positive amount of product in the warehouse.act_take_from_warehouse()to take a positive amount of product out of the warehouse (must be <= in_warehouse.act_produce()to order the production of a positive amount of the product.act_demand_satisfied()to mark one of the demands from queue as satisfied. Notice that in this case, you must make sure to remove the corresponding amount of product units from the system. If sufficient units are in amount, you would simply not store these in the warehouse. You could also simply take some units out of the warehouse withact_take_from_warehouse().
- Parameters:
time (
float) – the current system timeproduct_id (
int) – the id of the productamount (
int) – the amount of the product that appearsin_warehouse (
int) – the amount of the product currently in the warehousein_production (
int) – the amounf of product currently under productionpending_demands (
tuple[Demand,...]) – the pending orders for the product
- Return type:
- event_station(time, station_id, queue)[source]¶
Process an event for a given station.
If this event happens, the station is not busy. It could process a job and there is at least one job that it could process. You can now select the job to be executed from the queue and pass it to
act_exec_job().
moptipyapps.prodsched.statistics module¶
A statistics record for the simulation.
This module provides a record with statistics derived from one single MFC simulation. It can store values such as the mean fill rate or the mean stock level.
- moptipyapps.prodsched.statistics.COL_PRODUCT_PREFIX: Final[str] = 'product_'¶
the product column prefix
- moptipyapps.prodsched.statistics.ROW_FULFILLED_RATE: Final[str] = 'fulfilled.rate'¶
the fulfilled rate
- moptipyapps.prodsched.statistics.ROW_SIMULATION_TIME: Final[str] = 'time.s: '¶
the simulation time getter
- moptipyapps.prodsched.statistics.ROW_STOCK_LEVEL_MEAN: Final[str] = 'stocklevel.mean'¶
the mean stock level row
- class moptipyapps.prodsched.statistics.Statistics(n_products)[source]¶
Bases:
objectA statistics record based on production scheduling.
- copy_from(stat)[source]¶
Copy the contents of another statistics record.
- Parameters:
stat (
Statistics) – the other statistics record- Return type:
- fulfilled_rates:
Final[list[int|float|None]]¶ the fraction of demands that were fulfilled, on a per-product basis
- immediate_rates:
Final[list[int|float|None]]¶ the fraction of demands that were immediately satisfied, on a per-product basis
- production_time:
StreamStatistics|None¶ the overall production time statistics
- production_times:
Final[list[StreamStatistics|None]]¶ the production time statistics per-product
- waiting_time:
StreamStatistics|None¶ the overall waiting time for all demands that were not immediately satisfied – only counting demands that were actually satisfied
- waiting_times:
Final[list[StreamStatistics|None]]¶ the average waiting time for all demands that were not immediately satisfied – only counting demands that were actually satisfied
moptipyapps.prodsched.statistics_collector module¶
A tool for collecting statistics from an MFC simulation.
A statistics collector is a special Listener that can be plugged into a Simulation. During the execution of the simulation, it gathers statistics about what is going on. It finally stores these into a Statistics record. Such a record can then be used to understand the key characteristics of the behavior of the simulation in on a given Instance.
>>> instance = Instance(
... name="test2", n_products=2, n_customers=1, n_stations=2, n_demands=5,
... time_end_warmup=21, time_end_measure=10000,
... routes=[[0, 1], [1, 0]],
... demands=[[0, 0, 1, 10, 20, 90], [1, 0, 0, 5, 22, 200],
... [2, 0, 1, 7, 30, 200], [3, 0, 1, 6, 60, 200],
... [4, 0, 0, 125, 5, 2000]],
... warehous_at_t0=[2, 1],
... station_product_unit_times=[[[10.0, 50.0, 15.0, 100.0],
... [ 5.0, 20.0, 7.0, 35.0, 4.0, 50.0]],
... [[ 5.0, 24.0, 7.0, 80.0],
... [ 3.0, 21.0, 6.0, 50.0,]]])
>>> from moptipyapps.prodsched.simulation import Simulation
>>> statistics = Statistics(instance.n_products)
>>> collector = StatisticsCollector(instance)
>>> collector.set_dest(statistics)
>>> simulation = Simulation(instance, collector)
>>> simulation.ctrl_run()
>>> print("\n".join(str(statistics).split("\n")[:-1]))
stat;total;product_0;product_1
trp.min;229.57142857142858;455.6;229.57142857142858
trp.mean;304.8888888888889;455.6;246.92307692307693
trp.max;455.6;455.6;267.1666666666667
trp.sd;97.56326157594913;0;19.50721118346466
cwt.min;1603;2278;1603
cwt.mean;1829.3333333333333;2278;1605
cwt.max;2278;2278;1607
cwt.sd;388.56187838403986;;2.8284271247461903
fill.rate;0;0;0
stocklevel.mean;0.6078765407355446;0.4497444633730835;0.15813207736246118
fulfilled.rate;1;1;1
- class moptipyapps.prodsched.statistics_collector.StatisticsCollector(instance)[source]¶
Bases:
ListenerA listener for simulation events.
- produce_at_end(time, station_id, job)[source]¶
Report the completion of the production of a product at a station.
- product_in_warehouse(time, product_id, amount, is_in_measure_period)[source]¶
Report a change of the amount of products in the warehouse.
- set_dest(dest)[source]¶
Set the statistics record to fill.
- Parameters:
dest (
Statistics) – the destination- Return type: