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"]
}

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

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

以前最適化計算をGAMSで行う - GIGの続き。 モデルと使うデータを外から読み込ませて、計算させたいということで、まずはDBに計算するデータを入れてみる。

今回は使うものはこちら。

ということで、MySQLをインストール(インストール方法は割愛)したら早速Workbenchでデータベースを作っていく。

Connectionの作成

接続できるかまずはConnectionを作成する。

f:id:hermesian:20140621133502p:plain

データベースを作成

  • “Local"コネクション画面で、上メニューのf:id:hermesian:20140621133503p:plainリポジトリに”+”(プラス)マークがついたアイコンをクリックする。

  • 以下のプロパティを設定する

    • Schema Name: transport
    • Default Collation; utf8 - default collation

データベースの利用ユーザを作成

f:id:hermesian:20140621133504p:plain

再び”Local"コネクション画面で、左ペインの[Users and Privileges]を選択して、作業ユーザを作成する

  • [Login]タブで
    • “Login Name” ; work
    • “Limit Connectivity to Hosts Matching” ; localhost
    • “Password / Confirm Password” ; (適当なパスワード)
  • [Schema Privileges]タブで
    • [Add Entry]をクリックして、[Selected schema]から対象のデータベースを選択する(今回なら"transport")
    • その後[Select “ALL”]でtransportデータベースに対して、全操作を許可
  • 最後に[OK] > [Apply]ボタンをクリック

ER図を作成

先ほど作成した”tranport”データベース上にテーブルをER図を用いて、作成していく。

f:id:hermesian:20140621133505p:plain

ホーム画面の“Models”の横3つ目のマークを選択し、[Create EER Model from Database]を選択し、画面に従ってER図作成画面に進む。

参考にしたのは、$GAMS_HOME/gams24.2_osx_x64_64_sfx/apifiles/Data/transport.xls。

今回は、CapacityとDemandの両地域の総当たり表なので、中間テーブルとして表現しようとしたが、モデルを登録する人からすると、外部キーの組み合わせを考えながら、登録するのはやってられないので、3つ別々のテーブルとした。

その後、[EER Diagrams] > [Add Diagram]で3つ下のようなテーブルを作ってみる。 オートインクリメントさせたい場合は、[AI]にチェックする(こうすると、後述のExcelシートではIDが必要ないが、IDをリセットさせる必要がある)

f:id:hermesian:20140621133508p:plain

f:id:hermesian:20140621133506p:plain

f:id:hermesian:20140621133507p:plain

ER図からテーブルを作成

それでは、作ったER図からテーブルを作成する。 [Database] > [Forward Engineer]を選択する。

f:id:hermesian:20140621133509p:plain

するとデータベースへの接続画面が表示されるので、必要に応じて設定し[Continue]ボタンをクリックする。

f:id:hermesian:20140621133510p:plain

データベースの作成時のオプションとしては、既に同じテーブルをDropするように”DROP Objects Before Each CREATE Object”にチェックし、[Continue]ボタンをクリックする。

f:id:hermesian:20140621133511p:plain

次の”Select Objects”は[Show Filter]で作りたいテーブルを絞ったりすることができるみたい。今回はそのまま作るので、[Continue]ボタンをクリックする。

それで出来たDDLは以下。

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';

DROP SCHEMA IF EXISTS `transport` ;
CREATE SCHEMA IF NOT EXISTS `transport` DEFAULT CHARACTER SET utf8 ;
USE `transport` ;

