当前位置: 首页 > news >正文

用Manim实现高尔顿板(Galton Board)

高尔顿板的介绍

 高尔顿板(Galton Board),有时也称为贝尔图(Bean Machine),是由英国统计学家弗朗西斯·高尔顿(Francis Galton)于19世纪末发明的一种物理装置,用于演示随机分布和大数法则的概念。它通过简单的机械原理展示了概率和统计的基本概念。

高尔顿板是一个简单而有效的工具,通过直观的物理演示使得复杂的概率和统计概念变得易于理解。它不仅是教育的有效工具,也是研究随机性和分布特性的重要模型。

结构与原理

  1. 结构:

    • 高尔顿板通常由一个倾斜的木板或其他材料制成,面板上排列着若干个固定的小钉或障碍物,形成一个网格状的结构。底部有多个接收容器(例如小盒子或小槽),用于收集掉落的颗粒或小球。
  2. 工作原理:

    • 顶部的槽(或投入口)用于放置小球。当小球从顶部落下时,它们会碰到网格中的钉子。每次碰撞时,小球都有50%的概率向左或向右偏移,导致小球沿着随机路径向下移动。
    • 随着小球不断下落,它们最终将停在底部的接收容器中。由于每个球的下落路径是随机的,经过多次实验后,落入各个槽中的小球数量会呈现出明显的钟形正态分布。

数学与统计意义

  • 大数法则: 高尔顿板是展示大数法则的经典案例之一。随着投入的小球数量的增加,落入各个接收容器的数量趋向于正态分布,即使小球的每次下落是随机的,但总体的结果表现出稳定的模式。
  • 中立性和随机性: 高尔顿板展示了随机性下的平衡现象。虽然每个小球的移动路径是随机的,但它们最终的数量分布却可以预测。

应用

  • 高尔顿板常用于教育和教学,帮助学生理解概率、统计、正态分布、大数法则等概念。
  • 也被广泛应用于统计学、心理学和经济学等其他学科的可视化实验中。

创建manim代码 

