球面上の点を平面上にステレオ投影する

先週のさらに続き。先週は、球面上の点を平面上に投影するのに X, Y 座標をそのまま使って水平投影したけど、今度はステレオ投影というのをやってみる。
ステレオ投影は Wikipedia に詳しく載っている。
先週と同じように、下半球の点 300 個を XY 平面(赤道面)上に投影する。やってみたのが下の図。

コードは次の通り。

#!/usr/bin/env ruby
# encoding: utf-8 require 'csv' def sp(x, y, z)
[x / (1.0 - z), y / (1.0 - z)]
end csv = File.open(ARGV.shift, "r") csv.each_line do |line|
x, y, z = line.parse_csv.map{|v| v.to_f }
r = Math.sqrt(x * x + y * y + z * z)
print sp(x, y, z).map{|v| v * r }.to_csv
end

やっつけ仕事なので、ヘルプもないけど、先週の gss_gen コマンド(--cartesian オプション付き)の結果を入力として、投影点の座標を CSV で出力する。

多数の点を球面上に一様分布させる(2)

一昨日の続き。最初と最後の点を極から移動(位置調整)するのを実装した。ついでに、gem 化して RubyGems.org にアップしておいた。gem install gss_generator でインストールできる。
使い方は次の通り。

^o^ > gss_gen --cartesian --relocate 1.0 600 > c600r.csv

--cartesian はデカルト座標系で出力、--relocate は位置調整のオプション。結果は CSV 形式で標準出力に出力するので c600r.csv ファイルにリダイレクトしている。

結果を一昨日と同様の図にしたものを載せる。

最初の点が極(中心)からずれて、一様性が増しているのがわかる。

多数の点を球面上に一様分布させる

ちょっと面白いものを見つけた。

cf. LEDドームのLEDの並びを決めるのに使用した計算式 – jakaladaのブログ

球面上に任意個の点を一様分布させる、座標を求めるもの。一般化螺旋集合(generalized spiral set)というものを使っている。元の資料(論文)はここ。

cf. 多数の点を球面上に一様分布させるソフトウェアGSS Generator

上のリンク先のブログでは Python を使っているので、Ruby でやってみた。

#!/usr/bin/env ruby
# encoding: utf-8

require 'gss'
require 'optparse'
require 'csv'

options = {}
opts = OptionParser.new
opts.banner = "Generate points with uniform distribution on the sphere."
opts.on("-c", "--cartesian", "Cartesian coordinate."){|v|
  options[:cartesian] = true
}
opts.on_tail("-h", "--help", "Show this message."){|v|
  print opts.help
  exit
}
opts.on_tail("-v", "--version", "Show version."){|v|
  puts "v#{GSS::VERSION}"
  exit
}
opts.parse!

r = ARGV.shift.to_f
n = ARGV.shift.to_i

gss = GSS::GSS.new
points = gss.generate(r, n)
points.each do |p|
  if options[:cartesian]
    coord = p.to_cartesian
    print coord.to_csv
  else
    print [p.r, p.theta, p.phi].to_csv
  end
end
# encoding: utf-8

require "gss/polar_point"

module GSS
  class GSS
    def generate(r, n)
      theta_1 = Math::PI
      phi_1 = 0.0

      points = []
      points << PolarPoint.new(r, theta_1, phi_1)
      2.upto(n) do |k|
        h_k = -1.0 + 2.0 * (k - 1) / (n - 1)
        theta_k = Math.acos(h_k)
        phi_k = points.last.phi + 3.6 / Math.sqrt(n) * 1 / Math.sqrt(1 - h_k ** 2)
        phi_k = phi_k.infinite? ? 0.0 : phi_k % (Math::PI * 2.0)
        points << PolarPoint.new(r, theta_k, phi_k)
      end
      points
    end
  end # of class GSS
end # of module GSS
# encoding: utf-8
module GSS
  class PolarPoint
    attr_reader :r, :theta, :phi

    def initialize(r, theta, phi)
      @r = r
      @theta = theta
      @phi = phi
    end

    def to_cartesian
      x = @r * Math.sin(@theta) * Math.cos(@phi)
      y = @r * Math.sin(@theta) * Math.sin(@phi)
      z = @r * Math.cos(@theta)
      [x, y, z]
    end
  end # of class PolarPoint
end # of module GSS

最初のがコマンドで、あとの2つがライブラリ。引数に球の半径と配置したい点の個数を指定すると、各点の極座標を CSV 形式で出力する。

