2.3. Sun Up code Snapshot

Working Simulation for this Topic

Jeff Elpern


This is a temporary section that contains the latest working simulation code covering the topic in the section. This code does run without know major issues. However, the detailed description in the preceding sections most likely will not cover all the functionality in the working code.

When the documentation complete this section will be removed. Each section end with a working example covering the features introduced to that point.

The following code snapshot is file kese_gw_proto1_v7.py as of Feb 15, 2007

Features in Snapshoot. The v7 snapshot is feature complete for the Sun Up Chapter objectives. This includes the following:

  • Creation of a Geography. A hexagon Geo map of the simulation world is generated and stored in a data structure.

  • Character visualization of Geo Map. The Geo map is displayed with simple ascii characters. This includes a display showing Geo coordinates and a Geo map display showing the number of families in each Geo..

  • Modeling Arrival of New Families. Simulation let user set the number of new family units that arrive each day. The value in the code is 3 new families.

  • control pace of simulation. The real time pace of each simulation day is controlled. Default in 2 real time seconds per simulation day.

  • Geo capacity for support family units. Each Geo has a capacity to support a limited number if family units. This is modeled as a Process with limited capacity. The family units in a Geo compete for "a living." Those that do not find a living move on.

  • Family unit Behaviors. Two family behaviors are model. First, a family unit tries to make a living each day. If they succeed they stay in the "make living" behavior. If no living is available the family unit switches to the "Move" behavior and start the search for a Geo where they can make a living.

Figure 1. Code Snapshot as of Feb 15, 2008

# Knowledge and Economics Simulation Engine
# RealTime driver "Go West" scenario 

from SimPy.SimulationRT  import *
from random import *
import time


keseWorldWidth = 5
keseWorldHight = 8

simDays = 20
simHours = simDays * 24

averageGeoFamilyLivings = 3
externalFamiliesPerDay = 8

# Print feedback control
printPreBannerStuff = False

printNewGeos = False
printGeoLivings = False             # This takes True to print all or (z,q) for a specific Geo or False for non
printGeoSunupEvent = False          # Same as above

printDaylightCycle = False

printNewFamily = False
printFamilyMovein = False
printFamilyMovingout = False
printFamilySunupEvent = False
printFamilyGotLiving = False
printFamilyNoLining = False
printFamilyStartsMoving = False
printFamilyMovingTo = False

# -------------------------------------------------------
# World - the Master container for KESE
# -------------------------------------------------------
        
