2014年12月7日日曜日

DELL XPS 13 9333 に Ubuntu 14.04 を入れてみた

一日の仕事の 95%くらいが Eclipseで Javaを書くような案件だと、自分の場合、わりと経験が長く、また工夫も蓄積されている分、Windows PCでも別段問題を感じる事もなく、割と自由に思ったことができる。

ただ最近の職務内容が Linuxベースのクラウド環境やマイコンボードの上で、IDE を使わず LL言語でプロトタイピングするようなタスクが多くなってきて、現場で貸与されている Windows7 デスクトップ機だとミスマッチを感じる状況がかなり多い。

そこで自前で、非 Windows機を持ち込む事にした。

またミーティングなどに持ち歩けるように、ラップトップにしたい。ただし、遊び限定で使ってる MacBook Air はすでに持っているものの、自分の場合、ショートカットに加えて、コンテキストメニューキー(またはShift+F10)と、ニーモニックも最大限活用するタイプなので、OSX は必然的に却下。

となると Linux、特に自宅の開発環境で親しんでいる Ubuntu が相応しかろうという事になる。

入れたマシン
DELL XPS 13 9333 (Windows 8.1 プリインストール版)

XPS 13 には 9333以前に、L321x と L322x というバージョンがあり、これらについてはドライバやら何やら一通り検証済みの Sputnikという DELL 謹製の Ubuntu ディストリビューションが提供されていて、iso をダウンロードすることもできる。

ただ、後継の 9333 については、どうも Sputnik的なものが無いらしく(日本では買えないプリインストール版を除けば)、なので普通の Ubuntuを、ドライバとか足りてるのかどうかわからないまま、試しにインストールしてみることにした。Windows は可愛そうだがつぶす事にしたが、ダメだった場合の時に備えて、購入時にリカバリメディアも付けてもらった。

やったこと

以下、動かすまでにやったこと。唯一の手順では無いだろうし、正しいかどうかすら定かではないけど、参考まで。

* Ubuntu をダウンロードする
ここを見ると、Dell XPS 13 9333 は、Ubuntu 12.04で認証されているらしい。BIOS に Legacy と UEFI があるが、preinstall 版と同じ Legacy を選んでダウンロードした。

* USB メモりに書き込む
16GB の USBメモリに UNetBootin で書き込んだ。ディスクイメージにチェックを入れて、ダウンロードしたイメージを選択。タイプは USBドライブで、ドライブに USBを指定しておく。

あと忘れてはいけないのが、「スペースは、リブートしてもファイルを維持するために使用(Ubuntuのみ)」の欄に、適当な容量を指定しておくこと。 ここを見ると、展開するファイルのサイズ総計より少し多いくらいと書いているので、8192MB を指定してみた。で、「OK」を押下すると、書き込みが始まる。時間は結構かかる。

(ちなみに、この指定を忘れて 0MB のまま作業すると、usb からの live ではちゃんと起動してネットワークもタッチパッドも問題なく動くのに、インストールするとキーボード以外うんともすんとも反応しなくなる。lsmod してみると、何もリストアップされない。)


* live USB で起動する
USB ポートに USBメモリを挿して電源を入れる。起動中に適当なタイミングで F12 を押すと、起動オプションを選べるようになるので、"LEGACY BOOT"の下の"USB Storage Device"を選択。次の画面で "Try Ubuntu without installing" を選択。

問題なく起動。画面が結構きれいなので、少し感動する。

* インストールする
USB から上手く立ち上がったら、ここで wifiを設定しておく。ネットワークに繋がって、トラックパッドとかも使えることを確認したら、"Install Ubuntu 12.04.5 LTS"のアイコンをクリックしてインストール開始。

言語を日本語に指定、「インストール中のアップデートのダウンロード」にチェック、「サードパーティのソフトウェアのインストール」にもチェック。 インストールの種類では、自分の場合、直前に失敗したインストールがあったので、「削除してインストール」を選択。ドライブは一個しか無いのでそのまま。 で、「インストール」押下。

インストール中、所在地、キーボードレイアウト、ユーザ情報などを聞かれるので、適当に指定。かなーり待つが、問題なくインストール終了。

