I am simulating people movements and their elevator usage. An elevator can take up multiple persons before moving to another floor. The default process has a capacity parameter, however, these indicate the number of processes and not the number of people using the elevator at the same time.
I have tried to use multiple of the resources available, such as Container, Store, and Base. The elevator should be requested and these objects do not have the functionality to be requested. Hence, the only suitable solution is to inherent from the base.Resource class. I have tried to create a subclass Elevator, implementing from base.Resource and adjusting the function _do_get to take multiple elements from the queue. I am pretty confident that this is not the proper way to implement it and it gives an error as well: RuntimeError: <Request() object at 0x1ffb4474be0> has already been triggered. I have no clue which files to adjust to make Simpy happy. Could someone point me in the right direction?
#dataclass
class Elevator(simpy.Resource):
current_floor: int = 0
available_floors: List[int] = field(default_factory=lambda: [0, 1])
capacity: int = 3
# load_carriers: List[LoadCarrier] = field(default_factory=list)
move_time: int = 5
def __init__(self, env: Environment, capacity: int = 1, elevator_capacity: int = 1):
self.elevator_capacity = elevator_capacity
if capacity <= 0:
raise ValueError('"capacity" must be > 0.')
super().__init__(env, capacity)
self.users: List[Request] = []
"""List of :class:`Request` events for the processes that are currently
using the resource."""
self.queue = self.put_queue
"""Queue of pending :class:`Request` events. Alias of
:attr:`~simpy.resources.base.BaseResource.put_queue`.
"""
#property
def count(self) -> int:
"""Number of users currently using the resource."""
return len(self.users)
if TYPE_CHECKING:
def request(self) -> Request:
"""Request a usage slot."""
return Request(self)
def release(self, request: Request) -> Release:
"""Release a usage slot."""
return Release(self, request)
else:
request = BoundClass(Request)
release = BoundClass(Release)
def _do_put(self, event: Request) -> None:
if len(self.users) < self.capacity:
self.users.append(event)
event.usage_since = self._env.now
event.succeed()
def _do_get(self, event: Release) -> None:
for i in range(min(self.elevator_capacity, len(self.users))):
try:
event = self.users.pop(0)
event.succeed()
# self.users.remove(event.request) # type: ignore
except ValueError:
pass
# event.succeed()
So here is the solution I came up with. The tricky bit is I chained two events together. When you queue up for the elevator you get a event that fires when the elevator arrives. This event also returns a second event that fires when you get to your destination floor. This second event is a common event shared by all the passengers that are on the elevator and going to the same floor. Firing this one event notifies a bunch of passengers. This subscribe broadcast pattern can greatly reduce the number of events the model needs to process which in turn improves performance. I use the chained events because if you are in a queue, and the guy in front of you gets on and you do not, then that guy is also going to get off before you, requiring a different destination arrive event. Put another way, I do not know when you will get off until you get on, so I need to defer that part till you actually get onto the elevator.
"""
Simple elevator demo using events to implements a subscribe, broadcast pattern to let passengers know when
they have reached there floor. All the passengers getting off on the same floor are waiting on the
same one event.
Programmer: Michael R. Gibbs
"""
import simpy
import random
class Passenger():
"""
simple class with unique id per passenger
"""
next_id = 1
#classmethod
def get_next_id(cls):
id = cls.next_id
cls.next_id += 1
return id
def __init__(self):
self.id = self.get_next_id()
class Elevator():
""""
Elevator that move people from floor to floor
Has a max compatity
Uses a event to notifiy passengers when they can get on the elevator
and when they arrive at their destination floor
"""
class Move_Goal():
"""
wrapps passengers so we can track where they are going to
"""
def __init__(self, passenger, start_floor, dest_floor, onboard_event):
self.passenger = passenger
self.start_floor = start_floor
self.dest_floor = dest_floor
self.onboard_event = onboard_event
self.arrive_event = None
def __init__(self,env, passenger_cap, floors):
self.env = env
self.passenger_cap = passenger_cap
self.floors = floors
self.on_floor = 0
self.move_inc = 1
# list of passengers on elevator, one per floor
self.on_board = {f:[] for f in range(1,floors + 1)}
# queue for passengers waitting to get on elevator, one queue per floor
self.boarding_queues = {f:[] for f in range(1,floors + 1)}
# events to notify passengers when they have arrived at their floor, one per floor
self.arrive_events = {f: simpy.Event(env) for f in range(1, floors + 1)}
# start elevator
env.process(self._move_next_floor())
def _move_next_floor(self):
"""
Moves the elevator up and down
Elevator stops at every floor
"""
while True:
# move time to next floor
yield self.env.timeout(5)
# update floor elevator is at
self.on_floor = self.on_floor + self.move_inc
# check if elevator needs to change direction
if self.on_floor == self.floors:
self.move_inc = -1
elif self.on_floor == 1:
self.move_inc = 1
# unload and notify passengers that want to get of at this floor
arrive_event = self.arrive_events[self.on_floor]
self.arrive_events[self.on_floor] = simpy.Event(self.env)
arrive_event.succeed()
self.on_board[self.on_floor] = []
# load new passengers
# get open capacity
used_cap = 0
for p in self.on_board.values():
used_cap += len(p)
open_cap = self.passenger_cap - used_cap
# get boarding passengers
boarding = self.boarding_queues[self.on_floor][:open_cap]
self.boarding_queues[self.on_floor] = self.boarding_queues[self.on_floor][open_cap:]
# sort bording into dest floors
for p in boarding:
# give passenger common event for arriving at destination floor
p.arrive_event = self.arrive_events[p.dest_floor]
# notify passeger that they are onboard the elevator
p.onboard_event.succeed()
self.on_board[p.dest_floor].append(p)
def move_to(self, passenger, from_floor, to_floor):
"""
Return a event that fires when the passenger gets on the elevator
The event returns another event that fires when the passager
arrives at their destination floor
(uses the env.process() to convert a process to a event)
"""
return self.env.process(self._move_to(passenger, from_floor, to_floor))
def _move_to(self, passenger, from_floor, to_floor):
"""
Puts the passenger into a queue for the elevator
"""
# creat event to notify passenger when they can get onto the elemator
onboard_event = simpy.Event(self.env)
# save move data in a wrapper and put passenger into queue
move_goal = self.Move_Goal(passenger, from_floor, to_floor, onboard_event)
self.boarding_queues[from_floor].append(move_goal)
# wait for elevator to arrive, and have space for passenger
yield onboard_event
# get destination arrival event
dest_event = self.arrive_events[to_floor]
move_goal.arrive_event = dest_event
return dest_event
def use_elevator(env, elevator, passenger, start_floor, end_floor):
"""
process for using a elevator to move from one floor to another
"""
print(f'{env.now:.2f} passenger {passenger.id} has queued on floor {start_floor}')
arrive_event = yield elevator.move_to(passenger, start_floor, end_floor)
print(f'{env.now:.2f} passenger {passenger.id} has boarded on floor {start_floor}')
yield arrive_event
print(f'{env.now:.2f} passenger {passenger.id} has arrived on floor {end_floor}')
def gen_passengers(env, elevator):
"""
creates passengers to use a elevatore
"""
floor_set = {f for f in range(1, elevator.floors + 1)}
while True:
# time between arrivals
yield env.timeout(random.uniform(0,5))
# get passenger and where they want to go
passenger = Passenger()
start_floor, end_floor = random.sample(floor_set, 2)
# use the elevator to get there
env.process(use_elevator(env, elevator, passenger, start_floor, end_floor))
# boot up
env = simpy.Environment()
elevator = Elevator(env, 20, 3)
env.process(gen_passengers(env, elevator))
env.run(100)
Related
I have two relay boards, each works on a separate serial port. I am trying to build a script so that the individual boards accept commands in their own thread. I want to somehow store the command queue and communication thread based on this device's ID (in a keyed dict) to target it for actions. The original script I'm using for a guide is 650 lines so I haven't posted it here. I'm in over my head a bit, can anyone make some suggestions?
import queue
class easydaq(object):
def __init__(self):
self.commQueue = {"/dev/ttyUSB0": None, "/dev/ttyUSB1": None}
q = queue.Queue
def _getDevCommQueue(self, dev_id):
return (self.commQueue.get(dev_id, 0))
def _sendToDevCommQueue(self, dev_id, action):
q = self._getDevCommQueue(dev_id)
q.put(action)
dev_id = "/dev/ttyUSB0"
action = b'C\x01'
my_new_instance = easydaq()
my_new_instance._sendToDevCommQueue(dev_id, action)
I have been searching for days for a solution to this issue, if anyone can point me in the right direction, I will be so grateful.
I have been writing a program that allows a user to create multiple trading bots at the same time, each bot is created as an individual thread that makes an api call to Binance for the latest market data and evaluates this data with conditional statements and place trades accordingly. The code I am using for this has been trimmed to only the essential parts for simplicity.. I have a Bot class...
class Bot(threading.Thread):
def __init__(self, symbol, time, exposure, strategy):
threading.Thread.__init__(self)
self.symbol = symbol
self.time = time
self.exposure = exposure
self.strategy = strategy
self.stop_bot = False
def scanner(self):
while self.stop_bot == False:
client = Client(self.api_key, self.secret_key, {"verify": True, "timeout": 20})
price_data = client.get_klines(symbol=self.symbol, interval=self.time_interval)
self.df = pd.DataFrame(price_data)
self.modified_df = pd.DataFrame(price_data)
time.sleep(10)
def kill(self):
self.stop_bot = True
And the Bot class is called from the bot manager terminal class...
class Bot_Manager:
def __init__(self):
self.bot_list = []
bot = object
def terminal(self):
while True:
user_input = input('(1) Create new Bot (2) Stop Bot')
if user_input == '1':
symbol = 'OMGUSDT'
time = '1D'
exposure = 1000
strategy = 'SAR'
bot_name = input('name your bot: ')
bot = Bot(symbol=symbol, time=time, exposure=exposure, strategy=strategy, bot_name=bot_name)
scanner_thread = threading.Thread(target=bot.scanner, daemon=True)
scanner_thread.name = bot_name
scanner_thread.start()
self.bot_list.append(scanner_thread)
elif user_input == '2':
for thread in threading.enumerate():
print(thread.name)
print(self.bot_list)
user_input = input('Bot to stop: ')
i = int(user_input)
print(self.bot_list[i])
Now I am able to create multiple threads / bots by repeatedly selecting option 1. However the issue I am facing is stopping these threads when a user selects option 2. If for example I create 3 bots and name them Bot A, Bot B and Bot C.. when I enumerate this in a for loop, i get the following:
MainThread
Bot A
Bot B
Bot C
and when I store each thread into a list and print the list I see the following:
[<Thread(Bot A, started 8932)>, <Thread(Bot B, started 12268)>, <Thread(Bot C, started 13436)>]
I would like the user to be able to select the thread / bot they want to stop from the list, so in this example if the user types 1, it should return the thread <Thread(Bot B, started 12268)>and stop this thread by passing the variable stop_bot = True. However I haven't had much luck with this.
When I call the function bot.kill() it only stops the last thread that was created, so for this example, Bot C. When it runs again, it doesn't remove any other thread. Is there anyway to pass in the variable stop_bot = True on an already created object / thread? Or is there another method to this that I have overlooked... Any help would be greatly appreciated....
I managed to find a solution to this by adding multiple bots to a data frame and passing this into the bot class. 1 thread was created to iterate through the data frame and by removing rows of the data frame, the bots would be removed.
I am trying to setup a new server for a project in mechanical engineering. I plan on running a priority queue on the server for sorting what it will do next. Most mechanical engineers do not know how to code so I cannot find many people to help.
For reasons that are difficult to go into, I need to avoid most built in functions withing python. I want to limit myself to the following: pop, insert, if statements, and such. I have a simple one written, however, I need this in one function that computes whenever I input values. The current one essentially will sort at the end.
I want to make it into class PriorityQueue:
Then allow it to sort as items enter
class Queue:
def __init__(self, iL=[]):
self.aList = iL[:]
def enqueue(self, newItem:'item'):
"""put an item in back the queue"""
# your code...
self.aList.append(newItem)
def dequeue(self)->'item': #return the item
"""remove an item from front of the queue, return the item"""
# your code...
return self.aList.pop(0)
def empty(self):
"""Is the queue empty?"""
# your code...
return self.aList == []
def __repr__(self): # for testing only
return repr(self.aList)
# Use the Queue class as a Priority Queue by
# having a priority associated with each item, e.g.:
# (2,'code') where item 'code' have priority 2
# (1, 'eat') this entry has a priority 1 and the item 'eat'
# (3, 'sleep') 'sleep' has priority 3
# Let's consider #1 to be the highest priority, be #1, then 2, then 3
# given this type of priority queue as input
# define a function to sort the queue,
# such that highest priority item (smallest number)will be served first
# return a sorted queue
def queueSorted(qu:Queue) -> Queue:
# your code...
# must use only those 3 queue functions
# cannot use any python function for sorting
qusorted = Queue()
while not qu.empty():
# find min, enqueue it into sorted
qutemp = Queue()
imin = qu.dequeue()
while not qu.empty():
itemp = qu.dequeue()
if itemp < imin:
itemp, imin = imin, itemp
qutemp.enqueue(itemp)
qusorted.enqueue(imin)
# do rest
qu = qutemp
return qusorted
I am trying to learn python ( and programming in general). For now I am trying to make a simple bank where users can send/deposit/withdraw money.
I have already created the deposit and withdraw functions and are working. Now I am totally confused on how to write the send function as the user will be sending money and the other will be receiving the money.
Should I write 2 seperate functions fo send and receive, but then how to trigger both in the same time? ( another function containing both) ?
I hope you can help me with this,
so far this is my code:
Classes:
class Account(object):
def __init__(self, name, account_number, initial_amount):
self.name = name
self.no = account_number
self.balance = initial_amount
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
def dump(self):
s = '%s, %s, balance: %s' % \
(self.name, self.no, self.balance)
print s
def get_balance(self):
print(self.balance)
def send(self, sender, receiver, amount):
self.sender = sender
self.receiver = receiver
self.balance -= amount
main.py:
from classes.Account import Account
a1 = Account('John Doe', '19371554951', 20000)
a2 = Account('Jenny Doe', '19371564761', 20000)
a1.deposit(1000)
a1.withdraw(4000)
a2.withdraw(10500)
a2.withdraw(3500)
a1.get_balance()
I know this may be basic, but I hope I can get help here.
Thank you
You already have deposit and withdraw methods so you might as well use them.
Transferring money is essentially withdrawing from one account and depositing it in another.
This can be implemented with a static method that accepts 2 accounts and the amount which will encapsulate the idea of "transfer":
class Account:
.
.
.
#staticmethod
def transfer(from_account, to_account, amount):
from_account.withdraw(amount)
to_account.deposit(amount)
# TODO perhaps you will want to use a try-except block
# to implement a transaction: if either withdrawing or depositing
# fails you will want to rollback the changes.
usage:
from classes.Account import Account
a1 = Account('John Doe', '19371554951', 20000)
a2 = Account('Jenny Doe', '19371564761', 20000)
print(a1.balance)
print(a2.balance)
Account.transfer(a1, a2, 10)
print(a1.balance)
print(a2.balance)
# 20000
# 20000
# 19990
# 20010
I'm trying to understand how the actor model works by modeling a bank. First, here's some code illustrating why we need models for concurrent systems:
import time
from threading import Thread
bank = {'joe': 100}
class Withdrawal(Thread):
"""
Models a concurrent withdrawal for 'joe'. In this example, 'bank'
is a shared resource not protected and accessible from any thread.
Args:
amount (double) how much to withdraw
sleep (bool) config to sleep the thread during the withdrawal
"""
def __init__(self, amount, sleep = False):
self.amount = amount
self.sleep = sleep
Thread.__init__(self)
def run(self):
"""
Overrides method in Thread.
Returns: void
"""
balance = bank['joe']
if balance >= self.amount:
if self.sleep:
time.sleep(5)
bank['joe'] -= self.amount
t1 = Withdrawal(80, True)
t2 = Withdrawal(80)
t1.start()
t2.start()
After running the code, the balance for 'joe' should be -60 after five seconds. This is because bank is unprotected from concurrent access, and pausing for five seconds during concurrent execution means that we can't guarantee that the data won't be accessed at different states. In this case, the first thread accesses the bank after the second thread has finished withdrawing but doesn't check that a withdrawal is still possible. As a result the account goes negative.
If we model the bank and withdrawals as actors, we can protect access to the account since its state is managed on a different thread that's separate from those trying to withdraw from it.
from queue import Queue
from threading import Thread
import time
import random
class Actor(Thread):
"""
Models an actor in the actor model for concurrent computation
see https://en.wikipedia.org/wiki/Actor_model for theoretical overview
Args:
handles (dict) mapping of public methods that are callable
on message data after message has been read
"""
def __init__(self, handles):
self.handles = handles
self.mailbox = Queue()
Thread.__init__(self, daemon=True)
def run(self):
"""
Overrides method in Thread. Once the thread has started,
we listen for messages and process one by one when they are received
Returns: void
"""
self.read_messages()
def send(self, actor, message):
"""
Puts a Message in the recipient actor's mailbox
Args:
actor (Actor) to receive message
message (Message) object to send actor
Returns: void
"""
actor.mailbox.put(message)
def read_messages(self):
"""
Reads messages one at a time and calls the target class handler
Returns: void
"""
while 1:
message = self.mailbox.get()
action = message.target
if action in self.handles:
self.handles[action](message.data)
class Message:
"""
Models a message in the actor model
Args:
sender (Actor) instance that owns the message
data (dict) message data that can be consumed
target (string) function in the recipient Actor to we'd like run when read
"""
def __init__(self, sender, data, target):
self.sender = sender
self.data = data
self.target = target
class Bank(Actor):
"""
Models a bank. Can be used in concurrent computations.
Args:
bank (dict) name to amount mapping that models state of Bank
"""
def __init__(self, bank):
self.bank = bank
Actor.__init__(self, {'withdraw': lambda data: self.withdraw(data)})
def withdraw(self, data):
"""
Action handler for 'withdraw' messages. Withdraw
if we can cover the requested amount
Args:
data (dict) message data
Returns: void
"""
name, amount = data['name'], data['amount']
if self.bank[name] >= amount:
if data['sleep']:
time.sleep(2)
self.bank[name] -= amount
class Withdrawal(Actor):
"""
Models a withdrawal. Can be used in concurrent computations.
Args:
bank (Bank) shared resource to transact with
sleep (bool) config to request that the bank sleep during a withdrawal
"""
def __init__(self, bank, sleep=False):
self.bank = bank
self.sleep = sleep
Actor.__init__(self, {})
def withdraw(self, name, amount):
"""
Wrapper for sending a withdrawl message
Args:
name (string) owner of the account in our bank
amount (double) amount we'd like to withdraw
Returns: void
"""
data = {'sleep': self.sleep, 'name': name, 'amount': amount}
Actor.send(self, self.bank, Message(self, data, 'withdraw'))
Let's now test:
bank = Bank({'joe': 100})
bank.start()
actors = []
for _ in range(100):
a = Withdrawal(bank, random.randint(0, 1))
a.start()
actors.append(a)
for a in actors:
a.withdraw('joe', 15)
Is this understanding correct? Even though the bank sleeps during withdrawals, no simultaneous withdrawal can corrupt the data because it's managed on a different thread than the withdrawals.
Simultaneous withdrawal can no longer occur, true, but it's because the withdraw messages are handled serially, not concurrently, by the single Bank thread inside the Bank.read_messages loop. This means that the sleep commands are also executed serially; the entire message queue is going to stall and yield control for 2 seconds whenever the bank has to sleep during a withdrawal. (Given the Bank's modeled action, this is essentially unavoidable).
If access to an object is isolated to a single thread it is generally considered thread safe.
The other actors cannot access the bank's storage directly but only send messages requesting a withdrawal, so updates only occur in the bank thread and the check-and-set race condition in the original design is eliminated.