MIT6_0002F16_ProblemSet3
实验内容:
本次实验主要围绕模拟一个机器人打扫房间的场景进行,通过随机模拟以及利用各种数学公式等,完成模拟。因此对随机类中的许多函数需要掌握。
这个实验非常有趣,因为提供的test函数将会使自己的函数可视化!
实验流程:
阅读实验pdf,可知实验具体要求,按照要求一步一步解决。
Problem 1: Implementing the RectangularRoom and Robot classes
这一部分主要是填写两个类:RectangularRoom类以及Robot类,分别表示一个矩形房间和一个机器人,在此处二者均为抽象类,不做实例化。在进行代码之前先看一下Position类,该类表示二维空间的一个坐标,并掌握该类的函数意义和使用方法。两个类中均有部分函数标注有not change,表示不需要在抽象类中填写。
完成Rectangular类时:成员变量tiles表示将大矩形分成的许多小方格对应的脏乱程度,因此在这里用字典表示,且字典映射关系为 position->dirt_amount 。需要注意的是小方格用它左下角的点表示即可,因此需要用math.floor等函数判断一个任意位置的点所在的小方格位置。还有就是要注意dirt_amount不能为负数。
完成Robot类时:机器人最初的位置、角度是随机确定的,用类Position实例化即可表示位置,因此需要用到Position类中的get_random_position,以及random类中的random.uniform。
具体代码:(为避免冗余,报告中将自带的英文注释删去)
class RectangularRoom(object):
def __init__(self, width, height, dirt_amount):
#根据要求将RectangularRoom的成员变量赋值
self.width=width
self.height=height
#这里需要注意的是灰尘量指的是每个小格的灰尘量,故定义小格并赋值,用字典表示
if width > 0 and height > 0 and dirt_amount >= 0:
#tiles表示字典,映射关系为 position:(x,y)->dirt_amount
self.tiles = {}
#用循环赋值
for x in range(width):
for y in range(height):
self.tiles[(x, y)] = dirt_amount
else:
raise ValueError
#将对应位置的灰尘量减去capacity,当然需要注意不能是负值
def clean_tile_at_position(self, pos, capacity):
#这里需要注意的是,pos代表的是一个点的坐标,所以这里需要将其对应的方格表示出来
#用math.floor求出不大于x,y的正整数
pos_x, pos_y = math.floor(pos.get_x()), math.floor(pos.get_y())
#如果capacity小于对应的方格的灰尘值,则说明不会产生负值,减去即可
if capacity <= self.tiles[(pos_x, pos_y)]:
self.tiles[(pos_x, pos_y)] -= capacity
#否则为了避免负数,将其直接设为0
else:
self.tiles[(pos_x, pos_y)] = 0
#判断某一方格是否清扫干净
def is_tile_cleaned(self, m, n):
#判断(m,n)代表的灰尘值是否为0,为0说明清除干净
return self.tiles[(m,n)]==0
#返回总的干净的格子数目
def get_num_cleaned_tiles(self):
#直接遍历tiles,如果出现0,则加1
num_cleaned_tiles = 0
for the_tile ,tile_dirt in self.tiles.items():
if tile_dirt == 0:
num_cleaned_tiles += 1
return num_cleaned_tiles
#判断某一点是否在该房间内
def is_position_in_room(self, pos):
#得到pos对应的正整数点,然后查看其是否在tiles中
x, y = math.floor(pos.get_x()), math.floor(pos.get_y())
return (x, y) in self.tiles
#返回某一tile的dirt_amount值
def get_dirt_amount(self, m, n):
return self.tiles[(m,n)]
#返回总的tiles数量 ,这里标注了不要改变
def get_num_tiles(self):
# do not change -- implement in subclasses.
raise NotImplementedError
#判断某一pos是否合法,这里标注了不要改变
def is_position_valid(self, pos):
# do not change -- implement in subclasses
raise NotImplementedError
#得到一个随机的位置, 这里标注了不要改变
def get_random_position(self):
# do not change -- implement in subclasses
raise NotImplementedError
class Robot(object):
def __init__(self, room, speed, capacity):
#根据参数赋值即可
self.room = room
self.speed = speed
self.capacity = capacity
#机器人的位置和方向均为随机的
self.position = room.get_random_position()
self.direction = random.uniform(0, 360)
#返回机器人的位置
def get_robot_position(self):
return self.position
#返回机器人的方向
def get_robot_direction(self):
return self.direction
#设置机器人的位置
def set_robot_position(self, position):
self.position=position
#设置机器人的方向
def set_robot_direction(self, direction):
self.direction=direction
#移动位置并打扫 在此处标注了不要修改
def update_position_and_clean(self):
# do not change -- implement in subclasses
raise NotImplementedError
Problem 2: Implementing EmptyRoom and FurnishedRoom
该部分主要是完成两个类:EmptyRoom类 以及 FurnishedRoom 类。二者的区别主要就在于FurnishedRoom在Room的基础上,增添了家具,家具将占据一块区域,该区域机器人将不可进入。因此,FurnishedRoom在实现某点是否可用、某小方格是否可用、随机产生一个合法位置等函数时,不仅需要判断是否在房间内,还要判断是否没有被家具占据,FurnishedRoom 类中成员变量 furniture_tiles存取被家具占据的小方格坐标。需要注意的是加入家具时是随机大小、随机位置。
代码如下:(为避免冗余,报告中将自带的英文注释删去)
#不带家具的空房间
class EmptyRoom(RectangularRoom):
#返回小方格数目
def get_num_tiles(self):
return len(self.tiles)
#判断一个点是否在房间中,调用父类函数is_position_in_room即可
def is_position_valid(self, pos):
return self.is_position_in_room(pos)
#得到一个随机的合法位置 ,利用随机random.uniform,再判断随机的位置是否合法
def get_random_position(self):
while True:
rand_x = random.uniform(0.0, self.width)
rand_y = random.uniform(0.0, self.height)
rand_Pos = Position(rand_x, rand_y)
if self.is_position_valid(rand_Pos):
return rand_Pos
#FurnishedRoom表示一个长房间包括一个家具,机器人不能通过家具所在的区域
class FurnishedRoom(RectangularRoom):
def __init__(self, width, height, dirt_amount):
RectangularRoom.__init__(self, width, height, dirt_amount)
self.furniture_tiles = []
#将家具占据的小方格均加入到 furniture_tiles 中
def add_furniture_to_room(self):
furniture_width = random.randint(1, self.width - 1)
furniture_height = random.randint(1, self.height - 1)
f_bottom_left_x = random.randint(0, self.width - furniture_width)
f_bottom_left_y = random.randint(0, self.height - furniture_height)
for i in range(f_bottom_left_x, f_bottom_left_x + furniture_width):
for j in range(f_bottom_left_y, f_bottom_left_y + furniture_height):
self.furniture_tiles.append((i,j))
#判断一个方格是否被家具占据
def is_tile_furnished(self, m, n):
return (m, n) in self.furniture_tiles
#判断一个位置是否被家具占据
def is_position_furnished(self, pos):
return self.is_tile_furnished(math.floor(pos.get_x()), math.floor(pos.get_y()))
#如果一个位置在房间里且没有被家具占据,返回True
def is_position_valid(self, pos):
return (not self.is_position_furnished(pos)) and self.is_position_in_room(pos)
#求房间中总的不被家具占据的小方格数量
def get_num_tiles(self):
answer = 0
for tile in self.tiles:
m,n = tile
if not self.is_tile_furnished(m, n):
answer += 1
return answer
#返回一个可用的位置,该位置在房间里且不被家具占据
def get_random_position(self):
while True:
rand_x = random.uniform(0.0, self.width)
rand_y = random.uniform(0.0, self.height)
rand_Pos = Position(rand_x, rand_y)
if self.is_position_valid(rand_Pos):
return rand_Pos
Problem 3: StandardRobot and Simulating a Timestep
该部分让写一个StandardRobot 类,该类需要写一个标准的 update_position_and_clean 函数,主要就是将通过路径上的小方格进行clean。移动位置可以通过position中的get_new_position实现,每次触壁后将获得一个随机方向。实现较为简单:
#移动位置并清扫所路过的小方格
def update_position_and_clean(self):
new_pos = self.get_robot_position().get_new_position(self.get_robot_direction(), self.speed)
if self.room.is_position_valid(new_pos):
self.set_robot_position(new_pos)
self.room.clean_tile_at_position(self.get_robot_position(), self.capacity)
else: #表示触壁,此时需要将方向改变,改变为一个随机值
self.set_robot_direction(random.uniform(0, 360))
将测试的注释删去,运行程序:
通过运动轨迹知道运行正确。
Problem 4: Implementing FaultyRobot
模拟一个出错的机器人轨迹,发生错误时不扫地且随机更改方向。
def update_position_and_clean(self):
#没发生错误的话就正常运行,将stardrobot复制下来即可:
if not self.gets_faulty():
new_pos = self.get_robot_position().get_new_position(self.get_robot_direction(), self.speed)
if self.room.is_position_valid(new_pos):
self.set_robot_position(new_pos)
self.room.clean_tile_at_position(self.get_robot_position(), self.capacity)
else:
self.set_robot_direction(random.uniform(0, 360))
#如果发生错误,只更改方向
else:
self.set_robot_direction(random.uniform(0, 360))
运行结果:
查看动画可知轨迹符合预期,但是总时间较标准机器人有所增长。
Problem 5: Creating the Simulator
模拟指定机器人打扫特定的房间,通过多遍模拟得出打扫房间的平均时间。每次打扫房间直至房间的整洁程度达到指定要求。每一次使用指定的机器人类型,这样可以使得函数具有更强的延展性。
def run_simulation(num_robots, speed, capacity, width, height, dirt_amount, min_coverage, num_trials, robot_type):
trial_times = []
#模拟每一次循环
for trial_no in range(num_trials):
#创建指定的空房间,脏度为dirt_amounnt
room = EmptyRoom(width, height, dirt_amount)
#创建指定的机器人
robot_crate = {}
for robot in range(num_robots):
robot_crate[robot] = robot_type(room, speed, capacity)
# 机器人应该一直工作直至房间的干净程度达到要求
time_step = 0
current_coverage = room.get_num_cleaned_tiles() / room.get_num_tiles()
#当未达到干净要求时:
while current_coverage < min_coverage:
# 每一个机器人执行一步
for num, robot in robot_crate.items():
robot.update_position_and_clean()
# step+1
time_step += 1
#检查已经走了多少步
current_coverage = room.get_num_cleaned_tiles() / room.get_num_tiles()
#记录数据
trial_times.append(time_step)
#返回平均时间
return sum(trial_times)/len(trial_times)
运行结果:
runfile('C:/Users/MI/Desktop/ps3/ps3.py', wdir='C:/Users/MI/Desktop/ps3')
Reloaded modules: ps3_visualize, ps3_verify_movement27
avg time steps: 285.28
avg time steps: 574.6
avg time steps: 706.62
avg time steps: 1257.14
avg time steps: 412.74
Problem 6: Running the Simulator
1)How does the performance of the two robot types compare when cleaning 80% of a 20x20 room?
很显然看出StandardRobot的性能更好,但是随着数量的增加,两种机器人性能非常接近。清理一个房间的时间基本上与机器人数量成反比。
2) How does the performance of the two robot types compare when two of each robot cleans 80% of rooms with dimensions 10x30, 20x15, 25x12, and 50x6?
我们能够看出FautyRobot在这些形状中性能较差,并且我们能够看出长宽比越大,FaultyRobot受影响越大。而StandardRobot受影响较小,FaultyRobot性能受形状影响非常明显。