-- -----------------------------------------------------
-- Table `transport`.`MARKET`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `transport`.`MARKET` (
  `id` INT NOT NULL,
  `MARKET` VARCHAR(45) NOT NULL,
  `DEMAND` VARCHAR(45) NOT NULL,
  `DESCRIPTION` VARCHAR(45) NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `transport`.`PLANT`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `transport`.`PLANT` (
  `id` INT NOT NULL,
  `PLANT` VARCHAR(45) NOT NULL,
  `CAPACITY` VARCHAR(45) NOT NULL,
  `DESCRIPTION` VARCHAR(45) NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `transport`.`DISTANCE`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `transport`.`DISTANCE` (
  `PLANT` VARCHAR(45) NOT NULL,
  `MARKET` VARCHAR(45) NOT NULL,
  `DISTANCE` VARCHAR(45) NOT NULL)
ENGINE = InnoDB;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

最後にこのDDLが発行される。これでForward Engineer終了。 テーブルが作成されていることが確認できる。

ExcelデータをDBUnitを使って読み込み

DBUnitを使って、Excelからデータを読み込むようにしてみた。 読ませるデータは、Excel(xls形式)で各シートが一つのテーブルとして読み込まれる。

@Marketシート

f:id:hermesian:20140621140638p:plain

--2014.06.29 修正 -- "sandiego" -> "san-diego"

@Plantシート

f:id:hermesian:20140629202942p:plain

@Distanceシート

f:id:hermesian:20140621140640p:plain

注意したいのは、xlsx形式のファイルには対応しておらず、実行すると下記のようなエラーが発生する(内部で利用しているApache POIがxlsのAPIを利用しているため)。

Exception in thread "main" org.apache.poi.poifs.filesystem.OfficeXmlFileException: The supplied data appears to be in the Office 2007+ XML. You are calling the part of POI that deals with OLE2 Office Documents. You need to call a different part of POI to process this data (eg XSSF instead of HSSF)

まずはpom.xmlについて。

<!-- 省略 -->


    <!-- DBUnit -->
    <dependency>
        <groupId>org.dbunit</groupId>
        <artifactId>dbunit</artifactId>
        <version>2.5.0</version>
    </dependency>
    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.30</version>
    </dependency>
    <!-- Apache POI -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>3.2-FINAL</version>
    </dependency>


<!-- 省略 -->

DBUnitApache POIの互換性を考えないと行けなくて、安易にApache POIの最新版を選択すると、下記のような例外が実行時に発生する。

Exception in thread "main" java.lang.NoSuchMethodError: org.apache.poi.hssf.usermodel.HSSFDateUtil.isCellDateFormatted(Lorg/apache/poi/hssf/usermodel/HSSFCell;)Z

現行の3.10-FINALは無理だったので、3.2-FINALを使ってみたら大丈夫でした。(参照: 有効なWikiNameではありません - @//メモ)。

またDBのプロパティはpropertyファイルとして用意する。

@ mysql.properties

# DBのプロパティ
DB_URL=jdbc:mysql://localhost:3306/transport
DB_USER=work
DB_PASSWORD=(適当なぱすわぁど)

最後に実行クラスは以下。

gist610709ea8da4ec1b91e1

怪談狩り (中山 市郎)

怪談狩り 市朗百物語 (幽ブックス)

怪談狩り 市朗百物語 (幽ブックス)

いい加減読もうと思って、本日外出した際に購入し、今晩読了。 怪しいことが今宵、訪れますように。

個人的に好きなのは、スナック経営者や風俗嬢、お笑い芸人が会った怪談。 じっとりと重く、あと引く話が好きなんです。。

先月は東京公演が会ったみたいで、残念ながら参加できなかった。 次回はぜひとも参加したいな。

中山市朗ダークナイト

HTML5プロフェッショナル認定資格を取ってきた

本日受けてきた。予約した時間より30分早くついたら、 空いてるし、今から受ける?見たいな形ですぐに入れた。

HTML5 Professional Certification HTML5プロフェッショナル認定試験 | 多くの企業が推進する次世代Web言語の認定資格 | LPI-Japan

一応、どんな試験かご紹介しておくと、

  • LPI-Japanが提供するHTML5とその周辺技術(Javascript, CSS)に特化した試験
  • Level 1が公開中(Level2は現在策定中)
  • 15,000円
  • 65問 / 90分
  • 認定の有効性は、5年

結果、71点(合格ライン:70点)!あぶなっ! 途中、外で街宣車がうるさかったな...(という言い訳)

勉強方法

あくまで自分のしてきたことなので、試験対策にはならないかと。

施策 (1)

公式教科書があるみたいので、まずはそれから初めて見ました。 Kindle版を購入して、Nexus7で読んでみた。

HTML5プロフェッショナル認定資格 レベル1 教科書

HTML5プロフェッショナル認定資格 レベル1 教科書

読んでみて...

評価されていたほど、網羅率の低さは気にならなかったかな。 むしろこれを読んで、各内容の深堀が必須です。

誤字や構成ミス?

  • 誤字
    • rt(Ruby Parentheses) -> rp(Ruby Parentheses)
    • @kayframes -> @keyframes
  • Kindleで見たときに、画面で記述のダブりが何カ所か出てた(特に表で)

施策 (2)

他の人がどんな試験対策しているのかなーと思って、以下のブログを拝見すると O'Reilly本で参考になるのがあるみたい。

タイトルとか決めてないけどこのままでもいいかもしんない: HTML5プロフェッショナル認定試験を受験してみた

HTML5クックブック

HTML5クックブック

一通りは読んでみた。先に紹介した公式教科書のHTML5パートを深堀したないようです(試験対象ではない位置情報なども参考になった)

施策 (3)

公式ページにサンプル問題が掲載されています。 出題される問題の形式としては、これが一番正確でした(<- そりゃそうか)

サンプル問題/例題解説 | HTML5 Professional Certification HTML5プロフェッショナル認定試験 | 多くの企業が推進する次世代Web言語の認定資格 | LPI-Japan

これをしておけば良かった

実際に自分で作るなど、手を動かすこと。 当たり前だけど、使って覚えた方が早いし、後に残る。

何より出題される問題のいつかで、与えられたHTML, CSS, Javascriptではどのような見え方が妥当であるかという問題もあった。

これから

秋にはLevel2が出てくることだし、一層使っていないと合格は難しそう。 テーマを決めて、なんか作ってみよ。

そういえばJava Day Tokyo 2014行ったんだった

出張先に「品川プリンスホテル」と書く後ろめたさたるや昨年と同じUDXで開催してくれたら良かったのに。会場めちゃくちゃ広かった…

f:id:hermesian:20140522100254j:plain

聞いた講演はこれ。 最後までいるつもりだったけど、仕事がスタックしてそうな予感がしたのと、 Lambda, Streamを触らず来てしまった場違い感から、Java SE8とJava EE 8の話だけ聞いておこうと。

  • [10:00 - 12:00] Keynote (Nandini Ramani / Cameron Purdy)
  • [13:30 - 14:20] Java SE 8概要 (Simon Ritter)
  • [14:35 - 15:25] JavaEE.Next():Java EE 7, 8, and Beyond (Reza Rahman)

発表資料一覧

http://www.oracle.com/us/dm/seo100304328-jp-jp-lw-ipi1-ev-2196625-ja.html

Keynote

話題はこらへん

  • Java SE 8リリース
  • IoT

Java Strategy Keynote (Nandini Ramani)

Javaの現状と未来について

  • Vice President, Java & IoT
  • 2000+ Registrants
  • Glassfishについては特に言及なし(keynoteにあるけど)
  • Javaプラットフォームの統合がミッション
    • APIが多様化してきた
  • Java ME8の言語としては、Java SE 8の部分集合 (APIは部分的に重なる)

IoTについて

  • データの増加
    • SNSの大量データ
    • 7.6 Billionの人口に対して50 Billionのデバイスがインターネットにつながっている
    • 2015 8000 EBが見込まれる
  • JavaはIoTにおいてもオープンなプラットフォームであり続ける
  • 小さなものから大きなものまで
  • SOCベンダと協力し、様々なデバイスも対応できる
    • Raspberry Pi
    • ARM
    • gemalto
    • freescale
    • ST
    • Qualcomm

PaPeRo (Shin Ishiguro, NEC)

  • コミュニケーションロボット(PaPeRo)
  • IoTのケーススタディ
  • クラウド(考える(集める・調べる))とロボットのセンシング(見る・感じる, 聞く・話す)

電子マネー決済端末 (Masaya Abuta, Panasonic)

  • Java ME Embeddedのショーケース
  • 電子マネー決済端末
    • 地味に聞こえるけど、業界的には増加傾向
    • 一つの端末に複数の電子マネーをのせる状況
    • ポイント等の販促サービスについてもサービスで必要
    • 300k stepの1/3がJava
    • 開発効率やセキュリティ面でJavaの利用が継続される

Java Strategy Keynote (Cameron Purdy, Senior Vice President, Product Development)

Java EEのロードマップについて説明

  • グーグルでの利用率も高い
  • Release June 2013
  • Major Field
    • HTML5 (WebSokets, JSON)
    • Easier Development
    • Enterprise (Batch, JMS)
  • Deliver More Apps to More Devices with Confidence
  • Positioning for the Future
    • Java EE 7 ; Develop Apps with HTML5
    • Java EE8 ; Cloud上でのアプリに即してセキュリティの強化

Java & Embedded Demonstration (Stephen Chin (Java Technology Ambassador JavaOne Content Chair)

f:id:hermesian:20140522111838j:plain

Java & Community (Simon Ritter, Head of Technology Evangelism)

  • Jav)One (September 28th - October 2nd)の開催通知。
  • 日本Java ユーザグループの紹介 (Yusuke Suzuki, Chairman JJUG)
    • JJUG(じぇーじゃぐと読むのは初めて知った) CCC 2014 Spring
      • 23セッション + 2ハンズオン
      • ユーザ主体の勉強会
      • 次回は秋
    • ナイトセミナー
      • 毎月第4水曜日19:00-21:00

Java SE 8概要(Simon Ritter, Head of Java Techonology Evangelism)

  • 何が新しいのか?
    • 3つのNew JSR
    • Update一杯
    • ガッツリ改良された

言語レベル

Core Library

パフォーマンスの向上 ベストプラクティスの取り込み

  • Concurency Update
    • 要はスレッドの性能を上げたというお話
  • Bulk Data Operation For Collections
    • java.util.stream
    • ストリームデータの処理を行える仕組みがでてきた
    • forループの入れ子でプログラムが汚くならない
    • Lambda記法で集計処理かいてなど柔軟な記述ができそう
  • Parallel Array Sorting
  • Data and Time API
    • new date, time API
  • JDBC 4.2
    • 特に目立った改修点はないとのこと。
  • Base64 Encoding and Decoding
    • 画像データの処理で利用?

Platform

  • Compact Profiles
    • Project Jigsaw(jarより便利な利用形態(モジュール)を作るプロジェクト)への布石

VM

  • Nashorn Javascript Engine
    • 既存の資産を流用しやすくするため、JavaJavascriptを処理できる仕組み

JavaEE.next() Java EE 7, 8, Beyond (Reza Rahman, Java EE/GlassFish Evangelist)

Java EE 7おさらい

  • 割愛
  • JavaEEでBatch作ったことなかったので、やってみよう

Java EE 8 ?

次はこの辺らしい

JSON Binding Security Simplication JCache Action-oriented Web framework/HTML 5 alignment (Struts 2みたい。JSFとは競合しない??)

NoSQLの取り回しの標準化があるかと思ったが、NoSQL自体の普及率の低さからまだやらないみたい。 やるなら、JPAベース。

なお、GlassFishは4.0.1からJava SE 8対応とのこと。 http://www.slideshare.net/iwasakihirofumi/future-of-java-ee-with-java-se-8 -> GlassFishまだ大丈夫?

Java Day Tokyo 2014 Togetter

http://togetter.com/li/670503

最適化計算をGAMSで行う

この分野について全くのド素人だけど、仕事で使いそうなので備忘録がてら書いていく。

この分野について

オペレーションズ・リサーチ(OR)は名前の通り、第2次世界対戦中に確立されたもので、あるシステムを管理する人に対して、適切な解を求めることを目的とする。最適化計算(数理計画法とも呼ばれる)は、ORの一つという位置づけである。

対象となる研究対象は、第6回 オペレーションズ・リサーチ(OR)---数学モデルを駆使して,経営戦略を立案する:ITproに分かりやすく概要が紹介されている。最適化計算の代表的な例題として、「巡回セールスマン問題」がある。これは複数の都市があった場合、どのような経路でセールスマンが巡回すれば、移動コストを最小化できるのかという解を求めるものである。

こういった問題をどのようにモデル化するか、どのようにそのモデルを解くかというのがこの分野の取り組む課題となる。

GAMSについて

先の最適化問題を解くにあたり、多次元配列をごりごりと計算することが必要になってくる。 幸いにして、いくつか最適化問題に特化したソフトウェアがある。

Mathematicaはもしかしたら大学等で利用していた人もいるかもしれない。競争力のない(金のない)研究室出身の私に取っては、うらやましいかぎり。

GAMS(General Algebraic Modeling System)は、Mathematicaと同様にモデリング言語と、その実行環境を提供するもの。しかし、個人的に利用する分であればライセンスは必要なさそう(扱える配列やソルバーといった機能制限があるみたいだが)。

日本語だとあまり情報見かけないので、 これから参考にしそうな情報のリンクを張っておく。。

GAMSのコードの構造

ユーザマニュアルのP.29にコードの構造が掲載されている。

GAMSのコードは、Data, Model, Solutionの記述に大別できる。 Dataは最適化計算で利用されるパラメータや定数である。Modelは最適化で良く目にする制約条件や他の計算式、最小化(または最大化)の対象式である目的関数が含まれる。最後のSolutionはModelをどのソルバで解くかと、何を結果として出力するかを定義する。

マニュアルに沿って、GAMSの構成要素を解説してみる。 おおよそのGAMSコードの構成は以下のようになっている。

  • Set(s) : 配列の添字を定義するブロック識別子
  • Data : Prameter(s), Table(s), Scalar(s)といったブロック識別子をまとめた概念で、要は入力値
  • Variable(s) : 最適化する際に選択肢となる変数(決定変数または内生変数)を定義するブロック識別子
  • Equation(s) : 数式や不等式を定義するブロック識別子。何を最小化(または最大化)するかを評価する式(目的関数)も含まれる
  • Model : どの式をモデルに含めるかを定義するブロック識別子
  • Solve : どのモデルを解くかと、どう解くか(線形計画法(lp)か、非線形計画法(nlp)か)および解くべき目的関数の識別名、最後に最大化するか、最小化するかを定義するブロック識別子

チュートリアルに掲載されている「輸送問題」を見てみる。

  • 農場(供給側)としてSeattle, San Diegoの2拠点がある
  • 市場(需要側)としてNew York, Chicago, Topekaの3拠点がある
  • 各農場、各市場間の距離がd(i, j)と決まっている
  • 供給量、需要量にそれぞれ制限が制約条件として課せられている
  • このとき、移動コストを最小限にするには、それぞれの農場からどこの市場に対してどの程度出荷すれば良いかを求める

ちなみに$titleは、モデルのタイトルを記述できる識別子。$ontext … $offtextは間に挟んだ文書がコメント扱いされる識別を表す。とりあえず、今はざっくり構成を把握して、詳細は別途調べとく。

$title Transport.gms
$ontext
This is tutorial model.
$offtext

Sets
         i canning plants / seattle, san-diego /
         j markets        / new-york, chicago, topeka /;

Parameters

         a(i) capacity of plant i in cases
           / seattle   350
             san-diego 600 /

         b(j) demand at market j in cases
           / new-york  325
             chicago   300
             topeka    275 /;

Table d(i,j) distance in thousands of miles
                    new-york chicago topeka
         seattle      2.5      1.7    1.8
         san-diego    2.5      1.8    1.4 ;

Scalar f freight in dollars per case per thousand miles /90/ ;

Parameter c(i, j) transport cost in thousands of dollars per case ;
          c(i, j) = f * d(i, j) / 1000 ;

Variables
         x(i, j) shipment quantities in cases
         z       total transportation costs in thousands of dollars ;

Positive Variable x;

Equations
         cost define objective function
         supply(i) observe supply limit at plant i
         demand(j) satisfy demand at market j ;

cost.. z =e= sum((i, j), c(i, j)*x(i, j)) ;
supply(i).. sum(j, x(i, j)) =l= a(i) ;
demand(j).. sum(i, x(i, j)) =g= b(j) ;

Model transport /all/ ;

Solve transport using lp minimizing z ;

Display x.l, x.m ;

GAMSのAPIを利用する

GAMSのAPIとして、Java, Phython, .NETが用意されている(C, C++など他もあるけどドキュメントが足りなそう)。.NETはドキュメントがPDFで提供されていないっぽい。

Javaを使って、上の問題を解くサンプルを動かしてみよう。 GAMSフォルダに移動すると、apifilesというディレクトリがある。 その配下に各言語向けのAPIやそれを利用する実装例が格納されている。

JavaAPIは[apifiles] > [Java] > [api]配下に格納されている。 また、上の輸送問題(Transport.gmsとする)を解くサンプルとして、[api]と同じ階層に[transport]があり、様々な実装例が紹介されている(それらを補足する説明がチュートリアルとして用意されている)。

それでは、pom.xmlの設定から始める。 GAMSに関係するところだけ。

簡単な輸送問題を解くクラスを作成

     <!— GAMSのインストールパスを設定 —>
    <properties>
        <gams.path>/Applications/GAMS/gams24.2_osx_x64_64_sfx</gams.path>
    </properties>

    <!-- GAMSJavaAPI.jar —>
    <dependencies>
    <dependency>
            <groupId>com.gams.api</groupId>
            <artifactId>GAMSJavaAPI</artifactId>
            <version>1.0</version>
            <scope>system</scope>
            <systemPath>${gams.path}/apifiles/Java/api/GAMSJavaAPI.jar</systemPath>
        </dependency>
</dependencies>

<build>
        <plugins>
            <!-- DLLを利用するために必要 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <workingDirectory>target</workingDirectory>
                    <argLine>-Djava.library.path=${gams.path}/apifiles/Java/api</argLine>
                </configuration>
            </plugin>
       </plugins>
</build>

上の輸送問題のモデル(transport.gms)を読み込んで、解くクラスは以下のようになる。

public class Transport {

     /**
     * Resource Bundle .
     */
    private static final ResourceBundle resource = ResourceBundle.getBundle("GAMSSetting");

    /**
     * GAMSジョブを実行するメソッド.
     */
    public void runJob() {

        System.out.println("check:" + resource.getString("WORKINGDIR"));
        System.out.println("check:" + resource.getString("GAMSDIR"));

        // ユーザの引数を与えて、GAMSWorkspaceインスタンスを生成する
        // 引数1 : Workingディレクトリを指定 (デフォルトだと, System.getProperty("java.io.tmpdir")で取得されたパス
        // 引数2 : GAMSのインストールディレクトリを指定 (デフォルトだと、環境変数PATHを見る)
        // 引数3 ; デバッグレベルを指定 (デフォルトだと、 OFF)
        GAMSWorkspace ws = new GAMSWorkspace(
                resource.getString("WORKINGDIR"),
                resource.getString("GAMSDIR"),
                GAMSGlobals.DebugLevel.OFF
        );
        // GAMSのモデルライブラリからtrnsportモデル(輸送モデル)を指定し、GAMSJobインスタンスを生成
        // TODO フルパスじゃないとダメみたい
        GAMSJob t1 = ws.addJobFromFile(resource.getString("INPUTGAMSFILE"));

        // t1ジョブを実行する
        t1.run();

        // ジョブの結果が格納されたデータベース(メモリ上?)からxの結果を取得
        System.out.println("Ran with Default:");
        GAMSVariable x = t1.OutDB().getVariable("x");
        for (GAMSVariableRecord rec :  x) {
            System.out.print("x(" + rec.getKeys()[0] + ", " + rec.getKeys()[1] + "):");
            System.out.print(", level    = " + rec.getLevel());
            System.out.println(", marginal = " + rec.getMarginal());
        }

        // Workディレクトリをクリアする
        // cleanup(ws.workingDirectory());
    }

    /**
     * 引数で指定されたWorkディレクトリ内のファイルを削除するメソッド.
     * @param directory
     */
    static void cleanup(String directory)  {
        File directoryToDelete = new File(directory);
        String files[] = directoryToDelete.list();
        for (String file : files) {
            File fileToDelete = new File(directoryToDelete, file);
            try {
                 fileToDelete.delete();
            } catch(Exception e){
                 e.printStackTrace();
            }
        }
        try {
           directoryToDelete.delete();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

ResourceBundleで読んでいるプロパティファイルは以下。

# GAMSの設定関連のプロパティファイル

GAMSDIR=/Applications/GAMS/gams24.2_osx_x64_64_sfx
WORKINGDIR=/Users/hermesian/NetBeansProjects/GAMSEJB/work
INPUTGAMSFILE=/Users/hermesian/NetBeansProjects/GAMSEJB/src/main/resources/Transport.gms

クラスを記述したらとりあえず単体テストしてみる。

package com.fujitsu.kime.gamsdemo.example;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class Transport2Test {

    /**
     * テスト対象クラス - Transport2
     */
    private Transport2 sut2;

    @Before
    public void setUp() {
        sut2 = new Transport2();
    }   
    @After
    public void tearDown() {
        sut2 = null;
    }

    /**
     * <per>
     * シミュレーションが動作することを確認する。
     * </pre>
     */
    @Test
    public void testRunJob() {
        System.out.println("runJob");
        sut2.runJob();
    }
}

結局、環境変数にパスの設定が必要なのか…

上記の単体試験を実行すると、失敗して下記のようなエラーメセッージが現れる。

エラー発生:GAMS system directory [null] not found from environment variable!

環境変数に設定されてないよーというものであるが、 そもそも下記の記述でGAMSのインストールを指定していて、 ここの設定が先に見られるはずなのだが…

        GAMSWorkspace ws = new GAMSWorkspace(
                resource.getString("WORKINGDIR"),
                resource.getString("GAMSDIR"),
                GAMSGlobals.DebugLevel.OFF
        );

単体テスト実行時だからかな…不明。。

環境変数export DYLD_LIBRARY_PATH=/Applications/GAMS/gams24.2_osx_x64_64_sfxを設定して、とりあえず結果がでるか確認する。

runJob
check:/Users/hermesian/NetBeansProjects/GAMSEJB/work
check:/Applications/GAMS/gams24.2_osx_x64_64_sfx
Ran with Default
x(seattle,new-york):
, level =50.0
, marginal =0.0
x(seattle,chicago):
, level =300.0
, marginal =0.0
x(seattle,topeka):
, level =0.0
, marginal =0.036000000000000004
x(san-diego,new-york):
, level =275.0
, marginal =0.0
x(san-diego,chicago):
, level =0.0
, marginal =0.009000000000000008
x(san-diego,topeka):
, level =275.0
, marginal =0.0
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.143 sec

なんとか動いた。

TODO

もう少しシミュレーションっぽい機能を拡充したい。。

  • Jobキューの管理
  • データの分離
  • 可視化

AngularJSでD3.jsを利用する記事を読んだ

普段サーバサイドしかもSI側だからもっぱらインストールや技術調査見たいのしかやらないけど、解析プラットフォームをプライベートで開発してきたい。可視化は何よりも重要。

AngularJSについて超ざっくり把握

MVW(Model, View, Whatever)なんて説明されているAngularJS。 Backbone.jsを触ったことがあるけど、同じようにクライアントサイドのMVCフレームワークかと思ってた。

  • DOMとの切れ目がはっきりしない
    • AngularJS流のHTMLになる(DOMの再定義)
  • Modelがない
    • Controllerと切り離していない

AngularJSのプロジェクトを作成するには、Angular-seedから作るか、Yeomanから作るか。

D3.jsを使う

読んだ記事はこちら

基本的な流れ

  1. D3.jsをDIする
  2. 使いたいプロット手法(line chart, bar chart, etc)を利用するdirectiveをDIする
  3. Controllerからデータを与える

1.に関して、サードパーティ製のライブラリを使うのであれば同じ手法が使えそう。

  • D3.jsのコードをそのままコピペ
'use strict';

angular.module('d3')
.factory('d3Service', [function() {
var d3Service;
d3Service = (d3.min.jsのコピペ)
return d3Service;
}]);
  • D3.jsを非同期にDOMに読み込む
'use strict';

angular.module('d3', [])
.factory('d3Service', ['$document', '$window', '$q', '$rootScope',
  function($document, $window, $q, $rootScope) {
    var d = $q.defer(),
        d3service = {
          d3: function() { return d.promise; }
        };
  function onScriptLoad() {
    // Load client in the browser
    $rootScope.$apply(function() { d.resolve($window.d3); });
  }
  var scriptTag = $document[0].createElement('script');
  scriptTag.type = 'text/javascript'; 
  scriptTag.async = true;
  scriptTag.src = 'http://d3js.org/d3.v3.min.js';
  scriptTag.onreadystatechange = function () {
    if (this.readyState == 'complete') onScriptLoad();
  };
  scriptTag.onload = onScriptLoad;

  var s = $document[0].getElementsByTagName('body')[0];
  s.appendChild(scriptTag);

  return d3service;
}]);

後は各directiveで以下のように利用する。

angular.module('myApp', ['d3'])
  .directive('barBars', ['d3Service', function(d3Service) {
    return {
  restrict: 'A',
      link: function(scope, ele, attrs) {
        d3Service.d3().then(function(d3) {
          // d3オブジェクトが利用できる
        });
      }}
  }]);

html側は以下のように記述する

<body ng-app="myApp">

<div d3-bars></div>

これからすること

全然AngularJSの記述になれていないせいか、棒グラフ、ドーナッツグラフを作成するdirectiveをそれぞれ作ってみたけど、後のチャート機能が塗りつぶしてしまって、記事に出てたような縦に並べることができない(2つのdirectiveをチェーンに並べて、一つのコントローラ配下にするとできるが)。

もしかしたら、それぞれファイルを分けてコントーラを作るのか?(コントローラもメソッドチェーンっぽくそれぞれ記述していた)。

とりあえずプロット毎にdirectiveを作りたいな。 他の実装を見て勉強しておきたい。