* 再起動
「インストールが完了しました」メッセージボックスで、「今すぐ再起動する」を押下。すぐに立ち上がる。live USB 起動の時に設定した wifi も効いているので、すぐにネットワークにつながる。

* 14.04 にアップグレード
しばらく画面をみてボーっとしていると、14.04にアップグレードできるというポップアップが表示されるので、言われるままに「今すぐアップデート」。
またしばらく待つとアップグレードが終わる。

再起動すると 14.04が立ち上がるが、System program problem detected と表示され、report problem を押下すると gtk-update-icon-cache-3.0が予期せず停止したとのこと。「問題を報告」して続行。再び再起動すると、今度は出ない。その後、特に変わった様子はない。

やらなかったこと

DELLのドライバダウンロードサイトで、XPS 13 9333 用のドライバが提供されてるようだけど、Ubuntu 12.04用らしいので、インストールするのはやめておいた。今のところ特に差し障りない。何かあったら、書き足しておく。

2014年12月4日木曜日

EC2 (Ubuntu) に Yesod を入れてみたメモ

AWSの EC2インスタンス上で、Yesod を動かすのに手間取ったので、メモしておく。

----
EC2 は、Ubuntu を使ってみた。Ubuntu Server 14.04 LTS (HVM), SSD Volume Type - ami-e74b60e6 というものを使用。

Security Group の設定では、TCP の 3000を 開けておく。キーペアもちゃんとログインできる奴を指定する。ちなみに Ubuntu の場合、ユーザ名は ec2-user ではなくて ubuntu になる。

ここまでが前提。以下、本題。

■ Haskell Platform のインストール
まずバイナリを持ってきて、チェックサムを確認する。
$ wget https://www.haskell.org/platform/download/2014.2.0.0/haskell-platform-2014.2.0.0-unknown-linux-x86_64.tar.gz
$ sha256sum haskell-platform-2014.2.0.0-unknown-linux-x86_64.tar.gz
tar の中身が、絶対パスになっているので、/ に移動してから展開する。展開したら、activate-hs を実行する。
cd /
sudo tar xvf /home/ubuntu/haskell-platform-2014.2.0.0-unknown-linux-x86_64.tar.gz
sudo /usr/local/haskell/ghc-7.8.3-x86_64/bin/activate-hs
ここまで、Haskell Platform の指示に従った。

■ 追加ライブラリ等のインストール
Haskell Platform の 指示の中に、「追加のライブラリのインストールが必要かも知れない」、「libgmp.so.10 は必要だ」などとある。いろいろ試行錯誤したところ、以下の追加インストールで上手くようだ。
cd ~
sudo apt-get update
sudo apt-get install gcc
sudo apt-get install libgmp3-dev
sudo apt-get install libz-dev

■ Yesod のインストール
cabal update して、指示通りに cabal-install を入れなおし、yesod-bin をインストールする。

基本的には Yesod サイトの quick start guide の記述と同じだが、--force-reinstalls を指定している。このオプションを付けないと、「--force-reinstalls を付けてやり直せ」といった内容のメッセージと共にインストールが失敗する。

しばらく待つとインストールが終わるが、パスが通ってないので設定しておく。
cabal update
cabal install cabal-install
cabal install yesod-bin --force-reinstalls --max-backjumps=-1 --reorder-goals
PATH=$PATH:/home/ubuntu/.cabal/bin

■ scaffoldを起動してみる
まず適当にワークスペースを作っておく。
mkdir workspace
cd workspace/
yesod init を実行すると、プロジェクト名と永続化のタイプを聞いてくる。ここでは yesod01 、simple とした。
yesod init
yesod init が終わると、最後に起動するためのコマンドが表示されるが、ここでは敢えて別けて実行してみた(意味は変わらない)。
まず生成されたディレクトリに入って、次にビルド。ビルドできたら実行。
cd yesod01/
cabal install -j --enable-test --max-backjumps=-1 --reorder-goals
yesod devel
上手く行けば、コンソールにポート3000で起動したというログが出力され、ブラウザを開くと Welcome to Yesod! のページが表示されるはず。

----
本当は、hsenv か cabal sandbox で動かすつもりだったけど難航したので、とりあえず素で動かしてみた。サンドボックス環境が上手くいったら、続きを書く。

