MATLABで作った解析アルゴリズムをシステム化する際に、コンパイルしてMATLAB Production Serverに展開すればRESTful APIでやり取りできるので、JavaScriptからもコールできる。Webフロントエンドを試しに作ってみたのでその備忘録。
サンプル
ドキュメントのExampleに債券計算を題材にしたMATLABコードとHTML/JavaScriptのサンプルがあるので、手始めにまずはここから始めてみる。
HTML/JavaScript側
bptool.htmlとcalculatePrice.jsをコピペしてほぼ完了だが、bptool.htmlの<script src="calculatePrice.js" type="text/javascript"></script>
のコメントアウトを外しておく。
MATLAB側
サンプルのpricecalc.mをそのまま使う。
function price = pricecalc(face_value, coupon_payment,...
interest_rate, num_payments)
M = face_value;
C = coupon_payment;
N = num_payments;
i = interest_rate;
price = C * ( (1 - (1 + i)^-N) / i ) + M * (1 + i)^-N;
MATLABの「アプリ」→「Production Serverコンパイラ」をクリックしてコンパイル画面を開く。
- タイプ:デフォルトの「配布可能なアーカイブ」のまま。
- エクスポートする関数:pricecalc.mを追加
- アーカイブ情報:「BondTools」に変更
これで「パッケージ化」をクリックするとMATLAB Production Server用のパッケージ(.ctfファイル)が作成される。が、今回はWebフロントエンドを作成するのが目的なので、パッケージ化をせずに「クライアントのテスト」をクリックする。これによってMATLAB上でMATLAB Production Serverのもどきが立ち上がる。
「テスト」タブに移るので以下のとおり設定。
- ポート:デフォルトの9910のまま
- CORSを有効にする:チェックを入れる
「起動」ボタンをクリックすると、http://localhost:9910/BondTools でサービスがListenするようになる。
動作確認
Webサーバーも立てずに、先程のHTML/JavaScriptをPCのデスクトップに置き、ブラウザでbptool.htmlを開く。
スライダーを左右に動かしてパラメータ(Face Value、Coupon Paymant、Number of payments、Interest Rate)に応じて債券価格が計算される。
MATLABで実行させているMATLAB Production Serverもどきへの入出力もうまくできている。
ただ、NativeのHTML/JavaScriptで画面を作っているので、画面がしょぼい。もっとファンシーにしたい。
後半に続く。
もっとファンシーなWebフロントにしたい
どうせならフレームワークを使ってMaterial Designの画面を作ってみようと思ったので、備忘録後半。
AngularのインストールはAngularのQuick Start Guideや他の記事にお任しして、ngコマンドでプロジェクトを作成するところから。
Angularプロジェクトの作成
プロジェクト名はmpsBondCalcという名前にして、ng newでプロジェクトの作成。
これで一通りのAngularのテンプレートが作成される。
Angular-Materialのインストール
プロジェクトのルートフォルダーにチェンジディレクトリして、Angular-Material一式をインストール。
cd mpsBondCalc
npm install --save @angular/material @angular/cdk
npm install --save @angular/animations
npm install --save hammerjs
AngularのGetting Startedの Step 5: Gesture Supportにあるとおり、スライダーを使うにはHammerJSが必須となるので、main.tsにimport 'hammerjs';
を追加する。
モジュールにAngular-Materialで今回使用するインプットフォーム、スライダーなどをインポートする(下記のMat*)。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatCardModule, MatInputModule, MatSliderModule, MatDividerModule } from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpModule } from '@angular/http';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
MatCardModule,
MatInputModule,
MatSliderModule,
MatDividerModule,
BrowserAnimationsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
メインとなるapp.component.htmlとapp.component.tsは、サンプルのbptool.htmlとcalculatePrice.jsを移植しつつ、DOM操作などAngular用に適宜書き直す。区切り線もbptool.htmlでは<hr>
を使っていたが、ここではAngular-Materialのdivider(mat-divider)を使ってみる。
<mat-card class="result">
<mat-card-content>
<h1><a>Bond Pricing Tool</a></h1>
<h2></h2>
This example shows an application that calculates a bond price from a simple formula.<p>
You run this example by entering the following known values into a simple graphical interface:<p>
<ul>
<li>Face Value (or value of bond at maturity) - M</li>
<li>Coupon payment - C</li>
<li>Number of payments - N</li>
<li>Interest rate - i</li>
</ul>
The application calculates price (P) based on the following equation:<p>
P = C * ( (1 - (1 + i)^-N) / i ) + M * (1 + i)^-N<p>
<mat-divider [inset]="true"></mat-divider>
<h3>M: Face Value </h3>
<form class="example-form form-inline" novalidate>
<mat-form-field class="example-full-width">
<input matInput type="number" name="facevalueValue" [(ngModel)]="facevalueValue" min="0" max="10000" (change)="calculatePrice()">
</mat-form-field>
<mat-slider
name="couponPaymentSlider"
class="example-margin"
[max]=10000
[min]=0
[(ngModel)]="facevalueValue"
(change)="calculatePrice()">
</mat-slider>
</form>
<h3>C: Coupon Payment </h3>
<form class="example-form" novalidate>
<mat-form-field class="example-full-width">
<input matInput type="number" name="couponPaymentValue" [(ngModel)]="couponPaymentValue" min="0" max="1000" (change)="calculatePrice()">
</mat-form-field>
<mat-slider
name="couponPaymentSlider"
class="example-margin"
[max]=1000
[min]=0
[step]=0.01
[(ngModel)]="couponPaymentValue"
(change)="calculatePrice()">
</mat-slider>
</form>
<h3>N: Number of payments </h3>
<form class="example-form" novalidate>
<mat-form-field class="example-full-width">
<input matInput type="number" name="numPaymentsValue" [(ngModel)]="numPaymentsValue" min="0" max="1000" (change)="calculatePrice()">
</mat-form-field>
<mat-slider
name="numPaymentsSlider"
class="example-margin"
[max]=1000
[min]=0
[(ngModel)]="numPaymentsValue"
(change)="calculatePrice()">
</mat-slider>
</form>
<h3>i: Interest rate </h3>
<form class="example-form" novalidate>
<mat-form-field class="example-full-width ">
<input matInput type="number" name="interestRateInput" [(ngModel)]="interestRateValue" min="0" max="1" step="0.01" (change)="calculatePrice()">
</mat-form-field>
<mat-slider
name="interestRateSlider"
id="interest_rate_slider"
class="example-margin"
[max]=1
[min]=0
[step]=0.01
[(ngModel)]="interestRateValue"
(change)="calculatePrice()">
</mat-slider>
</form>
<h2>BOND PRICE</h2>
<p id="price_of_bond_value" style="font-weight: bold" #price_of_bond_value>
<p id="error" style="color:red" #error>
<mat-divider [inset]="true"></mat-divider>
<h3>Request to MPS Server</h3>
<p id="request" #request>
<h3>Response from MPS Server</h3>
<p id="response" #response>
<mat-divider [inset]="true"></mat-divider>
</mat-card-content>
</mat-card>
import {Component, ViewChild, ElementRef} from '@angular/core';
import {Http, Headers} from "@angular/http";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild('request') requestElement: ElementRef;
@ViewChild('response') responseElement: ElementRef;
@ViewChild('error') errorElement: ElementRef;
@ViewChild('price_of_bond_value') priceOfBondValueElement: ElementRef;
// Initial values
facevalueValue = 0;
couponPaymentValue = 0;
numPaymentsValue = 0;
interestRateValue = 0;
httpOptions = {
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
};
constructor(private http: Http) {}
// Calculate
calculatePrice() {
let url = "http://localhost:9910/BondTools/pricecalc";
//Use MPS RESTful API to specify params using JSON
let params = {
"nargout": 1,
"rhs": [this.facevalueValue, this.couponPaymentValue, this.interestRateValue, this.numPaymentsValue]
};
let requestElement = this.requestElement.nativeElement;
requestElement.innerHTML = "URL: " + url + "<br>" + "Method: POST <br>" + "Data:" + JSON.stringify(params);
const response = this.http.post(url, params, {headers: this.httpOptions.headers}).toPromise()
.then(response => {
//Use MPS RESTful API to check HTTP Status
if (response.status === 200) {
// Deserialization: Converting text back into JSON object
// Response from server is deserialized
let result = response.json();
//Use MPS RESTful API to retrieve response in "lhs"
if ('lhs' in result) {
let errorElement = this.errorElement.nativeElement;
errorElement.innerHTML = "";
let priceOfBondValueElement = this.priceOfBondValueElement.nativeElement;
priceOfBondValueElement.innerHTML = "Bond Price: " + result.lhs[0].mwdata;
} else {
let errorElement = this.errorElement.nativeElement;
errorElement.innerHTML = "Error: " + result.error.message;
}
} else {
let errorElement = this.errorElement.nativeElement;
errorElement.innerHTML = "Error:" + response.statusText;
}
let responseElement = this.responseElement.nativeElement;
responseElement.innerHTML = "Status: " + response.status + "<br>"
+ "Status message: " + response.statusText + "<br>" +
"Response text: " + JSON.stringify(response.json());
});
}
}
書き直しは必要最小限にしたいと思いつつ、入力フォームやスライダーのところでngModleを使うとかなり実装が楽なので、かなり書き直してしまった。
実行結果
それではAngular-MaterialによるWebフロントでの実行結果を確認。
よしよし、入力フォームとスライダーがモダンになっていい感じ。
Webサーバーにデプロイする時はng build --prod
でTypeScriptをビルドして、できたHTML/JavaScriptをWebサーバーに置き、MATLAB Production ServerもMATLAB上での「もどき」ではなく、ちゃんとパッケージ化してCTFファイルをMATLAB Production Serverに持っていって実行させれば良いのだが、Webフロントの開発を行う時はデバッグしやすい「もどき」が便利。