用Manim实现高尔顿板(Galton Board)
高尔顿板的介绍
高尔顿板(Galton Board),有时也称为贝尔图(Bean Machine),是由英国统计学家弗朗西斯·高尔顿(Francis Galton)于19世纪末发明的一种物理装置,用于演示随机分布和大数法则的概念。它通过简单的机械原理展示了概率和统计的基本概念。
高尔顿板是一个简单而有效的工具,通过直观的物理演示使得复杂的概率和统计概念变得易于理解。它不仅是教育的有效工具,也是研究随机性和分布特性的重要模型。
结构与原理
-
结构:
- 高尔顿板通常由一个倾斜的木板或其他材料制成,面板上排列着若干个固定的小钉或障碍物,形成一个网格状的结构。底部有多个接收容器(例如小盒子或小槽),用于收集掉落的颗粒或小球。
-
工作原理:
- 顶部的槽(或投入口)用于放置小球。当小球从顶部落下时,它们会碰到网格中的钉子。每次碰撞时,小球都有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 # 是否活动的标志
我想要的理想型结果:
实际运行结果:
代码解释
-
GaltonBoard 类: 该类继承自 Manim 的
Scene
,用于创建高尔顿板的动画。配置参数定义了高尔顿板的运行时间、点的总数、点之间的延迟、圆点的大小和位置等信息。 -
构造函数:
construct
方法是动画的主入口,创建所有组件(表格、计数器、六边形、顶点、小点等),并控制它们的动画效果。 -
创建六边形和顶点:
createHexagons
和createVertices
方法用于生成高尔顿板上的六边形及其顶点,以便点沿着这些顶点掉落。 -
生成路径和小点:
createItems
方法创建小点并为其分配路径,路径的生成基于随机索引,决定了每个点在高尔顿板上掉落的方向。 -
动画更新: 动画通过
UpdateFromFunc
不断更新每个小点的位置,直到所有小点都掉落完毕。 -
路径生成:
createPath
方法根据随机生成的索引创建路径,通过计算每个点的坐标来绘制连线。 -
计数器和堆叠统计: 使用计数器记录每个点通过的次数,并在界面上显示。
总结
此代码实现了一种经典的概率分布演示工具,通过高尔顿板的随机掉落过程展示大数法则,提供了视觉化的理解,并使用 Manim 库进行高效的动画展示。
相关文章:

用Manim实现高尔顿板(Galton Board)
高尔顿板的介绍 高尔顿板(Galton Board),有时也称为贝尔图(Bean Machine),是由英国统计学家弗朗西斯高尔顿(Francis Galton)于19世纪末发明的一种物理装置,用于演示随机分…...
OpenCV视频I/O(7)视频采集类VideoCapture之初始化视频捕获设备或打开一个视频文件函数open()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 为视频捕获打开一个视频文件、捕获设备或 IP 视频流。 这是一个重载成员函数,提供给用户方便使用。它与上述函数的不同之处仅在于它所…...

vue3+vite@4+ts+elementplus创建项目详解
1、第一步创建项目cnpm init vite4 2、设置vue3.2局域网可访问配置: 找到项目路径下的package.json目录下找到script对象下面添加一下代码: "serve": "vite --host 0.0.0.0" 启动项目命令不在是dev而是:cnpm run serve 3…...

Python 从入门到实战34(实例2:绘制蟒蛇)
我们的目标是:通过这一套资料学习下来,通过熟练掌握python基础,然后结合经典实例、实践相结合,使我们完全掌握python,并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库MySQL操作的相关知识。今天学习一个使用…...

Visual Studio C# 处理和修复 WinRiver II 测量项目 MMT 文件错误
Visual Studio C# 处理和修复 WinRiver II 测量项目 MMT 文件错误 前言一、WinRiver II 测量项目 MMT 文件的结构二、WinRiver II 无法打开或操作测量项目 MMT 文件2.1 无法载入船测多线法测量文件2.2 可以载入测验项目 MMT 文件,但 ADCP 后处理软件无法写入信息2.3…...
JAVA实现大写金额转小写金额
在金融行业中经常需要把大写金额转成小写金额,之前在一次开发中有个类似的需求,翻阅了好多博文,都没找到合适的,故没办法,就花了点时间研究并实现! 实现代码如下: private static final Character ZERO 零;private s…...

