Simulating a production process

Arjen Markus (7 april 2020) I got the idea from a column in the newspaper: because of the corona crisis, at this moment in time the demand for all manner of medical supplies, such as face masks or pipettes for testing, is far greater than in ordinary times. However, the production of these items is geared to (cost) efficiency, like so many other things: they are produced "just in time" and there is little stock. So when the demand suddenly increases considerably, the production line cannot cope with the demand and the delivery times grow.

This can - in principle - be simulated in a straightforward way:

  • The factory produces a certain amount/number of products based on the demands that come in, with a particular maximum.
  • To manufacture the product a certain amount of raw materials is required - this needs to be supplied too, but this also has a certain maximum.
  • We need to keep track of how much is produced, how much is in stock and how fast it can be delivered.

It is simply a lot of bookkeeping.

(From experiments with this little production model I found that it makes not much sense to have the parameters Pmax en Msupply be independent. The one with the lower value effectively controls the production.)

Example of a simulation

Here is a screenshot of the GUI and a simulation:

Production process - graph

Code

The code below consists of two parts:

  • The code for the simulation itself, which can be run as is
  • The code for a simple GUI: it shows the results in a graph that gets refreshed and filled when you hit the "Run model" button.

When you run the simulation, you will see that sometimes it produces really large demands. The code does not analyse the consequences, though. That might be a useful addition.

Code for the simulation

# production_line.tcl --
#     Simplified model of a production chain:
#
#     TODO:
#     - Why can Dtotal get negative?
#     - A GUI to set the parameters and show the results in graph form
#     - Keep track of the time of delivery
#
#     A factory is producing a product on demand according to the modern efficiency principles -
#     keep only small stocks of raw materials and finished products. If an order comes in, the
#     product is manufactured. This works fine, as long as the demand is not too large and the raw
#     materials can be delivered.
#
#     We use a single unit of time (say a week), to measure the demand, the production and the
#     time of delivery.
#
#     Parameters:
#     P         - the amount of product that will be produced in this time unit (or number of products)
#     Pmax      - the maximum production per time unit
#     D         - the current demand for the product (comes in once a week/time unit)
#     Dtotal    - the total demand at this moment (if we have a backlog)
#     Pstock    - the amount of product in stock
#     Psmax     - the maximum amount in stock - we will never produce more than can be stored
#     MP        - the amount of raw materials per unit of product
#     M         - the amount of raw materials required to make P of the product
#     Mstock    - the amount of raw materials in stock
#     Msmax     - the maximum amount of raw materials in stock
#     Msupply   - the maximum supply of raw materials
#
#     We need to keep track of:
#     - the demand over time
#     - the production over time
#     - the mean time of delivery over time
#
#     Our experiment entails that the demand varies randomly over time, but can make a significant
#     jump, overloading the production facilities.
#
#     Dmean    - mean demand
#     Ddev     - fluctuation in the demand - currently modelled via a uniformly random variation
#


# getDemand --
#     Generate the demand for this time step
#
# Arguments:
#     None
#
# Returns:
#     The new value for the demand
#
proc getDemand {} {
    global Dmean
    global Ddev
    expr { $Dmean + $Ddev * (2.0 * rand() - 1.0) }
}

# getIdealProduction --
#     Determine the ideal production level (assuming that the raw materials
#     are in plenty supply
#
# Arguments:
#     demand         Total demand for this time unit
#
# Result:
#     Production to be made and amount of products from stock
#
proc getIdealProduction {demand} {
    global Pmax
    global Pstock
    global Psmax

    #
    # We may have some product in stock, in that case, use the stock
    # If that is not enough, produce what is demanded (up to the maximum)
    # If the stock is empty, produce extra
    #
    set Psold $Pstock

    if { $demand <= $Pstock } {
        set Pstock     [expr {$Pstock - $demand}]
        set production 0.0
    } elseif { $Pstock > 0.0 } {
        set production [expr {min($demand-$Pstock,$Pmax)}]
        set Pstock     0.0
    } else {
        set production [expr {min($demand+$Psmax,$Pmax)}]
        set Pstock     [expr {max(min($production-$demand,$Psmax),0.0)}]
    }
    return [list $production [expr {$Psold - $Pstock}]]
}

# getRawMaterials --
#     Determine how much raw materials are needed/can be delivered
#
# Arguments:
#     production     Ideal production
#
# Result:
#     Amount of raw materials that will be used for the production in this time unit,
#     the amount that is ordered to fill the stock and an indication whether it can
#     be delivered
#
proc getRawMaterials {production} {
    global MP
    global Mstock
    global Msmax
    global Msupply

    #
    # We may have some raw materials in stock, in that case, use the stock
    # If that is not enough, order what is demanded (up to the maximum)
    # If the stock is empty, order extra
    #
    set useRaw     [expr {$MP * $production}]
    set orderExtra 0.0

    if { $useRaw <= $Mstock } {
        set Mstock     [expr {$Mstock - $useRaw}]
        set orderRaw   0.0
    } elseif { $Mstock > 0.0 } {
        set orderRaw   [expr {max($useRaw-$Mstock, 0.0)}]
        set Mstock     [expr {max($Mstock-$useRaw, 0.0)}]
    } else {
        set orderRaw   $useRaw
        set orderExtra $Msmax
        set Mstock     $Msmax
    }

    #
    # Check that this amount can be delivered
    #
    # - hier komt de denkfout vandaan!
    #
    if { $orderRaw+$orderExtra > $Msupply } {
        set deliverable 0
        set orderRaw    $Msupply
        set orderExtra  0.0
        set Mstock      [expr {max($Msupply - $useRaw,0.0)}]
    } else {
        set deliverable 1
    }

    return [list $orderRaw $orderExtra $deliverable]
}