from manim import *  
import random  class GaltonBoard(Scene):  # 配置信息  config = {  "runTime": 16,  # 动画运行时间  "itemsTotal": 100,  # 总点数  "itemDelayFrames": 1,  # 点出现间隔(帧数)  "hexSize": .2,  # 六边形的大小  "hexVerticalShift": .6,  # 六边形的垂直偏移  "hexGorizontalShift": .4,  # 六边形的水平偏移  "hexRowsCount": 7,  # 六边形的行数  "firstHexCenterX": -3,  # 第一个六边形的中心x坐标  "firstHexCenterY": 3,  # 第一个六边形的中心y坐标  "durationSeconds": 2,  # 每个点的运动持续时间  "circleRadius": .05,  # 小圆点的半径  "firstDot": [-3, 4.3, 0]  # 第一个点的位置  }  frameNumber = 0  # 帧计数器  def construct(self):  # 创建表格、计数器、六边形、顶点和小点  table = self.createTable()  # 生成表格  counter = self.createCounter()  # 生成计数器  hexagons = self.createHexagons()  # 生成六边形  vertices = self.createVertices()  # 生成六边形的顶点  items = self.createItems(vertices)  # 生成小点  # 帧更新函数  def updateFrameFunction(table):  durationSeconds = GaltonBoard.config["durationSeconds"]  durationFrames = durationSeconds * self.camera.frame_rate  # 单位时间内的帧数  self.frameNumber += 1  for item in items:  if item.isActive and self.frameNumber > item.startFrame:  alpha = (self.frameNumber - item.startFrame) / durationFrames  if (alpha <= 1.0):  point = item.path.point_from_proportion(rate_functions.linear(alpha))  # 获取小点在路径上的位置  item.circle.move_to(point)  # 移动小点  else:  updateCounter()  # 更新计数器  updateStackValue(item.stackIndex)  # 更新堆叠值  item.isActive = False  # 设置点为非活动状态  # 更新计数器函数  def updateCounter():  val = counter[0].get_value()  # 获取计数器当前值  val += 1  # 增加计数  counter[0].set_value(val)  # 更新计数器的值  # 更新堆叠值的函数  def updateStackValue(stackValueIndex):  cell = table.get_entries((1, stackValueIndex + 1))  # 获取表格中对应单元格  val = cell.get_value()  # 获取该单元格的当前值  val += 1  # 增加堆叠值  cell.set_value(val)  # 更新单元格值  # 渲染六边形和表格与计数器  self.play(FadeIn(hexagons, run_time=1))  self.play(FadeIn(table, run_time=1))  self.play(FadeIn(counter, run_time=1))  # 为更新函数准备需要更新的对象  wrapper = VGroup(table, counter)  for item in items:  wrapper.add(item.circle)  runTime = GaltonBoard.config["runTime"]  # 开始更新动画  self.play(UpdateFromFunc(wrapper, updateFrameFunction), run_time=runTime)  self.wait(3)  # 等待3秒以查看结果  def createTable(self):  # 创建一个整数表来显示点的堆叠数量  table = IntegerTable(  [[0, 0, 0, 0, 0, 0, 0, 0],],  # 初始化表格  line_config={"stroke_width": 1, "color": Yline_config={"stroke_width": 1, "color": YELLOW},  # 表格线的样式设置  cell_config={"stroke_width": 1, "color": WHITE},  # 单元格的样式设置  )  table.move_to(UP * 3)  # 将表格移动到画面上方  return table  def createCounter(self):  # 创建一个计数器用于计数通过的点  counter = DecimalNumber(0)  # 创建一个数值对象,初始值为0  counter.move_to(UP * 3 + RIGHT * 5)  # 将计数器移动到适当位置  return [counter]  # 返回计数器对象列表  def createHexagons(self):  hexagons = VGroup()  # 创建一个用于存放六边形的组  hexSize = GaltonBoard.config["hexSize"]  # 获取六边形的大小  hexVerticalShift = GaltonBoard.config["hexVerticalShift"]  # 获取垂直偏移量  hexGorizontalShift = GaltonBoard.config["hexGorizontalShift"]  # 获取水平偏移量  hexRowsCount = GaltonBoard.config["hexRowsCount"]  # 获取行数  # 循环生成六边形  for row in range(hexRowsCount):  for col in range(3):  hexagon = RegularPolygon(n=6, radius=hexSize)  # 创建一个六边形  hexagon.move_to(  (col * hexGorizontalShift, row * hexVerticalShift, 0)  # 设置六边形位置  )  hexagons.add(hexagon)  # 将六边形加入组中  return hexagons  # 返回所有六边形  def createVertices(self):  # 创建六边形的顶点坐标  vertices = []  hexSize = GaltonBoard.config["hexSize"]  # 获取六边形的大小  hexVerticalShift = GaltonBoard.config["hexVerticalShift"]  # 获取垂直偏移量  # 根据行数计算每行的顶点坐标  for row in range(GaltonBoard.config["hexRowsCount"]):  vertexRow = []  for i in range(3):  # 每行有3个顶点  vertexRow.append(np.array([  i * GaltonBoard.config["hexGorizontalShift"],  row * hexVerticalShift,  0  ]))  vertices.append(vertexRow)  # 将顶点按行添加到列表中  return vertices  # 返回所有顶点  def createItems(self, vertices):  # 创建小点并为其分配路径  itemsTotal = GaltonBoard.config["itemsTotal"]  # 获取总点数  circleRadius = GaltonBoard.config["circleRadius"]  # 小圆点半径  itemDelayFrames = GaltonBoard.config["itemDelayFrames"]  # 小点出现间隔  firstDot = GaltonBoard.config["firstDot"]  # 第一个小点的位置  items = []  # 存放小点的列表  startFrame = 0  # 起始帧计数  stackValues = [0] * 9  # 存储堆叠数的列表,初始化为0  for k in range(itemsTotal):  item = Item()  # 初始化点  circle = Circle(radius=circleRadius, color=GREEN, fill_opacity=1)  # 创建小圆点  pathIndex = self.createPathIndex()  # 生成路径索引  stackIndex = pathIndex.bit_count()  # 计算堆叠索引  stackValues[stackIndex] += 1  # 增加堆叠值  path = self.createPath(vertices, pathIndex, stackValues[stackIndex])  # 创建路径  item.path = path  # 分配路径  item.circle = circle  # 分配圆点  item.stackIndex = stackIndex  # 设置堆叠索引  item.startFrame = startFrame  # 设置起始帧  startFrame += itemDelayFrames  # 更新起始帧  self.add(circle)  # 将圆点添加到场景中  circle.move_to(firstDot)  # 移动圆点到第一个位置  items.append(item)  # 将点添加到列表中  # 如果需要可以显示路径  # self.add(path)  return items  # 返回所有小点  def createPathIndex(self):  # 随机生成一个路径索引  return random.randrange(128)  # 返回0到127之间的随机整数  def createPath(self, vertices, pathIndex, itemsCountInStack):  # 根据路径索引和堆叠数创建路径  firstDot = GaltonBoard.config["firstDot"]  # 获取第一个点的位置  rowCapacity = 3  # 每行最大容量  # 计算最后一个点在网格中的位置  lastDotRowIndex = (itemsCountInStack - 1) // rowCapacity  lastDotColIndex = (itemsCountInStack - 1) % rowCapacity   path = Line(firstDot, vertices[0][0], stroke_width=1)  # 创建起始点到第一个点的线  previousDot = vertices[0][0]  binary = bin(pathIndex)[2:].zfill(7)  # 将路径索引转为二进制,左侧填0到7位  rowIndex, colIndex = 1, 0  # 初始化行列索引  # 根据路径索引的二进制值生成路径  for digit in binary:  if digit == '0':  pathTmp = ArcBetweenPoints(previousDot, vertices[rowIndex][colIndex], angle=PI / 2, stroke_width=1)  # 向左转90度  else:  colIndex += 1  pathTmp = ArcBetweenPoints(previousDot, vertices[rowIndex][colIndex], angle=-PI / 2, stroke_width=1)  # 向右转90度  previousDot = vertices[rowIndex][colIndex]  path.append_vectorized_mobject(pathTmp)  # 将路径片段添加到路径中  rowIndex += 1  # 计算最后一个点的坐标  lastDotWidth = .1  # 最后一个点的宽度  lastDotHeight = .1  # 最后一个点的高度  lastDotX = previousDot[0]  # 获取最后一个点的x坐标  # 根据最后点的位置调整x坐标  if lastDotColIndex == 0:  lastDotX -= lastDotWidth  elif lastDotColIndex == 2:  lastDotX += lastDotWidth  lastDotY = previousDot[1] - 2.4 + lastDotHeight * lastDotRowIndex  # 计算最后一个点的y坐标  pathLast = Line(previousDot, [lastDotX, lastDotY, 0], stroke_width=1)  # 连接到最后一个点的路径  path.append_vectorized_mobject(pathLast)  # 将最后的路径段添加到路径中  return path  # 返回生成的路径  def showDotMap(self, showAxes):  # 显示点的坐标图  for x in range(-7, 8):  for y in range(-4, 5):  dot = Dot(np.array([x, y, 0]), radius=0.02)  # 创建一个小点  self.add(dot)  # 将点添加到场景中  if showAxes:  ax = Axes(x_range=[-7, 7], y_range=[-4, 4], x_length=14, y_length=8)  # 创建坐标轴  self.add(ax)  # 将坐标轴添加到场景中  class Item:  # 定义小点的类  circle = None  # 圆点  path = None  # 路径  startFrame = 0  # 开始帧  stackIndex = 0  # 堆叠索引  isActive = True  # 是否活动的标志

 我想要的理想型结果:

