1

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

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

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

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

替代的想法是:

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

感谢您的任何意见和建议!

4

3 回答 3

3

。垫

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

数据库

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

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

风俗

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

于 2012-06-12T16:49:23.003 回答
2

我也尝试了这两种选择。

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

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

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

于 2012-06-12T22:02:19.817 回答
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 中)需要分配搜索和测试。即使不完美,我希望如果有人想做类似的事情,下面的代码片段可能会有用。

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

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

sqlite3 数据库名称.db < 数据库名称结构.sql

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

开始; 创建表 Table1Name (Column1Name real, Column2Name real, Column2Name real); 创建表 Table2Name (Column1Name real, Column2Name real, Column2Name real); 犯罪;

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

sqlite3 数据库名称.db < uploadCsvToDatabaseName.sql

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

。分隔器 ”,”

.import Table1Data.csv 表1名称

.import Table2Data.csv 表2名称

。出口

为此,您需要在系统路径中包含 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 在其 Database Toolbox (cursor.fetch)文档中提供了如何执行此操作的建议。但是,使用 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

于 2012-12-19T20:53:28.690 回答