【问题标题】:Store data snapshots from an agent-based Matlab simulation存储来自基于代理的 Matlab 模拟的数据快照
【发布时间】:2012-06-15 13:55:03
【问题描述】:

我正在 MATLAB 中编写一个基于代理的模拟,其中代理轮流玩游戏,互相卖东西。几乎所有东西(代理、项目、位置、合同...)都是使用 MATLAB 的 OOP 功能作为对象实现的。

每一轮我都想为我的模拟拍摄快照并将其存储在磁盘上,以便我以后可以分析模拟是如何发展的。现在我的问题是最好的方法是什么?

我目前的想法是主循环调用每个代理并要求它报告其状态(例如,您拥有多少财产,您的合同义务是什么,账户余额。 .. 如有必要,代理会调用他们拥有的对象并询问其状态并将该信息包含在他们的报告中)。我的想法是使 agents 报告成为一个字符串,可能是 XML 格式。然后将所有报告与时间戳一起添加并添加到文本文件的末尾

但由于我从未做过这样的事情,我不确定这是否是一个好方法。 我主要关心除了让数据采用我以后可以轻松分析的格式之外,还有创建快照并将其写入磁盘的速度。由于我的模拟相当大,我希望每一轮都存储大量数据。

其他想法是:

  1. 将所有内容存储在数据库中。但我认为与文本文件相比,数据库访问速度相当慢。而且由于每个代理拥有的对象数量可能会发生变化,因此我也不太确定数据库结构。
  2. 使用 .mat 文件。但我不知道它们是否易于扩展,以及它们将如何应对不断变化的结构(即每轮拥有不同物品的代理)

感谢任何 cmets 和建议!