^o^ > ruby -Ilib exe/gss_gen 1.0 600

極座標じゃなく、デカルト座標(XYZ座標)がほしい時には --cartesian オプション。

^o^ > ruby -Ilib exe/gss_gen --cartesian 1.0 600

下の図は、発生させた600個の点のうち下半分の300個を XY 平面上に投影した図。

各点を順に線でつないでみると、螺旋になっているのがよくわかる。

……なんか螺旋が逆になってるな。元論文じゃ球面を下から見たと書いてあるからそのせいか?

ともあれ、それらしいのはできた。ただし、最初と最後の点の座標を調整するのはやってない。時間があったらやってみよう。

SinatraでSECURITY WARNING: No secret option provided to Rack::Session::Cookie.というワーニングが出たときの対処法

Sinatra でアプリを作ってるんだけど、セッションを利用するとタイトルのようなワーニングが出た。もっと詳しく書くと次のとおり。

SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
This poses a security threat. It is strongly recommended that you
provide a secret to prevent exploits that may be possible from crafted
cookies. This will not be supported in future versions of Rack, and
future versions will even invalidate your existing user cookies.

ググってみても見つかるのは、Rails のバージョンを上げろとか、Rack のバージョンを 1.4.1 にしろとかいうのばっかり。Rails は使ってないし、Rack は 1.6.5 なんだけどなあ。そんな中で見つけたのが↓このページ。

cf. SECURITY WARNING: No secret option provided to Rack::Session::Cookie

config.ru で :session_secret に値を設定すればいいらしい。
具体的には次のようにした。

enable :sessions
set :session_secret, 'somethingverysecret1234'

これでめでたくワーニングが出なくなった。

Ubuntuで作ったwheelパッケージをWindowsにインストールする

このあいだ Ubuntu で作った wheel パッケージ、Windows にインストールしてみた。
pure Python なのであたりまえだけど、何の問題もなくインストールできた。

^o^ > pip install myapp-0.0.1-py2-none-any.whl
Processing c:\users\hiro\documents\tmp\myapp-0.0.1-py2-none-any.whl
Installing collected packages: myapp
Successfully installed myapp-0.0.1

動作確認。

^o^ > myapp foo bar baz
['C:\\Python27\\Scripts\\myapp', 'foo', 'bar', 'baz']

^o^ > which myapp
C:/Python27/Scripts/myapp.EXE

へぇ、Windows だと .EXE ファイルができるんだ。

Elixir 練習問題 StringsAndBinaries-7

defmodule Sales do
  def csv_to_list(filename) do
    {:ok, file} = File.open(filename, [:read])
    headers = (IO.read(file, :line) |> parse_csv_atom)
    IO.stream(file, :line)
    |> Enum.map(fn l -> parse_csv(l) end)
    |> Enum.map(fn sales -> Enum.zip(headers, sales) end)
  end

  defp parse_csv_atom(line) do
    String.trim(line, "\n")
    |> String.split(",")
    |> Enum.map(&String.to_atom/1)
  end

  defp parse_csv(line) do
    [id, ship_to, net_amount] = (String.trim(line, "\n") |> String.split(","))
    [String.to_integer(id), String.to_atom(String.trim(ship_to, ":")), String.to_float(net_amount)]
  end
end

IO.inspect Sales.csv_to_list("sales.csv")
^o^ > cat sales.csv
id,ship_to,net_amount
123,:NC,100.00
124,:OK,35.50
125,:TX,24.00
126,:TX,44.80
127,:NC,25.00
128,:MA,10.00
129,:CA,102.00
130,:NC,50.00

^o^ > elixir practice_11_7.exs
[[id: 123, ship_to: :NC, net_amount: 100.0],
 [id: 124, ship_to: :OK, net_amount: 35.5],
 [id: 125, ship_to: :TX, net_amount: 24.0],
 [id: 126, ship_to: :TX, net_amount: 44.8],
 [id: 127, ship_to: :NC, net_amount: 25.0],
 [id: 128, ship_to: :MA, net_amount: 10.0],
 [id: 129, ship_to: :CA, net_amount: 102.0],
 [id: 130, ship_to: :NC, net_amount: 50.0]]

Elixir 練習問題 StringsAndBinaries-6

