いものやま。

雑多な知識の寄せ集め

2021年にやっていきたいこと。

f:id:yamaimo0625:20210101203756p:plain
初日の出@江戸川土手

あけましておめでとうございます! 今年もよろしくお願いします!

ということで、今年やっていきたいことのリストアップ。

仕事

仕事に関しては、数理最適化を使う案件がいろいろ入ってくると思うので、それらをこなして実績を積んでいきたい。 本当は、やったことを公表もできるといいんだけど。 まぁ、企業だとなかなか難しいところもあるので、それはワンチャンあればという感じかな。

そして、データ分析の仕事に関してはまだまだ経験値が足りてないと思うので、地力を伸ばしていきたい。 とくに、ベイズ推定に関して理解できてない部分が大きいので、ちょっと頑張ろうと思う。

他、数理最適化に関してはもうちょっと理論的な部分(凸解析とか)を勉強しなおしたいというのはあるけど、うーん、できるかな・・・

同人活動など

同人活動に関しては、3冊は難しいので、2冊くらい新刊を生やしたい。 コウモリ少女の続編を書きたいんだよなぁ。

あと、 \TeXバイバイするために、いろいろ調べたりアウトプットを出していければなと思ってる。 まぁ、優先度としては仕事に関する勉強(数理最適化や機械学習ベイズ統計)の方が高いので、やるやる詐欺で終わる可能性が高いけど。

体調管理

あとは、体調管理。

これは地道に続けていかないとなので、ふりかえりをしながら意識が持続するようにしていきたいと思う。

とりあえず、以下を続けていく予定:

  • 早寝早起き
  • ラジオ体操
  • 散歩
  • ストレッチ

上記に加えて、新型コロナのせいで運動する量が圧倒的に減っているので、もう少し何か運動できるといいな。 ロードバイクはおそらく乗らないけど(車を運転するようになって、ロードバイクがさらに怖く感じられるようになった・・・あと、落車のトラウマはやっぱり大きい)、普通の自転車に乗る機会はもうちょっと増やしたいところ。

ブログ

最後にブログに関して。

これはあまり気負わずに、ゆるくやっていけたらと思う。 アウトプットの方法はブログだけに限らないので。 同人誌でもいいし、何かOSSをやるでもいいし。

ただ、去年はアウトプットの量自体が減ってしまったので、今年はどういった形でもいいから少しずつアウトプットを積み重ねていくことは意識したいかな。

今日はここまで!

2020年をふりかえってみた。

毎年恒例のふりかえり。 (びばさんに倣って平仮名の「ふりかえり」でw)

去年のふりかえりは以下:

そして、今年やっていきたいと考えていたことは以下:

仕事

まずは仕事に関して。

新型コロナがあったりとかで転職活動も大変だったんだけど、7月から無事働き始めることができた。

新しい仕事は電力関係の仕事で、データ分析や数理最適化を業務で使うことができたので、とてもよかった。 データ分析に関してはまだまだ未熟なのでもっと地力をつけていかないとという感じだけど、数理最適化に関してはチームの中では比較的専門的な立場で成果を出すことができたと思う。 そして、数理最適化を使う案件が増えてきてる感じがあるので、今後も楽しみ。 もっと力をつけて貢献していきたいし、そういったことを通して社会での数理最適化の価値を高めて、自身の市場価値を上げていければなと。

ちなみに、仕事で実際に数理最適化を使ってて課題に思ったのは、不確実性への対処。 確率計画法を調べたりもしたけど、他にもロバスト最適化やオンライン最適化という枠組みもあったりするので、使える道具を増やしていきたい。

他にも、新しい職場ではGCPやGitを使って開発できてるんで、やっと普通の開発環境になったという感がある。

あと、基本的にリモートなのもすごく助かった。 満員電車は本当に疲弊させられるので・・・ 新型コロナが落ち着いたあともリモートは続いて欲しいなぁ。

体調管理

次に、体調管理に関して。

これは新型コロナでいろいろ変わったところがある。

まず、プールには全然行けなかった。 新型コロナで緊急事態宣言が出た頃は、プール自体が休館に。 そして、秋くらいになるとプール自体は再開したんだけど、換気のために窓は全開の状態で、水の中は暖かいけど外に出るとめっちゃ寒く、数回行ってこれは無理だなと諦めた。

