moptipyapps.prodsched package

Examples of production scheduling.

Submodules

moptipyapps.prodsched.generator module

Methods for generating instances.

>>> from moptipyapps.prodsched.instance import to_stream
>>> def receive(inst: Instance) -> None:
...     for z in to_stream(inst):
...         print(z)
>>> with InstanceGenerator(receive) as ig:
...     ig.set_seed(11323)
class moptipyapps.prodsched.generator.Customer(id, n_demands, time_offset, demands_products, between_order_times, until_deadline)[source]

Bases: object

A customer represents purchase orders sequences.

between_order_times: IntDistribution

the time between orders

demands_products: tuple[tuple[int, IntDistribution], ...]

the products that the customer orders and the corresponding amount distributions

id: int

the customer ID

n_demands: int

the number of demands that the customer will issue

time_offset: int

the time when the customer begins doing their orders

until_deadline: IntDistribution

the base time given until the deadline

moptipyapps.prodsched.generator.INFO_AGGREGATED: Final[str] = 'AGGREGATED '

an aggregation of the giving sampling method

moptipyapps.prodsched.generator.INFO_BETWEEN_ORDER_JITTER: Final[str] = 'src_between_order_jitter'

the time jitter added to the days between orders

moptipyapps.prodsched.generator.INFO_CUSTOMERS: Final[str] = 'src_customers'

the source for the customers

moptipyapps.prodsched.generator.INFO_CUSTOMER_BASE_AMOUNT: Final[str] = 'src_customer_base_amount'

the base amount that the customer usually orders

moptipyapps.prodsched.generator.INFO_CUSTOMER_BETWEEN_ORDER_DAYS: Final[str] = 'src_customer_between_order_days'

the distribution for the number of days between orders per customer

moptipyapps.prodsched.generator.INFO_CUSTOMER_SIZE: Final[str] = 'src_customer_size'

the size of the custmer

moptipyapps.prodsched.generator.INFO_CUSTOMER_TIME_OFFSET: Final[str] = 'src_customer_time_offset'

the time offset of the customer

moptipyapps.prodsched.generator.INFO_CUSTOMER_TO_DEADLINE_DAYS: Final[str] = 'src_customer_to_deadline_days'

the normal time that the customer provides until the deadline

moptipyapps.prodsched.generator.INFO_DAY_TIME_UNIS: Final[str] = 'src_day_time_units'

the key for the day time unit distribution

moptipyapps.prodsched.generator.INFO_DAY_TIME_UNIS_GEN: Final[str] = 'src_day_time_units_gen'

the key for the day time unit distribution generator

moptipyapps.prodsched.generator.INFO_DEMANDS: Final[str] = 'src_demands'

the source for the routes

moptipyapps.prodsched.generator.INFO_DEMANDS_PER_CUSTOMER: Final[str] = 'src_demands_per_customer'

the distribution for the number of demands per customer

moptipyapps.prodsched.generator.INFO_DETERMINISTIC: Final[str] = 'ONLY_ONE_CHOICE'

a deterministic structure

moptipyapps.prodsched.generator.INFO_FIXED: Final[str] = 'USER_PROVIDED'

a fixed structure

moptipyapps.prodsched.generator.INFO_MACHINES_PER_PRODUCT: Final[str] = 'src_routes_machines_per_product'

the machines per product, if any

moptipyapps.prodsched.generator.INFO_MACHINE_ORDER_OFFSET: Final[str] = 'src_routes_machine_order_offset'

the machines order offset

moptipyapps.prodsched.generator.INFO_N_CUSTOMERS: Final[str] = 'src_n_customers'

the source for the number of customers

moptipyapps.prodsched.generator.INFO_N_DEMANDS: Final[str] = 'src_n_demands'

the source for the number of demands

moptipyapps.prodsched.generator.INFO_N_MACHINES: Final[str] = 'src_n_machines'

the source for the number of machines

moptipyapps.prodsched.generator.INFO_N_PRODUCTS: Final[str] = 'src_n_products'

the source for the number of products

moptipyapps.prodsched.generator.INFO_ORDER_AMOUNT_JITTER: Final[str] = 'src_order_amount_jitter'

the jitter applied to the orders

moptipyapps.prodsched.generator.INFO_PRODUCTION_TIME_UNITS: Final[str] = 'src_production_time_units'

