存储来自基于代理的Matlab仿真的数据快照

时间:2012-06-12 16:27:40

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

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

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

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

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

其他想法是:

  1. 将所有内容存储在数据库中。但我认为与文本文件相比,数据库访问速度相当慢。由于每个代理拥有的对象数量都可以改变,我对数据库结构也不太了解。
  2. 使用 .mat文件。但我不知道它们是否易于扩展,以及它们如何应对不断变化的结构(即每轮拥有不同物品的代理商)。
  3. 感谢您的任何意见和建议!

3 个答案:

答案 0 :(得分:3)

<强> .MAT

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

<强>数据库

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

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

自定义

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

答案 1 :(得分:2)

我也尝试了两种选择。

除非您只有很少的数据,否则我会阻止保存单独的mat文件。想出独特的名字然后收集它们是相当麻烦的。在并行计算时不管你在同时访问文件时可能会遇到问题。

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

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

答案 2 :(得分: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将这些文件导入数据库比使用数据库工具箱快得多。 (我听说有些人使用xml文件)

将模拟数据上传到数据库后,我将数据库工具箱与jdbc驱动程序(sqlite-jdbc-3.7.2)一起使用,以将SQL查询发送到数据库。由于这些查询只返回了很少的数据,因此数据库工具箱不是瓶颈。

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

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

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

  

sqlite3 DatabaseName.db&lt; DatabaseNameStructure.sql

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

  

开始; create table Table1Name(Column1Name real,Column2Name real,Column2Name real); create table Table2Name(Column1Name real,Column2Name real,Column2Name real);提交;

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

  

sqlite3 DatabaseName.db&lt; uploadCsvToDatabaseName.sql

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

.separator“,”

.import Table1Data.csv Table1Name

.import Table2Data.csv Table2Name

.exit

为此,您需要在系统路径中安装sqlite3.exe,例如保存在C:\ Windows \ System32下。您可以根据data / csv设置在Matlab中创建stings,然后使用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会因为错误而导致错误。

  3. 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