实际运行结果:

代码解释

  1. GaltonBoard 类: 该类继承自 Manim 的 Scene,用于创建高尔顿板的动画。配置参数定义了高尔顿板的运行时间、点的总数、点之间的延迟、圆点的大小和位置等信息。

  2. 构造函数construct 方法是动画的主入口,创建所有组件(表格、计数器、六边形、顶点、小点等),并控制它们的动画效果。

  3. 创建六边形和顶点createHexagons 和 createVertices 方法用于生成高尔顿板上的六边形及其顶点,以便点沿着这些顶点掉落。

  4. 生成路径和小点createItems 方法创建小点并为其分配路径,路径的生成基于随机索引,决定了每个点在高尔顿板上掉落的方向。

  5. 动画更新: 动画通过 UpdateFromFunc 不断更新每个小点的位置,直到所有小点都掉落完毕。

  6. 路径生成createPath 方法根据随机生成的索引创建路径,通过计算每个点的坐标来绘制连线。

  7. 计数器和堆叠统计: 使用计数器记录每个点通过的次数,并在界面上显示。

总结

此代码实现了一种经典的概率分布演示工具,通过高尔顿板的随机掉落过程展示大数法则,提供了视觉化的理解,并使用 Manim 库进行高效的动画展示。

http://www.lryc.cn/news/453497.html

相关文章:

  • OpenCV视频I/O(7)视频采集类VideoCapture之初始化视频捕获设备或打开一个视频文件函数open()的使用
  • vue3+vite@4+ts+elementplus创建项目详解
  • Python 从入门到实战34(实例2:绘制蟒蛇)
  • Visual Studio C# 处理和修复 WinRiver II 测量项目 MMT 文件错误
  • JAVA实现大写金额转小写金额
  • 如何使用ssm实现基于SSM的宠物服务平台的设计与实现+vue
  • 【C++学习笔记 21】C++中的动态数组 vertor
  • MongoDB 快速入门+单机部署(附带脚本)
  • 组合数求法汇总
  • Python知识点:在Python编程中,如何使用Joblib进行并行计算
  • matlab-对比两张图片的CIElab分量的差值并形成直方图
  • (十七)、Mac 安装k8s
  • 信息学奥赛一本通 2087:【22CSPJ普及组】解密(decode) | 洛谷 P8814 [CSP-J 2022] 解密
  • 【重学 MySQL】四十八、DCL 中的 commit 和 rollback
  • Java面试八股之认证授权
  • RCE_绕过综合
  • 关于Generator,async 和 await的介绍
  • Redis数据库与GO(二):list,set
  • c++知识点总结
  • 无IDEA不Java:快速掌握Java集成开发环境
  • 9.30学习记录(补)
  • 移动应用中提升用户体验的因素
  • VS与VSCode的区别
  • 用Python和OpenCV实现人脸识别:构建智能识别系统
  • 微积分-反函数6.5(指数增长和衰减)
  • C初阶(十二)do - while循环 --- 致敬革命烈士
  • 从零开始:SpringBoot实现古典舞在线交流平台
  • AL生成文章标题指定路径保存:创新工具助力内容创作高效启航
  • java基础知识汇总
  • 2.点位管理|前后端如何交互——帝可得后台管理系统