GIG

赴くままに技術を。

DBからモデルにデータを読み込ませる(後編)

チュートリアルを参考に、DBから読み込んで計算

前回作ったDBからデータを引っ張ってきて、計算させてみる。 ほぼチュートリアル(Transport9.java)が参考になりそう。接続させるところだけ修正して、動かしてみた。

他に参考になりそうなところでは、いったんMySQLからcsvファイルを作り、それを読み込むような手法もあった(databases:mysql [GAMS Interfaces Wiki])。 ということで下のコードはTransport9.javaほぼそのまま。

public class TransportFromDB {

    /**
     * Modelを組み立て実行する
     */
    public void runJob() {
        
        GAMSWorkspace ws = new GAMSWorkspace();
        
        // ひな形となるモデルを読み込み
        GAMSJob tp = ws.addJobFromFile("/PATH/TO/YOUR/Model.gms");
        
        GAMSDatabase gdb;
        try {
            // GAMSDatabaseをDBから読み込み、組み立て
            gdb = readDataFromDB(ws);

            // Optionを設定
            // ソルバとしてxpressを設定している
            GAMSOptions opt = ws.addOptions();
            opt.defines("gdxincname", gdb.getName());
            opt.setAllModelTypes("xpress");

            // 計算の実行
            tp.run(opt, gdb);
        
            // 計算結果の出力
            for (GAMSVariableRecord rec : tp.OutDB().getVariable("x")) {
            System.out.println("x(" + rec.getKeys()[0] + ","
                    + rec.getKeys()[1] +
                    "): level=" + rec.getLevel() +
                    " marginal=" + rec.getMarginal());
            }
        } catch (SQLException ex) {
            Logger.getLogger(TransportFromDB.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }

    /**
     * MySQLからデータを読み込む
     * @param ws GAMSWorkspace
     * @return 返却するGAMSDatabase
     * @throws SQLException 例外
     */
    private GAMSDatabase readDataFromDB(GAMSWorkspace ws) throws SQLException {
        // プロパティファイル(mysql.properties)からパラメータを読み取り
        ResourceBundle rb = ResourceBundle.getBundle("mysql");
        GAMSDatabase gdb = ws.addDatabase();
        
        // Connectionを取得
        Connection conn = DriverManager.getConnection(
                    rb.getString("DB_URL"),
                    rb.getString("DB_USER"),
                    rb.getString("DB_PASSWORD")
        );
        
        // read GAMS sets
        readSet(conn, gdb, "SELECT Plant FROM Plant", "i", 1, "canning plants");
        readSet(conn, gdb, "SELECT Market FROM Market", "j", 1, "markets");
        
        // read GAMS parameters
        readParameter(conn, gdb, "SELECT Plant, Capacity FROM Plant", "a", 1, "capacity of plant i in cases");
        readParameter(conn, gdb, "SELECT Market, Demand FROM Market", "b", 1, "demand at market j in cases");
        readParameter(conn, gdb, "SELECT Plant,Market,Distance FROM Distance", "d", 2, "distance in thousands of miles");

        return gdb;
    }
    
    /**
     * Set項目をDBから取得してGAMSDatabaseに設定する
     * @param conn DBコネクション
     * @param gdb GAMSDatabase
     * @param query SQLクエリ
     * @param setName Set名
     * @param setDimension Setの取り得る次元
     * @param setDescription Setの説明
     * @throws SQLException 例外
     */
    private void readSet(Connection conn, GAMSDatabase gdb, String query, String setName, int setDimension, String setDescription) throws SQLException {
        
        Statement st = conn.createStatement();
        
        ResultSet rs = st.executeQuery(query);
        ResultSetMetaData meta = rs.getMetaData();
        
        if (meta.getColumnCount() != setDimension) {
            System.err.println("ERROR!");
            conn.close();
            System.exit(-1);
        }
        
        GAMSSet set = gdb.addSet(setName, setDimension, setDescription);
        String[] keys = new String[setDimension];
        
        while (rs.next()) {
            for (int i=0; i<setDimension; i++) {
                keys[i] = rs.getString(i+1);
            }
            set.addRecord(keys);
        }
        st.close();   
    }

    /**
     * Parameter項目をDBから取得してGAMSDabaseに設定する
     * @param conn DBコネクション
     * @param gdb GAMSDatabase
     * @param query SQLクエリ
     * @param paramName Parameter名
     * @param paramDim Parameterの取り得る次元
     * @param paramDesciption Parameterの説明
     * @throws SQLException 例外
     */
    private void readParameter(Connection conn, GAMSDatabase gdb, String query, String paramName, int paramDim, String paramDesciption) throws SQLException {
        
        Statement st = conn.createStatement();
        
        ResultSet rs = st.executeQuery(query);
        ResultSetMetaData meta = rs.getMetaData();

        int numberOfColumns = meta.getColumnCount();
        if (numberOfColumns != (paramDim + 1)) {
            System.err.println("ERROR!");
            conn.close();
            System.exit(-1);
        }
        
        GAMSParameter parameter = gdb.addParameter(paramName, paramDim, paramDesciption);
        String[] keys = new String[paramDim];
        
        while(rs.next()) {
            for(int i=0; i<paramDim; i++) {
                keys[i] = rs.getString(i+1);
            }
            parameter.addRecord(keys).setValue(Double.valueOf(rs.getString(numberOfColumns)));
        }
        st.close();
    }
}

コードの中で読んでいるmysql.propertiesは下記で、src/main/resourcesに置いておく。

# DBのプロパティ
DB_URL=jdbc:mysql://localhost:3306/transport
DB_USER=work
DB_PASSWORD=(workに対して設定したもの)

以前書いた最適化計算をGAMSで行う - GIGでは、Netbeans上から動作を確認したが、Netbeans8に上げてからか、システムの環境変数を拾ってくれない。 どこかで調べなくては。

結局コマンドライン上から動かして見たが、次に「おや?」と思ったのが、結果が以前の同じモデルの計算と異なる(ダメじゃん...)。

*.lstファイルを比較してみたけど、展開した式は同じ...。これも調べなくては。

モデルをひっぺがす

上記の場合、当然SQL分をべた書きしている箇所などは外から読ませほうがいい。 Set, Parameterなどデータの型や次元は、今データベースにいれていない。これらはモデルのメタデータというくくりで、実際のデータと分けた方が良いかもしれない。

例えば、以下のmodel.jsonをModelを登録時に作成するなど(そういえば結果の格納は何にも考えていなかった...)

{
    "Model" : {
        "name" : "transport"
    },
    "Data" : {
        "i" : {
            "type" : "set",
            "dim" : "1"
        },
        "j" : {
            "type" : "set",
            "dim" : "1"
        },
        "a" : {
            "type" : "paramer",
            "dim" : "1"
        },
        "b" : {
            "type" : "paramer",
            "dim" : "1"
        },
        "d" : {
            "type" : "paramer",
            "dim" : "2"
        }
    },
    "Results" : ["x.l", "x.m"]
}

まだまだ考えがまとまっていない。。