1. Hello SimPM: first project simulation

1.1. Overview

The objective here is to build a minimal project simulation using SimPM’s discrete-event engine. This walkthrough mirrors the runnable example in example/simple earthmoving.py and shows how to inspect queueing and utilization once the run finishes.

1.2. Scenario

Model a simple earthmoving loop:

  • Ten trucks cycle through load → haul → dump → return.

  • A single loader is the shared resource; only one truck can load at a time.

  • We track how long the loader is busy, how long trucks wait, and how much dirt is moved.

1.3. Full example (from the examples folder)

The complete, well-documented example with comprehensive comments and results analysis:

"""
Simple Earthmoving Operation Simulation

This simulation models a basic earthmoving project with:
- 1 loader that loads dirt into trucks
- 10 trucks that haul dirt from pit to dump site
- Goal: Calculate loader utilization and truck waiting times

PROCESS FLOW:
    1. Truck waits for loader to be available (if queued)
    2. Loader loads 60 units of dirt into truck (7 minutes)
    3. Truck hauls to dump site (17 minutes)
    4. Truck dumps dirt (3 minutes)
    5. Truck returns to pit (13 minutes)
    6. Truck returns to step 1

KEY METRICS:
    - Loader utilization percentage
    - Average truck waiting time at loader
    - Total project completion time
"""
import simpm
import simpm.des as des

def truck_cycle(truck: des.Entity, loader: des.Resource, dumped_dirt: des.Resource):
    """
    Truck operation cycle process.

    Simulates the complete cycle of a truck in the earthmoving operation:
    loading dirt, hauling to dump site, dumping, and returning to pit.
    """
    while True:
        # LOADING PHASE: Wait for loader availability, then load 60 units
        yield truck.get(loader, 1)  # Acquire the loader (wait if busy)
        yield truck.do("loading", 7)  # Loading takes 7 minutes
        yield truck.put(loader, 1)  # Release the loader for next truck

        # HAULING PHASE: Transport loaded dirt to dump site
        yield truck.do("hauling", 17)  # Hauling takes 17 minutes

        # DUMPING PHASE: Dump the 60 units of dirt
        yield truck.do("dumping", 3)   # Dumping takes 3 minutes
        yield truck.add(dumped_dirt, 60)  # Record the dumped dirt

        # RETURN PHASE: Return to pit for next load
        yield truck.do("return", 13)  # Return journey takes 13 minutes

# Create the discrete event simulation environment
env = des.Environment("Earthmoving")

# Create the loader resource (only 1 loader available)
loader = des.Resource(env, "loader", init=1, capacity=1, log=True)

# Create counter for total dumped dirt (tracks project progress)
dumped_dirt = des.Resource(env, "dirt", init=0, capacity=100000, log=True)

# Create 10 truck entities
trucks = env.create_entities("truck", 10, print_actions=False, log=True)

# Start the truck cycle process for each truck
for t in trucks:
    env.process(truck_cycle(t, loader, dumped_dirt))

# Run the simulation until all processes complete
simpm.run(env, dashboard=False)

# Print results and analysis
print(f"Project finished at t = {env.now:.2f} minutes")
print(f"Loader Utilization: {loader.average_utilization()*100:.2f}%")
print("Loader queue stats:\n", loader.waiting_time().describe())
print("Truck 0 schedule sample:\n", trucks[0].schedule().head())

1.4. How it works

  1. Environment and resourcessimpm.des.Environment keeps simulation time. simpm.des.Resource tracks capacity and queue behavior (log=True records status changes for later analysis).

  2. Entities and processessimpm.des.Environment.create_entities() produces simpm.des.Entity objects. get and put wrap resource requests; do performs time-consuming activities; add deposits output into an inventory-like resource.

  3. Process lifecycleenv.process schedules truck_cycle for each truck. The process uses yield statements to represent time-consuming activities. Once scheduled, simpm.run executes the simulation until no events remain.

  4. Analysis outputs – Methods like status_log(), waiting_time(), average_utilization(), and schedule() expose queueing and productivity insights after the run completes.

1.5. Simulation Results

Running the simulation produces the following results:

============================================================================
EARTHMOVING PROJECT SIMULATION RESULTS
============================================================================

