関数の初等な連続性の距離空間への一般化
距離空間
距離空間とは、次の公理を満たす距離関数(distance function) を持つ集合 のことをいう。
つまり、
- 2点 間の距離は 以上
- 2点 間の距離が であることは、 と は同じ点
- 距離関数の引数を入れ替えても、2点間の距離は変わらない
- を経由すると、2点 をまっすぐ進むよりも距離は大きいか、等しくなる
という自然な距離の性質を満たす関数 が定義されている集合のことを距離空間という。
距離空間は、考える集合 とその上の距離関数 の2つを組みにしたもの として表される。
例えば、実数 の上に、 と の差の絶対値 を距離関数として入れれば、その は距離空間になるし、2次元ユークリッド平面 上に を入れたもの も距離空間になる。
注意して欲しいのは、上の定義を満たすような集合と距離関数が与えられれば、それは距離空間と言われる、ということである。
つまり、自然に感じられないような定義、例えば、集合 上に、
というような関数を定義してやると、これは距離関数となり、 は距離空間となる(確かめるのは簡単なので、試してみて欲しい)。
関数の連続性
実数空間 上の関数の連続性とは、理系の大学1年生ならならうかもしれないが、-論法という形で表される(-もあるけど、ここでは割愛)。
つまり、関数 が点 で連続(continuous)であるとは、任意の正の実数 を考えたとき、ある
を満たすような正の実数 が存在することをいう。
すなわち、どんな距離 をとっても、 との距離が となるような全ての点 に対して と との距離が より小さくような が存在すれば、関数は連続であると主張している。
wpa_cliに渡せるアクションスクリプト
wpa_cliはwpa_supplicantからのイベントを待ち受けて実行されるアクションスクリプトというものを設定できる。 このアクションスクリプトは第二引数にwpa_supplicantからのイベントが渡される。
イベント | 意味 |
---|---|
CONNECTED | APへの接続時に発行される |
DISCONNECTED | APから接続解除されたときに発行される |
これを使って、次のようなスクリプト(ここではhoge.sh
)を作ってやり、
case "$2" in "CONNECTED") echo "APへ接続されたときの処理。DHCPとかを連続して呼びたいときとか";; "DISCONNECTED") echo "APから接続解除されたときの処理、クリーンアップとか";; esac
wpa_cliの-a
オプションの引数として指定してやると、wpa_supplicantからイベントが呼ばれるたびに、登録したスクリプトが発行される。
wpa_cli -B -i wlan0 -a hoge.sh
みたいにしてやると、バックグラウンドで動作しつつ、wlan0
インターフェースを監視して、wpa_supplicantからイベントが来たら、hoge.sh
が呼ばれる。
オプション | 意味 |
---|---|
-B |
wpa_cliをバックグランドで起動するようにする |
-i wlan0 |
操作対象のWi-Fiインターフェースを指定する。wlan0 に好きなインターフェース名を入れる |
問題は、アクションスクリプトに実行権限が付いているかどうかという部分。 気付かないと時間が溶ける(溶けた)。 この手のスクリプト名を指定しておいて実行させるようなコマンドは気を効かせて、bashコマンドとかでスクリプト名指定して確実に走らせるようにしたりすることが多い(気がする)、のだけど、このwpa_cliはそんなことはしてくれない。 実行権限を適切に付けてやらねばならない。
chmod +x hoge.sh
原因
理由はわかりきっているが、きちんと書いておく。 なお、適宜いらない部分は削除している。
まず、main
関数のオプション解析部で、-a
に渡されたオプション引数をaction_file
という変数に収めている。
/* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n54 */ static const char *action_file = NULL; int main(int argc, char *argv[]) { /* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n4641 */ for (;;) { c = getopt(argc, argv, "a:Bg:G:hi:p:P:s:v"); if (c < 0) break; switch (c) { case 'a': action_file = optarg; break; } } }
main
関数内の前処理が終了したら、action_file
変数を見て、スクリプトが指定されていたらwpa_cli_action
関数へ処理を移す。
int main(int argc, char *argv[]) { /* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n4724 */ if (action_file) wpa_cli_action(ctrl_conn); }
このwpa_cli_action
が、wpa_cli_action_receive
をAP接続イベントが発生したときのハンドラとして登録しておく。
/* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n4533 */ static void wpa_cli_action(struct wpa_ctrl *ctrl) { /* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n4544 */ eloop_register_read_sock(fd, wpa_cli_action_receive, ctrl, NULL); }
APへ接続できたら、wpa_cli_action_receive
が呼ばれる。
wpa_cli_action_receive
はwpa_cli_recv_pending
を単に呼びだすだけ。
/* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n4525 */ static void wpa_cli_action_receive(int sock, void *eloop_ctx, void *sock_ctx) { /* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n4529 */ wpa_cli_recv_pending(ctrl, 1); }
このwpa_cli_action_recive
がwpa_cli_action_process
を呼ぶ。
/* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n4185 */ static void wpa_cli_recv_pending(struct wpa_ctrl *ctrl, int action_monitor) { /* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n4191 */ while (wpa_ctrl_pending(ctrl) > 0) { if (wpa_ctrl_recv(ctrl, buf, &len) == 0) { if (action_monitor) wpa_cli_action_process(buf); }
wpa_cli_action_process
が内部でwpa_cli_exec
を呼ぶ。
/* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n3907 */ static void wpa_cli_action_process(const char *msg) { /* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n3939 */ if (str_starts(pos, WPA_EVENT_CONNECTED)) { if (wpa_cli_connected <= 0 || new_id != wpa_cli_last_id) { wpa_cli_connected = 1; wpa_cli_last_id = new_id; wpa_cli_exec(action_file, ifname, "CONNECTED"); } } }
wpa_cli_exec
は、os_exec
を呼び出してプログラムの実行を行っている。
/* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n3884 */ static int wpa_cli_exec(const char *program, const char *arg1, const char *arg2) { /* https://w1.fi/cgit/hostap/tree/wpa_supplicant/wpa_cli.c#n3900 */ res = os_exec(program, arg, 1); }
このos_exec
関数はutils/os.h
内で定義されていて、wpa_cli.c
はutils/common.h
をインクルードしているため、この関数が利用できる。
関数本体はutils/os_internal.c
に定義されていて、この関数が子プロセスを生成してexecv
を実行している。
/* https://w1.fi/cgit/hostap/tree/src/utils/os_internal.c#n503 */ int os_exec(const char *program, const char *arg, int wait_completion) { /* https://w1.fi/cgit/hostap/tree/src/utils/os_internal.c#n508 */ pid = fork(); if (pid == 0) { execv(program, argv); } }
ここまで見るとわかるけど、bash
やsh
を引数に渡していない。
当然、実行権限が付与されていないと駄目になるという話だった。
bashスクリプトの位置を取得する
bashスクリプトをプロジェクトルートを起点として走らせたいことがある。 そこで、bashスクリプトが格納されている絶対パスをとりたくなるが、これは次のようすると取れる。
SCRIPT_PATH="$( cd "$( dirname "$( readlink -f "$BASH_SOURCE" )" )" >/dev/null 2>&1 && pwd )"
解説
$BASH_SOURCE
でbashスクリプトの実体へのパスがとれる。
しかし、$BASH_SOURCE
はシンボリックリンクの解決が行われていない状態のパスが入っているので、readlink
コマンドで解決させている。
ただ、これでも不十分で、パスの間に挟まっているシンボリックリンクも解決する必要がある。そこで、readlink
に-f
を指定して、階層の途中にあるシンボリックリンクも全部解決してもらっている。
これはGNU Core Utilitiesで利用できるがMacでは利用できない。
readlinkを再帰的にするような関数を自前で実装するか、GNU Core utilitiesをインストールするなどしなければならない。
最後に、直したパスからdirname
コマンドでbashスクリプト名を剥がし、そのディレクトリへ移動してpwd
すると絶対パスが得られる。
JREのスリム化
Dockerなどで、Javaの実行環境を小さくしたいというのはある。
そいうときに使えるのがjlink
とjdeps
(リンク先はJava 10のもの)。
精確にはjdeps
はライブラリの依存関係を調べるためのツールで、jlink
は実行環境を作るためのツール。
これらは$JAVA_HOME/bin
にある。
Arch Linuxであれば、/usr/lib/jvm/default/bin
の下にある。
この2つを使って小さなJava実行環境を作っていく。
なお、記事執筆時点でのバージョンはJava 11.0.3。
環境はArch Linuxでやってるので、当然なが他の環境では$JAVA_HOME
は違うので、
find / -iname jdeps
をシェルに実行させるなりして探して欲しい。
Javaプログラムの依存関係を調査
まず、プログラムを動かすのに必要な標準ライブラリをjdeps
で調べる。
$ jdeps --list-deps *.jar
--list-deps
はプログラムが参照しているモジュール(java.base
など)を表示するためのオプション。
得られたモジュールリスト(たとえば、プログラムがjava.base
とjava.desktop
に依存していたとする)を元に、jlink
でJavaの実行環境をminjre
下に作成する。
$ jlink --compress=2 --module-path $JAVA_HOME/jmods --add-modules java.base,java.desktop --output minjre
モジュールの情報は.jmod
拡張子がついたファイルに収められていて、基本的なモジュールについての情報を収めたファイルは$JAVA_HOME/jmods/
の下にある。
絶対に必要なオプションが--module-path
と--add-modules
の2つで、
--module-path modulepath
: モジュールの情報が収められたファイルのあるディレクトリmodulepath
を使用するモジュールパスとして設定--add-modules module[,module..]
: JREに追加したいモジュールmodule
を設定。複数個指定する場合はmodule1,module2
のようにカンマ(,
)区切り
を指定しなければならない。 また、
--output output_dir
: 出力先output_dir
を設定。指定したディレクトリの下にJRE環境ができる--compress=n
: 圧縮するかどうかを設定する0
: 圧縮なし1
: 定数文字列を共有させる2
: ZIPによる圧縮
は必要あれば設定して欲しい。
以上で、最小限のJRE環境を作ることができる。
自分で試したときはjava.base
とjava.xml
、それにjava.naming
をcompress=2
でバージョンがJava 11で作成したが、JREの環境自体は52MB程になった。
標準だと245MBくらいなので、大分小さい環境ができた。
CrystalのIndexableの実装
本家のAPIドキュメントに Indexable(T)
の詳しい実装方法がなくて苦戦した。
最初は #unsafe_fetch
だけを実装すればよいのかと思ったが、どうも、#unsafe_fetch
を実装しただけだとスタックオーバーフローを起こしてプログラムが死ぬ。
不思議に思ってドキュメントを見直したところ、単純に#unsafe_fetch
と #size
の2つのメソッドを実装する必要があった(ドキュメント内でabstraft def
を検索すればよかった)。
他の Hogeable
系は 「#foo
メソッドを実装してね」とかちゃんと書いてあるのに、これだけ書いてない...
以下、いらないかもだけど、実装例
class Klass(T) include Indexable(T) def initialize(n : Int) @n = n @buffer = Pointer(T).malloc(n) end def size @n end def unsafe_fetch(index : Int) @buffer[index] end end
集合の濃度について
2つの有限集合とが同じ数の元を持つとは、に含まれる元の個数とに含まれる元の個数を数えればわかる。
また、有限集合が有限集合以上の元を持つことは、集合の元の個数をと表すことでと表すことができるし、は有限集合が有限集合よりも元を多くもっていると言える。
この関係は集合の要素の個数を一般化したときにどうなるのだろうか?
また、どのようにこの個数の一般化はなしとげられるのだろうか?
今回はここについて記しておく。
準備
まず、集合の元の個数の一般化にあたって必要な全単射と逆関数、それに合成関数について記す。
集合からへの関数が全単射であるとは、一言で言えば、がとの要素を一対一で対応付けているということを意味している。
厳密に言えば、関数が
を満たすことをいう。
が全射であるとは、どのに対しても、必ず対応するがあることを言っている。
すなわち、写される先のどの元も、関数によって写されるもとが存在することを意味している。
また、が単射であるとは、どのに対しても、必ず、写された先が等しい()ならば、でなければならないことを意味している。
これは、関数によって写される先は重ならないということを意味する。
以上から、全単射はどの写される先の元も写されるもとが存在し、しかも写された先は重ならないという良い性質を持っている。
この全単射な関数の写す先と写されるもとを対応づける関数を逆関数という。
この関数がという性質を持つことは明らかであると思う。
また、関数で写したものをで写すということが考えられる。
これによって、の元はへ写されることとなる。
つまり、というものが考える。
これは関数の合成と呼ばれ、合成された関数を合成関数と呼び、と表される。
実は、とが全単射ならば、合成関数は全単射となることが知られている。
以上の定義をもちいて、濃度について調べていく。
濃度
先程の全単射な関数を考えることで、集合が同じだけの元を持つということを定義できる。
全単射な関数が1つでもあれば、集合同士は過不足なく、しかも重複もなく元を対応づけることができる。
これによって、集合どうしが同じだけの元を持つということが言える。
つまり、とに全単射な関数が少なくとも1つでもあれば、とは対等であるという。
対等であることは記号を使って、と表される。
この関係には次の性質が存在することは簡単に確認できる。
簡単に言えば:
- : という関数を考えると、これは全単射
- : からへは全単射な関数が少なくとも1つ存在するので、その逆関数を考える
- : からへ全単射な関数が存在し、からへの全単射な関数が存在するので、その合成関数は全単射となる
ということになる。
この関係によって、集合は対等なものと対等でないものにわけることができる。
つまり、集合がであって、さらに集合がであれば、になるし、集合とが対等でなければ、それは同じだけの要素を持っていないので別のものであるとみなせる。
これをさまざまな集合に対して行えば、対等なものどうしのグループにわけることができる。
分類したグループに対して、
- 同じグループの集合には同じしるしをつける
- 異なるグループの集合には異なるしるしをつける
という規則のもと付けたしるしのことを濃度といい、Aの濃度はと書かれる。
とが同じグループに入る、つまり、ならばであるし、逆にであれば、同じグループに入っているので、となる。
しるしとしては、例えば、つまりが個の元を持つ元であればというしるしをつければよい。
可算集合
有限集合にはしるしとして自然数を割り当てた。
しかし、自然数全体の集合や実数全体の集合などの要素が無限にある集合の濃度には自然数というしるしをつけることはできない。
一般にの濃度にはという記号がわりあてられる。
は"アレフゼロ"と読む。
つまり、の濃度を持つ集合はと対等だし、と対等な集合の濃度はとなる。
このと対等な集合のことを可算集合あるいは可付番集合という。
なお、有限集合と可算集合を、合わせて高々可算な集合という
可付番な集合としては
- 整数全体の集合
- 有理数全体の集合
- 整数係数のみをもつ代数方程式の解となる数(これを代数的数という)の集合
がある。
- : と並べて前から順々に自然数を割り当てていく
- : まず、正の有理数だけを考え、下図の矢印の順番に自然数を割り当てればよい。従って、正の有理数全体の集合と自然数全体の集合は対等となる。あとは負の有理数と正の有理数を、整数と同じように自然数を割り振り直す
- 代数的数の集合: と仮定しても一般性を失しなわないので とする。一般に方程式において、なる自然数をその方程式の高さという。高さはあきらかに2以上であるし、高さが同じ方程式の数は有限個となる。なぜならば、高さを1つ決めれば、とならなければならないからである。さらに、方程式の解の個数は最大でもなので、代数的数も有限個しかないことがわかる。
意外なことに、と、との濃度は同じとなる。
また、可算集合の無限部分集合は可算となる。
これはの中から、にはいっていないものを除いて、残ったものに小さい順に自然数を割り当てればよいことからわかる。
さらに、高々可算な集合の和集合もまた高々可算な集合となる。
- もも有限集合とするこのとき、あきらかにも有限個しか元を持たない
- かのどちらか一方が有限集合で、もう一方が可算集合とする。なお、ここではを有限集合、すなわち、他方、を可算集合、すなわちとする。の元をと並べて、前から自然数を割り当てればよい
- もlも可算集合とする。すなわち、とする。このとき、というように並べて、前から自然数を割り当てればよい
どの場合も重なるものは飛ばして考えればよい。
非可算集合
やは同じ濃度であったが、実数全体の集合の濃度は同じとなるのだろうか?
実は同じとはならないことがカントールによって証明されている。
このの濃度はと書かれるが、これがと等しくないことを簡単に証明してみる。
それには、の部分集合に可算でないものがあることを証明すればよい。
そこで、が可算でないこと示す。
まず、が可算であると仮定する。
の元はからまでの自然数を用いて、と表せるので、
とならべる。
このうち、の部分に対し、
として、数を定義する。
明らかになので、はあると等しくなる。しかし、の定義からどのに対してもは等しくない数を桁目に含んでいる。
よって、。
しかし、これは矛盾となるので、は可算集合とはならない。
さらに、からへの関数全体の濃度はでもでもない。
ここではこのことについて考えてみる。
まず、どんなに対しても定数を割り当てる関数を考えると、。
このような関数全体の集合をとすれば明らかに任意のはなので、はの部分集合となる。
はあるに対して定義されており、それが全ての実数に対して定義されているので、とは一対一対応している。
すなわち、。
ここから、は高々可算な集合でないことがわかる。
高々可算な集合であれば、その全ての部分集合もまた高々可算となるが、は高々可算な集合ではないからである。
次に、とが対等でないことを示す。
今、からへの全単射の関数があると仮定する。
すると、任意のの元はあるからで写されたものとなる。
これをとする。
ここで、任意のに対し、を考える。
これは実数を受けとって、実数を返しているので、。
よって、これはあると等しい。
つまり、。
とくに、。
しかし、これはであることに矛盾。
よって、との濃度は等しくない。
以上から、の濃度はでもでもない。
濃度の大小
有限集合においては、の濃度がの濃度よりも小さいということは、の中に、と対等な真部分集合(つまり、かつはの部分集合)があるといことになる。
このときに限ってが成立することとなる。
しかし、この定義を無限集合にそのまま適用することはできない。
たとえば、ではあるものの、先程示したようにもも濃度はである。
よってそのままこの定義を無限集合へと延長して利用することはできない。
そこで、濃度、及びを持つどんな集合()にたいしても、なるがあれば、はよりも大きくない、あるいははよりも小さくないといい、あるいはと書かれる。
とくに、かつのとき、はよりも小さい、あるいははよりも大きいといい、と書く。
これは有限集合でも無限集合でも濃度を上手く比較することができる。
しかし、これはとを持つ任意の集合について成立しなければ、が成立しないということには注意しなければならない。
ここで、とし、なるがあるとすれば、となるが存在する、という性質について言及しておく。
まず、からからへの一対一対応するがある。
の元をによって写した先の全体をとすると、で、。
このをとおくと、で、しかも、とから。
よって、。
また、ということが示せる。
まず、となる集合を考える。
から、かつとなるがある。
からへの一対一対応を、の元をで写したもの全体をとする。
すると、かつ、。
ゆえに、。
これはということを示してる。
以上から、まとめると、からへの関数全体の集合の濃度をとすると、
であって、
- はの部分集合
- からへの関数全体の集合証明の中で利用した、任意のをあるに対応づける関数の集合とは対等()
という事実から、という事実が得られる。
なお、となるがあるかどうか、またとなるがあるかは解決不能であることが知られている。
関数の閉包の凸包と凸包の閉包は等しいか
関数の閉包の凸包と凸包の閉包は等しいとは限らない.
その例を記す.
準備
話を始める前にいくつか定義しておく.
まず, 次のような非負実数だけを集めた集合を定義しておく.
次に, 関数のエピグラフを
と定義する.これは点における関数値よりも上にある値とを組にした集合のことをいう.
イメージとしては次のような感じになる.
また, 集合が凸集合であるとは
となることをいう.これは任意の2点を結んだ線分が含まれるような集合のこといい, 凹みのないような集合を指す.
この凸集合を用いて, 部分集合の凸包というものを考える.
集合の凸包とはを含む最小の凸集合と定義する.
さらに, 集合の閉包をを含む最小の閉集合と定義する.
例えばの閉包は
これらを用いて関数の凸包と閉包を定義していく.
まず, 関数の凸包とはエピグラフの凸包をエピグラフとするような関数のことをいう.