the key for the production time unit distribution

moptipyapps.prodsched.generator.INFO_PRODUCTION_TIME_UNITS_GEN: Final[str] = 'src_production_time_units_gen'

the key for the production time unit distribution generator

moptipyapps.prodsched.generator.INFO_PRODUCTS_PER_CUSTOMER: Final[str] = 'src_products_per_customer'

the distribution for the number of products per customer

moptipyapps.prodsched.generator.INFO_PRODUCT_BASE_DEMAND: Final[str] = 'src_product_base_demand'

the distribution of the amount that is usually ordered per product

moptipyapps.prodsched.generator.INFO_PRODUCT_BASE_DEMANDS: Final[str] = 'src_product_base_demands'

the source for the product base demands

moptipyapps.prodsched.generator.INFO_RAND_SEED: Final[str] = 'generator_rand_seed'

the random seed of the generator

moptipyapps.prodsched.generator.INFO_ROUTES: Final[str] = 'src_routes'

the source for the routes

moptipyapps.prodsched.generator.INFO_SAMPLED: Final[str] = 'SAMPLED'

a sampled structure

moptipyapps.prodsched.generator.INFO_TO_DEADLINE_JITTER: Final[str] = 'src_to_deadline_jitter'

the jitter applied until the deadline

class moptipyapps.prodsched.generator.InstanceGenerator(consumer)[source]

Bases: AbstractContextManager

The class for generating instances.

customers()[source]

Get the customer settings.

Return type:

tuple[Customer, ...]

Returns:

the customer settings

day_time_units()[source]

Get the day time units distribution.

Return type:

IntDistribution

Returns:

the day time units distribution

demands()[source]

Get the demands of the scenario.

Return type:

tuple[Demand, ...]

Returns:

the demands

n_customers()[source]

Get the number of customers.

Return type:

int

Returns:

the number of customers

n_demands()[source]

Get the number of demands.

Return type:

int

Returns:

the number of demands

n_machines()[source]

Get the number of machines.

Return type:

int

Returns:

the number of machines

n_products()[source]

Get the number of products.

Return type:

int

Returns:

the number of products

name()[source]

Get the name.

Return type:

str

Returns:

the name

product_base_demands()[source]

Get the product base demand unit.

Return type:

tuple[IntDistribution, ...]

Returns:

the base demand distributions

production_time_units()[source]

Get the production time units base distribution.

Return type:

IntDistribution

Returns:

the production time units base distribution

routes()[source]

Get the routes for the products through the machines.

Return type:

tuple[tuple[int, ...], ...]

Returns:

the routes for the products through the machines

seed()[source]

Get the seed.

Return type:

int

Returns:

the seed

set_customers(customers=None, time_offset=None, demands_per_customer=None, products_per_customer=None, customer_size=None, between_order_days=None, between_order_jitter=None, to_deadline_days=None, to_deadline_jitter=None)[source]

Set the customers.

You can either set the customers directly, by providing customers. Or they can be randomly sampled.

Parameters:
  • customers (Optional[Iterable[Customer]], default: None) – the customers

  • time_offset (int | IntDistribution | None, default: None) – the distribution for the time offset when customers begin ordering

  • demands_per_customer (int | IntDistribution | None, default: None) – an optional distribution of the number of demands that each customer may issue. In total, there will always be exactly the number of expected demands. The distributions are sampled again and again, until that number is reached.

  • products_per_customer (int | IntDistribution | None, default: None) – the products to be sampled per customer

  • customer_size (int | IntDistribution | None, default: None) – the multiplier of the order amount

  • between_order_days (int | IntDistribution | None, default: None) – a distribution for the number of days between customer orders

  • between_order_jitter (int | IntDistribution | None, default: None) – the jitter for the time between orders

  • to_deadline_days (int | IntDistribution | None, default: None) – the normal amount of days that a customer gives as deadline

  • to_deadline_jitter (int | IntDistribution | None, default: None) – the jitter for the above

Return type:

None

set_day_time_units(tu)[source]

Set the distribution of the time units per day.

Parameters:

tu (int | IntDistribution) – the time units per day

Return type:

None

set_demands(demands=None)[source]

Set the demands.

You can either set the demands directly, by providing demands. Or they can be randomly sampled.

Parameters:

demands (Optional[Iterable[Demand]], default: None) – the demands