class World(Process):

    def __init__(self, width, height, realTime):
        Process.__init__(self, name='kese_world')
        # store world map size
        self.geosWide = width
        self.geosHigh = height
        # store world map range        
        self.geoXrange = (0, self.geosWide-1)
        self.geoYrange = (0, self.geosHigh-1 )

        self.rt = realTime
        print '********* test', self.rt.timeClock(), 'ticks', self.rt.timeTicks()
        
        # show ranges stored -- debugging
        if printPreBannerStuff == True:
            print 'X Wide', self.geosWide, \
                  'X range', self.geoXrange, \
                  ', X min', self.geoXrange[0], \
                  ', X max', self.geoXrange[1]
            print 'Y High', self.geosHigh, \
                  'Y range', self.geoYrange, \
                  ', Y min', self.geoYrange[0], \
                  ', Y max', self.geoYrange[1]
        

        # display sim to user
        self.Banner()

        # generate geo map
        self.worldGeos = WorldGeosMap(world = self)

        # store of next Family ID
        self.nextFamilyId = 1

        #create and activate associated Processes
        externalitiesToWorld = Externality(self)
        activate(externalitiesToWorld, externalitiesToWorld.keseCycle())

    #####################################################################################
    #   main PEM for simulation - this controls the knowledge and economics cycle
    #####################################################################################
     
    def economicCycle(self):
         
        while True:
            # ---  ecomonic cycle messages go here ---
            yield waitevent, self, newDay
            # fire externalities messge in hour 2 of day
            yield hold, self, 1
            externalityPhase.signal()
            
            yield waitevent, self, sunUp           # families will try to get living resource         
            yield hold, self, 1                     # let all livings request queue with priorities
            allocateLivings.signal()                # message to renew livings
            yield hold, self, 1
            livingsAllocated.signal()               # let families know living allocations for day are completed
            
            yield waitevent, self, dayEnd
            # display population distribution in world
            self.popMap()

            print 'end of day %d -- hour %d' %(now()/24, now()), '\n'

        print 'Knowledge and Economics Simulation ended with run time of ', self.rt.timeTicks()

    # ------------ World helper 
    def Banner(self):

        print '\n\n\n'
        print '                                  KESE  \n'
        print '                           Go West Scenario'
        print '               Knowledge and Economics Simulation Engine'
        print '                 OpenSimulations a division of SQI Inc.'
        print '\n\n\n'


    def worldGeosWide(self):
        return self.geosWide

    def worldGeosHigh(self):
        return self.geosHigh

    def getGeo(self, coordinates):  # returns handle to geo object given x y geo center
        return self.worldGeos.getGeo(coordinates)
    
    def getGeoIds(self):
        return self.worldGeos.getGeoIds()  # returns a list of ids

    def worldFamilyPopulation(self):
        totalFamilies = 0
        for statusGeoId in self.getGeoIds():
            geoToStatus = self.getGeo(statusGeoId)
            totalFamilies += geoToStatus.getGeoTotalFamies()
        return totalFamilies



        # -----------------------------------------------
        # Helper methods for economic cycle
        # -----------------------------------------------
        
    def newFamily(self, geoToLiveIn):
        # create family and place in geo
        newFamily = Family(self.nextFamilyId, geoToLiveIn, self)
        activate(newFamily, newFamily.familyActions())
        
        geo = self.getGeo(geoToLiveIn)
        geo.familyMoveIn(self.nextFamilyId, newFamily)
        
        # increment for next family
        self.nextFamilyId = self.nextFamilyId + 1

    def familiesByGeo(self):
        for statusGeoId in self.getGeoIds():
            geoToStatus = self.getGeo(statusGeoId)
            print 'Geo', statusGeoId, 'has families', geoToStatus.getFamilyIds()

    def getFamiliesIn(self,geoCoordinates):
        geoToStatus = self.getGeo(geoCoordinates)
        return len(geoToStatus.getFamilyIds())

    def popMap(self):
        print '\n\nGeo Center Map'

        for q in range(0, -(self.geosHigh), -1):
            # print 'q is -------------------- ', q
            mapRowTopOffset = []
            mapRowBottomLeft = []
            #for qRow in range(0, self.geosWide+1, 1):
            for qRow in range(0, self.geosWide, 1):
                # print 'qRow is ', qRow
                zRow = qRow - self.geosHigh - q + 1
                # print 'zRow is ', zRow
                x = zRow 
                y = q + qRow + 1 
                geoCoord = (x, y)
                # print geoCoord
                mapRowTopOffset.append( self.getFamiliesIn((x,y)) )
                
                x = zRow 
                y = q + qRow
                geoCoord = (x, y)
                # print geoCoord
                mapRowBottomLeft.append( self.getFamiliesIn((x,y)) )

            # print mapRowTopOffset
            # print mapRowBottomLeft

            display = '      '
            for coord in mapRowTopOffset:
                display += str(coord).center(12)
            print display

            display = ''
            for coord in mapRowBottomLeft:
                display += str(coord).center(12)
            print display

    def geoCoordMap(self):
        print '\n\nGeo Center Map'

        for q in range(0, -(self.geosHigh), -1):
            # print 'q is -------------------- ', q
            mapRowTopOffset = []
            mapRowBottomLeft = []
            #for qRow in range(0, self.geosWide+1, 1):
            for qRow in range(0, self.geosWide, 1):
                # print 'qRow is ', qRow
                zRow = qRow - self.geosHigh - q + 1
                # print 'zRow is ', zRow
                x = zRow 
                y = q + qRow + 1 
                geoCoord = (x, y)
                # print geoCoord
                mapRowTopOffset.append(geoCoord)
                
                x = zRow 
                y = q + qRow
                geoCoord = (x, y)
                # print geoCoord
                mapRowBottomLeft.append(geoCoord)

            # print mapRowTopOffset
            display = '      '
            for coord in mapRowTopOffset:
                display += str(coord).center(12)
            print display
            # print mapRowBottomLeft
            display = ''
            for coord in mapRowBottomLeft:
                display += str(coord).center(12)
            print display

        print '\n\n'



        
# --------------------------------------------------------
# GeoMapping for the World
# --------------------------------------------------------

