def __init__(self, geo, start_timestamp=None, rel_speed=None, seed=1, allow_dup=False): """Initialize the MOSP Simulation. @param geo: geo model for simulation, a mosp.geo.osm.OSMModel extending the mops.collide.World @param start_timestamp: time.time timestamp when simulation starts - used to calc DateTime of simlation out of simulation ticks. @param rel_speed: (SimPy) ratio simulation time over wallclock time; example: rel_speed=200 executes 200 units of simulation time in about one second @param seed: seed for simulation random generator @param allow_dup: allow duplicates? only one or multiple Simulations can be startet at once """ SimulationRT.SimulationRT.__init__(self) assert allow_dup or osm.GLOBAL_SIM is None osm.GLOBAL_SIM = self self.initialize() # init SimPy.SimulationRT self.geo = geo #: the geo-modell of the Simulation self.monitors = [] #: contains all Monitors of the Simulation self.rel_speed = rel_speed if rel_speed else 1 self.start_timestamp = self.start_timestamp if start_timestamp else time.time() self.random = random.Random(seed) #: central simulation-wide random generator self.next_person_id = 0 #: the next id that will be given to a new person self.persons = group.PersonGroup() #: stores simulated Persons self.removed_persons = {} #: stores removed Persons for later use self.person_alarm_clock = PersonWakeUp('Omni-present person wake up alarm', self) #: central Process for waking up Persons on pause self.messages = [] #: stores scheduled Calls of PersonGroups for execution as zombie's infect() group.Message.sim = self geo.initialize(self) # load OSM data, load/calculate routing table and grid
class Simulation(SimulationRT.SimulationRT): """A MOSP Simulation. Extends SimPy's SimulationRT (Simulation with Real Time Synchronization) to store MOSP specific data and implement specific methods. @author: F. Ludwig @author: P. Tute @author: B. Henne""" def __init__(self, geo, start_timestamp=None, rel_speed=None, seed=1, allow_dup=False): """Initialize the MOSP Simulation. @param geo: geo model for simulation, a mosp.geo.osm.OSMModel extending the mops.collide.World @param start_timestamp: time.time timestamp when simulation starts - used to calc DateTime of simlation out of simulation ticks. @param rel_speed: (SimPy) ratio simulation time over wallclock time; example: rel_speed=200 executes 200 units of simulation time in about one second @param seed: seed for simulation random generator @param allow_dup: allow duplicates? only one or multiple Simulations can be startet at once """ SimulationRT.SimulationRT.__init__(self) assert allow_dup or osm.GLOBAL_SIM is None osm.GLOBAL_SIM = self self.initialize() # init SimPy.SimulationRT self.geo = geo #: the geo-modell of the Simulation self.monitors = [] #: contains all Monitors of the Simulation self.rel_speed = rel_speed if rel_speed else 1 self.start_timestamp = self.start_timestamp if start_timestamp else time.time() self.random = random.Random(seed) #: central simulation-wide random generator self.next_person_id = 0 #: the next id that will be given to a new person self.persons = group.PersonGroup() #: stores simulated Persons self.removed_persons = {} #: stores removed Persons for later use self.person_alarm_clock = PersonWakeUp('Omni-present person wake up alarm', self) #: central Process for waking up Persons on pause self.messages = [] #: stores scheduled Calls of PersonGroups for execution as zombie's infect() group.Message.sim = self geo.initialize(self) # load OSM data, load/calculate routing table and grid def add_monitor(self, monitor_cls, tick=1, **kwargs): """Add a Monitor to Simulation to produce any kind of output. @param monitor_cls: the monitor class from mops.monitors @param tick: new monitor will observe every tick tick @param kwargs: keyword parameters for monitor class instantiation @return: reference to new, added monitor instance """ mon = monitor_cls('mon'+str(len(self.monitors)), self, tick, kwargs) self.monitors.append(mon) return mon def add_persons(self, pers_cls, n=1, monitor=None, args=None): """Add a Person to Simulation. @param pers_cls: the person class (inherited from mosp.core.Person) @param n: the number of new, added instances of pers_cls @param monitor: (list of) monitor(s) the person(s) shall be observed by @param args: dictionary of arguments for pers_cls instantiation """ if not args: args = {} for i in xrange(n): seed = self.random.randrange(2**24) # must be >> number of persons! pers = pers_cls(self.next_person_id, self, random.Random(seed), **args) self.next_person_id += 1 if monitor is not None: if isinstance(monitor, monitors.EmptyMonitor): # a single monitor monitor.append(pers) elif hasattr(monitor,'__iter__'): # an iterable list of monitors for mon in monitor: mon.append(pers) self.activate(pers, pers.go(), 0) self.persons.add(pers) def get_person(self, id): """Find a person by its ID. @param: the ID of the person @return: the person or None, if the ID was not found """ result = self.persons.filter(p_id=id) if len(result) == 1: return result.pop() else: return None def del_person(self, person): """Remove a person from the simulation. The person will be saved in self.removed_persons for later reference. @todo: try using Process.cancel() from SimPy (is broken atm and may stay broken...try again with new go) @todo: maybe remove person from monitors @param person: the person to be removed. The person must have been added and must be active. """ self.removed_persons[person.p_id] = person person.stop_actions(True) person.remove_from_sim = True self.persons.remove(person) def readd_person(self, id, changes={}): """Add a previously removed person to the simulation again. @param id: id of the removed person @type id: int @param changes: changes to be made when reinserting the person @type changes: dict with pairs of 'variable_name': value (variable_name in a string) """ if id not in self.removed_persons: print 'Tried to re-add unknown person...ignored.' print id, type(id) print self.removed_persons return person = self.removed_persons[id] person.__dict__.update(changes) person.current_way.persons.append(person) person.current_coords = person.current_coords_impl for a in person._stopped_actions_for_removal: a.start() person._stopped_actions_for_removal = [] self.persons.add(person) # Bad hack: unterminate person._terminated = False person._nextTime = None self.activate(person, person.go()) person.readd_actions() def run(self, until, real_time, monitor=True): """Runs Simulation after setup. @param until: simulation runs until this tick @param real_time: run in real-time? or as fast as possible @param monitor: start defined monitors? """ if monitor: if len(self.monitors) == 0: raise monitors.NoSimulationMonitorDefinedException('at mosp.Simulation.run()') for mon in self.monitors: mon.init() # alarm for person.pause_movement() self.activate(self.person_alarm_clock, self.person_alarm_clock.serve(), 0) # based on code from SimPy.SimulationRT.simulate self.rtstart = self.wallclock() self.rtset(self.rel_speed) last_event_time = 0 avg_delta = 0 while self._timestamps and not self._stop: next_event_time = self.peek() # new implementation of real_time, only rel_speed = 1 if real_time and self.rel_speed == 1: now = time.time() sim_now = self.start_timestamp + self.now() delta = now - sim_now #avg_delta = 0.8 * avg_delta + 0.2 * delta #print '-'*50 #print 'delta (>0, wenn sim zu langsam)', delta #print 'avg_delta', avg_delta if delta < 0: time.sleep(-delta) if last_event_time != next_event_time: pass # replaces next logging statement #logging.debug('Simulation.run.next_event_time = %s' % next_event_time) last_event_time = next_event_time if next_event_time > until: break #print '-'*50 #print 'delta (>0, wenn sim zu langsam)', delta #print 'avg_delta', avg_delta if real_time and self.rel_speed != 1: delay = ( next_event_time / self.rel_speed - (self.wallclock() - self.rtstart) ) if delay > 0: time.sleep(delay) # do communication stuff while self.messages and self.messages[0].time < next_event_time: # execute messages heappop(self.messages)() # execute __call__() of popped object self.step() # There are still events in the timestamps list and the simulation # has not been manually stopped. This means we have reached the stop # time. for m in self.monitors: m.end() if not self._stop and self._timestamps: self._t = until return 'SimPy: Normal exit' else: return 'SimPy: No activities scheduled' def __getstate__(self): """Returns Simulation information for pickling using the pickle module.""" state = self.__dict__ #print 'get state', state del state['sim'] return state