LAPACK wrapper + Matrix/Vector + Some well-known statistical techniques = Lisys

長らく放置状態になっていた Lisys ですが,コード管理を GitHub に変更しました.コード管理が中途半端なまま放置するのは精神的によくありませんでしたので.

https://github.com/krdlab/lisys

これはなに?

LAPACK のほんの一部を C# 向けにラップ + Matrix/Vector + 少量の分析手法」が含まれたライブラリです.

エントリを掘り返してみると,作成はずいぶん前ですね.

昔,Windows 向けの GUI 分析ツールを作るために作成しました.作成当時は .NET 向けの良いライブラリがなく,「じゃあ LAPACK を利用させてもらおうかな」ということでできあがったものです.
元々は CLAPACK の使い方を学ぶために C++ でコードを書いており,そいつの派生物として作成しました.


しかし今となっては Math.NET Project がありますから,そちらの利用を検討した方が良いかもしれません.DotNumerics というのもあります.これらは pure C# なので Lisys よりも利用が簡単です.

何ができるの?

sample や test を見ていただければ.

64bit 対応が...

既に Visual Studio 2012 が出ているものの,Lisys は Visual C# 2010 Express と Visual C++ 2010 Express を利用して作成しています (2012 が出る大分前から 2008 → 2010 移行を開始したものの,しばらく放置してたらいつの間にか 2012 が出ていた).

Visual C++ 2010 Express Edition は 32bit コンパイラのみであるため,混合アセンブリを生成する Lisys も 32bit 版しか動作確認をしていません *1

LAPACK の Windows サポート

ビルド済バイナリ配布や自分でビルドする方法,C++ から利用する方法等がまとめられています.素晴らしいですね.

今回は上記を参考に CLAPACK から LAPACK 利用に変更しました *2

*1:残念すぎる

*2:CLAPACK は,Windows でしかも .NET から利用するのは割としんどい

CLAPACK-3.1.1-VisualStudio を利用する

長らく Lisys は CLAPACK3-Windows を利用していたのですが,開発環境を Visual Studio 2008 へ移行するのに伴い,CLAPACK-3.1.1-VisualStudio に変更しました.

CLAPACK

問題

しかしまぁ,すんなりといくはずもなく...

CLAPACK-3.1.1-VisualStudio は /MT でビルドされているらしく,/clr でビルドしている Lisys ではリンカエラーが発生します.

// ↓例えばこんな感じのエラー
エラー 8 error LNK2005: _exit は既に MSVCRT.lib(MSVCR90.dll) で定義されています。 libcmt.lib CLW

Lisys を /MT にできればよいのですが,/clr では /MT を指定できません (Lisys は混合アセンブリです).

C ランタイム ライブラリ


よって,CLAPACK-3.1.1-VisualStudio を /MD でリビルドする必要があります.

ビルド環境

Windows XP SP3
Visual Studio 2008 SP1

オプション変更

blas,clapack,libf2c プロジェクトのプロパティを開き,以下の値に変更します.

  • [構成プロパティ]-[C/C++]-[コード生成] の [ランタイム ライブラリ]
    • Release: /MD
    • Debug : /MDd

コード修正

// at F2CLIBS\libf2c\fio.h
...
extern int isatty(int);
...

が,

// Visual Studio 9.0\vc\include\io.h
...
_Check_return_ _CRT_NONSTDC_DEPRECATE(_isatty) _CRTIMP int __cdecl isatty(_In_ int _FileHandle);
...

のリンケージと異なるため,リンク時にエラーが発生します (libf2c プロジェクトの open.c にて発生).

今回は,fio.h の "extern int isatty(int);" をコメントアウトすることで対処します.

ビルド

Debug without wrap Win32/Release without wrap Win32 でビルドします.
大量のエラー&警告が発生します (主にテストプロジェクトで発生) が,blas/clapack/libf2c プロジェクトのビルドが正しくできていれば問題ないと思います.

利用方法

Lisys-0.6.4 の readme.ja にある「■ ライブラリのビルド方法」をご覧ください.
基本的には,clapack_nowrap.lib,BLAS_nowrap.lib,libf2c.lib をリンク対象に追加すれば OK です.

追記 2010/06/29

以下のサイトによると,6 ファイルほどプロジェクトから漏れているようです.