Return type:

None

set_n_customers(n_customers)[source]

Set the number of customers.

Parameters:

n_customers (int | IntDistribution) – the number of customers, either explicitly or implicitly set.

Return type:

None

set_n_demands(n_demands)[source]

Set the number of demands.

Parameters:

n_demands (int | IntDistribution) – the number of demands, either explicitly or implicitly set.

Return type:

None

set_n_machines(n_machines)[source]

Set the number of machines.

Parameters:

n_machines (int | IntDistribution) – the number of machines, either explicitly or implicitly set.

Return type:

None

set_n_products(n_products)[source]

Set the number of products.

Parameters:

n_products (int | IntDistribution) – the number of products, either explicitly or implicitly set.

Return type:

None

set_name(name)[source]

Set the name of the instance.

Parameters:

name (str) – the name of the instance.

Return type:

None

set_product_base_demands(base_demands=None, base_demand=None)[source]

Set the base unit distributions for the products.

Parameters:
Return type:

None

set_production_time_units(tu)[source]

Set the distribution of the base time units per production step.

Parameters:

tu (int | IntDistribution) – the time units per day

Return type:

None

set_routes(routes=None, n_machines_per_product=None, machine_order_offset=None)[source]

Set the routes on which the products pass through machines.

The routes can either be set directly as a constance, by providing routes. Otherwise, they can be sampled randomly.

If you specify routes, then this parameter provides a sequence of sequences: For each product, it specifies the machines through which the product must pass. No product can pass through the same machine twice. No machine must be left unused. Each product passes through at least one machine.

If you want to randomly sample the machines, you can provide a distribution from which the number of machines through which each product needs to go is drawn via n_machines_per_product.

Now, in the real world, even if we have different products with different production steps, the order in which the products pass through machines is not completely “random”. If we randomly sample machines, then our idea to realize a reasonable order is as follows: If a product goes through a set of machines {a, b, c}, then it tends to go through the machine with the lower indices first. In some cases, the order may be different, but generally lower-index machines tend to come earlier in production cycles. To realize this, once we have the machines for a product selected, each machine gets base priority p = 10*i, where i is the machine index. To this base priority, we add a random number distributed as machine_order_offset. This offset can thus change the order priority of the machines. The machines are then used by the product based on the order priority.

Parameters:
Return type:

None

set_seed(seed=None)[source]

Set the random seed either explicitly or automatically.

Parameters:

seed (int | None, default: None) – the optional random seed

Return type:

None

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 machines be 4. >>> n_machines = 4

There will be 6 customer demands. >>> n_demands = 6

Each product may take a different route through different machines. >>> 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 machine requires a certain working time for each unit of each product. This production time may vary over time. For example, maybe machine 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 machines that a product is actually routed through. >>> m0_p0 = [10, 20, 11, 40, 8, 60] >>> m0_p1 = [12, 20, 7, 40, 11, 70] >>> m0_p2 = [] >>> m1_p0 = [] >>> m1_p1 = [20, 50, 30, 120, 7, 200] >>> m1_p2 = [21, 50, 29, 130, 8, 190] >>> m2_p0 = [ 8, 20, 9, 60] >>> m2_p1 = [10, 90] >>> m2_p2 = [12, 70, 30, 120] >>> m3_p0 = [70, 200, 3, 220] >>> m3_p1 = [60, 220, 5, 260] >>> m3_p2 = [30, 210, 10, 300] >>> machine_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_machines, n_demands, … routes, demands, warehous_at_t0, … machine_product_unit_times, infos) >>> instance.name ‘my_instance’

