Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Elevator Simulation

## overview

the code models an elevator inspired by event-driven entities in game development systems.

the goal here is to simulate an elevator and to use this simulation to generate data to train an ML model.

## how it works

the code is split into the following:

```
devtest/
├── models/
│ ├── elevator.py # core elevator logic
│ └── building.py # manages the building and elevators
├── simulation/
│ └── simulator.py # main simulation runner
├── data/
│ └── collector.py # saves data to sqlite
├── tests/
│ └── test_elevator.py # simple tests that check if system is okay
├── config.py # settings for the simulation
└── main.py # demos
```

the simulation runs in discrete time steps (called _ticks_, inspired by game engines). in each tick, the elevator can move one floor.

after taking a quick look online, i found out that elevators generally use the SCAN algorithm: the elevator keeps going in one direction as long as there are requests in that direction. once it's done, it switches directions if needed. this approach is more efficient than jumping around for every new request.

we also have a couple of "business rules":
- if an elevator is 80% full, it won't accept new hall calls.
- if an elevator has been moving for 5 minutes straight, it takes a 30-second maintenance break.

both of these rules are parameterized, so their values can be changed.

## how to use it

### run the demo

to see it in action, just run:
```bash
python main.py
```

here's the terminal output:
```
=== ELEVATOR SIMULATION DEMO ===
running test scenario:
- request floor 3 (elevator should go UP)
- request floor 2 while moving up (should serve 2 then 3)
- request floor 1 and 0 (should serve both going DOWN)
running scenario with 4 requests...
request for floor 3: success
all elevators idle, continuing to next request...
request for floor 2: success
all elevators idle, continuing to next request...
request for floor 1: success
all elevators idle, continuing to next request...
request for floor 0: success
all elevators idle, continuing to next request...
scenario completed in 9.0 seconds
scenario generated 10 events
final elevator state:
- current floor: 0
- direction: IDLE
- pending requests: []
- time moving: 2.0s
- time idle: 4.0s
- occupancy: 0/8
- near capacity: False
- maintenance mode: False

=== RANDOM SIMULATION FOR DATA COLLECTION ===
starting simulation for 60 seconds...
added request for floor 3
elevator elevator_1: arrived at floor 3
added request for floor 5
elevator elevator_1: arrived at floor 5
added request for floor 2
elevator elevator_1: arrived at floor 2
added request for floor 5
elevator elevator_1: arrived at floor 5
added request for floor 3
elevator elevator_1: arrived at floor 3
added request for floor 5
elevator elevator_1: arrived at floor 5
added request for floor 3
elevator elevator_1: arrived at floor 3
simulation completed after 60 ticks
collected 34 events
data summary:
- total events recorded: 34
- simulation duration: 60.3s
- storage type: sqlite
- df shape: (34, 12)
- event types: {'moving': 23, 'arrived': 11}
- data exported to: elevator_simulation_data.csv
simulation complete!
```

### run tests
to run the tests:
```bash
python tests/test_elevator.py
```

### configuration

you can change things like the number of floors and elevators in `config.py`.

## the data

all the simulation events are saved in a sqlite database file called `elevator_simulation.db`. this makes it easy to use the data later.

here's what we save for each event:
- `timestamp`: time of the event
- `elevator_id`: id of the elevator
- `event_type`: what happened (e.g., `moving`, `arrived`, `became_idle`)
- `current_floor`: where the elevator is
- `direction`: which way the elevator is going
- `pending_requests`: how many requests are left
- `occupancy`: how many people are inside
- `time_since_last_request`: time since the last button was pressed
- `hour_of_day`: hour of day when the event happened
- `day_of_week`: day of week when the event happened

### exporting to csv

after running a simulation, the data is exported a CSV file, which allows it to be easily fed into an ML model via `pandas`.
20 changes: 20 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# building setup
BUILDING_CONFIG = {
'floors': [0, 1, 2, 3, 4, 5], # available floors (0 is ground level)
'num_elevators': 1, # number of elevators
'elevator_capacity': 8, # max people that fit inside
}

# timing settings
SIMULATION_CONFIG = {
'tick_duration': 1.0, # how long each tick lasts (seconds)
'floor_travel_time': 3.0, # time to move between floors
'door_time': 2.0, # time for doors to open/close
'max_wait_time': 60.0, # how long elevator waits around doing nothing
}

