2016年8月20日星期六

用LibSim模拟北京大学图书馆存包柜占用情况

近年来,北京大学图书馆的存包柜被长期占用的情况十分严重。尽管图书馆在《北京大学图书馆存包柜使用须知》中明确规定了闭馆前不带走物品和挂锁的行为为占柜,并应该被清理,而因为种种原因这一规定并没有得到彻底的执行。
近来,随着图书馆方面加强了对存包柜的清理,一些利益相关者在北大未名BBS的PKULibrary版面上产生了很多的争执。有一些网友认为,清理存书柜的行为不具有正当性,而违规占柜是正当的。他们的理由包括清理存包柜会导致实际的利用效率下降。
针对这些争论,我对北京大学图书馆的存包过程进行了简化假设,并构建了模拟模型,利用python语言对其进行了实现和求解,并研究了不同参数下的模型结果。
该模型的假设简要概括如下:
  1. 北京大学图书馆(PKULib)每天开放10小时。
  2. PKULib有1000位读者,其中100位为不遵守规定的读者(badreader),900位为遵守规定的读者(goodreader)。
  3. 北京大学图书馆提供150个存包柜(locker)供读者使用。
  4. 每天,所有读者都会一个个的进馆,并都会使用存包柜。其进入图书馆的时间基本满足均匀分布,即每小时进入的人数相差不大。每人进馆后都会寻找空的储物柜,如果找到,该读者会感到开心(happiness);如果存包柜都被沾满,该读者会感到郁闷(madness)。
  5. goodreader每天会在图书馆呆到下一个整点,且这一天就不再来图书馆。他们走的时候会带走存包柜里自己的东西。
  6. badreader走的时间谁也不知道,而且他们不会带走自己在存包柜里的东西。
  7. 每隔一段时间(emptyperiod),图书馆会在闭馆后清理存包柜,此时badreader占用的存包柜会被清空。直到下一次badreader来到图书馆,他们才会重新占据一个存包柜。
我编写并求解了这一模型。在参数emptyperiod不同的情况下,获得了不同的结果。经过多次尝试,发现参数不变的情况下结果的差异不大(可以算方差表征)。典型的值如下:
当每天清一次柜时,happiness为90209,madness为9791;
当每30天清一次柜时,happiness为1314,madness为98686。
这样的模拟结果说明,在模型假设条件下,每天清柜相比每30天清柜,会显著的增加happiness,降低madness。

附:代码LibSim_1_0_0.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# FileName: LibSim.py
'''Libsim 1.0.0
 
LibSim program is an agent-based simulation program to study
the occupation of lockers in Peking University library. It is
a python 3 styled code written in object-oriented fashion.
 
Programmer:
Meng Xiangxi
PhD student, Peking University
mengxiangxibme(>_<)gmail.com
 
History:
1.0.0 Released 20141213
'''
 
import random
 
# Model specifications, constants
hours = 10 # Total openning hours of PKULib
emptyperiod = 30 # Period of evacuation
simdays = 100 # Days to simulate
numlocker = 150 # Set number of lockers
numgoodreader = 900 # Set number of good readers
numbadreader = 100 # Set number of bad readers    
 
#=====================Classes====================#
 
class Locker:
   '''The locker in PKULib'''
   empty = True # If the locker is empty
   readerID = 'null' # The reader occupying the locker
 
   def chkempty(self): # Check if the locker is empty
       return self.empty
 
   def setempty(self, value): # Change the empty status
       self.empty = value     # value is Boolean
 
   def getreaderID(self):     # Return the occupyer
       return self.readerID
   
   def setreaderID(self, ID): # Change the occupyer
       self.readerID = ID
 
   def emptygoodreader(self): # Only good readers leave
       if self.readerID[0] == 'G':
           self.empty = True
           self.readerID = 'null'
 
class Reader:
   '''Parent class for the readers'''
   numlocker = -1 # The last used locker number
 
   def chklocker(self, locker): # When entering PKULib, reader look for locker
       for lockernum in range(0,numlocker): # Traverse the lockers
           if locker[lockernum].chkempty(): # If locker is empty
               inchappy()                   # Feel happy
               locker[lockernum].setempty(False) # Occupy it
               self.numlocker = lockernum   # Record the locker number
               return 0                     # Stop looking for lockers
       self.numlocker = -1                  # Cannot find locker
 
   def getlockernum(self): # Return the number of locker last used
       return self.numlocker
 
   def leave(self,locker): # Leaving PKULib, release the locker
       if self.numlocker > 0:
           locker[self.numlocker].setreaderID('Null')
           locker[self.numlocker].setempty(True)
           self.numlocker = -1
       