>>> instance.n_customers
5
>>> instance.n_machines
4
>>> instance.n_demands
6
>>> instance.n_products
3
>>> instance.routes
((0, 3, 2), (0, 2, 1, 3), (1, 2, 3))
>>> instance.demands
(Demand(release_time=1240, deadline=3000, demand_id=0, customer_id=0, product_id=1, amount=20), Demand(release_time=2300, deadline=4000, demand_id=1, customer_id=1, product_id=0, amount=10), Demand(release_time=4234, deadline=27080, demand_id=5, customer_id=3, product_id=0, amount=19), Demand(release_time=5410, deadline=16720, demand_id=4, customer_id=4, product_id=2, amount=23), Demand(release_time=7300, deadline=9000, demand_id=3, customer_id=3, product_id=1, amount=12), Demand(release_time=8300, deadline=11000, demand_id=2, customer_id=2, product_id=2, amount=7))
>>> instance.warehous_at_t0
(10, 0, 6)
>>> instance.machine_product_unit_times
(((10, 20, 11, 40, 8, 60), (12, 20, 7, 40, 11, 70), ()), ((), (20, 50, 30, 120, 7, 200), (21, 50, 29, 130, 8, 190)), ((8, 20, 9, 60), (10, 90), (12, 70, 30, 120)), ((70, 200, 3, 220), (60, 220, 5, 260), (30, 210, 10, 300)))
>>> 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

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_machines == instance.n_machines 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.warehous_at_t0 == instance.warehous_at_t0 True >>> i2.machine_product_unit_times == instance.machine_product_unit_times True >>> i2.infos == instance.infos True

moptipyapps.prodsched.instance.DEMAND_AMOUNT: Final[int] = 3

the index of the demanded amount

moptipyapps.prodsched.instance.DEMAND_CUSTOMER: Final[int] = 1

the index of the customer ID

moptipyapps.prodsched.instance.DEMAND_DEADLINE: Final[int] = 5

the index of the demand deadline

moptipyapps.prodsched.instance.DEMAND_ID: Final[int] = 0

the index of the demand ID

moptipyapps.prodsched.instance.DEMAND_PRODUCT: Final[int] = 2

the index of the product ID

moptipyapps.prodsched.instance.DEMAND_TIME: Final[int] = 4

the index of the demand release time

class moptipyapps.prodsched.instance.Demand(release_time, deadline, demand_id, customer_id, product_id, amount)[source]

Bases: Iterable[int]

The record for demands.

amount: int

the amount

customer_id: int

the customer ID

deadline: int

the deadline, i.e., when the customer expects the result

demand_id: int

the ID of the demand

product_id: int

the ID of the product

release_time: int

the release time, i.e., when the demand enters the system

class moptipyapps.prodsched.instance.Instance(name, n_products, n_customers, n_machines, n_demands, routes, demands, warehous_at_t0, machine_product_unit_times, infos=None)[source]

Bases: Component

An 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.

machine_product_unit_times: Final[tuple[tuple[tuple[int, ...], ...], ...]]

The per-machine unit production times for each product. Each machine 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 machine, 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.

n_customers: Final[int]

the number of customers in the scenario

n_demands: Final[int]

the number of demands in the scenario

n_machines: Final[int]

the number of machines or workstations in the scenario

n_products: Final[int]

the number of products in the scenario

name: Final[str]

the name of this instance

routes: Final[tuple[tuple[int, ...], ...]]

the product routes, i.e., the machines through which each product must pass

warehous_at_t0: Final[tuple[int, ...]]

The units of product in the warehouse at time step 0. For each product, we have either 0 or a positive amount of product.

moptipyapps.prodsched.instance.KEY_DEMAND: Final[str] = 'demand'

the first part of the demand key

moptipyapps.prodsched.instance.KEY_IDX_END: Final[str] = ']'

the end of a key index

moptipyapps.prodsched.instance.KEY_IDX_START: Final[str] = '['

the start of a key index

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_NAME: Final[str] = 'name'

the instance name key

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_MACHINES: Final[str] = 'n_machines'

the key for the number of machines

moptipyapps.prodsched.instance.KEY_N_PRODUCTS: Final[str] = 'n_products'

the key for the number of products

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.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:
  • start_time (int) – the starting time of the job

  • amount (int) – the number of units to be produced

  • production_times (tuple[int, ...]) – the production times array

Return type:

int

Returns:

the end time

Here, the production time is 10 time units / 1 product unit, valid until end time 100. >>> compute_finish_time(0, 1, (10, 100)) 10

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, 1, (10, 100)) 260

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, 1, (10, 100)) 100

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 cannot be completed until the end of the period. So only 0.5 units are produced, needing 5 time units and finishing at period 100. Then the production time window begins again. Since there is only 1 production time, in the second production cycle there will still be 10 time units per production unit. The remaining 0.5 units will be produced in 5 time units, meaning that the overall production is finished at time step 105. >>> compute_finish_time(95, 1, (10, 100)) 105