class WorldGeosMap:

    def __init__(self, world):
        myWorld = world
        geos = {}
        
        # create geo set for map (in a dictionalry structure)
        self.geos = {}

        print 'world is', myWorld.worldGeosWide(), 'geos wide'
        print 'world is', myWorld.worldGeosHigh(), 'geos high'


        # create the Goes for requested map size
        #for z in range(0, myWorld.worldGeosWide()+1, 1):
        for z in range(0, myWorld.worldGeosWide(), 1):
            for zCol in range(-myWorld.worldGeosHigh()+1, 1, 1):
                qCol = -(myWorld.worldGeosHigh() - 1 + zCol)
                # first column
                x = zCol+z
                y = z+qCol
                if printNewGeos == True:
                    print 'I am a new Geo with coordinates are %d - %d' %(x, y)
                self.geos[(x,y)] = Geo(geosX = x, geosY = y, world = self)
                activate(self.geos[(x,y)], self.geos[(x,y)].day())
                
                # second offset column
                x = zCol+z
                y = z+qCol+1
                if printNewGeos == True:
                    print 'I am a new Geo with coordinates are %d - %d' %(x, y)
                self.geos[(x,y)] = Geo(geosX = x, geosY = y, world = self)
                activate(self.geos[(x,y)], self.geos[(x,y)].day())


        # Summary date for world map
        print '** Total number of geo locations greated:', len(self.geos)
        # Show Geo co-ordinate map
        myWorld.geoCoordMap()
 

    def getGeo(self, coordinates): # returns handle to geo object given x y geo center
        return self.geos[coordinates]

    def getGeoIds(self):
        return self.geos.keys()


# --------------------------------------------------------
# Geo -  the core economic region
# --------------------------------------------------------

class Geo(Process):

    def __init__(self, geosX, geosY, world):
        Process.__init__(self, name='Geo')
        self.myWorld = world
        
        # create geo object at specific co-ordinates
        self.geoLocation = (geosX, geosY)
        # print 'I am a new Geo at (%d,%d)' %self.geoLocation          

        # create the resource level for family support and set default
        self.cycleResourceUnits =  averageGeoFamilyLivings
        
        # livings is a general measure of how many families a geo can support
        self.livings = Level(name = 'family_livings',
                        unitName = 'units',
                        initialBuffered = 0,
                        putQType = FIFO,
                        getQType = PriorityQ)

        # Common Knowledge in Geo
        self.bestNeighor = (0, 0)           # commmon knowledge of where the best Geo to find living 
        
        # create the dictionary object to hold the faimly units
        self.familiesInGeo = {}
        
    def day(self):

        # allocate livings
        while True:
            '''
            In this simulation newDay is an economic event. At dawn each family awakes and
            tries to secure a living for the day by making a request to the livings resource
            for the geo. 
            '''
            yield waitevent, self, newDay
            self.updateCommonKnowledge()

            
            yield waitevent, self, sunUp            
            if ((printGeoSunupEvent == True) or (printGeoSunupEvent == self.geoLocation)):
                print 'Geo', self.geoLocation, 'got sunUp event at', now()   # instructional
            '''
            The
            '''
            yield waitevent, self, allocateLivings
            if ((printGeoLivings == True) or (printGeoLivings == self.geoLocation)):
                print 'Geo', self.geoLocation, 'got allocateLivings event at', now(), \
                'with %d livings from prior day' %self.livings.amount    
                
            '''
            We can model the case where an unsettled geo is to as having more resourses that will
            be available at the sustaining rate. This is done by starting the resource with
            and initial buffer greater that the renewable rate. The test below does not update
            the resource level until below sustaining levels.
            '''
            
            if self.livings.amount < self.cycleResourceUnits:
                yield put, self, self.livings, (self.cycleResourceUnits - self.livings.amount)
            
            yield hold, self, 1    
            # print 'Available Livings is Geo', self.geoLocation, 'at', now(), 'are %d' %self.livings.amount
            
    def familyMoveIn(self, familyId, family):
        self.familiesInGeo[familyId] = family
        if printFamilyMovein == True:
            print 'Family', family.getFamilyId(), 'just moved into Geo',self.geoLocation
        return self.geoLocation                           
            
    def familyMoveOut(self, familyId, family): 
        del self.familiesInGeo[familyId]
        if printFamilyMovingout == True:
            print 'Family', family.getFamilyId(), 'just moved out of geo', self.geoLocation  


    def getFamilyIds(self):
        return self.familiesInGeo.keys()
    
    def getGeoLocation(self):
        return self.geoLocation

    def getGeoTotalFamies(self):
        return len(self.familiesInGeo.keys())

    def geoState(self):
        print 'first test'

    # information on resource levels
    def geoLivings(self):
        return self.livings            # provide livings resourse level object
    
    def geoResourceLevel(self):
        return self.livings.amount

    def geoResourcesAvailable(self):
        return self.livings.amount - getGeoTotalFamies()

    def updateCommonKnowledge(self):

        # find lowest popultion Geo - Has to be next to this one
        bestGeo = (0,0)
        lowestPop = 999999
        for offSet in ( (1,-1),(0,-1),(-1,0),(-1,1),(1,0),(0,1) ):
            # test to see that Geo is on World map
            if (self.geoLocation[0]+offSet[0], self.geoLocation[1]+offSet[1]) in self.myWorld.getGeoIds():
                # look aroung this Geo for a better opportunity
                tryThisGeo = self.myWorld.getGeo( (self.geoLocation[0]+offSet[0], self.geoLocation[1]+offSet[1]) )

                # print 'From', self.geoLocation, 'try this Geo is ',(self.geoLocation[0]+offSet[0], self.geoLocation[1]+offSet[1]),  'with families', tryThisGeo.getGeoTotalFamies()
                if tryThisGeo.getGeoTotalFamies() < lowestPop:
                    bestGeo = (self.geoLocation[0]+offSet[0], self.geoLocation[1]+offSet[1])
                    # print 'families inside loop', tryThisGeo.getGeoTotalFamies()
                    lowestPop = tryThisGeo.getGeoTotalFamies()
        # print 'From Geo', self.geoLocation, 'the best move is to', bestGeo
        
        # lowest populated neighbor has been found so update "common knowledge"
        self.bestNeighor = bestGeo
                    
    def bestNeighorForLiving(self):
        return self.bestNeighor


  