#
# Set defaults
#
set Pmax        215.0
set Pstock       50.0
set Psmax       100.0

set MP            1.0
set Msmax        10.0
set Mstock        0.0

set Dmean       200.0
set Ddev         50.0

# runModel --
#     Run the simulation
#
# Arguments:
#     print        Print the output (if 1, otherwise store the result)
#
# Result:
#     Output to the screen or results in a global array
#
proc runModel {print} {
    global Msupply
    global result
    global Pmax
    global Pstock
    global Mstock
    global MP

    set t         -1
    set demand     0.0
    set production 0.0
    set Dtotal        0.0  ;# No backlog
    set Msupply     $Pmax  ;# These must be in some relation ...

    if { $print } {
        puts [format %5d%10.2f%10.2f%10.2f%10.2f $t $demand $Dtotal $production $Pstock]
    } else {
        set result(t)           {}
        set result(demand)      {}
        set result(Dtotal)      {}
        set result(production)  {}
        set result(Pstock)      {}
        set result(Mstock)      {}
        set result(deliverable) {}
    }

    for {set t 0} {$t < 200} {incr t} {
        set demand [getDemand]
        set Dtotal [expr {$Dtotal + $demand}]

        lassign [getIdealProduction $Dtotal] production fromStock
        lassign [getRawMaterials $production] orderRaw orderExtra deliverable

        set prodIdeal $production
        if { ! $deliverable } {
            set production [expr {min( $production, $orderRaw / $MP )}]
        }
        set Dtotal [expr {$Dtotal - ($production+$fromStock)}]

        if { $print } {
            puts [format %5d%10.2f%10.2f%10.2f%10.2f%10.2f%10.2f%5d $t $demand $Dtotal $production $prodIdeal $Pstock $Mstock $deliverable]
        } else {
            lappend result(t)           $t
            lappend result(demand)      $demand
            lappend result(Dtotal)      $Dtotal
            lappend result(production)  $production
            lappend result(Pstock)      $Pstock
            lappend result(Mstock)      $Mstock
            lappend result(deliverable) $deliverable
        }
    }
}

# test --
#     First test:
#     Can we produce?
#
if { ! [info exists gui] } {
    runModel 1
}

Code for the GUI

# production_gui.tcl --
#     GUI for setting the parameters of the production line simulation and
#     displaying the results
#
package require Plotchart

set gui 1
source production_line.tcl

# setupMainWindow --
#     Set up the main window
#
# Arguments:
#     None
#
# Result:
#     Main window filled in with several entry widgets and a canvas
#
proc setupMainWindow {} {
    global p

    canvas .c -bg white -height 500 -width 700

    frame  .f
    label  .f.txt_pmax  -text "Maximum production"    ; entry .f.entry_pmax  -textvariable Pmax
    label  .f.txt_psmax -text "Maximum storage"       ; entry .f.entry_psmax -textvariable Psmax
    label  .f.txt_dmean -text "Mean demand"           ; entry .f.entry_dmean -textvariable Dmean
    label  .f.txt_ddev  -text "Fluctuation in demand" ; entry .f.entry_ddev  -textvariable Ddev

    label  .f.empty     -text ""
    label  .f.empty2    -text ""

    button .f.run       -text "Run model" -command [list runSimulation]

    grid .f.txt_pmax  .f.entry_pmax  -pady 2 -sticky w
    grid .f.txt_psmax .f.entry_psmax -pady 2 -sticky w
    grid .f.empty     -                      -sticky w
    grid .f.txt_dmean .f.entry_dmean -pady 2 -sticky w
    grid .f.txt_ddev  .f.entry_ddev  -pady 2 -sticky w
    grid .f.empty2    -                      -sticky w
    grid .f.run                              -sticky w

    grid .f .c -sticky news -padx 3

    set p [::Plotchart::createXYPlot .c {0 200 50} {0 300 50}]
}

# runSimulation --
#     Run the simulation and present the result
#
# Arguments:
#     None
#
# Result:
#     Simulation results shown
#
proc runSimulation {} {
    global result
    global p

    runModel 0

    $p deletedata

    $p dataconfig demand     -colour blue
    $p dataconfig production -colour lime
    $p dataconfig backlog    -colour red

    foreach t $result(t) demand $result(demand) production $result(production) Dtotal $result(Dtotal) {
        $p plot demand     $t $demand
        $p plot production $t $production
        $p plot backlog    $t $Dtotal
    }
}

# main --
#     Start the program
#
setupMainWindow