Now we have two production periods. The production begins again at time step 95. 0.5 units are finished until the production period ends at time step 100. Then, the second production period begins, requiring 20 time units per product unit. We thus need 10 time units to finish the remaining 0.5 product unit, completing the job at time unit 110. >>> compute_finish_time(95, 1, (10, 100, 20, 200)) 110

Now things get more complex. We want to do 10 units of product. We can finish 0.5 units until the first period ends after 5 time steps. Then, in the second period, we can do exactly 2 units of product until the period ends at time 140. This leaves 7.5 units of product to be done in the third period, where each unit needs 50 time units. Thus, we end up needing 5 + 40 + 7.5*50 time units, which add to the starting time. >>> compute_finish_time(95, 10, (10, 100, 20, 140, 50, 5000)) 515 >>> 95 + (0.5*10 + 2*20 + 7.5*50) 515.0

This is the same as the example before, except that the third period now ends at time step 200. Thus, we can only do 1.2=(200-140)/50 units of product in the 200 - 140 time units this period lasts. Then the production cycle wraps over. Luckily, we can complete the remaining 6.3 units of product in the first period, where each product unit needs only 10 time units. >>> compute_finish_time(95, 10, (10, 100, 20, 140, 50, 200)) 263 >>> 95 + (0.5*10 + 2*20 + 1.2*50 + 6.3*10) 263.0

Here we illustrate that production times are rounded to the nearest larger integer. If we encounter a situation such that the production would need 246.6666 time units, we return 247. The only exception for this “rounding up” strategy is if the result is very very close to the nearest integer. For example, 10.00006103515625 would be rounded to 11, whereas 10.0000610351562 will be rounded to 10. (0.00006103515625 = 1/16384 = 2**⁻14). >>> compute_finish_time(95, 10, (10, 100, 20, 140, 50, 200, 3, 207)) 247 >>> 95 + (0.5*10 + 2*20 + 1.2*50 + (7/3)*3 + (6.3-(7/3))*10) 246.66666666666666

moptipyapps.prodsched.instance.from_stream(stream)[source]

Read an instance from a data stream.

Parameters:

stream (Iterable[str]) – the data stream

Return type:

Instance

Returns:

the instance

moptipyapps.prodsched.instance.to_stream(instance)[source]

Convert an instance to a stream of data.

Parameters:

instance (Instance) – the instance to convert to a stream

Return type:

Generator[str, None, None]

Returns:

the stream of data

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. There are 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 machines what to do.

## 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 invoke ctrl_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:

    1. An amount of a product has been produced (amount > 0).

    2. 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).

    3. 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 machines. First it passes through machine 0, then through machine 1. The per-unit production time is always 10 time units on machine 0 and 30 time units on machine 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_machines=2, n_demands=1, … routes=[[0, 1]], … demands=[[0, 0, 0, 10, 20, 100]], … warehous_at_t0=[0], … machine_product_unit_times=[[[10, 10000]], … [[30, 10000]]])

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 machine 0. Since machine 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 machine 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()) >>> simulation.ctrl_run() start T=20: EVENT product=0, amount=0, in_warehouse=0, in_production=0, 1 pending demands T=20: EVENT machine=0, 1 jobs queued T=20: beginning to produce 10 units of product 0 on machine 0 T=120: finished producing 10 units of product 0 on machine 0 T=120: EVENT machine=1, 1 jobs queued T=120: beginning to produce 10 units of product 0 on machine 1 T=420: finished producing 10 units of product 0 on machine 1 T=420: EVENT product=0, amount=10, in_warehouse=0, in_production=0, 1 pending demands T=420: demand 0 for 10 units of product 0 satisfied T=420: finished