class GoodReader(Reader):
   '''Readers taking their own belongings'''
   def __init__(self): # Inherit the __init__() and parameter of Reader
       Reader.__init__(self)
       numlocker = -1
       self.numlocker = numlocker
 
   def chklocker(self,locker,ID): # The chklocker() function of child class
       for lockernum in range(0,numlocker):
           if locker[lockernum].chkempty():
               inchappy()
               locker[lockernum].setempty(False)
               self.numlocker = lockernum
               locker[lockernum].setreaderID('G'+str(ID)) # G for 'GoodReader'
               return 0
       self.numlocker = -1
       incmad()
 
class BadReader(Reader):
   '''Readers leaving their own belongings'''
   def __init__(self): # Inherit the __init__() and parameter of Reader
       Reader.__init__(self)
       numlocker = -1  # Don't know why have to write this... But bugs if not
       self.numlocker = numlocker
       
   def chklocker(self,locker,ID): # The chklocker() function of child class
       for lockernum in range(0,numlocker):
           if locker[lockernum].chkempty():
               inchappy()
               locker[lockernum].setempty(False)
               self.numlocker = lockernum
               locker[lockernum].setreaderID('B'+str(ID)) # B for 'BadReader'
               return 0
       self.numlocker = -1
       incmad()
 
#====================Paramters===================#
 
# Total number of readers
numtotreader = numgoodreader + numbadreader
 
# Hourly entrance number
hrnumreader = (numgoodreader+numbadreader)//hours
hrnumreaderres = (numgoodreader+numbadreader)%hours
 
# Initialize the parameters
locker = [Locker() for _ in range(numlocker)]
goodreader = [GoodReader() for _ in range(numgoodreader)]
badreader = [BadReader() for _ in range(numbadreader)]
 
# Target variable
happiness = 0
madness = 0
 
def inchappy():
   '''Access and alter the variable 'happiness'.'''
   global happiness
   happiness += 1
 
def incmad():
   '''Access and alter the variable 'madness'.'''
   global madness
   madness += 1
 
#=====================Function===================#
 
def readerenter(namelistpara, numreaderenterpara):
   '''A certain reader enters.'''
   numreaderenter = namelistpara[numreaderenterpara]
   # Convert to the subscript of the reader
   if numreaderenter < 0:
       goodreader[-numreaderenter-1].chklocker(locker,-numreaderenter-1)
       # Convert the negative values to the subscript of goodreader
   else:
       badreader[numreaderenter].chklocker(locker, numreaderenter)
 
#=======================Main=====================#
 
for day in range(0,simdays): # Simulate for each day
   if day%emptyperiod == 0: # Empty the lockers periodically
       for i in badreader:  # Propell badreaders
           i.leave(locker)
   namelist = []            # Initialze namelist container
### Here, namelist combines goodreader and badreader. Negative values are
### for goodreader and vice versa. The subscript of goodreader is obtained by
### convertion: -numnamelist - 1.
   for numnamelist in range(-numgoodreader, numbadreader):
   # Construct original namelist
       namelist.append(numnamelist)
   random.shuffle(namelist) # Shuffle(): change the order of element randomly
   for varhour in range(0, hours-1):  # Simulate hour by hour
       for numreaderenterori in range(varhour*hrnumreader, (varhour+1)*hrnumreader):
       # Every hour except the last, an equal number of readers enter
           readerenter(namelist, numreaderenterori)
       for goodreaderleave in goodreader: # goodreader leave after one hour
           goodreaderleave.leave(locker)
   varhour = hours - 1 # The last opening hour
   for numreaderenterori in range(varhour*hrnumreader, numtotreader):
   # In the last hour, all unentered readers enters
       readerenter(namelist, numreaderenterori)
   for goodreaderleave in goodreader: # All goodreader leave
           goodreaderleave.leave(locker)
 
#======================Output====================#
 
print('happiness:', happiness)
print('madness:', madness)

没有评论 :

发表评论