# ---------------------------------------------------------------------------
# Externalities - Economic forces that are outside the behavior of simulation
# ---------------------------------------------------------------------------

class Externality(Process):

    def __init__(self, world):
        Process.__init__(self, name='externality')
        self.geoWorld = world

    def keseCycle(self):               # the PEM for Externality
        while True:
            yield waitevent, self, (externalityPhase)

            for x in range(0, externalFamiliesPerDay, 1):
                # create a new family and place them in geo2.2
                self.geoWorld.newFamily((0,0))


    
# -------------------------------------------------------
# Family -
# -------------------------------------------------------

class Family(Process):
    maxPriority = 1000    # will be reduced by day appeared on map

    def __init__(self, familyId, liveIn, world):
        Process.__init__(self, name='Family')

        # create a new family and place them a in geo
        self.familyId = familyId
        self.myWorld = world
        self.liveIn = liveIn                                # coordinates of Geo that we live in
        self.geoLivingIn = world.getGeo(self.liveIn)        # Geo object ref - for peformance
        self.appearDay = int(now()/24)
        self.geoMoveInDay = int(now()/24)
        self.behaviorCase = 'makeLiving'
        if printNewFamily == True:
            print 'We are the new family unit', self.familyId, 'living in geo', self.liveIn, \
              'created at', now()

        # print 'Geo living in',  self.geoLivingIn -------------

    def familyActions(self):         # PEM for Family
        while True:
            yield waitevent, self, sunUp
            '''
            The bahavior of the Family unit is determined by the following cases:

            makeLiving - the family is in a Geo and trying to settle and make a living. If they can't
                        find the resources they switch to a travel behavior.
            '''
            if printGeoSunupEvent == True:
                print 'sunUp for Family %d with priority %d' \
                      %(self.familyId, self.maxPriority - self.appearDay)

            # ------------ make living behavior ------------------------------------------
            if self.behaviorCase == 'makeLiving':
                # try to make a living
                geoLivingIn = self.myWorld.getGeo(self.liveIn)
                livingsResourseForGeo = geoLivingIn.geoLivings()
                yield (get, self, livingsResourseForGeo, 1, (self.maxPriority - self.appearDay)),\
                       (waitevent, self, livingsAllocated)
                # print 'Family', self.familyId, 'released from livings resource yield at', now()
                if self.acquired(livingsResourseForGeo):
                    if printFamilyGotLiving == True:
                        print 'Family', self.familyId, 'in Geo', self.liveIn, \
                              'at game hour', now(), \
                              'got living for day', int(now()/24)
                else:
                    if printFamilyNoLining == True:
                        print 'Family', self.familyId, 'in Geo', self.liveIn, \
                              'at game hour', now(), \
                              'no living available for day', int(now()/24)
                        print '  -- We must now move the family'
                    # place family in travel behavior
                    self.behaviorCase = 'travelToNewGeo'

            # --------------- travel behavior -------------------------------------------
            elif self.behaviorCase == 'travelToNewGeo':
                # select Geo to move to using the Common Knowledge in this Geo
                geoLivingIn = self.myWorld.getGeo(self.liveIn)
                targetGeoCoord = geoLivingIn.bestNeighorForLiving()
                # get Geo object of move target
                targetGeo = self.myWorld.getGeo(targetGeoCoord)

                # print 'The moving target Geo is', targetGeoCoord
                if printFamilyMovingTo == True:
                    print 'Family %d living in' %self.familyId, self.liveIn,\
                          ' --> moving to', targetGeo.getGeoLocation()
        
                # wait for sunUp of next day
                yield waitevent, self, sunUp
                if printFamilyStartsMoving == True:
                    print 'Family', self.familyId, 'starts moving to new Geo at', now()

                # remove fom Geo
                self.geoLivingIn.familyMoveOut(self.familyId, self)

                # wait for sunDown
                yield waitevent, self, sunDown

                # add to new Geo
                geoMovingTo = self.myWorld.getGeo(targetGeoCoord)
                # add family and have family update where they live
                self.liveIn = geoMovingTo.familyMoveIn(self.familyId, self)
                # now update Geo object reference
                self.geoLivingIn = self.myWorld.getGeo(self.liveIn)
                # change family behavior to looking for a living
                self.behaviorCase = 'makeLiving'
                
            else:
                print 'WE HAVE BIG PROBLEM. FAMILY BEHAVIOR STATE DOES NOT MATCH ANY KNOWN BEHAVIORS'


    def getFamilyId(self):
        return self.familyId