>>> instance = Instance(
...     name="test2", n_products=2, n_customers=1, n_machines=2, n_demands=2,
...     routes=[[0, 1], [1, 0]],
...     demands=[[0, 0, 1, 10, 20, 90], [1, 0, 0, 5, 22, 200]],
...     warehous_at_t0=[2, 1],
...     machine_product_unit_times=[[[10, 50, 15, 100], [5, 20, 7, 35, 4, 50]],
...                                 [[ 5, 24,  7,  80], [3, 21, 6, 50,]]])
>>> instance.name
'test2'
>>> simulation = Simulation(instance, PrintingListener())
>>> simulation.ctrl_run()
start
T=0: EVENT product=0, amount=2, in_warehouse=0, in_production=0, 0 pending demands
T=0: 2 units of product 0 in warehouse
T=0: EVENT product=1, amount=1, in_warehouse=0, in_production=0, 0 pending demands
T=0: 1 units of product 1 in warehouse
T=20: EVENT product=1, amount=0, in_warehouse=1, in_production=0, 1 pending demands
T=20: EVENT machine=1, 1 jobs queued
T=20: beginning to produce 9 units of product 1 on machine 1
T=22: EVENT product=0, amount=0, in_warehouse=2, in_production=0, 1 pending demands
T=22: EVENT machine=0, 1 jobs queued
T=22: beginning to produce 3 units of product 0 on machine 0
T=53: finished producing 3 units of product 0 on machine 0
T=62: finished producing 9 units of product 1 on machine 1
T=62: EVENT machine=1, 1 jobs queued
T=62: beginning to produce 3 units of product 0 on machine 1
T=62: EVENT machine=0, 1 jobs queued
T=62: beginning to produce 9 units of product 1 on machine 0
T=83: finished producing 3 units of product 0 on machine 1
T=83: EVENT product=0, amount=3, in_warehouse=2, in_production=0, 1 pending demands
T=83: demand 1 for 5 units of product 0 satisfied
T=83: 0 units of product 0 in warehouse
T=108: finished producing 9 units of product 1 on machine 0
T=108: EVENT product=1, amount=9, in_warehouse=1, in_production=0, 1 pending demands
T=108: demand 0 for 10 units of product 1 satisfied
T=108: 0 units of product 1 in warehouse
T=108: finished
>>> simulation.ctrl_reset()
>>> simulation.ctrl_run()
start
T=0: EVENT product=0, amount=2, in_warehouse=0, in_production=0, 0 pending demands
T=0: 2 units of product 0 in warehouse
T=0: EVENT product=1, amount=1, in_warehouse=0, in_production=0, 0 pending demands
T=0: 1 units of product 1 in warehouse
T=20: EVENT product=1, amount=0, in_warehouse=1, in_production=0, 1 pending demands
T=20: EVENT machine=1, 1 jobs queued
T=20: beginning to produce 9 units of product 1 on machine 1
T=22: EVENT product=0, amount=0, in_warehouse=2, in_production=0, 1 pending demands
T=22: EVENT machine=0, 1 jobs queued
T=22: beginning to produce 3 units of product 0 on machine 0
T=53: finished producing 3 units of product 0 on machine 0
T=62: finished producing 9 units of product 1 on machine 1
T=62: EVENT machine=1, 1 jobs queued
T=62: beginning to produce 3 units of product 0 on machine 1
T=62: EVENT machine=0, 1 jobs queued
T=62: beginning to produce 9 units of product 1 on machine 0
T=83: finished producing 3 units of product 0 on machine 1
T=83: EVENT product=0, amount=3, in_warehouse=2, in_production=0, 1 pending demands
T=83: demand 1 for 5 units of product 0 satisfied
T=83: 0 units of product 0 in warehouse
T=108: finished producing 9 units of product 1 on machine 0
T=108: EVENT product=1, amount=9, in_warehouse=1, in_production=0, 1 pending demands
T=108: demand 0 for 10 units of product 1 satisfied
T=108: 0 units of product 1 in warehouse
T=108: finished
class moptipyapps.prodsched.simulation.Job(product_id, amount, issue_time=-1, machine_time=-1, step=-1)[source]

Bases: object

The record for a production job.

amount: int

the amount to produce

issue_time: int = -1

the time when the job was issued

machine_time: int = -1

the time when the job arrived at the queue of the current machine.

product_id: int

the ID of the product to be produced.

step: int = -1

the current job step, starts at 0.

class moptipyapps.prodsched.simulation.Listener[source]

Bases: object

A listener for simulation events.

demand_satisfied(time, demand)[source]

Report that a given demand has been satisfied.

Parameters:
  • time (int) – the time index when the demand was satisfied

  • demand (Demand) – the demand that was satisfied

Return type:

None

event_machine(time, machine_id, queue)[source]

Get notified right before Simulation.event_machine().

If this event happens, the machine 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().

Parameters:
  • time (int) – the current time

  • machine_id (int) – the machine ID

  • queue (tuple[Job, ...]) – the job queue for this machine

Return type:

None