念のため確認してみると,実際には 8 ファイル漏れています (blas.vcproj と BLAS/SRC/*.c を突き合わせて確認).

  • csrot.c
  • drotm.c
  • drotmg.c
  • dsdot.c
  • sdsdot.c
  • srotm.c
  • srotmg.c
  • zdrot.c

上記を blas プロジェクトの Srouce Files に追加すれば OK.上記のやつらは LAPACK-revisions3.1.1.info によると,3.1.1 で加えられた新しい関数っぽい.何で抜けてたんだろう...

lib のシンボル確認は以下の通り.

dumpbin /linkermember hoge.lib /out:hoge.symbol.txt

Lisys-0.6.4

(2012/11/11: github に移りました → http://d.hatena.ne.jp/KrdLab/20121111/1352633124)

Visual Studio 2008 向けにバイナリを作成しました.

(追記 2010/12/19:zip へアクセスできなくなっているので,ここ からダウンロードしてください)


ターゲットは .NET Framework 2.0 のままです.
クラスやメソッド等に変更はありません (ので revision アップです).
だたし,内部で利用している CLAPACK を CLAPACK-3.1.1-VisualStudio に変更しました.
(以前のバージョンはこちら→ http://d.hatena.ne.jp/KrdLab/20080101/1199199036)


C のライブラリをリンクしている都合上,Visual C++ Runtime のバージョンがズレているとアセンブリロード時にエラーが発生します.
エラーが発生した場合は,以下のサイトから Runtime をダウンロードしてください.


また,ヘルプファイルの作成に Sandcastle を使いましたので,多少見た目が変わっています.


お手柔らかに...

Gnuplot を操る (その 2)

http://d.hatena.ne.jp/KrdLab/20070909/1189315658
で作ったやつを更新しました.


Wgnuplot ver. 0.6.1


利用方法についてですが,
コンストラクタに指定するパスを "wgnuplot.exe" から "pgnuplot.exe" に変更しました.

"wgnuplot.exe" と合わせて "pgnuplot.exe" も実行パスに配置する必要があります.

サンプル

using KrdLab.Tools.Wgnuplot;
namespace Test {
  class Tester {
    static void Main(String[] args) {
      // ↓.\pgnuplot.exe 以外の場合はパスを指定する必要あり
      using(Wgnuplot p = Wgnuplot()) {
        p.Send("splot x**2 + y**2");
        System.Console.Read();
      }
    }
  }
}

コンストラクタへのパス指定ですが,例えば以下のような配置の場合は ".\gnuplot\bin\pgnuplot.exe" を指定します.

/${実行ファイルの配置場所}
  ├ hoge.exe    ← gnuplot を利用しているとする
  ├ /gnuplot
  │   ├ /bin
  │   │   ├ pgnuplot.exe
  │   │   ├ wgnuplot.exe
  │   │   ...
  │   └ /contrib
  ...

変更内容

前は wgnuplot に直接メッセージを送っていたのですが,今回は pgnuplot を使ってパイプ経由でメッセージ (コマンド) を送っています.
そこらへんの絡みでちょこちょこ修正しました.

C# で ScriptControl を使って VBScript を実行する

ついでに,C# で実装したクラスのオブジェクトをスクリプト側から利用します.


公開側では,

  • プロジェクト種別を「クラスライブラリ」にして,プロジェクトを作成
  • プロジェクトの [プロパティ - アプリケーション - 出力の種類(U) - アセンブリ情報(I)] で [アセンブリを COM 参照可能にする(M)] を ON
  • プロジェクトの [プロパティ - ビルド - 出力] で [COM 相互運用機能の登録(C)] を ON


以下のような感じで実装 (参考:http://msdn.microsoft.com/ja-jp/library/c3fd4a20.aspx)

    public interface ITest
    {
        String Text
        {
            set;
            get;
        }
    }

    [ClassInterface(ClassInterfaceType.None)]
    public class Test : ITest
    {
        private String text = "ほげ";

        #region ITest メンバ

        public string Text
        {
            get
            {
                return this.text;
            }
            set
            {
                this.text = value;
            }
        }

        #endregion
    }


利用側では,

  • "Microsoft Script Control 1.0" を参照設定
  • 公開側プロジェクトが生成するアセンブリを参照設定


以下のような感じで利用

    class Program
    {
        static void Main(string[] args)
        {
            IScriptControl ctrl = new ScriptControl();
            ctrl.Language = "VBScript";

            ITest test = new Test();
            ctrl.AddObject("Test", test, false);

            ctrl.AddCode("Test.Text = \"ふぉー\"");
            Object value = ctrl.Eval("Test.Text");
            System.Console.WriteLine(value);
        }
    }


VBScript 側のプロシージャに引数を渡す場合は,"Run" メソッドを使う.


"AddObject" メソッドの説明については,以下のページぐらいしか見つけられなかった.
http://www.jose.it-berater.org/smfforum/index.php?topic=1048.0


あと,"AddCode" と "ExecuteStatement" の違いがよくわかりません...orz
どちらもステートメントを入力しないとエラーになりました.

マルチスレッドプログラミング

ThreadPool に仕事を投げ込むタイプのマルチスレッドプログラミングです.
とりあえず簡単なサンプルを書いてテストすることに.


やりたいことは,

  • Person は Task を TaskManager に Post する
  • TaskManager は Task を ThreadPool に投げる
  • Person は自分が頼んだ Task をキャンセルすることができる

ぐらいだろうか.


BackgroundWorker で良いんじゃないか?とも思いましたが,Pool にポイポイ投げ込む感じがほしかったので,今回は使いませんでした.

/// <summary>
/// 丸投げ
/// </summary>
public class Person
{
    private readonly string id;

    public Person(string id)
    {
        this.id = id;
    }

    public string Id
    {
        get { return this.id; }
    }

    // タスクを受ける.
    public void Accept(Task task)
    {
        TaskManager.Instance.Post(this, task);
    }

    // 指定された ID のタスクをキャンセルする.
    public void CancelTask(params String[] ids)
    {
        TaskManager.Instance.Cancel(this, ids);
    }
}

/// <summary>
/// タスク抽象
/// </summary>
public abstract class Task
{
    protected readonly string id;

    protected volatile Thread thread = null;

    protected volatile Exception exception = null;

    protected volatile bool cancel = false;

    protected Task(string id)
    {
        this.id = id;
    }

    public string Id
    {
        get { return this.id; }
    }

    public Exception ThrownException
    {
        set { this.exception = value; }
        get { return this.exception; }
    }

    // タスク実行
    public virtual void Execute()
    {
        this.thread = Thread.CurrentThread;
        if (this.cancel)
        {
            // 事前にキャンセルされた場合
            return;
        }

        // 実行
        Main();
    }

    // タスクのメイン処理
    protected abstract void Main();

    // タスクが完了したときに呼び出す.
    // Main が例外を throw するかもしれないので別メソッド
    public virtual void Finished()
    { }

    // タスクをキャンセルする.
    // すぐには止まらないけど...
    public virtual void Cancel()
    {
        this.cancel = true;

        if (this.thread == null)
        {
            return;
        }
        if (!this.thread.ManagedThreadId.Equals(
            Thread.CurrentThread.ManagedThreadId))
        {
            this.thread.Interrupt();
        }
    }
}

/// <summary>
/// 一応管理クラス
/// </summary>
public sealed class TaskManager
{
    private static readonly TaskManager self = new TaskManager();

    private readonly Dictionary<Object, List<Task>> taskMap;

    private volatile Boolean enable;

    private TaskManager()
    {
        this.taskMap = new Dictionary<Object, List<Task>>();
        this.enable = true;
    }

    public static TaskManager Instance
    {
        get { return self; }
    }

    // client の task として post する.
    public void Post(Object client, Task task)
    {
        if (task == null)
        {
            return;
        }
        AddTask(client, task);

        ThreadPool.QueueUserWorkItem(delegate(Object o)
        {
            try
            {
                task.Execute();
            }
            catch (Exception e)
            {
                task.ThrownException = e;
            }
            finally
            {
                lock (this.taskMap)
                {
                    this.taskMap[client].Remove(task);
                    if (this.taskMap[client].Count < 1)
                    {
                        this.taskMap.Remove(client);
                    }
                }
                task.Finished();
            }
        });
    }

    private void AddTask(Object client, Task task)
    {
        lock (this.taskMap)
        {
            if (!this.enable)
            {
                throw new InvalidOperationException();
            }
            if (!this.taskMap.ContainsKey(client))
            {
                this.taskMap.Add(client, new List<Task>());
            }
            // 同一 ID のタスクが投入されても,そのまま Add する
            this.taskMap[client].Add(task);
        }
    }

    // 指定された client が post したタスク(idで指定)をキャンセルする.
    public void Cancel(Object client, params String[] ids)
    {
        Task[] tasks = new Task[0];
        lock (this.taskMap)
        {
            if (!this.taskMap.ContainsKey(client))
            {
                return;
            }
            tasks = FindAll(client, ids);
        }
        CallCancel(tasks);
    }

    private Task[] FindAll(Object client, String[] ids)
    {
        Task[] tasks = new Task[0];
        lock (this.taskMap)
        {
            if (0 < ids.Length)
            {
                List<Task> targets = this.taskMap[client].FindAll(delegate(Task t)
                {
                    foreach (String id in ids)
                    {
                        if (t.Id.Equals(id))
                        {
                            return true;
                        }
                    }
                    return false;
                });
                tasks = targets.ToArray();
            }
            else
            {
                tasks = this.taskMap[client].ToArray();
            }
        }
        return tasks;
    }

    private void CallCancel(Task[] tasks)
    {
        foreach (Task task in tasks)
        {
            task.Cancel();
        }
    }

    // すべてのタスクを終了させる.
    public void CleanUp()
    {
        Task[] tasks = new Task[0];
        lock (this.taskMap)
        {
            this.enable = false;
            tasks = CollectAllTasks();
        }
        CallCancel(tasks);
    }

    private Task[] CollectAllTasks()
    {
        List<Task> tasks = new List<Task>();
        foreach (List<Task> list in this.taskMap.Values)
        {
            tasks.AddRange(list);
        }
        return tasks.ToArray();
    }
}

無駄かな?もっと良い方法があるのかな?


テストコード

/// <summary>
/// Main
/// </summary>
class Program
{
    static void Main(string[] args)
    {
        Person p1 = new Person("client-1");
        p1.Accept(new TaskA("A1"));
        p1.Accept(new TaskB("B1"));

        System.Console.ReadLine();

        p1.CancelTask("A1", "B1");
        System.Console.WriteLine("Cancel A1 and B1");

        p1.Accept(new TaskB("B2"));
        System.Console.WriteLine("Request B2");

        System.Console.ReadLine();

        p1.CancelTask();
        System.Console.WriteLine("Cancel B2");

        p1.Accept(new TaskB("B3"));
        p1.Accept(new TaskB("B4"));
        p1.Accept(new TaskB("B5"));
        System.Console.WriteLine("Request B2");

        System.Console.ReadLine();

        TaskManager.Instance.CleanUp();
        try
        {
            p1.Accept(new TaskB("B6"));
        }
        catch (InvalidOperationException)
        {
            System.Console.WriteLine("TaskManager.enable == false.");
        }
        System.Console.WriteLine("Clean up");

        System.Console.ReadLine();
    }
}

/// <summary>
/// 5秒タスク
/// </summary>
public class TaskA : Task
{
    public TaskA(string id)
        : base(id)
    { }

    protected override void Main()
    {
        if (this.cancel)
        {
            System.Console.WriteLine(
                "TaskA(id=" + this.id + "): cancel");
            return;
        }
        System.Console.WriteLine("TaskA(id=" + this.id + ")");
        try
        {
            Thread.Sleep(5000);
        }
        catch (ThreadInterruptedException)
        {
            System.Console.WriteLine(
                "TaskA(id=" + this.id + "): throw ThreadInterruptedException");
        }
        System.Console.WriteLine("TaskA(id=" + this.id + "): end thread");
    }
}

/// <summary>
/// 無限タスク
/// </summary>
public class TaskB : Task
{
    public TaskB(string id)
        : base(id)
    { }

    protected override void Main()
    {
        while (true)
        {
            if (this.cancel)
            {
                System.Console.WriteLine(
                    "TaskB(id=" + this.id + "): cancel");
                break;
            }
            System.Console.WriteLine("TaskB(id=" + this.id + ")");
            try
            {
                Thread.Sleep(1000);
            }
            catch (ThreadInterruptedException)
            {
                System.Console.WriteLine(
                    "TaskB(id=" + this.id + "): throw ThreadInterruptedException");
                break;
            }
        }
        System.Console.WriteLine("TaskB(id=" + this.id + "): end thread");
    }
}