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

news2024/10/5 14:34:47

高尔顿板的介绍

 高尔顿板(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": Y
                        line_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.coloradmin.cn/o/2190020.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【智能算法应用】蒲公英优化算法求解二维路径规划问题

摘要 在二维路径规划问题中&#xff0c;通常需要在不规则的障碍物环境中找到一条从起点到终点的最优路径。本文应用蒲公英优化算法&#xff08;DOA&#xff09;进行路径规划&#xff0c;其能够有效避开障碍物并找到最短路径。通过实验验证&#xff0c;DOA具有收敛速度快、全局…

2024年【金属非金属矿山(露天矿山)安全管理人员】模拟试题及金属非金属矿山(露天矿山)安全管理人员模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 金属非金属矿山&#xff08;露天矿山&#xff09;安全管理人员模拟试题参考答案及金属非金属矿山&#xff08;露天矿山&#xff09;安全管理人员考试试题解析是安全生产模拟考试一点通题库老师及金属非金属矿山&#…

SAP学习笔记 - Basis01 - 创建Client ,拷贝Client

最近工作当中用到了Client间数据移送的内容&#xff0c;想把自己的虚机给弄两个Client。 最后也没完全弄成&#xff0c;先把过程整理一下&#xff0c;以后有空接着弄。 目录 1&#xff0c;SALE - 新建逻辑系统 2&#xff0c;SCC4 - 分配Client到集团 3&#xff0c;RZ10 - 取…

算法【Java】—— 二叉树的深搜

深搜 深搜简单来说就是一直递归到底&#xff0c;然后返回&#xff0c;以二叉树为例&#xff0c;就是从根节点出发一直搜索到叶子节点&#xff0c;然后想上返回。 这里简单说明一下&#xff1a;深搜的英文缩写是 dfs&#xff0c;下面定义深搜函数名我直接命名为 dfs 实战演练 …

AVL树的创建与检测

个人主页&#xff1a;敲上瘾-CSDN博客 个人专栏&#xff1a;游戏、数据结构、c语言基础、c学习、算法 目录 一、什么是AVL树&#xff1f; 二、平衡因子 1、什么是平衡因子&#xff1f; 2、平衡因子如何更新&#xff1f; 三、单旋 1、左单旋 ​编辑 2、右单旋 四、双旋…

OSPF的不规则区域

1.远离骨干非骨干区域 2.不连续骨干 解决方案 tunnel ---点到点GRE 在合法与非ABR间建立隧道&#xff0c;然后将其宣告于OSPF协议中&#xff1b; 缺点&#xff1a;1、周期和触发信息对中间穿越区域造成资源占用&#xff08;当同一条路由来自不同区域&#xff0c;路由器会先…

JS基础练习|动态创建多个input并且用数组记录其中的数据

效果图 、 在点击添加输入框的时候&#xff0c;创建新的元素&#xff0c;并且为其绑定响应的事件。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-…

稀缺森林火险等级预测算法,基于xgboost方法的火险等级预测,共划分5级,依据当前地区月份,降水量,风力等参数进行预测,并提供15000字的报告

森林火险等级预测算法&#xff0c;基于xgboost方法的火险等级预测&#xff0c;共划分5级&#xff0c;依据当前地区月份&#xff0c;降水量&#xff0c;风力等参数进行预测&#xff0c;并提供15000字的报告 森林火险等级预测算法介绍 项目名称 基于XGBoost的森林火险等级预测算…

双向无头非循环链表的简单实现及介绍

前言 欢迎大家阅读小奥奇的新作&#xff0c;听说上一篇我们留下了一点点 “ 简单的题目 ” &#xff0c;我们在本篇要干什么呢&#xff0c;请看本篇任务&#xff01; 本篇任务概述&#xff1a; 1、解决 “ 简单的遗留题目 ” 2、 LInkedList&#xff08;双向&#xff09;的使用…

2.1MyBatis——ORM对象关系映射

2.1MyBatis——ORM对象关系映射 1. 验证映射配置2.ResultType和ResultMap2.1ResultMap是最终的ORM依据2.2ResultType和ResultMap的使用区别 3.具体的转换逻辑3.1 TypeHandle类型转换 5.总结 概括的说&#xff0c;MyBatis中&#xff0c;对于映射关系的声明是由开发者在xml文件手…

“2024年最流行的10个前端框架”

大多数时候&#xff0c;前端开发人员需要使用一组组合语言来构建他们的前端 Web 应用程序。 HTML 负责网页中的基本布局&#xff0c;CSS 管理视觉格式和结构&#xff0c;JavaScript 用于维护交互性和功能。在这篇文章中&#xff0c;我们将了解最好的前端框架&#xff0c;这些框…

FL Studio 24.1.2.4381中文版免费下载及FL Studio 24最新使用学习教程

家好呀&#xff0c;作为一个资深的音乐爱好者和制作人&#xff0c;今天我要安利一个我最近超级痴迷的数字音频工作站软件——FL Studio24.1.2.4381中文版。这款产品可是让我的音乐创作之路如虎添翼&#xff0c;快来跟我一起看看它的炫酷功能吧&#xff01; 最近接到很多小伙伴的…

2024 ciscn WP

一、MISC 1.火锅链观光打卡 打开后连接自己的钱包&#xff0c;然后点击开始游戏&#xff0c;答题八次后点击获取NFT&#xff0c;得到有flag的图片 没什么多说的&#xff0c;知识问答题 兑换 NFT Flag{y0u_ar3_hotpot_K1ng} 2.Power Trajectory Diagram 方法1&#xff1a; 使用p…

操作系统实验之银行算法

一、实验目的 采用高级语言编写一个动态分配系统资源的程序&#xff0c;模拟死锁现象&#xff0c;观察死锁发生的条件&#xff0c;并采用适当的算法&#xff0c;有效地防止死锁的发生。 二、实验内容 本次实验采用银行算法防止死锁的发生。设有3个并发进程共享10个系统资源。在…

1c语言基础

1.关键字 一、数据类型关键字 A基本数据类型&#xff08;5个&#xff09; void&#xff1a;声明函数无返回值或无参数&#xff0c;声明无类型指针&#xff0c;显式丢弃运算结果char&#xff1a;字符型类型数据&#xff0c;属于整型数据的一种int&#xff1a;整型数据&#x…

Ollama 运行视觉语言模型LLaVA

Ollama的LLaVA&#xff08;大型语言和视觉助手&#xff09;模型集已更新至 1.6 版&#xff0c;支持&#xff1a; 更高的图像分辨率&#xff1a;支持高达 4 倍的像素&#xff0c;使模型能够掌握更多细节。改进的文本识别和推理能力&#xff1a;在附加文档、图表和图表数据集上进…

Github界面学习

之前并没有使用到其他功能大多数是看代码&#xff0c;然后看discussion&#xff1b; now,在做毕设的时候发现了一个gymnasium关于异步环境的bug&#xff0c;查看github发现已经被修复了&#xff1b; 因此希望学习一下修复者是在哪个module修复以及如何修复以及提交代码&#…

Spring Boot框架在大学生就业招聘中的应用

3系统分析 3.1可行性分析 通过对本大学生就业招聘系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本大学生就业招聘系统采用JAVA作为开发语言&#xff0c;S…

kaggle实战3RossmanStore商店销售额预测XgBoost解决回归问题案例1

kaggle实战2信用卡反欺诈逻辑回归模型案例1 数据集下载地址 https://download.csdn.net/download/AnalogElectronic/89844637 https://tianchi.aliyun.com/dataset/89785 加载数据 #预测销售额 回归问题 import numpy as np import pandas as pd import matplotlib.pyplot a…

无神论文解读之ControlNet:Adding Conditional Control to Text-to-Image Diffusion Models

一、什么是ControlNet ControlNet是一种能够控制模型生成内容的方法&#xff0c;能够对文生图等模型添加限制信息&#xff08;边缘、深度图、法向量图、姿势点图等&#xff09;&#xff0c;在当今生成比较火的时代很流行。 这种方法使得能够直接提供空间信息控制图片以更细粒…