jsimIO Assembly

Case description

The system consists in an assembly line to produce hinges.

The assembly line is composed of a linear conveyor belt, 20 stations and 4 buffers. It is assumed that the buffer of each part feeder has such capacity that there is no starvation risk. In this way it is possible to exclude the feeding system from the analysis of the system. The time required for each task is considered deterministic thanks to the high level of automation.

The stations are subject to failures.

Formal model

System design

  1. Import the libraries required to instantiate the objects, perform the simulation and export the log file.

     from jsimIO import *
  2. Define and initialize the objects required: Model, Source, Station, Sink, Fork, Part. Buffers are included in the object Station. Each instance has to be linked with the model taken into consideration.

     # Create a Model instance, with the name of the model
    
     model = Model("Hinge_Line_Assembly")
    
     # Create network nodes
    
     source = Source(model, "Source")  # 1
    
     sink = Sink(model, "Sink")  # 2
    
     # buffer size + 1, servers count as one buffer place

    Each machine is modelled as a station with fixed processing time and unitary capacity. The assembly process is not considered when building the stations, but it is defined by ad-hoc objects (Join).

     m1 = Station(model, "PPW1", buffer_size=1)
    
     ...

    The rotating transfer station is modelled as a set of machines, each one representative of one available position in the rotating table.

     b1 = Station(model, "RT1_1", buffer_size=1)
     b2 = Station(model, "RT1_2", buffer_size=1)
     b3 = Station(model, "RT1_3", buffer_size=1)
     b4 = Station(model, "RT1_4", buffer_size=1)
     b5 = Station(model, "RT1_5", buffer_size=1)
    
     ...

    The conveyor belt acts also as buffer and processes the parts with a FIFO logic. Each buffer is modelled as a set of 5 machines with unitary capacity, each one representative of one available space. The number of available spaces depends on the distance between each set of stations.

     c1_1 = Station(model, "B1_1", buffer_size=1)
     c1_2 = Station(model, "B1_2", buffer_size=1)
     c1_3 = Station(model, "B1_3", buffer_size=1)
     c1_4 = Station(model, "B1_4", buffer_size=1)
     c1_5 = Station(model, "B1_5", buffer_size=1)
    
     ...

    The Fork allows to split the final product into its components. This procedure is required by the software, which needs the fork object if at least a join exists.

     fork = Fork(model, "Fork", "Final Product")
     ...
  3. Define the different parts required for the assembly. The final product (final) is generated by the source. All the other components are generated from final by the fork. The first two components, are joined into "final" after the first Join. The only interarrival time that will be considered by the model is the one of Final Product, in fact each component is created in the simulation by the object fork from the object final.

     final = OpenClass(model, "Final Product", source, Dist.Determ(8))
     component_a = OpenClass(model, "Hinge Wing", fork, Dist.Determ(8))
     component_b = OpenClass(model, "Hinge Wing Screw", fork, Dist.Determ(8))
     component_c = OpenClass(model, "Hinge Clip", fork, Dist.Determ(8))
     component_d = OpenClass(model, "Hinge Pin 1", fork, Dist.Determ(8))
     component_e = OpenClass(model, "Hinge Connector 1", fork, Dist.Determ(8))
     component_f = OpenClass(model, "Hinge Spring", fork, Dist.Determ(8))
     component_g = OpenClass(model, "Hinge Pin 2", fork, Dist.Determ(8))
     component_h = OpenClass(model, "Hinge Connector 2", fork, Dist.Determ(8))
     component_i = OpenClass(model, "Hinge Pin 3", fork, Dist.Determ(8))
     component_j = OpenClass(model, "Hinge Box", fork, Dist.Determ(8))
     component_k = OpenClass(model, "Hinge Hook", fork, Dist.Determ(8))
  4. Define the Joins, that are (# of "component" -1) that generate "final" everytime two components are joined.

     join1 = Join(model, "Join 1", final, 2)
     join2 = Join(model, "Join 2", final, 2)
     join3 = Join(model, "Join HP1", final, 2)
     join4 = Join(model, "Join HC1", final, 2)
     join5 = Join(model, "Join HS", final, 2)
     join6 = Join(model, "Join HP2", final, 2)
     join7 = Join(model, "Join HC2", final, 2)
     join8 = Join(model, "Join HP3", final, 2)
     join9 = Join(model, "Join HB", final, 2)
     join10 = Join(model, "Join HH", final, 2)
  5. Define the processing time of machines (buffers and conveyors included). The rotating buffer and the conveyor have a fixed processing time to take into account the transportation time required to move the pallets along a single position.

     m1.set_service(part, Dist.Determ(7))
     b1.set_service(part, Dist.Determ(4))
     b2.set_service(part, Dist.Determ(4))
     b3.set_service(part, Dist.Determ(4))
     b4.set_service(part, Dist.Determ(4))
     b5.set_service(part, Dist.Determ(4))
    
     ...
  6. Define the routing of the parts with the successors of each instance. The source is connected to the fork, and the fork then is connected to each join (starting from component_b-join1). The fork takes the first component (component_a) and is connected with the following production machine, in this phase component_a represents the frame.

     # routing
    
     source.add_route(final, fork, 1)
     fork.add_route(component_a, m1, 1)
     fork.add_route(component_b, join1, 1)
     fork.add_route(component_c, join2, 1)
     fork.add_route(component_d, join3, 1)
     fork.add_route(component_e, join4, 1)
     fork.add_route(component_f, join5, 1)
     fork.add_route(component_g, join6, 1)
     fork.add_route(component_h, join7, 1)
     fork.add_route(component_i, join8, 1)
     fork.add_route(component_j, join9, 1)
     fork.add_route(component_k, join10, 1)

    The Joins are visited just before the corresponding assembly machine, in this way each assembly operation can happen only if both the required parts are available in the needed position of the line. Each Join takes "final" as the output of the asembly operation and links it to the successive station.

     ...
    
     b4.add_route(component_a, b5, 1)
     b5.add_route(component_a, join1, 1)
    
     join1.add_route(final, m2, 1)
    
     m2.add_route(final, b6, 1)
    
     ...
    
     c1_4.add_route(final, c1_5, 1)
     c1_5.add_route(final, join2, 1)
     join2.add_route(final, m5, 1)
    
     m5.add_route(final, join3, 1)
    
     join3.add_route(final, m6, 1)
    
     m6.add_route(final, m7, 1)
    
     ...
    
     # Connect all the links
    
     model.add_measure()
  7. Run the simulation.

     path = model.write_jsimg()
     ret = model.solve_jsimg()
     print(ret)

Results

A folder named "nameof_model""date"_"time" is created.

It contains an input file .jsimg for the simulation in JSIM, a file .jsim with the results of the simulation and a file .csv containing the log of the simulation.

The LOG table is created as shown below. The columns indicate in order:

  • Loggername: referred to the name of the station

  • Timestamp

  • Job_ID: referred to the number of the job

  • Class_ID: referred to the name of the part type.

LOG

This is part of the JsimGraph model created.

Among the evaluated performances, the throughput can be visualised as below:

Simulation limits

Since JSIM automatically ends the simulation based on a spectral analysis of the output to ensure that the system reaches the steady state, the time and power of the processor required to get the results could be unavailable. To solve this issue some options to limit the number of processed samples, the number of events occurred in the system, the simulated time either the real time of execution can be implemented.

options = Default.ModelOptions
options["maxSamples"] = "100000000"
options["maxEvents"] = "100000000"
options["maxSimulated"] = "1000"
options["maxTime"] = "100000000"

They have to be declared in substitution of the default values, then the model instance has to be defined by specifying these options.

model = Model("JSIM_assembly", options)

Another way is to limit directly the RAM used by the device to address the simulation. This is a rough approach that does not allow the user to keep the control on the options of the simulation, so the first one is to be preferred.

ret = model.solve_jsimg(max_memory_mb=1000)

Last updated