defmodule Str do
  def capitalize(word) do
    String.to_charlist(word)
    |> to_upper([])
    |> to_string
  end

  defp to_upper([], result) do
    Enum.reverse(result)
  end
  defp to_upper([32|tail], result) do
    to_upper(tail, [32|result])
  end
  defp to_upper([?.|tail], result) do
    to_upper(tail, result)
  end
  defp to_upper([c|tail], result) do
    to_lower(tail, [upcase(c)|result])
  end
  defp upcase(c) when c <= ?Z, do: c
  defp upcase(c),              do: c - 32

  defp to_lower([], result) do
    Enum.reverse(result)
  end
  defp to_lower([32|tail], result) do
    to_lower(tail, [32|result])
  end
  defp to_lower([?.|tail], result) do
    to_upper(tail, [?.|result])
  end
  defp to_lower([c|tail], result) do
    to_lower(tail, [lowcase(c)|result])
  end
  defp lowcase(c) when c >= 97, do: c
  defp lowcase(c),              do: c + 32
end


IO.puts Str.capitalize("oh. a DOG. woof. ")
^o^ > elixir practice_11_6.exs
Oh. A dog. Woof.

Elixir 練習問題 StringsAndBinaries-5

defmodule Str do
  def center(slist) do
    len = longest(slist)
    slist
    |> Enum.map(fn s -> _center(s, len) end)
    |> Enum.each(&IO.puts/1)
  end

  defp longest(slist) do
    slist
    |> Enum.map(&String.length/1)
    |> Enum.max
  end

  defp _center(str, len) do
    l = div(len - String.length(str), 2)
    String.duplicate(" ", l) <> str
  end
end


Str.center(["cat", "zebra", "elephant"])
^o^ > elixir practice_11_5.exs
  cat
 zebra
elephant

Elixir 練習問題 StringsAndBinaries-4

defmodule Calculater do
  def calculate(charlist) do
    parse(charlist)
    |> calc
  end

  defp parse(charlist) do
    tokenize(charlist, [[]])
    |> _parse
  end

  defp tokenize([], result) do
    Enum.reverse(result)
  end
  defp tokenize([32|tail], result) do
    tokenize(tail, [[] | result])
  end
  defp tokenize([head|tail], [cur|rest]) do
    tokenize(tail, [cur ++ [head] | rest])
  end

  defp _parse([opnd1, op, opnd2]) do
    num1 = number(opnd1, 0)
    num2 = number(opnd2, 0)
    {op, num1, num2}
  end

  defp number([], value) do
    value
  end
  defp number([digit|tail], value) do
    number(tail, value * 10 + digit - ?0)
  end

  defp calc({'+', n1, n2}), do: n1 + n2
  defp calc({'-', n1, n2}), do: n1 - n2
  defp calc({'*', n1, n2}), do: n1 * n2
  defp calc({'/', n1, n2}), do: n1 / n2
end


IO.puts Calculater.calculate('123 + 27')
IO.puts Calculater.calculate('123 - 23')
IO.puts Calculater.calculate('100 * 2')
IO.puts Calculater.calculate('100 / 5')
^o^ > elixir practice_11_4.exs
150
100
200
20.0

Pythonのwith構文を覚えた!

基本的な使い方

ファイルを開いて読み込む処理が典型的かな。今まではこうしていた:

f = open('data.txt', 'r')
print f.read()
f.close()

with 構文を使うとこう書ける:

with open('data.txt', 'r') as f:
    print f.read()

開いたファイルは、ブロックを抜けるときに自動で閉じてくれる。

withに対応したクラス

with 構文に書けるクラスはファイルだけじゃない。__enter____exit__ の2つのメソッドを持ったクラス(のインスタンス)なら何でもいい。__enter__ はブロックに入るときに、__exit__ はブロックを抜けるときに呼ばれる。
ちょっと試してみよう。

class Test():
    def __init__(self, hoge):
        print 'init'
        self.hoge = hoge

    def print_hoge(self):
        print self.hoge

    def __enter__(self):
        print 'enter'
        return self

    def __exit__(self, type, value, traceback):
         print 'exit'

with Test('hoge') as t:
    t.print_hoge()

実行結果:

takatoh@apostrophe $ python test_with.py
init
enter
hoge
exit

ブロックの中を実行する前に __enter__ が、あとに __exit__ が呼ばれているのがわかる。
as t: の部分の t には、__enter__ の返り値が代入されるので、self を返している。

ともあれ、1行短くなって見やすくなる。これからはこうやって書くようにしよう。