蒙特卡罗方法 Monte Carlo methods,或称蒙特卡罗实验 Monte Carlo experiments,一大类计算算法的集合,依靠重复的随机抽样来获得数值结果。基本概念是利用随机性来解决理论上可能是确定性的问题。蒙特卡洛模拟是十分常用,使用广泛(计算机模拟,最优化,数值积分,依据概率分布生成图像等等)的方法。
其他例子包括:对输入中具有重大不确定性的现象进行建模,如商业中的风险计算,以及在数学中对具有复杂边界条件的多维定积分进行评估。在系统工程问题(空间、石油勘探、飞机设计等)的应用中,基于蒙特卡罗的故障预测、成本超支和进度超支通常比人类的直觉或其他的“软性”方法更有效。 在物理学相关问题中,蒙特卡罗方法可用于模拟具有多个耦合自由度的系统,如流体、无序材料、强耦合固体和细胞结构(参见细胞波茨模型 Cellular Potts Model、相互作用粒子系统 Interacting Particle Systems、麦肯-弗拉索夫过程 McKean-Vlasov Processes、气体动力学模型 Kinetic Models of Gases)。 中国科学院高能物理研究所
蒙特卡罗模拟数学原理
什么是蒙特卡罗模拟 中国科学院高能物理研究所 https://mp.weixin.qq.com/s/Fid3BY5ArhHx7dxHnU1PMg
实例:估计π值
ximport randomimport matplotlib.pyplot as pltimport numpy as np
def estimate_pi(num_samples): """ 使用蒙特卡罗方法估计π值,并返回估计结果列表。 参数: num_samples (int): 随机样本的数量。 返回: list: 包含每次迭代后π的估计值。 """ circle_points = 0 pi_estimates = [] for i in range(1, num_samples + 1): # 生成一个随机点(x, y),位于[-1, 1] x [-1, 1]内 x = random.uniform(-1, 1) y = random.uniform(-1, 1) # 计算点到原点的距离 distance = x**2 + y**2 # 如果点在单位圆内,则增加circle_points计数 if distance <= 1: circle_points += 1 # 计算π的估计值 pi_estimate = 4 * circle_points / i pi_estimates.append(pi_estimate) return pi_estimates
# 设置随机样本数量num_samples = 100000
# 调用函数估计πpi_estimates = estimate_pi(num_samples)
# 定义y轴刻度标签的格式化函数def format_yaxis(value, tick_number): # 找到π的倍数 N = int(np.round(value / np.pi)) if N == 0: return "0" elif N == 1: return r"\pi" else: return rf"{N}\pi"
# 绘制π估计值随样本量增加的变化曲线plt.figure(figsize=(10, 6))plt.plot(range(1, num_samples + 1), pi_estimates, label='Estimated π')plt.axhline(y=np.pi, color='r', linestyle='--', label='Actual π')
# 设置y轴的刻度标签plt.gca().yaxis.set_major_locator(plt.MultipleLocator(np.pi))plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(format_yaxis))
# 设置其他图表元素plt.xlabel('Number of Samples')plt.ylabel('Estimate of π')plt.title('Estimation of π Using Monte Carlo Method')plt.legend()plt.grid(True)plt.show()实例:测试保险产品的风险测试
年龄分布生成
我们先创建了0到100岁范围的年龄段,并使用了Dirichlet分布随机生成不同年龄段的概率,这样可以模拟真实的人口年龄结构。
使用
numpy.random.choice函数,根据年龄分布的概率随机生成了10,000个客户的年龄。
寿命模拟
假设寿命服从正态分布,均值为75岁,标准差为10岁,生成了每个客户的预期寿命。为了保证合理性,将寿命限制在0到120岁之间。
年金支付模拟
每个客户领取年金的年数是他们的寿命减去当前年龄,如果寿命大于当前年龄则计算年金支付年数,否则不支付年金。
假设年金每年支付1000元,计算每个客户的年金总支付额。
蒙特卡洛模拟
通过对10,000个客户的随机年龄和寿命模拟,程序多次重复此过程,最终通过大量随机试验近似计算整个客户群体的年金保险总支付金额。蒙特卡洛模拟通过大量随机样本反复计算,得出年金支付的平均、最大、最小等统计结果,帮助分析风险。
此方法有效地模拟了年金保险的风险和不确定性,并通过模拟结果帮助决策者评估风险敞口。
xxxxxxxxxximport numpy as npimport matplotlib.pyplot as plt
# 假设你有不同年龄段的年龄分布,比如0-100岁ages = np.arange(0, 101)# 设定一个假设的概率分布,真实情况下应根据普查数据获得probabilities = np.random.dirichlet(np.ones(len(ages)), size=1)[0]
# 蒙特卡洛模拟生成客户群体N = 10000 # 假设生成1万个客户simulated_ages = np.random.choice(ages, size=N, p=probabilities)
# 假设寿命服从正态分布,均值为75岁,标准差为10lifetimes = np.random.normal(loc=75, scale=10, size=N)# 过滤掉不合理的寿命,比如负数或超过某个最大寿命的值lifetimes = np.clip(lifetimes, a_min=0, a_max=120)
# 模拟年金保险支付情况,假设每年支付1000元年金annual_annuity_payment = 1000
# 计算每个客户的领取年金年数annuity_years = np.maximum(lifetimes - simulated_ages, 0)
# 计算年金总支付额total_annuity_payments = annuity_years * annual_annuity_payment
# 显示模拟的人群年龄分布和寿命分布fig, axs = plt.subplots(2, 1, figsize=(8, 10))
# 人群年龄分布axs[0].hist(simulated_ages, bins=30, color='skyblue', edgecolor='black')axs[0].set_title("Age Distribution of Simulated Clients")axs[0].set_xlabel("Age")axs[0].set_ylabel("Number of Clients")
# 寿命分布axs[1].hist(lifetimes, bins=30, color='lightgreen', edgecolor='black')axs[1].set_title("Lifetime Distribution")axs[1].set_xlabel("Age at Death")axs[1].set_ylabel("Number of Clients")
plt.tight_layout()plt.show()
# 显示总年金支付额的描述性统计total_annuity_summary = { "Mean Total Annuity Payments": np.mean(total_annuity_payments), "Median Total Annuity Payments": np.median(total_annuity_payments), "Max Total Annuity Payments": np.max(total_annuity_payments), "Min Total Annuity Payments": np.min(total_annuity_payments), "Total Payments (Sum)": np.sum(total_annuity_payments)}
import pandas as pdsummary_df = pd.DataFrame(total_annuity_summary, index=["Values"])蒙特卡洛树搜索MCTS
蒙特卡洛模拟在棋类游戏和博弈论模型中的应用,主要体现在蒙特卡洛树搜索(Monte Carlo Tree Search, MCTS),这是一种强大的搜索算法,常用于决策问题中的策略优化。以下是一些经典的实例:
1. 围棋中的蒙特卡洛树搜索(AlphaGo)
背景:围棋是一个极为复杂的棋类游戏,棋盘上存在极其庞大的可能性空间,传统的穷举搜索方法几乎无法有效应用于围棋。
AlphaGo:谷歌的AlphaGo在围棋中使用了蒙特卡洛树搜索与深度学习结合的技术来选择最优策略。MCTS在每个节点上进行随机模拟(又称为Playout),通过模拟随机对局来评估当前节点的潜在获胜概率。经过大量模拟之后,MCTS可以选择那些获胜概率更高的决策路径。
MCTS流程:
选择(Selection):根据已知信息(如获胜概率)从根节点到子节点,逐步选择未完全扩展的节点。
扩展(Expansion):如果当前节点未终结游戏,扩展一个新的子节点。
模拟(Simulation):从新扩展的节点开始进行一次随机的完全对局模拟,直到游戏结束。
反向传播(Backpropagation):根据模拟的结果,更新从当前节点到根节点之间所有节点的获胜概率。
AlphaGo 通过结合MCTS和神经网络评估函数,使其在围棋领域实现了突破,成功击败了人类顶级选手。
2. 国际象棋中的MCTS(Leela Chess Zero)
背景:国际象棋也是一种具有巨大决策空间的棋类游戏,虽然比围棋复杂度稍低,但仍然不容易通过传统穷举法找到最优策略。
Leela Chess Zero:类似于AlphaGo的围棋算法,Leela Chess Zero也结合了蒙特卡洛树搜索和神经网络评估函数。在棋局的每个状态下,MCTS通过模拟一系列随机的对局过程来评估某个动作的有效性。Leela Chess Zero通过大量模拟和训练,逐步提升其对棋局的理解和决策能力。
具体过程:系统通过模拟大量可能的棋局走法,在棋盘上扩展不同的分支,反复评估各个分支的结果,从而选择获胜概率最大的策略。
3. 博弈论模型中的蒙特卡洛模拟(扑克与其他对抗性游戏)
背景:扑克等对抗性博弈游戏中,玩家的策略选择需要考虑对手的反应和未来可能的牌局变化,博弈空间非常庞大且存在信息不完全的问题。
DeepStack与Libratus(扑克AI):这些扑克AI使用了MCTS与博弈论中的纳什均衡相结合的技术,通过蒙特卡洛模拟评估不同对局的可能结果。特别是在不完全信息的场景下(如对手牌未知),AI通过大量随机模拟可能的牌局走向和对手反应,来选择最有利的策略。
MCTS在扑克中的应用:
信息集处理:在扑克中,部分信息对玩家不可见(如对手的手牌),MCTS可以通过模拟对手可能持有的手牌集合,推测最优策略。
模拟对局:通过随机模拟未来可能的发牌和下注过程,预测对手的策略反应,从而调整自己的策略选择。
4. 博弈树搜索中的MCTS(一般博弈论问题)
背景:在博弈论模型中,双方的策略会相互影响,双方都试图通过预测对手的行为来做出最佳决策。
应用实例:在简单的博弈论问题中,比如经典的囚徒困境或市场竞争模型,玩家可以通过MCTS进行模拟,预测在对方采取某种策略的情况下自己的最佳回应。MCTS通过大量的策略模拟,逐步找到一种接近纳什均衡的解,从而选择最优的博弈策略。
5. Hex棋中的MCTS
背景:Hex棋是一种棋类游戏,其目标是在棋盘上连接两个对边。Hex棋的复杂性较高,传统搜索算法难以处理。
应用实例:MCTS在Hex棋中非常有效,通过模拟大量对局,随机生成不同的棋步组合,并通过模拟的结果判断每个棋步的胜算几率。MCTS逐步扩展每个节点的搜索树,并更新获胜概率,从而帮助玩家做出更优的策略选择。
实例:井字棋MCTS决策
xxxxxxxxxximport randomimport mathfrom copy import deepcopy
# 定义井字棋游戏class TicTacToe: def __init__(self): self.board = [[' ' for _ in range(3)] for _ in range(3)] self.current_player = 'X'
def make_move(self, row, col): if self.board[row][col] == ' ': self.board[row][col] = self.current_player self.current_player = 'O' if self.current_player == 'X' else 'X' return True return False
def get_winner(self): # 检查行、列、对角线是否有赢家 for i in range(3): if self.board[i][0] == self.board[i][1] == self.board[i][2] != ' ': return self.board[i][0] if self.board[0][i] == self.board[1][i] == self.board[2][i] != ' ': return self.board[0][i] if self.board[0][0] == self.board[1][1] == self.board[2][2] != ' ': return self.board[0][0] if self.board[0][2] == self.board[1][1] == self.board[2][0] != ' ': return self.board[0][2] return None
def is_full(self): return all(self.board[i][j] != ' ' for i in range(3) for j in range(3))
def get_available_moves(self): return [(i, j) for i in range(3) for j in range(3) if self.board[i][j] == ' ']
def clone(self): return deepcopy(self)
# 定义MCTS节点class Node: def __init__(self, state, parent=None): self.state = state self.parent = parent self.children = [] self.visits = 0 self.wins = 0 self.untried_moves = state.get_available_moves()
def is_fully_expanded(self): return len(self.untried_moves) == 0
def best_child(self, c_param=1.4): choices_weights = [ (child.wins / child.visits) + c_param * math.sqrt((2 * math.log(self.visits) / child.visits)) for child in self.children ] return self.children[choices_weights.index(max(choices_weights))]
def expand(self): move = self.untried_moves.pop() new_state = self.state.clone() new_state.make_move(*move) child_node = Node(new_state, parent=self) self.children.append(child_node) return child_node
def update(self, result): self.visits += 1 if result == 'X': self.wins += 1
# MCTS主函数def mcts(root_state, iterations=1000): root_node = Node(root_state)
for _ in range(iterations): node = root_node state = root_state.clone()
# 选择阶段:找到未完全扩展的节点 while not node.is_fully_expanded() and node.children: node = node.best_child()
# 扩展阶段:如果节点没有完全扩展,扩展一个子节点 if node.untried_moves: node = node.expand()
# 模拟阶段:随机模拟一局游戏直到结束 while not state.is_full() and state.get_winner() is None: available_moves = state.get_available_moves() move = random.choice(available_moves) state.make_move(*move)
# 反向传播阶段:根据模拟结果更新节点 winner = state.get_winner() while node is not None: node.update(winner) node = node.parent
return root_node.best_child(c_param=0).state
# 游戏测试def print_board(state): for row in state.board: print(' | '.join(row)) print('-' * 5)
if __name__ == "__main__": game = TicTacToe()
while game.get_winner() is None and not game.is_full(): print("Current board:") print_board(game)
if game.current_player == 'X': print("MCTS is thinking...") game = mcts(game, iterations=1000) else: move = input("Enter your move (row, col): ").split(',') game.make_move(int(move[0]), int(move[1]))
print("Game Over") print_board(game) winner = game.get_winner() if winner: print(f"The winner is {winner}!") else: print("It's a draw!")
物理模拟
计算机对于物理的模拟是非连续的,我们只能按照一定时间间隔(Tick)去计算全局物理量。最经典的例子为FPS游戏中的弹道模拟、命中判定。
FPS中的计算机模拟
费力不讨好的FPS核心逻辑:命中判定,什么是延迟补偿,服务器tick,以及为什么会吞子弹 https://www.bilibili.com/video/BV1Ne411D7HB/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=d482d60c82056fef722a6116dbfed026 [1]梁白鸥.实时网络游戏中关键技术的研究与实现[D].电子科技大学,2007. 实时网络游戏中关键技术的研究与实现_梁白鸥.pdf 在第一人称射击(FPS)场景中,诸如击杀、受伤等事件通常是瞬间发生的。然而,计算机需要一定的时间来进行判定计算,例如确定这些事件是否发生、玩家如何移动等。这使得计算机只能每隔一段时间间隔
(最短小于一次判定计算花费的时间)。
具体来说,计算机需要利用
弹道轨迹模拟算法
基于射击参数,使用抛物线方程(也可以之间)来计算子弹的飞行路径(x,y位置)。这个过程可以通过调整子弹初速度来考虑不同枪械种类的影响。
g 是重力加速度(大约为 9.8 m/s²)。
t 是时间。
v0 是子弹的初速度。
θ 是发射角度。
y0和x0分别是子弹发射时的初始高度和水平位置。
若不考虑子弹下坠,即高速短距离射击,或者是激光枪。则可以使用直线方程如下。
概率的射击算法
射击圆形物体
其中r是目标的半径。
导弹一般很少直接命中飞机。导弹一般会到达一个“最接近点” 然后在飞机附近引爆。之后导弹的碎片从爆炸点呈环或球状散开飞机被弹片命中继而被摧毁。这个算法可以用于爆炸性投射武器、火球来瞄准飞机等敌人计算弹片影 响的概率公式如下。
射击的延迟补偿
网络游戏的模拟一般在服务器进行。所有的玩家操作都需要上传到服务器。数据到达服务器之前服务器仍然在不停的模拟整个游戏世界,同时,玩家在此期间也在进行新的操作。
例如:一个玩家在时刻10.50秒的时候向一个目标射击 射击的信息被打包发送到服务器上,10.60秒的时候,该射击数据到达服务器。而射击对象的网络延迟较低,其新的位置信息已经在10.55秒到达了服务器,服务器在10.60秒时按照玩家射击数据进行判断时,射击对象已经不在10.50秒的位置,由此服务器判断未打中。因此可能给玩家呈现一个不真实的现象本来在客户端上已经打到目标但是实际上有服务器仲裁的结果却并没有打到。
基于历史信息的延迟补偿
由此,可以在服务器和客户端上都保存玩家的各个时刻的历史状态信息。
假设当前的网络延迟为
若使用射击方玩家的网络延迟