Visual Studio 2008でQtの開発環境を作る
最近,諸般の事情からVisual Studio 2008という古い処理系でQtの開発環境を構築することになったのですが,幾らかの事情でつっかえたのでメモとして残しておくことにします.今回必要だったのはQT 4系だったので大雑把な手順としては以下です.
- Qt 4.8.4のインストールして環境変数
PATH
にbin
(例えばC:\Qt\4.8.4\bin\bin
)を追加 - Debugging Tools for Windowsのインストール
- Qt Visual Studio Add-in 1.11.1のインストール
- QtCreator 2.7.1のインストール
- 環境変数
WindowsSDKDir
の設定
最後以外は特殊な手続きではありません.Qt本体,Add-inとQtCreatorQt の公式サイトからインストーラを取得して実行すれば十分です.Debugging Tools for WindowsはWindows SDKをWindows Software Development Kit (SDK) for Windows 8のサイトから取得し,インストーラからセットアップを行います.後はQtCreatorのメニューのツール
→オプション
→ビルドと実行
→キット
と進んでコンパイラやデバッガの値を適切に設定します.
さて,問題となるのは最後の部分です.前述の方法で設定を行えばQtCreatorから以下のようなHello World
プログラムがビルドできるようになるはずですが,場合によってはエラーとなっていまいます.
#include <iostream> int main() { std::cout << "Hello World!" << std::endl; return 0; }
内容は以下です.
エラー: LNK1104: ファイル 'kernel32.lib' を開くことができません。
結論から言うと,この問題は環境変数を追加することで解決できます.
WindowsSdkDir=C:\Program Files\Microsoft SDKs\Windows\v6.0A\
ただし,パスは実際にSDKをインストールしたパスとします.問題の原因はQtCreatorが処理系の特定に用いるvsvarsall32.bat
が適切に記述されておらず,SDKが正しく使えない状態になっている事によります.既定ではC:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools\vsvarsall32.bat
を見ることで,上記の環境変数を設定すれば良いと分かります.
Eigenの特異値分解を利用した主成分分析
今日はEigenを利用してデータ分析の手法として良く知られた主成分分析を実装してみます.基本的な部分は以下の記事と同じで,C言語やVBAから利用可能なDLLをビルドしようというものです.
さて,主成分分析は次元のデータベクタ,()について,その共分散行列の固有値問題を解くことで行うことができます.しかしながら,特にである場合,共分散行列の計算に時間がかかるなどの問題があります.こうした場合は特異値分解を利用した方法が推奨されます.具体的には平均減算を行ったデータベクタを横に並べた以下の行列の特異値問題を解くものです.
ただし,は,()の平均ベクタです.このの左特異行列が主成分ベクタに,特異値の二乗が主成分に対応します.
Eigenを利用する場合,実装は非常に簡単であり,その骨子は以下のようになります.
- 配列を受け取って
Map
オブジェクトで行列とみなす rowwise()
とmean()
で平均ベクタを計算- 平均減算した行列を
JacobiSVD
オブジェクトで特異値分解 - 左特異行列から主成分ベクタを,特異値の二乗から主成分を得る
Map
を利用することでコピー操作が不要になり,また,組み込み関数を利用するだけでfor
文などを利用した反復処理さえ不要になります.以下はデータベクタを列優先で行列として与え,平均ベクタと本の主成分ベクタ,それに対応する主成分を計算する関数です.
// This code is licensed under the GPL. // simplepca.cpp #include "eigen_vba.h" // Eigen #include <Eigen/Dense> using namespace Eigen; #ifdef _DEBUG #include <iostream> using namespace std; #endif // 特異値分解を用いた単純な主成分分析 EIGEN_VBA_API int CALL_VBADLL SimplePCA_(int m, int n, int r, double *_a, double *_mu, double *_lambda, double *_v){ // Map オブジェクト (既定通りの列優先) Map<MatrixXd> a(_a, m, n); Map<VectorXd> mu(_mu, m); Map<VectorXd> lambda(_lambda, r); Map<MatrixXd> u(_v, m, r); // 平均を計算する mu = a.rowwise().mean(); // 平均減算した行列を特異値分解する // (ComputeThinUを与えて特異値と左特異ベクタだけを計算する) JacobiSVD<MatrixXd> SVD((a.colwise() - mu), ComputeThinU); // 特異値の二乗と左特異ベクタを返す lambda = (SVD.singularValues()).block(0, 0, r, 1).array().square(); u = (SVD.matrixU()).block(0, 0, m, r); #ifdef _DEBUG cout << "A=[" << a << "];" << endl; cout << "mu=[" << mu << "];" << endl; cout << "lambda=[" << lambda << "];" << endl; cout << "V=[" << u << "];" << endl; #endif return 0; }
ビルドの方法は前回の記事を参照してください.乱数で構成した行列の第二主成分までの計算結果は以下です.
A=[0.423394 0.104033 0.561979 0.988998 0.477972 0.18587 0.0909832 0.281566 0.924881 0.155299 0.271587 0.481722]; mu=[0.363135 0.550947 0.432477 0.302869]; lambda=[0.749016 0.128381]; V=[ 0.170522 0.830388 -0.631153 0.5002 0.70674 0.245461 0.270346 0.00231725];
平均ベクタ,主成分,主成分ベクタが正しく計算されていることが分かります.
FreeBSD 9.1-RC2 公開
FreeBSD 9.1 Releaseに向けたRC2が昨日よりソースコードの形で入手可能になっていたようです(SVNWebのnewvers.sh).前回のRC1はRelease AnnouncementでCVSにエクスポートしないという方針が取られるとのことでした.
With both the doc and ports repositories now moved to SVN it has been
decided to not export the 9.1 release branch activity to CVS. So
csup/cvsup update mechanisms are not available for updating to 9.1-RC1.
If you would like to use SVN the branch to use is releng/9.1.
しかしながら,CVSWebのnewvers.shを見る限り,今回はエクスポートが行われているようです.RC2と同時に開始されるportsのfeature freezeに合わせてローカルのtinderboxを更新するつもりでしたので,既定のcsup
でもソースコードが入手できるのは良いのですが,著者は現在のところ
- ベースシステム → subversion
- tinderbox → CVS
という方法で更新を行っているのでそろそろtinderboxも既定でsubversionを使うようになって欲しいとは思います.
am-utilsを使ったUDF形式のDVDのマウント
今日はFreeBSDでUDF形式のDVDをマウントする際,どのような設定をするのが便利かと言うことについて考えてみます.ここでの論点は以下です.
- root権限を用いないマウント操作は可能か
- DVDをマウントする際に
udf
,cd9660
のいずれを用いるか - 日本語ファイル名を文字化けさせずにマウントするにはどのようにするか
このようなことを考えるのは,先頃,日本語のファイル名を含むディスクからのコピーで幾らかの問題に遭遇したためです.二三の方法を試した限りでの結論を述べれば,以下の方法が良さそうです.
まず,root権限の必要性ですが,FreeBSDにはam-utilsがamd
,Auto Mount Daemonとして添付されていますので,これを使えば良さそうです.二番目のデバイスの選択ですが,UDF Bridgeによる互換性機能を使えば9660
デバイスでもマウントは可能ですが,三番目の日本語ファイル名との兼ね合いからするとudf
デバイスが良さそうです.要するに最も面倒なのは日本語ファイル名を文字化けさせない,という要請につきるようです.
なお,本記事の内容はFreeBSD 9.1-RC1 (amd64)で行い,端末のロケールはUTF-8の設定になっています.
まず,日本語のファイル名を含むUDF形式のDVDを用意し,マウント時に用いるデバイスとKernel iconv使用の違いで文字化けの有無を調べます.
kldload udf kldload libiconv kldload udf_iconv kldload cd9660_iconv mount -t udf /dev/cd0 /cdrom/ && ls -lat /cdrom && umount /cdrom mount -t cd9660 /dev/cd0 /cdrom/ && ls -lat /cdrom && umount /cdrom mount -t udf -o -C=UTF-8 /dev/cd0 /cdrom/ && ls -lat /cdrom && umount /cdrom mount -t cd9660 -o -C=UTF-8 /dev/cd0 /cdrom/ && ls -lat /cdrom && umount /cdrom
結果は以下です.
device | kiconv | 日本語ファイル名の表示 |
---|---|---|
udf |
- | × |
cd9660 |
- | × |
udf |
○ | ○ |
cd9660 |
○ | × |
UDF形式のファイル名の文字コードはそもそもUTFなのですから,Kernel iconvの設定は不要になるように思うのですが,最上段を見る限りそうでもないようです.したがって,udf
とKernel iconvの設定でam-utilsを使うのが良さそうです.
さて,実際の設定方法です.まず,起動時にUDFやKernel iconvのモジュールを読み込むために/boot/loader.conf
に以下を追記します.
udf_load="YES" libiconv_load="YES" udf_iconv_load="YES"
次に以下の内容を/etc/amd.map
に追記します.
udf type:=program;\ fs:=/cdrom;\ mount:="/sbin/mount mount -t udf -o -C=UTF-8 /dev/cd0 ${fs}";\ unmount:="/sbin/umount umount ${fs}";
ただし,空白は適切な数のtab
とspace
に置き換えます.最後に/etc/rc.conf
に以下を追記します.
amd_enable="YES" amd_flags="-a /.amd_mnt -l syslog /am-utils /etc/amd.map"
以下のようにしてamd
を起動します.
/etc/rc.d amd restart
ユーザー権限でマウントを行うには以下のようにします.
cd /am-utils/udf # 自動的にマウントが行われる
マウントを解除するには以下のようにします.
cd # 別のディレクトリに移動する amq -u /am-utils/udf # アンマウント操作 df # 確認する
以上がam-utilsを使って日本語ファイル名を含むUDF形式のディスクを文字化けさせずにマウントするための手順です.
最後になりますが,-C=UTF-8
の部分を-C=EUCJP
にしたところ,ls
の出力をlv
に渡すことで正常な表示が行われました.したがって,端末のロケールがEUC-JPならばKernel iconvを使いさえすればudf
,cd9660
いずれのデバイスを使っても日本語の表示は問題ないと思われます.
findを使った再帰的なファイル属性変更
CD-Rなどの読み出し専用のメディアからコピーしたファイルや,FreeBSDとは異なる処理系で作成されたもので実行属性がついてしまっているファイルの属性を変更したい場合があります.こうした場合はchmod
を利用することになるのですが,数が多い場合やディレクトリの階層が深い場合,作業に一考を要することになります.典型的には以下の要望がchmod
への再帰オプション-R
では解決できないためです.
- ディレクトリに与える属性は
755
- ファイルに与える属性は
644
この場合,find
とxargs
を利用することになります.find
は-type
引数によって列挙するファイルの種類を指定できるからです.したがって,あるディレクトリtargetdir
を再帰的に探索し,下位の階層にあるディレクトリとファイルにそれぞれ755
,644
なる属性を与えるには以下のようにします.
find /path/to/targetdir -type f -print0 | xargs -n 100 -0 chmod 644 find /path/to/targetdir -type d -print0 | xargs -n 100 -0 chmod 755
xargs
を使っているのはfind
が列挙したファイル名が多すぎる場合にこれを適当な(ここでは100)に分割してchmod
に与えるためです.find
とxargs
それぞれに与えている-print0
と-0
引数は空白類をエスケープするためのものです.著者個人は空白類をファイル名に含めることはないのですが,他者から提供を受けたデータ類にはそういったものもあるためです.
ちなみに,find
の引数には有用なものが多くあり,単純な内容であれば-exec
引数によって直接的に何らかの操作を行うことも可能です.また,ファイルの削除程度であれば専用の引数が用意されています.例えばディレクトリtargetdir
を再帰的に探索して*~
なるファイル名を持つものを削除するには以下のようにします.
find /path/to/targetdir -type f -name "*~" -print -delete
rsyncによる文字コード変換を伴うファイル同期
ファイル同期に良く用いられるrsyncにはiconvを利用してファイルの文字コードを変換する機能があります.今日はFreeBSD上でこの機能を利用してみます.
さて,rsyncはFreeBSDのportsに取り込まれており,net/rsync
からインストール可能なのですが,件のオプションは既定では有効になっていません.iconvによる文字コード変換を有効にしてインストールを行うには以下のようにします.
cd /usr/ports/net/rsync make WITH_ICONV=true install clean rehash
ファイル名の文字コードをShift-JIS
からUTF-8
に変換しつつフォルダを同期するには以下のようにします.
rsync -av --iconv=Shift-JIS,UTF-8 /path/to/srcdir/. /path/to/dstdir/.
なお,iconvで利用可能な文字コードは以下のようにすることで調べることができます.
iconv -l
ちなみに実際に行いたかったことは日本語ファイル名を含むCD-Rからのデータのコピーです.このディスクは古いデータの整理を行っている最中に出土したもので,色々試したのですが文字コードが何であるかいまいち良く分からなかったという事情のためです.結論だけ述べるとFreeBSDのKernel iconvと組み合わせることでUTF-8のファイル名でのデータのコピーができました.
kldload libiconv
kldload cd9660_iconv
mount -t cd9660 -o -C=Shift-JIS /dev/cd0 /media/cdrom
rsync -av --iconv=Shift-JIS,UTF-8 /media/cdrom/. /path/to/dstdir/.
Shift-JIS
を経由するのは何となく非効率な気もしますが,こうしたことを行う理由はKernel iconvにおけるファイル名の文字コード変換で変換先をUTFにする場合,その機能に制限があるためです.
Excel VBAのインターフェース継承を試す
Excel VBAには一応クラスの機能がありオブジェクト志向でプログラムを作成することもできます.さて,今日はImplements
キーワードを利用したインターフェース継承を試してみます.インターフェース継承をざっくりと説明すると,クラスモジュールを「扱う側」の処理を共通化することに利用できる考え方となるように思います.多相性の実現が可能となると言えるかも知れません.
実装例として,ある関数で記述される数値の配列をクラス化し,それをグラフとして描画する処理を共通化してみます.そこで,以下のようなものを実装してみます.
- グラフを描画するための関数 (
DrawGraph()
) - 上記関数に与えるインタフェースを定義したクラス(抽象クラス
iGraphData
) - 抽象クラスを継承し,データを保持するクラス(正弦波クラス
cSinusoidal
,直線クラスcLinear
)
まず,グラフを描画するのに必要なデータを考えると以下のようなものがありそうです.
- データの数とデータ
- 横軸と縦軸の名前
- 関数名
これらを取得するためのプロパティを定義した抽象クラスを作れば良さそうです.例えば以下のようにします.
' iGraphData.bas Option Explicit ' データの数を取得する Public Property Get nD() As Long End Property ' i番目のXを取得する Public Property Get XV(tIdx As Long) As Double End Property ' i番目のYを取得する Public Property Get V(tIdx As Long) As Double End Property ' 横軸の名称を取得する Public Property Get strXVName() As String End Property ' 縦軸の名称を取得する Public Property Get strVName() As String End Property ' 関数の名称を取得する Public Property Get strFName() As String End Property
次に上記のようなクラスを与えて,グラフとして描画する関数DrawGraph
を以下のように記述します.
' DrawGraph.bas Option Explicit ' グラフを描画する関数 Public Sub DrawGraph(tSheet As String, tGD As iGraphData) Dim i As Long ' シートを作成する With ThisWorkbook .Sheets.Add After:=.Sheets(.Sheets.Count) .Sheets(.Sheets.Count).Name = tSheet End With With ThisWorkbook.Worksheets(tSheet) ' 前に出す .Activate ' データを書き出す For i = 0 To tGD.nD - 1 .Cells(i + 1, 1) = tGD.XV(i) .Cells(i + 1, 2) = tGD.V(i) Next ' グラフを描画する .Shapes.AddChart.Select With ActiveChart '.HasTitle = True .ChartType = xlLine .Axes(xlCategory, xlPrimary).HasTitle = True .Axes(xlCategory, xlPrimary).AxisTitle.Characters.Text = tGD.strXVName .Axes(xlValue, xlPrimary).HasTitle = True .Axes(xlValue, xlPrimary).AxisTitle.Characters.Text = tGD.strVName .SetSourceData Source:=Range("A1") With .SeriesCollection(1) .Values = Range(Cells(1, 2), Cells(1 + tGD.nD - 1, 2)) .XValues = Range(Cells(1, 1), Cells(1 + tGD.nD - 1, 1)) .MarkerStyle = xlMarkerStyleCircle .Name = tGD.strFName .MarkerStyle = xlMarkerStyleNone ' マーカーを消す End With .Parent.Top = Range("C2").Top .Parent.Left = Range("C2").Left End With End With End Sub
やっていることは横軸の値と縦軸の値を1列目と2列目に書き出し,線グラフとして描画するものです.
さて,どちらかと言えばここからが本題です.クラスiGraphData
は関数DrawGraph
とのデータのやり取りを記述するもので,何の処理も記述していませんから,数値データを保持するための具体的なクラスとしてcSinusoical
を実装します.
' cSinusoical.bas Option Explicit Private mNData As Long Private mValue() As Double Private mXValue() As Double Private mScale As Double Private mTheta0 As Double Private mIsCalculated As Boolean ' インターフェース継承 Implements iGraphData ' データの数を取得する Public Property Get iGraphData_nD() As Long If mIsCalculated Then iGraphData_nD = mNData End If End Property ' i番目のXを取得する Public Property Get iGraphData_XV(tIdx As Long) As Double If mIsCalculated Then iGraphData_XV = mXValue(tIdx) End If End Property ' i番目のXを取得する Public Property Get iGraphData_V(tIdx As Long) As Double If mIsCalculated Then iGraphData_V = mValue(tIdx) End If End Property ' 横軸の名称を取得する Public Property Get iGraphData_strXVName() As String iGraphData_strXVName = "Theta" End Property ' 縦軸の名称を取得する Public Property Get iGraphData_strVName() As String iGraphData_strVName = "Y" End Property ' 関数の名称を取得する Public Property Get iGraphData_strFName() As String iGraphData_strFName = "Sinusoidal Curve" End Property ' コンストラクタ Private Sub Class_Initialize() mIsCalculated = False End Sub ' デストラクタ Private Sub Class_Terminate() End Sub ' 関数値を計算する Public Sub Calc(tXVmin As Double, tXVMax As Double, tDiv As Long, _ tScale As Double, tTheta0 As Double) Dim i As Long ' 領域確保 ReDim mValue(tDiv - 1) As Double ReDim mXValue(tDiv - 1) As Double mScale = tScale mTheta0 = tTheta0 mNData = tDiv ' 計算を実行する For i = 0 To mNData - 1 mXValue(i) = tXVmin + i * (tXVMax - tXVmin) / tDiv mValue(i) = mScale * Sin(mXValue(i) + mTheta0) Next mIsCalculated = True End Sub
このクラスは以下の関数値を計算し配列として保持するものです.
同様に以下の直線の関数値を計算して配列として保持するクラスcLinear
を実装します.
' cLinear.bas Option Explicit Private mNData As Long Private mValue() As Double Private mXValue() As Double Private mAValue As Double Private mBValue As Double Private mIsCalculated As Boolean ' インターフェース継承 Implements iGraphData ' データの数を取得する Public Property Get iGraphData_nD() As Long If mIsCalculated Then iGraphData_nD = mNData End If End Property ' i番目のXを取得する Public Property Get iGraphData_XV(tIdx As Long) As Double If mIsCalculated Then iGraphData_XV = mXValue(tIdx) End If End Property ' i番目のXを取得する Public Property Get iGraphData_V(tIdx As Long) As Double If mIsCalculated Then iGraphData_V = mValue(tIdx) End If End Property ' 横軸の名称を取得する Public Property Get iGraphData_strXVName() As String iGraphData_strXVName = "X" End Property ' 縦軸の名称を取得する Public Property Get iGraphData_strVName() As String iGraphData_strVName = "Y" End Property ' 関数の名称を取得する Public Property Get iGraphData_strFName() As String iGraphData_strFName = "Linear Function" End Property ' コンストラクタ Private Sub Class_Initialize() mIsCalculated = False End Sub ' デストラクタ Private Sub Class_Terminate() End Sub ' 関数値を計算する Public Sub Calc(tXVmin As Double, tXVMax As Double, tDiv As Long, _ tAvalue As Double, tBvalue As Double) Dim i As Long ' 領域確保 ReDim mValue(tDiv - 1) As Double ReDim mXValue(tDiv - 1) As Double mAValue = tAvalue mBValue = tBvalue mNData = tDiv ' 計算を実行する For i = 0 To mNData - 1 mXValue(i) = tXVmin + i * (tXVMax - tXVmin) / tDiv mValue(i) = mAValue * mXValue(i) + mBValue Next mIsCalculated = True End Sub
最後に呼び出し側である関数ExecDrawGraph
を以下のように実装します.
' Main.bas Option Explicit Public Sub ExecDrawGraph() Dim cSin As cSinusoidal Dim cLin As cLinear Set cSin = New cSinusoidal Call cSin.Calc(0, 2 * 3.14, 100, 1.5, -3.14 / 2) Call DrawGraph("sin_curve", cSin) Set cLin = New cLinear Call cLin.Calc(0, 10, 100, 3, 2) Call DrawGraph("linear_func", cLin) End Sub
これらを実行するとシートsin_curve
に正弦波の関数が,linear_func
に直線の関数が描画されます.
内部処理がそれぞれ微妙に異なる二つのクラスcSinusoidal
とcLinear
についてグラフを描画する関数を共通化できたことが分かります.この程度の例では何が利点かは分かりづらいのですが,クラスモジュールと継承を使うことは以下の利点があります.
- データとデータを扱う手段をクラスとしてまとめ込める
- インターフェース継承によってクラスを「扱う側」の処理を共通にできる
データに対する処理が複雑化した場合,似たような処理に共通の実装を用いるにはクラスモジュールを使うべきでしょう.重要なのはインターフェースを継承したクラスの内部処理がどのように実装されていても良いという点です.今回の例ではcSinusoidal
とcLinear
は双方ともデータを配列で保持していましたが,ファイルやシートに記述されたデータを読み出すクラスであっても,データ列が二次元であればiGraphData
を継承することで関数DrawGraph()
によってグラフを描画できます.
ちなみに,cSinusoidal
とcLinear
の中身がほとんど同じであるわけですが,実装継承ができればこの辺りもかなり綺麗になるように思います.とは言え,VBAの仕様上それはできないようなので仕方がないのかも知れません.