あと、リモートワークで通勤がなくなったので、本当に運動量が落ちた。 通勤してた頃は1日に大体6,000歩くらいは歩いてたんだけど、新型コロナで引きこもってからは1,000歩も歩かない日がほとんどになったり。

これはよくないということで、今は昼に家の周りを20分くらい歩くようにした。 本当はもっと歩いた方がいいんだろうけど、これで2,000歩くらいは歩けてる感じ。

他、年初に挙げていたのは、早寝早起きとラジオ体操、それとストレッチ。

早寝早起きについては、転職してからはちょっとできてない感じ。 週末のふりかえりで少し意識すると数日は直るんだけど、どうしても寝るのが遅くなる。。。 何かいい方法があればいいんだけど、地道に改善していく感じか。

ラジオ体操はちゃんと続けられてる。 ストレッチの効果もあって、少しずつ体も柔らかくなってきてる気がする。

ストレッチについてはなかなかできてなかったんだけど、ストレッチ音声を作ったのがよかった。

ちなみに、上の記事だと40分弱の音声を作ったんだけど、40分だと長くて続かなかった。 なので、途中からは20分強に縮めたものを作ってやるようにした。

やってみて分かったのは、30分より短いかどうかって心理的にはけっこう重要だということ。 30分より短いと30分という単位で考えられるけど、30分より長いと1時間という単位で考えてしまうというか。

さらに、最近はアニメを見るついでにストレッチをやるようにしてる。 そうすると「ストレッチするぞ」っていうスイッチを入れる必要がないので、けっこう手軽にストレッチをすることができたり。 (そういう意味でも、30分より短いというのは重要)

まぁ、そんな感じで、完璧にできてたわけではないけど、ふりかえりをしながら少しずつ改善はできた。 まだまだ運動が足りてない感じはあるので、来年も改善を続けていきたい。

ブログ

ブログに関しては、ほとんど記事を書けなかった。 仕事をしてると、なかなか大変。。。

ちょっと試したこととしては、日記ブログがある。 まぁ、結論としてはダメな感じだったけど。

ただ、上記の記事でも書いた通り、得るものはあった。 もっとも、あまり記事は書けてないんだけど。

勉強

勉強に関しては、今年はPythonにだいぶ触れたなという感じがある。 まぁ、結局Pythonを好きになることはなかったけど。 この言語、ホントどうにかならないものか・・・ ただ、相変わらず嫌いではあるものの、とりあえず一通り使えるようにはなったと思う。

数理最適化に関しては、実務で使えてるというのが大きかったと思う。 理論的な部分ももっとやっていきたい感じはあるけど。

あと、今年は統計検定2級をとったりもした。

それに関連して、機械学習には数理最適化の文脈じゃなくてベイズ統計の文脈での研究もあって、そっちはまだキャッチアップできてないので、来年は少しキャッチアップしていきたいところ。

あとは \TeXに関して。 結局、ほとんど勉強しなかったんだけど、組版関連のいろいろな勉強会で話を聞いて思ったのは、やっぱり自分でどうにかしないとダメなのかなということ。  \TeXがツラいのは分かりきったこととして、SATySFiはベター \TeXの域を抜けれてないし、Vivliostyleはブラウザ側の実装の関係でツラい部分が残ってる感じ。 Re:VIEWやPandocはPDFを出力しようとすると結局 \TeXに頼ることになるし。 他には、JupyterでPDFを出力したりもできるみたいなんだけど、力不足感は否めない。。。

同人活動

同人活動に関しては、年初に挙げた目標は、新刊を3冊出すこと。

ただ、新型コロナの影響でリアルイベントがなくなったりで、けっこう影響が大きかった。

結果として、以下の2冊を出すことができた:

本当はもう1冊、コウモリ少女の続きを出したかったんだけど、調査が間に合わなかったりで無理だった。 残念。。。

このあたり、やっぱり仕事をしながらだと難しい感じがある。 なんとか執筆する時間と気力を生む方法を考えていきたいなぁ。

その他

あと、今年大きかったのは、今更ながら車の免許をとったこと。 これでだいぶ生活が変わった。