2014年11月17日月曜日

Erlang の遅延評価で素数の無限リスト

昨日のエントリで、素数の無限リストを作る方法の基礎がわかったので、今日は Erlang と PriorityQueue を使っての試行。

Erlang の無限リストを書く場合、デフォで遅延評価ってわけじゃないので、[先頭要素|fun()-> 再帰呼出し end]として記述した上で、例えば[H|F] なら F()と明示的に呼び出して次の[H'|F']を得るという作法になるが、実装できないことはない。

これを活用して、Haskell の Data.Numbers.Primes をパクってみた。

元の Haskell コードを思い切って一言で言ってしまうと、「ホイールで生成した素数候補の無限リストを、PriorityQueue に蓄積された倍数を用いてふるいにかけつつ、同時にPriorityQueue も随時更新していくコード」といったもの。

なのでポイントとしては、ホイールと、無限リストと、PriorityQueue と、「ふるい」そのものって感じだけど、今回はホイール自体の生成は割愛し(簡単だし)ベタ書きのハードコードとした。

こんなんなった。
-module(primes).
-compile(export_all).

enqueue(Ns, Queue) -> merge({ Ns, [] }, Queue).

merge({}, Y)  -> Y;
merge(X,  {}) -> X;
merge(X,  Y)  -> 
  case prio(X) =< prio(Y) of
    true -> join(X, Y);
    _    -> join(Y, X)
  end.

prio({[X|_], _}) -> X.
join({Ns, Qs}, Q) -> {Ns, [Q|Qs]}.

mergeAll([])  -> {};
mergeAll([Q]) -> Q;
mergeAll([Q1|[Q2|Qs]]) -> merge(merge(Q1, Q2), mergeAll(Qs)).

spin(Start, Wheel) ->
  [Start| fun() -> spin(Start, Wheel, Wheel) end].
spin(Prev, [],     Wheel) -> spin(Prev, Wheel, Wheel);
spin(Prev, [W|Ws], Wheel) ->
  Prime = Prev + W,
  [Prime | fun() -> spin(Prime, Ws, Wheel) end].

comps([N|Nl])    -> [N*N| fun()-> comps(N, Nl()) end ].
comps(P, [N|Nl]) -> [P*N| fun()-> comps(P, Nl()) end ].

sieve([N|Ns], {})    ->
  [N| fun()-> sieve(Ns(), enqueue(comps([N|Ns]), {})) end];
sieve([N|Ns], Queue) ->
  {[M|Ms], Qs} = Queue,
  if N < M -> [N| fun()-> sieve(Ns(), enqueue(comps([N|Ns]), Queue)) end];
     true  -> Q = mergeAll(Qs),
              if M == N -> sieve(Ns(),   enqueue(Ms(), Q));
                 M <  N -> sieve([N|Ns], enqueue(Ms(), Q))
              end
  end.

to_lazy([], Another) -> Another;
to_lazy([P|Ps], Another) -> [P | fun()->to_lazy(Ps, Another) end].

primes([P|Ps], Wheel) ->
  sieve(to_lazy(lists:reverse(Ps), spin(P, Wheel)), {}).
  
nth(1, [P|_]) -> P;
nth(N, [_|Pl]) -> nth(N-1, Pl()).

main(N) -> nth(N, primes([7,5,3,2], [4,2,4,2,4,6,2,6])).
実行すると以下のようになった。
2> timer:tc(primes, main, [1000000]).
{67717516,15485863}
100万番目の素数 15485863 を得るのに、1分ちょいかかってしまっているが、まあこんなもんだ。(以前、PriorityQueue を知らずに、同じように Erlang で無限素数リストを作ってみた事があったが、それにくらべると遥かに速い。)

■ コードをざっと解説すると、まず無限リストだけど、ここでは次のものが該当する。
  • 素数候補の無限リスト: 例えば、ホイールが[2,4]だと、5から始めて[2,4,2,4,..]と足していって、[5,7,11,13,17....]を生成していく。下のコードでは、関数 spin/2, spin/3 がこれをやっている。
  • 合成数の無限リスト: 素数候補リスト中のある数と、その数以降の部分リストを用いて、合成数の無限リストを作る。例えば 7ならば、[7*7,7*11,7*13,7*17....] となる。判別された素数ごとに、この無限リストが生成される。comps/1, comps/2 がそれ。
