[F#] 連立一次方程式を解く

逆行列、行列式とできたので、連立一次方程式を解かせる。有限要素法のいわゆる「solve」というやつで、解析の手順として、

  • pre : 構造を設定、要素に分割
  • solve : 連立一次方程式を解く(それだけじゃない?)
  • post : ひずみの結果を表示する

という手順になっている。
Gauss の消去法を使って直接的に解くわけだが、これって、そのまんま LU 分解なわけで。なので、solve 自体は反復法なども含めた意味で使わないとダメっぽい。

let solve (A' : matrix) (F': vector) = 
    let A = A'.Copy()
    let F = F'.Copy()
    let n = A.NumCols-1
    // 前進消去
    for i in 0..n-1 do
        for j in i+1..n do
            let p = A.[j,i]/A.[i,i]
            F.[j] <- F.&#91;j&#93; - p * F.&#91;i&#93;
            for k in 0..n do
                A.&#91;j,k&#93; <- A.&#91;j,k&#93; - p * A.&#91;i,k&#93;
    // 後退代入
    F.&#91;n&#93; <- F.&#91;n&#93;/A.&#91;n,n&#93;
    for i in n-1..-1..0 do
        for j in i+1..n do
            F.&#91;i&#93; <- F.&#91;i&#93; - A.&#91;i,j&#93;*F.&#91;j&#93;
        F.&#91;i&#93; <- F.&#91;i&#93;/A.&#91;i,i&#93;
    F
&#91;/code&#93;
<p>
「有限要素法概説」にあった前進消去「上三角化」したあとに後退代入でひとつずつ計算する。
</p>
[code lang="csharp"]
// 2x -y = 2
// -x +2y= 5 を解く

let M = matrix[[2.;-1.];
               [-1.;2.]]
let F = vector[2.0;5.0]

solve M F 

こんな風にいれておいて、実行すると

val it : Vector<float> = vector [|3.0; 4.0|]

こんな風に結果が出る。

実は、F# MathProvider にも同じものがあって、

let M = matrix[[2.;-1.];
               [-1.;2.]]
let F = vector[2.0;5.0]

let sol = MathProvider.LinearAlgebra.solve M F

として解くと同じ事ができる。

有限要素法の場合、ひとつの節点の自由度は6になるので、節点の数の6倍の計算量になる。さらに、要素が持つ節点(頂点)は三角形二次要素場合は6点になるので、要素数の36倍の計算量なる。が、計算量が多くなるといっても、多くなっても2桁増えるぐらいだから大したことはない?ことはないか。前進消去で、n^3/2 ぐらいのオーダーになるので、n が 100倍になると、100万倍ぐらいの計算量が増える。となると、この素直な方法で解いてしまうと、メッシュ数を2倍細かくすると約8倍の計算量が必要になる。確かに、高速化が必要な分野であるかも。
このあたりは後で実測して試してみよう。

カテゴリー: F# | 1件のコメント

[F#] 行列式を計算する

F# MathProvider だと、あっさりと MathProvider.LinearAlgebra.inv なる関数があるのだが、自作してみる。

let det ( A' : matrix ) =
    let A = A'.Copy()
    let n = A.NumCols-1
    let B = Matrix.identity A.NumCols
    let mutable det = 1.0

    for k in 0..n do
        // akkを1にする
        let p = A.[k,k]
        det <- det * p
        for j in 0..n do
            A.&#91;k,j&#93; <- A.&#91;k,j&#93; / p
            B.&#91;k,j&#93; <- B.&#91;k,j&#93; / p
        // k行以外から引く
        for i in 0..n do
            if i <> k then
                let d = A.[i,k]
                for j in 0..n do
                    A.[i,j] <- A.&#91;i,j&#93; - A.&#91;k,j&#93; * d
                    B.&#91;i,j&#93; <- B.&#91;i,j&#93; - B.&#91;k,j&#93; * d
    det
&#91;/code&#93;
<p>
逆行列の計算
<a href="http://www.asahi-net.or.jp/~uc3k-ymd/Lesson/Section03/invmat.html">http://www.asahi-net.or.jp/~uc3k-ymd/Lesson/Section03/invmat.html</a>
</p>
<p>
の中にある Fortran のコードから擬似写しなんだけど、「det &lt;- det * p」なところで、行列式を保存。他はいらないはずなんだが、うまく括りだせないのでそのままです。
数学的には det A = 0 の場合は、逆行列が計算できないので連立一次方程式が解けないことなるのだが、有限要素法の場合には拘束条件を間違えない限り、逆行列は存在するので問題なし(なはず)。この方式だと det の計算自体に逆行列の計算が含まれてしまっているので本末転倒だし、計算時間もかかってしまう。なので、実際には 0 除算にならなければ逆行列の計算を続ける、という方式でよいのかも。
</p>
[code lang="csharp"]
let A = matrix [[3.0; 1.0;]; 
                [2.0; 5.0;]]

det A

な感じで計算ができて、

val it : float = 13.0

の結果が得られる。

カテゴリー: F# | [F#] 行列式を計算する はコメントを受け付けていません

[F#] 逆行列を計算する

F#で数値・線形代数計算をするためのライブラリ紹介(F# PowerPack, F# MathProvider)
http://d.hatena.ne.jp/teramonagi/20111215/1323874810#20111215fn6

を見ると、F# PowerPack と F# MathProvider を使うと、逆行列が簡単に解けます。

#r "FSharp.PowerPack.dll";
#r @"D:toolsMathProviderNet 4.5MathProvider.dll";

let A = matrix [[3.0; 1.0;];
                [2.0; 5.0;]]

let det = MathProvider.LinearAlgebra.det A
let inv = MathProvider.LinearAlgebra.inv A

こんな風にすると、

val A : matrix = matrix [[3.0; 1.0]
                         [2.0; 5.0]]
val det : float = 13.0
val inv : matrix = matrix [[0.3846153846; -0.07692307692]
                           [-0.1538461538; 0.2307692308]]

と出ます。めでたしめでたし…なのは、後から知ったわけで、実は F# MathProvider は必ず Blas.dll と lapack.dll が必要なのかと思って躊躇してたんですよね。できることならば、F# だけで作りたい、というか、ストアアプリとか自前で高速化(有限要素法で1万節点とか)を考えると、中身を理解しておきたい、という願望があったので…車輪の再発明をしてしまいました。

let inv ( A' : matrix ) =
    let A = A'.Copy()
    let n = A.NumCols-1
    let B = Matrix.identity A.NumCols
    for k in 0..n do
        // akkを1にする
        let p = A.[k,k]
        for j in 0..n do
            A.[k,j] <- A.[k,j] / p
            B.[k,j] <- B.[k,j] / p
        // k行以外から引く
        for i in 0..n do
            if i <> k then
                let d = A.[i,k]
                for j in 0..n do
                    A.[i,j] <- A.[i,j] - A.[k,j] * d
                    B.[i,j] <- B.[i,j] - B.[k,j] * d
    B

逆行列の計算
http://www.asahi-net.or.jp/~uc3k-ymd/Lesson/Section03/invmat.html

を参考にしてべたべたな手計算の方法をF#に直しています。結果が0になるところも計算してしまって無駄が多いので、高速化のし甲斐がある…というか、Fortran とか C言語とかのコードもあちこちにあるので、それらをコンバートしたほうが良いかも。
ただし、有限要素法で使う逆行列の場合には、対称性をうまく利用したり計算量を減らすことができるので、一般的な逆行列よりも手を加えたほうが実用的かもしれません。が、ADVENTURE のように大規模な構造(1億節点など)とは違って、節点数のオーダーが違えば、最近のPCならばさして高速化せずとも結構なところまでいけるのでは?と考えています。これは先行き試してみたいところです。

実はこのF#のコード、半日ほどかかったんですよね。なかなか数値が合わなくて苦労したのは、F#の文法に馴れていなかったためもあるのですが、既存の Fortran コードとは別の形で実装して手計算そのままを移したためです。まあ、勉強になったからいいか。正月だし。

これが「できたーっ!!!」後に気付いたのが、F# MathProvider の実装です。Blas.dll を使わない場合には、自前の F# コードを使うようにできているんですね。なるほど。
以下が抜粋、LU分解と上三角を使っています。

/// Given A[n,n] find it's inverse.
/// This call may fail.
let Inverse a = 
  if HaveService() then LinearAlgebraService.inverse a
                   else LinearAlgebraManaged.Inverse a

let Inverse A =
    let (n,m) = matrixDims A
    if n <> m then invalid_arg "Matrix must be square when computing its inverse." 
    let P,L,U = LU A
    (SolveTriangularLinearSystems U (SolveTriangularLinearSystems L ((Matrix.identity n).PermuteRows P) true) false)

    let LU A =
        let (nA,mA) = matrixDims A
        if nA<>mA then invalid_arg "lu: not square";
        let L = Matrix.zero nA nA
        let U = Matrix.copy A
        let P = [| 0 .. nA-1 |]
        let abs (x:float) = System.Math.Abs x
        let swapR X i j =                           //  REVIEW should we make this a general method?
            let (nX,mX) = matrixDims X
            let t = X.[i .. i,0 .. ]
            for k=0 to mX-1 do
                X.[i,k] <- X.[j,k]
                X.[j,k] <- t.[0,k]
            done
            
        for i=0 to nA-2 do
            let mutable maxi = i        //  Find the biggest pivot element.
            for k=i+1 to nA-1 do
                if abs(U.[maxi,i]) < abs(U.[k,i]) then maxi <- k
            done
            //let maxi = maxIndex (i+1) (nA-1) (fun k -> abs(U.[k,i]))
            
            if maxi <> i then
                swapR U i maxi     // Swap rows if needed.
                swapR L i maxi     // REVIEW can be made more performant.
                let t = P.[i]
                P.[i] <- P.[maxi]
                P.[maxi] <- t
            
            for j=i+1 to nA-1 do
                L.[j,i] <- U.[j,i] / U.[i,i]
                for k=i+1 to nA-1 do
                    U.[j,k] <- U.[j,k] - L.[j,i] * U.[i,k]
                done
                U.[j,i] <- 0.0
            done
        done
        (((*P.Length,*)Permutation.ofArray P),L + Matrix.identity nA,U)

    let SolveTriangularLinearSystems K B isLower =
        if isLower then
            let (nK,mK) = matrixDims K
            let (nB,mB) = matrixDims B
            if nK<>mK || nB<> nK then invalid_arg "Cannot do backward substitution on non-square matrices."
            let X = Matrix.zero nK mK
            for i=0 to nK-1 do
                for k=0 to mB-1 do
                    let s = ref B.[i,k]
                    for j=0 to i-1 do
                        s := !s - K.[i,j] * X.[j,k]
                    done
                    X.[i,k] <- !s / K.[i,i]
                done
            done
            X
        else
            let (nK,mK) = matrixDims K
            let (nB,mB) = matrixDims B
            if nK<>mK || nB<> nK then invalid_arg "Cannot do backward substitution on non-square matrices."
            let X = Matrix.zero nK mK
            for i=0 to nK-1 do
                for k=0 to mB-1 do
                    let s = ref B.[nK-i-1,k]
                    for j=0 to i-1 do
                        s := !s - K.[nK-i-1,nK-j-1] * X.[nK-j-1,k]
                    done
                    X.[nK-i-1,k] <- !s / K.[nK-i-1,nK-i-1]
                done
            done
            X

まあ、数式は理解して使ったほうがいいので、しばらくは自前で実装ということで。

カテゴリー: F# | [F#] 逆行列を計算する はコメントを受け付けていません

年頭なので初心に帰って

去年と一昨年は何気に正月に仕事をしていたのですが、今年は年末に原稿の殴り書きをした後にプログラムを殴り書きして正月休みを作りました。妻の実家にノートパソコンを持って行ってプログラムの下書きなどをすることが多いのですが、今回はノートパソコンなし。その代りに「実践 有限要素法シミュレーション」の本を持って行って、通読…はできなくて基礎編の30ページちょいぐらいを解いていました。F#とFortranの話…のその前に – Moonmile Solutions Blog の時に書いた構造計算にF#が使えるのか?って話の続きですね。行列計算をやって逆行列を作って、連立一次方程式を解いてというのを手計算でやって大学の頃を思い出して、うーっとなっておりました。幸いにして、正月はノートパソコンを持っていかなかったので、F#で組むこともせず、Fortranのコードを見ることもせず、ストアアプリのライブラリを作ることはなかったのですが、久しぶりに鉛筆で筆算をしていると、理解は深まるんではないかな~などと思うわけですが、どうなんでしょう?ただ、コードをコンバートしているだけよりは理解が深まったのではないかなと思ったり…いやいや、車輪の再発明というパターンに陥るかもしれませんが。

さて、色々やりたいことはあるけれど、地道にやることと仕事でやることと自分の技量的にやれないことがあって、もんもんと考えることも多いのですが、ざっと正月にメモしていたものを書き下し。

  • 画像解析をしてパズルを解く。
    以前やっていた、OpenCVの再開です。テンプレートマッチングは自前で書くことができたので、汎用性を殺せば比較的楽に作れるかと。
  • マインドストームでロボットアームを作る。
    せっかく EV3 を買ったので(というかこれが目的なので)ストアアプリから制御します。
  • 有限要素法を F# で解く。
    材料工学は素人なのですが、原子力関係なので行列式にはなじみがあるので(得意とは言えないが)、似た感じでけるかなという目論見です。仕事で関わっているところもこれなので、専門用語の理解も含めて。
  • ストアアプリ用のパズルコンポーネントを作る。
    親馬鹿シリーズ(ということにして)、手軽にパズルゲームが作れるまでのコンポーネントを作れればよいかなと。ロジックを F#、画面を C#、アニメーションを XAML/storybaord、画像関係を C++/CX で組みあわせれば、そこそこのものが作れるかなという目論見

あとは、めでたく Microsoft MVP の Visual C# を再受賞させていただいたので、手元の MSDN を存分に使って、

  • Xamarinの話
  • CakePHPとWPFの仕事
  • FotranとC++の仕事

を引き続き。

引っ越しを機会にため込んでいた文庫本をどんどんと廃棄しつつあります。一度読んだ本もれば一度も読んだことがない本(=積読)もあるわけで、技術書のように古くなった本を捨てるパターンもあれば、時々めくってみる古い本もあります。まだ段ボール15箱ぐらい残っているのですが、これも整理して本棚に詰めれるだけにして、本は図書館で借りたりして(近くに図書館のある場所に引っ越しました)家で仕事をして、作りたいものが作れるようになる感じが自分にとって向いているのかな、再確認したり、そう無理に思い込んでみたり。あ、そうそう、TOC用のタスクスケジューラが欲しいところですね。TFSの作業項目と連携させるやつ。この手のやつもぼちぼちと作って行きましょう。

ひとまず、やりたい目標はいっぱい掲げておいて、自分が実現できるもの(興味が続くものというのも含めて)に手を付けていこう。「楽観的に構想を練り、悲観的に計画し、楽観的に実行する」というパターンで。

カテゴリー: 雑談 | 年頭なので初心に帰って はコメントを受け付けていません

F#とFortranの話…のその前に

F# Advent Calendar 2013 – connpass
http://connpass.com/event/3935/

の22日目…なのですが、既に23日深夜という…ええ、GMTで計算しても22日目に入らないですね。本来ならば、Fortranの関数や構造体をF#で使おう、というネタで書こうと思ったですが、もろもろの件が忙しくてkindleで「Programing F# 3.0」を眺めるだけで精一杯で先に進みませんでした。いやいや、言い訳よりもまずは手を動かさなくちゃね、ってことで、構想だけつらつらと1時間ほど書き連ねます。

■F#をFortranモジュールと連携する

構造解析をFortranで作っていると画面作成をどうするのか?で悩みますが、手元のものはC++で作ってあります。Fortranの関数や構造体をC言語レベルに書き直して、C++で読み込ませるという手順ですね。実は、Fortranにも.NETと連結できるソフトウェアもあるのですが、手元の Intel Fortran はそのままでは .NET とは繋がりません。これをやるならば、Fortran –> C言語 –> C++/CLI とするか、直接 C言語 –> C# ってスタイルになります。F# は関数言語といえ、.NET言語の仲間ですから、F#からC言語のライブラリを呼び出すことも可能(だと思う)でしょう。C言語のAPIレベルをひとつひとつ再定義するのは結構な手間ですし、ネイティブのデータとのコンバートに時間がかかってしまっていては、せっかくFortranを使っている意味がありません。

そこで、以前、画像解析の時に使っていた、パターンで、Fortran –> C++/CLI –> F# というコンバートのパターンを考えます。本来のF#の利用しどころはさておき、実際のコアな計算は、Fortranの中に入っているので、F#からアクセスしたいデータはもう少し「データを簡便に利用したい」というところです。

GUIのようなグラフィックの部分は、DirectXとWPFを使っています。旧来のDirectXなので、これ専用にライブラリ化されてしまってまったく手が出せない感じになっています。また、WPFのほうは、FortranからC言語下へのデータコンバートをしたのち、C#を使って描画させます。このあたり、専用グラフィック用のC++と、別途ツールのWPFとが混在していて、えらいことになっているんですが…まあ、先行きはわかりません。なかなか大変です。

現在のソフトウェア構造としては、

  • データを保持しているところのFortoran
  • データを高速に扱うためのFortranコード
  • データをDirectXで3D描画するためのC++
  • GUIを楽に作るためのC#とWPFの画面

というパターンになっています。

■関数を関数として扱うためのF#

さて、このソフトウェア構造の中でF#に何を期待するのか?といえば、「式を式のまま記述したい」という希望があります。一応、30年前ぐらいの関数言語の教科書(関数言語の発祥は、オブジェクト指向と同じころにあります)を通読したのですが、数式を数式のまま扱うという入口から私は入っています…つーか、まだ入り口の前なんですが。

image

たとえば、こんな式があります。私の時代の材料工学は荷重する点に対して正確な計算を行うのですが、最近の構造解析では適当なメッシュを切って膨大な計算で済ませます。適当な荷重を計算した上で、メッシュ(格子点以外に三角点のセルもあります)に対しての荷重を再計算します。

image

メッシュに対する荷重を計算した後は、それぞれの部材の相互の斥力を計算するわけですが、これはFotranのライブラリの中に入っています。かつてはFortranに直していたわけですが、このFortranのデータ構造と数式の書き方は、やっぱりコンピュータに沿った書き方なんですね。いわゆるベクトルを扱う訳ですが、これがforループします。30年ほど前はそれはそれでよかったのですが、そのforループにちょっと疑問があるわけです。

全てのメッシュ(あるは交点)に対して、同じ式を適用するのであれば話は簡単です。

do y=1, YMAX
do x=1, XMAX
  … 式を書く
enddo
enddo

とすればOKです(実は、最近のFortranはベクトルが使えるので、もっと簡単に1行で書けます)。

ですが、メッシュを使う場合、均一に式を適用するのではなくて、必ず「境界条件」というのが入ってきます。メッシュの端っこの部分です。他の部材と接するところや、ボルトで固定されているところとか、素材が異なるところですね。この境界条件あるいは特定条件を指定するために、ループの中にif文を入れます。

do y=1, YMAX
do x=1, XMAX
  if ( 特定の条件の時 ) then …
enddo
enddo

プログラミングをしているときは、これは当たり前で自然な書き方なのですが(いやいや、matchを使うと違うでしょ…って話は後ほど)、数式の場合にはこのif条件をこんな風に書きます。

y = g(x)
  y = 1    := {x|x=1}
  y = f(x) := {x|1<x,x<MAX}
  y = 1    := {x|x=MAX}

(ちょっと正確な書き方を忘れてしまった…工学部なのに orz)

1とMAXの時はy=1にして、1以上のときはf関数を適用する、というよくあるパターンですね。これをif文を使ってちまちま直すのがプログラミングだったわけですが、いやいや、それらの数式は数式のまま扱おうよ、という発想が関数言語の発祥です…と言い切っていいものかわからないのですが、最初の教科書を見るとそんな感じですね。

これをF#で書くとこんな感じになります。

let g x =
  match x with
  | 1 -> 1
  | MAX -> 1
  | _ -> f(x)

1の時が境界条件(特殊条件)で、そのほかの値の時はf関数を適用するのがわかりやすいのです。これをC言語で書くと、

if ( x == 0 ) {
  return 1;
} else if ( x == MAX ) {
  return 1;
} else {
  return f(x);
}

な感じになります。「慣れ」といえば慣れなんですが、特殊条件のときと通常の条件(メインストリーム)のときの区別がわかりやすいかな、という利点があります。

実際コーディングしたときに、F#の実行スピードとFortranの実行スピードはどうなのかも問題なのですが、複雑な数式を適用させたときに、

  • 直した数式が、プログラムのどれにあたるか即判断できるか?
  • 直した数式が、プログラムと同じであるか即判断できるか?

ってところが関数言語の利点かなと思っています。構造の数式とか画像解析の数式を修正したときに、それがプログラムのどの部分にあたるのか?が判断しやす利点は、そのままトライ&エラーがやりやすいという利点でもあります。以前、テンプレートマッチングの式を OpneCV を使って自作していたのですが、C++ で組むよりも、この手の関数言語のほうが楽ではないか?と思っています。相関式とかさらっと書けるプログラマの方はよいのですが、境界条件も含めると結構複雑な感じになってしまうんですよね。このあたり、ゲームアルゴリズムも同じかと思っています。

■ロジックを書くのに最低限必要なF#の文法はなんだろうか?

…ってのが、私の当面の課題で、もろもろのF#の便利な機能はさておき、F# と設計について考えてみた #FsAdventJP – かたちづくり と似た感じで進めたいと思っています。

  • ミニマムなデータ構造(Fortran、C言語互換)
  • データ構造アクセス(C++/CLI経由?)
  • 高速化計算(Fortran)
  • 通常計算(F#)
  • 数式の検証、試行錯誤(F#)
  • GUI(C#)

なところですね、画像解析の場合は、OpenCVを使いたいので、このあたりがC++になります。また、DirectXを使ってC++/CX経由になっても、F#の使いどころはありそうです。

これを踏まえたとき、最低限必要なところといえば、

  • 構造体アクセス(他アセンブリのアクセス、open)
  • パターンマッチング(match?)
  • リストアクセス(Listあるいは配列?)

だけで十分では?と乱暴なことを考えていたりするのですが、このあたりはもうちょっと先に実際にやってみてということで。

# タプルとか便利かな、と思っていくつか試したのですが、Fortranで扱うのが構造体のみだし。そのあたりは、うまいことC++/CLIでwrapしてF#から構造体へ直アクセスしたほうがよいかなと試行
&実験中です。

カテゴリー: F# | 1件のコメント

矢野顕子さとがえるコンサート2013に行ってきました

恒例のさとがえるコンサートに行ってきました。下の子は託児所に預けて、上の子と三人でNHKホールへ。託児所はNHKホール内にあるので、直行直帰という感じです。

今回は20年前に発表された「エレファントホテル」の全13曲プラスアルファということで、2時間強の結構な時間になりました。最近は1時間半ぐらい終了のパターンが多いので、ちょっと小3の娘には長かったかも。

ゲストは奥田民夫。バンドメンバは、テルミンを含む不思議な楽器のメンバでMATOKKU(松本淳一/トリ音/久保智美)です。

▶ “MATOKKU” LIVE~ヤノさん、マトック園であそぶ~ – YouTube
http://www.youtube.com/watch?v=8Iei9UfPVZE

たぶん、普段は現代音楽っぽいのでしょうが、今回はエレファントホテルのアレンジということで、ふつうの音楽してます。いや、矢野顕子の音楽自体が「ふつう」かというと、ふつうからは随分外れていると思うので、それはまた奇妙な組み合わせということです。が、20年前の声の復元性は、今回非常に高かった気がします。あの年齢であの声の高さとテンションを維持しているのが、すごいというか独自だからなのか、その独自性を維持することが重要≒日常なのか、という点で毎度見習わないと、と思ったりします。

夏にさそうあきら著「ミュジコフィリア」を読んだのを思い出して、矢野顕子の出す「は」の音が、「はの音楽なのかな?」と想像したり、フランク・ザッパのイエローシャークを連想したり、と楽しみました。

今年はいろいろあって大変で、実は年末までもいろいろありそうで大変なのですが、それでも「大変」の中に埋もれてしまう人生は詰まらないから、自分で動ける範囲は自分の自由意思で動く。いやいや、もっと生物学的に利己的な遺伝子的に動いているのを前提として、自分が好きなことをやる姿勢を維持しないと、と心新たにという一日。

カテゴリー: 雑談 | 矢野顕子さとがえるコンサート2013に行ってきました はコメントを受け付けていません

[win8.1] ストアアプリで手軽に画像を加工しよう

Windows Store App Advent Calendar 2013
http://qiita.com/advent-calendar/2013/win-store-app/participants
の4日目です。

Windows 8のストアアプリの場合は、画像の加工が大変だったのですが、8.1の場合は「<a href=”http://msdn.microsoft.com/ja-jp/library/system.windows.media.imaging.rendertargetbitmap(v=vs.110).aspx”>RenderTargetBitmap クラス </a>」を使うと「手軽」にできますっ!!! というネタです。実際に手軽にできるかどうかは別として(!?)、DirectXを使ったり、Unityを使ったり、OpenCVを使ったり、いろいろ独自で画像処理をがんばったりしなくても、できる範囲ができました。って感じですね。8 の時は手が出しようがなかったのですが、まあ、できるようになった第一歩かと。ちなみに、この手の方法で画像加工をする場合は、WPFで組むほうが断然楽です。いろいろなエフェクトも用意されているし、図形なんかもいろいろあります。WinRTの場合は、結構画像まわりが削られてしまっているのでBlendを使ってもあまりうまくできなことが多いのです。
が、あえてストアアプリでやる利用があるとすれば、画像のプレビューとか、ちょっとしたフレームの加工とか、そんな感じですかね。本格的なところはDirectXを使ったほうがいい気がします。ちょっと、DirectX – C++/CXの調査は停滞していて進めていないのですが。

■できあがりイメージ

出来上がりは、こんな感じです。

画像の上に文字を張り付けたり(これが一番簡単)、切り抜きをしたり、透明度を変えて擬似的に色調を変えてみたり、マスクをかけてみたり。それぞれは、XAMLでキャンバス(canvas)上に重ね合わせています。重ねた後の画像をそのまま保存できるのが、RenderTargetBitmapクラスの利点です…つーか、WPFのほうがすんなり使えるんですがね。

■重ね合わせの方法

重ね合わせの部分をピックアップすると、下記のようにCanvasの上に貼り付けていきます。RenderTargetBitmap自体は、UIElementのレンダリングを保存できるので、GridでもOKですし、ImageそのままでOKです。

<Canvas 
        x:Name="panel4"
        Tapped="panel_Tapped"
        HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="200" Canvas.Left="224" Margin="783,173,0,0">

    <Image 
            x:Name="img4"
            Source="ms-appx:///images/haruna.jpg"
            HorizontalAlignment="Left" Height="200"  VerticalAlignment="Top" Width="200" Opacity="0.5"/>
    <Image 
            x:Name="img4mask"
            Source="ms-appx:///images/mask.png"
            HorizontalAlignment="Left" Height="200"  VerticalAlignment="Top" Width="200" Opacity="1"/>
    <TextBlock 
            FontSize="32"
            Canvas.Left="10" TextWrapping="Wrap" Text="マスク" Canvas.Top="151"/>
</Canvas>

ここではマスク画像を、mask.png であらかじめ作っておいて重ね合わせているわけですが、本当は動的に作りたいところですよね。

■ファイルの読み込み

定番のファイル読み込みは、こんな感じ。FileOpenPicker を使います。

/// <summary>
/// ファイルを選択
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Button_Click(object sender, RoutedEventArgs e)
{
    var picker = new FileOpenPicker();
    picker.CommitButtonText = "画像を選択する";
    picker.FileTypeFilter.Add(".jpg");
    picker.FileTypeFilter.Add(".jpeg");
    picker.FileTypeFilter.Add(".png");
    picker.ViewMode = PickerViewMode.Thumbnail;
    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    // 画像ファイルを指定する
    var file = await picker.PickSingleFileAsync();
    // 選択されなかった場合
    if (file == null)
    {
        return;
    }
    // 画面へ表示
    var stream = await file.OpenReadAsync();
    var bmp = new BitmapImage();
    bmp.SetSource(stream);
    this.img1.Source = bmp;
    this.img2.Source = bmp;
    this.img3.Source = bmp;
    this.img4.Source = bmp;
}

ペタペタと4つ分 Image を貼っていますが、実際のツールにする場合は配列をつかうとか諸々修正してください。

■ファイルへ保存

画像ファイルとして保存するときは、選択は FileSavePicker です。PanelSave.Save ってのが独自に作ったところで(実際は、Microsoft さんのサンプル集にあります)。

/// <summary>
/// パネルをクリック
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void panel_Tapped(object sender, TappedRoutedEventArgs e)
{
    var panel = sender as UIElement;
    // 保存先を選択する
    var savePicker = new FileSavePicker();
    savePicker.DefaultFileExtension = ".png";
    savePicker.FileTypeChoices.Add(".png", new List<string> { ".png" });
    savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    savePicker.SuggestedFileName = "snapshot.png";
    var saveFile = await savePicker.PickSaveFileAsync();
    // キャンセルされた場合
    if (saveFile == null)
        return;
    // ファイルに保存
    PanelSave.Save( panel, saveFile);
}

ざっと、該当箇所を整理しただけなので、こんな感じ。RenderTargetBitmapクラスで、レンダリングした後の画像バッファを取り出して、BitmapEncoderを使ってファイルを保存するという流れですね。メモリ上でやりたいときは、MemoryStream を使えばいいはずなのですが、これはまだ試していません。
StorageFile だと、ユーザーがファイルを指定しないといけないので、アプリケーションのデータフォルダやピクチャーフォルダに編集後のファイルを複数ファイル自動で保存できるといいので。このあたりは、また調べていく予定です。

public class PanelSave
{
    /// <summary>
    /// 指定した要素をレンダリングして保存
    /// </summary>
    /// <param name="el"></param>
    /// <param name="saveFile"></param>
    public static async void Save( UIElement el,  StorageFile saveFile )
    {
        // レンダリングをして画像バッファを取得
        RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
        await renderTargetBitmap.RenderAsync(el);
        var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();

        // Encode the image to the selected file on disk
        using (var fileStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
        {
            // PNG形式で保存
            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, fileStream);
            encoder.SetPixelData(
                BitmapPixelFormat.Bgra8,
                BitmapAlphaMode.Ignore,
                (uint)renderTargetBitmap.PixelWidth,
                (uint)renderTargetBitmap.PixelHeight,
                DisplayInformation.GetForCurrentView().LogicalDpi,
                DisplayInformation.GetForCurrentView().LogicalDpi,
                pixelBuffer.ToArray());
            await encoder.FlushAsync();
        }
    }
}

保存するとこんな感じ。

■どうやって「手軽」に作るか?

重ね合わせのキャンバスを手軽に保存できることはわかったのですが、じゃあ画像の加工は手軽にできるのか?ってのが問題ですね。ちょこっとやった感じでは、手軽に作れる範囲はかなり限られます。マスク画像や切り抜き画像は、あらかじめマスクや図形の合成を作っておかないといけないので、結構コツが要ります。思い通りできるまでにBlendと格闘、さらにはアドビ系のツールとかExpression Designerとの格闘が必要になるので、手軽にとはいきません。ああ、XAMLでお絵かきできる方ならば、あるいは「手軽」かもと思いもしますが。

  • 文字を画像に重ね合わせる(コピーライトとか日付を表示とか)
  • 画像をフレームに合わせる(あらかじめ、フレームを透過PNGで作っておく)
  • 透明度を変更させて、それっぽい感じに(セピア色とか)

画像自体に手を加えるのは、DirectX の範疇になってしまうので、あくまでXAMLの図形を重ね合わせてできる範囲ですね。画像をブラシにすれば図形の内側に塗り付けてという裏技もあるので結構なことができるはずなのですが、それはまた後日(まだ調べきれていない。WPFだとできるのはわかっているんですが)。

■サンプルコード

使ったサンプルコードは、こちら。
http://sdrv.ms/IpvVFF

カテゴリー: 開発, WinRT | [win8.1] ストアアプリで手軽に画像を加工しよう はコメントを受け付けていません

ノーマルにMSTestを使おう

TDD Advent Calendar 2013の参加状況確認・参加登録 – Qiita [キータ]
http://qiita.com/advent-calendar/2013/tddadventjp/participants

の一発目です。トップバッターなので、ノーマルに MSTest を使う話を1時間ほど書いていきます。TDDで何ができるの?とか、PHPUnitとか、NUnitとか、gtestとか、CppUnitとか、その他もろもろのお話は先行き出てくるはず(?)なので、そちらに譲るとして、Visual Studio 2013 の Express 2013 for Widnwos Desktop を使って、MSTest を使って見よう、という感じで進めていきます。

■いきなりUIを作る

image

こんな感じで、いきなりUIを作ってみます。最初のTextBox1とTextBox2の内容を足したら、TextBox3に入る、という簡単な計算機ですね。つーか、簡単すぎてあれなのですが、まあ、これを作っていきます。

■いきなり計算をする。

/// 計算する
private void button1_Click(object sender, EventArgs e)
{
	int x = int.Parse(textBox1.Text);
	int y = int.Parse(textBox2.Text);
	int z = x + y;
	textBox3.Text = z.ToString();
}

いきなりコードを書いていきます。これぐらいのコードならばUIにそのまま書いていいよね、って具合に書いていきますが、これじゃダメなのはわかりきってますね。だって、TextBox1.Text が空欄のときは、int.Parse でエラーになるし、なんか、いろいろ TDD 的にダメなところが満載です。

image

デバッグ実行して、ちまちまとエラー箇所を直すのもアリなのですが、ひと手間かけて、もう少しなんとかしましょう。

■ボタンクリックと計算する部分を分ける

せめて「UIとロジック」を分けようということで、ボタンをクリックしたところと、実際に計算するところを分けます。テキストボックスの文字列をそのまま渡して、文字列でもらってくると関数として楽ですよね、という発想です。

/// 計算するボタンを押す
private void button1_Click(object sender, EventArgs e)
{
	textBox3.Text = calc(textBox1.Text, textBox2.Text);
}

/// 計算する
private string calc(string x, string y)
{
	int xx = 0;
	int yy = 0;
	if (int.TryParse(x, out xx) == false) return "";
	if (int.TryParse(y, out yy) == false) return "";

	int zz = xx + yy;
	return zz.ToString();
}

まあ、これも悪くはないのですが…、ええと、MSTest を使うときは、private メソッドでは無理なので、public にしないとダメですね。

■テスト対象を public にする

calc メソッドを public にしてテストから呼び出せるようにします。

/// 計算する
public string calc(string x, string y)
{
	int xx = 0;
	int yy = 0;
	if (int.TryParse(x, out xx) == false) return "";
	if (int.TryParse(y, out yy) == false) return "";

	int zz = xx + yy;
	return zz.ToString();
}

おお、これなら calc メソッドがテストできそうですね。

■テストコードを書く

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WindowsFormsApplication1;

namespace UnitTestProject1
{
[TestClass]
	public class UnitTest1
	{
		[TestMethod]
		public void TestMethod1()
		{
			var frm = new Form1();
			string ans = frm.calc("1","2");
			Assert.AreEqual("3", ans);
		}
	}
}

こんな感じで、文字列の「1」と文字列の「2」を渡したら文字列の「3」を戻すコードを書きます。参照設定で、フォームのあるプロジェクトを設定するのと、System.Windows.Forms も参照設定するのを忘れずに。ちょっとややこしいですね。

image

無事、テストが成功するとこんな風に緑のチェックマークが出ます。

そうそう、テキストボックスが空欄の場合も追加して実行すると、

image

こんな風に次々とテストが書けます。めでたしめでたし…で終わると怒られそうですね。フォームをそのままnewするのは、まあそこそこ業務のテストとしてはあるのですが、しかしフォームにいろいろな部品が乗っかったり、フォームを初期化するときにデータベースに接続したりするときは、テストが重くなりそうです。まあ、実際重くなるし、初期化で失敗したりしてテストが自動化できなくなってしまいます。

■フォームとは別のクラスにする

フォームをnewしたくないので、calcメソッドを別のクラスに移します。別にLogicクラスというのを作って、

public class Logic
{
	/// 計算する
	public string calc(string x, string y)
	{
		int xx = 0;
		int yy = 0;
		if (int.TryParse(x, out xx) == false) return "";
		if (int.TryParse(y, out yy) == false) return "";

		int zz = xx + yy;
		return zz.ToString();
	}
}

フォームのボタンクリック部分を書き換えます。

/// 計算するボタンを押す
private void button1_Click(object sender, EventArgs e)
{
	var l = new Logic();
	textBox3.Text = l.calc(textBox1.Text, textBox2.Text);
}

そうして、テストも書き換えます。

namespace UnitTestProject1
{
	[TestClass]
	public class UnitTest1
	{
		[TestMethod]
		public void TestMethod1()
		{
			var l = new Logic();
			string ans = l.calc("1","2");
			Assert.AreEqual("3", ans);
		}

		[TestMethod]
		public void Test空欄の場合()
		{
			var l = new Logic();
			string ans = l.calc("", "2");
			Assert.AreEqual("", ans);
		}
	}
}

フォーム自体を呼び出さなくなったので、System.Windows.Forms の参照設定はいらなくなります。テスト対象のアセンブリ(この場合は、Form1を含むアセンブリ)を参照設定します。

image

ほどよく、いい感じになってきました。

■ロジックをシンプルにする

さて、calc メソッドは、足し算をするメソッドですが、引数が文字列になっています。文字列の足し算は文字列の連結に見えるし、数値だけを足すようにしたほうがロジックがシンプルになりますよね。なので、calc メソッドの引数を数値に変えましょう。

public int calc( int x, int y)
{
	int z = x + y;
	return z;
}

UI のほうは、数値を渡すように変更をします。

private void button1_Click(object sender, EventArgs e)
{
	string x = textBox1.Text;
	string y = textBox2.Text;
	int xx, yy;
	if (int.TryParse(x, out xx) == false) return ;
	if (int.TryParse(y, out yy) == false) return ;

	var l = new Logic();
	int ans = l.calc(xx, yy);
	textBox3.Text = ans.ToString();
}

次いでにテストのほうも書き換えます。

public void TestMethod1()
{
	var l = new Logic();
	int ans = l.calc(1,2);
	Assert.AreEqual(3, ans);
}

空欄の場合は、UIのところでチェックをしているので、テストを取り除きます。

image

calc メソッドは、必ず数値が渡されるので、ロジックがシンプルになっています。その分、UIで数値以外をチェックしているので、UIが複雑になっていますが。いやいや、これはこれで便利なのです。

■UIで空欄と数値以外でメッセージを表示する

UIの入力時に、空欄のときと、数値以外の2つのパターンでエラーメッセージを表示してみましょう。

private void button1_Click(object sender, EventArgs e)
{
	string x = textBox1.Text;
	string y = textBox2.Text;
	if (x == "")
	{
		MessageBox.Show("xが空欄です");
		return;
	}
	if (y == "")
	{
		MessageBox.Show("yが空欄です");
		return;
	}

	int xx, yy;
	if (int.TryParse(x, out xx) == false)
	{
		MessageBox.Show("xに数値を入れてください");
		return;
	}
	if (int.TryParse(y, out yy) == false)
	{
		MessageBox.Show("yに数値を入れてください");
		return;
	}

	var l = new Logic();
	int ans = l.calc(xx, yy);
	textBox3.Text = ans.ToString();
}

結構長くなりますが、テキストボックスに入力した値をひとつひとつチェックすることができますね(この部分をテスト可能にすることもできるのですが、ここではフォームを手動でテストするということで)。ロジックの calc メソッドでは「計算する」ことだけに集中できます。

■答えが10以上の時に、エラーにする

今度は、計算結果が10以上になったらエラーにする処理を追加してみましょう。10以上になることは、calc メソッド内でしかわからないので、例外処理を入れます。

public int calc(int x, int y)
{
	int z = x + y;
	if ( z >= 10 )
	{
		throw new Exception( "答えが10以上です");
	}
	return z;
}

UI のほうでは、calc メソッドを呼び出したときに例外が発生したらメッセージを表示させます。

try
{
	var l = new Logic();
	int ans = l.calc(xx, yy);
	textBox3.Text = ans.ToString();
}
catch ( Exception ex)
{
	MessageBox.Show(ex.Message);
}

■例外をチェックするテストコードを書く

どうせなので、テストコードも追加します。

 [TestMethod]
public void Test例外発生()
{
	var l = new Logic();
	try
	{
		int ans = l.calc(5, 6);
	}
	catch
	{
		// 例外が発生すればOK
		return;
	}
	// 例外が発生ない場合
	Assert.Fail();
}

例外が発生しない場合に「失敗」になります。

image

■テストコードから書いてみる

今度は、テストコードのほうを先に書いてみましょう。calcメソッドの引数が0未満の場合はエラーにします。

public void Test引数チェック1()
{
	var l = new Logic();
	try
	{
		int ans = l.calc(-1, 6);
	}
	catch
	{
		// 例外が発生すればOK
		return;
	}
	// 例外が発生ない場合
	Assert.Fail();
}
public void Test引数チェック2()
{
	var l = new Logic();
	try
	{
		int ans = l.calc(0, -1);
	}
	catch
	{
		// 例外が発生すればOK
		return;
	}
	// 例外が発生ない場合
	Assert.Fail();
}

引数は2つあるので、テストは2つ必要です。これを実行すると2つ「失敗」します。

image

■テストが通るようにロジックを書き直す

ロジックのほうを直します。テストをしたときにオールグリーンになればOKです。

public int calc(int x, int y)
{
	if (x < 0 || y < 0)
	{
		throw new Exception("数値は0以上を指定してください");
	}
	int z = x + y;
	if ( z >= 10 )
	{
		throw new Exception( "答えが10以上です");
	}
	return z;
}

image

■なぜ、ロジックをUIから分離させるのか?

ここまで試した方はわかると思いますが(いやいや、TDDだから周知の事実かもしれないけど)、分離したほうがテストがしやすいのです。逆にいえば、UIべったりにロジックを入れてしまうとテストしずらい。あるいは、テストできないコードになります。まあ、UI自体をテストすることも可能なので、テストできない訳ではないのですが。それはそれとして、TDDの基本の基本はここからスタートです。ケントベックの「テスト駆動開発入門」は残念ながら絶版扱いになっていますが、このチュートリアル(Phythonで書かれている) を1回通してみると、どこにTDDを使えば「効果的なのか」がわかります。逆に言えば、作業量を考えたときに効果的でないところにTDDを利用してもダメなんです、ということです。

で、私がプログラムを書くときに気を付けるのは、テストがしやすいクラスあるいは設計をする、ところに注力してます。テスト可能なクラス≒テストしやすい設計≒不具合対処がしやすい、というパターンですね。まあ、うまくいかないことも多々あるので、完全にという訳にはいきませんが、コアな部分(ライブラリ部分)をテスト可能にしておくのは必須かな、と常々思っています。まあ、ひとまず、1日目のチュートリアルということで。

カテゴリー: 雑談 | ノーマルにMSTestを使おう はコメントを受け付けていません

VS2013のWPFプロジェクトに「Blendで開く」を追加する方法

Visual Studio 2012からある問題なのですが、WPFプロジェクトからBlendを開くことができません。ストアアプリやSilverlightのXAMLファイルからは開けるのに、何故にWPFプロジェクトからは駄目なのか?は謎なのですが…多分、バグですよね、これ。

Blendで開く操作がプロジェクトの種類によって異なる | Microsoft Connect
https://connect.microsoft.com/VisualStudio/feedback/details/781581/blend
VS2012のWPFプロジェクトに、「Blendで開く」メニューを追加する – SourceChord
http://d.hatena.ne.jp/minami_SC/20130407/1365351086

上記のVS2012で、外部ツールを使ってコンテキストメニューに追加する方法はVS2013でも有効です。なので、まあ自分で使う分にはいいか、ってところなんですが…ストアアプリのプロジェクトには「Blendで開く(B)…」があるのだから、どっかで設定はしているハズなんですよ、と思い直して探してみました。

■コンテキストメニューのカスタマイズ

Visual Studio ではメニューやらコンテキストメニューは、カスタマイズが可能です。メニュー部分を右クリックして「カスタマイズ」を選択、そのあとに「カスタマイズ」ダイアログのコマンドタブを開くと、Visual Studio で利用するメニューを変更できます。

このコンテキストメニューの「プロジェクトとソリューション コンテキストメニュー|項目」に、アイテムを右クリックしたときにコンテキストメニューが割り当てられています。これをどうやって探すのかは不明なんですが…勘で見つけるしかないんですかね?

image

これを見ると、「Blend で開く」が3つ並んでいます。なんで3つ並んでいるのか分かりませんが、「B付き」と「B無し」と、「…無し」ですね。なんか理由があるのかな?と思って、この元ネタを探します。

元ネタを探すのが結構苦労したのですが、「コマンド追加」ボタンをクリックして、「表示」カテゴリの中にあります。ここには「B付き」と「B無し」はあるのですが「…無し」はありません。まあ、良しとしましょう。

image

さて、ここでストアアプリのコンテキストメニューを見てみると、「B付き」のメニューを使っていることが分かります。「B無し」は何処で使っているのかわかりませんが、ストアアプリのプロジェクトと同じものを使えば動くのかな?と想像ができます。

image

そんな訳で、先のカスタマイズで「B付き」のBlendで開くを追加すると、WPFプロジェクトにも「Blendで開く」が無事表示されます。めでたし、めでたしと思い気や、実は違うのです。WPFプロジェクトに「Blendで開く」が表示されるようになったのですが、今度はストアアプリのほうに「Blendで開く」が2つ表示されるようになってしまいます。

こんな風に新しく「Blendで追加」を追加すると…

image

ストアアプリのほうに二重にメニューが出てきてしまいます。

image

という訳で、なんとなくわかりますね。おそらく、最初の「Blendで開く(B)…」が変な登録の仕方をされているようなのです。

そんな訳で、

  1. もともとある、「Blend で開く(B)…」を削除する。
  2. コマンド追加で、「Blend で開く(B)…」を追加する。

という入れ替えをしてやると、無事 WPF でも Blendで開くが表示されます。

image

ちなみに、二重に「Blendを開く」を追加してしまった場合は「すべてリセット」ボタンで、リセットさせてください。何故か、カスタマイズのダイアログを開くと二重にあるはずの「Blendを開く」メニューがひとつしかなくて、目的のメニューを削除できません(これも多分、不具合)。まあ、2つとも同じ名称なのでどちらを削除していいか分からないので、リセットしかないのですが。

■プロジェクトのコンテキストメニュ―に追加

先の方法は、*.xaml の右クリックのコンテキストメニューなのですが、WPF プロジェクト自体のコンテキストメニューにも追加できます。

プロジェクトのほうは「プロジェクトとソリューション コンテキストメニュー|プロジェクト」のほうの「Bあり」のBlendで開くを、削除→追加します。

image

これで、ストアアプリやSilverlightのプロジェクトと同じように、WPFプロジェクトでもBlendと行き来が可能になります…って、これは、VS2013のUpdateで直してほしいところ。

カテゴリー: 開発 | VS2013のWPFプロジェクトに「Blendで開く」を追加する方法 はコメントを受け付けていません

艦これ 諜報員 ver.0.5.1

秘書艦の切り替えがうまくできていなかったので、バグフィックス。編集で「変更」をしたときのタイミングではなくて、執務室を開いたときの ship2 で取得するように変更しました。ブラウザで艦これを開いたあとに、諜報員を開く、そのあとポチポチと執務室と編成を移動させれば、秘書官が変わります…という妙な仕様ですね。

image

最近はデスクトップに五月雨が鎮座しておりふ。

最新の実行ファイルは こちら から。

カテゴリー: 艦これ | 艦これ 諜報員 ver.0.5.1 はコメントを受け付けていません