如何使用ssm实现基于SSM的宠物服务平台的设计与实现+vue
TOC ssm779基于SSM的宠物服务平台的设计与实现vue 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大,随着当前时代的信息化,科学化发展,让社会各行业领域都争相使用新的信息技术,对行业内的各种相关数据进行科学化&#x…...
【C++学习笔记 21】C++中的动态数组 vertor
静态数组 首先来创建一个静态数组 #include <iostream> #include <string>struct Vertex {float x, y, z; };std::ostream& operator<<(std::ostream& stream, const Vertex& vertex) {stream << vertex.x << "," <&…...

MongoDB 快速入门+单机部署(附带脚本)
目录 介绍 体系结构 数据模型 BSON BSON 数据类型 特点 高性能 高可用 高扩展 丰富的查询支持 其他特点 部署 单机部署 普通安装 脚本安装 Docker Compose 安装 卸载 停止 MongoDB 删除包 删除数据目录 参考: https://docs.mongoing.com/ 介绍…...
组合数求法汇总
一:递推求解 对于组合数,有此式: C n m C n − 1 m − 1 C n − 1 m C_{n}^{m}C_{n-1}^{m-1}C_{n-1}^{m} CnmCn−1m−1Cn−1m。 C n m C_{n}^{m} Cnm 可理解为 n n n 个数中选 m m m 个,不同的方案。对于第 n n n 个…...
Python知识点:在Python编程中,如何使用Joblib进行并行计算
开篇,先说一个好消息,截止到2025年1月1日前,翻到文末找到我,赠送定制版的开题报告和任务书,先到先得!过期不候! Joblib是一个Python库,它被设计用来提供轻便的并行计算解决方案&…...
matlab-对比两张图片的CIElab分量的差值并形成直方图
%对比两张图片的CIElab分量的差值并形成直方图,改个路径就能用,图片分辨率要一致 close all; clear all; clc; I1imread(E:\test\resources\image\1.jpg); I2imread(E:\test\resources\image\2.jpg); lab1 rgb2lab(I1); lab2 rgb2lab(I2); % 提取色度…...

(十七)、Mac 安装k8s
文章目录 1、Enable Kubernetes2、查看k8s运行状态3、启用 kubernetes-dashboard3.1、如果启动成功,可以在浏览器访问3.2、如果没有跳转,需要单独安装 kubernetes-dashboard3.2.1、方式一:一步到位3.2.2、方式二:逐步进行 1、Enab…...
信息学奥赛一本通 2087:【22CSPJ普及组】解密(decode) | 洛谷 P8814 [CSP-J 2022] 解密
【题目链接】 洛谷 P8814 [CSP-J 2022] 解密 ybt 2087:【22CSPJ普及组】解密(decode) 【题目考点】 1. 数学:一元二次方程求根 【解题思路】 输入n,d,e,满足 n p ∗ q np*q np∗q e ∗ d ( p − 1 ) ( q − 1…...

【重学 MySQL】四十八、DCL 中的 commit 和 rollback
【重学 MySQL】四十八、DCL 中的 commit 和 rollback commit的定义与作用rollback的定义与作用使用场景相关示例注意事项DDL 和 DML 的说明 在MySQL中,DCL(Data Control Language,数据控制语言)用于管理数据库用户和控制数据的访问…...
Java面试八股之认证授权
一、概念: 1、什么是认证?什么是授权? 认证 用于在系统登录时,验证身份的凭证,类似于账号、密码等。 授权 用户在访问资源时,根据权限的不同对资源访问程度不同。 2、什么是cookie?什么是…...
RCE_绕过综合
<aside> 💡 管道符 </aside> <aside> 💡 通配符绕过 </aside> **匹配任何字符串/文本,包括空字符串;*代表任意字符(0个或多个)? 匹配任何一个字符(不…...

关于Generator,async 和 await的介绍
在本篇文章中我们主要围绕下面几个问题来介绍async 和await 🍰Generator的作用,async 及 await 的特点,它们的优点和缺点分别是什么?await 原理是什么? 📅我的感受是我们先来了解Generator,在去…...

Redis数据库与GO(二):list,set
一、list(列表) list(列表)是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。List本质是个链表, list是一个双向链表,其元素是有序的,元…...
c++知识点总结
1.把字符串a复制到b里面 #include<iostream> #include<string.h> using namespace std; int main() {char a[110],b[110];cin>>a;int n strlen(a);for(int i 0;i<n1;i){b[i] a[i];}cout<<b;return 0; }2.比较两个字符串的大小 如果a大返回1&…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...