event_product(time, product_id, amount, in_warehouse, in_production, pending_demands)[source]

Get notified right before Simulation.event_product().

Parameters:
  • time (int) – the current system time

  • product_id (int) – the id of the product

  • amount (int) – the amount of the product that appears

  • in_warehouse (int) – the amount of the product currently in the warehouse

  • in_production (int) – the amounf of product currently under production

  • pending_demands (tuple[Demand, ...]) – the pending orders for the product

Return type:

None

finished(time)[source]

Be notified that the simulation has been finished.

Parameters:

time (int) – the time when we are finished

Return type:

None

produce_at_begin(time, machine_id, job)[source]

Report the start of the production of a certain product at a machine.

Parameters:
  • time (int) – the current time

  • machine_id (int) – the machine ID

  • job (Job) – the production job

Return type:

None

produce_at_end(time, machine_id, job)[source]

Report the completion of the production of a product at a machine.

Parameters:
  • time (int) – the current time

  • machine_id (int) – the machine ID

  • job (Job) – the production job

Return type:

None

product_in_warehouse(time, product_id, amount)[source]

Report a change of the amount of products in the warehouse.

Parameters:
  • time (int) – the current time

  • product_id (int) – the product ID

  • amount (int) – the new absolute total amount of that product in the warehouse

Return type:

None

start()[source]

Get notification that the simulation is starting.

Return type:

None

class moptipyapps.prodsched.simulation.PrintingListener[source]

Bases: Listener

A listener that just prints simulation events.

demand_satisfied(time, demand)[source]

Print that a demand was satisfied.

Return type:

None

event_machine(time, machine_id, queue)[source]

Print the machine event.

Return type:

None

event_product(time, product_id, amount, in_warehouse, in_production, pending_demands)[source]

Print the prouct event.

Return type:

None

finished(time)[source]

Print that the simulation has finished.

Return type:

None

produce_at_begin(time, machine_id, job)[source]

Print that the production at a given machine begun.

Return type:

None

produce_at_end(time, machine_id, job)[source]

Print that the production at a given machine ended.

Return type:

None

product_in_warehouse(time, product_id, amount)[source]

Print the product amount in the warehouse.

Return type:

None

start()[source]

Print that the simulation begins.

Return type:

None

class moptipyapps.prodsched.simulation.Simulation(instance, listener)[source]

Bases: object

A simulator for production scheduling.

act_demand_satisfied(demand)[source]

Notify the system that a given demand has been satisfied.

Parameters:

demand (Demand) – the demand that was satisfied

Return type:

None

act_exec_job(job)[source]

Execute the job on its current machine.

Parameters:

job (Job) – the job to be executed

Return type:

None

act_produce(product_id, amount)[source]

Order the production of amount units of product.

Parameters:
  • product_id (int) – the product ID

  • amount (int) – the amount that needs to be produced

Return type:

None

act_store_in_warehouse(product_id, amount)[source]

Add a certain amount of product to the warehouse.

Parameters:
  • product_id (int) – the product ID

  • amount (int) – the amount

Return type:

None

act_take_from_warehouse(product_id, amount)[source]

Remove a certain amount of product to the warehouse.

Parameters:
  • product_id (int) – the product ID

  • amount (int) – the amount

Return type:

None

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:

None

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:

None

event_machine(time, machine_id, queue)[source]

Process an event for a given machine.

If this event happens, the machine 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().

Parameters:
  • time (int) – the current time

  • machine_id (int) – the machine ID

  • queue (tuple[Job, ...]) – the job queue for this machine

Return type:

None

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:

  1. An amount of a product has been produced (amount > 0).

  2. 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).

  3. 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:

  1. act_store_in_warehouse() to store a positive amount of product in the warehouse.

  2. act_take_from_warehouse() to take a positive amount of product out of the warehouse (must be <= in_warehouse.

  3. act_produce() to order the production of a positive amount of the product.

  4. 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 with act_take_from_warehouse().

Parameters:
  • time (int) – the current system time

  • product_id (int) – the id of the product

  • amount (int) – the amount of the product that appears

  • in_warehouse (int) – the amount of the product currently in the warehouse

  • in_production (int) – the amounf of product currently under production

  • pending_demands (tuple[Demand, ...]) – the pending orders for the product

Return type:

None

instance: Final[Instance]

the instance whose data is simulated