I'm just getting started with SimPy so maybe I'm missing something major here. I have a very simple Process which I want to just increment a number once per second.
class Simulation:
def __init__(self):
self.env = simpy.Environment()
thing = Thing()
p = self.env.process(thing.go(self.env))
self.env.run()
simulation = Simulation()
class Thing():
def __init__(self):
self.x = 1
def go(self,env):
while True:
self.x = self.x + 1
print("Current value: {}".format(self.x))
yield env.timeout(1)
The timeout seems to be getting ignored here. It doesn't matter what value I pass as the delay, or whether I include the line at all, x increases at the same rate(over 1 million within seconds)
You seem to be confusing real-time with sim-time. Have your print statement include the current time (env.now). You should get something like this:
Current value: 2 , time now: 0
Current value: 3 , time now: 1
Current value: 4 , time now: 2
Current value: 5 , time now: 3
:
So, you can see the simulation clock is incrementing as is should; the simulation is just really fast. If you want to run in real time, don't use simpy.environment(). Instead, use simpy.rt.RealTimeEnvironment().
See here for more details:
https://simpy.readthedocs.io/en/latest/topical_guides/real-time-simulations.html
Related
I'm pretty new to Python. I'm trying to make a text-based strategy-like game in python and I want to have a value to increase constantly(I wanna have some other values to increase or decrease at the same time too but this is just for beginning). But if I use a While True loop I can't do anything else in the program. It just keeps rising the value but I can't do anything else. I want it to increase continuously while I can get some inputs from the user or run some other functions. Tell me if there is a module I can use or anything else please.
import time
print("PLANET EARTH" + "" + "\n Buildings:",
" ", "Resources:")
class ironMine():
def __init__(self, bc, ps, w):
self.buildingCost = bc
self.productionSpeed = ps
self.warehouse = w
def production(self):
while True:
print(" " +
"iron:", self.warehouse,
end="\r")
self.warehouse += self.productionSpeed
time.sleep(0.5)
x=input("Write something")
if x == upgrade:
self.productionSpeed += 5
else:
print("there is no such command")
t1 = ironMine([300,200,100], 10, 0)
t1.production()
For example this part is the resource production part for iron. I just add an random input to show I cant get it done. And I don't know if this part if x == upgrade: self.productionSpeed += 5 will update the existing self.productionSpeed value for object t1.
Effectively, you are trying to implement your own version of a clock, which counts up at some rate, using a loop like this:
value = initial_value
while True:
time.sleep(1)
value += rate
There is more to your code than that, of course; you also want to read user input and control the rate based on that. But at the core of it, you're trying to create a clock, and it's not working because your clock stops "ticking" while input is waiting for the user to enter something.
Instead of writing a clock, you should use one from the standard library. The time.monotonic() function works like a clock, in the sense that if you call the function twice, the difference between the two numbers is the number of seconds which elapsed between the two function calls.
The simple "clock" above, which has a variable value increasing at a fixed rate, can be replaced by a function call which calculates the current value based on the number of seconds that have elapsed, instead of continuously maintaining its current value in a variable:
import time
initial_time = time.monotonic()
def get_current_value():
current_time = time.monotonic()
seconds = current_time - initial_time
# use int(seconds) for discrete updates once per second
return initial_value + rate * int(seconds)
For your case, where the rate can change dynamically, it is a bit more complicated, but the key idea is the same; don't write your own clock, use an existing one. Since there are two things we need to be able to do - get the current value, and change the rate - let's encapsulate those two operations in a class:
import time
class TimeBasedVariable:
def __init__(self, initial_value, rate):
self.initial_value = initial_value
self.rate = rate
self.initial_time = time.monotonic()
def get_value(self, current_time=None):
if current_time is None:
current_time = time.monotonic()
seconds = current_time - self.initial_time
return self.initial_value + self.rate * int(seconds)
def set_rate(self, rate):
# reset the reference point to the current time
t = time.monotonic()
self.initial_value = self.get_value(t)
self.initial_time = t
self.rate = rate
Note that I simplified the problem slightly by making the variable update every second, rather than every 0.5 seconds. If you do want it to update every half-second, just write int(2 * seconds) instead of int(seconds).
I'm working with the schedule package built by Dan Bader and I'm not sure how to pass a counter into the package.
Here is the basic example of how this is used:
def job(message="stuff"):
print("I'm working on:", str(message))
schedule.every(10).seconds.do(job)
while True:
schedule.run_pending()
time.sleep(1)
So this prints I'm working on: stuff every 10 seconds which is great but what its missing for my application is a counter. I'd like to count every time a job is run and pass that into my function but I haven't been able to figure that out.
This was my latest attempt:
def job(count = 0):
print(count)
count = 0
schedule.every(10).seconds.do(job, count)
while True:
schedule.run_pending()
time.sleep(1)
count += 1
I thought that having count as a parameter and looping it within the while loop would work but it did not.
At the end of the day, I need a platform that will allow me to constantly run a function at some interval and keep track of how many times the job has run.
Any help would be much appreciated
Well, not sure it's the more obvious way, but I would do something like
import schedule
import time
class Count(object):
def __init__(self):
self._count = 0
def __str__(self):
count = self._count
self._count += 1
return str(count)
def job(count):
print(count)
count = Count()
schedule.every(1).seconds.do(job, count)
while True:
schedule.run_pending()
Count's instance just increment itself when printed, so if you are sure to print it only in job you keep track of how many times it was ran.
The only real pro VS the dict solution is you don't have to compute anything in job - count handles itself alone.
Edit
Since some SO folks seems to like my idea even if (and that's clearly true) it hide some functional aspects of the counter (nobody expect to handle an instance that increment an integer when printed) here is a little update that makes it less tricky, less confusing, in brief: better.
class CountLogger(object):
def __init__(self):
self._count = 0
def log(self, message = ''):
print('{}: {}'.format(self._count, message))
self._count += 1
It does almost the exact same thing, but calling CounterLogger.log() to see an integer getting incremented is less puzzling.
Just replace print(count) by count.log().
You can use a mutable object like a dictionary to achieve what you want. (Your original count variable is an integer, which is NOT mutable in Python.) The following works:
import schedule
import time
store = {'count': 0}
def job(data):
data['count'] += 1
print(data['count'])
schedule.every(10).seconds.do(job, store)
while True:
schedule.run_pending()
time.sleep(1)
I'm intermidiate bee for python and would like to run the same class instances of few in parallel mode for fetching data and decision making for financial market. To proceed my idea, I run the following code to see how python works, it seems it works one complete run of first class instance and after second class instances, I would like to run this parallely, how can I...?
Below is the some sample code for testing..
import threading
import time
class thr(object):
def __init__(self, name):
self.name = name
self.x = 0
def run(self):
for i in list(range(10)):
self.x +=1
print("something {0} {1}".format(self.name, self.x))
time.sleep(1)
F = thr("First")
S = thr("Second")
threading.Thread(target=F.run())
threading.Thread(target=S.run())
and the results as below....
something First 1
something First 2
something First 3
something First 4
something First 5
something First 6
something First 7
something First 8
something First 9
something First 10
something Second 1
something Second 2
something Second 3
something Second 4
something Second 5
something Second 6
something Second 7
something Second 8
something Second 9
something Second 10
Out[27]: <Thread(Thread-25, initial)>
The problem is here:
threading.Thread(target=F.run())
threading.Thread(target=S.run())
target= takes a callable object or None. F.run() executes F.run immediately, waits for it finish, and then passes the return value (which is None in your run() method) as the target.
You want something like this instead:
t1 = threading.Thread(target=F.run)
t2 = threading.Thread(target=S.run)
t1.start()
t2.start()
Note that there's no parentheses after run
Here's the complete program with the suggested change:
import threading
import time
class thr(object):
def __init__(self, name):
self.name = name
self.x = 0
def run(self):
for i in list(range(10)):
self.x +=1
print("something {0} {1}".format(self.name, self.x))
time.sleep(1)
F = thr("First")
S = thr("Second")
t1 = threading.Thread(target=F.run)
t2 = threading.Thread(target=S.run)
t1.start()
t2.start()
And output (Python 3.6.1):
$ python sf.py
something First 1
something Second 1
something Second 2
something First 2
something Second 3
something First 3
something Second 4
something First 4
something Second 5
something First 5
something Second 6
something First 6
something Second 7
something First 7
something First 8
something Second 8
something First 9
something Second 9
something First 10
something Second 10
I'm using SimPy in Python to create a Discrete Event Simulation that requires resources to be available based on a schedule input by the user in my case in a csv file. The aim is to represent different numbers of the same resource (e.g. staff) being available at different times of day. As far I as I can tell this isn't something that is available in base SimPy - like resource priorities.
I have managed to get this working and have included the code below to show how. However I wanted to ask the community if there is a better way to achieve this functionality in SimPy?
The code below works by requesting the resources at the start of each day for the times they are not supposed to be available - with a much higher priority to ensure they get the resource. The resources are then released at the appropriate times for use by other events/processes. As I say it works but seems wasteful with a lot of dummy processes working to ensure the correct true availability of resources. Any comments which would lead to improvements would be welcomed.
so the csv looks like:
Number time
0 23
50 22
100 17
50 10
20 8
5 6
where Number represents the number of staff that are the become available at the defined time. For example: There will be 5 staff from 6-8, 20 from 8-10, 50 from 10-17 and so on until the end of the day.
The code:
import csv
import simpy
# empty list ready to hold the input data in the csv
input_list = []
# a dummy process that "uses" staff until the end of the current day
def take_res():
req = staff.request(priority=-100)
yield req # Request a staff resource at set priority
yield test_env.timeout(24 - test_env.now)
# A dummy process that "uses" staff for the time those staff should not
# be available for the real processes
def request_res(delay, avail_time):
req = staff.request(priority=-100)
yield req # Request a staff resource at set priority
yield test_env.timeout(delay)
yield staff.release(req)
# pass time it is avail for
yield test_env.timeout(avail_time)
test_env.process(take_res())
# used to print current levels of resource usage
def print_usage():
print('At time %0.2f %d res are in use' % (test_env.now, staff.count))
yield test_env.timeout(0.5)
test_env.process(print_usage())
# used to open the csv and read the data into a list
with open('staff_schedule.csv', mode="r") as infile:
reader = csv.reader(infile)
next(reader, None) # ignore header
for row in reader:
input_list.append(row[:2])
# calculates the time the current number of resources will be
# available for and adds to the list
i = 0
for row in the_list:
if i == 0:
row.append(24 - int(input_list[i][1]))
else:
row.append(int(input_list[i-1][1]) - int(input_list[i][1]))
i += 1
# converts list to tuple of tuples to prevent any accidental
# edits from this point in
staff_tuple = tuple(tuple(row) for row in input_list)
print(staff_tuple)
# define environment and creates resources
test_env = simpy.Environment()
staff = simpy.PriorityResource(test_env, capacity=sum(int(l[0]) for l in staff_tuple))
# for each row in the tuple run dummy processes to hold resources
# according to schedule in the csv
for item in the_tuple:
print(item[0])
for i in range(int(item[0])):
test_env.process(request_res(int(item[1]), int(item[2])))
# run event to print usage over time
test_env.process(print_usage())
# run for 25 hours - so 1 day
test_env.run(until=25)
I tried something else, I overloaded the Resource class, only adding one method and while I don't fully understand the source code, it seems to work properly. You can tell the resource to change the capacity somewhere in your simulation.
from simpy.resources.resource import Resource, Request, Release
from simpy.core import BoundClass
from simpy.resources.base import BaseResource
class VariableResource(BaseResource):
def __init__(self, env, capacity):
super(VariableResource, self).__init__(env, capacity)
self.users = []
self.queue = self.put_queue
#property
def count(self):
return len(self.users)
request = BoundClass(Request)
release = BoundClass(Release)
def _do_put(self, event):
if len(self.users) < self.capacity:
self.users.append(event)
event.usage_since = self._env.now
event.succeed()
def _do_get(self, event):
try:
self.users.remove(event.request)
except ValueError:
pass
event.succeed()
def _change_capacity(self, capacity):
self._capacity = capacity
I think this should work, but I'm not a 100% confident about how the triggers work.
I solved creating a Resource for each time window. Each arrival is processed in the function service, and each customer will be assigned to a resource depending on the arrival time. In case a customer has to wait in queue and has to be re-asigned to the next time window, it is removed from current Resource and re-assigned to the next Resource. This is done by modifying the request as:
with self.Morning.request() as req1:
yield req1 | self.env.timeout(self.durationMorning)
The code:
import simpy
import numpy as np
import itertools
class Queue():
def __init__(self, env, N_m, N_e):
self.Arrival = {}
self.StartService = {}
self.FinishService = {}
self.Morning = simpy.Resource(env, N_m)
self.Evening = simpy.Resource(env, N_e)
self.env = env
self.durationMorning = 30
#arrivals/second
def t_arrival(self,t):
if t<self.durationMorning:
return 1
else:
return 2
def t_service(self):
return 5
def service(self,i):
arrival_time = self.env.now
if arrival_time==self.durationMorning:
yield self.env.timeout(0.0001)
# Add Arrival
system.Arrival[i] = arrival_time
# Morning shift
if self.env.now < self.durationMorning:
with self.Morning.request() as req1:
yield req1 | self.env.timeout(self.durationMorning)
if self.env.now < self.durationMorning:
system.StartService[i] = self.env.now
yield self.env.timeout(self.t_service())
print(f'{i} arrived at {self.Arrival[i]} done at {self.env.now} by 1')
self.FinishService[i] = self.env.now
# Evening shift
if (self.env.now >= self.durationMorning) & (i not in self.FinishService):
with self.Evening.request() as req2:
yield req2
system.StartService[i] = self.env.now
yield self.env.timeout(self.t_service())
print(f'{i} arrived at {self.Arrival[i]} done at {self.env.now} by 2')
self.FinishService[i] = self.env.now
def arrivals(self):
for i in itertools.count():
self.env.process(self.service(i))
t = self.t_arrival(self.env.now)
yield self.env.timeout(t)
env = simpy.Environment()
system = Queue(env, N_morning, N_evening)
system.env.process(system.arrivals())
system.env.run(until=60)
0 arrived at 0 done at 5 by 1
1 arrived at 1 done at 6 by 1
2 arrived at 2 done at 10 by 1
3 arrived at 3 done at 11 by 1
4 arrived at 4 done at 15 by 1
5 arrived at 5 done at 16 by 1
6 arrived at 6 done at 20 by 1
7 arrived at 7 done at 21 by 1
8 arrived at 8 done at 25 by 1
9 arrived at 9 done at 26 by 1
10 arrived at 10 done at 30 by 1
11 arrived at 11 done at 31 by 1
12 arrived at 12 done at 35 by 2
13 arrived at 13 done at 40 by 2
14 arrived at 14 done at 45 by 2
15 arrived at 15 done at 50 by 2
16 arrived at 16 done at 55 by 2
SimPy related
Maybe you can use PreemptiveResource (see this example). With this, you would only need one blocker-process per resource as it can just "kick" less important processes.
Python related
Document your code. What’s the purpose of take_res() and request_res()? (Why do both functions use priority=-100, anyway?)
Use better names. the_list or the_tuple is not very helpful.
Instead of the_list.append(row[0], row[1]) you can do the_list.append(row[:2]).
Why do you convert the list of lists into a tuple of tuples? As far as I can see the benefit. But it adds extra code and thus, extra confusion and expra possibilities for programming errors.
You should leave the with open(file) block as soon as possible (after the first four lines, in your case). There’s no need to keep the file open longer than necessary and when you are done iterating over all lines, you no longer need it.
This is how I solved it for my application. It's not perfect but was the best I could do given my basic level of skill with Python and SimPy.
The result is the correct number of Advisers are available at the desired times.
First I define a store and set the capacity to be equal to the total number of adviser instances that will exist within the simulation.
self.adviser_store = simpy.FilterStore(self.env,
capacity=self.total_ad_instances)
The instances of the Adviser class required are created in an initialization step which for brevity I have not included. I actually use a JSON file to customize the individual adviser instances which are then placed in a list.
The run parameter in the class definition below is actually another class that contains all info related to the current run of the simulation - so for example it contains the start and end dates for the simulation. self.start_date therefore defines the date that adviser starts working. self.run.start_date is the start date for the simulation.
class Adviser(object):
def __init__(self, run, id_num, start_time, end_time, start_date, end_date):
self.env = run.env
self.run = run
self.id_num = id_num
self.start_time = start_time
self.end_time = end_time
self.start_date = datetime.datetime.strptime(start_date, '%Y, %m, %d')
self.end_date = datetime.datetime.strptime(end_date, '%Y, %m, %d')
self.ad_type = ad_type
self.avail = False
self.run.env.process(self.set_availability())
So as you can see creating the adviser class also starts the process to set the availability. In the example below I've simplified it to set the same availability each day for a given date range. You could of course set different availabilities depending on date/day etc.
def set_availability(self):
start_delay = self.start_time + (self.start_date - self.run.start_date).total_seconds()/3600 # this returns the time in hours until the resource becomes available and is applied below.
end_delay = self.end_time + (self.start_date - self.run.start_date).total_seconds()/3600
repeat = (self.end_date - self.start_date).days + 1 # defines how man days to repeat it for
for i in range(repeat):
start_delayed(self.run.env, self.add_to_store(), start_delay)
start_delayed(self.run.env, self.remove_from_store(), end_delay)
start_delay += 24
end_delay += 24
yield self.run.env.timeout(0)
def add_to_store(self):
self.run.ad_avail.remove(self) # take adviser from a list
self.run.adviser_store.put(self) # and put it in the store
yield self.run.env.timeout(0)
def remove_from_store(self):
current_ad = yield self.run.adviser_store.get(lambda item: item.id_num == self.id_num) # get itself from the store
self.run.ad_avail.append(current_ad) # and put it back in the list
yield self.run.env.timeout(0)
So essentially customers can only request advisers from the store and the advisers will only be in the store at certain times. the rest of the time they are in the list attached to the current run of the simulation.
I think there is still a pitfall here. The adviser object may be in use when it is due to become unavailable. I haven't noticed if this happens as yet or the impact if it does.
File "C:\Users\Tom\Desktop\Tetris!\tetris.py", line 206, in typeSet
Globals.blockArray[i].x.append(7)
IndexError: list index out of range
I get the above error for the 4th line in typeSet
At initialization:
def main():
initialize()
def initialize():
Globals.running = True
addBlock()
class Globals:
running = True
blockArray = []
blockNum = 0
And then later on:
def addBlock():
Globals.blockArray.append(block())
class block:
def __init__(self):
self.id = Globals.blockNum
Globals.blockNum += 1
self.x = []
self.y = []
self.landed = False
self.blockType = 1#random.randint(1,6)
self.typeSet()
def typeSet(self):
i = self.id
if self.blockType == 1:
#square(i)
Globals.blockArray[i].x.append(7)
Globals.blockArray[i].y.append(0)
Globals.blockArray[i].x.append(7)
Globals.blockArray[i].y.append(1)
Globals.blockArray[i].x.append(8)
Globals.blockArray[i].y.append(0)
Globals.blockArray[i].x.append(8)
Globals.blockArray[i].y.append(1)
Edit: added more code and switched it so the id should start at 0. Error code hasn't changed
Not enough code. The error tells you the exact problem. Globals.blockArray does not have a member at position i. That's why you shouldn't work with global variables when you can avoid them, since it can be a hard time making sure your global variables have the expected values in them.
I'm not sure but maybe you want to do
Globals.blockArray.append(self)
in the init function, and also increase Globals.blockNum after the assignment to self.id.
Every time an instance of block is created, Globals.blockNum is incremented by 1 and self.id is set to the current value of Globals.blockNum.
Later (in typeSet) self.id is used to index into Globals.blockArray.
The error occurs when Globals.blockArray doesn't have at least self.id + 1 items in it.
If Globals.blockNum keeps increasing and its value is used (indirectly) to index into Globals.blockArray, this will likely cause the error (unless something causes Globals.blockArray to keep growing too.
While nothing immediate comes to mind looking at your code above, the first thing I would try would be to print the contents of Globals.blockArray, and Globals.blockArray[i].