【问题讨论】:

    标签: oop matlab logging simulation agent-based-modeling


    【解决方案1】:

    在这个答案中,我总结了我最终实施的解决方案。 它是专门为解决我的特定问题而设置的,所以我建议 您还可以查看 Luca Geretti 和 bdecaf 的答案 替代选项。

    我实现了什么

    我选择 SQLite 作为数据库,因为它将数据存储在一个文件中并且易于设置和处理。 (使用 sqlite3 和 sqlite-jdbc-3.7.2 驱动程序不需要安装太多,数据库都在一个简单的文件中。)

    Matlab 数据库工具箱被证明太慢了,无法导出在我的模拟过程中从 Matlab 中创建的大量数据。因此,我编写了一个“DataOutputManager”类将数据快照转储到 csv 文件中

    在模拟之后,DataOutputManager 创建两个批处理文件和两个带有 SQL 命令的 sql 文本文件并执行批处理文件。第一个批处理文件通过运行 sqlite3.exe (www.sqlite.org) 并在第一个文本文件中为其提供 SQL 命令来创建 SQLite 数据库。

    创建数据库后,sqlite3 被告知使用第二个批处理和文本文件将数据从 csv 文件导入数据库。这不是一个“漂亮”的解决方案,但将数据写入 csv 然后使用 sqlite3 将这些文件导入数据库比使用 Database Toolbox 快得多。 (听说有人用xml文件)

    模拟数据上传到数据库后我使用Database Toolbox和一个jdbc驱动程序(sqlite-jdbc-3.7.2)向数据库发送SQL查询。由于这些查询只返回很少的数据,因此 Database Toolbox 不是瓶颈。

    设置所有这些(在 Windows 7 中)需要分配搜索和测试。尽管不完美,但如果有人想做类似的事情,我希望以下 sn-ps 可能有用。

    创建 SQLite 数据库并将数据从 csv 导入数据库:

    使用 sqlite3 创建数据库的第一个 .bat 文件结构如下:

    sqlite3 数据库名称.db

    第一个文本文件 (.sql) 称为 DatabaseNameStructure.sql,其结构如下:

    开始;创建表 Table1Name (Column1Name real, Column2Name real, Column2Name real);创建表 Table2Name (Column1Name real, Column2Name real, Column2Name real);提交;

    让 sqlite3 将 csv 文件上传到数据库的第二个 .bat 文件的结构如下:

    sqlite3 DatabaseName.db

    第二个文本文件 (.sql) 称为 uploadCsvToDatabaseName.sql,其结构如下:

    .分隔符","

    .import Table1Data.csv Table1Name

    .import Table2Data.csv Table2Name

    .退出

    为此,您需要在系统路径中包含 sqlite3.exe,例如保存在 C:\Windows\System32 下。您根据数据/csv 设置在 Matlab 中创建字符串,然后使用 fprintf() 将它们写入上述格式的文件中。然后使用 winopen() 从 Matlab 执行 bat 文件。

    使用 jdbc 驱动将 Matlab 连接到 SQLite 数据库:

    Bryan Downing 的以下视频对我的开发很有帮助:http://www.youtube.com/watch?v=5QNyOe79l-s

    我创建了一个 Matlab 类(“DataAnalyser”),它连接到数据库并对模拟结果运行所有分析。这是用于设置与数据库通信的类构造函数和连接函数。 (我删掉了我实现中不那么重要的部分)


     function Analyser=DataAnalyser()
    
            % add SQLite JDBC Driver to java path
            Analyser.JdbcDriverFileName='sqlite-jdbc-3.7.2.jar';
    
            % Ask User for Driver Path
            [Analyser.JdbcDriverFileName, Analyser.JdbcDriverFilePath] = uigetfile( {'*.jar','*.jar'},['Select the JDBC Driver file (',Analyser.JdbcDriverFileName,')']);
            Analyser.JdbcDriverFilePath=[Analyser.JdbcDriverFilePath,Analyser.JdbcDriverFileName];
    
            JavaDynamicPath=javaclasspath('-dynamic'); % read everything from the dynamic path
            if~any(strcmp(Analyser.JdbcDriverFilePath,JavaDynamicPath))           
                disp(['Adding Path of ',Analyser.JdbcDriverFileName,' to java dynamic class path'])
                javaaddpath(Analyser.JdbcDriverFilePath);
            else
                disp(['Path of ',Analyser.JdbcDriverFileName,' is already part of the java dynamic class and does not need to be added']);
            end
    
    
            Analyser.JdbcDriver='org.sqlite.JDBC';
            % Ask User for Database File
            [Analyser.DbFileName, Analyser.DbFilePath] = uigetfile( '*.db','Select the SQLite DataBase File ');
            Analyser.DbFilePath=[Analyser.DbFilePath,Analyser.DbFileName];
    
            Analyser.DbURL=sprintf('jdbc:sqlite:%s',Analyser.DbFilePath);
            % Set Timeout of trying to connect with Database to 5 seconds
            logintimeout(Analyser.JdbcDriver,5);               
        end
    

        function [conn,isConnected]=connect(Analyser)
            % Creates connection to database.            
            Analyser.Connection=database(Analyser.DbFilePath,'','',Analyser.JdbcDriver,Analyser.DbURL);
            conn=Analyser.Connection;
            isConnected=isconnection(Analyser.Connection);                  
        end
    

    从连接的 SQLite 数据库中获取数据到 Matlab 中

    我还为 DataAnalyser 编写了一个函数,该函数在给定 sql 查询时从数据库中获取数据。我在这里发布它的主要部分有两个原因。

    1. 不是一次导入所有数据,而是像此函数中那样分批导入数据,这样可以加快数据导入速度。

    2. Mathworks 在其数据库工具箱 (cursor.fetch) documentation 中提供了如何执行此操作的建议。但是,使用 jdbc 和 SQLite 会因为错误而导致错误。

    引用来自 Mathworks 支持:

    我们之前已经看到,如果您位于记录集的末尾,SQLite JDBC 驱动程序不允许查询有关记录集的某些元数据;但是根据 JDBC 规范,这应该是允许的。

    这个函数可以解决这个问题:


    function OutputData=getData(Analyser,SqlQuery,varargin)
            % getData(Analyser,SqlQuery)
            % getData(Analyser,SqlQuery, setdbprefsString)
            % getData(Analyser,SqlQuery,RowLimitPerImportCycle)
            % getData(Analyser,SqlQuery,RowLimitPerImportCycle,setdbprefsArg1String,setdbprefsArg2String)
            % getData(Analyser,SqlQuery,[],setdbprefsArg1String,setdbprefsArg2String)
            % 
            % RowLimitPerImportCycle sets the Limit on howmany Data rows 
            % are imported per cycle. 
            % Default is RowLimitPerImportCycle = 6000
            %
            % setdbprefsArg1String Default 'datareturnformat' 
            % setdbprefsArg2String Default 'numeric'
            % Hence setdbprefs('datareturnformat','numeric') is the Default
            %            
            % function is partially based on cursor.fetch Documentation for 
            % Matlab R2012b:
            % http://www.mathworks.de/de/help/database/ug/cursor.fetch.html
            % Example #6 as of 10.Oct.2012
            % The Mathworks' cursor.fetch Documentation mentioned above had
            % some errors. These errors were (among other changes)
            % corrected and a bug report was send to Mathworks on 10.Oct.2012
    
            if isempty(Analyser.Connection)                
                disp('No open connection to Database found.')
                disp(['Trying to connect to: ',Analyser.DbFileName])
                Analyser.connect
            end
    
            % Get Setting
            if nargin>2
                RowLimitPerImportCycle=varargin{1};
            else
                RowLimitPerImportCycle=[];
            end
            if ~isnumeric(RowLimitPerImportCycle) || isempty(RowLimitPerImportCycle)
                %Default
                RowLimitPerImportCycle=5000;
            end
    
            if nargin>4
                setdbprefsArg1String=varargin{2};
                setdbprefsArg2String=varargin{3};
            else
                setdbprefsArg1String='';
                setdbprefsArg2String='';
            end
            if ischar(setdbprefsArg1String) && ~isempty(setdbprefsArg1String) && ischar(setdbprefsArg2String) && ~isempty(setdbprefsArg2String)
                setdbprefs(setdbprefsArg1String,setdbprefsArg2String)
            else
                %Default
                setdbprefs('datareturnformat','numeric');
            end
    
    
    
            % get Curser
            curs=exec(Analyser.Connection,SqlQuery);
            if ~isempty(curs.Message)
                warning('Model:SQLMessageGetData',[curs.Message, '/n while executing SqlQuery: ',SqlQuery])
            end
    
            % import Data
            FirstRow = 1;
            LastRow = RowLimitPerImportCycle;
            firstLoop=true;
    
            while true
    
                curs = fetch(curs,RowLimitPerImportCycle);
                if rows(curs)==0
                    if firstLoop == true
                        OutputData=[];
                    end
                    break
                end
    
                AuxData = curs.Data;
                numImportedRows = size(AuxData,1);
                if numImportedRows < RowLimitPerImportCycle
                    OutputData(FirstRow:LastRow-(RowLimitPerImportCycle-numImportedRows), :) = AuxData;
                else
                    OutputData(FirstRow:LastRow, :) = AuxData;
                end
    
                FirstRow = FirstRow + RowLimitPerImportCycle;
                LastRow = LastRow + RowLimitPerImportCycle;
                firstLoop=false;
    
                if rows(curs)<RowLimitPerImportCycle
                    break
                end
            end
    
        end
    

    【讨论】:

      【解决方案2】:

      我也尝试了这两个选项。

      除非您只有很少的数据,否则我不鼓励保存单独的 mat 文件。想出唯一的名称然后收集它们是相当麻烦的。并行化计算时不要理会,同时访问文件时可能会遇到问题。

      对于数据库,我喜欢 matlab 中 MySql 服务器和mym 命令的组合。服务器,因此您可以同时从多个进程访问(对于并行化至关重要)。 Mym 因为它允许将 matlab 对象直接写入数据库中的 blob 字段 - 节省了一些重写。

      正如上面提到的 SQLite - 我一直在修改它。但是比较生气。您必须自己序列化对象。让多个进程访问数据库也是有问题的。

      【讨论】:

        【解决方案3】:

        .mat

        由于您的目标是 Matlab,我将从 .mat 文件开始。如果您需要在某个时间将数据重新加载到 Matlab 中,那么与 XML 存储相比,这是一个更可取的解决方案。你应该只用单元阵列来表达你的快照。您不必担心结构的变化:例如,如果代理每轮拥有不同的项目,则每轮的项目可以存储为另一个(嵌套)元胞数组。

        数据库

        如果不再从 Matlab 读取快照,请考虑使用 SQL 接口。这允许您扩展持久层的性能。您可以从使用 SQLite 开始,然后,如果您发现在某些指标下需要更好的性能,则转向更“严肃”的 DBMS。

        关于您对数据库结构的疑问,您的快照肯定有一个结构:我认为快照中的任何可变内容都不会通过适当的数据库应用程序设计来管理。

        自定义

        如果您确实处于 I/O 密集型场景并且您最终只需要附加数据,那么专用解决方案是一项合理的投资。你失去了一些灵活性,你可能会后悔,但是,嘿,你想要最好的! 不过,我建议不要跳上 XML 船:它不是最紧凑的解决方案,因此您可能会遇到非常大的数据集的问题。如果不设计自己的格式,我宁愿使用 JSON:它紧凑、通用,并且可能有一些库可以帮助您在 Matlab 中解析它。等等不,实际上there are

        【讨论】:

        • 感谢您的建议,我已经制定了一个数据结构,现在将使用 MySQL DB(如 bdecaf 建议的那样)和 Matlab 数据库工具箱
        • 不客气。对于未来的读者,提及数据库工具箱也很好。
        • 我刚刚在下面添加了部分最终实现。它非常适合我的设置并且可能更漂亮 - 但它可能对尝试使用 SQLite 和 Matlab 的一些人有所帮助。
        • @Jacob 最好将您的答案整合到您的问题中,或者不接受我的答案并接受您的答案。否则感兴趣的读者可能会错过您提供的信息。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-08-17
        • 1970-01-01
        • 2017-06-29
        • 1970-01-01
        • 2021-04-23
        • 1970-01-01
        • 2023-03-22
        相关资源
        最近更新 更多