免許をとったのは今年の1月だったけど、運転の練習を兼ねて、できるだけ車で出かけるようにしてた。 そのおかげもあってか、やっと運転にも慣れてきた感じだけど、代わりに自転車に全然乗らなくなってしまった。 それで運動不足が余計に酷くなってる感じもあるので、来年はもう少しは自転車にも乗るようにしたい。


何はともあれ、転職して経済的に安定して、数理最適化を仕事で使えるようになったりと、いい感じな年だった。

一方で、アウトプットは減ってしまってる感じがあるので、来年はもう少し頑張りたい。

今日はここまで! よいお年を!

天体ショーを整数計画問題で解いてみた。

ペンシルパズルの「天体ショー」を整数計画問題として定式化してソルバーを使って解いてみた。

これは数理最適化 Advent Calendar 2020の19日目の記事です。

天体ショー

ペンシルパズルだとピクロス数独が有名だけど、天体ショーもその一つ。

いろいろなペンシルパズルがあるけど、自分はこの天体ショーが大好き。

  • 問題の図が星を散りばめたようになっていて、まさに「天体ショー」
  • ルールが「点対称」を使ったものになっていて、「点対称」と「天体ショー」を掛けているのが素敵
  • 解き終わったときにイラストが出てくるので、解く喜びが大きい
  • 序盤はサクサクとテンポよく進み、中盤で考えどころがあって、大きなブロックを切り出せると興奮する
  • 問題を作るのも比較的簡単(ドット絵を考えて、点対称な図形で区切っていけばいい)

そこで、天体ショーのソルバーを作ってみることを考えた。

人間が解くようにロジックを組んでいってもいいんだけど、整数計画問題として定式化さえできれば、汎用ソルバーで解くこともできる。 なので、今回は整数計画問題として定式化することを考えてみた。

ただ、検索してみると、すでに先人が。

鉛筆パズルの整数計画法による解法定式化集
(52番目に天体ショーがある)

なので、これを実装するだけかなと思ったんだけど、ちゃんと読んでみると問題があることが分かった。

連結制約

基本的な方針は、あるマスがある星に属するかどうかを0-1変数で表現する先人の方法でOK。 ただ、先人の定式化だと「ある星に属するマスがすべて連結している」という条件(以下、連結制約)をちゃんと表現できていないことに気がついた。

一応、「孤立したマスがない」「2マスだけが繋がったブロックがない」「L字型にだけ繋がったブロックがない」という条件は入っていて、これで大体OKだったらしい。

けど、次のような簡単な問題でも上記の制約だけではダメになる:

f:id:yamaimo0625:20201219224218p:plain

この問題の正しい答えは以下:

f:id:yamaimo0625:20201219225415p:plain

けど、先人の入れた制約だと、以下のような答えも正しいとなってしまう:

f:id:yamaimo0625:20201219230227p:plain

先人の制約はすべて守られているけど、明らかに上のブロックと下のブロックは真ん中のブロックと連結してないので、正しい答えになっていない。

じゃあ、どうすれば連結制約を正しく表現できるのか・・・?

これが想像以上に難しかった。 ああでもない、こうでもないと悩み続け、昨日やっと答えが分かった。

全域木

答えは「ブロックが全域木になっていることを表現する」というもの。

任意のブロックについて、マスを頂点、マスとマスの境界を辺と考えると、これはグラフになっていて、「ブロックが連結である」ことと「グラフに全域木が存在する」ことは同値になっている。 (※ここでは全域木の定義を「連結」で「閉路を持たない」グラフとする。単に「閉路を持たない」グラフは全域森)

そして、ブロックは星のあるマスから伸びていくと考えると、グラフに全域木が存在するなら、星のないマスはすべて、ただ1つの親を持つことになる。 (逆に、星のないマスがすべて、ただ1つの親を持つなら、グラフに全域木が存在する)

そこで、 D = \{\textrm{up}, \textrm{right}, \textrm{down}, \textrm{left}\}として、マス nに対して、方向 d \in Dのマスが親かどうかを、変数 p_{n, d} \in \{0, 1\}で表現することにする。 さらに、星のないマス nに対して、星のあるマスまでのパスの長さを変数 l_n \in \mathbb{Z}_{\ge 0}で表現することにする。