■ 次に PriorityQueue。
Haskell の コードだけ眺めていたときは、さっぱりが訳が分からなかったけど、
sieve [3,5..] Empty から開始して 12,13個の素数を得る過程を、紙とシャーペンの筆算で評価したり図に書いたりしていると、ほぼ体得できた。

元のコードと同様、Pairing Heap 様に構成し、合成数の無限リストのそれぞれの先頭要素を比較して優先度を判定している。

型としては、元の Haskell コードでは、
data Queue = Empty | Fork [Integer] [Queue]
といった形で定義されているけど、ここでは単純に Empty を空タプル{}、Fork を{ 倍数リスト、[倍数リスト] }として表現することにした。
enqueue/2, merge/2, prio/2, join/2, mergeAll/1 が該当する。

■ 3. そして「ふるい」。
与えられた素数候補のリストの先頭要素を、PriorityQueue の頂上にある合成数と比較して、同じなら合成数としてキャンセル、小さければ素数と判定するというコード。素数候補リスト先頭要素が、PriorityQueue 頂上の合成数より大きくなってしまう場合もあるが、その際には PriorityQueue からその合成数を除外して再トライする。

まあ、ざっくり言うとそんな感じだけど、この辺りは自然言語でコード以上に簡潔に説明するのは難しい。

2014年11月14日金曜日

デンドログラムを jQuery + Underscore.js + Canvas で描いてみる

『集合知プログラミング』に載ってる、デンドログラムを書いてみる。 この ブログエントリに display:none で見えなくした div が含まれていて、『集合知プログラミング』のサンプルデータを納めてある。

データの形式は、行に「ブログ」、列に「単語」をとった表形式で、あるブログに含まれる単語の数が集計してあるというもの。

このデータをブラウザ上の Javascript で分析し、ブログ同士でピアソン相関を算出して近いものからペアにしてまとめあげていくと、クラスタができあがる。

それをツリー状に図示すると以下ようなデンドログラムになる。