Project finished at t = 11752.00 minutes
Approximately 195.87 hours

Loader Utilization: 100.00%
(Loader was busy 100.00% of the time)

Loader Queue Statistics:
  Total waiting samples: 1676
  Average truck waiting time: 30.01 minutes
  Maximum truck waiting time: 63.00 minutes
  Minimum truck waiting time: 0.00 minutes

Sample Schedule (Truck 0 first 10 actions):
  activity  start_time  finish_time
0  loading           0            7
1  hauling           7           24
2  dumping          24           27
3   return          27           40
4  loading          70           77
5  hauling          77           94
6  dumping          94           97
7   return          97          110
8  loading         140          147
9  hauling         147          164

Total dirt dumped: 99960 units
Total load cycles completed: 1666 loads

Key Insights:

  • Project Duration: The project takes approximately 196 hours (11,752 minutes) to move nearly 100,000 units of dirt with 10 trucks and 1 loader.

  • Loader is the Bottleneck: The loader operates at 100% utilization, meaning it is never idle. This indicates the loader is the constraining resource.

  • Truck Waiting Times: Trucks wait an average of 30 minutes per cycle to access the loader, with a maximum wait of 63 minutes. This queuing delay represents lost productivity.

  • Production Rate: The system completes 1,666 load cycles (roughly 167 loads per truck), indicating that truck utilization is reduced due to waiting at the loader.

  • Optimization Opportunities:

    • Adding a second loader would significantly reduce truck waiting times

    • Faster loading equipment could increase throughput

    • Different fleet sizes could be tested to find optimal truck-to-loader ratio

    • Variable travel times would provide more realistic modeling

1.6. Entity attributes in action

Entities behave like dictionaries for arbitrary metadata. You can store custom attributes (such as truck size) and use them inside the process logic with square-bracket access:

env = des.Environment("Earthmoving with sizes")
loader = des.Resource(env, "loader", init=1, capacity=1, log=True)
dumped_dirt = des.Resource(env, "dirt", init=0, capacity=100000, log=True)

# Attach a size attribute (cubic meters per load) to each truck
trucks = env.create_entities("truck", 3, log=True)
for t, size in zip(trucks, [30, 35, 50]):
    t["size"] = size

def truck_cycle(truck: des.Entity, loader: des.Resource, dumped_dirt: des.Resource):
    while True:
        # Load time grows with truck size
        yield truck.get(loader, 1)
        yield truck.do("loading", 5 + truck["size"] / 20)
        yield truck.put(loader, 1)

        # Haul and dump exactly the truck's load size
        yield truck.do("hauling", 17)
        yield truck.do("dumping", 3)
        yield truck.add(dumped_dirt, truck["size"])

        yield truck.do("return", 13)

How it works:

  1. Setting attributes – After creating entities, assign custom data via dictionary notation: truck["size"] = 50. This stores a truck-specific parameter that persists throughout the simulation.

  2. Using attributes in calculations – Reference the attribute inside process functions: loading_time = 5 + truck["size"] / 20. Here, a larger truck takes longer to load because of the size multiplier.

  3. Using attributes in operations – Pass attributes to resource methods: dumped_dirt.add(truck["size"]) deposits a different amount of dirt depending on each truck’s capacity.

  4. Parametric comparison – This pattern enables easy comparison of different truck fleets. You can measure how size affects loading time, total output, and queue behavior without duplicating the process logic.

Key benefit: Attributes decouple entity parameters from hardcoded values. You can run the same simulation with different truck sizes, priorities, or capabilities just by changing attribute values. This makes it simple to conduct sensitivity analysis and optimize designs.

See example/earthmoving_with_truck_sizes.py for a complete worked example with attributes, analysis code, and detailed results.

1.7. Try it yourself

Run the shipped example directly:

python example/simple earthmoving.py

The script includes:

  • Comprehensive docstrings explaining each process and its parameters

  • Detailed inline comments describing each simulation phase

  • Organized code sections separating processes, setup, execution, and analysis

  • Complete results reporting with statistics and insights

  • Performance metrics including utilization, waiting times, and throughput

Then review the generated output to see which trucks waited longest, how hard the loader was pushed, and how much dirt was successfully moved.