このとき、次の3つが制約として必要になる:

  1. 星のない任意のマスについて、親はただ1つ存在する
     \sum_{d\in D} p_{n, d} = 1
  2. 親のパスの長さは、子のパスの長さよりちょうど1小さい
     -M (1- p_{n, d}) \le l_{n} - l_{n'} - 1 \le M (1 - p_{n, d})
    (※  Mは十分に大きな正の整数で、子 nと親 n'は隣り合っていて、 dは適切な方向とする)
  3. 親は子と同じ星に属する
     p_{n, d} + x_{n, m} - 1 \le x_{n', m}
    (※ 親 n'は子 nに隣接していて、 dは適切な方向とし、変数 x_{n, m} \in \{0, 1\}はマス nが星 mに属するかどうかを表すとする)

2つめの制約はちょっとテクニカルで、 n'が親でないなら -M \le l_{n} - l_{n'} - 1 \le Mとなるので実質意味のない制約になり、逆に n'が親なら l_{n} - l_{n'} - 1 = 0なので子のパスの長さが親よりちょうど1大きいという制約になる。

また、最後の制約は、 p_{n, d} = 1(つまり n' nの親)かつ x_{n, m} = 1(つまり n mに属する)のときに限り、左辺は1となって(それ以外は0以下)、そのときに限り x_{n', m} = 1(つまり n' mに属する)となる(それ以外は右辺は0でも1でもいい)ので、ちゃんと条件を表現できていることが分かると思う。

(2020-12-26追記)
2.は元の制約だと閉路になる場合があったので、修正した。

PICOS

あとは他の制約なども数式で表現してソルバーに食わせればいいだけで、PythonからPICOS(+GLPK)を使って解いてみた。

ちなみに、PICOSはPuLPOR-Toolsのようなライブラリなんだけど、類似したライブラリの中で一番Pythonっぽいコードが書けるので、自分は気に入ってる。 ソルバーもデフォルトでCVXOPTがついてきて、他にもGLPKやSCIP、あと持ってればCPLEXやGurobiも使える。 さらにはLPやMIPだけじゃなくてSOCPやSDPも解ける優れもの。 (知名度だけは低い・・・)

まず、問題はテキストで以下のように書くことにした:

-----------
| | |o|*|o|
--o--------
| | | * | |
---------o-
|o|*|o| | |
-----------
| * | |*| |
-----------
| |o| | |o|
-----------

oが白い星で、*が黒い星。

これを読み込んで情報を保持するために、MarkerクラスとProblemクラスを定義:

from enum import Enum

class Marker:
    class Color(Enum):
        WHITE = 0
        BLACK = 1
    
    def __init__(self, color, row, col):
        self.color = color
        self.row = row
        self.col = col
class Problem:
    @classmethod
    def read_str(cls, contents):
        lines = contents.splitlines()
        row_size = len(lines) // 2
        col_size = len(lines[0]) // 2
        markers = []
        for r_idx, line in enumerate(lines[1:-1]):  # 最初と最後の行は冗長なので無視
            for c_idx, char in enumerate(line[1:-1]): # 最初と最後の列は冗長なので無視
                row = r_idx / 2
                col = c_idx / 2
                if char == 'o':
                    marker = Marker(Marker.Color.WHITE, row, col)
                    markers.append(marker)
                elif char == '*':
                    marker = Marker(Marker.Color.BLACK, row, col)
                    markers.append(marker)
        return cls(row_size, col_size, markers)
    
    @classmethod
    def read_file(cls, filepath):
        with open(filepath) as f:
            contents = f.read()
        return cls.read_str(contents)    
    
    def __init__(self, row_size, col_size, markers):
        self.row_size = row_size
        self.col_size = col_size
        self.markers = markers

そして、解く関数を以下のように定義:
※めっちゃ長い

import numpy as np
import math
import picos

# 方向
directions = ['up', 'right', 'down', 'left']

def solve_by_picos(problem, solver='glpk'):
    # 整数計画問題
    ip = picos.Problem()
    ip.set_objective('find')
    
    # 変数 ==========

    # (row,col)のマスがmarkerに属するかどうか
    belong = {}
    for row in range(problem.row_size):
        for col in range(problem.col_size):
            for i, marker in enumerate(problem.markers):
                belong[row,col,marker] = picos.BinaryVariable(f'belong_{row}_{col}_{i}')

    # (row,col)のマスについて、directionのマスが全域木で親かどうか
    parent = {}
    for row in range(problem.row_size):
        for col in range(problem.col_size):
            for direction in directions:
                parent[row,col,direction] = picos.BinaryVariable(f'parent_{row}_{col}_{direction}')
    # 上端、右端、下端、左端については、各方向が親になることはないので、変数を0に置き換える
    for row in range(problem.row_size):
        # 左端は左が親にならない
        parent[row,0,'left'] = 0
        # 右端は右が親にならない
        parent[row,problem.col_size-1,'right'] = 0
    for col in range(problem.col_size):
        # 上端は上が親にならない
        parent[0,col,'up'] = 0
        # 下端は下が親にならない
        parent[problem.row_size-1,col,'down'] = 0

    # (row,col)のマスの、全域木でのルートまでの距離(明確に属するマスをルートとする)
    distance = {}
    for row in range(problem.row_size):
        for col in range(problem.col_size):
            distance[row,col] = picos.IntegerVariable(f'distance_{row}_{col}', lower=0)

    # 制約 ==========
    
    # 各マーカーについて、明確に属するマスを制約として表現する
    # (距離が0になるという制約も入れる)
    # このとき、明確に属するマスは親が不要なので、変数を0で置き換える
    for marker in problem.markers:
        row_range = range(math.floor(marker.row), math.ceil(marker.row)+1)
        col_range = range(math.floor(marker.col), math.ceil(marker.col)+1)
        for row in row_range:
            for col in col_range:
                ip.add_constraint(belong[row,col,marker] == 1)
                ip.add_constraint(distance[row,col] == 0)
                for direction in directions:
                    parent[row,col,direction] = 0

    # 各マスはいずれかのマーカーに属する
    for row in range(problem.row_size):
        for col in range(problem.col_size):
            belong_sum = sum(belong[row,col,marker] for marker in problem.markers)
            ip.add_constraint(belong_sum == 1)

    # 点対称の位置にあるマスは同じマーカーに属する
    # NOTE:
    # 点対称なマスに関して、以下が成り立つ:
    #   (row + opposite_row) / 2 = marker.row, (col + opposite_col) / 2 = marker.col
    # なので、点対称なマスの位置は、以下で計算できる:
    #   opposite_row = 2*marker.row - row, opposite_col = 2*marker_col - col
    # まず、点対称なマスが枠外になる場合、そのマスがマーカーに属することはない。
    # そして、
    #   opposite_row < row
    # であれば、行についてマーカーを跨いで反対側に来ているので、すでに登録済み(再度登録はしない)であり、
    #   opposite_row == row かつ opposite_col <= col
    # のときも、マーカーのある行の列についてマーカーを跨いで反対側に来ているので、すでに登録済み(再度登録はしない)。
    for marker in problem.markers:
        for row in range(problem.row_size):
            opposite_row = int(2*marker.row - row)
            for col in range(problem.col_size):
                opposite_col = int(2*marker.col - col)
                
                opposite_is_out = (
                    (opposite_row < 0) or (problem.row_size <= opposite_row)
                    or (opposite_col < 0) or (problem.col_size <= opposite_col)
                )
                if opposite_is_out:
                    ip.add_constraint(belong[row,col,marker] == 0)
                else:
                    already_added = (
                        (opposite_row < row)
                        or ((opposite_row == row) and (opposite_col <= col))
                    )
                    if not already_added:
                        ip.add_constraint(belong[row,col,marker] == belong[opposite_row,opposite_col,marker])

    # 親が不要でないマスは、いずれか1つの方向が親になる
    for row in range(problem.row_size):
        for col in range(problem.col_size):
            parent_sum = sum(parent[row,col,direction] for direction in directions)
            if type(parent_sum) != int:
                ip.add_constraint(parent_sum == 1)

    # 親の距離は子よりちょうど1小さい
    # NOTE:
    # distance[row,col]とdistance[parent_row,parent_col]について、
    # a. 親になっているなら 0 <= distance[row,col] - distance[parent_row,parent_col] - 1 <= 0
    # b. 親になっていないなら -∞ <= distance[row,col] - distance[parent_row,parent_col] - 1 <= ∞
    # が成り立つので、十分大きなMに対して
    # -M*(1-parent[row,col,direction]) <= distance[row,col] - distance[parent_row,parent_col] - 1 <= M*(1-parent[row,col,direction])
    # という制約で表現できる。
    M = problem.row_size * problem.col_size
    for row in range(problem.row_size):
        for col in range(problem.col_size):
            for direction in directions:
                if type(parent[row,col,direction]) != int:
                    if direction == 'up':
                        parent_row = row - 1
                        parent_col = col
                    elif direction == 'right':
                        parent_row = row
                        parent_col = col + 1
                    elif direction == 'down':
                        parent_row = row + 1
                        parent_col = col
                    else:
                        parent_row = row
                        parent_col = col - 1
                    dist_expr = distance[row,col] - distance[parent_row,parent_col] - 1
                    ip.add_constraint(dist_expr >= -M * (1-parent[row,col,direction]))
                    ip.add_constraint(dist_expr <= M * (1-parent[row,col,direction]))

    # 親は子と同じマーカーに属する
    # NOTE:
    # directionの方向のマスが親(parent[row,col,direction] == 1)で、
    # 子のマスがmarkerに属する(belong[row,col,marker] == 1)ならば、
    # 親のマスもmarkerに属さなければならない(belong[parent_row,parent_col,marker] == 1)。
    # この論理は以下の制約で表現できる:
    #    parent[row,col,direction] + belong[row,col,marker] - 1 <= belong[parent_row,parent_col,marker]
    # (両方の条件が満たされたときだけ左辺は1になる(それ以外は0以下)ことに注意)
    for row in range(problem.row_size):
        for col in range(problem.col_size):
            for direction in directions:
                if type(parent[row,col,direction]) != int:
                    if direction == 'up':
                        parent_row = row - 1
                        parent_col = col
                    elif direction == 'right':
                        parent_row = row
                        parent_col = col + 1
                    elif direction == 'down':
                        parent_row = row + 1
                        parent_col = col
                    else:
                        parent_row = row
                        parent_col = col - 1
                    for marker in problem.markers:
                        ip.add_constraint(parent[row,col,direction] + belong[row,col,marker] - 1 <= belong[parent_row,parent_col,marker])

    ip.solve(solver=solver)
    
    # 答えの構築
    # 各マスの属するマーカーが入ったndarrayを返す
    answer = np.zeros((problem.row_size, problem.col_size), dtype=np.object)
    for row in range(problem.row_size):
        for col in range(problem.col_size):
            for i, marker in enumerate(problem.markers):
                if belong[row,col,marker].value == 1:
                    answer[row,col] = marker
                    break
    
    return answer

詳細はコメントなどを読んでもらえれば・・・

これでちゃんと連結制約を守って問題を解くことができた。


最後に、自分でも天体ショーの問題を作ってみたので、貼り付けてみる:

f:id:yamaimo0625:20201220002252p:plain

鉛筆片手に解いてもらってもいいし、プログラムで解いてもいいので、楽しんでもらえればと思う。


上記のコードや、問題や解答をPNGで出力するコードは、以下のリポジトリを参照:
※上の問題の答えも入ってるので注意


数理最適化をテーマにした同人誌を書いてるので、よろしければこちらもどうぞ:

今日はここまで!

ペンシルパズル本116 天体ショー1

ペンシルパズル本116 天体ショー1

  • 作者:ニコリ
  • 発売日: 2006/10/10
  • メディア: 単行本

ペンシルパズル本135 天体ショー2

ペンシルパズル本135 天体ショー2

  • 作者:ニコリ
  • 発売日: 2009/02/10
  • メディア: 単行本

未踏ジュニア2020年度最終成果報告会を見てみた。

去年の未踏ジュニアが面白かったので、今年も見てみた。

去年の記事は以下:

感想

去年に引き続き、今年の発表もとても面白かった。 オンラインということでいろいろ大変だったと思うけど、YouTubeTwitterのコメント、質問もちゃんと拾われていて、とてもよかった。

そして、今年もみんなレベルが高い・・・! 技術力もそうだけど、ユーザを思いやる気持ちやプロジェクトをしっかりとやりきる行動力などが本当に凄かった。

どの発表も面白かったんだけど、自分が特に印象に残った発表をいくつか紹介したい。

Mer

多機能電子リコーダー。

これが想像以上に凄かった。

電子リコーダーと聞いて、既存のウィンドシンセと何が違うんだろう?と思ってたんだけど、発表を聞いて納得。

  • 息を吹くだけでなく、吸っても音が出る
  • 管の下側を捻ることでピッチを調整できる

この2つが大きな違いで、とても面白いと思った。 実際に触ってみたい・・・

技術的にも丁寧な作りがされていて、たとえば最初はマイクで入力しようとしてたけど、それだと他の楽器の影響を受けてしまうので、気圧計を使って入力するように変更したとのこと。 さらに、気圧計も吹き込んだ息の湿度で壊れてしまうということがあったので、直接息がかからないように変更したらしい。 よく考えられてるなぁと感心。

発表ではデモも予定されてたようだけど、ちょっとアクシデントがあって残念ながらデモはなしに。 ハードウェア系はこういうことがよくあるから怖い・・・ ぜひともどこかで演奏も聞いてみたいなぁ。

ぶらっしゅとーく

小さい子でも使える筆談アプリ。

小さい頃の体験が動機となって、小さい子やお年寄りでも使える筆談アプリを作ったとのこと。

このアプリを作ったのは小6の子だったんだけど、本当にユーザのことを考えてプロダクトを作っていて、その姿勢がとても素晴らしいと思った。 大人のPdMでも、ここまで真剣にユーザのことを考えてる人はなかなかいないんじゃないかな。

そして、プレゼンがすごく堂々としているのがとても印象的だった。 焦ることなく、ゆっくりとしっかりした声で説明をしていて、これには舌を巻くばかり。 こんなに度胸の据わった小学生がいるのか・・・

spaghetian

リレーで作った自作CPU。

なんだろう、とにかくかっこよかった・・・!

CPUを作るという時点ですでに凄いけど、それを半導体使わずにリレーだけでやるというのが本当に凄かった。 命令セットも自分で考えたらしい。

そして、動作してるときの様子が本当にかっこいい。 カチャカチャとリレーが小気味よく動く音を鳴らしながら、LEDがピカピカと光って計算が進んでいく様子は、最高の一言。 いやー、これはいい・・・

さらにそれを3台繋いで通信までさせてピンポンゲームをやってみせたり、これも凄かった。


ということで、とりあえず3つだけ紹介したけど、他の発表もとても面白かった。

アーカイブも公開されてるので、興味ある人はぜひ見て欲しい。

今日はここまで!

「ロマ数本 発売記念イベント」に参加してみた。

数学にまつわる面白い話をプレゼンするイベントである「ロマンティック数学ナイト」。

そのオフィシャル本が発売され、発売記念イベントが開催されたので、参加してみた。

実は、このイベントの存在を知ったのがチケット販売締め切りの10/23(金)の朝で、ギリギリでの購入だった。 よく調べもせずに買ったので、どんなものかなとちょっと不安でもあったんだけど、予想を遥かに上回る面白さで、買ってよかった。

話題は多岐にわたり、どの話も面白かった。 これは数学的な面白さというよりかは、「自分はこの数学をこんなに愛してるんだ」というパッション迸ってるのが面白かった感じ。 数学が分からなくても(実際、自分も聞いててよく分かってないものが多い)、もっと話を聞いてみたいと思わせるものがあった。 こういうプレゼン能力、凄いよね。

それにしても、ゼータ関数って凄いのね。 複素関数論が自分はちょっとサッパリなので(ちゃんと勉強してればよかった・・・)、ほとんど理解できてないんだけど、凄さだけは伝わってきた。 これに魅入られる人が多いのは理解できる。

あと、最大公約数の話は、「比喩で用語を使うな」というのには全力で頷きながらも、ちょっと捉え方が違うんじゃないかなとも思ったり。

他にも、フ記法とかデカルトヒルベルトの話とか、さらには一般相対性理論の話とか、とても興味深い話が多かった。 これは本も買って読まないとな・・・

今日はここまで!

笑う数学

笑う数学

笑う数学 ルート4

笑う数学 ルート4

QK部 トランプゲーム部の結成と挑戦

QK部 トランプゲーム部の結成と挑戦

「CSS組版 Vivliostyle ユーザーと開発者の集い 2020秋」に参加してみた。

Vivliostyleの勉強会があったので、参加してみた。

3部構成になっていて、1部では開発者側からの最新トピックの紹介、2部ではユーザ側からの事例紹介、3部ではVivliostyleに限らないCSS組版の話題となっていた。

1部を聞いていて、試しに使ってみようかなとも思ったけど、2部、3部を聞くと、なかなか大変そうというのが正直なところ。 そこまで頑張るならもう諦めてWord使うかInDesignで普通に組版するか本気出して \TeXに手を出すかした方がいいのではとも思ってしまう。

これはVivliostyle自身の問題というよりかはブラウザ側の問題なんだけど、じゃあブラウザ使うのやめますとはできないのがツラいところ。 Re:VIEWを使ってて \TeXの問題が出てくるように、Vivliostyle使ってるとブラウザの問題が出てくる感じ。 総じて組版はツラみが多い・・・

とはいえ、 \TeXやSATySFiに比べれば筋はいいと思うので、今後に期待。

まぁ、自分に時間と余裕があれば、一からシステム作りたいんだけどね・・・ (ちょっと手をつけたりもしたんだけど、優先すべきものではないのでペンディング中)

今日はここまで!

『Rubyで作る奇妙なプログラミング言語 ヘンな言語のつくりかた』を読んでみた。

先週、『自作プログラミング言語の集い』という勉強会に参加してみた。

そこで紹介されてた『Rubyで作る奇妙なプログラミング言語 ヘンな言語のつくりかた』という本が面白そうだったので、買って読んでみた。

概要

内容はタイトルの通りで、Rubyを使って変わった仕様のプログラミング言語の処理系を作ってみようというもの。

言語処理系の本って、字句解析や構文解析の話から始まり、けっこう頑張って読む必要があるイメージが強い。

けど、この本はそういうのはすっ飛ばして、「こういうヘンな言語があるよ」という紹介から始まり、とりあえず動く処理系を作る感じになってた。 なので、すごく気楽にスラスラと読めた。

とはいっても、ちゃんとインタプリタコンパイラトランスレータ)、中間言語VMをそれぞれ扱ってて、最後はちゃんと構文木も作っている。 なので、子供騙しというわけではなくて、ちゃんとした内容になっていた。 『RubyでつくるRuby』ですごくガッカリしたのとは正反対な感じ。

扱ってる言語

ちなみに、扱ってる言語は以下:

  • HQ9+
  • Brainf*ck
  • Whitespace

HQ9+はオモチャ言語なのでいいとして、Brainf*ckはインタプリタトランスレータRubyプログラムに変換する)、Whitespaceは中間言語方式の処理系を作る。

Brainf*ckやWhitespace、名前は有名だけど仕様を全然知らなかったので、その詳細を知れてよかった。

意外だったのがBrainf*ckで、とんでもなく分かりにくい言語なのかと思ってたら、全然そんなことなかった。 (手順を踏めば)好きなアドレスにアクセスできるメモリがあって、ちゃんと構造化もされてるとは。 見た目で驚かされるけど、アセンブラに比べたら全然高級な仕様だと思う。 Rubyへのトランスレータが簡単に作れるのも納得。

一方、Whitespaceはもっとアセンブラに近い感じ。 中間言語に変換してたけど、ほぼアセンブラなので簡単。 VMもスタックマシーンで実装簡単だし。

本のオリジナル言語

本ではさらにオリジナル言語を考案して実装するというのをやってる。

  • Starry
  • Bolic

Starryは見た目がAAの星空みたいに書ける言語。 アイディアの段階から実際にそんなふうに書けるのかを確かめながら実装を進めていく様子が楽しめた。

そしてBolicはユニコードの記号で書く言語。 これは構文解析して構文木を作るのまでやるので、少し本格的な感じだった。

その他

さらにオマケとしていくつかの言語が紹介されてる。

凄かったのはGrass。

これも言語としては知ってたけど、どういうふうになってるのかまでざっくりと説明されてた。 書きやすいかどうかは別として、関数型言語は奥が深い・・・


そんな感じで、とても面白い本だった。 商業誌だけど、ノリは同人誌っぽくて、読んでてとても楽しい本だった。

こういう本読むと、やっぱりプログラミング言語作りたいなぁってなるよね。 (Lucidから目を背けつつ・・・リソースが足りない)

今日はここまで!