ピアソン相関が 1に近づくほど(距離が小さいほど)赤みを増すように、canvas の strokeStyle を調整している。
      (function(ch03Plot01, $, undefined) {
        var $this = ch03Plot01;
        function peason(v1, v2) {
          var sums = _.chain(_.zip(v1, v2))
            .reduce(function(acc, vs){
              var a = vs[0];
              var b = vs[1];
              return [acc[0]+a, acc[1]+b, acc[2]+a*a, acc[3]+b*b, acc[4]+a*b];
            }, [0.0, 0.0, 0.0, 0.0, 0.0]).value();
          var n = v1.length;
          var num = sums[4] - sums[0]*sums[1]/n;
          var den = Math.pow((sums[2]-sums[0]*sums[0]/n)*(sums[3]-sums[1]*sums[1]/n), 0.5);

          return den==0 ? 0 : 1.0-num/den;
        }
        function Bicluster(args) {
          this.id = args.id;
          this.vec = args.vec;
          this.distance = args.distance ? args.distance : 0.0;
          this.left = args.left;
          this.right = args.right;
        }
        $this.hcluster = function (rows) {
          var distances = {};
          var currentclustid = -1;
          var clust = _.chain(_.zip(_.range(rows.length), rows))
            .map(function(idRow) {
              return new Bicluster({vec:idRow[1], id:idRow[0]});
            }).value();
          while (clust.length > 1) {
            var lowestpair = [0, 1];
            var closest = peason(clust[0].vec, clust[1].vec);
            for (var i =0; i<clust.length; ++i) {
              var x = _.range(i+10, clust.length);
              var idi = clust[i].id;
              for (var j=i+1; j<clust.length; ++j) {
                var idj = clust[j].id;
                var key = [idi, idj];
                if (!distances[key]) {
                  distances[key] = peason(clust[i].vec, clust[j].vec);
                }
                var d = distances[key];
                if (d < closest) {
                  closest=d;
                  lowestpair=[i, j];
                }
              }
            }
            var mergevec = _.chain(_.range(clust[0].vec.length))
                .map(function(i){
                  return (clust[lowestpair[0]].vec[i]+clust[lowestpair[1]].vec[i])/2.0;
                })
                .value();
            var newcluster=new Bicluster({
              vec:mergevec,
              left:clust[lowestpair[0]],
              right:clust[lowestpair[1]],
              distance:closest,
              id:currentclustid
            });
            currentclustid = currentclustid-1;
            clust.splice([lowestpair[1]], 1);
            clust.splice([lowestpair[0]], 1);
            clust.push(newcluster);
          }
          return clust[0];
        };
      } (window.ch03Plot01 = window.ch03Plot01 || {}, jQuery));
      function remove(arr, index) {
        arr.aplice(arr, index)
      }
      function getHeight(clust) {
        if (!clust.left && !clust.right) return 1;
        else return getHeight(clust.left) + getHeight(clust.right);
      }
      function getDepth(clust) {
        if (!clust.left && !clust.right) return 0;
        else Math.max(getDepth(clust.left), getDepth(clust.right))+clust.distance;
      }
      function drawDendrogram(clust, labels) {
        var h = getHeight(clust)*15;
        var w = 1200;
        var depth = getDepth(clust);
        var scalling = (w-150)/depth;

        var ctx = $('#pci-ch03-dendrogram').get(0).getContext('2d');

        $('#pci-ch03-dendrogram').attr('height', h);
        drawNode(ctx, clust, 10, h/2, scalling, labels);
        ctx.stroke();
      }

      function drawLine(ctx, x1, y1, x2, y2) {
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();
      };
      function rgbToHex(R,G,B) {return toHex(R)+toHex(G)+toHex(B)}
      function toHex(n) {
       n = parseInt(n,10);
       if (isNaN(n)) return "00";
       n = Math.max(0,Math.min(n,255));
       return "0123456789ABCDEF".charAt((n-n%16)/16)
            + "0123456789ABCDEF".charAt(n%16);
      }
      function drawNode(ctx, clust, x, y, scaling, labels) {
        if (clust.id < 0) {
          var h1 = getHeight(clust.left)*15;
          var h2 = getHeight(clust.right)*15;
          var top = y-(h1+h2)/2;
          var bottom = y+(h1+h2)/2;
          var ll = clust.distance*scaling;

          ctx.lineWidth=0.5;
          var lineColor ="#"+rgbToHex(
            Math.floor(127*(2-clust.distance*1.9)),
            Math.floor(64*(clust.distance*1.9)),
            Math.floor(127*(clust.distance*1.9)));

          ctx.strokeStyle=lineColor;

          drawLine(ctx, x, top+h1/2, x, bottom-h2/2);
          drawLine(ctx, x, top+h1/2, x+6, top+h1/2);
          drawLine(ctx, x, bottom-h2/2, x+6, bottom-h2/2);
          ctx.stroke();
          drawNode(ctx, clust.left, x+6, top+h1/2, scaling, labels);
          drawNode(ctx, clust.right, x+6, bottom-h2/2, scaling, labels);
        } else {
          ctx.fillStyle="#AAAAAA";
          ctx.fillText(labels[clust.id], (x+5), (y+2));
        }
      }
      $(function() {
        var lines = $('#blogdata').text().match(/[^\r\n]+/g);
        var colnames = _.rest(lines[0].match(/[^_]+/g));
        var $ol = $('ol');
        var size = null;
        var rowname_and_data = _.chain(_.rest(lines))
          .reduce(function(acc, line){
            var cells = line.match(/[^_]+/g);
            acc[0].push(cells[0]);
            acc[1].push(_.chain(_.rest(cells)).map(function(ch){return parseInt(ch);}).value());
            if (size == null) size = _.rest(cells).length;
            return acc;
          }, [[], []])
          .value();
        var rownames = rowname_and_data[0];
        var data=rowname_and_data[1];
        var clust = ch03Plot01.hcluster(data);
        drawDendrogram(clust, rownames);
      });

書籍では Python を使って jpg ファイルを出力していたが、ここでは計算も描画も Javascript を使ってみた。

jQuery と Underscore.js を使って、Canvas に描画している。

一昔かふた昔くらいまえは、よくこういうのに VB を使った解説とか書籍があったが、今だと Javascript + Canvas が一番お手軽かもしれない。