diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4892723 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Lint with ruff + run: ruff check src/ tests/ + + - name: Type check with mypy + run: mypy src/viscot/ --strict --ignore-missing-imports + + - name: Run tests with coverage + run: | + pytest tests/ -v --cov=viscot --cov-report=term-missing --cov-fail-under=80 diff --git a/.gitignore b/.gitignore index c02b293..deac5b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,12 @@ __pycache__ parser.out -parsetab.py +parsetab.py flow_picture -.vscode \ No newline at end of file +.vscode +.coverage +*.egg-info/ +test_output/ +.mypy_cache/ +.ruff_cache/ +.pytest_cache/ +.venv/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7c140f7 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +.PHONY: all test clean install lint svg + +all: test + +install: + pip install -e ".[dev]" --break-system-packages + +test: + python3 -m pytest tests/ -q + +svg: + python3 -m pytest tests/test_canvas.py::TestExhaustive \ + tests/test_canvas.py::TestNoSplineCrossings \ + tests/test_canvas.py::TestSmallCrossings \ + tests/test_canvas.py::TestStress \ + -v --save-images=test_output/svg --image-format=svg + +lint: + python3 -m ruff check src/ tests/ + python3 -m mypy src/ + +clean: + $(RM) -rf src/viscot/__pycache__ src/viscot/core/__pycache__ + $(RM) -rf tests/__pycache__ .pytest_cache + $(RM) -f .coverage + $(RM) -f src/viscot/core/parser.out src/viscot/core/parsetab.py + $(RM) -rf test_output/ diff --git a/README.md b/README.md index 67b2bb2..72c6a23 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,87 @@ -# Visualization program of tree representation of structurally stable incompressible flow in two dimensional multiply-connected domain +# visCOT -このプログラムは2次元多重連結領域内における構造安定な非圧縮流れの木表現の入力に対して,同一のトポロジーを表す2次元上の図を作図するものです. -想定されている入力は,Consで繋がれた木を同一の高さとして見た場合の,深さが3までの木です. +**COT(Combinatorial Orbit Topology)表記から流線図を自動生成する可視化ツール** + +2次元多重連結領域上の構造安定な非圧縮流れのトポロジーを、COT木表現から自動的に描画します。 +深さに制限はなく、再帰的に任意の深さの木を可視化できます。 + +## 出力例 + +| 一様流 `A0()` | 閉軌道を含む一様流 `A0(a+(l+))` | 2つの閉軌道 `A0(a+(l+).a+(l+))` | +|:---:|:---:|:---:| +| ![一様流](examples/example_uniform.png) | ![閉軌道](examples/example_closed_orbit.png) | ![2つの閉軌道](examples/example_two_orbits.png) | + +| 鞍点結合 `B0+(l+,c-(l-,).c-(l-,))` | 3つの鞍点結合 | 障害物を含む流れ `A0(a+(b++(B+{},B+{})))` | +|:---:|:---:|:---:| +| ![鞍点結合](examples/example_saddle.png) | ![3つの鞍点結合](examples/example_three_saddle.png) | ![障害物を含む流れ](examples/example_complex_multiply.png) | + + +## 特徴 + +- **COT表記のパース** — PLY(Python Lex-Yacc)ベースのパーサで木構造を構文解析 +- **自動レイアウト** — 高さベースのスペーシングにより流線の重なりを防止 +- **スプライン補間** — scipy による滑らかな流線の描画 +- **レイアウト最適化** — Nelder-Mead 法による自動パラメータ最適化 +- **多彩な出力形式** — PNG, SVG, PDF, EPS に対応 +- **対話モード** — 式を入力して即座にプレビュー ## Requirements -+ Python3 -+ Matplotlib -+ PLY -## Linux Ubuntuにてインストール例 -+ 本プログラムをダウンロード -``` -git clone https://github.com/yokoyama-lab/Visualization-program-of-flow.git -``` +- Python 3.10+ +- matplotlib 3.7+ +- numpy 1.24+ +- scipy 1.10+ +- PLY 3.11+ -+ Python をインストール -``` -sudo apt install python3 -``` +## インストール -+ Matplotlib をインストール -``` -cd Thesis_program -pip3 install Matplotlib -pip3 install numpy -pip3 install scipy +```bash +git clone https://github.com/yokoyama-lab/visCOT.git +cd visCOT +pip install -e . --break-system-packages ``` -+ PLY をインストール +開発用の依存パッケージ(pytest, ruff, mypy)も合わせてインストールする場合: + +```bash +pip install -e ".[dev]" --break-system-packages ``` -pip3 install PLY + +## 使い方 + +COT木表現を標準入力から与えて可視化する: + +```bash +echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot ``` -## 実行例 -プログラムを起動し,木表現を入力する. +ファイルに保存する場合: + +```bash +echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot -o output.png ``` -python3 visualize.py + +ベクター形式で出力する場合: + +```bash +echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot -o output.svg +echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot -o output.pdf ``` -木表現は,例えば次のように入力する. +対話モードで起動する場合: + +```bash +viscot -i ``` -a0(cons(a2(cons(c+(l,n),cons(c+(l,n),n)),cons(c-(l,n),cons(c-(l,n),n))),n)) + +## テスト + +```bash +make test ``` -入力用の木表現が「test.txt」に用意されているので試してみてください. +## Lint + +```bash +make lint +``` diff --git a/examples/example_closed_orbit.png b/examples/example_closed_orbit.png new file mode 100644 index 0000000..880cf65 Binary files /dev/null and b/examples/example_closed_orbit.png differ diff --git a/examples/example_complex_multiply.png b/examples/example_complex_multiply.png new file mode 100644 index 0000000..d82baba Binary files /dev/null and b/examples/example_complex_multiply.png differ diff --git a/examples/example_saddle.png b/examples/example_saddle.png new file mode 100644 index 0000000..bd489f1 Binary files /dev/null and b/examples/example_saddle.png differ diff --git a/examples/example_three_saddle.png b/examples/example_three_saddle.png new file mode 100644 index 0000000..2af4dc7 Binary files /dev/null and b/examples/example_three_saddle.png differ diff --git a/examples/example_two_orbits.png b/examples/example_two_orbits.png new file mode 100644 index 0000000..4e05b69 Binary files /dev/null and b/examples/example_two_orbits.png differ diff --git a/examples/example_uniform.png b/examples/example_uniform.png new file mode 100644 index 0000000..dbd13e5 Binary files /dev/null and b/examples/example_uniform.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d316bcc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,59 @@ +[build-system] +requires = ["setuptools>=68.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "viscot" +version = "0.2.0" +description = "Visualization of tree representation of structurally stable incompressible flows" +requires-python = ">=3.10" +dependencies = [ + "numpy>=1.24", + "matplotlib>=3.7", + "scipy>=1.10", + "ply>=3.11", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "pytest-cov>=4.0", + "mypy>=1.5", + "ruff>=0.1", +] +eval = [ + "pandas>=2.0", +] + +[project.scripts] +viscot = "viscot.cli:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.mypy] +strict = true +warn_return_any = true +warn_unused_configs = true + +[[tool.mypy.overrides]] +module = ["ply", "ply.*", "scipy", "scipy.*"] +ignore_missing_imports = true + +[tool.ruff] +target-version = "py310" +line-length = 100 + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "N", "UP", "B", "A", "SIM"] + +[tool.ruff.lint.per-file-ignores] +# PLY requires t_XXX and p_xxx naming conventions +"src/viscot/core/lexer.py" = ["N816"] +"src/viscot/core/parser.py" = ["N802", "N816"] +# Node class names mirror COT notation (B0_plus, C_minus, etc.) +# B027: plot_arrow is intentionally a no-op default, not abstract +"src/viscot/core/nodes.py" = ["N801", "B027"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 6a1de63..0000000 --- a/src/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# パッケージを示す空ファイル \ No newline at end of file diff --git a/src/flow.py b/src/flow.py deleted file mode 100644 index 525912e..0000000 --- a/src/flow.py +++ /dev/null @@ -1,575 +0,0 @@ -# -*- coding: utf-8 -*- - -import abc - -import math -import numpy as np -import matplotlib.pyplot as plt -import matplotlib as mpl -from scipy import interpolate - -def theta_point(theta, r, center): - """ - 半径とthetaと中心点を使って二次元上の点の位置を求める関数 - """ - return (r * math.cos(theta) + center[0], r * math.sin(theta) + center[1]) - -def c_list_high(children_ocu_list): - """ - C系の配列[(self.high,self.bottom_length),...]から最も大きい高さを求める関数 - """ - return max(map(lambda x: x[0], children_ocu_list)) - -def c_list_circ_length(children_ocu_list, margin): - """ - C系の配列[(self.high,self.bottom_length),...]から円周を求める関数 - """ - # marginはc同士の間に空けたいスペース - circ_length = 0 # 円周を保存する変数 - longest_child = 0 # 最も長い子供の長さを保存する変数 - for child in children_ocu_list: - circ_length += child[1]+margin # スペース分円周を伸ばす - if longest_child < child[1]: - longest_child = child[1] - # もし円周の半分以上の長さを持つ子供がいれば、円周の長さをその子供に合わせる - # (C系とb系が重なることを避ける) - if circ_length/2 <= longest_child: - circ_length = longest_child*2 - return circ_length - -def make_list_for_c(children_ocu_list, parent_r, parent_center, parent_type, margin, parent_length=0, first_child=False): - """ - Cをdrawするための配列[[基準点からの距離,親の半径,親の中心,親がB0かどうか],...]を作成する関数 - """ - # parent_lengthは親の円の特定の位置から書き始めたいとき用(a2など) - c_list = [] - length = parent_length - if parent_type and first_child: - length += 0.3 - for child in children_ocu_list: - # 子供それぞれについて円周の基準点からどれだけ離れているかと、betaの半径、betaの中心、親がB0かどうか - c_list.append({"length":length, "parent_r":parent_r, "parent_center":parent_center, "parent_type":parent_type}) - if length+(margin/len(children_ocu_list))-child[1] < length: - length += 1.5 - else: - length += (margin/len(children_ocu_list))-child[1]+1 - else: - for child in children_ocu_list: - length += margin - c_list.append({"length":length, "parent_r":parent_r, "parent_center":parent_center, "parent_type":parent_type}) - length += child[1] - return c_list - -class Canvas: - """ - 図を描画する領域 - """ - def __init__(self): - """ - matplotlibの初期化設定 - """ - self.ax = plt.axes() - plt.axis('off') - self.ax.set_aspect('equal') - - def show_canvas(self): - """ - 作成された図を表示 - """ - plt.tight_layout() - plt.show() - - def save_canvas(self, file_name): - """ - 作成された画像を保存 - """ - print("save picture! ") - plt.tight_layout() - plt.savefig(file_name) - - def clear_canvas(self): - """ - matplotlibのデータ削除 - """ - plt.close("all") - plt.cla() - plt.axis('off') - self.ax.set_aspect('equal') - - def spline(self, x, y, point, deg): - """ - スプライン補間 - """ - tck, u = interpolate.splprep([x, y], k=deg, s=0) - u = np.linspace(0, 1, num=point, endpoint=True) - spline = interpolate.splev(u, tck) - return spline[0], spline[1] - - def draw_spline(self, xy): - """ - スプライン補間関数、引数はx座標y座標のタプルのリスト - """ - count = len(xy) - x = [] - y = [] - for i in range(0, count): - a_xy = xy[i] - x.append(a_xy[0]) - y.append(a_xy[1]) - if count >= 4: - a, b = self.spline(x, y, 100, 3) - elif count == 3: - a, b = self.spline(x, y, 100, 2) - plt.plot(a, b, color="black") - - def draw_circle(self, r, center=(0, 0), circle_fill=False, fc="grey"): - """ - 円描画、引数centerはタプル - """ - if circle_fill: - circ = plt.Circle(center, r, ec="black", fc=fc, linewidth=1.5) - else: - circ = plt.Circle(center, r, ec="black", fill=False, linewidth=1.5) - self.ax.add_patch(circ) - self.ax.plot() - - def draw_arrow(self, center, theta=0): - """ - 矢印を描画する - """ - # theta=0で右向きの矢印 - col = 'k' - arst = 'wedge,tail_width=0.6,shrink_factor=0.5' - plt.annotate('', xy=(center[0]+(0.1*math.cos(theta)), center[1]+(0.05*math.sin(theta))), xytext=(center[0]+(0.1*math.cos(math.pi+theta)), center[1]+(0.1*math.sin(math.pi+theta))), arrowprops=dict(arrowstyle=arst, connectionstyle='arc3', facecolor=col, edgecolor=col, shrinkA=0, shrinkB=0)) - - def draw_point(self, center): - """ - zoomした際大きさが変化する点をプロットする関数 - """ - plt.plot([center[0]], [center[1]], 'k.') - - def draw_line(self, xy_1, xy_2): - """ - xy_1からxy_2まで直線を引く関数 - """ - plt.plot([xy_1[0], xy_2[0]], [xy_1[1], xy_2[1]], 'k-') - - def axvspan(self, r): - """ - 半径rの周りを塗りつぶす関数 - """ - self.ax.axvspan(-r, r, -r, r, color="gray", alpha=0.5) - -class Node(object, metaclass=abc.ABCMeta): - @abc.abstractmethod - def __init__(self): - self.canvas = None - self.head = None - self.tail = None - - def draw(self, *arg): - """ - 描画処理を行うメソッド - """ - pass - - def plot_arrow(self, *arg): - """ - 矢印などの描画を行う - """ - pass - - def set_canvas(self, canvas): - """ - 描画を行うキャンバスを指定する - """ - self.canvas = canvas - if self.head is not None: - self.head.set_canvas(canvas) - if self.tail is not None: - self.tail.set_canvas(canvas) - -class A0(Node): - """ - A0を扱うクラス - """ - def __init__(self, head): # 子の半径を定義 - super().__init__() - self.type = "A0" - self.head = head # 抽象構文木の作成 - self.margin = 0.5 - - def draw(self): - long_child = 0 - childrens_info = [] # drawで引数として渡す - count_r = 0 - if self.head.type == "Nil": - # 一様流を書く - self.canvas.draw_line((-1, 0), (1, 0)) - self.canvas.draw_arrow((0, 0), math.pi) - else: - for child in self.head.occupation: # 子供達の中で一番長いrを求める - if child[0] > long_child: - long_child = child[0] - edge = long_child + self.margin - for child in self.head.occupation: - # 次の子供の中心点をy軸に-r*2して繰り返す - count_r += child[0] + self.margin - # 子供それぞれについて中心点を作成して配列に格納 - childrens_info.append({"center":(0, -count_r), "edge":edge}) - count_r += child[0] + self.margin - self.head.draw(childrens_info) - -class B0(Node): - """ - B0+,B0-の抽象クラス - """ - def __init__(self, head, tail): - super().__init__() - self.head = head - self.tail = tail - self.margin = 0.5 - high_children = c_list_high(tail.occupation) - children_length = c_list_circ_length(tail.occupation, self.margin) - self.r = max(children_length / (2 * math.pi), head.r + high_children + self.margin) - - def draw(self): - side_r = self.r + self.margin - self.canvas.axvspan(side_r) - self.canvas.draw_circle(self.r, (0, 0), circle_fill=True, fc="white") - self.plot_arrow() - for_children = make_list_for_c(self.tail.occupation, self.r, (0, 0), True, 2*self.r*math.pi, first_child=True) - self.head.draw((0, 0)) - self.tail.draw(for_children) - -class B0_plus(B0): - """ - B0+を扱うクラス - """ - def plot_arrow(self): - self.canvas.draw_arrow((self.r, 0), math.pi/2) - -class B0_minus(B0): - """ - B0-を扱うクラス - """ - def plot_arrow(self): - self.canvas.draw_arrow((self.r, 0), math.pi*1.5) - -class A_Flip(Node): - """ - a+,a-の抽象クラス - """ - def __init__(self, head): - super().__init__() - self.head = head - self.margin = 0.5 # 子の専有領域と親の領域の余白 - self.r = head.r + self.margin - - def draw(self, info_dic): # 描画する際に親から与える中心点 - center = info_dic["center"] - edge = info_dic["edge"] - self.canvas.draw_circle(self.r, center) - self.plot_arrow(center,edge) - self.head.draw(center) - -class A_plus(A_Flip): - """ - a+を扱うクラス - """ - def __init__(self, head): - super().__init__(head) - self.type = "A_plus" - self.occupation = [(self.r, self.type)] - - def plot_arrow(self, center, edge): - self.canvas.draw_point((center[0], center[1]-self.r)) - self.canvas.draw_arrow((center[0]-self.r, center[1]), theta=math.pi*1.5) - self.canvas.draw_arrow((center[0]+self.r, center[1]), theta=math.pi/2) - self.canvas.draw_line((-edge, center[1]-self.r), (edge, center[1]-self.r)) - self.canvas.draw_arrow((-edge/2, center[1]-self.r), math.pi) - self.canvas.draw_arrow((edge/2, center[1]-self.r), math.pi) - -class A_minus(A_Flip): - """ - a-を扱うクラス - """ - def __init__(self, head): - super().__init__(head) - self.type = "A_minus" - self.occupation = [(self.r, self.type)] - - def plot_arrow(self, center, edge): - self.canvas.draw_point((center[0], center[1]+self.r)) - self.canvas.draw_arrow((center[0]-self.r, center[1]), theta=math.pi/2) - self.canvas.draw_arrow((center[0]+self.r, center[1]), theta=math.pi*1.5) - self.canvas.draw_line((-edge, center[1]+self.r), (edge, center[1]+self.r)) - self.canvas.draw_arrow((-edge/2, center[1]+self.r), math.pi) - self.canvas.draw_arrow((edge/2, center[1]+self.r), math.pi) - -class A2(Node): - """ - a2を扱うクラス - """ - def __init__(self, head, tail): - super().__init__() - self.type = "A2" - self.head = head - self.tail = tail - self.margin = 0.5 # 子同士のスペースの定義 - self.high = max(c_list_high(head.occupation), c_list_high(tail.occupation)) - len_of_plus_circ = c_list_circ_length(head.occupation, self.margin) + self.margin # plus回りの長さ - len_of_minus_circ = c_list_circ_length(tail.occupation, self.margin) + self.margin # minus回りの長さ - if len_of_plus_circ >= len_of_minus_circ: - self.len_of_circ = len_of_plus_circ * 2 - else: - self.len_of_circ = len_of_minus_circ * 2 - self.center_r = self.len_of_circ / (2 * math.pi) # a_2の円の半径 - self.r = self.center_r + self.high # 専有領域の半径 - self.occupation = [(self.r, self.type)] - - def draw(self, info_dic): - center = info_dic["center"] - edge = info_dic["edge"] - self.canvas.draw_circle(self.center_r, center, circle_fill=True) # a_2の描画 - self.canvas.draw_point((center[0]+self.center_r, center[1])) # 一様流との交点の描画(右) - self.canvas.draw_point((center[0]-self.center_r, center[1])) # 一様流との交点の描画(左) - self.canvas.draw_line((center[0]-self.r, center[1]), (center[0]-self.center_r, center[1])) - self.canvas.draw_line((center[0]+self.center_r, center[1]), (center[0]+self.r, center[1])) - self.canvas.draw_line((-edge, center[1]), (-self.r, center[1])) - self.canvas.draw_line((self.r, center[1]), (edge, center[1])) - self.canvas.draw_arrow(((-edge-self.r)/2, center[1]), math.pi) - self.canvas.draw_arrow(((self.r+edge)/2, center[1]), math.pi) - for_plus_children = make_list_for_c(self.head.occupation, self.center_r, center, False, self.margin) - for_minus_children = make_list_for_c(self.tail.occupation, self.center_r, center, False, self.margin, parent_length=self.len_of_circ/2) - self.head.draw(for_plus_children) - self.tail.draw(for_minus_children) - -class Cons(Node): - """ - consを扱うクラス - """ - def __init__(self, head, tail): - super().__init__() - self.head = head - self.tail = tail - self.type = head.type - head_child = [s for s in head.occupation if s != (0, 0)] - tail_child = [s for s in tail.occupation if s != (0, 0)] - self.occupation = [] - for child in head_child: - self.occupation.append(child) - for child in tail_child: - self.occupation.append(child) - - def draw(self, children_list): - self.head.draw(children_list.pop(0)) - if len(children_list) != 0: - self.tail.draw(children_list) - -class Nil(Node): - """ - nilを扱うクラス - """ - def __init__(self): - super().__init__() - self.type = "Nil" - self.occupation = [(0, 0)] - -class Leaf(Node): - """ - leafを扱うクラス - """ - def __init__(self): - super().__init__() - self.r = 0 - -class B_Evc(Node): - """ - b++,b--の抽象クラス - """ - def __init__(self, head, tail): - super().__init__() - # headは上の円の半径、tailは下の円の半径 - self.head = head - self.tail = tail - self.margin = 0.5 # 子の専有領域と親の領域の余白 - self.l_up_r = head.r # 上の図の占有領域(半径) - self.l_down_r = tail.r # 下の図の占有領域(半径) - self.r = (2 * self.l_up_r + 2 * self.l_down_r + 4 * self.margin) / 2 # 全体の占有領域(半径) - - def draw(self, center=(0, 0)): # 描画する際に親から与える中心点 - self.canvas.draw_point((center[0], self.l_down_r+center[1]-self.l_up_r)) # 2つの円の交点 - self.canvas.draw_circle(self.l_up_r+self.margin, (center[0], self.l_down_r+self.margin+center[1])) # 上の円 - self.canvas.draw_circle(self.l_down_r+self.margin, (center[0], -self.l_up_r-self.margin+center[1])) # 下の円 - self.plot_arrow(center) - self.head.draw((center[0], self.l_down_r+self.margin+center[1])) - self.tail.draw((center[0], -self.l_up_r-self.margin+center[1])) - -class B_Flip(Node): - """ - b+-,b--の抽象クラス - """ - def __init__(self, head, tail): - super().__init__() - self.head = head - self.tail = tail - self.margin = 0.5 # 子の専有領域と親の領域の余白 - self.l_up_r = head.r # 上の図の占有領域(半径) - self.l_down_r = tail.r # 下の図の占有領域(半径) - self.r = (2 * self.l_up_r + 2 * self.l_down_r + 4 * self.margin) / 2 - - def draw(self, center=(0, 0)): # 描画する際に親から与える中心点 - self.canvas.draw_circle(self.l_up_r+self.margin, (center[0], self.l_down_r+self.margin+center[1])) - self.canvas.draw_circle(self.l_up_r+self.l_down_r+2*self.margin, center) - self.canvas.draw_point((center[0], self.l_down_r+self.margin+center[1]+self.l_up_r+self.margin)) - self.plot_arrow(center) - self.head.draw((center[0], self.l_down_r+self.margin+center[1])) - self.tail.draw((center[0], -self.l_up_r-self.margin+center[1])) - -class Beta(Node): - """ - beta+,beta-の抽象クラス - """ - def __init__(self, head): - super().__init__() - self.head = head - self.margin = 0.5 # 要素の両脇に作るスペースの大きさ - high_children = c_list_high(head.occupation) - children_length = c_list_circ_length(head.occupation, self.margin) - self.center_r = children_length / (2 * math.pi) # betaの円 - if children_length < 1: - self.center_r = 7 / (2 * math.pi) - self.r = self.center_r + high_children # 親に渡す全体の大きさ - - def draw(self, center): - self.canvas.draw_circle(self.center_r, center, circle_fill=True) - for_children = make_list_for_c(self.head.occupation, self.center_r, center, False, self.margin) - self.plot_arrow(center) - self.head.draw(for_children) - -class B_plus_plus(B_Evc): - """ - b++を扱うクラス - """ - def plot_arrow(self, center): - # 上の円の矢印 - self.canvas.draw_arrow((center[0], self.l_down_r+2*self.margin+center[1]+self.l_up_r), math.pi) - # 下の円の矢印 - self.canvas.draw_arrow((center[0], -self.l_up_r-2*self.margin+center[1]-self.l_down_r), 0) - -class B_plus_minus(B_Flip): - """ - b+-を扱うクラス - """ - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0], self.l_down_r+self.margin+center[1]-self.l_up_r-self.margin), theta=math.pi) - self.canvas.draw_arrow((center[0], center[1]-(self.l_up_r+self.l_down_r+2*self.margin))) - -class Beta_plus(Beta): - """ - beta+を扱うクラス - """ - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0]+self.center_r, center[1]), math.pi/2) - -class B_minus_minus(B_Evc): - """ - b--を扱うクラス - """ - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0], self.tail.r+2*self.margin+center[1]+self.head.r), 0) - self.canvas.draw_arrow((center[0],-self.head.r-2*self.margin+center[1]-self.tail.r), math.pi) - -class B_minus_plus(B_Flip): - """ - b-+を扱うクラス - """ - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0], self.l_down_r+self.margin+center[1]-self.l_up_r-self.margin), theta=0) - self.canvas.draw_arrow((center[0], center[1]-(self.l_up_r+self.l_down_r+2*self.margin)), theta=math.pi) - -class Beta_minus(Beta): - """ - beta-を扱うクラス - """ - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0]+self.center_r, center[1]), math.pi*1.5) - -class C(Node): - """ - c+,c-の抽象クラス - """ - def __init__(self, head, tail): - super().__init__() - self.head = head - self.tail = tail - self.margin = 1 # c系の要素の両脇に作るスペースの大きさ - self.circ_margin = 0.5 # 子のb系の要素と親の間の距離 - self.high_children = c_list_high(tail.occupation) - self.children_length = c_list_circ_length(tail.occupation, self.margin) - bottom_length = max(head.r*2, self.children_length) - self.high = 2 * head.r + self.high_children + self.margin - if (self.head.r == 0) and (len(self.tail.occupation) != 1): - self.high += len(self.tail.occupation) * 1 - self.occupation = [(self.high, bottom_length)] - - def draw(self, c_data): - if self.high_children == 0: - self.high_children = 0.3 - length = c_data["length"] - center_r = c_data["parent_r"] - center = c_data["parent_center"] - bool_b0 = c_data["parent_type"] - start_theta = length / center_r - start_point = theta_point(start_theta, center_r, center) - end_theta = (length + self.children_length) / center_r - end_point = theta_point(end_theta, center_r, center) - high_theta = (end_theta-start_theta) / 2 + start_theta - if bool_b0: - high_point = theta_point(high_theta, center_r-self.high, center) - b_center = theta_point(high_theta, center_r-self.high_children-self.circ_margin-self.head.r, center) - else: - high_point = theta_point(high_theta, center_r+self.high, center) - b_center = theta_point(high_theta, center_r+self.high_children+self.circ_margin+self.head.r, center) - self.plot_arrow(bool_b0, high_point, high_theta) - if self.head.r != 0: - # 180-(90+high_theta)bの専有領域の中心を基準に三角関数を適用するための準備 - b_r_theta = math.pi - (math.pi/2+high_theta) - # 0度の点 - b_r_center = theta_point(-b_r_theta, self.head.r+self.circ_margin, b_center) - # 180度の点 - b_l_center = theta_point(math.pi-b_r_theta, self.head.r+self.circ_margin, b_center) - if self.head.r * 2 < self.children_length / 2: - self.canvas.draw_spline([start_point, high_point, end_point]) - else: - self.canvas.draw_spline([start_point, b_r_center, high_point, b_l_center, end_point]) - else: - self.canvas.draw_spline([start_point, high_point, end_point]) - self.canvas.draw_point(start_point) - self.canvas.draw_point(end_point) - for_children = make_list_for_c(self.tail.occupation, center_r, center, bool_b0, self.margin/1.5, parent_length=length) - self.head.draw(b_center) - self.tail.draw(for_children) - -class C_plus(C): - """ - c+を扱うクラス - """ - def __init__(self, head, tail): - super().__init__(head, tail) - self.type = "C_plus" - - def plot_arrow(self, bool_b0, high_point, high_theta): - self.canvas.draw_arrow(high_point, high_theta+ math.pi*(1.5 if bool_b0 else 0.5)) - -class C_minus(C): - """ - c-を扱うクラス - """ - def __init__(self, head, tail): - super().__init__(head, tail) - self.type = "C_minus" - - def plot_arrow(self, bool_b0, high_point, high_theta): - self.canvas.draw_arrow(high_point, high_theta+ math.pi*(0.5 if bool_b0 else 1.5)) diff --git a/src/lex.py b/src/lex.py deleted file mode 100644 index d0ae871..0000000 --- a/src/lex.py +++ /dev/null @@ -1,50 +0,0 @@ -# lexerを生成する.PLYを用いる. - -import ply.lex as lex - -tokens = ( - 'A0', 'B0_PLUS', 'B0_MINUS', - 'A_PLUS', 'A_MINUS', 'A2', - 'B_PLUS_PLUS', 'B_PLUS_MINUS', 'B_MINUS_PLUS', 'B_MINUS_MINUS', - 'BETA_PLUS', 'BETA_MINUS', - 'C_PLUS', 'C_MINUS', - 'CONS', 'NIL', 'LEAF', -) - -literals = "()," - -t_A0 = r'a0' -t_B0_PLUS = r'b0\+' -t_B0_MINUS = r'b0\-' - -t_A_PLUS = r'a\+' -t_A_MINUS = r'a\-' -t_A2 = r'a2' - -t_B_PLUS_PLUS = r'b\+\+' -t_B_PLUS_MINUS = r'b\+\-' -t_B_MINUS_PLUS = r'b\-\+' -t_B_MINUS_MINUS = r'b\-\-' - -t_BETA_PLUS = r'be\+' -t_BETA_MINUS = r'be\-' - -t_C_PLUS = r'c\+' -t_C_MINUS = r'c\-' - -t_CONS = r'cons' -t_NIL = r'n' -t_LEAF = r'l' - -t_ignore = ' \t\n' # 入力を無視する -t_ignore_COMMENT = r'\#.*' # コメントを無視する - -# error handling -def t_error(t): - print("不正な文字 '%s'" % t.value[0]) - t.lexer.skip(1) - -lexer = lex.lex() - -if __name__ == '__main__': - lex.runmain() diff --git a/src/viscot/__init__.py b/src/viscot/__init__.py new file mode 100644 index 0000000..cd9fb52 --- /dev/null +++ b/src/viscot/__init__.py @@ -0,0 +1,3 @@ +"""visCOT — Visualization of COT tree representations.""" + +__version__ = "0.2.0" diff --git a/src/viscot/cli.py b/src/viscot/cli.py new file mode 100644 index 0000000..2d84ac7 --- /dev/null +++ b/src/viscot/cli.py @@ -0,0 +1,102 @@ +"""Entry point for visCOT — visualize COT tree representations.""" + +from __future__ import annotations + +import argparse +import sys + +from . import __version__ +from .core.canvas import Canvas +from .core.parser import parse + + +def main() -> None: + arg_parser = argparse.ArgumentParser( + description="Visualize COT tree representation of structurally stable " + "incompressible flows." + ) + arg_parser.add_argument( + "-V", "--version", action="version", version=f"%(prog)s {__version__}" + ) + arg_parser.add_argument( + "expression", + nargs="?", + default=None, + help="COT expression to visualize (reads stdin if omitted).", + ) + arg_parser.add_argument( + "-i", "--interactive", help="interactive mode", action="store_true" + ) + arg_parser.add_argument( + "-o", "--output", help="specify an output file (.png, .pdf, .svg, .eps)." + ) + arg_parser.add_argument( + "-f", "--file", + type=argparse.FileType("r"), + default=None, + help="read expression from a file instead of stdin.", + ) + arg_parser.add_argument( + "--dpi", + type=int, + default=150, + help="output image resolution in DPI (default: 150).", + ) + arg_parser.add_argument( + "--parse-only", + help="parse and print the tree structure without drawing.", + action="store_true", + ) + args = arg_parser.parse_args() + + if args.parse_only: + text = _read_input(args) + tree = parse(text) + print(tree.show()) + return + + canvas = Canvas() + if args.interactive: + while True: + try: + s = input(">>> ") + tree = parse(s) + tree.set_canvas(canvas) + tree.draw() + action = input("Select (save/show): ") + if action == "save": + filename = input("Filename: ") + if not filename: + filename = s + ".png" + canvas.save_canvas(filename, dpi=args.dpi) + elif action == "show": + canvas.show_canvas() + canvas.clear_canvas() + except (AttributeError, ValueError) as e: + print(f"Error: {e}", file=sys.stderr) + except EOFError: + break + else: + text = _read_input(args) + tree = parse(text) + tree.set_canvas(canvas) + tree.draw() + if args.output is None: + canvas.show_canvas() + else: + canvas.save_canvas(args.output, dpi=args.dpi) + canvas.close() + + +def _read_input(args: argparse.Namespace) -> str: + """Read expression from positional argument, file, or stdin.""" + if args.expression: + return str(args.expression) + if args.file: + with args.file as f: + return str(f.read()).strip() + return sys.stdin.read().strip() + + +if __name__ == "__main__": + main() diff --git a/src/viscot/core/__init__.py b/src/viscot/core/__init__.py new file mode 100644 index 0000000..108734d --- /dev/null +++ b/src/viscot/core/__init__.py @@ -0,0 +1,70 @@ +"""Core modules for visCOT.""" + +from .canvas import ( + Canvas, + DrawnArrow, + DrawnCircle, + DrawnElement, + DrawnLine, + DrawnPoint, + DrawnSpline, +) +from .config import DEFAULT_CONFIG, LayoutConfig +from .nodes import ( + A0, + A2, + B0, + A_Flip, + A_minus, + A_plus, + B0_minus, + B0_plus, + B_Evc, + B_Flip, + B_minus_minus, + B_minus_plus, + B_plus_minus, + B_plus_plus, + Beta, + Beta_minus, + Beta_plus, + C, + C_minus, + C_plus, + Cons, + DrawContext, + Leaf, + Leaf_minus, + Leaf_plus, + Nil, + Node, + OccupationInfo, + use_config, +) +from .parser import parse + + +def render_expression(expression: str, config: LayoutConfig | None = None) -> Canvas: + """Parse and render a COT expression, returning the Canvas.""" + canvas = Canvas(config=config) + tree = parse(expression, config=config) + tree.set_canvas(canvas) + tree.draw() + canvas.avoid_obstacles() + return canvas + + +__all__ = [ + "Canvas", "DrawnCircle", "DrawnElement", "DrawnLine", "DrawnSpline", "DrawnArrow", "DrawnPoint", + "DEFAULT_CONFIG", "LayoutConfig", + "A0", "A2", "A_Flip", "A_minus", "A_plus", + "B0", "B0_minus", "B0_plus", + "B_Evc", "B_Flip", + "B_minus_minus", "B_minus_plus", "B_plus_minus", "B_plus_plus", + "Beta", "Beta_minus", "Beta_plus", + "C", "C_minus", "C_plus", + "Cons", "DrawContext", "Leaf", "Leaf_minus", "Leaf_plus", "Nil", "Node", + "OccupationInfo", "use_config", + "parse", + "render_expression", +] diff --git a/src/viscot/core/canvas.py b/src/viscot/core/canvas.py new file mode 100644 index 0000000..7425804 --- /dev/null +++ b/src/viscot/core/canvas.py @@ -0,0 +1,244 @@ +"""Canvas class — drawing surface with DrawnElement recording.""" + +from __future__ import annotations + +import math +from dataclasses import dataclass + +import matplotlib.patches as mpatches +import matplotlib.pyplot as plt +import numpy as np +from scipy import interpolate + +from .config import DEFAULT_CONFIG, LayoutConfig + +# --- DrawnElement types --- + + +@dataclass +class DrawnCircle: + center: tuple[float, float] + radius: float + filled: bool = False + + +@dataclass +class DrawnSpline: + points: np.ndarray # (N, 2) + line: object = None # matplotlib Line2D reference for updating + + +@dataclass +class DrawnLine: + start: tuple[float, float] + end: tuple[float, float] + + +@dataclass +class DrawnArrow: + center: tuple[float, float] + theta: float + + +@dataclass +class DrawnPoint: + center: tuple[float, float] + + +DrawnElement = DrawnCircle | DrawnSpline | DrawnLine | DrawnArrow | DrawnPoint + + +class Canvas: + """Drawing surface backed by matplotlib, with element recording for metrics.""" + + def __init__(self, config: LayoutConfig | None = None) -> None: + self.config = config or DEFAULT_CONFIG + self._drawn_elements: list[DrawnElement] = [] + self._init_figure() + + def _init_figure(self) -> None: + """Create (or reset) the matplotlib figure and axes.""" + self.fig, self.ax = plt.subplots() + plt.axis("off") + self.ax.set_aspect("equal") + + @property + def drawn_elements(self) -> list[DrawnElement]: + return self._drawn_elements + + def save_canvas(self, file_name: str, dpi: int = 150) -> None: + self._fit_figure() + self.fig.savefig(file_name, dpi=dpi, bbox_inches="tight") + + def show_canvas(self) -> None: + self._fit_figure() + plt.show() + + def _fit_figure(self) -> None: + """Resize the figure so that 1 data-unit ≈ 0.15 inches.""" + xlim = self.ax.get_xlim() + ylim = self.ax.get_ylim() + data_w = xlim[1] - xlim[0] + data_h = ylim[1] - ylim[0] + scale = 0.15 # inches per data-unit + w = max(data_w * scale, 4.0) + h = max(data_h * scale, 3.0) + self.fig.set_size_inches(w, h) + + def close(self) -> None: + """Close the figure without re-creating. Use for final cleanup.""" + plt.close(self.fig) + self._drawn_elements.clear() + + def __enter__(self) -> Canvas: + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def avoid_obstacles(self, margin: float = 0.5) -> None: + """Push spline points outside filled obstacle circles. + + For each spline point that falls inside a filled circle, + project it radially outward to the circle boundary + margin. + Then update the matplotlib Line2D to reflect the correction. + + The largest filled circle (B0's white background) is excluded + because it is not an obstacle — C splines are meant to be inside it. + """ + filled = [ + e for e in self._drawn_elements + if isinstance(e, DrawnCircle) and e.filled + ] + if not filled: + return + # Exclude B0's white background circle (always centered at origin) + obstacles = [ + (e.center, e.radius) + for e in filled + if not (abs(e.center[0]) < 0.01 and abs(e.center[1]) < 0.01) + ] + if not obstacles: + return + for elem in self._drawn_elements: + if not isinstance(elem, DrawnSpline): + continue + pts = elem.points + n_pts = len(pts) + modified = False + for (cx, cy), r in obstacles: + dx = pts[:, 0] - cx + dy = pts[:, 1] - cy + dists = np.sqrt(dx**2 + dy**2) + target = r + margin + mask = dists < target + # Protect start and end points — they are anchored + # on the parent circle and must not be moved. + mask[0] = False + mask[n_pts - 1] = False + if not mask.any(): + continue + for i in np.where(mask)[0]: + d = dists[i] + if d < 1e-10: + pts[i, 0] = cx + target + continue + pts[i, 0] = cx + dx[i] * target / d + pts[i, 1] = cy + dy[i] * target / d + modified = True + if modified and elem.line is not None: + elem.line.remove() + new_lines = self.ax.plot(pts[:, 0], pts[:, 1], color="black") + elem.line = new_lines[0] + + def clear_canvas(self) -> None: + """Reset the canvas for reuse, clearing all drawn elements.""" + self.ax.clear() + plt.axis("off") + self.ax.set_aspect("equal") + self._drawn_elements.clear() + + def _spline( + self, + x: list[float], + y: list[float], + num_points: int, + degree: int, + ) -> tuple[np.ndarray, np.ndarray]: + tck, _ = interpolate.splprep([x, y], k=degree, s=0) + u = np.linspace(0, 1, num=num_points, endpoint=True) + result = interpolate.splev(u, tck) + return result[0], result[1] + + def draw_spline(self, xy: list[list[float] | tuple[float, float]]) -> None: + if len(xy) < 3: + raise ValueError(f"draw_spline requires at least 3 points, got {len(xy)}") + num_points = self.config.spline_num_points + xs, ys = zip(*xy, strict=True) + a, b = self._spline( + list(xs), + list(ys), + num_points, + min(len(xy), 4) - 1, + ) + lines = self.ax.plot(a, b, color="black") + pts = np.column_stack([a, b]) + self._drawn_elements.append(DrawnSpline(points=pts, line=lines[0])) + + def draw_circle( + self, + r: float, + center: tuple[float, float] = (0, 0), + circle_fill: bool = False, + fc: str = "gray", + ) -> None: + common = {"ec": "black", "linewidth": self.config.line_width} + if circle_fill: + circ = mpatches.Circle(center, r, fc=fc, **common) + else: + circ = mpatches.Circle(center, r, fill=False, **common) + self.ax.add_patch(circ) + self._drawn_elements.append(DrawnCircle(center=center, radius=r, filled=circle_fill)) + + def draw_arrow(self, center: tuple[float, float], theta: float = 0) -> None: + tw = self.config.arrow_tail_width + sf = self.config.arrow_shrink_factor + arst = f"wedge,tail_width={tw},shrink_factor={sf}" + self.ax.annotate( + "", + xy=( + center[0] + 0.1 * math.cos(theta), + center[1] + 0.05 * math.sin(theta), + ), + xytext=( + center[0] + 0.1 * math.cos(math.pi + theta), + center[1] + 0.1 * math.sin(math.pi + theta), + ), + arrowprops=dict( + arrowstyle=arst, + connectionstyle="arc3", + facecolor="k", + edgecolor="k", + shrinkA=0, + shrinkB=0, + ), + ) + self._drawn_elements.append(DrawnArrow(center=center, theta=theta)) + + def draw_point(self, center: tuple[float, float]) -> None: + self.ax.plot([center[0]], [center[1]], "k.") + self._drawn_elements.append(DrawnPoint(center=center)) + + def draw_line( + self, + xy_1: tuple[float, float], + xy_2: tuple[float, float], + ) -> None: + self.ax.plot([xy_1[0], xy_2[0]], [xy_1[1], xy_2[1]], "k-") + self._drawn_elements.append(DrawnLine(start=xy_1, end=xy_2)) + + def axvspan(self, r: float) -> None: + ratio = self.config.axvspan_margin_ratio + self.ax.axvspan(-r * ratio, r * ratio, color="gray", alpha=0.5) + self.ax.set_xlim(-r * ratio, r * ratio) + self.ax.set_ylim(-r * ratio, r * ratio) diff --git a/src/viscot/core/config.py b/src/viscot/core/config.py new file mode 100644 index 0000000..424b4ea --- /dev/null +++ b/src/viscot/core/config.py @@ -0,0 +1,61 @@ +"""Layout configuration — centralizes magic numbers from flow.py.""" + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class LayoutConfig: + """All tunable layout parameters in one place.""" + + # A0 + a0_margin: float = 0.5 + + # A_Flip (a+, a-) + a_flip_margin: float = 0.5 + + # A2 + a2_margin: float = 0.5 + + # B0 (B0+, B0-) + b0_margin: float = 0.5 + + # B_Evc (b++, b--) + b_evc_margin: float = 0.5 + + # B_Flip (b+-, b-+) + b_flip_margin: float = 0.5 + + # Beta (B+, B-) + beta_margin: float = 0.5 + beta_min_circ: float = 7.0 + + # C (c+, c-) + c_margin: float = 1.0 + c_circ_margin: float = 0.5 + c_height_spacing_factor: float = 3.0 # extra arc-length per unit height + c_min_child_height: float = 0.3 # fallback height when C has no children + c_spline_clearance_factor: float = 1.0 # additional width per unit height to prevent crossings + c_child_start_offset: float = 1.5 # min padding (× height) to separate child C from parent C start + + # B0 first-child offset + b0_first_child_offset: float = 0.3 + b0_min_inner_radius: float = 2.0 # minimum radius after subtracting child height + + # Spline interpolation points + spline_num_points: int = 100 + + # Arrow style + arrow_tail_width: float = 0.6 + arrow_shrink_factor: float = 0.5 + + # Line width + line_width: float = 1.5 + + # axvspan margin ratio + axvspan_margin_ratio: float = 1.1 + + +# Global default config instance +DEFAULT_CONFIG = LayoutConfig() diff --git a/src/viscot/core/lexer.py b/src/viscot/core/lexer.py new file mode 100644 index 0000000..23745b1 --- /dev/null +++ b/src/viscot/core/lexer.py @@ -0,0 +1,68 @@ +"""Lexer for COT tree notation — uses PLY.""" + +from __future__ import annotations + +import ply.lex as lex + +tokens = ( + "A0", + "B0_PLUS", + "B0_MINUS", + "A_PLUS", + "A_MINUS", + "A2", + "B_PLUS_PLUS", + "B_PLUS_MINUS", + "B_MINUS_PLUS", + "B_MINUS_MINUS", + "BETA_PLUS", + "BETA_MINUS", + "C_PLUS", + "C_MINUS", + "CONS", + "NIL", + "LEAF_PLUS", + "LEAF_MINUS", +) + +literals = "(),{}." + +t_A0 = r"A0" +t_B0_PLUS = r"B0\+" +t_B0_MINUS = r"B0\-" + +t_A_PLUS = r"a\+" +t_A_MINUS = r"a\-" +t_A2 = r"a2" + +t_B_PLUS_PLUS = r"b\+\+" +t_B_PLUS_MINUS = r"b\+\-" +t_B_MINUS_PLUS = r"b\-\+" +t_B_MINUS_MINUS = r"b\-\-" + +t_BETA_PLUS = r"B\+" +t_BETA_MINUS = r"B\-" + +t_C_PLUS = r"c\+" +t_C_MINUS = r"c\-" + +t_CONS = r"cons" +t_NIL = r"n" +t_LEAF_PLUS = r"l\+" +t_LEAF_MINUS = r"l\-" + +t_ignore = " \t\n" +t_ignore_COMMENT = r"\#.*" + + +class COTLexError(ValueError): + """Raised when the lexer encounters an illegal character.""" + + +def t_error(t: lex.LexToken) -> None: + raise COTLexError( + f"Illegal character {t.value[0]!r} at position {t.lexpos}" + ) + + +lexer = lex.lex() diff --git a/src/viscot/core/nodes.py b/src/viscot/core/nodes.py new file mode 100644 index 0000000..8d74113 --- /dev/null +++ b/src/viscot/core/nodes.py @@ -0,0 +1,852 @@ +"""Node hierarchy for COT tree representation.""" + +from __future__ import annotations + +import abc +import math +from collections.abc import Generator +from contextlib import contextmanager +from dataclasses import dataclass +from typing import Any + +from .canvas import Canvas +from .config import DEFAULT_CONFIG, LayoutConfig + +# --- Active configuration context --- + +_active_config: LayoutConfig = DEFAULT_CONFIG + + +@contextmanager +def use_config(config: LayoutConfig) -> Generator[None, None, None]: + """Temporarily set the active config for node construction.""" + global _active_config + prev = _active_config + _active_config = config + try: + yield + finally: + _active_config = prev + + +# --- OccupationInfo --- + + +@dataclass +class OccupationInfo: + """Occupation area of a node.""" + + height: float + width: float + + +# --- DrawContext --- + + +@dataclass +class DrawContext: + """Unified context passed to Node.draw().""" + + center: tuple[float, float] = (0.0, 0.0) + edge: float = 0.0 + # C-type fields + length: float = 0.0 + parent_r: float = 0.0 + parent_center: tuple[float, float] = (0.0, 0.0) + parent_type: bool = False + + +# --- Helper functions --- + + +def theta_point( + theta: float, r: float, center: tuple[float, float] +) -> tuple[float, float]: + return r * math.cos(theta) + center[0], r * math.sin(theta) + center[1] + + +def c_list_highest(children: list[OccupationInfo]) -> float: + return max(c.height for c in children) + + +def c_list_circ_length(children: list[OccupationInfo], margin: float) -> float: + circ_length = sum(c.width + margin for c in children) + widest = max(c.width for c in children) + return max(circ_length, widest * 2) + + +def make_list_for_c( + children: list[OccupationInfo], + parent_r: float, + parent_center: tuple[float, float], + parent_type: bool, + margin: float, + parent_length: float = 0, + first_child: bool = False, + config: LayoutConfig | None = None, +) -> list[DrawContext]: + if config is None: + config = DEFAULT_CONFIG + result: list[DrawContext] = [] + length = parent_length + if parent_type and first_child: + # B0: distribute children evenly around the full circumference. + # Each child occupies its width; the remaining arc is split as gaps. + n = len(children) + total_width = sum(c.width for c in children) + total_gap = margin - total_width # remaining arc after subtracting widths + gap = max(total_gap / n, config.b0_first_child_offset) if n > 0 else 0 + for child in children: + length += gap / 2 # half-gap before + result.append( + DrawContext( + length=length, + parent_r=parent_r, + parent_center=parent_center, + parent_type=parent_type, + ) + ) + length += child.width + gap / 2 # width + half-gap after + else: + for child in children: + length += margin + result.append( + DrawContext( + length=length, + parent_r=parent_r, + parent_center=parent_center, + parent_type=parent_type, + ) + ) + length += child.width + return result + + +# --- Node base --- + + +_CENTER_ORIGIN: tuple[float, float] = (0.0, 0.0) +_ZERO_OCCUPATION = OccupationInfo(height=0, width=0) + +# Type alias for center parameters that accept either DrawContext or raw coordinates +_CenterArg = DrawContext | tuple[float, float] + + +class Node(abc.ABC): + """Abstract base for all tree nodes.""" + + dir: int # +1 or -1, defined by subclasses + _show_prefix: str = "" + + @abc.abstractmethod + def __init__( + self, + head: Node | None = None, + tail: Node | None = None, + canvas: Canvas | None = None, + ) -> None: + self.canvas: Canvas | None = canvas + self.head = head + self.tail = tail + self.r: float = 0 + self.occupation: list[OccupationInfo] = [_ZERO_OCCUPATION] + self._config: LayoutConfig = _active_config + + @abc.abstractmethod + def draw(self, *args: Any, **kwargs: Any) -> None: + """Perform drawing.""" + + def plot_arrow(self, *args: Any, **kwargs: Any) -> None: + """Draw arrows. No-op by default.""" + + def set_canvas(self, canvas: Canvas) -> None: + self.canvas = canvas + if self.head is not None: + self.head.set_canvas(canvas) + if self.tail is not None: + self.tail.set_canvas(canvas) + + @property + def _cv(self) -> Canvas: + """Shorthand for accessing canvas with a runtime check.""" + if self.canvas is None: + raise RuntimeError("Canvas not set. Call set_canvas() before drawing.") + return self.canvas + + def dir2rad(self) -> float: + return (self.dir + 1.0) * math.pi / 2.0 + + @staticmethod + def _resolve_center( + center: _CenterArg, + ) -> tuple[float, float]: + """Extract center tuple from a DrawContext or pass through.""" + if isinstance(center, DrawContext): + return center.center + return center + + @staticmethod + def _resolve_center_edge( + info: DrawContext | None, + ) -> tuple[tuple[float, float], float]: + """Extract (center, edge) from a DrawContext or return defaults.""" + if isinstance(info, DrawContext): + return info.center, info.edge + return _CENTER_ORIGIN, 0.0 + + @abc.abstractmethod + def show(self) -> str: + """Return string representation.""" + + def _show_two_children(self) -> str: + """Common show() for nodes with two children: prefix(head,tail).""" + if self.head is None or self.tail is None: + raise RuntimeError("_show_two_children requires both head and tail") + return f"{self._show_prefix}({self.head.show()},{self.tail.show()})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Node): + return NotImplemented + return type(self) is type(other) and self.show() == other.show() + + def __hash__(self) -> int: + return hash((type(self).__name__, self.show())) + + def __repr__(self) -> str: + return self.show() + + +# --- Concrete nodes --- + + +class A0(Node): + """A0 node.""" + + head: Node + + def __init__(self, head: Node) -> None: + super().__init__(head) + + def draw(self, *args: Any, **kwargs: Any) -> None: + margin = self._config.a0_margin + childrens_info: list[DrawContext] = [] + if isinstance(self.head, Nil): + self._cv.draw_line((-1, 0), (1, 0)) + self._cv.draw_arrow((0, 0), 0) + else: + count_r = 0.0 + long_child = c_list_highest(self.head.occupation) + for child in self.head.occupation: + count_r += child.height + margin + childrens_info.append( + DrawContext(center=(0, -count_r), edge=long_child + margin) + ) + count_r += child.height + margin + self.head.draw(childrens_info) + + def show(self) -> str: + return "A0()" if isinstance(self.head, Nil) else f"A0({self.head.show()})" + + +class B0(Node): + """Abstract base for B0+, B0-.""" + + head: Node + tail: Node + _show_prefix: str + + def __init__(self, head: Node, tail: Node) -> None: + super().__init__(head, tail) + cfg = self._config + high_children = c_list_highest(tail.occupation) + # For B0, children are drawn inward so widest*2 rule is unnecessary. + # Use the sum of widths + margins directly, giving a tighter circle. + children_length = sum(c.width + cfg.b0_margin for c in tail.occupation) + # Ensure the inner radius (R - high_children) stays above a minimum + # so that inward-pointing C-splines don't converge and cross. + min_inner = max(head.r + cfg.b0_margin, cfg.b0_min_inner_radius) + self.r = max( + children_length / (2 * math.pi), + head.r + high_children + cfg.b0_margin, + high_children + min_inner, + ) + + def draw(self, *args: Any, **kwargs: Any) -> None: + self._cv.axvspan(self.r) + self._cv.draw_circle(self.r, _CENTER_ORIGIN, circle_fill=True, fc="white") + self.plot_arrow() + for_children = make_list_for_c( + self.tail.occupation, + self.r, + _CENTER_ORIGIN, + True, + 2 * self.r * math.pi, + first_child=True, + config=self._config, + ) + self.head.draw(_CENTER_ORIGIN) + self.tail.draw(for_children) + + def plot_arrow(self, *args: Any, **kwargs: Any) -> None: + self._cv.draw_arrow( + (self.r, 0), math.pi * 1.5 - self.dir2rad() + ) + + def show(self) -> str: + return self._show_two_children() + + +class B0_plus(B0): + """B0+ node.""" + + dir = 1 + _show_prefix = "B0+" + + +class B0_minus(B0): + """B0- node.""" + + dir = -1 + _show_prefix = "B0-" + + +class A_Flip(Node): + """Abstract base for a+, a-.""" + + head: Node + _show_prefix: str + + def __init__(self, head: Node) -> None: + super().__init__(head) + margin = self._config.a_flip_margin + self.r: float = head.r + margin + self.occupation: list[OccupationInfo] = [OccupationInfo(height=self.r, width=0)] + + def draw(self, info: DrawContext | None = None, *args: Any, **kwargs: Any) -> None: + center, edge = self._resolve_center_edge(info) + self._cv.draw_circle(self.r, center) + self.plot_arrow(center, edge) + self.head.draw(center) + + def plot_arrow( + self, + center: tuple[float, float] | None = None, + edge: float = 0, + *args: Any, + **kwargs: Any, + ) -> None: + if center is None: + center = _CENTER_ORIGIN + cx, cy = center + stag_y = cy + self.r * self.dir + self._cv.draw_point((cx, stag_y)) + self._cv.draw_arrow( + (cx - self.r, cy), theta=math.pi * 0.5 - self.dir2rad(), + ) + self._cv.draw_arrow( + (cx + self.r, cy), theta=math.pi * 1.5 - self.dir2rad(), + ) + self._cv.draw_line((-edge, stag_y), (edge, stag_y)) + self._cv.draw_arrow((-edge / 2, stag_y), 0) + self._cv.draw_arrow((edge / 2, stag_y), 0) + + def show(self) -> str: + return f"{self._show_prefix}({self.head.show()})" + + +class A_plus(A_Flip): + """a+ node.""" + + dir = 1 + _show_prefix = "a+" + + +class A_minus(A_Flip): + """a- node.""" + + dir = -1 + _show_prefix = "a-" + + +class A2(Node): + """a2 node.""" + + head: Node + tail: Node + + def __init__(self, head: Node, tail: Node) -> None: + super().__init__(head, tail) + cfg = self._config + margin = cfg.a2_margin + self.high = max( + c_list_highest(head.occupation), c_list_highest(tail.occupation) + ) + len_of_plus_circ = ( + c_list_circ_length(head.occupation, margin) + margin + ) + len_of_minus_circ = ( + c_list_circ_length(tail.occupation, margin) + margin + ) + self.len_of_circ = max(len_of_plus_circ, len_of_minus_circ) * 2 + self.center_r: float = max(self.len_of_circ / (2 * math.pi), 1.0) + self.r: float = self.center_r + self.high + self.occupation: list[OccupationInfo] = [OccupationInfo(height=self.r, width=0)] + + def draw(self, info: DrawContext | None = None, *args: Any, **kwargs: Any) -> None: + cfg = self._config + center, edge = self._resolve_center_edge(info) + cx, cy = center + self._cv.draw_circle(self.center_r, center, circle_fill=True) + self._cv.draw_point((cx + self.center_r, cy)) + self._cv.draw_point((cx - self.center_r, cy)) + self._cv.draw_line((cx - self.r, cy), (cx - self.center_r, cy)) + self._cv.draw_line((cx + self.center_r, cy), (cx + self.r, cy)) + self._cv.draw_line((-edge, cy), (-self.r, cy)) + self._cv.draw_line((self.r, cy), (edge, cy)) + self._cv.draw_arrow(((-edge - self.r) / 2, cy), 0) + self._cv.draw_arrow(((self.r + edge) / 2, cy), 0) + for_plus_children = make_list_for_c( + self.head.occupation, self.center_r, center, False, cfg.a2_margin, + parent_length=self.len_of_circ / 2, + config=cfg, + ) + for_minus_children = make_list_for_c( + self.tail.occupation, + self.center_r, + center, + False, + cfg.a2_margin, + config=cfg, + ) + self.head.draw(for_plus_children) + self.tail.draw(for_minus_children) + + def show(self) -> str: + return f"a2({self.head.show()},{self.tail.show()})" + + +class Cons(Node): + """Cons node — list constructor.""" + + head: Node + tail: Node + dir = 0 + + def __init__(self, head: Node, tail: Node) -> None: + super().__init__(head, tail) + self.occupation: list[OccupationInfo] = [ + s + for s in head.occupation + tail.occupation + if s != _ZERO_OCCUPATION + ] + + def draw( + self, children_list: list[DrawContext] | None = None, *args: Any, **kwargs: Any, + ) -> None: + if not isinstance(children_list, list): + self.head.draw(children_list) + return + self.head.draw(children_list[0]) + if len(children_list) > 1: + self.tail.draw(children_list[1:]) + + def show(self) -> str: + if isinstance(self.tail, Nil): + return self.head.show() + return f"{self.head.show()}.{self.tail.show()}" + + +class Nil(Node): + """Nil node — empty list.""" + + dir = 0 + + def __init__(self) -> None: + super().__init__() + self.occupation: list[OccupationInfo] = [_ZERO_OCCUPATION] + + def draw(self, *args: Any, **kwargs: Any) -> None: + pass + + def show(self) -> str: + return "" + + +class Leaf(Node): + """Abstract base for l+, l-.""" + + _show_prefix: str + + def __init__(self) -> None: + super().__init__() + self.r: float = 0 + + def draw(self, *args: Any, **kwargs: Any) -> None: + pass + + def show(self) -> str: + return self._show_prefix + + +class Leaf_plus(Leaf): + """l+ node.""" + + dir = 1 + _show_prefix = "l+" + + +class Leaf_minus(Leaf): + """l- node.""" + + dir = -1 + _show_prefix = "l-" + + +class B_Evc(Node): + """Abstract base for b++, b--.""" + + head: Node + tail: Node + _show_prefix: str + + def __init__(self, head: Node, tail: Node) -> None: + super().__init__(head, tail) + margin = self._config.b_evc_margin + self.r_up: float = head.r + self.r_lw: float = tail.r + self.r: float = self.r_up + self.r_lw + 2 * margin + + def draw(self, center: _CenterArg = (0, 0), *args: Any, **kwargs: Any) -> None: + center = self._resolve_center(center) + margin = self._config.b_evc_margin + cx, cy = center + upper = (cx, cy + self.r_lw + margin) + lower = (cx, cy - self.r_up - margin) + self._cv.draw_point((cx, cy + self.r_lw - self.r_up)) + self._cv.draw_circle(self.r_up + margin, upper) + self._cv.draw_circle(self.r_lw + margin, lower) + self.plot_arrow(center) + self.head.draw(upper) + self.tail.draw(lower) + + def plot_arrow(self, center: _CenterArg = (0, 0), *args: Any, **kwargs: Any) -> None: + margin = self._config.b_evc_margin + cx, cy = self._resolve_center(center) + self._cv.draw_arrow( + (cx, cy + self.r_lw + 2 * margin + self.r_up), + self.dir2rad(), + ) + self._cv.draw_arrow( + (cx, cy - self.r_up - 2 * margin - self.r_lw), + math.pi - self.dir2rad(), + ) + + def show(self) -> str: + return self._show_two_children() + + +class B_plus_plus(B_Evc): + """b++ node.""" + + dir = 1 + _show_prefix = "b++" + + +class B_minus_minus(B_Evc): + """b-- node.""" + + dir = -1 + _show_prefix = "b--" + + +class B_Flip(Node): + """Abstract base for b+-, b-+.""" + + head: Node + tail: Node + _show_prefix: str + + def __init__(self, head: Node, tail: Node) -> None: + super().__init__(head, tail) + margin = self._config.b_flip_margin + # tail (2nd child) goes inner/upper, head (1st child) goes outer/lower + self.r_up: float = tail.r + self.r_lw: float = head.r + self.r: float = self.r_up + self.r_lw + 2 * margin + + def draw(self, center: _CenterArg = (0, 0), *args: Any, **kwargs: Any) -> None: + center = self._resolve_center(center) + margin = self._config.b_flip_margin + cx, cy = center + inner_center = (cx, cy + self.r_lw + margin) + inner_r = self.r_up + margin + outer_r = self.r_up + self.r_lw + 2 * margin + self._cv.draw_circle(inner_r, inner_center) + self._cv.draw_circle(outer_r, center) + self._cv.draw_point((cx, inner_center[1] + inner_r)) + self.plot_arrow(center) + # tail (2nd child) → inner/upper circle + self.tail.draw(inner_center) + # head (1st child) → outer/lower region + self.head.draw((cx, cy - self.r_up - margin)) + + def plot_arrow(self, center: _CenterArg = (0, 0), *args: Any, **kwargs: Any) -> None: + cx, cy = self._resolve_center(center) + inner_bottom_y = cy + self.r_lw - self.r_up + outer_r = self.r_up + self.r_lw + 2 * self._config.b_flip_margin + # Arrow at bottom of inner circle: shows tail's (2nd sign) direction + self._cv.draw_arrow((cx, inner_bottom_y), theta=self.dir2rad()) + # Arrow at bottom of outer circle: shows head's (1st sign) direction + self._cv.draw_arrow((cx, cy - outer_r), theta=math.pi - self.dir2rad()) + + def show(self) -> str: + return self._show_two_children() + + +class B_plus_minus(B_Flip): + """b+- node.""" + + dir = 1 + _show_prefix = "b+-" + + +class B_minus_plus(B_Flip): + """b-+ node.""" + + dir = -1 + _show_prefix = "b-+" + + +class Beta(Node): + """Abstract base for B+, B-.""" + + head: Node + _show_prefix: str + + def __init__(self, head: Node) -> None: + super().__init__(head) + cfg = self._config + high_children = c_list_highest(head.occupation) + children_length = c_list_circ_length(head.occupation, cfg.beta_margin) + self.center_r: float = children_length / (2 * math.pi) + if children_length < 1: + self.center_r = cfg.beta_min_circ / (2 * math.pi) + self.r: float = self.center_r + high_children + + def draw(self, center: _CenterArg = (0, 0), *args: Any, **kwargs: Any) -> None: + center = self._resolve_center(center) + cfg = self._config + self._cv.draw_circle(self.center_r, center, circle_fill=True) + for_children = make_list_for_c( + self.head.occupation, self.center_r, center, False, + cfg.beta_margin, config=cfg, + ) + self.plot_arrow(center) + self.head.draw(for_children) + + def plot_arrow(self, center: _CenterArg = (0, 0), *args: Any, **kwargs: Any) -> None: + cx, cy = self._resolve_center(center) + self._cv.draw_arrow( + (cx + self.center_r, cy), + math.pi * 1.5 - self.dir2rad(), + ) + + def show(self) -> str: + return f"{self._show_prefix}{{{self.head.show()}}}" + + +class Beta_plus(Beta): + """B+ node.""" + + dir = 1 + _show_prefix = "B+" + + +class Beta_minus(Beta): + """B- node.""" + + dir = -1 + _show_prefix = "B-" + + +class C(Node): + """Abstract base for c+, c-.""" + + head: Node + tail: Node + _show_prefix: str + + def __init__(self, head: Node, tail: Node) -> None: + super().__init__(head, tail) + cfg = self._config + self.high_children: float = c_list_highest(tail.occupation) + self.children_length: float = c_list_circ_length(tail.occupation, cfg.c_margin) + # b_offset: how far the head b-node is placed from the parent circle. + # high must exceed b_offset + head.r + margin so that the spline apex + # clears the head obstacle circle entirely. + b_offset = max( + self.high_children + cfg.c_circ_margin + head.r, + head.r * 2 + cfg.c_circ_margin * 2, + ) + self.high: float = max( + 2 * head.r + self.high_children + cfg.c_margin, + b_offset + head.r + cfg.c_circ_margin, + ) + if self.head.r == 0 and len(self.tail.occupation) != 1: + self.high += len(self.tail.occupation) + # Effective arc-length extent: at least children_length, but wider for + # tall splines to prevent crossing with adjacent C-node splines. + # min_extent ensures enough room for child-start offset on both sides. + min_extent = self.children_length + 2 * self.high * cfg.c_child_start_offset + self.effective_extent: float = max( + self.children_length, + self.high * cfg.c_height_spacing_factor, + min_extent, + ) + # Add spline clearance: taller splines need wider angular allocation + # to prevent their curves from crossing neighbouring splines. + spline_clearance = self.high * cfg.c_spline_clearance_factor + bottom_length = max(head.r * 2, self.effective_extent + spline_clearance) + self.occupation: list[OccupationInfo] = [ + OccupationInfo(height=self.high, width=bottom_length) + ] + + def draw(self, c_data: DrawContext | None = None, *args: Any, **kwargs: Any) -> None: + cfg = self._config + high_children = max(self.high_children, cfg.c_min_child_height) + + if not isinstance(c_data, DrawContext): + return + + length = c_data.length + center_r = c_data.parent_r + center = c_data.parent_center + bool_b0 = c_data.parent_type + + start_theta = length / center_r + start_point = theta_point(start_theta, center_r, center) + end_theta = (length + self.effective_extent) / center_r + end_point = theta_point(end_theta, center_r, center) + high_theta = (end_theta - start_theta) / 2 + start_theta + sign = -1 if bool_b0 else 1 + high_point = theta_point(high_theta, center_r + sign * self.high, center) + # Place head b-node far enough from the parent circle so it doesn't + # overlap with the filled region. Minimum clearance = head.r + margin. + b_offset = max( + high_children + cfg.c_circ_margin + self.head.r, + self.head.r * 2 + cfg.c_circ_margin * 2, + ) + b_center = theta_point( + high_theta, + center_r + sign * b_offset, + center, + ) + self.plot_arrow(high_point, high_theta, bool_b0=bool_b0) + + # Build spline control points. + # For outward splines (non-B0 parent), insert guide points just + # outside the parent circle to prevent dipping into filled regions. + # For inward splines (B0 parent), guides are not needed. + use_guides = not bool_b0 + if use_guides: + nudge_frac = 0.12 + nudge_r = center_r + sign * self.high * nudge_frac + guide_start = theta_point( + start_theta + (high_theta - start_theta) * nudge_frac, + nudge_r, center, + ) + guide_end = theta_point( + end_theta - (end_theta - high_theta) * nudge_frac, + nudge_r, center, + ) + + if self.head.r != 0: + b_r_theta = math.pi - (math.pi / 2 + high_theta) + if use_guides: + # Outward (non-B0): wider bypass to clear the head obstacle + bypass_r = self.head.r * 2 + cfg.c_circ_margin + b_r_center = theta_point(-b_r_theta, bypass_r, b_center) + b_l_center = theta_point(math.pi - b_r_theta, bypass_r, b_center) + self._cv.draw_spline( + [start_point, guide_start, b_r_center, high_point, + b_l_center, guide_end, end_point] + ) + elif self.head.r * 2 < self.children_length / 2: + # B0 inward, small head: 3-point spline + self._cv.draw_spline([start_point, high_point, end_point]) + else: + # B0 inward, large head: 5-point bypass + bypass_r = self.head.r + cfg.c_circ_margin + b_r_center = theta_point(-b_r_theta, bypass_r, b_center) + b_l_center = theta_point(math.pi - b_r_theta, bypass_r, b_center) + self._cv.draw_spline( + [start_point, b_r_center, high_point, b_l_center, end_point] + ) + else: + if use_guides: + self._cv.draw_spline( + [start_point, guide_start, high_point, guide_end, end_point] + ) + else: + self._cv.draw_spline([start_point, high_point, end_point]) + self._cv.draw_point(start_point) + self._cv.draw_point(end_point) + # Center sub-children within the (possibly wider) effective extent. + # Enforce a minimum padding so that the first child C's start_point + # is far enough from this C's start_point to avoid crossing. + raw_padding = (self.effective_extent - self.children_length) / 2 + min_padding = self.high * cfg.c_child_start_offset + padding = max(raw_padding, min_padding) + # Adaptive margin: taller children need more spacing to avoid crossings + base_margin = cfg.c_margin / 1.5 + if self.tail.occupation and self.tail.occupation != [_ZERO_OCCUPATION]: + tallest_child = c_list_highest(self.tail.occupation) + height_bonus = tallest_child * cfg.c_spline_clearance_factor / max(center_r, 1.0) + adaptive_margin = base_margin + height_bonus + else: + adaptive_margin = base_margin + for_children = make_list_for_c( + self.tail.occupation, + center_r, + center, + bool_b0, + adaptive_margin, + parent_length=length + padding, + config=cfg, + ) + self.head.draw(b_center) + self.tail.draw(for_children) + + def plot_arrow( + self, + high_point: tuple[float, float] = (0, 0), + high_theta: float = 0, + bool_b0: bool = False, + *args: Any, + **kwargs: Any, + ) -> None: + theta = high_theta + math.pi * 0.5 + self.dir2rad() + if bool_b0: + # Reverse direction only for B0-parented C curves + theta += math.pi + self._cv.draw_arrow( + high_point, + theta, + ) + + def show(self) -> str: + return self._show_two_children() + + +class C_plus(C): + """c+ node.""" + + dir = -1 + _show_prefix = "c+" + + +class C_minus(C): + """c- node.""" + + dir = 1 + _show_prefix = "c-" diff --git a/src/viscot/core/parser.py b/src/viscot/core/parser.py new file mode 100644 index 0000000..ba406dd --- /dev/null +++ b/src/viscot/core/parser.py @@ -0,0 +1,192 @@ +"""Parser for COT tree notation — uses PLY yacc.""" + +from __future__ import annotations + +import ply.yacc as yacc + +from . import nodes +from .config import LayoutConfig +from .lexer import lexer as _lexer +from .lexer import tokens as _tokens # noqa: F401 — needed by PLY + +# Re-export for PLY +tokens = _tokens + +_last_error: str | None = None + + +def p_s(p): # type: ignore[no-untyped-def] + """s : A0 '(' as ')' + | B0_PLUS '(' b_plus ',' cs_minus ')' + | B0_MINUS '(' b_minus ',' cs_plus ')' + | '(' s ')'""" + if p[1] == "A0": + p[0] = nodes.A0(p[3]) + elif p[1] == "B0+": + p[0] = nodes.B0_plus(p[3], p[5]) + elif p[1] == "B0-": + p[0] = nodes.B0_minus(p[3], p[5]) + elif p[1] == "(": + p[0] = p[2] + + +def p_empty(p): # type: ignore[no-untyped-def] + """empty :""" + pass + + +def p_as(p): # type: ignore[no-untyped-def] + """as : empty + | a + | a '.' as1""" + if p[1] is None: + p[0] = nodes.Nil() + elif len(p) == 2: + p[0] = nodes.Cons(p[1], nodes.Nil()) + elif p[2] == ".": + p[0] = nodes.Cons(p[1], p[3]) + + +def p_as1(p): # type: ignore[no-untyped-def] + """as1 : a + | a '.' as1""" + if len(p) == 2: + p[0] = nodes.Cons(p[1], nodes.Nil()) + elif p[2] == ".": + p[0] = nodes.Cons(p[1], p[3]) + + +def p_a(p): # type: ignore[no-untyped-def] + """a : A_PLUS '(' b_plus ')' + | A_MINUS '(' b_minus ')' + | A2 '(' cs_plus ',' cs_minus ')'""" + if p[1] == "a+": + p[0] = nodes.A_plus(p[3]) + elif p[1] == "a-": + p[0] = nodes.A_minus(p[3]) + elif p[1] == "a2": + p[0] = nodes.A2(p[3], p[5]) + + +def p_b_plus(p): # type: ignore[no-untyped-def] + """b_plus : LEAF_PLUS + | B_PLUS_PLUS '(' b_plus ',' b_plus ')' + | B_PLUS_MINUS '(' b_plus ',' b_minus ')' + | BETA_PLUS '{' cs_plus '}'""" + if p[1] == "l+": + p[0] = nodes.Leaf_plus() + elif p[1] == "b++": + p[0] = nodes.B_plus_plus(p[3], p[5]) + elif p[1] == "b+-": + p[0] = nodes.B_plus_minus(p[3], p[5]) + elif p[1] == "B+": + p[0] = nodes.Beta_plus(p[3]) + + +def p_b_minus(p): # type: ignore[no-untyped-def] + """b_minus : LEAF_MINUS + | B_MINUS_MINUS '(' b_minus ',' b_minus ')' + | B_MINUS_PLUS '(' b_minus ',' b_plus ')' + | BETA_MINUS '{' cs_minus '}'""" + if p[1] == "l-": + p[0] = nodes.Leaf_minus() + elif p[1] == "b--": + p[0] = nodes.B_minus_minus(p[3], p[5]) + elif p[1] == "b-+": + p[0] = nodes.B_minus_plus(p[3], p[5]) + elif p[1] == "B-": + p[0] = nodes.Beta_minus(p[3]) + + +def p_c_plus(p): # type: ignore[no-untyped-def] + """c_plus : C_PLUS '(' b_plus ',' cs_minus ')'""" + p[0] = nodes.C_plus(p[3], p[5]) + + +def p_c_minus(p): # type: ignore[no-untyped-def] + """c_minus : C_MINUS '(' b_minus ',' cs_plus ')'""" + p[0] = nodes.C_minus(p[3], p[5]) + + +def p_cs_plus(p): # type: ignore[no-untyped-def] + """cs_plus : empty + | c_plus + | c_plus '.' cs_plus1 + | '(' cs_plus ')'""" + if p[1] is None: + p[0] = nodes.Nil() + elif p[1] != "(" and len(p) == 2: + p[0] = nodes.Cons(p[1], nodes.Nil()) + elif len(p) == 4 and p[2] == ".": + p[0] = nodes.Cons(p[1], p[3]) + elif p[1] == "(": + p[0] = p[2] + + +def p_cs_plus1(p): # type: ignore[no-untyped-def] + """cs_plus1 : c_plus + | c_plus '.' cs_plus1""" + if len(p) == 2: + p[0] = nodes.Cons(p[1], nodes.Nil()) + elif p[2] == ".": + p[0] = nodes.Cons(p[1], p[3]) + + +def p_cs_minus(p): # type: ignore[no-untyped-def] + """cs_minus : empty + | c_minus + | c_minus '.' cs_minus1 + | '(' cs_minus ')'""" + if p[1] is None: + p[0] = nodes.Nil() + elif p[1] != "(" and len(p) == 2: + p[0] = nodes.Cons(p[1], nodes.Nil()) + elif len(p) == 4 and p[2] == ".": + p[0] = nodes.Cons(p[1], p[3]) + elif p[1] == "(": + p[0] = p[2] + + +def p_cs_minus1(p): # type: ignore[no-untyped-def] + """cs_minus1 : c_minus + | c_minus '.' cs_minus1""" + if len(p) == 2: + p[0] = nodes.Cons(p[1], nodes.Nil()) + elif p[2] == ".": + p[0] = nodes.Cons(p[1], p[3]) + + +def p_error(p): # type: ignore[no-untyped-def] + global _last_error + if p is None: + _last_error = "unexpected end of input" + else: + _last_error = f"syntax error at {p.value!r} (position {p.lexpos})" + + +parser = yacc.yacc(debug=False, write_tables=False) + + +def parse(data: str, config: LayoutConfig | None = None) -> nodes.Node: + """Parse a COT tree notation string into a Node tree. + + Args: + data: COT tree notation string. + config: LayoutConfig to use for node construction. Defaults to DEFAULT_CONFIG. + + Returns: + Parsed Node tree. + """ + global _last_error + _last_error = None + + if config is not None: + with nodes.use_config(config): + result = parser.parse(data, lexer=_lexer.clone()) + else: + result = parser.parse(data, lexer=_lexer.clone()) + + if result is None: + detail = _last_error or "unknown error" + raise ValueError(f"Failed to parse {data!r}: {detail}") + return result # type: ignore[no-any-return] diff --git a/src/viscot/evaluation/__init__.py b/src/viscot/evaluation/__init__.py new file mode 100644 index 0000000..f7b3361 --- /dev/null +++ b/src/viscot/evaluation/__init__.py @@ -0,0 +1,10 @@ +"""Evaluation framework for visCOT.""" + +from .benchmark import BENCHMARK_EXPRESSIONS, run_benchmark +from .comparison import compare_before_after + +__all__ = [ + "BENCHMARK_EXPRESSIONS", + "run_benchmark", + "compare_before_after", +] diff --git a/src/viscot/evaluation/benchmark.py b/src/viscot/evaluation/benchmark.py new file mode 100644 index 0000000..d523cb6 --- /dev/null +++ b/src/viscot/evaluation/benchmark.py @@ -0,0 +1,91 @@ +"""Batch evaluation runner for COT expressions.""" + +from __future__ import annotations + +import matplotlib + +matplotlib.use("Agg") + +import sys +from dataclasses import dataclass + +from ..core import render_expression +from ..metrics.composite import CompositeScore, compute_composite_score + +# Benchmark expressions categorized by complexity +BENCHMARK_EXPRESSIONS: dict[str, list[str]] = { + "leaf_only": [ + "a0()", + "a0(a+(l+))", + "a0(a-(l-))", + "a0(a+(l+).a+(l+))", + "B0+(l+,)", + "B0-(l-,)", + ], + "single_nesting": [ + "B0+(l+,c-(l-,))", + "B0+(l+,c-(l-,).c-(l-,))", + "B0-(l-,c+(l+,))", + "B0+(b+-(l+,l-),)", + "B0-(b-+(l-,l+),)", + "B0+(b++(l+,l+),)", + ], + "double_nesting": [ + "B0+(b+-(l+,l-),c-(l-,).c-(l-,).c-(l-,))", + "B0+(b+-(l+,l-),c-(B-{},).c-(l-,).c-(l-,))", + "B0-(b-+(b-+(l-,l+),B+{}),c+(B+{},).c+(l+,).c+(l+,))", + "B0+(b+-(b+-(l+,l-),B-{}),c-(B-{},).c-(l-,).c-(l-,))", + ], + "max_depth": [ + "B0+(b+-(b+-(l+,l-),b+-(l+,l-)),c-(B-{c+(l+,)},).c-(l-,))", + ], +} + + +@dataclass +class BenchmarkResult: + """Result for a single benchmark expression.""" + + expression: str + category: str + score: CompositeScore + tree_depth: int + + +def _estimate_depth(expression: str) -> int: + """Rough estimate of tree depth by counting nesting.""" + depth = 0 + max_depth = 0 + for ch in expression: + if ch in "({": + depth += 1 + max_depth = max(max_depth, depth) + elif ch in ")}": + depth -= 1 + return max_depth + + +def run_benchmark() -> list[BenchmarkResult]: + """Run all benchmark expressions and collect scores. + + Returns: + List of BenchmarkResult for each expression. + """ + results: list[BenchmarkResult] = [] + for category, expressions in BENCHMARK_EXPRESSIONS.items(): + for expr in expressions: + try: + with render_expression(expr) as canvas: + score = compute_composite_score(canvas.drawn_elements) + depth = _estimate_depth(expr) + results.append( + BenchmarkResult( + expression=expr, + category=category, + score=score, + tree_depth=depth, + ) + ) + except (ValueError, RuntimeError) as e: + print(f"Failed to benchmark {expr!r}: {e}", file=sys.stderr) + return results diff --git a/src/viscot/evaluation/comparison.py b/src/viscot/evaluation/comparison.py new file mode 100644 index 0000000..2fec56d --- /dev/null +++ b/src/viscot/evaluation/comparison.py @@ -0,0 +1,113 @@ +"""Before/after statistical comparison of layout improvements.""" + +from __future__ import annotations + +import matplotlib + +matplotlib.use("Agg") + +import sys +from dataclasses import dataclass + +import numpy as np +from scipy import stats + +from ..core import render_expression +from ..core.config import LayoutConfig +from ..metrics.composite import CompositeScore, compute_composite_score + + +@dataclass +class ComparisonResult: + """Statistical comparison between two configurations.""" + + expressions: list[str] + scores_before: list[float] + scores_after: list[float] + d_cv_before: list[float] + d_cv_after: list[float] + wilcoxon_statistic: float + wilcoxon_pvalue: float + cohens_d: float + mean_improvement: float + + +def _render_and_score(expression: str, config: LayoutConfig | None = None) -> CompositeScore: + """Render expression with config and return composite score.""" + with render_expression(expression, config) as canvas: + return compute_composite_score(canvas.drawn_elements) + + +def _cohens_d(before: np.ndarray, after: np.ndarray) -> float: + """Compute Cohen's d effect size for paired samples.""" + diff = after - before + d_mean = np.mean(diff) + d_std = np.std(diff, ddof=1) + if d_std < 1e-15: + return 0.0 + return float(d_mean / d_std) + + +def compare_before_after( + expressions: list[str], + config_before: LayoutConfig | None = None, + config_after: LayoutConfig | None = None, +) -> ComparisonResult: + """Compare visualization quality before and after layout changes. + + Uses paired Wilcoxon signed-rank test and Cohen's d effect size. + + Args: + expressions: COT expressions to evaluate. + config_before: Configuration for "before" (default layout). + config_after: Configuration for "after" (improved layout). + + Returns: + ComparisonResult with statistical measures. + """ + scores_before: list[float] = [] + scores_after: list[float] = [] + d_cv_before: list[float] = [] + d_cv_after: list[float] = [] + + for expr in expressions: + try: + sb = _render_and_score(expr, config_before) + sa = _render_and_score(expr, config_after) + scores_before.append(sb.score) + scores_after.append(sa.score) + d_cv_before.append(sb.spacing.d_cv) + d_cv_after.append(sa.spacing.d_cv) + except (ValueError, RuntimeError) as e: + print(f"Skipping {expr!r}: {e}", file=sys.stderr) + + before_arr = np.array(scores_before) + after_arr = np.array(scores_after) + + # Wilcoxon signed-rank test + if len(before_arr) >= 6: + try: + stat_result = stats.wilcoxon(after_arr - before_arr, alternative="greater") + w_stat = float(stat_result.statistic) + w_pval = float(stat_result.pvalue) + except ValueError: + w_stat = 0.0 + w_pval = 1.0 + else: + w_stat = 0.0 + w_pval = 1.0 + + cd = _cohens_d(before_arr, after_arr) + mean_imp = float(np.mean(after_arr - before_arr)) if len(before_arr) > 0 else 0.0 + + return ComparisonResult( + expressions=expressions, + scores_before=scores_before, + scores_after=scores_after, + d_cv_before=d_cv_before, + d_cv_after=d_cv_after, + wilcoxon_statistic=w_stat, + wilcoxon_pvalue=w_pval, + cohens_d=cd, + mean_improvement=mean_imp, + ) diff --git a/src/viscot/evaluation/report.py b/src/viscot/evaluation/report.py new file mode 100644 index 0000000..800699e --- /dev/null +++ b/src/viscot/evaluation/report.py @@ -0,0 +1,129 @@ +"""Report generation — LaTeX tables and comparison plots.""" + +from __future__ import annotations + +import matplotlib + +matplotlib.use("Agg") + +from pathlib import Path + +import matplotlib.pyplot as plt + +from .benchmark import BenchmarkResult, run_benchmark +from .comparison import ComparisonResult + + +def generate_latex_table(results: list[BenchmarkResult], output_path: Path) -> None: + """Generate a LaTeX table summarizing benchmark results. + + Args: + results: List of benchmark results. + output_path: Path to write the .tex file. + """ + lines = [ + r"\begin{table}[htbp]", + r"\centering", + r"\caption{Readability metrics for benchmark expressions}", + r"\label{tab:benchmark}", + r"\begin{tabular}{llrrrr}", + r"\toprule", + r"Category & Expression & Crossings & $d_{cv}$ & Jerk & Score \\", + r"\midrule", + ] + + for r in results: + expr_escaped = r.expression.replace("_", r"\_") + if len(expr_escaped) > 30: + expr_escaped = expr_escaped[:27] + "..." + lines.append( + f" {r.category} & \\texttt{{{expr_escaped}}} & " + f"{r.score.overlap.crossing_count} & " + f"{r.score.spacing.d_cv:.3f} & " + f"{r.score.smoothness.jerk:.3f} & " + f"{r.score.score:.2f} \\\\" + ) + + lines.extend([ + r"\bottomrule", + r"\end{tabular}", + r"\end{table}", + ]) + + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text("\n".join(lines), encoding="utf-8") + + +def generate_comparison_plots( + comparison: ComparisonResult, + output_dir: Path, +) -> None: + """Generate before/after comparison plots. + + Creates: + - Box plot of d_cv before vs after + - Scatter plot of composite score vs tree depth + + Args: + comparison: ComparisonResult from compare_before_after. + output_dir: Directory to write plot images. + """ + output_dir.mkdir(parents=True, exist_ok=True) + + # Box plot: d_cv before vs after + fig, ax = plt.subplots(figsize=(6, 4)) + data = [comparison.d_cv_before, comparison.d_cv_after] + bp = ax.boxplot(data, tick_labels=["Before", "After"], patch_artist=True) + bp["boxes"][0].set_facecolor("#ff9999") + bp["boxes"][1].set_facecolor("#99ccff") + ax.set_ylabel("$d_{cv}$ (spacing coefficient of variation)") + ax.set_title("Spacing Uniformity: Before vs After") + ax.text( + 0.02, 0.98, + f"Wilcoxon p={comparison.wilcoxon_pvalue:.4f}\n" + f"Cohen's d={comparison.cohens_d:.3f}", + transform=ax.transAxes, + verticalalignment="top", + fontsize=9, + bbox=dict(boxstyle="round", facecolor="wheat", alpha=0.5), + ) + plt.tight_layout() + fig.savefig(output_dir / "d_cv_comparison.pdf") + fig.savefig(output_dir / "d_cv_comparison.png", dpi=150) + plt.close(fig) + + # Scatter: composite score before vs after + fig, ax = plt.subplots(figsize=(6, 4)) + n = len(comparison.scores_before) + ax.scatter(range(n), comparison.scores_before, label="Before", marker="x", color="red") + ax.scatter(range(n), comparison.scores_after, label="After", marker="o", color="blue") + ax.set_xlabel("Expression index") + ax.set_ylabel("Composite Score") + ax.set_title("Composite Readability Score: Before vs After") + ax.legend() + plt.tight_layout() + fig.savefig(output_dir / "score_comparison.pdf") + fig.savefig(output_dir / "score_comparison.png", dpi=150) + plt.close(fig) + + +def generate_full_report(output_dir: Path) -> None: + """Run benchmarks, generate tables and plots. + + Args: + output_dir: Directory for all output files. + """ + output_dir.mkdir(parents=True, exist_ok=True) + + # Run benchmark + results = run_benchmark() + generate_latex_table(results, output_dir / "benchmark_table.tex") + + print(f"Generated benchmark table with {len(results)} expressions") + for r in results: + print( + f" [{r.category}] {r.expression[:40]:40s} " + f"score={r.score.score:.2f} " + f"crossings={r.score.overlap.crossing_count} " + f"d_cv={r.score.spacing.d_cv:.3f}" + ) diff --git a/src/viscot/layout/__init__.py b/src/viscot/layout/__init__.py new file mode 100644 index 0000000..9386a97 --- /dev/null +++ b/src/viscot/layout/__init__.py @@ -0,0 +1,11 @@ +"""Layout optimization modules.""" + +from .occupation import CircularOccupation, EllipticalOccupation, OccupationArea +from .optimizer import optimize_layout + +__all__ = [ + "CircularOccupation", + "EllipticalOccupation", + "OccupationArea", + "optimize_layout", +] diff --git a/src/viscot/layout/occupation.py b/src/viscot/layout/occupation.py new file mode 100644 index 0000000..cb97b02 --- /dev/null +++ b/src/viscot/layout/occupation.py @@ -0,0 +1,96 @@ +"""Adaptive occupation areas — elliptical replacement for circular.""" + +from __future__ import annotations + +import math +from dataclasses import dataclass + + +@dataclass +class CircularOccupation: + """Standard circular occupation area.""" + + center: tuple[float, float] + radius: float + + def bounding_box(self) -> tuple[float, float, float, float]: + """Return (x_min, y_min, x_max, y_max).""" + cx, cy = self.center + return (cx - self.radius, cy - self.radius, cx + self.radius, cy + self.radius) + + def area(self) -> float: + return math.pi * self.radius ** 2 + + +@dataclass +class EllipticalOccupation: + """Elliptical occupation area for B_Evc nodes. + + Replaces the circular area that causes excess whitespace. + semi_major = r_up + r_lw + 2*margin (vertical extent) + semi_minor = max(r_up, r_lw) + margin (horizontal extent) + """ + + center: tuple[float, float] + semi_major: float # vertical + semi_minor: float # horizontal + + def bounding_box(self) -> tuple[float, float, float, float]: + cx, cy = self.center + return ( + cx - self.semi_minor, + cy - self.semi_major, + cx + self.semi_minor, + cy + self.semi_major, + ) + + def area(self) -> float: + return math.pi * self.semi_major * self.semi_minor + + +OccupationArea = CircularOccupation | EllipticalOccupation + + +def compute_b_evc_occupation( + r_up: float, + r_lw: float, + margin: float, + center: tuple[float, float] = (0, 0), + use_ellipse: bool = True, +) -> OccupationArea: + """Compute occupation area for B_Evc (b++/b--) nodes. + + Args: + r_up: Radius of the upper child's occupation area. + r_lw: Radius of the lower child's occupation area. + margin: Margin between children and parent boundary. + center: Center of the occupation area. + use_ellipse: If True, use elliptical area (reduced whitespace). + + Returns: + Either EllipticalOccupation or CircularOccupation. + """ + if use_ellipse: + semi_major = r_up + r_lw + 2 * margin + semi_minor = max(r_up, r_lw) + margin + return EllipticalOccupation( + center=center, semi_major=semi_major, semi_minor=semi_minor + ) + r = r_up + r_lw + 2 * margin + return CircularOccupation(center=center, radius=r) + + +def depth_adaptive_margin( + base_margin: float, + depth: int, + n_siblings: int, +) -> float: + """Compute depth-adaptive margin. + + margin = base_margin * (0.8 ** depth) * min(1.0, 3.0 / max(n_siblings, 1)) + + Deeper nodes and nodes with more siblings get smaller margins. + """ + depth_factor = 0.8 ** depth + sibling_factor = min(1.0, 3.0 / max(n_siblings, 1)) + return base_margin * depth_factor * sibling_factor diff --git a/src/viscot/layout/optimizer.py b/src/viscot/layout/optimizer.py new file mode 100644 index 0000000..886a8bf --- /dev/null +++ b/src/viscot/layout/optimizer.py @@ -0,0 +1,87 @@ +"""Metrics-driven layout optimization using scipy.optimize.""" + +from __future__ import annotations + +import numpy as np + +from ..core import render_expression +from ..core.config import LayoutConfig +from ..metrics.composite import compute_composite_score + + +def _objective(params: np.ndarray, expression: str) -> float: + """Objective function: negative composite score (to minimize).""" + config = LayoutConfig( + a0_margin=float(params[0]), + a_flip_margin=float(params[1]), + a2_margin=float(params[2]), + b0_margin=float(params[3]), + b_evc_margin=float(params[4]), + b_flip_margin=float(params[5]), + beta_margin=float(params[6]), + c_margin=float(params[7]), + c_circ_margin=float(params[8]), + c_height_spacing_factor=float(params[9]), + ) + try: + with render_expression(expression, config) as canvas: + score = compute_composite_score(canvas.drawn_elements) + return -score.score # minimize negative score + except (ValueError, RuntimeError): + return 1e6 # penalty for invalid configs + + +def optimize_layout( + expression: str, + initial_config: LayoutConfig | None = None, + max_iter: int = 100, +) -> LayoutConfig: + """Optimize layout margins using Nelder-Mead. + + Args: + expression: COT tree notation string. + initial_config: Starting configuration. Defaults to DEFAULT_CONFIG. + max_iter: Maximum optimization iterations. + + Returns: + Optimized LayoutConfig. + """ + from scipy.optimize import minimize + + if initial_config is None: + initial_config = LayoutConfig() + + x0 = np.array([ + initial_config.a0_margin, + initial_config.a_flip_margin, + initial_config.a2_margin, + initial_config.b0_margin, + initial_config.b_evc_margin, + initial_config.b_flip_margin, + initial_config.beta_margin, + initial_config.c_margin, + initial_config.c_circ_margin, + initial_config.c_height_spacing_factor, + ]) + + result = minimize( + _objective, + x0, + args=(expression,), + method="Nelder-Mead", + options={"maxiter": max_iter, "xatol": 0.01, "fatol": 0.1}, + ) + + opt = result.x + return LayoutConfig( + a0_margin=float(opt[0]), + a_flip_margin=float(opt[1]), + a2_margin=float(opt[2]), + b0_margin=float(opt[3]), + b_evc_margin=float(opt[4]), + b_flip_margin=float(opt[5]), + beta_margin=float(opt[6]), + c_margin=float(opt[7]), + c_circ_margin=float(opt[8]), + c_height_spacing_factor=float(opt[9]), + ) diff --git a/src/viscot/metrics/__init__.py b/src/viscot/metrics/__init__.py new file mode 100644 index 0000000..97fa56e --- /dev/null +++ b/src/viscot/metrics/__init__.py @@ -0,0 +1,13 @@ +"""Readability metrics for COT visualizations.""" + +from .composite import CompositeScore, compute_composite_score +from .overlap import OverlapResult, compute_overlap +from .smoothness import SmoothnessResult, compute_smoothness +from .spacing import SpacingResult, compute_spacing + +__all__ = [ + "CompositeScore", "compute_composite_score", + "OverlapResult", "compute_overlap", + "SmoothnessResult", "compute_smoothness", + "SpacingResult", "compute_spacing", +] diff --git a/src/viscot/metrics/composite.py b/src/viscot/metrics/composite.py new file mode 100644 index 0000000..5de8f96 --- /dev/null +++ b/src/viscot/metrics/composite.py @@ -0,0 +1,54 @@ +"""Composite readability score combining M1, M2, M3.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from ..core.canvas import DrawnElement +from .overlap import OverlapResult, compute_overlap +from .smoothness import SmoothnessResult, compute_smoothness +from .spacing import SpacingResult, compute_spacing + + +@dataclass +class CompositeScore: + """Combined readability score (higher is better).""" + + score: float + overlap: OverlapResult + spacing: SpacingResult + smoothness: SmoothnessResult + + +def compute_composite_score( + elements: list[DrawnElement], + w1: float = 100.0, + w2: float = 1.0, + w3: float = 1.0, +) -> CompositeScore: + """Compute composite readability score. + + Score = -w1 * crossing_count - w2 * d_cv - w3 * jerk + Higher is better (ideally 0 when no crossings, uniform spacing, smooth curves). + + Args: + elements: List of drawn elements from Canvas. + w1: Weight for crossing count penalty. + w2: Weight for spacing coefficient of variation penalty. + w3: Weight for jerk (curvature variation) penalty. + + Returns: + CompositeScore with individual and combined results. + """ + overlap = compute_overlap(elements, spline_only=True) + spacing = compute_spacing(elements) + smoothness = compute_smoothness(elements) + + score = -w1 * overlap.crossing_count - w2 * spacing.d_cv - w3 * smoothness.jerk + + return CompositeScore( + score=score, + overlap=overlap, + spacing=spacing, + smoothness=smoothness, + ) diff --git a/src/viscot/metrics/overlap.py b/src/viscot/metrics/overlap.py new file mode 100644 index 0000000..bee36c4 --- /dev/null +++ b/src/viscot/metrics/overlap.py @@ -0,0 +1,127 @@ +"""M1: Overlap and crossing detection metric.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +from ..core.canvas import DrawnElement, DrawnSpline +from .sampling import elements_to_polylines + + +def _bounding_box(poly: np.ndarray) -> tuple[float, float, float, float]: + """Return (x_min, y_min, x_max, y_max) for a polyline.""" + return ( + float(poly[:, 0].min()), + float(poly[:, 1].min()), + float(poly[:, 0].max()), + float(poly[:, 1].max()), + ) + + +def _bboxes_overlap( + a: tuple[float, float, float, float], + b: tuple[float, float, float, float], +) -> bool: + """Check if two bounding boxes overlap.""" + return a[0] <= b[2] and a[2] >= b[0] and a[1] <= b[3] and a[3] >= b[1] + + +def _segments_intersect( + p1: np.ndarray, + p2: np.ndarray, + p3: np.ndarray, + p4: np.ndarray, +) -> bool: + """Check if line segment p1-p2 intersects p3-p4 using cross products.""" + d1 = p2 - p1 + d2 = p4 - p3 + + cross = d1[0] * d2[1] - d1[1] * d2[0] + if abs(cross) < 1e-12: + return False + + t = ((p3[0] - p1[0]) * d2[1] - (p3[1] - p1[1]) * d2[0]) / cross + u = ((p3[0] - p1[0]) * d1[1] - (p3[1] - p1[1]) * d1[0]) / cross + + return bool(0 < t < 1 and 0 < u < 1) + + +@dataclass +class OverlapResult: + """Result of overlap/crossing detection.""" + + crossing_count: int + overlap_ratio: float # fraction of points within epsilon of another element + + +def compute_overlap( + elements: list[DrawnElement], + epsilon: float = 0.05, + spline_only: bool = False, +) -> OverlapResult: + """Compute crossing count and overlap ratio for drawn elements. + + Uses bounding box pruning to skip pairs that cannot intersect. + + Args: + elements: List of drawn elements to analyze. + epsilon: Distance threshold for considering points as overlapping. + spline_only: If True, only analyze spline elements (excludes + structural crossings between splines and parent circles). + + Returns: + OverlapResult with crossing_count and overlap_ratio. + """ + if spline_only: + target = [e for e in elements if isinstance(e, DrawnSpline)] + polylines = [e.points for e in target] + else: + polylines = elements_to_polylines(elements) + + if len(polylines) < 2: + return OverlapResult(crossing_count=0, overlap_ratio=0.0) + + # Precompute bounding boxes + bboxes = [_bounding_box(p) for p in polylines] + + # Crossing detection between different polylines (with bbox pruning) + crossing_count = 0 + for i in range(len(polylines)): + for j in range(i + 1, len(polylines)): + if not _bboxes_overlap(bboxes[i], bboxes[j]): + continue + pi = polylines[i] + pj = polylines[j] + for si in range(len(pi) - 1): + for sj in range(len(pj) - 1): + if _segments_intersect(pi[si], pi[si + 1], pj[sj], pj[sj + 1]): + crossing_count += 1 + + # Overlap ratio using epsilon-neighborhood + total_points = 0 + overlap_points = 0 + for i in range(len(polylines)): + for pi_idx in range(len(polylines[i])): + total_points += 1 + pt = polylines[i][pi_idx] + for j in range(len(polylines)): + if i == j: + continue + # Bbox check: skip if point is far from polyline j + bj = bboxes[j] + if (pt[0] < bj[0] - epsilon or pt[0] > bj[2] + epsilon + or pt[1] < bj[1] - epsilon or pt[1] > bj[3] + epsilon): + continue + dists = np.linalg.norm(polylines[j] - pt, axis=1) + if np.min(dists) < epsilon: + overlap_points += 1 + break + + overlap_ratio = overlap_points / total_points if total_points > 0 else 0.0 + + return OverlapResult( + crossing_count=crossing_count, + overlap_ratio=overlap_ratio, + ) diff --git a/src/viscot/metrics/sampling.py b/src/viscot/metrics/sampling.py new file mode 100644 index 0000000..28c2425 --- /dev/null +++ b/src/viscot/metrics/sampling.py @@ -0,0 +1,39 @@ +"""Utilities for discretizing drawn elements into polylines.""" + +from __future__ import annotations + +import math + +import numpy as np + +from ..core.canvas import DrawnCircle, DrawnElement, DrawnLine, DrawnSpline + + +def circle_to_polyline(circle: DrawnCircle, num_points: int = 64) -> np.ndarray: + """Discretize a circle into a polyline of (num_points, 2).""" + angles = np.linspace(0, 2 * math.pi, num_points, endpoint=False) + cx, cy = circle.center + xs = cx + circle.radius * np.cos(angles) + ys = cy + circle.radius * np.sin(angles) + return np.column_stack([xs, ys]) + + +def line_to_polyline(line: DrawnLine) -> np.ndarray: + """Convert a line segment to a polyline of (2, 2).""" + return np.array([line.start, line.end]) + + +def element_to_polyline(elem: DrawnElement) -> np.ndarray | None: + """Convert a DrawnElement to a polyline, or None if not applicable.""" + if isinstance(elem, DrawnSpline): + return elem.points + if isinstance(elem, DrawnCircle): + return circle_to_polyline(elem) + if isinstance(elem, DrawnLine): + return line_to_polyline(elem) + return None + + +def elements_to_polylines(elements: list[DrawnElement]) -> list[np.ndarray]: + """Convert all applicable drawn elements to polylines.""" + return [p for p in (element_to_polyline(e) for e in elements) if p is not None] diff --git a/src/viscot/metrics/smoothness.py b/src/viscot/metrics/smoothness.py new file mode 100644 index 0000000..2f73f90 --- /dev/null +++ b/src/viscot/metrics/smoothness.py @@ -0,0 +1,85 @@ +"""M3: Curvature-based smoothness metric.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +from ..core.canvas import DrawnElement, DrawnSpline +from .sampling import elements_to_polylines + + +def _menger_curvature(p1: np.ndarray, p2: np.ndarray, p3: np.ndarray) -> float: + """Compute Menger curvature for three consecutive points. + + Returns the discrete curvature: 2 * |triangle_area| / (|p1p2| * |p2p3| * |p1p3|). + A circle has constant curvature; a straight line has zero curvature. + """ + d12 = np.linalg.norm(p2 - p1) + d23 = np.linalg.norm(p3 - p2) + d13 = np.linalg.norm(p3 - p1) + denom = d12 * d23 * d13 + if denom < 1e-15: + return 0.0 + # Twice the signed area of the triangle + area = abs((p2[0] - p1[0]) * (p3[1] - p1[1]) - (p3[0] - p1[0]) * (p2[1] - p1[1])) + return float(2.0 * area / denom) + + +@dataclass +class SmoothnessResult: + """Result of smoothness analysis.""" + + kappa_max: float + kappa_mean: float + kappa_var: float + jerk: float # integral of curvature change rate + + +def compute_smoothness(elements: list[DrawnElement]) -> SmoothnessResult: + """Compute curvature-based smoothness metrics. + + For circles, curvature is constant so jerk ≈ 0. + For splines, jerk measures how much the curvature varies — lower is smoother. + + Returns: + SmoothnessResult with kappa_max, kappa_mean, kappa_var, jerk. + """ + # Only analyze splines (curves), not circles or lines + spline_polylines = [ + elem.points for elem in elements + if isinstance(elem, DrawnSpline) and len(elem.points) >= 3 + ] + + if not spline_polylines: + # Fall back to all polylines + spline_polylines = [p for p in elements_to_polylines(elements) if len(p) >= 3] + + if not spline_polylines: + return SmoothnessResult(kappa_max=0.0, kappa_mean=0.0, kappa_var=0.0, jerk=0.0) + + all_curvatures: list[float] = [] + total_jerk = 0.0 + + for poly in spline_polylines: + curvatures = [] + for i in range(1, len(poly) - 1): + k = _menger_curvature(poly[i - 1], poly[i], poly[i + 1]) + curvatures.append(k) + if curvatures: + all_curvatures.extend(curvatures) + # Jerk = sum of |dk/ds| (discrete derivative of curvature) + for i in range(1, len(curvatures)): + total_jerk += abs(curvatures[i] - curvatures[i - 1]) + + if not all_curvatures: + return SmoothnessResult(kappa_max=0.0, kappa_mean=0.0, kappa_var=0.0, jerk=0.0) + + arr = np.array(all_curvatures) + return SmoothnessResult( + kappa_max=float(np.max(arr)), + kappa_mean=float(np.mean(arr)), + kappa_var=float(np.var(arr)), + jerk=total_jerk, + ) diff --git a/src/viscot/metrics/spacing.py b/src/viscot/metrics/spacing.py new file mode 100644 index 0000000..9ce8d76 --- /dev/null +++ b/src/viscot/metrics/spacing.py @@ -0,0 +1,60 @@ +"""M2: Streamline spacing metric.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +from ..core.canvas import DrawnElement +from .sampling import elements_to_polylines + + +@dataclass +class SpacingResult: + """Result of spacing analysis.""" + + d_min: float + d_mean: float + d_std: float + d_cv: float # coefficient of variation = std / mean + + +def compute_spacing(elements: list[DrawnElement]) -> SpacingResult: + """Compute pairwise minimum distances between drawn elements. + + Lower d_cv means more uniform spacing, corresponding to + the "appropriate distance" criterion from the thesis. + + Returns: + SpacingResult with d_min, d_mean, d_std, d_cv. + """ + polylines = elements_to_polylines(elements) + if len(polylines) < 2: + return SpacingResult(d_min=0.0, d_mean=0.0, d_std=0.0, d_cv=0.0) + + # Compute bounding box diagonal for normalization + all_points = np.vstack(polylines) + bbox_min = all_points.min(axis=0) + bbox_max = all_points.max(axis=0) + diag = np.linalg.norm(bbox_max - bbox_min) + if diag < 1e-12: + return SpacingResult(d_min=0.0, d_mean=0.0, d_std=0.0, d_cv=0.0) + + # Pairwise minimum distances + min_dists: list[float] = [] + for i in range(len(polylines)): + for j in range(i + 1, len(polylines)): + # Compute all pairwise distances between points + diff = polylines[i][:, np.newaxis, :] - polylines[j][np.newaxis, :, :] + dists = np.linalg.norm(diff, axis=2) + min_dists.append(float(np.min(dists))) + + dists_arr = np.array(min_dists) / diag # normalize by bbox diagonal + + d_min = float(np.min(dists_arr)) + d_mean = float(np.mean(dists_arr)) + d_std = float(np.std(dists_arr)) + d_cv = d_std / d_mean if d_mean > 1e-12 else 0.0 + + return SpacingResult(d_min=d_min, d_mean=d_mean, d_std=d_std, d_cv=d_cv) diff --git a/src/yacc.py b/src/yacc.py deleted file mode 100644 index ff44a2c..0000000 --- a/src/yacc.py +++ /dev/null @@ -1,85 +0,0 @@ -# Yacc. PLYを用いる - -import ply.yacc as yacc -from .lex import tokens -import sys -from . import flow - -def p_s(p): - '''s : A0 '(' as ')' - | B0_PLUS '(' b_plus ',' '(' cs_minus ')' ')' - | B0_MINUS '(' b_minus ',' '(' cs_plus ')' ')' ''' - if p[1] == 'a0': p[0] = flow.A0(p[3]) - elif p[1] == 'b0+': p[0] = flow.B0_plus(p[3], p[6]) - elif p[1] == 'b0-': p[0] = flow.B0_minus(p[3], p[6]) - -def p_as(p): - '''as : NIL - | CONS '(' a ',' as ')' ''' - if p[1] == 'n': p[0] = flow.Nil() - elif p[1] == 'cons': p[0] = flow.Cons(p[3], p[5]) - -def p_a(p): - '''a : A_PLUS '(' b_plus ')' - | A_MINUS '(' b_minus ')' - | A2 '(' cs_plus ',' cs_minus ')' ''' - if p[1] == 'a+': p[0] = flow.A_plus(p[3]) - elif p[1] == 'a-': p[0] = flow.A_minus(p[3]) - elif p[1] == 'a2': p[0] = flow.A2(p[3],p[5]) - -def p_b_plus(p): - '''b_plus : LEAF - | B_PLUS_PLUS '(' b_plus ',' b_plus ')' - | B_PLUS_MINUS '(' b_plus ',' b_minus ')' - | BETA_PLUS '(' cs_plus ')' ''' - if p[1] == 'l': p[0] = flow.Leaf() - elif p[1] == 'b++': p[0] = flow.B_plus_plus(p[3], p[5]) - elif p[1] == 'b+-': p[0] = flow.B_plus_minus(p[3], p[5]) - elif p[1] == 'be+': p[0] = flow.Beta_plus(p[3]) - -def p_b_minus(p): - '''b_minus : LEAF - | B_MINUS_MINUS '(' b_minus ',' b_minus ')' - | B_MINUS_PLUS '(' b_minus ',' b_plus ')' - | BETA_MINUS '(' cs_minus ')' ''' - if p[1] == 'l': p[0] = flow.Leaf() - elif p[1] == 'b--': p[0] = flow.B_minus_minus(p[3], p[5]) - elif p[1] == 'b-+': p[0] = flow.B_minus_plus(p[3], p[5]) - elif p[1] == 'be-': p[0] = flow.Beta_minus(p[3]) - -def p_c_plus(p): - '''c_plus : C_PLUS '(' b_plus ',' cs_minus ')' ''' - p[0] = flow.C_plus(p[3], p[5]) - -def p_c_minus(p): - '''c_minus : C_MINUS '(' b_minus ',' cs_plus ')' ''' - p[0] = flow.C_minus(p[3], p[5]) - -def p_cs_plus(p): - '''cs_plus : NIL - | CONS '(' c_plus ',' cs_plus ')' ''' - if p[1] == 'n': p[0] = flow.Nil() - elif p[1] == 'cons': p[0] = flow.Cons(p[3], p[5]) - -def p_cs_minus(p): - '''cs_minus : NIL - | CONS '(' c_minus ',' cs_minus ')' ''' - if p[1] == 'n': p[0] = flow.Nil() - elif p[1] == 'cons': p[0] = flow.Cons(p[3], p[5]) - -def p_error(p): - print ('Syntax error in input %s' %p) - -parser = yacc.yacc() - -def parse(data): - return yacc.parse(data) - -if __name__ == '__main__': - while True: - try: - s = input('>>> ') - except EOFError: - break - parser.parse(s).draw() - break diff --git a/test.txt b/test.txt deleted file mode 100644 index 390d48d..0000000 --- a/test.txt +++ /dev/null @@ -1,1950 +0,0 @@ -木表現 - -#a0(n) - -#a0(cons(a+(l),n)) -左広がりのみ(描画4まで) - -#a0(cons(a+(l),n)) -#a0(cons(a-(l),n)) -#a0(cons(a2(n,n),n)) - -#a0(cons(a+(b++(l,l)),n)) -#a0(cons(a+(b+-(l,l)),n)) -#a0(cons(a+(be+(n)),n)) -a -#a0(cons(a-(b--(l,l)),n)) -#a0(cons(a-(b-+(l,l)),n)) -#a0(cons(a-(be-(n)),n)) - -#a0(cons(a2(cons(c+(l,n),n),n),n)) - -#a0(cons(a+(b++(b++(l,l),l)),n)) -#a0(cons(a+(b++(b+-(l,l),l)),n)) -#a0(cons(a+(b++(be+(n),l)),n)) -#a0(cons(a+(b+-(b++(l,l),l)),n)) -#a0(cons(a+(b+-(b+-(l,l),l)),n)) -#a0(cons(a+(b+-(be+(n),l)),n)) -#a0(cons(a+(be+(cons(c+(l,n),n))),n)) cons(c+(l,n),n) - -#a0(cons(a-(b--(b--(l,l),l)),n)) -#a0(cons(a-(b--(b-+(l,l),l)),n)) -#a0(cons(a-(b--(be-(n),l)),n)) -#a0(cons(a-(b-+(b--(l,l),l)),n)) -#a0(cons(a-(b-+(b-+(l,l),l)),n)) -#a0(cons(a-(b-+(be-(n),l)),n))   -#a0(cons(a-(be-(cons(c-(l,n),n))),n)) - -#a0(cons(a2(cons(c+(b++(l,l),n),n),n),n)) b++(l,l) -#a0(cons(a2(cons(c+(b+-(l,l),n),n),n),n)) -#a0(cons(a2(cons(c+(be+(n),n),n),n),n)) - -___________________________________________________________________________________ - -右広がりのみ(描画4) - -#a0(cons(a+(l),cons(a+(l),n))) cons(a+(l),n) -#a0(cons(a+(l),cons(a-(l),n))) cons(a-(l),n) -#a0(cons(a+(l),cons(a2(n,n),n))) cons(a2(n,n),n) - -#a0(cons(a-(l),cons(a+(l),n))) -#a0(cons(a-(l),cons(a-(l),n))) -#a0(cons(a-(l),cons(a2(n,n),n))) - -#a0(cons(a2(n,n),cons(a+(l),n))) -#a0(cons(a2(n,n),cons(a-(l),n))) -#a0(cons(a2(n,n),cons(a2(n,n),n))) - -#a0(cons(a+(l),cons(a+(l),cons(a+(l),n)))) -#a0(cons(a+(l),cons(a+(l),cons(a-(l),n)))) -#a0(cons(a+(l),cons(a+(l),cons(a2(n,n),n)))) -#a0(cons(a+(l),cons(a-(l),cons(a+(l),n)))) -#a0(cons(a+(l),cons(a-(l),cons(a-(l),n)))) -#a0(cons(a+(l),cons(a-(l),cons(a2(n,n),n)))) -#a0(cons(a+(l),cons(a2(n,n),cons(a+(l),n)))) -#a0(cons(a+(l),cons(a2(n,n),cons(a-(l),n)))) -#a0(cons(a+(l),cons(a2(n,n),cons(a2(n,n),n)))) - -#a0(cons(a-(l),cons(a+(l),cons(a+(l),n)))) -#a0(cons(a-(l),cons(a+(l),cons(a-(l),n)))) -#a0(cons(a-(l),cons(a+(l),cons(a2(n,n),n)))) -#a0(cons(a-(l),cons(a-(l),cons(a+(l),n)))) -#a0(cons(a-(l),cons(a-(l),cons(a-(l),n)))) -#a0(cons(a-(l),cons(a-(l),cons(a2(n,n),n)))) -#a0(cons(a-(l),cons(a2(n,n),cons(a+(l),n)))) -#a0(cons(a-(l),cons(a2(n,n),cons(a-(l),n)))) -#a0(cons(a-(l),cons(a2(n,n),cons(a2(n,n),n)))) - -#a0(cons(a2(n,n),cons(a+(l),cons(a+(l),n)))) -#a0(cons(a2(n,n),cons(a+(l),cons(a-(l),n)))) -#a0(cons(a2(n,n),cons(a+(l),cons(a2(n,n),n)))) -#a0(cons(a2(n,n),cons(a-(l),cons(a+(l),n)))) -#a0(cons(a2(n,n),cons(a-(l),cons(a-(l),n)))) -#a0(cons(a2(n,n),cons(a-(l),cons(a2(n,n),n)))) -#a0(cons(a2(n,n),cons(a2(n,n),cons(a+(l),n)))) -#a0(cons(a2(n,n),cons(a2(n,n),cons(a-(l),n)))) -#a0(cons(a2(n,n),cons(a2(n,n),cons(a2(n,n),n)))) - -_____________________________________________________________________ - -左 右 -#a0(cons(a+(b++(l,l)),cons(a+(l),n))) -#a0(cons(a+(b++(l,l)),cons(a-(l),n))) -#a0(cons(a+(b++(l,l)),cons(a2(n,n),n))) -#a0(cons(a+(b+-(l,l)),cons(a+(l),n))) -#a0(cons(a+(b+-(l,l)),cons(a-(l),n))) -#a0(cons(a+(b+-(l,l)),cons(a2(n,n),n))) -#a0(cons(a+(be+(n)),cons(a+(l),n))) -#a0(cons(a+(be+(n)),cons(a-(l),n))) -#a0(cons(a+(be+(n)),cons(a2(n,n),n))) - -#a0(cons(a-(b--(l,l)),cons(a+(l),n))) -#a0(cons(a-(b--(l,l)),cons(a-(l),n))) -#a0(cons(a-(b--(l,l)),cons(a2(n,n),n))) -#a0(cons(a-(b-+(l,l)),cons(a+(l),n))) -#a0(cons(a-(b-+(l,l)),cons(a-(l),n))) -#a0(cons(a-(b-+(l,l)),cons(a2(n,n),n))) -#a0(cons(a-(be-(n)),cons(a+(l),n))) -#a0(cons(a-(be-(n)),cons(a-(l),n))) -#a0(cons(a-(be-(n)),cons(a2(n,n),n))) - -#a0(cons(a2(cons(c+(l,n),n),n),cons(a+(l),n))) -#a0(cons(a2(cons(c+(l,n),n),n),cons(a-(l),n))) -#a0(cons(a2(cons(c+(l,n),n),n),cons(a2(n,n),n))) - -\\________________________________________________________ - -#a0(cons(a+(b++(b++(l,l),l)),cons(a+(l),n))) -#a0(cons(a+(b++(b++(l,l),l)),cons(a-(l),n))) -#a0(cons(a+(b++(b++(l,l),l)),cons(a2(n,n),n))) -#a0(cons(a+(b++(b+-(l,l),l)),cons(a+(l),n))) -#a0(cons(a+(b++(b+-(l,l),l)),cons(a-(l),n))) -#a0(cons(a+(b++(b+-(l,l),l)),cons(a2(n,n),n))) -#a0(cons(a+(b++(be+(n),l)),cons(a+(l),n))) -#a0(cons(a+(b++(be+(n),l)),cons(a-(l),n))) -#a0(cons(a+(b++(be+(n),l)),cons(a2(n,n),n))) -#a0(cons(a+(b+-(b++(l,l),l)),cons(a+(l),n))) -#a0(cons(a+(b+-(b++(l,l),l)),cons(a-(l),n))) -#a0(cons(a+(b+-(b++(l,l),l)),cons(a2(n,n),n))) -#a0(cons(a+(b+-(b+-(l,l),l)),cons(a+(l),n))) -#a0(cons(a+(b+-(b+-(l,l),l)),cons(a-(l),n))) -#a0(cons(a+(b+-(b+-(l,l),l)),cons(a2(n,n),n))) -#a0(cons(a+(b+-(be+(n),l)),cons(a+(l),n))) -#a0(cons(a+(b+-(be+(n),l)),cons(a-(l),n))) -#a0(cons(a+(b+-(be+(n),l)),cons(a2(n,n),n))) -#a0(cons(a+(be+(cons(c+(l,n),n))),cons(a+(l),n))) -#a0(cons(a+(be+(cons(c+(l,n),n))),cons(a-(l),n))) -#a0(cons(a+(be+(cons(c+(l,n),n))),cons(a2(n,n),n))) - -R #a0(cons(a-(b--(b--(l,l),l)),cons(a+(l),n))) -R #a0(cons(a-(b--(b--(l,l),l)),cons(a-(l),n))) -R #a0(cons(a-(b--(b--(l,l),l)),cons(a2(n,n),n))) -R #a0(cons(a-(b--(b-+(l,l),l)),cons(a+(l),n))) -R #a0(cons(a-(b--(b-+(l,l),l)),cons(a-(l),n))) -R #a0(cons(a-(b--(b-+(l,l),l)),cons(a2(n,n),n))) -R #a0(cons(a-(b--(be-(n),l)),cons(a+(l),n))) -R #a0(cons(a-(b--(be-(n),l)),cons(a-(l),n))) -R #a0(cons(a-(b--(be-(n),l)),cons(a2(n,n),n))) -R #a0(cons(a-(b-+(b--(l,l),l)),cons(a+(l),n))) -R #a0(cons(a-(b-+(b--(l,l),l)),cons(a-(l),n))) -R #a0(cons(a-(b-+(b--(l,l),l)),cons(a2(n,n),n))) -R #a0(cons(a-(b-+(b-+(l,l),l)),cons(a+(l),n))) -R #a0(cons(a-(b-+(b-+(l,l),l)),cons(a-(l),n))) -R #a0(cons(a-(b-+(b-+(l,l),l)),cons(a2(n,n),n))) -R #a0(cons(a-(b-+(be-(n),l)),cons(a+(l),n))) -R #a0(cons(a-(b-+(be-(n),l)),cons(a-(l),n))) -R #a0(cons(a-(b-+(be-(n),l)),cons(a2(n,n),n))) -R #a0(cons(a-(be-(cons(c-(l,n),n))),cons(a+(l),n))) -R #a0(cons(a-(be-(cons(c-(l,n),n))),cons(a-(l),n))) -R #a0(cons(a-(be-(cons(c-(l,n),n))),cons(a2(n,n),n))) - -#a0(cons(a2(cons(c+(b++(l,l),n),n),n),cons(a+(l),n))) -#a0(cons(a2(cons(c+(b++(l,l),n),n),n),cons(a-(l),n))) -#a0(cons(a2(cons(c+(b++(l,l),n),n),n),cons(a2(n,n),n))) -#a0(cons(a2(cons(c+(b+-(l,l),n),n),n),cons(a+(l),n))) -#a0(cons(a2(cons(c+(b+-(l,l),n),n),n),cons(a-(l),n))) -#a0(cons(a2(cons(c+(b+-(l,l),n),n),n),cons(a2(n,n),n))) -#a0(cons(a2(cons(c+(be+(n),n),n),n),cons(a+(l),n))) -#a0(cons(a2(cons(c+(be+(n),n),n),n),cons(a-(l),n))) -#a0(cons(a2(cons(c+(be+(n),n),n),n),cons(a2(n,n),n))) - -以降亀谷 -[a系] -:深さ1 -#a0(n) - -:深さ2 -#a0(cons(a+(l),n)) -#a0(cons(a-(l),n)) -#a0(cons(a2(n,n),n)) - -:深さ3 -::a0(cons(a+(l),n)) -1 -#a0(cons(a+(b++(l,l)),n)) -#a0(cons(a+(b+-(l,l)),n)) -#a0(cons(a+(be+(n)),n)) -2 -#a0(cons(a+(l),cons(a+(l),n))) -#a0(cons(a+(l),cons(a-(l),n))) -#a0(cons(a+(l),cons(a2(n,n),n))) -1,2 -#a0(cons(a+(b++(l,l)),cons(a+(l),n))) -#a0(cons(a+(b++(l,l)),cons(a-(l),n))) -#a0(cons(a+(b++(l,l)),cons(a2(n,n),n))) -#a0(cons(a+(b+-(l,l)),cons(a+(l),n))) -#a0(cons(a+(b+-(l,l)),cons(a-(l),n))) -#a0(cons(a+(b+-(l,l)),cons(a2(n,n),n))) -#a0(cons(a+(be+(n)),cons(a+(l),n))) -#a0(cons(a+(be+(n)),cons(a-(l),n))) -#a0(cons(a+(be+(n)),cons(a2(n,n),n))) - -::a0(cons(a-(l),n)) -1 -#a0(cons(a-(b--(l,l)),n)) -#a0(cons(a-(b-+(l,l)),n)) -#a0(cons(a-(be-(n)),n)) -2 -#a0(cons(a-(l),cons(a+(l),n))) -#a0(cons(a-(l),cons(a-(l),n))) -#a0(cons(a-(l),cons(a2(n,n),n))) -1,2 -#a0(cons(a-(b--(l,l)),cons(a-(l),n))) -#a0(cons(a-(b--(l,l)),cons(a-(l),n))) -#a0(cons(a-(b--(l,l)),cons(a2(n,n),n))) -#a0(cons(a-(b-+(l,l)),cons(a-(l),n))) -#a0(cons(a-(b-+(l,l)),cons(a-(l),n))) -#a0(cons(a-(b-+(l,l)),cons(a2(n,n),n))) -#a0(cons(a-(be-(n)),cons(a-(l),n))) -#a0(cons(a-(be-(n)),cons(a-(l),n))) -#a0(cons(a-(be-(n)),cons(a2(n,n),n))) - -::a0(cons(a2(n,n),n)) -1 -#a0(cons(a2(cons(c+(l,n),n),n),n)) -2 -#a0(cons(a2(n,cons(c-(l,n),n)),n)) -3 -#a0(cons(a2(n,n),cons(a+(l),n))) -#a0(cons(a2(n,n),cons(a-(l),n))) -#a0(cons(a2(n,n),cons(a2(n,n),n))) -1,2 -#a0(cons(a2(cons(c+(l,n),n),cons(c-(l,n),n)),n)) -1,3 -#a0(cons(a2(cons(c+(l,n),n),n),cons(a+(l),n))) -#a0(cons(a2(cons(c+(l,n),n),n),cons(a-(l),n))) -#a0(cons(a2(cons(c+(l,n),n),n),cons(a2(n,n),n))) -2,3 -#a0(cons(a2(n,cons(c-(l,n),n)),cons(a+(l),n))) -#a0(cons(a2(n,cons(c-(l,n),n)),cons(a-(l),n))) -#a0(cons(a2(n,cons(c-(l,n),n)),cons(a2(n,n),n))) -1,2,3 -#a0(cons(a2(cons(c+(l,n),n),cons(c-(l,n),n)),cons(a+(l),n))) -#a0(cons(a2(cons(c+(l,n),n),cons(c-(l,n),n)),cons(a-(l),n))) -#a0(cons(a2(cons(c+(l,n),n),cons(c-(l,n),n)),cons(a2(n,n),n))) - -深さ4 -::a0(cons(a+(b++(l,l)),n)) -1 -#a0(cons(a+(b++(b++(l,l),l)),n)) -#a0(cons(a+(b++(b+-(l,l),l)),n)) -#a0(cons(a+(b++(be+(n),l)),n)) -2 -#a0(cons(a+(b++(l,b++(l,l))),n)) -#a0(cons(a+(b++(l,b+-(l,l))),n)) -#a0(cons(a+(b++(l,be+(n))),n)) -3 -#a0(cons(a+(b++(l,l)),cons(a+(l),n))) -#a0(cons(a+(b++(l,l)),cons(a-(l),n))) -#a0(cons(a+(b++(l,l)),cons(a2(n,n),n))) -1,2 -#a0(cons(a+(b++(b++(l,l),b++(l,l))),n)) -#a0(cons(a+(b++(b++(l,l),b+-(l,l))),n)) -#a0(cons(a+(b++(b++(l,l),be+(n))),n)) -#a0(cons(a+(b++(b+-(l,l),b++(l,l))),n)) -#a0(cons(a+(b++(b+-(l,l),b+-(l,l))),n)) -#a0(cons(a+(b++(b+-(l,l),be+(n))),n)) -#a0(cons(a+(b++(be+(n),b++(l,l))),n)) -#a0(cons(a+(b++(be+(n),b+-(l,l))),n)) -#a0(cons(a+(b++(be+(n),be+(n))),n)) -1,3 -#a0(cons(a+(b++(b++(l,l),l)),cons(a+(l),n))) -#a0(cons(a+(b++(b++(l,l),l)),cons(a-(l),n))) -#a0(cons(a+(b++(b++(l,l),l)),cons(a2(n,n),n))) -#a0(cons(a+(b++(b+-(l,l),l)),cons(a+(l),n))) -#a0(cons(a+(b++(b+-(l,l),l)),cons(a-(l),n))) -#a0(cons(a+(b++(b+-(l,l),l)),cons(a2(n,n),n))) -#a0(cons(a+(b++(be+(n),l)),cons(a+(l),n))) -#a0(cons(a+(b++(be+(n),l)),cons(a-(l),n))) -#a0(cons(a+(b++(be+(n),l)),cons(a2(n,n),n))) -2,3 -#a0(cons(a+(b++(l,b++(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(l,b++(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(l,b++(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(l,b+-(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(l,b+-(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(l,b+-(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(l,be+(n))),cons(a+(l),n))) -#a0(cons(a+(b++(l,be+(n))),cons(a-(l),n))) -#a0(cons(a+(b++(l,be+(n))),cons(a2(n,n),n))) -1,2,3 -#a0(cons(a+(b++(b++(l,l),b++(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(b++(l,l),b++(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(b++(l,l),b++(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b++(l,l),b+-(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(b++(l,l),b+-(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(b++(l,l),b+-(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b++(l,l),be+(n))),cons(a+(l),n))) -#a0(cons(a+(b++(b++(l,l),be+(n))),cons(a-(l),n))) -#a0(cons(a+(b++(b++(l,l),be+(n))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b+-(l,l),b++(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(b+-(l,l),b++(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(b+-(l,l),b++(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b+-(l,l),b+-(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(b+-(l,l),b+-(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(b+-(l,l),b+-(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b+-(l,l),be+(n))),cons(a+(l),n))) -#a0(cons(a+(b++(b+-(l,l),be+(n))),cons(a-(l),n))) -#a0(cons(a+(b++(b+-(l,l),be+(n))),cons(a2(n,n),n))) -#a0(cons(a+(b++(be+(n),b++(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(be+(n),b++(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(be+(n),b++(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(be+(n),b+-(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(be+(n),b+-(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(be+(n),b+-(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(be+(n),be+(n))),cons(a+(l),n))) -#a0(cons(a+(b++(be+(n),be+(n))),cons(a-(l),n))) -#a0(cons(a+(b++(be+(n),be+(n))),cons(a2(n,n),n))) - -::a0(cons(a+(b+-(l,l)),n)) -1 -#a0(cons(a+(b+-(b++(l,l),l)),n)) -#a0(cons(a+(b+-(b+-(l,l),l)),n)) -#a0(cons(a+(b+-(be+(n),l)),n)) -2 -#a0(cons(a+(b+-(l,b++(l,l))),n)) -#a0(cons(a+(b+-(l,b+-(l,l))),n)) -#a0(cons(a+(b+-(l,be+(n))),n)) -3 -#a0(cons(a+(b+-(l,l)),cons(a+(l),n))) -#a0(cons(a+(b+-(l,l)),cons(a-(l),n))) -#a0(cons(a+(b+-(l,l)),cons(a2(n,n),n))) -1,2 -#a0(cons(a+(b+-(b++(l,l),b--(l,l))),n)) -#a0(cons(a+(b+-(b++(l,l),b-+(l,l))),n)) -#a0(cons(a+(b+-(b++(l,l),be-(n))),n)) -#a0(cons(a+(b+-(b+-(l,l),b--(l,l))),n)) -#a0(cons(a+(b+-(b+-(l,l),b-+(l,l))),n)) -#a0(cons(a+(b+-(b+-(l,l),be-(n))),n)) -#a0(cons(a+(b+-(be+(n),b--(l,l))),n)) -#a0(cons(a+(b+-(be+(n),b-+(l,l))),n)) -#a0(cons(a+(b+-(be+(n),be-(n))),n)) -1,3 -#a0(cons(a+(b+-(b++(l,l),l)),cons(a+(l),n))) -#a0(cons(a+(b+-(b++(l,l),l)),cons(a-(l),n))) -#a0(cons(a+(b+-(b++(l,l),l)),cons(a2(n,n),n))) -#a0(cons(a+(b+-(b+-(l,l),l)),cons(a+(l),n))) -#a0(cons(a+(b+-(b+-(l,l),l)),cons(a-(l),n))) -#a0(cons(a+(b+-(b+-(l,l),l)),cons(a2(n,n),n))) -#a0(cons(a+(b+-(be+(n),l)),cons(a+(l),n))) -#a0(cons(a+(b+-(be+(n),l)),cons(a-(l),n))) -#a0(cons(a+(b+-(be+(n),l)),cons(a2(n,n),n))) -2,3 -#a0(cons(a+(b++(l,b++(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(l,b++(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(l,b++(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(l,b+-(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(l,b+-(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(l,b+-(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(l,be+(n))),cons(a+(l),n))) -#a0(cons(a+(b++(l,be+(n))),cons(a-(l),n))) -#a0(cons(a+(b++(l,be+(n))),cons(a2(n,n),n))) -1,2,3 -#a0(cons(a+(b++(b++(l,l),b++(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(b++(l,l),b++(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(b++(l,l),b++(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b++(l,l),b+-(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(b++(l,l),b+-(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(b++(l,l),b+-(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b++(l,l),be+(n))),cons(a+(l),n))) -#a0(cons(a+(b++(b++(l,l),be+(n))),cons(a-(l),n))) -#a0(cons(a+(b++(b++(l,l),be+(n))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b+-(l,l),b++(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(b+-(l,l),b++(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(b+-(l,l),b++(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b+-(l,l),b+-(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(b+-(l,l),b+-(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(b+-(l,l),b+-(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(b+-(l,l),be+(n))),cons(a+(l),n))) -#a0(cons(a+(b++(b+-(l,l),be+(n))),cons(a-(l),n))) -#a0(cons(a+(b++(b+-(l,l),be+(n))),cons(a2(n,n),n))) -#a0(cons(a+(b++(be+(n),b++(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(be+(n),b++(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(be+(n),b++(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(be+(n),b+-(l,l))),cons(a+(l),n))) -#a0(cons(a+(b++(be+(n),b+-(l,l))),cons(a-(l),n))) -#a0(cons(a+(b++(be+(n),b+-(l,l))),cons(a2(n,n),n))) -#a0(cons(a+(b++(be+(n),be+(n))),cons(a+(l),n))) -#a0(cons(a+(b++(be+(n),be+(n))),cons(a-(l),n))) -#a0(cons(a+(b++(be+(n),be+(n))),cons(a2(n,n),n))) - - -[b系] -:深さ1 -#b0+(l,(n)) -#b0-(l,(n)) - -:深さ2 -::b0+(l,(n)) -1 -#b0+(b++(l,l),(n)) -#b0+(b+-(l,l),(n)) -#b0+(be+(n),(n)) -2 -#b0+(l,(cons(c-(l,n),n))) -1,2 -#b0+(b++(l,l),(cons(c-(l,n),n))) -#b0+(b+-(l,l),(cons(c-(l,n),n))) -#b0+(be+(n),(cons(c-(l,n),n))) - -::b0-(l,(n)) -1 -#b0-(b--(l,l),(n)) -#b0-(b-+(l,l),(n)) -#b0-(be-(n),(n)) -2 -#b0-(l,(cons(c+(l,n),n))) -1,2 -#b0-(b--(l,l),(cons(c+(l,n),n))) -#b0-(b-+(l,l),(cons(c+(l,n),n))) -#b0-(be-(n),(cons(c+(l,n),n))) - -:深さ3 -::b0+(b++(l,l),(n)) -1 -#b0+(b++(b++(l,l),l),(n)) -#b0+(b++(b+-(l,l),l),(n)) -#b0+(b++(be+(n),l),(n)) -2 -#b0+(b++(l,b++(l,l)),(n)) -#b0+(b++(l,b+-(l,l)),(n)) -#b0+(b++(l,be+(n)),(n)) -3 -#b0+(b++(l,l),(cons(c-(l,n),n))) -1,2 -#b0+(b++(b++(l,l),b++(l,l)),(n)) -#b0+(b++(b++(l,l),b+-(l,l)),(n)) -#b0+(b++(b++(l,l),be+(n)),(n)) -#b0+(b++(b+-(l,l),b++(l,l)),(n)) -#b0+(b++(b+-(l,l),b+-(l,l)),(n)) -#b0+(b++(b+-(l,l),be+(n)),(n)) -#b0+(b++(be+(n),b++(l,l)),(n)) -#b0+(b++(be+(n),b+-(l,l)),(n)) -#b0+(b++(be+(n),be+(n)),(n)) -2,3 -#b0+(b++(l,b++(l,l)),(cons(c-(l,n),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(l,n),n))) -#b0+(b++(l,be+(n)),(cons(c-(l,n),n))) -1,3 -#b0+(b++(b++(l,l),l),(cons(c-(l,n),n))) -#b0+(b++(b+-(l,l),l),(cons(c-(l,n),n))) -#b0+(b++(be+(n),l),(cons(c-(l,n),n))) -1,2,3 -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(l,n),n))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(l,n),n))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(l,n),n))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(l,n),n))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(l,n),n))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(l,n),n))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(l,n),n))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(l,n),n))) -#b0+(b++(be+(n),be+(n)),(cons(c-(l,n),n))) - -::b0+(b+-(l,l),(n)) -1 -#b0+(b+-(b++(l,l),l),(n)) -#b0+(b+-(b+-(l,l),l),(n)) -#b0+(b+-(be+(n),l),(n)) -2 -#b0+(b+-(l,b--(l,l)),(n)) -#b0+(b+-(l,b-+(l,l)),(n)) -#b0+(b+-(l,be-(n)),(n)) -3 -#b0+(b+-(l,l),(cons(c-(l,n),n))) -1,2 -#b0+(b+-(b++(l,l),b--(l,l)),(n)) -#b0+(b+-(b++(l,l),b-+(l,l)),(n)) -#b0+(b+-(b++(l,l),be-(n)),(n)) -#b0+(b+-(b+-(l,l),b--(l,l)),(n)) -#b0+(b+-(b+-(l,l),b-+(l,l)),(n)) -#b0+(b+-(b+-(l,l),be-(n)),(n)) -#b0+(b+-(be+(n),b--(l,l)),(n)) -#b0+(b+-(be+(n),b-+(l,l)),(n)) -#b0+(b+-(be+(n),be-(n)),(n)) -2,3 -#b0+(b+-(l,b--(l,l)),(cons(c-(l,n),n))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(l,n),n))) -#b0+(b+-(l,be-(n)),(cons(c-(l,n),n))) -1,3 -#b0+(b+-(b++(l,l),l),(cons(c-(l,n),n))) -#b0+(b+-(b+-(l,l),l),(cons(c-(l,n),n))) -#b0+(b+-(be+(n),l),(cons(c-(l,n),n))) -1,2,3 -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(l,n),n))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(l,n),n))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(l,n),n))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(l,n),n))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(l,n),n))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(l,n),n))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(l,n),n))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(l,n),n))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(l,n),n))) - -::b0+(be+(n),(n)) -1 -#b0+(be+(cons(c+(l,n),n)),(n)) -2 -#b0+(be+(n),(cons(c-(l,n),n))) -1,2 -#b0+(be+(cons(c+(l,n),n)),(cons(c-(l,n),n))) - -::b0+(l,(cons(c-(l,n),n))) -1 -済 -2以下問題発生 ためし#b0+(b++(l,l),cons(c-(b-+(b--(b--(b--(b--(b--(b--(l,l),l),l),l),l),l),l),n), n)) -#b0+(l,(cons(c-(b--(l,l),n),n))) -#b0+(l,(cons(c-(b-+(l,l),n),n))) -#b0+(l,(cons(c-(be-(n),n),n))) -3 -#b0+(l,(cons(c-(l,cons(c+(l,n),n)),n))) -4 -#b0+(l,(cons(c-(l,n),cons(c-(l,n),n)))) -1,2 -#b0+(b++(l,l),(cons(c-(b--(l,l),n),n))) -#b0+(b++(l,l),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(l,l),(cons(c-(be-(n),n),n))) -#b0+(b+-(l,l),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(l,l),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(l,l),(cons(c-(be-(n),n),n))) -#b0+(be+(n),(cons(c-(b--(l,l),n),n))) -#b0+(be+(n),(cons(c-(b-+(l,l),n),n))) -#b0+(be+(n),(cons(c-(be-(n),n),n))) -1,3 -#b0+(b++(l,l),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(l,l),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(be+(n),(cons(c-(l,cons(c+(l,n),n)),n))) -1,4 -#b0+(b++(l,l),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(l,l),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(l,n),cons(c-(l,n),n)))) -2,3 -#b0+(l,(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(l,(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(l,(cons(c-(be-(n),cons(c+(l,n),n)),n))) -2,4 -#b0+(l,(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(l,(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(l,(cons(c-(be-(n),n),cons(c-(l,n),n)))) -3,4 -#b0+(l,(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -1,2,3 -#b0+(b++(l,l),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(l,l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(l,l),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(l,l),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(l,l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(l,l),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(be+(n),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(be+(n),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(be+(n),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -1,2,4 -#b0+(b++(l,l),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(l,l),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(l,l),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(l,l),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(l,l),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(l,l),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -1,3,4 -#b0+(b++(l,l),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(l,l),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -2,3,4 -#b0+(l,(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(l,(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(l,(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -1,2,3,4 -#b0+(b++(l,l),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(l,l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(l,l),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(l,l),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(l,l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(l,l),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) - - -::b0+(b++(l,l),(cons(c-(l,n),n))) -1 -済 -2 -済 -3 -済 -4 -済 -5 -済 -1,2 -済 -1,3 -#b0+(b++(b++(l,l),l),(cons(c-(b--(l,l),n),n))) -#b0+(b++(b++(l,l),l),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(b++(l,l),l),(cons(c-(be-(n),n),n))) -#b0+(b++(b+-(l,l),l),(cons(c-(b--(l,l),n),n))) -#b0+(b++(b+-(l,l),l),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(b+-(l,l),l),(cons(c-(be-(n),n),n))) -#b0+(b++(be+(n),l),(cons(c-(b--(l,l),n),n))) -#b0+(b++(be+(n),l),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(be+(n),l),(cons(c-(be-(n),n),n))) -1,4 -#b0+(b++(b++(l,l),l),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),l),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),l),(cons(c-(l,cons(c+(l,n),n)),n))) -1,5 -#b0+(b++(b++(l,l),l),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),l),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),l),(cons(c-(l,n),cons(c-(l,n),n)))) - -2,3 -#b0+(b++(l,b++(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(l,b++(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(l,b++(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b++(l,be+(n)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(l,be+(n)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(l,be+(n)),(cons(c-(be-(n),n),n))) -2,4 -#b0+(b++(l,b++(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(l,be+(n)),(cons(c-(l,cons(c+(l,n),n)),n))) -2,5 -#b0+(b++(l,b++(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(l,b+-(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(l,be+(n)),(cons(c-(l,n),cons(c-(l,n),n)))) -3,4 -済 -3,5 -済 -4,5 -済 -1,2,3 -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(be-(n),n),n))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(be-(n),n),n))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b++(be+(n),be+(n)),(cons(c-(b--(l,l),n),n))) -#b0+(b++(be+(n),be+(n)),(cons(c-(b-+(l,l),n),n))) -#b0+(b++(be+(n),be+(n)),(cons(c-(be-(n),n),n))) -1,2,4 -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),be+(n)),(cons(c-(l,cons(c+(l,n),n)),n))) -1,2,5 -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),be+(n)),(cons(c-(l,n),cons(c-(l,n),n)))) -1,3,4 -#b0+(b++(b++(l,l),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),l),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),l),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),l),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -1,3,5 -#b0+(b++(b++(l,l),l),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),l),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),l),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),l),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),l),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),l),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),l),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),l),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),l),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -1,4,5 -#b0+(b++(b++(l,l),l),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),l),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),l),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -2,3,4 -#b0+(b++(l,b++(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(l,b++(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(l,b++(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(l,be+(n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(l,be+(n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(l,be+(n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -2,3,5 -#b0+(b++(l,b++(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n))) -#b0+(b++(l,b++(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(l,b++(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(l,b+-(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n))) -#b0+(b++(l,b+-(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(l,b+-(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(l,be+(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n))) -#b0+(b++(l,be+(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(l,be+(n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -2,4,5 -#b0+(b++(l,b++(l,l)),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(l,b+-(l,l)),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(l,be+(n)),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -3,4,5 -#b0+(b++(l,l),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,l),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,l),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -1,2,3,4 -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),be+(n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),be+(n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b++(be+(n),be+(n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -1,2,3,5 -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),be+(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),be+(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b++(be+(n),be+(n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -1,2,4,5 -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),be+(n)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -1,3,4,5 -#b0+(b++(b++(l,l),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(b++(l,l),l),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(b+-(l,l),l),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(be+(n),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(be+(n),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b++(be+(n),l),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -2,3,4,5 -#b0+(b++(l,b++(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,b++(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,b++(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,b+-(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,b+-(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,b+-(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,be+(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,be+(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(l,be+(n)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -1,2,3,4,5 -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),b++(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),b+-(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b++(l,l),be+(n)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),b++(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),b+-(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(b+-(l,l),be+(n)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),b++(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),b+-(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),be+(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),be+(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b++(be+(n),be+(n)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) - -::b0+(b+-(l,l),(cons(c-(l,n),n))) -1 -済 -2 -済 -3 -済 -4 -済 -5 -済 -1,2 -済 -1,3 -#b0+(b+-(b++(l,l),l),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(b++(l,l),l),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(b++(l,l),l),(cons(c-(be-(n),n),n))) -#b0+(b+-(b+-(l,l),l),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(b+-(l,l),l),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(b+-(l,l),l),(cons(c-(be-(n),n),n))) -#b0+(b+-(be+(n),l),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(be+(n),l),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(be+(n),l),(cons(c-(be-(n),n),n))) -1,4 -#b0+(b+-(b++(l,l),l),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),l),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),l),(cons(c-(l,cons(c+(l,n),n)),n))) -1,5 -#b0+(b+-(b++(l,l),l),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),l),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),l),(cons(c-(l,n),cons(c-(l,n),n)))) - -2,3 -#b0+(b+-(l,b--(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(l,b--(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(l,b--(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b+-(l,be-(n)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(l,be-(n)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(l,be-(n)),(cons(c-(be-(n),n),n))) -2,4 -#b0+(b+-(l,b--(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(l,be-(n)),(cons(c-(l,cons(c+(l,n),n)),n))) -2,5 -#b0+(b+-(l,b--(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(l,be-(n)),(cons(c-(l,n),cons(c-(l,n),n)))) -3,4 -済 -3,5 -済 -4,5 -済 -1,2,3 -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(be-(n),n),n))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(be-(n),n),n))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(be-(n),n),n))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(b--(l,l),n),n))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(b-+(l,l),n),n))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(be-(n),n),n))) -1,2,4 -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(l,cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(l,cons(c+(l,n),n)),n))) -1,2,5 -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(l,n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(l,n),cons(c-(l,n),n)))) -1,3,4 -#b0+(b+-(b++(l,l),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),l),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),l),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),l),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -1,3,5 -#b0+(b+-(b++(l,l),l),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),l),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),l),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),l),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),l),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),l),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),l),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),l),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),l),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -1,4,5 -#b0+(b+-(b++(l,l),l),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),l),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),l),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -2,3,4 -#b0+(b+-(l,b--(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(l,b--(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(l,b--(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(l,be-(n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(l,be-(n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(l,be-(n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -2,3,5 -#b0+(b+-(l,b--(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(l,b--(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(l,b--(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(l,be-(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(l,be-(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(l,be-(n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -2,4,5 -#b0+(b+-(l,b--(l,l)),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(l,be-(n)),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -3,4,5 -#b0+(b+-(l,l),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,l),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,l),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -1,2,3,4 -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -1,2,3,5 -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -1,2,4,5 -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n))))) -1,3,4,5 -#b0+(b+-(b++(l,l),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(b++(l,l),l),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(b+-(l,l),l),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),l),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),l),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(b+-(be+(n),l),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) -2,3,4,5 -#b0+(b+-(l,b--(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,b--(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,b--(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,be-(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,be-(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(l,be-(n)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -1,2,3,4,5 -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),b--(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b++(l,l),be-(n)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),b--(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(b+-(l,l),be-(n)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),b--(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(b--(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),cons(c-(l,n),n))))) -#b0+(b+-(be+(n),be-(n)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),n))))) - -試し()2個(中身厚い)b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(b--(b--(b--(l,l),l),l),l),n),cons(c-(b--(b--(b--(b--(l,l),l),l),l),n),n)))) -         2個(中身厚い)b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(b--(b--(b--(b--(b--(b--(b--(b--(b--(l,l),l),l),l),l),l),l),l),l),l),n),cons(c-(b--(b--(b--(b--(l,l),l),l),l),n),n)))) - 6個(中身厚い)b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(b--(b--(b--(l,l),l),l),l),n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n)))))))) - 6個(中身厚い)b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(b--(b--(b--(b--(l,l),l),l),l),l),n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n)))))))) -          12個:b0+(b+-(be+(n),b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n)))))))))))))) - 12個(中身厚く)b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(b--(l,l),l),n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n)))))))))))))) - 12個(中身厚く改)b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(b--(b--(b--(l,l),l),l),l),n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n)))))))))))))) -          24個:b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(b--(l,l),l),n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n)))))))))))))))))))))))))) -          48個:b0+(b+-(be+(n),b-+(l,l)),(cons(c-(be-(n),n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n)))))))))))))))))))))))))))))))))))))) - 48個(中身分厚く!):b0+(b+-(be+(n),b-+(l,l)),(cons(c-(b--(b--(l,l),l),n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),cons(c-(l,n),n)))))))))))))))))))))))))))))))))))))) -::b0+(be+(n),(cons(c-(l,n),n))) -1 -#b0+(be+(cons(c+(l,n),n)),(cons(c-(l,n),n))) -2 -#b0+(be+(n),(cons(c-(b--(l,l),n),n))) -#b0+(be+(n),(cons(c-(b-+(l,l),n),n))) -#b0+(be+(n),(cons(c-(be-(n),n),n))) -3 -#b0+(be+(n),(cons(c-(l,cons(c+(l,n),n)),n))) -4 -#b0+(be+(n),(cons(c-(l,n),cons(c-(l,n),n)))) - -1,2 -#b0+(be+(cons(c+(l,n),n)),(cons(c-(b--(l,l),n),n))) -#b0+(be+(cons(c+(l,n),n)),(cons(c-(b-+(l,l),n),n))) -#b0+(be+(cons(c+(l,n),n)),(cons(c-(be-(n),n),n))) -1,3 -#b0+(be+(cons(c+(l,n),n)),(cons(c-(l,cons(c+(l,n),n)),n))) -1,4 -#b0+(be+(cons(c+(l,n),n)),(cons(c-(l,n),cons(c-(l,n),n)))) -2,3 -#b0+(be+(n),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(be+(n),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(be+(n),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -2,4 -#b0+(be+(n),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -3,4 -#b0+(be+(n),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -1,2,3 -#b0+(be+(cons(c+(l,n),n)),(cons(c-(b--(l,l),cons(c+(l,n),n)),n))) -#b0+(be+(cons(c+(l,n),n)),(cons(c-(b-+(l,l),cons(c+(l,n),n)),n))) -#b0+(be+(cons(c+(l,n),n)),(cons(c-(be-(n),cons(c+(l,n),n)),n))) -1,2,4 -#b0+(be+(cons(c+(l,n),n)),(cons(c-(b--(l,l),n),cons(c-(l,n),n)))) -#b0+(be+(cons(c+(l,n),n)),(cons(c-(b-+(l,l),n),cons(c-(l,n),n)))) -#b0+(be+(cons(c+(l,n),n)),(cons(c-(be-(n),n),cons(c-(l,n),n)))) -1,3,4 -#b0+(be+(cons(c+(l,n),n)),(cons(c-(l,cons(c+(l,n),n)),cons(c-(l,n),n)))) -2,3,4 -#b0+(be+(n),(cons(c-(b--(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(b-+(l,l),cons(c+(l,n),n)),cons(c-(l,n),n)))) -#b0+(be+(n),(cons(c-(be-(n),cons(c+(l,n),n)),cons(c-(l,n),n)))) - ------------------------------------------------------------------------------------ -::b0-(b--(l,l),(n)) -1 -#b0-(b--(b--(l,l),l),(n)) -#b0-(b--(b-+(l,l),l),(n)) -#b0-(b--(be-(n),l),(n)) -2 -#b0-(b--(l,b--(l,l)),(n)) -#b0-(b--(l,b-+(l,l)),(n)) -#b0-(b--(l,be-(n)),(n)) -3 -#b0-(b--(l,l),(cons(c+(l,n),n))) -1,2 -#b0-(b--(b--(l,l),b--(l,l)),(n)) -#b0-(b--(b--(l,l),b-+(l,l)),(n)) -#b0-(b--(b--(l,l),be-(n)),(n)) -#b0-(b--(b-+(l,l),b--(l,l)),(n)) -#b0-(b--(b-+(l,l),b-+(l,l)),(n)) -#b0-(b--(b-+(l,l),be-(n)),(n)) -#b0-(b--(be-(n),b--(l,l)),(n)) -#b0-(b--(be-(n),b-+(l,l)),(n)) -#b0-(b--(be-(n),be-(n)),(n)) -2,3 -#b0-(b--(l,b--(l,l)),(cons(c+(l,n),n))) -#b0-(b--(l,b-+(l,l)),(cons(c+(l,n),n))) -#b0-(b--(l,be-(n)),(cons(c+(l,n),n))) -1,3 -#b0-(b--(b--(l,l),l),(cons(c+(l,n),n))) -#b0-(b--(b-+(l,l),l),(cons(c+(l,n),n))) -#b0-(b--(be-(n),l),(cons(c+(l,n),n))) -1,2,3 -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(l,n),n))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(l,n),n))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(l,n),n))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(l,n),n))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(l,n),n))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(l,n),n))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(l,n),n))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(l,n),n))) -#b0-(b--(be-(n),be-(n)),(cons(c+(l,n),n))) - -::b0-(b-+(l,l),(n)) -1 -#b0-(b-+(b--(l,l),l),(n)) -#b0-(b-+(b-+(l,l),l),(n)) -#b0-(b-+(be-(n),l),(n)) -2 -#b0-(b-+(l,b++(l,l)),(n)) -#b0-(b-+(l,b+-(l,l)),(n)) -#b0-(b-+(l,be+(n)),(n)) -3 -#b0-(b-+(l,l),(cons(c+(l,n),n))) -1,2 -#b0-(b-+(b--(l,l),b++(l,l)),(n)) -#b0-(b-+(b--(l,l),b+-(l,l)),(n)) -#b0-(b-+(b--(l,l),be+(n)),(n)) -#b0-(b-+(b-+(l,l),b++(l,l)),(n)) -#b0-(b-+(b-+(l,l),b++(l,l)),(n)) -#b0-(b-+(b-+(l,l),be+(n)),(n)) -#b0-(b-+(be-(n),b++(l,l)),(n)) -#b0-(b-+(be-(n),b+-(l,l)),(n)) -#b0-(b-+(be-(n),be+(n)),(n)) -2,3 -#b0-(b-+(l,b++(l,l)),(cons(c+(l,n),n))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(l,n),n))) -#b0-(b-+(l,be+(n)),(cons(c+(l,n),n))) -1,3 -#b0-(b-+(b--(l,l),l),(cons(c+(l,n),n))) -#b0-(b-+(b-+(l,l),l),(cons(c+(l,n),n))) -#b0-(b-+(be-(n),l),(cons(c+(l,n),n))) -1,2,3 -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(l,n),n))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(l,n),n))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(l,n),n))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(l,n),n))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(l,n),n))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(l,n),n))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(l,n),n))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(l,n),n))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(l,n),n))) - -::b0-(be-(n),(n)) -1 -#b0-(be-(cons(c-(l,n),n)),(n)) -2 -#b0-(be-(n),(cons(c+(l,n),n))) -1,2 -#b0-(be-(cons(c-(l,n),n)),(cons(c+(l,n),n))) - -::b0-(l,(cons(c+(l,n),n))) -1 -済 -#b0-(l,(cons(c+(b++(l,l),n),n))) -#b0-(l,(cons(c+(b+-(l,l),n),n))) -#b0-(l,(cons(c+(be+(n),n),n))) -3 -#b0-(l,(cons(c+(l,cons(c-(l,n),n)),n))) -4 -#b0-(l,(cons(c+(l,n),cons(c+(l,n),n)))) -1,2 -#b0-(b--(l,l),(cons(c+(b++(l,l),n),n))) -#b0-(b--(l,l),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(l,l),(cons(c+(be+(n),n),n))) -#b0-(b-+(l,l),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(l,l),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(l,l),(cons(c+(be+(n),n),n))) -#b0-(be-(n),(cons(c+(b++(l,l),n),n))) -#b0-(be-(n),(cons(c+(b+-(l,l),n),n))) -#b0-(be-(n),(cons(c+(be+(n),n),n))) -1,3 -#b0-(b--(l,l),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(l,l),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(be-(n),(cons(c+(l,cons(c-(l,n),n)),n))) -1,4 -#b0-(b--(l,l),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(l,l),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(l,n),cons(c+(l,n),n)))) -2,3 -#b0-(l,(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(l,(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(l,(cons(c+(be+(n),cons(c-(l,n),n)),n))) -2,4 -#b0-(l,(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(l,(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(l,(cons(c+(be+(n),n),cons(c+(l,n),n)))) -3,4 -#b0-(l,(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -1,2,3 -#b0-(b--(l,l),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(l,l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(l,l),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(l,l),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(l,l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(l,l),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(be-(n),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(be-(n),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(be-(n),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -1,2,4 -#b0-(b--(l,l),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(l,l),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(l,l),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(l,l),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(l,l),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(l,l),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -1,3,4 -#b0-(b--(l,l),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(l,l),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -2,3,4 -#b0-(l,(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(l,(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(l,(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) -1,2,3,4 -#b0-(b--(l,l),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(l,l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(l,l),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ -#b0-(b-+(l,l),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(l,l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(l,l),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -# - - - - -::b0-(b--(l,l),(cons(c+(l,n),n))) ::::::::::::::::::::::::::::::::::::::::::::::::: -1 -済 -2 -済 -3 -済 -4 -済 -5 -済 -1,2 -済 -1,3; -#b0-(b--(b--(l,l),l),(cons(c+(b++(l,l),n),n))) -#b0-(b--(b--(l,l),l),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(b--(l,l),l),(cons(c+(be+(n),n),n))) -#b0-(b--(b-+(l,l),l),(cons(c+(b++(l,l),n),n))) -#b0-(b--(b-+(l,l),l),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(b-+(l,l),l),(cons(c+(be+(n),n),n))) -#b0-(b--(be-(n),l),(cons(c+(b++(l,l),n),n))) -#b0-(b--(be-(n),l),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(be-(n),l),(cons(c+(be+(n),n),n))) -1,4 -#b0-(b--(b--(l,l),l),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),l),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),l),(cons(c+(l,cons(c-(l,n),n)),n))) -1,5 -#b0-(b--(b--(l,l),l),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),l),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),l),(cons(c+(l,n),cons(c+(l,n),n)))) - -2,3 -#b0-(b--(l,b--(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(l,b--(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(l,b--(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b--(l,b-+(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(l,b-+(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(l,b-+(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b--(l,be-(n)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(l,be-(n)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(l,be-(n)),(cons(c+(be+(n),n),n))) -2,4 -#b0-(b--(l,b--(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(l,b-+(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(l,be-(n)),(cons(c+(l,cons(c-(l,n),n)),n))) -2,5 -#b0-(b--(l,b--(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(l,b-+(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(l,be-(n)),(cons(c+(l,n),cons(c+(l,n),n)))) -3,4 -済 -3,5 -済 -4,5 -済 -1,2,3 -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(be+(n),n),n))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(be+(n),n),n))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b--(be-(n),be-(n)),(cons(c+(b++(l,l),n),n))) -#b0-(b--(be-(n),be-(n)),(cons(c+(b+-(l,l),n),n))) -#b0-(b--(be-(n),be-(n)),(cons(c+(be+(n),n),n))) -試し c系の中βにc系をつける:b0-(b--(b-+(l,l),be-(n)),(cons(c+(be+(cons(c+(l,n),n)),n),n))) - c系を増やしてみる(8個):b0-(b--(b-+(l,l),be-(n)),(cons(c+(be+(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))))))),n),n))) - c系を増やしてみる(16個):b0-(b--(b-+(l,l),be-(n)),(cons(c+(be+(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))))))))))))))),n),n))) - c系の中βにc系をつける改:b0-(b--(b-+(l,l),be-(n)),(cons(c+(be+(cons(c+(be+(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))))))),n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))))))),n),n))) -1,2,4 -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),be-(n)),(cons(c+(l,cons(c-(l,n),n)),n))) -1,2,5 -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),be-(n)),(cons(c+(l,n),cons(c+(l,n),n)))) -1,3,4 -#b0-(b--(b--(l,l),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),l),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),l),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),l),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -1,3,5 -#b0-(b--(b--(l,l),l),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),l),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),l),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),l),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),l),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),l),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),l),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),l),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),l),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -1,4,5 -#b0-(b--(b--(l,l),l),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),l),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),l),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -2,3,4 -#b0-(b--(l,b--(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(l,b--(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(l,b--(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(l,b-+(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(l,b-+(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(l,b-+(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(l,be-(n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(l,be-(n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(l,be-(n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -2,3,5 -#b0-(b--(l,b--(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(l,b--(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(l,b--(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(l,b-+(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(l,b-+(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(l,b-+(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(l,be-(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(l,be-(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(l,be-(n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -2,4,5 -#b0-(b--(l,b--(l,l)),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(l,b-+(l,l)),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(l,be-(n)),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -3,4,5 -#b0-(b--(l,l),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,l),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,l),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -1,2,3,4 -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),be-(n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),be-(n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b--(be-(n),be-(n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -1,2,3,5 -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),be-(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),be-(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b--(be-(n),be-(n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -1,2,4,5 -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),be-(n)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -1,3,4,5 -#b0-(b--(b--(l,l),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(b--(l,l),l),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(b-+(l,l),l),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(be-(n),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(be-(n),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b--(be-(n),l),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) -2,3,4,5 -#b0-(b--(l,b--(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,b--(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,b--(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,b-+(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,b-+(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,b-+(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,be-(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,be-(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(l,be-(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -1,2,3,4,5 -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),b--(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),b-+(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b--(l,l),be-(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),b--(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),b-+(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(b-+(l,l),be-(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),b--(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),b-+(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),be-(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),be-(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b--(be-(n),be-(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) - -::b0-(b-+(l,l),(cons(c+(l,n),n))) -1 -済 -2 -済 -3 -済 -4 -済 -5 -済 -1,2 -済 -1,3; -#b0-(b-+(b--(l,l),l),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(b--(l,l),l),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(b--(l,l),l),(cons(c+(be+(n),n),n))) -#b0-(b-+(b-+(l,l),l),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(b-+(l,l),l),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(b-+(l,l),l),(cons(c+(be+(n),n),n))) -#b0-(b-+(be-(n),l),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(be-(n),l),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(be-(n),l),(cons(c+(be+(n),n),n))) -1,4 -#b0-(b-+(b--(l,l),l),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),l),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),l),(cons(c+(l,cons(c-(l,n),n)),n))) -1,5 -#b0-(b-+(b--(l,l),l),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),l),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),l),(cons(c+(l,n),cons(c+(l,n),n)))) - -2,3 -#b0-(b-+(l,b++(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(l,b++(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(l,b++(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b-+(l,be+(n)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(l,be+(n)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(l,be+(n)),(cons(c+(be+(n),n),n))) -2,4 -#b0-(b-+(l,b++(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(l,be+(n)),(cons(c+(l,cons(c-(l,n),n)),n))) -2,5 -#b0-(b-+(l,b++(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(l,be+(n)),(cons(c+(l,n),cons(c+(l,n),n)))) -3,4 -済 -3,5 -済 -4,5 -済 -1,2,3 -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(be+(n),n),n))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(be+(n),n),n))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(be+(n),n),n))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(b++(l,l),n),n))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(b+-(l,l),n),n))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(be+(n),n),n))) -1,2,4 -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(l,cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(l,cons(c-(l,n),n)),n))) -1,2,5 -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(l,n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(l,n),cons(c+(l,n),n)))) -1,3,4 -#b0-(b-+(b--(l,l),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),l),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),l),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),l),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -1,3,5 -#b0-(b-+(b--(l,l),l),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),l),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),l),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),l),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),l),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),l),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),l),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),l),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),l),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -1,4,5 -#b0-(b-+(b--(l,l),l),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),l),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),l),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -2,3,4 -#b0-(b-+(l,b++(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(l,b++(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(l,b++(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(l,be+(n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(l,be+(n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(l,be+(n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -2,3,5 -#b0-(b-+(l,b++(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(l,b++(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(l,b++(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(l,be+(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(l,be+(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(l,be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -2,4,5 -#b0-(b-+(l,b++(l,l)),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(l,be+(n)),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -3,4,5 -#b0-(b-+(l,l),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,l),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,l),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -1,2,3,4 -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -1,2,3,5 -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -1,2,4,5 -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(l,n),cons(c+(l,n),cons(c+(l,n),n))))) -1,3,4,5 -#b0-(b-+(b--(l,l),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(b--(l,l),l),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(b-+(l,l),l),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),l),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),l),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(b-+(be-(n),l),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) -2,3,4,5 -#b0-(b-+(l,b++(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,b++(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,b++(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,b+-(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,be+(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,be+(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(l,be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -1,2,3,4,5 -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),b++(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),b+-(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b--(l,l),be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),b++(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),b+-(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(b-+(l,l),be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),b++(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),b+-(l,l)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(b++(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),cons(c+(l,n),n))))) -#b0-(b-+(be-(n),be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n))))) - -::b0-(be-(n),(cons(c+(l,n),n))) -1 -#b0-(be-(cons(c-(l,n),n)),(cons(c+(l,n),n))) -2 -#b0-(be-(n),(cons(c+(b++(l,l),n),n))) -#b0-(be-(n),(cons(c+(b+-(l,l),n),n))) -#b0-(be-(n),(cons(c+(be+(n),n),n))) -3 -#b0-(be-(n),(cons(c+(l,cons(c-(l,n),n)),n))) -4 -#b0-(be-(n),(cons(c+(l,n),cons(c+(l,n),n)))) - -1,2 -#b0-(be-(cons(c-(l,n),n)),(cons(c+(b++(l,l),n),n))) -#b0-(be-(cons(c-(l,n),n)),(cons(c+(b+-(l,l),n),n))) -#b0-(be-(cons(c-(l,n),n)),(cons(c+(be+(n),n),n))) -1,3 -#b0-(be-(cons(c-(l,n),n)),(cons(c+(l,cons(c-(l,n),n)),n))) -1,4 -#b0-(be-(cons(c-(l,n),n)),(cons(c+(l,n),cons(c+(l,n),n)))) -2,3 -#b0-(be-(n),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(be-(n),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(be-(n),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -2,4 -#b0-(be-(n),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -3,4 -#b0-(be-(n),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -1,2,3 -#b0-(be-(cons(c-(l,n),n)),(cons(c+(b++(l,l),cons(c-(l,n),n)),n))) -#b0-(be-(cons(c-(l,n),n)),(cons(c+(b+-(l,l),cons(c-(l,n),n)),n))) -#b0-(be-(cons(c-(l,n),n)),(cons(c+(be+(n),cons(c-(l,n),n)),n))) -1,2,4 -#b0-(be-(cons(c-(l,n),n)),(cons(c+(b++(l,l),n),cons(c+(l,n),n)))) -#b0-(be-(cons(c-(l,n),n)),(cons(c+(b+-(l,l),n),cons(c+(l,n),n)))) -#b0-(be-(cons(c-(l,n),n)),(cons(c+(be+(n),n),cons(c+(l,n),n)))) -1,3,4 -#b0-(be-(cons(c-(l,n),n)),(cons(c+(l,cons(c-(l,n),n)),cons(c+(l,n),n)))) -2,3,4 -#b0-(be-(n),(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(b+-(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)))) -#b0-(be-(n),(cons(c+(be+(n),cons(c-(l,n),n)),cons(c+(l,n),n)))) - - -a0(cons(a2(cons(c+(b++(l,l),cons(c-(l,n),n)),n),n),n)) -a0(cons(a2(cons(c+(b++(l,l),cons(c-(l,n),cons(c-(l,n),n))),n),n),n)) -a0(cons(a2(cons(c+(b++(l,l),cons(c-(l,n),n)),cons(c+(l,n),n)),n),n)) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..277e51d --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,185 @@ +"""Shared test fixtures.""" + +from __future__ import annotations + +import re +from pathlib import Path + +import matplotlib +import pytest + +matplotlib.use("Agg") # non-interactive backend for tests + +from viscot.core.canvas import Canvas +from viscot.core.parser import parse + + +def pytest_addoption(parser: pytest.Parser) -> None: + parser.addoption( + "--save-images", + action="store", + default=None, + metavar="DIR", + help="Save rendered images to DIR (e.g. --save-images=test_output)", + ) + parser.addoption( + "--image-format", + action="store", + default="png", + metavar="FMT", + help="Image format: png, svg, pdf (default: png)", + ) + + +@pytest.fixture +def save_image_dir(request: pytest.FixtureRequest) -> Path | None: + raw = request.config.getoption("--save-images") + if raw is None: + return None + d = Path(raw) + d.mkdir(parents=True, exist_ok=True) + return d + + +@pytest.fixture +def image_format(request: pytest.FixtureRequest) -> str: + return str(request.config.getoption("--image-format")) + + +@pytest.fixture +def canvas() -> Canvas: + """Provide a fresh Canvas.""" + c = Canvas() + yield c + c.close() + + +def _sanitize_filename(expr: str) -> str: + """Turn a COT expression into a safe filename.""" + return re.sub(r"[^a-zA-Z0-9_+-]", "_", expr)[:80] + + +@pytest.fixture +def render(save_image_dir: Path | None, image_format: str): + """Factory fixture: parse expression, set canvas, draw, return (tree, canvas). + + If --save-images is given, each rendered expression is saved in the + format specified by --image-format (default: png). + """ + canvases: list[Canvas] = [] + + def _render(expression: str) -> tuple: + canvas = Canvas() + canvases.append(canvas) + tree = parse(expression) + tree.set_canvas(canvas) + tree.draw() + if save_image_dir is not None: + fname = _sanitize_filename(expression) + "." + image_format + canvas.save_canvas(str(save_image_dir / fname)) + return tree, canvas + + yield _render + + for c in canvases: + c.close() + + +# Makefile test expressions +MAKEFILE_EXPRESSIONS = [ + "A0()", + "A0(a+(l+))", + "A0(a+(l+).a+(l+))", + "B0+(l+,)", + "B0+(l+,c-(l-,))", + "B0+(l+,c-(l-,).c-(l-,))", + "B0-(l-,)", + "B0+(b+-(l+,l-),c-(l-,).c-(l-,).c-(l-,))", + "B0+(b+-(l+,l-),c-(B-{},).c-(l-,).c-(l-,))", + "B0-(b-+(b-+(l-,l+),B+{}),c+(B+{},).c+(l+,).c+(l+,))", + "B0+(b+-(b+-(l+,l-),B-{}),c-(B-{},).c-(l-,).c-(l-,))", +] + +# Exhaustive: all valid COT expressions with 1, 2, or 3 nodes +EXHAUSTIVE_1_2_3 = [ + "A0()", + "A0(a2(,))", + "B0+(B+{},)", + "B0+(l+,)", + "B0-(B-{},)", + "B0-(l-,)", + "A0(a+(B+{}))", + "A0(a+(l+))", + "A0(a-(B-{}))", + "A0(a-(l-))", + "A0(a2(,).a2(,))", +] + +# Exhaustive: all valid COT expressions with 4 nodes (41 expressions) +EXHAUSTIVE_4 = [ + "A0(a+(B+{}).a2(,))", "A0(a+(l+).a2(,))", "A0(a-(B-{}).a2(,))", + "A0(a-(l-).a2(,))", "A0(a2(,).a+(B+{}))", "A0(a2(,).a+(l+))", + "A0(a2(,).a-(B-{}))", "A0(a2(,).a-(l-))", "A0(a2(,).a2(,).a2(,))", + "A0(a2(,c-(B-{},)))", "A0(a2(,c-(l-,)))", "A0(a2(c+(B+{},),))", + "A0(a2(c+(l+,),))", "B0+(B+{c+(B+{},)},)", "B0+(B+{c+(l+,)},)", + "B0+(B+{},c-(B-{},))", "B0+(B+{},c-(l-,))", "B0+(b++(B+{},B+{}),)", + "B0+(b++(B+{},l+),)", "B0+(b++(l+,B+{}),)", "B0+(b++(l+,l+),)", + "B0+(b+-(B+{},B-{}),)", "B0+(b+-(B+{},l-),)", "B0+(b+-(l+,B-{}),)", + "B0+(b+-(l+,l-),)", "B0+(l+,c-(B-{},))", "B0+(l+,c-(l-,))", + "B0-(B-{c-(B-{},)},)", "B0-(B-{c-(l-,)},)", "B0-(B-{},c+(B+{},))", + "B0-(B-{},c+(l+,))", "B0-(b-+(B-{},B+{}),)", "B0-(b-+(B-{},l+),)", + "B0-(b-+(l-,B+{}),)", "B0-(b-+(l-,l+),)", "B0-(b--(B-{},B-{}),)", + "B0-(b--(B-{},l-),)", "B0-(b--(l-,B-{}),)", "B0-(b--(l-,l-),)", + "B0-(l-,c+(B+{},))", "B0-(l-,c+(l+,))", +] + +# Exhaustive: all valid COT expressions with 5 nodes (57 expressions) +EXHAUSTIVE_5 = [ + "A0(a+(B+{c+(B+{},)}))", "A0(a+(B+{c+(l+,)}))", "A0(a+(B+{}).a+(B+{}))", + "A0(a+(B+{}).a+(l+))", "A0(a+(B+{}).a-(B-{}))", "A0(a+(B+{}).a-(l-))", + "A0(a+(B+{}).a2(,).a2(,))", "A0(a+(b++(B+{},B+{})))", "A0(a+(b++(B+{},l+)))", + "A0(a+(b++(l+,B+{})))", "A0(a+(b++(l+,l+)))", "A0(a+(b+-(B+{},B-{})))", + "A0(a+(b+-(B+{},l-)))", "A0(a+(b+-(l+,B-{})))", "A0(a+(b+-(l+,l-)))", + "A0(a+(l+).a+(B+{}))", "A0(a+(l+).a+(l+))", "A0(a+(l+).a-(B-{}))", + "A0(a+(l+).a-(l-))", "A0(a+(l+).a2(,).a2(,))", "A0(a-(B-{c-(B-{},)}))", + "A0(a-(B-{c-(l-,)}))", "A0(a-(B-{}).a+(B+{}))", "A0(a-(B-{}).a+(l+))", + "A0(a-(B-{}).a-(B-{}))", "A0(a-(B-{}).a-(l-))", "A0(a-(B-{}).a2(,).a2(,))", + "A0(a-(b-+(B-{},B+{})))", "A0(a-(b-+(B-{},l+)))", "A0(a-(b-+(l-,B+{})))", + "A0(a-(b-+(l-,l+)))", "A0(a-(b--(B-{},B-{})))", "A0(a-(b--(B-{},l-)))", + "A0(a-(b--(l-,B-{})))", "A0(a-(b--(l-,l-)))", "A0(a-(l-).a+(B+{}))", + "A0(a-(l-).a+(l+))", "A0(a-(l-).a-(B-{}))", "A0(a-(l-).a-(l-))", + "A0(a-(l-).a2(,).a2(,))", "A0(a2(,).a+(B+{}).a2(,))", + "A0(a2(,).a+(l+).a2(,))", "A0(a2(,).a-(B-{}).a2(,))", + "A0(a2(,).a-(l-).a2(,))", "A0(a2(,).a2(,).a+(B+{}))", + "A0(a2(,).a2(,).a+(l+))", "A0(a2(,).a2(,).a-(B-{}))", + "A0(a2(,).a2(,).a-(l-))", "A0(a2(,).a2(,).a2(,).a2(,))", + "A0(a2(,).a2(,c-(B-{},)))", "A0(a2(,).a2(,c-(l-,)))", + "A0(a2(,).a2(c+(B+{},),))", "A0(a2(,).a2(c+(l+,),))", + "A0(a2(,c-(B-{},)).a2(,))", "A0(a2(,c-(l-,)).a2(,))", + "A0(a2(c+(B+{},),).a2(,))", "A0(a2(c+(l+,),).a2(,))", +] + +# Smallest known expressions with spline crossings (for regression tracking) +CROSSING_EXPRS = { + # 20 nodes, 2 crossings — c nested inside c with deep b-tree + "20node": "B0+(l+,c-(b-+(l-,l+),c+(b+-(b+-(b++(l+,l+),b--(l-,l-)),B-{}),c-(B-{},c+(B+{},)))))", + # 22 nodes, 4-5 crossings — Beta nesting with multiple c children + "22node": "B0+(l+,c-(l-,c+(B+{c+(B+{},)},c-(B-{},c+(l+,)).c-(b--(l-,b-+(l-,l+)),c+(l+,).c+(l+,)))))", + # 24 nodes, 8-12 crossings — triple c chain with mixed Beta/b nodes + "24node": "B0+(l+,c-(l-,c+(l+,c-(b-+(l-,B+{}),c+(B+{},).c+(l+,)).c-(B-{},c+(B+{},)).c-(b-+(l-,l+),c+(b+-(l+,l-),)))))", +} + +# 102-node stress test expression (generated with seed=7) +STRESS_EXPR_102 = ( + "B0+(b++(b++(l+,b+-(B+{},l-)),l+)," + "c-(l-,c+(b++(b++(B+{},b++(l+,l+)),b+-(l+,l-))," + "c-(b-+(B-{},l+),c+(B+{},).c+(l+,))" + ".c-(B-{},c+(B+{},))" + ".c-(B-{},c+(b+-(l+,l-),))))" + ".c-(b-+(b-+(b-+(b--(l-,l-),B+{}),b++(B+{},b+-(l+,l-))),b+-(b++(l+,b+-(l+,l-)),l-))," + "c+(B+{c+(B+{},)}," + "c-(b-+(B-{},b++(l+,l+)),c+(b+-(l+,l-),))" + ".c-(b--(l-,B-{}),c+(b+-(l+,l-),).c+(b+-(l+,l-),)))" + ".c+(l+,c-(B-{},c+(b++(l+,l+),))" + ".c-(b--(b-+(l-,l+),b-+(l-,l+)),c+(l+,)))))" +) diff --git a/tests/test_canvas.py b/tests/test_canvas.py new file mode 100644 index 0000000..626c68c --- /dev/null +++ b/tests/test_canvas.py @@ -0,0 +1,252 @@ +"""Tests for Canvas — DrawnElement recording and spline interpolation.""" + +from __future__ import annotations + +import math + +import pytest +from conftest import MAKEFILE_EXPRESSIONS + +from viscot.core.canvas import ( + Canvas, + DrawnArrow, + DrawnCircle, + DrawnLine, + DrawnPoint, + DrawnSpline, +) + + +class TestDrawnElementRecording: + """Test that all drawing operations are recorded.""" + + def test_draw_circle_recorded(self, canvas: Canvas) -> None: + canvas.draw_circle(1.0, (0, 0)) + assert len(canvas.drawn_elements) == 1 + elem = canvas.drawn_elements[0] + assert isinstance(elem, DrawnCircle) + assert elem.radius == 1.0 + assert elem.center == (0, 0) + + def test_draw_line_recorded(self, canvas: Canvas) -> None: + canvas.draw_line((0, 0), (1, 1)) + assert len(canvas.drawn_elements) == 1 + elem = canvas.drawn_elements[0] + assert isinstance(elem, DrawnLine) + assert elem.start == (0, 0) + assert elem.end == (1, 1) + + def test_draw_arrow_recorded(self, canvas: Canvas) -> None: + canvas.draw_arrow((0, 0), math.pi) + assert len(canvas.drawn_elements) == 1 + assert isinstance(canvas.drawn_elements[0], DrawnArrow) + + def test_draw_point_recorded(self, canvas: Canvas) -> None: + canvas.draw_point((1, 2)) + assert len(canvas.drawn_elements) == 1 + assert isinstance(canvas.drawn_elements[0], DrawnPoint) + + def test_draw_spline_recorded(self, canvas: Canvas) -> None: + xy = [[0, 0], [1, 1], [2, 0]] + canvas.draw_spline(xy) + assert len(canvas.drawn_elements) == 1 + elem = canvas.drawn_elements[0] + assert isinstance(elem, DrawnSpline) + assert elem.points.shape[1] == 2 + assert len(elem.points) == canvas.config.spline_num_points + + def test_clear_canvas_resets_elements(self, canvas: Canvas) -> None: + canvas.draw_circle(1.0) + canvas.draw_line((0, 0), (1, 1)) + assert len(canvas.drawn_elements) == 2 + canvas.clear_canvas() + assert len(canvas.drawn_elements) == 0 + + def test_filled_circle_recorded(self, canvas: Canvas) -> None: + canvas.draw_circle(1.0, circle_fill=True) + elem = canvas.drawn_elements[0] + assert isinstance(elem, DrawnCircle) + assert elem.filled is True + + +class TestSplineInterpolation: + """Test spline computation.""" + + def test_spline_returns_correct_shape(self, canvas: Canvas) -> None: + x = [0, 1, 2, 3] + y = [0, 1, 0, -1] + sx, sy = canvas._spline(x, y, 50, 3) + assert len(sx) == 50 + assert len(sy) == 50 + + def test_spline_endpoints(self, canvas: Canvas) -> None: + x = [0.0, 1.0, 2.0] + y = [0.0, 1.0, 0.0] + sx, sy = canvas._spline(x, y, 100, 2) + assert abs(sx[0] - 0.0) < 1e-6 + assert abs(sx[-1] - 2.0) < 1e-6 + + +class TestClearCanvasBugFix: + """Bug #2: clear_canvas() should re-create fig/ax after plt.close().""" + + def test_clear_then_draw(self, canvas: Canvas) -> None: + canvas.draw_circle(1.0) + canvas.clear_canvas() + # Should not raise — ax should be valid + canvas.draw_circle(2.0) + assert len(canvas.drawn_elements) == 1 + + +class TestClose: + """Test Canvas.close() for final cleanup.""" + + def test_close_clears_elements(self) -> None: + c = Canvas() + c.draw_circle(1.0) + assert len(c.drawn_elements) == 1 + c.close() + assert len(c.drawn_elements) == 0 + + def test_save_uses_fig_specific(self, canvas: Canvas, tmp_path) -> None: + """save_canvas should use fig-specific savefig, not plt global.""" + canvas.draw_circle(1.0) + out = tmp_path / "test.png" + canvas.save_canvas(str(out)) + assert out.exists() + + +class TestFullRendering: + """Test that Makefile expressions render without error.""" + + @pytest.mark.parametrize("expr", MAKEFILE_EXPRESSIONS) + def test_render_expression(self, render, expr: str) -> None: + tree, canvas = render(expr) + assert len(canvas.drawn_elements) > 0 + + +_conftest = __import__("conftest") +_ALL_EXHAUSTIVE = ( + _conftest.EXHAUSTIVE_1_2_3 + + _conftest.EXHAUSTIVE_4 + + _conftest.EXHAUSTIVE_5 +) + + +class TestExhaustive: + """All valid COT expressions with 1-5 nodes — zero crossings.""" + + @pytest.mark.parametrize("expr", _ALL_EXHAUSTIVE) + def test_exhaustive_no_crossings(self, render, expr: str) -> None: + from viscot.metrics.overlap import compute_overlap + + _tree, canvas = render(expr) + result = compute_overlap(canvas.drawn_elements, spline_only=True) + assert result.crossing_count == 0, ( + f"{expr!r} produced {result.crossing_count} crossing(s)" + ) + + @pytest.mark.parametrize("expr", _ALL_EXHAUSTIVE) + def test_exhaustive_roundtrip(self, render, expr: str) -> None: + from viscot.core.parser import parse as cot_parse + + tree, _canvas = render(expr) + shown = tree.show() + assert cot_parse(shown).show() == shown + + +class TestNoSplineCrossings: + """Verify that rendered COT expressions have no spurious spline crossings. + + A correct visualization should produce streamlines (splines) that do + not cross each other. Structural crossings between circles and lines + are expected (e.g. a separatrix touching a boundary), so we use + ``spline_only=True`` to restrict the check to spline-vs-spline + intersections. + """ + + @pytest.mark.parametrize("expr", MAKEFILE_EXPRESSIONS) + def test_no_spline_crossings(self, render, expr: str) -> None: + from viscot.metrics.overlap import compute_overlap + + _tree, canvas = render(expr) + result = compute_overlap(canvas.drawn_elements, spline_only=True) + assert result.crossing_count == 0, ( + f"Expression {expr!r} produced {result.crossing_count} " + f"spline crossing(s)" + ) + + +class TestSmallCrossings: + """Track the smallest known expressions that produce spline crossings. + + These are regression baselines: layout improvements should reduce + the crossing counts, never increase them. + """ + + @pytest.mark.parametrize("label,expr", [ + pytest.param(k, v, id=k) for k, v in __import__("conftest").CROSSING_EXPRS.items() + ]) + def test_crossing_expr_renders(self, render, label: str, expr: str) -> None: + """Each crossing expression must parse, draw, and round-trip.""" + from viscot.core.parser import parse as cot_parse + + tree, canvas = render(expr) + assert len(canvas.drawn_elements) > 0 + shown = tree.show() + assert cot_parse(shown).show() == shown + + @pytest.mark.parametrize("label,expr,max_crossings", [ + ("20node", __import__("conftest").CROSSING_EXPRS["20node"], 0), + ("22node", __import__("conftest").CROSSING_EXPRS["22node"], 0), + ("24node", __import__("conftest").CROSSING_EXPRS["24node"], 0), + ]) + def test_crossing_baseline(self, render, label: str, expr: str, max_crossings: int) -> None: + """Crossing count must not regress beyond the recorded baseline.""" + from viscot.metrics.overlap import compute_overlap + + _tree, canvas = render(expr) + ov = compute_overlap(canvas.drawn_elements, spline_only=True) + assert ov.crossing_count <= max_crossings, ( + f"{label}: {ov.crossing_count} crossings (max allowed: {max_crossings})" + ) + + +class TestStress: + """Stress tests with large COT expressions.""" + + def test_102_node_renders(self, render) -> None: + """102-node expression must parse, draw, and round-trip.""" + from conftest import STRESS_EXPR_102 + from viscot.core.parser import parse as cot_parse + + tree, canvas = render(STRESS_EXPR_102) + assert len(canvas.drawn_elements) > 100 + # Round-trip + shown = tree.show() + tree2 = cot_parse(shown) + assert tree2.show() == shown + + def test_102_node_metrics(self, render) -> None: + """102-node expression: record crossing count as regression baseline. + + Current layout produces 34 spline crossings at 102 nodes. + This test documents the baseline and will detect regressions + (more crossings) or improvements (fewer crossings). + """ + from conftest import STRESS_EXPR_102 + from viscot.metrics.overlap import compute_overlap + from viscot.metrics.composite import compute_composite_score + + _tree, canvas = render(STRESS_EXPR_102) + overlap = compute_overlap(canvas.drawn_elements, spline_only=True) + score = compute_composite_score(canvas.drawn_elements) + + # Baseline: 0 crossings at 102 nodes (improved from 34→13→3→0). + # Fail if any crossings appear (regression). + assert overlap.crossing_count == 0, ( + f"Regression: 102-node stress test produced {overlap.crossing_count} " + f"crossings (expected: 0)" + ) + # Score should be finite + assert score.score > -1e9 diff --git a/tests/test_layout.py b/tests/test_layout.py new file mode 100644 index 0000000..a6090ea --- /dev/null +++ b/tests/test_layout.py @@ -0,0 +1,82 @@ +"""Tests for layout improvement modules.""" + +from __future__ import annotations + +import math + +from viscot.layout.occupation import ( + CircularOccupation, + EllipticalOccupation, + compute_b_evc_occupation, + depth_adaptive_margin, +) + + +class TestOccupationArea: + """Test occupation area calculations.""" + + def test_circular_bounding_box(self) -> None: + c = CircularOccupation(center=(0, 0), radius=1.0) + bbox = c.bounding_box() + assert bbox == (-1.0, -1.0, 1.0, 1.0) + + def test_circular_area(self) -> None: + c = CircularOccupation(center=(0, 0), radius=1.0) + assert abs(c.area() - math.pi) < 1e-10 + + def test_elliptical_bounding_box(self) -> None: + e = EllipticalOccupation(center=(0, 0), semi_major=2.0, semi_minor=1.0) + bbox = e.bounding_box() + assert bbox == (-1.0, -2.0, 1.0, 2.0) + + def test_elliptical_smaller_than_circular(self) -> None: + """Elliptical area should be smaller than circular for b++/b-- nodes.""" + r_up, r_lw, margin = 1.0, 0.5, 0.5 + circ = compute_b_evc_occupation(r_up, r_lw, margin, use_ellipse=False) + elli = compute_b_evc_occupation(r_up, r_lw, margin, use_ellipse=True) + assert elli.area() < circ.area() + + def test_elliptical_bbox_smaller(self) -> None: + """Elliptical bounding box should be tighter.""" + r_up, r_lw, margin = 1.0, 0.5, 0.5 + circ = compute_b_evc_occupation(r_up, r_lw, margin, use_ellipse=False) + elli = compute_b_evc_occupation(r_up, r_lw, margin, use_ellipse=True) + c_bbox = circ.bounding_box() + e_bbox = elli.bounding_box() + c_w = c_bbox[2] - c_bbox[0] + e_w = e_bbox[2] - e_bbox[0] + assert e_w <= c_w + + def test_symmetric_radii_ellipse_vs_circle(self) -> None: + """When r_up == r_lw, ellipse semi_minor < circular radius.""" + r_up, r_lw, margin = 1.0, 1.0, 0.5 + circ = compute_b_evc_occupation(r_up, r_lw, margin, use_ellipse=False) + elli = compute_b_evc_occupation(r_up, r_lw, margin, use_ellipse=True) + assert isinstance(circ, CircularOccupation) + assert isinstance(elli, EllipticalOccupation) + # Ellipse semi_minor = max(r_up, r_lw) + margin = 1.5 + # Circle radius = (2*1 + 2*1 + 4*0.5) / 2 = 3.0 + assert elli.semi_minor < circ.radius + + +class TestDepthAdaptiveMargin: + """Test depth-adaptive margin calculation.""" + + def test_depth_zero(self) -> None: + m = depth_adaptive_margin(1.0, 0, 1) + assert m == 1.0 + + def test_deeper_means_smaller(self) -> None: + m0 = depth_adaptive_margin(1.0, 0, 1) + m1 = depth_adaptive_margin(1.0, 1, 1) + m2 = depth_adaptive_margin(1.0, 2, 1) + assert m0 > m1 > m2 + + def test_more_siblings_means_smaller(self) -> None: + m1 = depth_adaptive_margin(1.0, 0, 1) + m5 = depth_adaptive_margin(1.0, 0, 5) + assert m1 > m5 + + def test_zero_siblings_safe(self) -> None: + m = depth_adaptive_margin(1.0, 0, 0) + assert m > 0 diff --git a/tests/test_metrics.py b/tests/test_metrics.py new file mode 100644 index 0000000..555b488 --- /dev/null +++ b/tests/test_metrics.py @@ -0,0 +1,172 @@ +"""Tests for readability metrics modules.""" + +from __future__ import annotations + +import math + +import numpy as np + +from viscot.core.canvas import DrawnCircle, DrawnLine, DrawnSpline +from viscot.metrics.composite import compute_composite_score +from viscot.metrics.overlap import compute_overlap +from viscot.metrics.sampling import circle_to_polyline, element_to_polyline, line_to_polyline +from viscot.metrics.smoothness import compute_smoothness +from viscot.metrics.spacing import compute_spacing + + +class TestSampling: + """Test element-to-polyline conversion.""" + + def test_circle_to_polyline(self) -> None: + c = DrawnCircle(center=(0, 0), radius=1.0) + poly = circle_to_polyline(c, num_points=32) + assert poly.shape == (32, 2) + # All points should be at distance 1 from center + dists = np.linalg.norm(poly, axis=1) + np.testing.assert_allclose(dists, 1.0, atol=1e-10) + + def test_line_to_polyline(self) -> None: + line = DrawnLine(start=(0, 0), end=(1, 1)) + poly = line_to_polyline(line) + assert poly.shape == (2, 2) + np.testing.assert_array_equal(poly[0], [0, 0]) + np.testing.assert_array_equal(poly[1], [1, 1]) + + def test_element_to_polyline_spline(self) -> None: + pts = np.array([[0, 0], [1, 1], [2, 0]]) + s = DrawnSpline(points=pts) + result = element_to_polyline(s) + assert result is not None + np.testing.assert_array_equal(result, pts) + + +class TestOverlap: + """Test M1: overlap and crossing detection.""" + + def test_no_crossing_parallel_lines(self) -> None: + elements = [ + DrawnLine(start=(0, 0), end=(1, 0)), + DrawnLine(start=(0, 1), end=(1, 1)), + ] + result = compute_overlap(elements, epsilon=0.01) + assert result.crossing_count == 0 + + def test_crossing_perpendicular_lines(self) -> None: + elements = [ + DrawnLine(start=(0, -1), end=(0, 1)), + DrawnLine(start=(-1, 0), end=(1, 0)), + ] + result = compute_overlap(elements, epsilon=0.01) + assert result.crossing_count == 1 + + def test_single_element_no_overlap(self) -> None: + elements = [DrawnLine(start=(0, 0), end=(1, 1))] + result = compute_overlap(elements) + assert result.crossing_count == 0 + assert result.overlap_ratio == 0.0 + + def test_empty_elements(self) -> None: + result = compute_overlap([]) + assert result.crossing_count == 0 + + def test_spline_only_excludes_circles(self) -> None: + """spline_only=True should ignore circle-line crossings.""" + elements = [ + DrawnCircle(center=(0, 0), radius=1.0), + DrawnLine(start=(-2, 0), end=(2, 0)), + ] + # Without spline_only: circle and line cross at (-1,0) and (1,0) + all_result = compute_overlap(elements) + assert all_result.crossing_count >= 1 + # With spline_only: no splines → no crossings + spline_result = compute_overlap(elements, spline_only=True) + assert spline_result.crossing_count == 0 + + def test_spline_only_detects_spline_crossings(self) -> None: + """spline_only=True should still detect spline-spline crossings.""" + elements = [ + DrawnSpline(points=np.array([[-2, -2], [-1, -1], [1, 1], [2, 2.0]])), + DrawnSpline(points=np.array([[-2, 2], [-1, 1], [1, -1], [2, -2.0]])), + ] + result = compute_overlap(elements, spline_only=True) + assert result.crossing_count >= 1 + + +class TestSpacing: + """Test M2: streamline spacing.""" + + def test_uniform_parallel_lines(self) -> None: + elements = [ + DrawnLine(start=(0, 0), end=(10, 0)), + DrawnLine(start=(0, 1), end=(10, 1)), + DrawnLine(start=(0, 2), end=(10, 2)), + ] + result = compute_spacing(elements) + assert result.d_min > 0 + assert result.d_cv < 0.5 # reasonably uniform + + def test_non_uniform_spacing(self) -> None: + elements = [ + DrawnLine(start=(0, 0), end=(10, 0)), + DrawnLine(start=(0, 0.1), end=(10, 0.1)), + DrawnLine(start=(0, 5), end=(10, 5)), + ] + result = compute_spacing(elements) + assert result.d_cv > 0.5 # non-uniform + + def test_single_element(self) -> None: + result = compute_spacing([DrawnLine(start=(0, 0), end=(1, 1))]) + assert result.d_min == 0.0 + + +class TestSmoothness: + """Test M3: curvature-based smoothness.""" + + def test_straight_line_zero_curvature(self) -> None: + pts = np.column_stack([np.linspace(0, 10, 50), np.zeros(50)]) + elements = [DrawnSpline(points=pts)] + result = compute_smoothness(elements) + assert result.kappa_max < 1e-10 + assert result.jerk < 1e-10 + + def test_circle_constant_curvature(self) -> None: + t = np.linspace(0, 2 * math.pi, 100, endpoint=False) + pts = np.column_stack([np.cos(t), np.sin(t)]) + elements = [DrawnSpline(points=pts)] + result = compute_smoothness(elements) + # Curvature should be approximately 1.0 for unit circle + assert abs(result.kappa_mean - 1.0) < 0.1 + # Jerk should be small (nearly constant curvature) + assert result.kappa_var < 0.01 + + def test_empty_elements(self) -> None: + result = compute_smoothness([]) + assert result.kappa_max == 0.0 + + +class TestComposite: + """Test composite score.""" + + def test_perfect_score_no_crossings(self) -> None: + elements = [ + DrawnSpline(points=np.array([[0, 0], [5, 0], [10, 0.0]])), + DrawnSpline(points=np.array([[0, 1], [5, 1], [10, 1.0]])), + ] + score = compute_composite_score(elements) + assert score.overlap.crossing_count == 0 + assert score.score <= 0 # score is non-positive + + def test_crossing_reduces_score(self) -> None: + # Use DrawnSpline elements since composite score uses spline_only mode. + # Splines need enough interior points for segment-level crossing. + no_cross = [ + DrawnSpline(points=np.array([[0, 0], [5, 0], [10, 0.0]])), + DrawnSpline(points=np.array([[0, 2], [5, 2], [10, 2.0]])), + ] + with_cross = [ + DrawnSpline(points=np.array([[-2, -2], [-1, -1], [1, 1], [2, 2.0]])), + DrawnSpline(points=np.array([[-2, 2], [-1, 1], [1, -1], [2, -2.0]])), + ] + score_good = compute_composite_score(no_cross) + score_bad = compute_composite_score(with_cross) + assert score_good.score > score_bad.score diff --git a/tests/test_nodes.py b/tests/test_nodes.py new file mode 100644 index 0000000..93b0bbe --- /dev/null +++ b/tests/test_nodes.py @@ -0,0 +1,201 @@ +"""Tests for node types — occupation, show(), equality, config propagation.""" + +from __future__ import annotations + +from viscot.core.config import LayoutConfig +from viscot.core.nodes import ( + A0, + A_minus, + A_plus, + B_minus_plus, + B_plus_minus, + B_plus_plus, + Beta_minus, + Beta_plus, + C_minus, + Cons, + Leaf_minus, + Leaf_plus, + Nil, + OccupationInfo, + use_config, +) +from viscot.core.parser import parse + + +class TestOccupation: + """Test occupation area calculations.""" + + def test_leaf_r_is_zero(self) -> None: + assert Leaf_plus().r == 0 + assert Leaf_minus().r == 0 + + def test_a_flip_occupation(self) -> None: + a = A_plus(Leaf_plus()) + assert len(a.occupation) == 1 + assert a.occupation[0].height == a.r + assert a.r > 0 + + def test_b_evc_r(self) -> None: + b = B_plus_plus(Leaf_plus(), Leaf_plus()) + # r = (2*0 + 2*0 + 4*0.5) / 2 = 1.0 + assert b.r == 1.0 + + def test_b_flip_r(self) -> None: + b = B_plus_minus(Leaf_plus(), Leaf_minus()) + assert b.r == 1.0 + + def test_b_minus_plus_inherits_b_flip(self) -> None: + """Bug #3: B_minus_plus should use B_Flip.plot_arrow, not hardcoded theta.""" + b = B_minus_plus(Leaf_minus(), Leaf_plus()) + assert b.dir == -1 + # Ensure plot_arrow is inherited from B_Flip, not overridden + from viscot.core.nodes import B_Flip + assert type(b).plot_arrow is B_Flip.plot_arrow + + def test_cons_occupation_filters_empty(self) -> None: + c1 = C_minus(Leaf_minus(), Nil()) + c2 = C_minus(Leaf_minus(), Nil()) + cons = Cons(c1, Cons(c2, Nil())) + # Should filter out {height: 0, width: 0} entries + for occ in cons.occupation: + assert occ != OccupationInfo(0, 0) + + def test_nil_occupation(self) -> None: + n = Nil() + assert n.occupation == [OccupationInfo(0, 0)] + + def test_beta_occupation(self) -> None: + b = Beta_plus(Nil()) + assert b.r > 0 + assert b.center_r > 0 + + +class TestShow: + """Test show() produces valid output.""" + + def test_nil_show(self) -> None: + assert Nil().show() == "" + + def test_leaf_show(self) -> None: + assert Leaf_plus().show() == "l+" + assert Leaf_minus().show() == "l-" + + def test_a_plus_show(self) -> None: + assert A_plus(Leaf_plus()).show() == "a+(l+)" + + def test_a0_empty_show(self) -> None: + assert A0(Nil()).show() == "A0()" + + def test_beta_minus_comment(self) -> None: + """Bug #5: Beta_minus.dir should be -1 (clockwise).""" + b = Beta_minus(Nil()) + assert b.dir == -1 + assert b.show() == "B-{}" + + +class TestDir: + """Test dir attribute correctness.""" + + def test_directions(self) -> None: + assert Leaf_plus.dir == 1 + assert Leaf_minus.dir == -1 + assert A_plus.dir == 1 + assert A_minus.dir == -1 + + +class TestEquality: + """Test Node.__eq__ and __hash__.""" + + def test_equal_nodes(self) -> None: + a = parse("A0(a+(l+))") + b = parse("A0(a+(l+))") + assert a == b + + def test_unequal_nodes(self) -> None: + a = parse("A0(a+(l+))") + b = parse("A0(a-(l-))") + assert a != b + + def test_different_type_not_equal(self) -> None: + a = parse("A0()") + assert a != "A0()" + assert a != 42 + + def test_hash_consistent(self) -> None: + a = parse("B0+(l+,c-(l-,))") + b = parse("B0+(l+,c-(l-,))") + assert hash(a) == hash(b) + + def test_usable_in_set(self) -> None: + trees = {parse("A0()"), parse("A0()"), parse("A0(a+(l+))")} + assert len(trees) == 2 + + def test_repr(self) -> None: + a = parse("A0(a+(l+))") + assert repr(a) == "A0(a+(l+))" + + +class TestConfigPropagation: + """Test that LayoutConfig is correctly propagated to nodes.""" + + def test_different_config_different_radius(self) -> None: + """Nodes constructed with different configs should have different radii.""" + default_tree = parse("A0(a+(l+))") + a_default = default_tree.head.head # A_plus node + r_default = a_default.r + + big_config = LayoutConfig(a_flip_margin=2.0) + big_tree = parse("A0(a+(l+))", config=big_config) + a_big = big_tree.head.head + r_big = a_big.r + + assert r_big > r_default + + def test_use_config_context_manager(self) -> None: + """use_config should temporarily change the active config.""" + cfg = LayoutConfig(b_evc_margin=1.5) + with use_config(cfg): + b = B_plus_plus(Leaf_plus(), Leaf_plus()) + # r = (2*0 + 2*0 + 4*1.5) / 2 = 3.0 + assert b.r == 3.0 + + def test_config_restored_after_context(self) -> None: + """After use_config exits, default config should be restored.""" + cfg = LayoutConfig(b_evc_margin=5.0) + with use_config(cfg): + pass + b = B_plus_plus(Leaf_plus(), Leaf_plus()) + # Default: r = (2*0 + 2*0 + 4*0.5) / 2 = 1.0 + assert b.r == 1.0 + + def test_parse_with_config_affects_b0(self) -> None: + """B0 radius should change with b0_margin config.""" + expr = "B0+(l+,c-(l-,).c-(l-,))" + r_default = parse(expr).r + r_big = parse(expr, config=LayoutConfig(b0_margin=5.0)).r + assert r_big > r_default + + def test_c_height_spacing_factor_widens_c_node(self) -> None: + """Higher c_height_spacing_factor should widen C-node occupation.""" + tree0 = parse("B0+(l+,c-(l-,))", config=LayoutConfig(c_height_spacing_factor=0.0)) + tree1 = parse("B0+(l+,c-(l-,))", config=LayoutConfig(c_height_spacing_factor=2.0)) + c0 = tree0.tail.head # C_minus node + c1 = tree1.tail.head + assert c1.effective_extent >= c0.effective_extent + + def test_b0_children_dont_wrap_around(self) -> None: + """B0 children should not wrap around the parent circle.""" + import math + cfg = LayoutConfig(c_margin=0.3) + tree = parse("B0+(l+,c-(l-,).c-(l-,).c-(l-,))", config=cfg) + from viscot.core.nodes import make_list_for_c + circumference = 2 * math.pi * tree.r + ctxs = make_list_for_c( + tree.tail.occupation, tree.r, (0, 0), True, + circumference, first_child=True, config=cfg, + ) + # Last child's end should not exceed first child's start + circumference + last_end = ctxs[-1].length + tree.tail.occupation[-1].width + first_start_wrapped = ctxs[0].length + circumference + assert last_end <= first_start_wrapped diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 0000000..6865484 --- /dev/null +++ b/tests/test_parser.py @@ -0,0 +1,192 @@ +"""Tests for the parser module.""" + +from __future__ import annotations + +import pytest +from conftest import MAKEFILE_EXPRESSIONS + +from viscot.core.nodes import ( + A0, + A2, + A_minus, + A_plus, + B0_minus, + B0_plus, + B_minus_minus, + B_minus_plus, + B_plus_minus, + B_plus_plus, + Beta_minus, + Beta_plus, + C_minus, + C_plus, + Cons, + Leaf_minus, + Leaf_plus, + Nil, +) +from viscot.core.parser import parse + + +class TestBasicParsing: + """Test basic parsing of all node types.""" + + def test_a0_empty(self) -> None: + tree = parse("A0()") + assert isinstance(tree, A0) + assert isinstance(tree.head, Nil) + + def test_a0_with_child(self) -> None: + tree = parse("A0(a+(l+))") + assert isinstance(tree, A0) + assert isinstance(tree.head, Cons) + assert isinstance(tree.head.head, A_plus) + + def test_a_plus(self) -> None: + tree = parse("A0(a+(l+))") + a = tree.head.head + assert isinstance(a, A_plus) + assert isinstance(a.head, Leaf_plus) + + def test_a_minus(self) -> None: + tree = parse("A0(a-(l-))") + a = tree.head.head + assert isinstance(a, A_minus) + assert isinstance(a.head, Leaf_minus) + + def test_b0_plus(self) -> None: + tree = parse("B0+(l+,)") + assert isinstance(tree, B0_plus) + assert isinstance(tree.head, Leaf_plus) + assert isinstance(tree.tail, Nil) + + def test_b0_minus(self) -> None: + tree = parse("B0-(l-,)") + assert isinstance(tree, B0_minus) + assert isinstance(tree.head, Leaf_minus) + + def test_b_plus_plus(self) -> None: + tree = parse("B0+(b++(l+,l+),)") + assert isinstance(tree.head, B_plus_plus) + + def test_b_plus_minus(self) -> None: + tree = parse("B0+(b+-(l+,l-),)") + assert isinstance(tree.head, B_plus_minus) + + def test_b_minus_plus(self) -> None: + tree = parse("B0-(b-+(l-,l+),)") + assert isinstance(tree.head, B_minus_plus) + + def test_b_minus_minus(self) -> None: + tree = parse("B0-(b--(l-,l-),)") + assert isinstance(tree.head, B_minus_minus) + + def test_beta_plus(self) -> None: + tree = parse("B0+(B+{},)") + assert isinstance(tree.head, Beta_plus) + + def test_beta_minus(self) -> None: + tree = parse("B0-(B-{},)") + assert isinstance(tree.head, Beta_minus) + + def test_c_plus(self) -> None: + tree = parse("B0-(l-,c+(l+,))") + assert isinstance(tree.tail, Cons) + assert isinstance(tree.tail.head, C_plus) + + def test_c_minus(self) -> None: + tree = parse("B0+(l+,c-(l-,))") + assert isinstance(tree.tail, Cons) + assert isinstance(tree.tail.head, C_minus) + + def test_cons_chain(self) -> None: + tree = parse("A0(a+(l+).a+(l+))") + assert isinstance(tree.head, Cons) + assert isinstance(tree.head.tail, Cons) + + def test_a2(self) -> None: + tree = parse("A0(a2(c+(l+,),c-(l-,)))") + a2 = tree.head.head + assert isinstance(a2, A2) + + +class TestBugFixes: + """Verify that all identified bugs are fixed.""" + + def test_bug1_cs_minus1_recursion(self) -> None: + """Bug #1: cs_minus1 should recurse to cs_minus1, not cs_minus.""" + # This expression has 3 c_minus nodes chained with dots + tree = parse("B0+(l+,c-(l-,).c-(l-,).c-(l-,))") + assert isinstance(tree, B0_plus) + # Walk the tail: Cons -> Cons -> Cons -> Nil + cons1 = tree.tail + assert isinstance(cons1, Cons) + assert isinstance(cons1.head, C_minus) + cons2 = cons1.tail + assert isinstance(cons2, Cons) + assert isinstance(cons2.head, C_minus) + cons3 = cons2.tail + assert isinstance(cons3, Cons) + assert isinstance(cons3.head, C_minus) + + def test_bug4_no_debug_print(self) -> None: + """Bug #4: B0+ parsing should not produce debug output.""" + import io + import sys + + captured = io.StringIO() + old_stdout = sys.stdout + sys.stdout = captured + try: + parse("B0+(l+,)") + finally: + sys.stdout = old_stdout + # No debug print should be present + assert "B0+" not in captured.getvalue() + + +class TestRoundTrip: + """Test parse(tree.show()) == original structure.""" + + @pytest.mark.parametrize("expr", MAKEFILE_EXPRESSIONS) + def test_show_roundtrip(self, expr: str) -> None: + tree1 = parse(expr) + shown = tree1.show() + tree2 = parse(shown) + assert tree2.show() == shown + + +class TestEdgeCases: + """Edge cases and error handling.""" + + def test_invalid_syntax_raises(self) -> None: + with pytest.raises(ValueError): + parse("invalid_input!!!") + + def test_parenthesized_s(self) -> None: + tree = parse("(A0())") + assert isinstance(tree, A0) + + def test_parenthesized_cs(self) -> None: + tree = parse("B0+(l+,(c-(l-,)))") + assert isinstance(tree.tail, Cons) + + def test_illegal_character_raises_lex_error(self) -> None: + from viscot.core.lexer import COTLexError + with pytest.raises((ValueError, COTLexError)): + parse("@@@") + + def test_error_message_includes_detail(self) -> None: + with pytest.raises(ValueError, match="Failed to parse"): + parse("A0(((") + + def test_empty_input_raises(self) -> None: + with pytest.raises(ValueError): + parse("") + + def test_parse_with_config(self) -> None: + from viscot.core.config import LayoutConfig + cfg = LayoutConfig(a_flip_margin=3.0) + tree = parse("A0(a+(l+))", config=cfg) + a = tree.head.head + assert a.r == 0 + 3.0 # leaf.r + a_flip_margin diff --git a/visualize.py b/visualize.py deleted file mode 100644 index 6c27880..0000000 --- a/visualize.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Visualization program of tree representation of structurally stable incompressible flow in two dimensional multiply-connected domain -""" -# -*- coding: utf-8 -*- - -import os -from src import flow, yacc -from src.flow import Canvas - -""" -入力した木表現に対する流線を表示 -""" -def main(): - while True: - try: - s = input('>>> ') - object = yacc.parser.parse(s) - canvas = Canvas() - object.set_canvas(canvas) - object.draw() - print("draw successful!") - print("You can save picture or watch in matplotlib:"+"\n"+"If you want to save, please type \"save\"."+"\n"+"If you want to watch, please type \"watch\".") - type = input(':') - if type == "save": - dirname = "flow_picture/" - os.makedirs(dirname, exist_ok=True) - filename = dirname + s + '.png' - canvas.save_canvas(filename) - elif type == "watch": - canvas.show_canvas() - else: - pass - canvas.clear_canvas() - except AttributeError: - print("please type correct syntax.") - except EOFError: - break - -if __name__ == "__main__": - main()