##################################################################################
# daylight massaging Process
##################################################################################
'''
This is a simple 12 hour cycle. However the procuss could be extended to
simulate seasons and/or latitude.

Note: this process also fires a new day event
'''

class Daylight(Process):

    def __init__(self):
        Process.__init__(self, name='Daylight')
        self.sunUpHour = 6
        self.sunDownHour = 18
    
    def sunEvents(self):

        # start 24 hour loop
        while True:
            yield hold, self, 1               # tick internal clock to first hour of next day
            # send start of new day event
            newDay.signal()
            if printDaylightCycle == True:
                print 'From Daylight Process:  Start of day at %d' %now()
            
            # wait for daylight and them sed event
            yield hold, self, self.sunUpHour
            sunUp.signal()
            if printDaylightCycle == True:
                print 'From Daylight Process: sun up at %d' %now()

            # wait for nightfall and then send event
            yield hold, self, (self.sunDownHour-self.sunUpHour)-1   # sub 1 to set at start of sundown
            sunDown.signal()
            if printDaylightCycle == True:
                print 'From Daylight Process: sundown at %d' %now()

            # wait for midnight
            yield hold, self, 24-self.sunDownHour
            dayEnd.signal()
            if printDaylightCycle == True:
             print 'From Daylight Process:  Day ended at %d' %now()

####################################################################################
#  Real Time helper class
####################################################################################

class RealTime():
    def __init__(self):
        self.launchTime = time.clock()
        # setup stuff for debugging
        if printPreBannerStuff == True:
            print 'simulation environment launched at %.1f' %self.launchTime

    def timeTicks(self):
        return time.clock() - self.launchTime

    def timeClock(self):
        return self.launchTime
    
    def reset(self):
        self.launchTime = time.clock()
        return self.launchTime

##########################################################################
#
# Module level code that initializes simulation
#
##########################################################################

rt = RealTime()

initialize()

# create World object which prints banner
kese_world = World(width = keseWorldWidth, height = keseWorldHight, realTime = rt)
activate(kese_world, kese_world.economicCycle())

# create day and daylight event Process
daylightEvents = Daylight()
activate(daylightEvents, daylightEvents.sunEvents())

# Create Event Objects
externalityPhase = SimEvent(name='Externality Phase signal for Economic Cycle')
sunUp = SimEvent(name='Sun rise event')
sunDown = SimEvent(name='Sun set event')
newDay = SimEvent(name='Start of day event')
dayEnd = SimEvent(name='End of day event')

allocateLivings = SimEvent(name='Renew livings resource')
livingsAllocated = SimEvent(name='Livings allocations completed for day')

# Reset start time for events that trigger running sim
print 'Simulation setup time was %.1f' %rt.timeTicks()
print 'Simulaiton run started at %.1f' %rt.reset()


# simualtion starts here
simulate(real_time=True, rel_speed=12, until=simHours) ##unit sim time = 1 sec clock

print 'Knowledge and Economics Simulation ended', \
      'with run time of %d.1 second per day' %(rt.timeTicks()/simDays)


      

KESL/RandD/keseGoWest/ChapSunUp/CodeSnapShot (last edited 2008-12-30 20:54:06 by jeff)