# where we save all the data for ML training
DATA_CONFIG = {
'db_file': 'elevator_simulation.db', # sqlite database file
'collect_events': True,
}
Empty file added data/__init__.py
Empty file.
172 changes: 172 additions & 0 deletions data/collector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import time
from datetime import datetime
from typing import List, Dict, Any
import sqlite3
import pandas as pd


class SQLiteDataCollector:
"""
collects all the elevator events and saves them to sqlite
"""

def __init__(self, db_file: str = "elevator_simulation.db"):
self.db_file = db_file
self.start_time = time.time()
self.last_request_time = self.start_time

self._initialize_database()

def _get_connection(self):
"""
connect to the sqlite db
"""
return sqlite3.connect(self.db_file)

def _initialize_database(self):
"""
set up the db table
"""
create_table_sql = """
CREATE TABLE IF NOT EXISTS elevator_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL NOT NULL,
simulation_time REAL NOT NULL,
elevator_id TEXT NOT NULL,
event_type TEXT NOT NULL,
current_floor INTEGER NOT NULL,
direction INTEGER NOT NULL,
is_moving BOOLEAN NOT NULL,
occupancy INTEGER NOT NULL,
pending_requests INTEGER NOT NULL,
time_since_last_request REAL NOT NULL,
hour_of_day INTEGER NOT NULL,
day_of_week INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""

with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(create_table_sql)
conn.commit()

def record_events(self, events: List[Dict[str, Any]], building_status: Dict):
"""
save elevator events with extra info for ML training
"""
if not events:
return

current_time = time.time()
simulation_time = current_time - self.start_time

# get time features
sim_datetime = datetime.fromtimestamp(current_time)
hour_of_day = sim_datetime.hour
day_of_week = sim_datetime.weekday()

rows = []

for event in events:
# get elevator info
elevator_id = event.get('elevator_id', 'unknown')
elevator_info = building_status.get('elevators', {}).get(elevator_id, {})

# count how many requests are still pending
pending_requests = (
len(elevator_info.get('up_requests', [])) +
len(elevator_info.get('down_requests', []))
)

time_since_last_request = current_time - self.last_request_time

# write everything into a row for the db
row_data = (
current_time,
simulation_time,
elevator_id,
event.get('type', 'unknown'),
event.get('floor', elevator_info.get('current_floor', 0)),
event.get('direction', 0),
elevator_info.get('is_moving', False),
elevator_info.get('occupancy', 0),
pending_requests,
time_since_last_request,
hour_of_day,
day_of_week,
)

rows.append(row_data)

self._insert_rows(rows)

def _insert_rows(self, rows: List[tuple]):
"""
insert rows into db
"""
insert_sql = """
INSERT INTO elevator_events (
timestamp, simulation_time, elevator_id, event_type, current_floor,
direction, is_moving, occupancy, pending_requests, time_since_last_request,
hour_of_day, day_of_week
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""

with self._get_connection() as conn:
cursor = conn.cursor()
cursor.executemany(insert_sql, rows)
conn.commit()

def record_request(self, floor: int, elevator_id: str):
"""
get last request time
"""
self.last_request_time = time.time()

def get_data_summary(self) -> Dict:
"""
get a quick summary of how much data was collected
"""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM elevator_events")
event_count = cursor.fetchone()[0]

return {
'total_events': event_count,
'storage_type': 'sqlite',
'simulation_duration': time.time() - self.start_time
}

def get_dataframe(self) -> pd.DataFrame:
"""
get all the data as a pandas dataframe
"""
query = """
SELECT timestamp, simulation_time, elevator_id, event_type, current_floor,
direction, is_moving, occupancy, pending_requests, time_since_last_request,
hour_of_day, day_of_week
FROM elevator_events
ORDER BY timestamp
"""

with self._get_connection() as conn:
return pd.read_sql_query(query, conn)

def export_to_csv(self, filename: str = "elevator_data.csv") -> str:
"""
export all data to a csv file
"""
df = self.get_dataframe()
df.to_csv(filename, index=False)
return filename

def clear_data(self):
"""
clear all data from the database (useful for testing)
"""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM elevator_events")
conn.commit()
Binary file added elevator_simulation.db
Binary file not shown.
35 changes: 35 additions & 0 deletions elevator_simulation_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
timestamp,simulation_time,elevator_id,event_type,current_floor,direction,is_moving,occupancy,pending_requests,time_since_last_request,hour_of_day,day_of_week
1751331166.891501,0.0009388923645019531,elevator_1,moving,1,1,1,0,1,1.5974044799804688e-05,21,0
1751331167.897501,1.0069389343261719,elevator_1,moving,2,1,1,0,1,1.0060160160064697,21,0
1751331168.903875,2.013313055038452,elevator_1,moving,3,1,0,0,0,2.01239013671875,21,0
1751331168.903875,2.013313055038452,elevator_1,arrived,3,0,0,0,0,2.01239013671875,21,0
1751331170.911757,4.021194934844971,elevator_1,moving,2,-1,0,0,0,0.0003178119659423828,21,0
1751331170.911757,4.021194934844971,elevator_1,arrived,2,0,0,0,0,0.0003178119659423828,21,0
1751331173.922013,7.031450986862183,elevator_1,moving,1,-1,0,0,0,0.0003349781036376953,21,0
1751331173.922013,7.031450986862183,elevator_1,arrived,1,0,0,0,0,0.0003349781036376953,21,0
1751331175.9330041,9.042442083358765,elevator_1,moving,0,-1,0,0,0,0.00030112266540527344,21,0
1751331175.9330041,9.042442083358765,elevator_1,arrived,0,0,0,0,0,0.00030112266540527344,21,0
1751331183.9714549,8.033185958862305,elevator_1,moving,1,1,1,0,1,0.0002639293670654297,21,0
1751331184.980256,9.041987180709839,elevator_1,moving,2,1,1,0,1,1.0090651512145996,21,0
1751331185.98958,10.051311016082764,elevator_1,moving,3,1,0,0,0,2.0183889865875244,21,0
1751331185.98958,10.051311016082764,elevator_1,arrived,3,0,0,0,0,2.0183889865875244,21,0
1751331192.0190668,16.080797910690308,elevator_1,moving,4,1,1,0,1,0.0003058910369873047,21,0
1751331193.0296419,17.091372966766357,elevator_1,moving,5,1,0,0,0,1.010880947113037,21,0
1751331193.0296419,17.091372966766357,elevator_1,arrived,5,0,0,0,0,1.010880947113037,21,0
1751331200.066695,24.12842607498169,elevator_1,moving,4,-1,1,0,1,0.000370025634765625,21,0
1751331201.076561,25.13829207420349,elevator_1,moving,3,-1,1,0,1,1.0102360248565674,21,0
1751331202.085664,26.147395133972168,elevator_1,moving,2,-1,0,0,0,2.019339084625244,21,0
1751331202.085664,26.147395133972168,elevator_1,arrived,2,0,0,0,0,2.019339084625244,21,0
1751331208.1116312,32.173362255096436,elevator_1,moving,3,1,1,0,1,0.0002541542053222656,21,0
1751331209.119939,33.18167018890381,elevator_1,moving,4,1,1,0,1,1.0085620880126953,21,0
1751331210.130243,34.19197416305542,elevator_1,moving,5,1,0,0,0,2.0188660621643066,21,0
1751331210.130243,34.19197416305542,elevator_1,arrived,5,0,0,0,0,2.0188660621643066,21,0
1751331216.157873,40.21960401535034,elevator_1,moving,4,-1,1,0,1,6.794929504394531e-05,21,0
1751331217.16702,41.22875118255615,elevator_1,moving,3,-1,0,0,0,1.0092151165008545,21,0
1751331217.16702,41.22875118255615,elevator_1,arrived,3,0,0,0,0,1.0092151165008545,21,0
1751331224.1976562,48.259387254714966,elevator_1,moving,4,1,1,0,1,0.0002770423889160156,21,0
1751331225.207409,49.269140005111694,elevator_1,moving,5,1,0,0,0,1.0100297927856445,21,0
1751331225.207409,49.269140005111694,elevator_1,arrived,5,0,0,0,0,1.0100297927856445,21,0
1751331232.243977,56.305708169937134,elevator_1,moving,4,-1,1,0,1,0.0002791881561279297,21,0
1751331233.2546718,57.31640291213989,elevator_1,moving,3,-1,0,0,0,1.0109739303588867,21,0
1751331233.2546718,57.31640291213989,elevator_1,arrived,3,0,0,0,0,1.0109739303588867,21,0
Loading
Loading