From a6ec2bc2e57d53b0b7d7227af54bd50abf3e8cf5 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Wed, 26 Aug 2020 22:22:30 +0900 Subject: [PATCH 01/17] =?UTF-8?q?flow.py=E3=81=AE=E3=83=AA=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/flow.py | 400 +++++++++++++++++++++++----------------------------- 1 file changed, 180 insertions(+), 220 deletions(-) diff --git a/src/flow.py b/src/flow.py index 525912e..064aff7 100644 --- a/src/flow.py +++ b/src/flow.py @@ -12,30 +12,27 @@ def theta_point(theta, r, center): """ 半径とthetaと中心点を使って二次元上の点の位置を求める関数 """ - return (r * math.cos(theta) + center[0], r * math.sin(theta) + center[1]) + return r * math.cos(theta) + center[0], r * math.sin(theta) + center[1] -def c_list_high(children_ocu_list): +def c_list_highest(children_ocu_list): """ - C系の配列[(self.high,self.bottom_length),...]から最も大きい高さを求める関数 + C系の配列[{'height': h0, 'width': w0},...]から最も大きい高さを求める関数 """ - return max(map(lambda x: x[0], children_ocu_list)) - -def c_list_circ_length(children_ocu_list, margin): + return max(map(lambda x: x['height'], children_ocu_list)) + +def c_list_circ_length(children_ocu_list, margin): """ - C系の配列[(self.high,self.bottom_length),...]から円周を求める関数 + C系の配列[(self.high,self.bottom_length),...]から円周の長さを求める関数 """ # marginはc同士の間に空けたいスペース circ_length = 0 # 円周を保存する変数 - longest_child = 0 # 最も長い子供の長さを保存する変数 + widest_child = 0 # 最も長い子供の長さを保存する変数 for child in children_ocu_list: - circ_length += child[1]+margin # スペース分円周を伸ばす - if longest_child < child[1]: - longest_child = child[1] + circ_length += child['width'] + margin # スペース分円周を伸ばす + widest_child = max(widest_child, child['width']) # もし円周の半分以上の長さを持つ子供がいれば、円周の長さをその子供に合わせる # (C系とb系が重なることを避ける) - if circ_length/2 <= longest_child: - circ_length = longest_child*2 - return circ_length + return max(circ_length, widest_child*2) def make_list_for_c(children_ocu_list, parent_r, parent_center, parent_type, margin, parent_length=0, first_child=False): """ @@ -48,16 +45,16 @@ def make_list_for_c(children_ocu_list, parent_r, parent_center, parent_type, mar 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: + 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['width'] < length: length += 1.5 else: - length += (margin/len(children_ocu_list))-child[1]+1 + length += (margin/len(children_ocu_list))-child['width']+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] + c_list.append({'length':length, 'parent_r':parent_r, 'parent_center':parent_center, 'parent_type':parent_type}) + length += child['width'] return c_list class Canvas: @@ -109,17 +106,11 @@ 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) + assert len(xy) >= 3 + a, b = self.spline([x for [x,_] in xy], + [y for [_,y] in xy], + 100, + min(len(xy),4)-1) plt.plot(a, b, color="black") def draw_circle(self, r, center=(0, 0), circle_fill=False, fc="grey"): @@ -127,7 +118,7 @@ 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) + 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) @@ -140,7 +131,12 @@ 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)) + 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): """ @@ -161,11 +157,13 @@ def axvspan(self, r): self.ax.axvspan(-r, r, -r, r, color="gray", alpha=0.5) class Node(object, metaclass=abc.ABCMeta): + dir + @abc.abstractmethod - def __init__(self): - self.canvas = None - self.head = None - self.tail = None + def __init__(self, head = None, tail = None, canvas = None): + self.canvas = canvas + self.head = head + self.tail = tail def draw(self, *arg): """ @@ -189,82 +187,80 @@ def set_canvas(self, canvas): if self.tail is not None: self.tail.set_canvas(canvas) + def dir2rad(self): + return (self.dir + 1.0) * math.pi / 2.0 + class A0(Node): """ A0を扱うクラス """ + margin = 0.5 + def __init__(self, head): # 子の半径を定義 - super().__init__() - self.type = "A0" - self.head = head # 抽象構文木の作成 - self.margin = 0.5 + super().__init__(head) def draw(self): - long_child = 0 childrens_info = [] # drawで引数として渡す - count_r = 0 - if self.head.type == "Nil": + if isinstance(self.head, 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 + count_r = 0 + long_child = c_list_highest(self.head.occupation) # 子供達の中で一番長いrを求める for child in self.head.occupation: # 次の子供の中心点をy軸に-r*2して繰り返す - count_r += child[0] + self.margin + count_r += child['height'] + A0.margin # 子供それぞれについて中心点を作成して配列に格納 - childrens_info.append({"center":(0, -count_r), "edge":edge}) - count_r += child[0] + self.margin + childrens_info.append({'center':(0, -count_r), 'edge':long_child + A0.margin}) + count_r += child['height'] + A0.margin self.head.draw(childrens_info) class B0(Node): """ B0+,B0-の抽象クラス """ + margin = 0.5 + 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) + super().__init__(head, tail) + high_children = c_list_highest(tail.occupation) + children_length = c_list_circ_length(tail.occupation, B0.margin) + self.r = max(children_length / (2 * math.pi), head.r + high_children + B0.margin) def draw(self): - side_r = self.r + self.margin - self.canvas.axvspan(side_r) + self.canvas.axvspan(B0.margin) 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) + def plot_arrow(self): + self.canvas.draw_arrow((self.r, 0), math.pi*0.5+self.dir2rad()) + class B0_plus(B0): """ B0+を扱うクラス """ - def plot_arrow(self): - self.canvas.draw_arrow((self.r, 0), math.pi/2) + dir = 1 # + 反時計回り class B0_minus(B0): """ B0-を扱うクラス """ - def plot_arrow(self): - self.canvas.draw_arrow((self.r, 0), math.pi*1.5) + dir = -1 # - 時計回り class A_Flip(Node): """ a+,a-の抽象クラス """ + margin = 0.5 # 子の専有領域と親の領域の余白 + def __init__(self, head): - super().__init__() - self.head = head - self.margin = 0.5 # 子の専有領域と親の領域の余白 - self.r = head.r + self.margin + super().__init__(head) + self.r = head.r + A_Flip.margin + self.occupation = [{'height': self.r, 'width': 0}] # 0: dummy def draw(self, info_dic): # 描画する際に親から与える中心点 center = info_dic["center"] @@ -273,64 +269,45 @@ def draw(self, info_dic): # 描画する際に親から与える中心点 self.plot_arrow(center,edge) self.head.draw(center) + def plot_arrow(self, center, edge): + self.canvas.draw_point((center[0], center[1]-self.r*self.dir)) + self.canvas.draw_arrow((center[0]-self.r, center[1]), theta=math.pi*1.5+self.dir2rad()) + self.canvas.draw_arrow((center[0]+self.r, center[1]), theta=math.pi*0.5+self.dir2rad()) + self.canvas.draw_line((-edge, center[1]-self.r*self.dir), (edge, center[1]-self.r*self.dir)) + self.canvas.draw_arrow((-edge/2, center[1]-self.r*self.dir), math.pi+self.dir2rad()) + self.canvas.draw_arrow(( edge/2, center[1]-self.r*self.dir), math.pi+self.dir2rad()) + 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) + dir = 1 # + 反時計回り 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) + dir = -1 # - 時計回り class A2(Node): """ a2を扱うクラス """ + margin = 0.5 # 子同士のスペースの定義 + 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 + super().__init__(head, tail) + self.high = max(c_list_highest(head.occupation), c_list_highest(tail.occupation)) + len_of_plus_circ = c_list_circ_length(head.occupation, A2.margin) + A2.margin # plus回りの長さ + len_of_minus_circ = c_list_circ_length(tail.occupation, A2.margin) + A2.margin # minus回りの長さ + self.len_of_circ = max(len_of_plus_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)] + self.occupation = [{'height': self.r, 'width': 0}] # 0: dummy def draw(self, info_dic): - center = info_dic["center"] - edge = info_dic["edge"] + 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])) # 一様流との交点の描画(左) @@ -339,9 +316,9 @@ def draw(self, info_dic): 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.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, A2.margin) + for_minus_children = make_list_for_c(self.tail.occupation, self.center_r, center, False, A2.margin, parent_length=self.len_of_circ/2) self.head.draw(for_plus_children) self.tail.draw(for_minus_children) @@ -350,21 +327,12 @@ 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) + super().__init__(head, tail) + self.occupation = [s for s in head.occupation + tail.occupation if s != {'height': 0, 'width': 0}] def draw(self, children_list): self.head.draw(children_list.pop(0)) - if len(children_list) != 0: + if len(children_list) > 0: self.tail.draw(children_list) class Nil(Node): @@ -373,8 +341,8 @@ class Nil(Node): """ def __init__(self): super().__init__() - self.type = "Nil" - self.occupation = [(0, 0)] + self.occupation = [{'height': 0, 'width': 0}] # 0: dummy + class Leaf(Node): """ @@ -388,55 +356,88 @@ 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 # 全体の占有領域(半径) + margin = 0.5 # 子の専有領域と親の領域の余白 + def __init__(self, head, tail): # headは上の円の半径、tailは下の円の半径 + super().__init__(head, tail) + self.r_up = head.r # 上の図の占有領域(半径) + self.r_lw = tail.r # 下の図の占有領域(半径) + self.r = (2 * self.r_up + 2 * self.r_lw + 4 * B_Evc.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.canvas.draw_point((center[0], self.r_lw+center[1]-self.r_up)) # 2つの円の交点 + self.canvas.draw_circle(self.r_up+B_Evc.margin, (center[0], self.r_lw+B_Evc.margin+center[1])) # 上の円 + self.canvas.draw_circle(self.r_lw+B_Evc.margin, (center[0], -self.r_up-B_Evc.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])) + self.head.draw((center[0], self.r_lw+B_Evc.margin+center[1])) + self.tail.draw((center[0], -self.r_up-B_Evc.margin+center[1])) + + def plot_arrow(self, center): + self.canvas.draw_arrow((center[0], self.r_lw+2*self.margin+center[1]+self.r_up), self.dir2rad()) # 上の円の矢印 + self.canvas.draw_arrow((center[0], -self.r_up-2*self.margin+center[1]-self.r_lw), math.pi - self.dir2rad()) # 下の円の矢印 + +class B_plus_plus(B_Evc): + """ + b++を扱うクラス + """ + dir = 1 # + 反時計回り + +class B_minus_minus(B_Evc): + """ + b--を扱うクラス + """ + dir = -1 # - 時計回り class B_Flip(Node): """ - b+-,b--の抽象クラス + b+-,b-+の抽象クラス """ + margin = 0.5 # 子の専有領域と親の領域の余白 + 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 + self.r_up = head.r # 上の図の占有領域(半径) + self.r_lw = tail.r # 下の図の占有領域(半径) + self.r = (2 * self.r_up + 2 * self.r_lw + 4 * B_Flip.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.canvas.draw_circle(self.r_up+B_Flip.margin, (center[0], self.r_lw+B_Flip.margin+center[1])) + self.canvas.draw_circle(self.r_up+self.r_lw+2*B_Flip.margin, center) + self.canvas.draw_point((center[0], self.r_lw+B_Flip.margin+center[1]+self.r_up+B_Flip.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])) + self.head.draw((center[0], self.r_lw+B_Flip.margin+center[1])) + self.tail.draw((center[0], -self.r_up-B_Flip.margin+center[1])) + + def plot_arrow(self, center): + self.canvas.draw_arrow((center[0], self.r_lw+B_Flip.margin+center[1]-self.r_up-B_Flip.margin), theta=self.dir2rad()) + self.canvas.draw_arrow((center[0], center[1]-(self.r_up+self.r_lw+2*B_Flip.margin)), theta=math.pi-self.dir2rad()) + +class B_plus_minus(B_Flip): + """ + b+-を扱うクラス + """ + dir = 1 # + 反時計回り + +class B_minus_plus(B_Flip): + """ + b-+を扱うクラス + """ + dir = -1 # - 時計回り + def plot_arrow(self, center): + self.canvas.draw_arrow((center[0], self.r_lw+B_Flip.margin+center[1]-self.r_up-B_Flip.margin), theta=0) + self.canvas.draw_arrow((center[0], center[1]-(self.r_up+self.r_lw+2*B_Flip.margin)), theta=math.pi) class Beta(Node): """ beta+,beta-の抽象クラス """ + margin = 0.5 # 要素の両脇に作るスペースの大きさ + 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) + super().__init__(head) + high_children = c_list_highest(head.occupation) + children_length = c_list_circ_length(head.occupation, Beta.margin) self.center_r = children_length / (2 * math.pi) # betaの円 if children_length < 1: self.center_r = 7 / (2 * math.pi) @@ -444,83 +445,49 @@ def __init__(self, head): 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) + for_children = make_list_for_c(self.head.occupation, self.center_r, center, False, Beta.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))) + self.canvas.draw_arrow((center[0]+self.center_r, center[1]), math.pi*0.5+self.dir2rad()) 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) + dir = 1 # + 反時計回り 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) + dir = -1 # + 反時計回り class C(Node): """ c+,c-の抽象クラス """ + margin = 1 # c系の要素の両脇に作るスペースの大きさ + circ_margin = 0.5 # 子のb系の要素と親の間の距離 + 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) + super().__init__(head, tail) + self.high_children = c_list_highest(tail.occupation) + self.children_length = c_list_circ_length(tail.occupation, C.margin) bottom_length = max(head.r*2, self.children_length) - self.high = 2 * head.r + self.high_children + self.margin + self.high = 2 * head.r + self.high_children + C.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)] + self.high += len(self.tail.occupation) + self.occupation = [{'height': self.high, 'width': bottom_length}] def draw(self, c_data): if self.high_children == 0: self.high_children = 0.3 - length = c_data["length"] + length = c_data["length"] center_r = c_data["parent_r"] - center = c_data["parent_center"] - bool_b0 = c_data["parent_type"] + 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 @@ -528,18 +495,18 @@ def draw(self, c_data): 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) + b_center = theta_point(high_theta, center_r-self.high_children-C.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) + b_center = theta_point(high_theta, center_r+self.high_children+C.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) + 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) + b_r_center = theta_point(-b_r_theta, self.head.r+C.circ_margin, b_center) # 180度の点 - b_l_center = theta_point(math.pi-b_r_theta, self.head.r+self.circ_margin, b_center) + b_l_center = theta_point(math.pi-b_r_theta, self.head.r+C.circ_margin, b_center) if self.head.r * 2 < self.children_length / 2: self.canvas.draw_spline([start_point, high_point, end_point]) else: @@ -548,28 +515,21 @@ def draw(self, c_data): 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) + for_children = make_list_for_c(self.tail.occupation, center_r, center, bool_b0, C.margin/1.5, parent_length=length) self.head.draw(b_center) self.tail.draw(for_children) + 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)+self.dir2rad()) + 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)) + dir = 1 # + 反時計回り 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)) + dir = -1 # - 時計回り From b82001879fe3d5834f45bdf519e434e76d71cca0 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Tue, 22 Dec 2020 13:32:00 +0900 Subject: [PATCH 02/17] toplevel --- src/Makefile | 8 +++++ src/visualize.py | 52 +++++++++++++++++++++++++++++++++ src/{ => visualize}/__init__.py | 0 src/{ => visualize}/flow.py | 2 +- src/{ => visualize}/lex.py | 0 src/{ => visualize}/yacc.py | 0 test.txt | 2 +- visualize.py | 40 ------------------------- 8 files changed, 62 insertions(+), 42 deletions(-) create mode 100644 src/Makefile create mode 100644 src/visualize.py rename src/{ => visualize}/__init__.py (100%) rename src/{ => visualize}/flow.py (99%) rename src/{ => visualize}/lex.py (100%) rename src/{ => visualize}/yacc.py (100%) delete mode 100644 visualize.py diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..d6c1fcc --- /dev/null +++ b/src/Makefile @@ -0,0 +1,8 @@ +all: + python3 visualize.py + +clean: + $(RM) -rf visualize/__pycache__ visualize/parser.out visualize/parsetab.py + +test: + echo "b0-(b-+(b-+(l,l),be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n)))))" | python3 visualize.py diff --git a/src/visualize.py b/src/visualize.py new file mode 100644 index 0000000..35c594a --- /dev/null +++ b/src/visualize.py @@ -0,0 +1,52 @@ +""" +Visualization program of tree representation of structurally stable incompressible flow in two dimensional multiply-connected domain +""" +# -*- coding: utf-8 -*- + +import os +from visualize import flow, yacc +from visualize.flow import Canvas +import argparse + +""" +入力した木表現に対する流線を表示 +""" +def main(): + parser = argparse.ArgumentParser(description='Visualize COT representation.') + parser.add_argument('-i', '--interactive', help='interactive mode', action='store_true') + parser.add_argument('-o', '--output', help='specify an output file (.png).') + args = parser.parse_args() + # print(args) + + canvas = Canvas() + if args.interactive: + while True: + try: + s = input('>>> ') + object = yacc.parser.parse(s) + object.set_canvas(canvas) + object.draw() + type = input('Select (save/show):') + if type == "save": + filename = s + '.png' + canvas.save_canvas(filename) + elif type == "show": + canvas.show_canvas() + else: + pass + canvas.clear_canvas() + except AttributeError: + print("please type correct syntax.") + except EOFError: + break + else: + object = yacc.parser.parse(input()) + object.set_canvas(canvas) + object.draw() + if args.output is None: + canvas.show_canvas() + else: + canvas.save_canvas(args.output) + +if __name__ == "__main__": + main() diff --git a/src/__init__.py b/src/visualize/__init__.py similarity index 100% rename from src/__init__.py rename to src/visualize/__init__.py diff --git a/src/flow.py b/src/visualize/flow.py similarity index 99% rename from src/flow.py rename to src/visualize/flow.py index 064aff7..eb60f33 100644 --- a/src/flow.py +++ b/src/visualize/flow.py @@ -80,7 +80,7 @@ def save_canvas(self, file_name): """ 作成された画像を保存 """ - print("save picture! ") + # print("save picture! ") plt.tight_layout() plt.savefig(file_name) diff --git a/src/lex.py b/src/visualize/lex.py similarity index 100% rename from src/lex.py rename to src/visualize/lex.py diff --git a/src/yacc.py b/src/visualize/yacc.py similarity index 100% rename from src/yacc.py rename to src/visualize/yacc.py diff --git a/test.txt b/test.txt index 390d48d..18ea47e 100644 --- a/test.txt +++ b/test.txt @@ -37,7 +37,7 @@ a #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)) +#a0(cons(a2(cons(c+(be+(n),n),n),n),n)) ___________________________________________________________________________________ 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() From 735a4e9848b72989b937055595d66f3656b90148 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Tue, 22 Dec 2020 13:54:08 +0900 Subject: [PATCH 03/17] readme --- README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 67b2bb2..5bce344 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -# Visualization program of tree representation of structurally stable incompressible flow in two dimensional multiply-connected domain +# visCOT: visualizing the tree representations of structurally stable incompressible flows in two dimensional multiply-connected domains このプログラムは2次元多重連結領域内における構造安定な非圧縮流れの木表現の入力に対して,同一のトポロジーを表す2次元上の図を作図するものです. 想定されている入力は,Consで繋がれた木を同一の高さとして見た場合の,深さが3までの木です. ## Requirements -+ Python3 -+ Matplotlib -+ PLY ++ Python3 (>= 3.8.5) ++ Matplotlib (>= 3.3.1) ++ numpy (>=1.19.1) ++ scipy (>=1.5.2) ++ PLY (>= 3.10) ## Linux Ubuntuにてインストール例 + 本プログラムをダウンロード @@ -21,7 +23,6 @@ sudo apt install python3 + Matplotlib をインストール ``` -cd Thesis_program pip3 install Matplotlib pip3 install numpy pip3 install scipy @@ -35,12 +36,7 @@ pip3 install PLY ## 実行例 プログラムを起動し,木表現を入力する. ``` -python3 visualize.py -``` - -木表現は,例えば次のように入力する. -``` -a0(cons(a2(cons(c+(l,n),cons(c+(l,n),n)),cons(c-(l,n),cons(c-(l,n),n))),n)) +echo "a0(cons(a2(cons(c+(l,n),cons(c+(l,n),n)),cons(c-(l,n),cons(c-(l,n),n))),n))" | python3 visualize.py ``` 入力用の木表現が「test.txt」に用意されているので試してみてください. From 99fb40c65af60eeec74d6695dfd7d6264ed8b847 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Mon, 20 Dec 2021 11:28:46 +0900 Subject: [PATCH 04/17] =?UTF-8?q?psiclone=E3=81=AE=E5=87=BA=E5=8A=9B?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=82=8B=E3=81=B9=E3=81=8F?= =?UTF-8?q?=E7=B7=A8=E9=9B=86=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Makefile | 5 ++++- src/visualize/flow.py | 14 +++++++++++++- src/visualize/lex.py | 15 ++++++++------- src/visualize/yacc.py | 40 +++++++++++++++++++++++++--------------- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/Makefile b/src/Makefile index d6c1fcc..14670de 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,4 +5,7 @@ clean: $(RM) -rf visualize/__pycache__ visualize/parser.out visualize/parsetab.py test: - echo "b0-(b-+(b-+(l,l),be+(n)),(cons(c+(be+(n),n),cons(c+(l,n),cons(c+(l,n),n)))))" | python3 visualize.py + echo "B0-(b-+(b-+(l-,l+),B+{n}),(cons(c+(B+{n},n),cons(c+(l+,n),cons(c+(l+,n),n)))))" | python3 visualize.py + echo "a0()" | python3 visualize.py + echo "a0(a+(l+))" | python3 visualize.py + echo "a0(a+(l+).a+(l+))" | python3 visualize.py diff --git a/src/visualize/flow.py b/src/visualize/flow.py index eb60f33..e54cecf 100644 --- a/src/visualize/flow.py +++ b/src/visualize/flow.py @@ -346,12 +346,24 @@ def __init__(self): class Leaf(Node): """ - leafを扱うクラス + l+,l-の抽象クラス """ def __init__(self): super().__init__() self.r = 0 +class Leaf_plus(Leaf): + """ + l+を扱うクラス + """ + dir = 1 + +class Leaf_minus(Leaf): + """ + l-を扱うクラス + """ + dir = -1 + class B_Evc(Node): """ b++,b--の抽象クラス diff --git a/src/visualize/lex.py b/src/visualize/lex.py index d0ae871..ada6534 100644 --- a/src/visualize/lex.py +++ b/src/visualize/lex.py @@ -8,14 +8,14 @@ 'B_PLUS_PLUS', 'B_PLUS_MINUS', 'B_MINUS_PLUS', 'B_MINUS_MINUS', 'BETA_PLUS', 'BETA_MINUS', 'C_PLUS', 'C_MINUS', - 'CONS', 'NIL', 'LEAF', + 'CONS', 'NIL', 'LEAF_PLUS', 'LEAF_MINUS', ) -literals = "()," +literals = "(),{}." t_A0 = r'a0' -t_B0_PLUS = r'b0\+' -t_B0_MINUS = r'b0\-' +t_B0_PLUS = r'B0\+' +t_B0_MINUS = r'B0\-' t_A_PLUS = r'a\+' t_A_MINUS = r'a\-' @@ -26,15 +26,16 @@ t_B_MINUS_PLUS = r'b\-\+' t_B_MINUS_MINUS = r'b\-\-' -t_BETA_PLUS = r'be\+' -t_BETA_MINUS = r'be\-' +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 = r'l' +t_LEAF_PLUS = r'l\+' +t_LEAF_MINUS = r'l\-' t_ignore = ' \t\n' # 入力を無視する t_ignore_COMMENT = r'\#.*' # コメントを無視する diff --git a/src/visualize/yacc.py b/src/visualize/yacc.py index ff44a2c..17be143 100644 --- a/src/visualize/yacc.py +++ b/src/visualize/yacc.py @@ -6,18 +6,28 @@ from . import flow def p_s(p): - '''s : A0 '(' as ')' + '''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]) + 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_empty(p): + 'empty :' + pass 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]) + '''as : empty ')' + | a as1 ''' + if p[2] == ')': p[0] = flow.Nil() + elif True: p[0] = flow.Cons(p[1], p[2]) + +def p_as1(p): + '''as1 : empty ')' + | '.' a as1''' + if p[2] == ')': p[0] = flow.Nil() + elif True: p[0] = flow.Cons(p[2], p[3]) def p_a(p): '''a : A_PLUS '(' b_plus ')' @@ -28,24 +38,24 @@ def p_a(p): elif p[1] == 'a2': p[0] = flow.A2(p[3],p[5]) def p_b_plus(p): - '''b_plus : LEAF + '''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] = flow.Leaf() + | BETA_PLUS '{' cs_plus '}' ''' + if p[1] == 'l+': p[0] = flow.Leaf_plus() 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]) + elif p[1] == 'B+': p[0] = flow.Beta_plus(p[3]) def p_b_minus(p): - '''b_minus : LEAF + '''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] = flow.Leaf() + | BETA_MINUS '{' cs_minus '}' ''' + if p[1] == 'l-': p[0] = flow.Leaf_minus() 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]) + elif p[1] == 'B-': p[0] = flow.Beta_minus(p[3]) def p_c_plus(p): '''c_plus : C_PLUS '(' b_plus ',' cs_minus ')' ''' From f62742f4f925eef443b8c6220bc3059b19528d92 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Mon, 20 Dec 2021 11:55:40 +0900 Subject: [PATCH 05/17] =?UTF-8?q?2021=E5=B9=B412=E6=9C=8820=E6=97=A5(?= =?UTF-8?q?=E6=9C=88)=E3=81=AF=E3=81=93=E3=81=93=E3=81=BE=E3=81=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Makefile | 7 ++++++ src/visualize/yacc.py | 50 ++++++++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/Makefile b/src/Makefile index 14670de..ea2d6a6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -6,6 +6,13 @@ clean: test: echo "B0-(b-+(b-+(l-,l+),B+{n}),(cons(c+(B+{n},n),cons(c+(l+,n),cons(c+(l+,n),n)))))" | python3 visualize.py + echo "B0+(b+-(b+-(l+,l-),B-{n}),(cons(c-(B-{n},n),cons(c-(l-,n),cons(c-(l-,n),n)))))" | python3 visualize.py echo "a0()" | python3 visualize.py echo "a0(a+(l+))" | python3 visualize.py echo "a0(a+(l+).a+(l+))" | python3 visualize.py + echo "B0+(l+,())" | python3 visualize.py + echo "B0+(l+,(c-(l-,n)))" | python3 visualize.py + echo "B0+(l+,(c-(l-,n).c-(l-,n)))" | python3 visualize.py + echo "B0-(l-,())" | python3 visualize.py + echo "B0+(b+-(l+,l-),(c-(l-,).c-(l-,).c-(l-,)))" | python3 visualize.py + echo "B0+(b+-(l+,l-),(c-(B-{},).c-(l-,).c-(l-,)))" | python3 visualize.py diff --git a/src/visualize/yacc.py b/src/visualize/yacc.py index 17be143..73877a7 100644 --- a/src/visualize/yacc.py +++ b/src/visualize/yacc.py @@ -7,8 +7,8 @@ def p_s(p): '''s : A0 '(' as - | B0_PLUS '(' b_plus ',' '(' cs_minus ')' ')' - | B0_MINUS '(' b_minus ',' '(' cs_plus ')' ')' ''' + | 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]) @@ -32,7 +32,7 @@ def p_as1(p): def p_a(p): '''a : A_PLUS '(' b_plus ')' | A_MINUS '(' b_minus ')' - | A2 '(' cs_plus ',' cs_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]) @@ -41,7 +41,7 @@ def p_b_plus(p): '''b_plus : LEAF_PLUS | B_PLUS_PLUS '(' b_plus ',' b_plus ')' | B_PLUS_MINUS '(' b_plus ',' b_minus ')' - | BETA_PLUS '{' cs_plus '}' ''' + | BETA_PLUS '{' cs_plus ''' if p[1] == 'l+': p[0] = flow.Leaf_plus() 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]) @@ -51,31 +51,51 @@ def p_b_minus(p): '''b_minus : LEAF_MINUS | B_MINUS_MINUS '(' b_minus ',' b_minus ')' | B_MINUS_PLUS '(' b_minus ',' b_plus ')' - | BETA_MINUS '{' cs_minus '}' ''' + | BETA_MINUS '{' cs_minus ''' if p[1] == 'l-': p[0] = flow.Leaf_minus() 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] == 'B-': p[0] = flow.Beta_minus(p[3]) def p_c_plus(p): - '''c_plus : C_PLUS '(' b_plus ',' cs_minus ')' ''' + '''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 ')' ''' + '''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]) + '''cs_plus : empty ')' + | empty '}' + | empty ',' + | c_plus cs_plus1 ''' + if p[2] == ')' or p[2] == '}' or p[2] == ',': p[0] = flow.Nil() + elif True: p[0] = flow.Cons(p[1], p[2]) + +def p_cs_plus1(p): + '''cs_plus1 : empty ')' + | empty '}' + | empty ',' + | '.' c_plus cs_plus1 ''' + if p[2] == ')' or p[2] == '}' or p[2] == ',': p[0] = flow.Nil() + elif True: p[0] = flow.Cons(p[2], p[3]) 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]) + '''cs_minus : empty ')' + | empty '}' + | empty ',' + | c_minus cs_minus1 ''' + if p[2] == ')' or p[2] == '}' or p[2] == ',': p[0] = flow.Nil() + elif True: p[0] = flow.Cons(p[1], p[2]) + +def p_cs_minus1(p): + '''cs_minus1 : empty ')' + | empty '}' + | empty ',' + | '.' c_minus cs_minus1 ''' + if p[2] == ')' or p[2] == '}' or p[2] == ',': p[0] = flow.Nil() + elif True: p[0] = flow.Cons(p[2], p[3]) def p_error(p): print ('Syntax error in input %s' %p) From bcb3077c5f8720a9462c31afe4374aa033ec4d56 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Fri, 24 Dec 2021 20:46:17 +0900 Subject: [PATCH 06/17] =?UTF-8?q?psiclone=E3=81=AE=E5=87=BA=E5=8A=9B?= =?UTF-8?q?=E3=82=92parse=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/Makefile | 16 +- src/visualize/flow.py | 68 ++ src/visualize/yacc.py | 90 +- test.txt | 1950 ----------------------------------------- 5 files changed, 125 insertions(+), 2001 deletions(-) delete mode 100644 test.txt diff --git a/README.md b/README.md index 5bce344..c1310f6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ pip3 install PLY ## 実行例 プログラムを起動し,木表現を入力する. ``` -echo "a0(cons(a2(cons(c+(l,n),cons(c+(l,n),n)),cons(c-(l,n),cons(c-(l,n),n))),n))" | python3 visualize.py +echo "a0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | python3 visualize.py ``` 入力用の木表現が「test.txt」に用意されているので試してみてください. diff --git a/src/Makefile b/src/Makefile index ea2d6a6..697076c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,14 +5,14 @@ clean: $(RM) -rf visualize/__pycache__ visualize/parser.out visualize/parsetab.py test: - echo "B0-(b-+(b-+(l-,l+),B+{n}),(cons(c+(B+{n},n),cons(c+(l+,n),cons(c+(l+,n),n)))))" | python3 visualize.py - echo "B0+(b+-(b+-(l+,l-),B-{n}),(cons(c-(B-{n},n),cons(c-(l-,n),cons(c-(l-,n),n)))))" | python3 visualize.py echo "a0()" | python3 visualize.py echo "a0(a+(l+))" | python3 visualize.py echo "a0(a+(l+).a+(l+))" | python3 visualize.py - echo "B0+(l+,())" | python3 visualize.py - echo "B0+(l+,(c-(l-,n)))" | python3 visualize.py - echo "B0+(l+,(c-(l-,n).c-(l-,n)))" | python3 visualize.py - echo "B0-(l-,())" | python3 visualize.py - echo "B0+(b+-(l+,l-),(c-(l-,).c-(l-,).c-(l-,)))" | python3 visualize.py - echo "B0+(b+-(l+,l-),(c-(B-{},).c-(l-,).c-(l-,)))" | python3 visualize.py + echo "B0+(l+,)" | python3 visualize.py + echo "B0+(l+,c-(l-,))" | python3 visualize.py + echo "B0+(l+,c-(l-,).c-(l-,))" | python3 visualize.py + echo "B0-(l-,)" | python3 visualize.py + echo "B0+(b+-(l+,l-),c-(l-,).c-(l-,).c-(l-,))" | python3 visualize.py + echo "B0+(b+-(l+,l-),c-(B-{},).c-(l-,).c-(l-,))" | python3 visualize.py + echo "B0-(b-+(b-+(l-,l+),B+{}),c+(B+{},).c+(l+,).c+(l+,))" | python3 visualize.py + echo "B0+(b+-(b+-(l+,l-),B-{}),c-(B-{},).c-(l-,).c-(l-,))" | python3 visualize.py diff --git a/src/visualize/flow.py b/src/visualize/flow.py index e54cecf..b12cbdc 100644 --- a/src/visualize/flow.py +++ b/src/visualize/flow.py @@ -190,6 +190,9 @@ def set_canvas(self, canvas): def dir2rad(self): return (self.dir + 1.0) * math.pi / 2.0 + def show(self): + pass + class A0(Node): """ A0を扱うクラス @@ -214,7 +217,17 @@ def draw(self): # 子供それぞれについて中心点を作成して配列に格納 childrens_info.append({'center':(0, -count_r), 'edge':long_child + A0.margin}) count_r += child['height'] + A0.margin + # for i in childrens_info: + # print(i) + # self.head.draw(i) self.head.draw(childrens_info) + + def show(self): +# print("a0("+self.head.show(self)+")") + if isinstance(self.head, Nil): + return "a0()" + else: + return "a0(" + self.head.show() + ")" class B0(Node): """ @@ -245,12 +258,18 @@ class B0_plus(B0): """ dir = 1 # + 反時計回り + def show(self): + return "B0+("+self.head.show()+","+self.tail.show()+")" + class B0_minus(B0): """ B0-を扱うクラス """ dir = -1 # - 時計回り + def show(self): + return "B0-("+self.head.show()+")" + class A_Flip(Node): """ a+,a-の抽象クラス @@ -263,6 +282,8 @@ def __init__(self, head): self.occupation = [{'height': self.r, 'width': 0}] # 0: dummy def draw(self, info_dic): # 描画する際に親から与える中心点 + print("here:") + print(info_dic) center = info_dic["center"] edge = info_dic["edge"] self.canvas.draw_circle(self.r, center) @@ -283,12 +304,18 @@ class A_plus(A_Flip): """ dir = 1 # + 反時計回り + def show(self): + return "a+("+self.head.show()+")" + class A_minus(A_Flip): """ a-を扱うクラス """ dir = -1 # - 時計回り + def show(self): + return "a-("+self.head.show()+")" + class A2(Node): """ a2を扱うクラス @@ -322,6 +349,9 @@ def draw(self, info_dic): self.head.draw(for_plus_children) self.tail.draw(for_minus_children) + def show(self): + print("a2("+self.head.show()+","+self.tail.show()+")") + class Cons(Node): """ consを扱うクラス @@ -335,6 +365,12 @@ def draw(self, children_list): if len(children_list) > 0: self.tail.draw(children_list) + def show(self): + if isinstance(self.tail, Nil): + return self.head.show() + else: + return self.head.show()+"."+self.tail.show() + class Nil(Node): """ nilを扱うクラス @@ -343,6 +379,8 @@ def __init__(self): super().__init__() self.occupation = [{'height': 0, 'width': 0}] # 0: dummy + def show(self): + return "" class Leaf(Node): """ @@ -358,12 +396,18 @@ class Leaf_plus(Leaf): """ dir = 1 + def show(self): + return "l+" + class Leaf_minus(Leaf): """ l-を扱うクラス """ dir = -1 + def show(self): + return "l-" + class B_Evc(Node): """ b++,b--の抽象クラス @@ -393,12 +437,18 @@ class B_plus_plus(B_Evc): """ dir = 1 # + 反時計回り + def show(self): + return "b++("+self.head.show()+","+self.tail.show()+")" + class B_minus_minus(B_Evc): """ b--を扱うクラス """ dir = -1 # - 時計回り + def show(self): + return "b--("+self.head.show()+","+self.tail.show()+")" + class B_Flip(Node): """ b+-,b-+の抽象クラス @@ -431,6 +481,9 @@ class B_plus_minus(B_Flip): """ dir = 1 # + 反時計回り + def show(self): + return "b+-("+self.head.show()+","+self.tail.show()+")" + class B_minus_plus(B_Flip): """ b-+を扱うクラス @@ -440,6 +493,9 @@ def plot_arrow(self, center): self.canvas.draw_arrow((center[0], self.r_lw+B_Flip.margin+center[1]-self.r_up-B_Flip.margin), theta=0) self.canvas.draw_arrow((center[0], center[1]-(self.r_up+self.r_lw+2*B_Flip.margin)), theta=math.pi) + def show(self): + return "b-+("+self.head.show()+","+self.tail.show()+")" + class Beta(Node): """ beta+,beta-の抽象クラス @@ -470,12 +526,18 @@ class Beta_plus(Beta): """ dir = 1 # + 反時計回り + def show(self): + return "B+("+self.head.show()+")" + class Beta_minus(Beta): """ beta-を扱うクラス """ dir = -1 # + 反時計回り + def show(self): + return "B-("+self.head.show()+")" + class C(Node): """ c+,c-の抽象クラス @@ -540,8 +602,14 @@ class C_plus(C): """ dir = 1 # + 反時計回り + def show(self): + return "c+("+self.head.show()+","+self.tail.show()+")" + class C_minus(C): """ c-を扱うクラス """ dir = -1 # - 時計回り + + def show(self): + return "c-("+self.head.show()+","+self.tail.show()+")" diff --git a/src/visualize/yacc.py b/src/visualize/yacc.py index 73877a7..ea5a8ce 100644 --- a/src/visualize/yacc.py +++ b/src/visualize/yacc.py @@ -6,33 +6,39 @@ from . import flow def p_s(p): - '''s : A0 '(' as - | B0_PLUS '(' b_plus ',' '(' cs_minus ')' - | B0_MINUS '(' b_minus ',' '(' cs_plus ')' ''' + '''s : A0 '(' as ')' + | B0_PLUS '(' b_plus ',' cs_minus ')' + | B0_MINUS '(' b_minus ',' cs_plus ')' + | '(' s ')' ''' 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]) + elif p[1] == 'B0+': + p[0] = flow.B0_plus(p[3], p[5]) + print(p[0].show()) + elif p[1] == 'B0-': p[0] = flow.B0_minus(p[3], p[5]) + elif p[1] == '(': p[0] = p[2] def p_empty(p): 'empty :' pass def p_as(p): - '''as : empty ')' - | a as1 ''' - if p[2] == ')': p[0] = flow.Nil() - elif True: p[0] = flow.Cons(p[1], p[2]) + '''as : empty + | a + | a '.' as1 ''' + if p[1] == None: p[0] = flow.Nil() + elif p[1] != None and len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) + elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) def p_as1(p): - '''as1 : empty ')' - | '.' a as1''' - if p[2] == ')': p[0] = flow.Nil() - elif True: p[0] = flow.Cons(p[2], p[3]) + '''as1 : a + | a '.' as1 ''' + if len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) + elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) def p_a(p): '''a : A_PLUS '(' b_plus ')' | A_MINUS '(' b_minus ')' - | A2 '(' cs_plus cs_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]) @@ -41,7 +47,7 @@ def p_b_plus(p): '''b_plus : LEAF_PLUS | B_PLUS_PLUS '(' b_plus ',' b_plus ')' | B_PLUS_MINUS '(' b_plus ',' b_minus ')' - | BETA_PLUS '{' cs_plus ''' + | BETA_PLUS '{' cs_plus '}' ''' if p[1] == 'l+': p[0] = flow.Leaf_plus() 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]) @@ -51,51 +57,51 @@ def p_b_minus(p): '''b_minus : LEAF_MINUS | B_MINUS_MINUS '(' b_minus ',' b_minus ')' | B_MINUS_PLUS '(' b_minus ',' b_plus ')' - | BETA_MINUS '{' cs_minus ''' + | BETA_MINUS '{' cs_minus '}' ''' if p[1] == 'l-': p[0] = flow.Leaf_minus() 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] == 'B-': p[0] = flow.Beta_minus(p[3]) def p_c_plus(p): - '''c_plus : C_PLUS '(' b_plus ',' cs_minus ''' + '''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 ''' + '''c_minus : C_MINUS '(' b_minus ',' cs_plus ')' ''' p[0] = flow.C_minus(p[3], p[5]) def p_cs_plus(p): - '''cs_plus : empty ')' - | empty '}' - | empty ',' - | c_plus cs_plus1 ''' - if p[2] == ')' or p[2] == '}' or p[2] == ',': p[0] = flow.Nil() - elif True: p[0] = flow.Cons(p[1], p[2]) + '''cs_plus : empty + | c_plus + | c_plus '.' cs_plus1 + | '(' cs_plus ')' ''' + if p[1] == None: p[0] = flow.Nil() + elif p[1] != None and len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) + elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) + elif p[1] == '(': p[0] = p[2] def p_cs_plus1(p): - '''cs_plus1 : empty ')' - | empty '}' - | empty ',' - | '.' c_plus cs_plus1 ''' - if p[2] == ')' or p[2] == '}' or p[2] == ',': p[0] = flow.Nil() - elif True: p[0] = flow.Cons(p[2], p[3]) + '''cs_plus1 : c_plus + | c_plus '.' cs_plus1 ''' + if len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) + elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) def p_cs_minus(p): - '''cs_minus : empty ')' - | empty '}' - | empty ',' - | c_minus cs_minus1 ''' - if p[2] == ')' or p[2] == '}' or p[2] == ',': p[0] = flow.Nil() - elif True: p[0] = flow.Cons(p[1], p[2]) + '''cs_minus : empty + | c_minus + | c_minus '.' cs_minus1 + | '(' cs_minus ')' ''' + if p[1] == None: p[0] = flow.Nil() + elif p[1] != None and len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) + elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) + elif p[1] == '(': p[0] = p[2] def p_cs_minus1(p): - '''cs_minus1 : empty ')' - | empty '}' - | empty ',' - | '.' c_minus cs_minus1 ''' - if p[2] == ')' or p[2] == '}' or p[2] == ',': p[0] = flow.Nil() - elif True: p[0] = flow.Cons(p[2], p[3]) + '''cs_minus1 : c_minus + | c_minus '.' cs_minus ''' + if len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) + elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) def p_error(p): print ('Syntax error in input %s' %p) diff --git a/test.txt b/test.txt deleted file mode 100644 index 18ea47e..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)) From 32e82741b97e6d3acd32b459545553837c2d8b9f Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Sat, 25 Dec 2021 09:07:45 +0900 Subject: [PATCH 07/17] =?UTF-8?q?B=E7=B3=BB=E3=81=AE=E5=91=A8=E3=82=8A?= =?UTF-8?q?=E3=81=AE=E5=A1=97=E3=82=8A=E3=81=A4=E3=81=B6=E3=81=97=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/visualize/flow.py | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/visualize/flow.py b/src/visualize/flow.py index b12cbdc..e3e5e03 100644 --- a/src/visualize/flow.py +++ b/src/visualize/flow.py @@ -80,7 +80,6 @@ def save_canvas(self, file_name): """ 作成された画像を保存 """ - # print("save picture! ") plt.tight_layout() plt.savefig(file_name) @@ -113,7 +112,7 @@ def draw_spline(self, xy): min(len(xy),4)-1) plt.plot(a, b, color="black") - def draw_circle(self, r, center=(0, 0), circle_fill=False, fc="grey"): + def draw_circle(self, r, center=(0, 0), circle_fill=False, fc="gray"): """ 円描画、引数centerはタプル """ @@ -152,9 +151,9 @@ def draw_line(self, xy_1, xy_2): def axvspan(self, r): """ - 半径rの周りを塗りつぶす関数 + 半径rの周りを塗りつぶす関数,マージン0.1倍 """ - self.ax.axvspan(-r, r, -r, r, color="gray", alpha=0.5) + self.ax.axvspan(-r*1.1, r*1.1, color="gray", alpha=0.5) class Node(object, metaclass=abc.ABCMeta): dir @@ -217,17 +216,10 @@ def draw(self): # 子供それぞれについて中心点を作成して配列に格納 childrens_info.append({'center':(0, -count_r), 'edge':long_child + A0.margin}) count_r += child['height'] + A0.margin - # for i in childrens_info: - # print(i) - # self.head.draw(i) self.head.draw(childrens_info) def show(self): -# print("a0("+self.head.show(self)+")") - if isinstance(self.head, Nil): - return "a0()" - else: - return "a0(" + self.head.show() + ")" + return "a0()" if isinstance(self.head, Nil) else "a0("+self.head.show()+")" class B0(Node): """ @@ -242,7 +234,7 @@ def __init__(self, head, tail): self.r = max(children_length / (2 * math.pi), head.r + high_children + B0.margin) def draw(self): - self.canvas.axvspan(B0.margin) + self.canvas.axvspan(self.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) @@ -268,7 +260,7 @@ class B0_minus(B0): dir = -1 # - 時計回り def show(self): - return "B0-("+self.head.show()+")" + return "B0-("+self.head.show()+","+self.tail.show()+")" class A_Flip(Node): """ @@ -282,8 +274,6 @@ def __init__(self, head): self.occupation = [{'height': self.r, 'width': 0}] # 0: dummy def draw(self, info_dic): # 描画する際に親から与える中心点 - print("here:") - print(info_dic) center = info_dic["center"] edge = info_dic["edge"] self.canvas.draw_circle(self.r, center) @@ -350,7 +340,7 @@ def draw(self, info_dic): self.tail.draw(for_minus_children) def show(self): - print("a2("+self.head.show()+","+self.tail.show()+")") + return "a2("+self.head.show()+","+self.tail.show()+")" class Cons(Node): """ @@ -366,10 +356,7 @@ def draw(self, children_list): self.tail.draw(children_list) def show(self): - if isinstance(self.tail, Nil): - return self.head.show() - else: - return self.head.show()+"."+self.tail.show() + return self.head.show() + ("" if isinstance(self.tail, Nil) else "."+self.tail.show()) class Nil(Node): """ From 3e3c953d5afa7bc2ac1ea2fdad9208d3c4f74dc5 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Sat, 25 Dec 2021 09:13:21 +0900 Subject: [PATCH 08/17] =?UTF-8?q?README.md=E3=82=92=E6=9B=B8=E3=81=8D?= =?UTF-8?q?=E6=8F=9B=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index c1310f6..75bf031 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,11 @@ git clone https://github.com/yokoyama-lab/Visualization-program-of-flow.git sudo apt install python3 ``` -+ Matplotlib をインストール ++ Matplotlib, numpy, scipy, PLY をインストール ``` pip3 install Matplotlib pip3 install numpy pip3 install scipy -``` - -+ PLY をインストール -``` pip3 install PLY ``` @@ -38,5 +34,3 @@ pip3 install PLY ``` echo "a0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | python3 visualize.py ``` - -入力用の木表現が「test.txt」に用意されているので試してみてください. From 88b021715b22e271ddf150c4b413122f0e3dc9b1 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Thu, 5 Mar 2026 23:52:07 +0900 Subject: [PATCH 09/17] @ --- src/visualize/flow.py | 11 ++++------- src/visualize/yacc.py | 3 +-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/visualize/flow.py b/src/visualize/flow.py index e3e5e03..d8c0002 100644 --- a/src/visualize/flow.py +++ b/src/visualize/flow.py @@ -88,7 +88,7 @@ def clear_canvas(self): matplotlibのデータ削除 """ plt.close("all") - plt.cla() + self.ax = plt.axes() plt.axis('off') self.ax.set_aspect('equal') @@ -476,9 +476,6 @@ class B_minus_plus(B_Flip): b-+を扱うクラス """ dir = -1 # - 時計回り - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0], self.r_lw+B_Flip.margin+center[1]-self.r_up-B_Flip.margin), theta=0) - self.canvas.draw_arrow((center[0], center[1]-(self.r_up+self.r_lw+2*B_Flip.margin)), theta=math.pi) def show(self): return "b-+("+self.head.show()+","+self.tail.show()+")" @@ -514,16 +511,16 @@ class Beta_plus(Beta): dir = 1 # + 反時計回り def show(self): - return "B+("+self.head.show()+")" + return "B+{"+self.head.show()+"}" class Beta_minus(Beta): """ beta-を扱うクラス """ - dir = -1 # + 反時計回り + dir = -1 # - 時計回り def show(self): - return "B-("+self.head.show()+")" + return "B-{"+self.head.show()+"}" class C(Node): """ diff --git a/src/visualize/yacc.py b/src/visualize/yacc.py index ea5a8ce..8c1cde0 100644 --- a/src/visualize/yacc.py +++ b/src/visualize/yacc.py @@ -13,7 +13,6 @@ def p_s(p): if p[1] == 'a0': p[0] = flow.A0(p[3]) elif p[1] == 'B0+': p[0] = flow.B0_plus(p[3], p[5]) - print(p[0].show()) elif p[1] == 'B0-': p[0] = flow.B0_minus(p[3], p[5]) elif p[1] == '(': p[0] = p[2] @@ -99,7 +98,7 @@ def p_cs_minus(p): def p_cs_minus1(p): '''cs_minus1 : c_minus - | c_minus '.' cs_minus ''' + | c_minus '.' cs_minus1 ''' if len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) From 09eb758febe00e261ebdbe9d57c5e0e55f934c4b Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Tue, 10 Mar 2026 08:01:00 +0900 Subject: [PATCH 10/17] refactor --- .github/workflows/ci.yml | 37 ++ .gitignore | 11 +- Makefile | 19 + README.md | 57 +- pyproject.toml | 59 +++ src/Makefile | 18 - src/viscot/__init__.py | 3 + src/viscot/cli.py | 102 ++++ src/viscot/core/__init__.py | 69 +++ src/viscot/core/canvas.py | 177 +++++++ src/viscot/core/config.py | 58 +++ src/viscot/core/lexer.py | 68 +++ src/viscot/core/nodes.py | 774 ++++++++++++++++++++++++++++ src/viscot/core/parser.py | 192 +++++++ src/viscot/evaluation/__init__.py | 10 + src/viscot/evaluation/benchmark.py | 91 ++++ src/viscot/evaluation/comparison.py | 113 ++++ src/viscot/evaluation/report.py | 129 +++++ src/viscot/layout/__init__.py | 11 + src/viscot/layout/occupation.py | 96 ++++ src/viscot/layout/optimizer.py | 87 ++++ src/viscot/metrics/__init__.py | 13 + src/viscot/metrics/composite.py | 54 ++ src/viscot/metrics/overlap.py | 127 +++++ src/viscot/metrics/sampling.py | 39 ++ src/viscot/metrics/smoothness.py | 85 +++ src/viscot/metrics/spacing.py | 60 +++ src/visualize.py | 52 -- src/visualize/__init__.py | 1 - src/visualize/flow.py | 599 --------------------- src/visualize/lex.py | 51 -- src/visualize/yacc.py | 120 ----- tests/conftest.py | 57 ++ tests/test_canvas.py | 125 +++++ tests/test_layout.py | 82 +++ tests/test_metrics.py | 172 +++++++ tests/test_nodes.py | 200 +++++++ tests/test_parser.py | 192 +++++++ 38 files changed, 3349 insertions(+), 861 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Makefile create mode 100644 pyproject.toml delete mode 100644 src/Makefile create mode 100644 src/viscot/__init__.py create mode 100644 src/viscot/cli.py create mode 100644 src/viscot/core/__init__.py create mode 100644 src/viscot/core/canvas.py create mode 100644 src/viscot/core/config.py create mode 100644 src/viscot/core/lexer.py create mode 100644 src/viscot/core/nodes.py create mode 100644 src/viscot/core/parser.py create mode 100644 src/viscot/evaluation/__init__.py create mode 100644 src/viscot/evaluation/benchmark.py create mode 100644 src/viscot/evaluation/comparison.py create mode 100644 src/viscot/evaluation/report.py create mode 100644 src/viscot/layout/__init__.py create mode 100644 src/viscot/layout/occupation.py create mode 100644 src/viscot/layout/optimizer.py create mode 100644 src/viscot/metrics/__init__.py create mode 100644 src/viscot/metrics/composite.py create mode 100644 src/viscot/metrics/overlap.py create mode 100644 src/viscot/metrics/sampling.py create mode 100644 src/viscot/metrics/smoothness.py create mode 100644 src/viscot/metrics/spacing.py delete mode 100644 src/visualize.py delete mode 100644 src/visualize/__init__.py delete mode 100644 src/visualize/flow.py delete mode 100644 src/visualize/lex.py delete mode 100644 src/visualize/yacc.py create mode 100644 tests/conftest.py create mode 100644 tests/test_canvas.py create mode 100644 tests/test_layout.py create mode 100644 tests/test_metrics.py create mode 100644 tests/test_nodes.py create mode 100644 tests/test_parser.py 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..f6a447c --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +.PHONY: all test clean install lint + +all: test + +install: + pip install -e ".[dev]" --break-system-packages + +test: + python3 -m pytest tests/ -q + +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 diff --git a/README.md b/README.md index 75bf031..28c2e80 100644 --- a/README.md +++ b/README.md @@ -4,33 +4,54 @@ 想定されている入力は,Consで繋がれた木を同一の高さとして見た場合の,深さが3までの木です. ## Requirements -+ Python3 (>= 3.8.5) -+ Matplotlib (>= 3.3.1) -+ numpy (>=1.19.1) -+ scipy (>=1.5.2) -+ PLY (>= 3.10) ++ Python3 (>= 3.10) ++ matplotlib (>= 3.7) ++ numpy (>= 1.24) ++ scipy (>= 1.10) ++ PLY (>= 3.11) + +## インストール + +``` +git clone https://github.com/yokoyama-lab/visCOT.git +cd visCOT +pip install -e . --break-system-packages +``` + +開発用の依存パッケージ(pytest, ruff, mypy)も合わせてインストールする場合: -## Linux Ubuntuにてインストール例 -+ 本プログラムをダウンロード ``` -git clone https://github.com/yokoyama-lab/Visualization-program-of-flow.git +pip install -e ".[dev]" --break-system-packages ``` -+ Python をインストール +## 実行例 + +COT木表現を標準入力から与えて可視化する: + ``` -sudo apt install python3 +echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot ``` -+ Matplotlib, numpy, scipy, PLY をインストール +ファイルに保存する場合: + ``` -pip3 install Matplotlib -pip3 install numpy -pip3 install scipy -pip3 install PLY +echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot -o output.png ``` -## 実行例 -プログラムを起動し,木表現を入力する. +対話モードで起動する場合: + +``` +viscot -i +``` + +## テスト + +``` +make test +``` + +## Lint + ``` -echo "a0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | python3 visualize.py +make lint ``` 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/Makefile b/src/Makefile deleted file mode 100644 index 697076c..0000000 --- a/src/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -all: - python3 visualize.py - -clean: - $(RM) -rf visualize/__pycache__ visualize/parser.out visualize/parsetab.py - -test: - echo "a0()" | python3 visualize.py - echo "a0(a+(l+))" | python3 visualize.py - echo "a0(a+(l+).a+(l+))" | python3 visualize.py - echo "B0+(l+,)" | python3 visualize.py - echo "B0+(l+,c-(l-,))" | python3 visualize.py - echo "B0+(l+,c-(l-,).c-(l-,))" | python3 visualize.py - echo "B0-(l-,)" | python3 visualize.py - echo "B0+(b+-(l+,l-),c-(l-,).c-(l-,).c-(l-,))" | python3 visualize.py - echo "B0+(b+-(l+,l-),c-(B-{},).c-(l-,).c-(l-,))" | python3 visualize.py - echo "B0-(b-+(b-+(l-,l+),B+{}),c+(B+{},).c+(l+,).c+(l+,))" | python3 visualize.py - echo "B0+(b+-(b+-(l+,l-),B-{}),c-(B-{},).c-(l-,).c-(l-,))" | python3 visualize.py 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..e52d73a --- /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)." + ) + 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..24a2e79 --- /dev/null +++ b/src/viscot/core/__init__.py @@ -0,0 +1,69 @@ +"""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() + 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..dfc9a84 --- /dev/null +++ b/src/viscot/core/canvas.py @@ -0,0 +1,177 @@ +"""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) + + +@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 show_canvas(self) -> None: + self.fig.tight_layout() + plt.show() + + def save_canvas(self, file_name: str, dpi: int = 150) -> None: + self.fig.tight_layout() + self.fig.savefig(file_name, dpi=dpi) + + 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 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, + ) + self.ax.plot(a, b, color="black") + pts = np.column_stack([a, b]) + self._drawn_elements.append(DrawnSpline(points=pts)) + + 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..6542336 --- /dev/null +++ b/src/viscot/core/config.py @@ -0,0 +1,58 @@ +"""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 = 0.5 # extra arc-length per unit height + c_min_child_height: float = 0.3 # fallback height when C has no children + + # B0 first-child offset + b0_first_child_offset: float = 0.3 + + # 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..cc8a2db --- /dev/null +++ b/src/viscot/core/nodes.py @@ -0,0 +1,774 @@ +"""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 の最初の子は親円周上で少しオフセットして配置開始 + length += config.b0_first_child_offset + share = margin / len(children) # circumference / n_children + for _ in children: + result.append( + DrawContext( + length=length, + parent_r=parent_r, + parent_center=parent_center, + parent_type=parent_type, + ) + ) + # Advance by share to distribute children evenly around the circle + length += share + 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) + children_length = c_list_circ_length(tail.occupation, cfg.b0_margin) + self.r = max( + children_length / (2 * math.pi), head.r + high_children + cfg.b0_margin + ) + + 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 = self.len_of_circ / (2 * math.pi) + 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, + config=cfg, + ) + for_minus_children = make_list_for_c( + self.tail.occupation, + self.center_r, + center, + False, + cfg.a2_margin, + parent_length=self.len_of_circ / 2, + 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) + self.high: float = 2 * head.r + self.high_children + cfg.c_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. + self.effective_extent: float = max( + self.children_length, + self.high * cfg.c_height_spacing_factor, + ) + bottom_length = max(head.r * 2, self.effective_extent) + 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) + b_center = theta_point( + high_theta, + center_r + sign * (high_children + cfg.c_circ_margin + self.head.r), + center, + ) + self.plot_arrow(high_point, high_theta) + if self.head.r != 0: + b_r_theta = math.pi - (math.pi / 2 + high_theta) + b_r_center = theta_point( + -b_r_theta, self.head.r + cfg.c_circ_margin, b_center + ) + b_l_center = theta_point( + math.pi - b_r_theta, self.head.r + cfg.c_circ_margin, b_center + ) + if self.head.r * 2 < self.children_length / 2: + self._cv.draw_spline( + [start_point, high_point, end_point] + ) + else: + self._cv.draw_spline( + [start_point, b_r_center, high_point, b_l_center, 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 + padding = (self.effective_extent - self.children_length) / 2 + for_children = make_list_for_c( + self.tail.occupation, + center_r, + center, + bool_b0, + cfg.c_margin / 1.5, + 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, + *args: Any, + **kwargs: Any, + ) -> None: + self._cv.draw_arrow( + high_point, + high_theta + math.pi * 0.5 + self.dir2rad(), + ) + + 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/visualize.py b/src/visualize.py deleted file mode 100644 index 35c594a..0000000 --- a/src/visualize.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Visualization program of tree representation of structurally stable incompressible flow in two dimensional multiply-connected domain -""" -# -*- coding: utf-8 -*- - -import os -from visualize import flow, yacc -from visualize.flow import Canvas -import argparse - -""" -入力した木表現に対する流線を表示 -""" -def main(): - parser = argparse.ArgumentParser(description='Visualize COT representation.') - parser.add_argument('-i', '--interactive', help='interactive mode', action='store_true') - parser.add_argument('-o', '--output', help='specify an output file (.png).') - args = parser.parse_args() - # print(args) - - canvas = Canvas() - if args.interactive: - while True: - try: - s = input('>>> ') - object = yacc.parser.parse(s) - object.set_canvas(canvas) - object.draw() - type = input('Select (save/show):') - if type == "save": - filename = s + '.png' - canvas.save_canvas(filename) - elif type == "show": - canvas.show_canvas() - else: - pass - canvas.clear_canvas() - except AttributeError: - print("please type correct syntax.") - except EOFError: - break - else: - object = yacc.parser.parse(input()) - object.set_canvas(canvas) - object.draw() - if args.output is None: - canvas.show_canvas() - else: - canvas.save_canvas(args.output) - -if __name__ == "__main__": - main() diff --git a/src/visualize/__init__.py b/src/visualize/__init__.py deleted file mode 100644 index 6a1de63..0000000 --- a/src/visualize/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# パッケージを示す空ファイル \ No newline at end of file diff --git a/src/visualize/flow.py b/src/visualize/flow.py deleted file mode 100644 index d8c0002..0000000 --- a/src/visualize/flow.py +++ /dev/null @@ -1,599 +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_highest(children_ocu_list): - """ - C系の配列[{'height': h0, 'width': w0},...]から最も大きい高さを求める関数 - """ - return max(map(lambda x: x['height'], children_ocu_list)) - -def c_list_circ_length(children_ocu_list, margin): - """ - C系の配列[(self.high,self.bottom_length),...]から円周の長さを求める関数 - """ - # marginはc同士の間に空けたいスペース - circ_length = 0 # 円周を保存する変数 - widest_child = 0 # 最も長い子供の長さを保存する変数 - for child in children_ocu_list: - circ_length += child['width'] + margin # スペース分円周を伸ばす - widest_child = max(widest_child, child['width']) - # もし円周の半分以上の長さを持つ子供がいれば、円周の長さをその子供に合わせる - # (C系とb系が重なることを避ける) - return max(circ_length, widest_child*2) - -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['width'] < length: - length += 1.5 - else: - length += (margin/len(children_ocu_list))-child['width']+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['width'] - 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): - """ - 作成された画像を保存 - """ - plt.tight_layout() - plt.savefig(file_name) - - def clear_canvas(self): - """ - matplotlibのデータ削除 - """ - plt.close("all") - self.ax = plt.axes() - 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座標のタプルのリスト - """ - assert len(xy) >= 3 - a, b = self.spline([x for [x,_] in xy], - [y for [_,y] in xy], - 100, - min(len(xy),4)-1) - plt.plot(a, b, color="black") - - def draw_circle(self, r, center=(0, 0), circle_fill=False, fc="gray"): - """ - 円描画、引数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の周りを塗りつぶす関数,マージン0.1倍 - """ - self.ax.axvspan(-r*1.1, r*1.1, color="gray", alpha=0.5) - -class Node(object, metaclass=abc.ABCMeta): - dir - - @abc.abstractmethod - def __init__(self, head = None, tail = None, canvas = None): - self.canvas = canvas - self.head = head - self.tail = tail - - 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) - - def dir2rad(self): - return (self.dir + 1.0) * math.pi / 2.0 - - def show(self): - pass - -class A0(Node): - """ - A0を扱うクラス - """ - margin = 0.5 - - def __init__(self, head): # 子の半径を定義 - super().__init__(head) - - def draw(self): - childrens_info = [] # drawで引数として渡す - if isinstance(self.head, Nil): - # 一様流を書く - self.canvas.draw_line((-1, 0), (1, 0)) - self.canvas.draw_arrow((0, 0), math.pi) - else: - count_r = 0 - long_child = c_list_highest(self.head.occupation) # 子供達の中で一番長いrを求める - for child in self.head.occupation: - # 次の子供の中心点をy軸に-r*2して繰り返す - count_r += child['height'] + A0.margin - # 子供それぞれについて中心点を作成して配列に格納 - childrens_info.append({'center':(0, -count_r), 'edge':long_child + A0.margin}) - count_r += child['height'] + A0.margin - self.head.draw(childrens_info) - - def show(self): - return "a0()" if isinstance(self.head, Nil) else "a0("+self.head.show()+")" - -class B0(Node): - """ - B0+,B0-の抽象クラス - """ - margin = 0.5 - - def __init__(self, head, tail): - super().__init__(head, tail) - high_children = c_list_highest(tail.occupation) - children_length = c_list_circ_length(tail.occupation, B0.margin) - self.r = max(children_length / (2 * math.pi), head.r + high_children + B0.margin) - - def draw(self): - self.canvas.axvspan(self.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) - - def plot_arrow(self): - self.canvas.draw_arrow((self.r, 0), math.pi*0.5+self.dir2rad()) - -class B0_plus(B0): - """ - B0+を扱うクラス - """ - dir = 1 # + 反時計回り - - def show(self): - return "B0+("+self.head.show()+","+self.tail.show()+")" - -class B0_minus(B0): - """ - B0-を扱うクラス - """ - dir = -1 # - 時計回り - - def show(self): - return "B0-("+self.head.show()+","+self.tail.show()+")" - -class A_Flip(Node): - """ - a+,a-の抽象クラス - """ - margin = 0.5 # 子の専有領域と親の領域の余白 - - def __init__(self, head): - super().__init__(head) - self.r = head.r + A_Flip.margin - self.occupation = [{'height': self.r, 'width': 0}] # 0: dummy - - 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) - - def plot_arrow(self, center, edge): - self.canvas.draw_point((center[0], center[1]-self.r*self.dir)) - self.canvas.draw_arrow((center[0]-self.r, center[1]), theta=math.pi*1.5+self.dir2rad()) - self.canvas.draw_arrow((center[0]+self.r, center[1]), theta=math.pi*0.5+self.dir2rad()) - self.canvas.draw_line((-edge, center[1]-self.r*self.dir), (edge, center[1]-self.r*self.dir)) - self.canvas.draw_arrow((-edge/2, center[1]-self.r*self.dir), math.pi+self.dir2rad()) - self.canvas.draw_arrow(( edge/2, center[1]-self.r*self.dir), math.pi+self.dir2rad()) - -class A_plus(A_Flip): - """ - a+を扱うクラス - """ - dir = 1 # + 反時計回り - - def show(self): - return "a+("+self.head.show()+")" - -class A_minus(A_Flip): - """ - a-を扱うクラス - """ - dir = -1 # - 時計回り - - def show(self): - return "a-("+self.head.show()+")" - -class A2(Node): - """ - a2を扱うクラス - """ - margin = 0.5 # 子同士のスペースの定義 - - def __init__(self, head, tail): - super().__init__(head, tail) - self.high = max(c_list_highest(head.occupation), c_list_highest(tail.occupation)) - len_of_plus_circ = c_list_circ_length(head.occupation, A2.margin) + A2.margin # plus回りの長さ - len_of_minus_circ = c_list_circ_length(tail.occupation, A2.margin) + A2.margin # minus回りの長さ - self.len_of_circ = max(len_of_plus_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 = [{'height': self.r, 'width': 0}] # 0: dummy - - 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, A2.margin) - for_minus_children = make_list_for_c(self.tail.occupation, self.center_r, center, False, A2.margin, parent_length=self.len_of_circ/2) - self.head.draw(for_plus_children) - self.tail.draw(for_minus_children) - - def show(self): - return "a2("+self.head.show()+","+self.tail.show()+")" - -class Cons(Node): - """ - consを扱うクラス - """ - def __init__(self, head, tail): - super().__init__(head, tail) - self.occupation = [s for s in head.occupation + tail.occupation if s != {'height': 0, 'width': 0}] - - def draw(self, children_list): - self.head.draw(children_list.pop(0)) - if len(children_list) > 0: - self.tail.draw(children_list) - - def show(self): - return self.head.show() + ("" if isinstance(self.tail, Nil) else "."+self.tail.show()) - -class Nil(Node): - """ - nilを扱うクラス - """ - def __init__(self): - super().__init__() - self.occupation = [{'height': 0, 'width': 0}] # 0: dummy - - def show(self): - return "" - -class Leaf(Node): - """ - l+,l-の抽象クラス - """ - def __init__(self): - super().__init__() - self.r = 0 - -class Leaf_plus(Leaf): - """ - l+を扱うクラス - """ - dir = 1 - - def show(self): - return "l+" - -class Leaf_minus(Leaf): - """ - l-を扱うクラス - """ - dir = -1 - - def show(self): - return "l-" - -class B_Evc(Node): - """ - b++,b--の抽象クラス - """ - margin = 0.5 # 子の専有領域と親の領域の余白 - def __init__(self, head, tail): # headは上の円の半径、tailは下の円の半径 - super().__init__(head, tail) - self.r_up = head.r # 上の図の占有領域(半径) - self.r_lw = tail.r # 下の図の占有領域(半径) - self.r = (2 * self.r_up + 2 * self.r_lw + 4 * B_Evc.margin) / 2 # 全体の占有領域(半径) - - def draw(self, center=(0, 0)): # 描画する際に親から与える中心点 - self.canvas.draw_point((center[0], self.r_lw+center[1]-self.r_up)) # 2つの円の交点 - self.canvas.draw_circle(self.r_up+B_Evc.margin, (center[0], self.r_lw+B_Evc.margin+center[1])) # 上の円 - self.canvas.draw_circle(self.r_lw+B_Evc.margin, (center[0], -self.r_up-B_Evc.margin+center[1])) # 下の円 - self.plot_arrow(center) - self.head.draw((center[0], self.r_lw+B_Evc.margin+center[1])) - self.tail.draw((center[0], -self.r_up-B_Evc.margin+center[1])) - - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0], self.r_lw+2*self.margin+center[1]+self.r_up), self.dir2rad()) # 上の円の矢印 - self.canvas.draw_arrow((center[0], -self.r_up-2*self.margin+center[1]-self.r_lw), math.pi - self.dir2rad()) # 下の円の矢印 - -class B_plus_plus(B_Evc): - """ - b++を扱うクラス - """ - dir = 1 # + 反時計回り - - def show(self): - return "b++("+self.head.show()+","+self.tail.show()+")" - -class B_minus_minus(B_Evc): - """ - b--を扱うクラス - """ - dir = -1 # - 時計回り - - def show(self): - return "b--("+self.head.show()+","+self.tail.show()+")" - -class B_Flip(Node): - """ - b+-,b-+の抽象クラス - """ - margin = 0.5 # 子の専有領域と親の領域の余白 - - def __init__(self, head, tail): - super().__init__() - self.head = head - self.tail = tail - self.r_up = head.r # 上の図の占有領域(半径) - self.r_lw = tail.r # 下の図の占有領域(半径) - self.r = (2 * self.r_up + 2 * self.r_lw + 4 * B_Flip.margin) / 2 - - def draw(self, center=(0, 0)): # 描画する際に親から与える中心点 - self.canvas.draw_circle(self.r_up+B_Flip.margin, (center[0], self.r_lw+B_Flip.margin+center[1])) - self.canvas.draw_circle(self.r_up+self.r_lw+2*B_Flip.margin, center) - self.canvas.draw_point((center[0], self.r_lw+B_Flip.margin+center[1]+self.r_up+B_Flip.margin)) - self.plot_arrow(center) - self.head.draw((center[0], self.r_lw+B_Flip.margin+center[1])) - self.tail.draw((center[0], -self.r_up-B_Flip.margin+center[1])) - - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0], self.r_lw+B_Flip.margin+center[1]-self.r_up-B_Flip.margin), theta=self.dir2rad()) - self.canvas.draw_arrow((center[0], center[1]-(self.r_up+self.r_lw+2*B_Flip.margin)), theta=math.pi-self.dir2rad()) - -class B_plus_minus(B_Flip): - """ - b+-を扱うクラス - """ - dir = 1 # + 反時計回り - - def show(self): - return "b+-("+self.head.show()+","+self.tail.show()+")" - -class B_minus_plus(B_Flip): - """ - b-+を扱うクラス - """ - dir = -1 # - 時計回り - - def show(self): - return "b-+("+self.head.show()+","+self.tail.show()+")" - -class Beta(Node): - """ - beta+,beta-の抽象クラス - """ - margin = 0.5 # 要素の両脇に作るスペースの大きさ - - def __init__(self, head): - super().__init__(head) - high_children = c_list_highest(head.occupation) - children_length = c_list_circ_length(head.occupation, Beta.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, Beta.margin) - self.plot_arrow(center) - self.head.draw(for_children) - - def plot_arrow(self, center): - self.canvas.draw_arrow((center[0]+self.center_r, center[1]), math.pi*0.5+self.dir2rad()) - -class Beta_plus(Beta): - """ - beta+を扱うクラス - """ - dir = 1 # + 反時計回り - - def show(self): - return "B+{"+self.head.show()+"}" - -class Beta_minus(Beta): - """ - beta-を扱うクラス - """ - dir = -1 # - 時計回り - - def show(self): - return "B-{"+self.head.show()+"}" - -class C(Node): - """ - c+,c-の抽象クラス - """ - margin = 1 # c系の要素の両脇に作るスペースの大きさ - circ_margin = 0.5 # 子のb系の要素と親の間の距離 - - def __init__(self, head, tail): - super().__init__(head, tail) - self.high_children = c_list_highest(tail.occupation) - self.children_length = c_list_circ_length(tail.occupation, C.margin) - bottom_length = max(head.r*2, self.children_length) - self.high = 2 * head.r + self.high_children + C.margin - if (self.head.r == 0) and (len(self.tail.occupation) != 1): - self.high += len(self.tail.occupation) - self.occupation = [{'height': self.high, 'width': 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-C.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+C.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+C.circ_margin, b_center) - # 180度の点 - b_l_center = theta_point(math.pi-b_r_theta, self.head.r+C.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, C.margin/1.5, parent_length=length) - self.head.draw(b_center) - self.tail.draw(for_children) - - 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)+self.dir2rad()) - -class C_plus(C): - """ - c+を扱うクラス - """ - dir = 1 # + 反時計回り - - def show(self): - return "c+("+self.head.show()+","+self.tail.show()+")" - -class C_minus(C): - """ - c-を扱うクラス - """ - dir = -1 # - 時計回り - - def show(self): - return "c-("+self.head.show()+","+self.tail.show()+")" diff --git a/src/visualize/lex.py b/src/visualize/lex.py deleted file mode 100644 index ada6534..0000000 --- a/src/visualize/lex.py +++ /dev/null @@ -1,51 +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_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'\#.*' # コメントを無視する - -# 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/visualize/yacc.py b/src/visualize/yacc.py deleted file mode 100644 index 8c1cde0..0000000 --- a/src/visualize/yacc.py +++ /dev/null @@ -1,120 +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 ')' - | '(' s ')' ''' - if p[1] == 'a0': p[0] = flow.A0(p[3]) - elif p[1] == 'B0+': - p[0] = flow.B0_plus(p[3], p[5]) - elif p[1] == 'B0-': p[0] = flow.B0_minus(p[3], p[5]) - elif p[1] == '(': p[0] = p[2] - -def p_empty(p): - 'empty :' - pass - -def p_as(p): - '''as : empty - | a - | a '.' as1 ''' - if p[1] == None: p[0] = flow.Nil() - elif p[1] != None and len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) - elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) - -def p_as1(p): - '''as1 : a - | a '.' as1 ''' - if len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) - elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) - -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_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] = flow.Leaf_plus() - 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] == 'B+': p[0] = flow.Beta_plus(p[3]) - -def p_b_minus(p): - '''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] = flow.Leaf_minus() - 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] == 'B-': 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 : empty - | c_plus - | c_plus '.' cs_plus1 - | '(' cs_plus ')' ''' - if p[1] == None: p[0] = flow.Nil() - elif p[1] != None and len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) - elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) - elif p[1] == '(': p[0] = p[2] - -def p_cs_plus1(p): - '''cs_plus1 : c_plus - | c_plus '.' cs_plus1 ''' - if len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) - elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) - -def p_cs_minus(p): - '''cs_minus : empty - | c_minus - | c_minus '.' cs_minus1 - | '(' cs_minus ')' ''' - if p[1] == None: p[0] = flow.Nil() - elif p[1] != None and len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) - elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) - elif p[1] == '(': p[0] = p[2] - -def p_cs_minus1(p): - '''cs_minus1 : c_minus - | c_minus '.' cs_minus1 ''' - if len(p) == 2: p[0] = flow.Cons(p[1], flow.Nil()) - elif p[2] == '.': p[0] = flow.Cons(p[1], p[3]) - -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/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..967e58e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,57 @@ +"""Shared test fixtures.""" + +from __future__ import annotations + +import matplotlib +import pytest + +matplotlib.use("Agg") # non-interactive backend for tests + +from viscot.core.canvas import Canvas +from viscot.core.parser import parse + + +@pytest.fixture +def canvas() -> Canvas: + """Provide a fresh Canvas.""" + c = Canvas() + yield c + c.close() + + +@pytest.fixture +def render(): + """Factory fixture: parse expression, set canvas, draw, return (tree, canvas). + + Automatically closes all created canvases to prevent figure leaks. + """ + canvases: list[Canvas] = [] + + def _render(expression: str) -> tuple: + canvas = Canvas() + canvases.append(canvas) + tree = parse(expression) + tree.set_canvas(canvas) + tree.draw() + 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-,))", +] diff --git a/tests/test_canvas.py b/tests/test_canvas.py new file mode 100644 index 0000000..afedb04 --- /dev/null +++ b/tests/test_canvas.py @@ -0,0 +1,125 @@ +"""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 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..676ed75 --- /dev/null +++ b/tests/test_nodes.py @@ -0,0 +1,200 @@ +"""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.""" + r_default = parse("B0+(l+,)").r + r_big = parse("B0+(l+,)", config=LayoutConfig(b0_margin=2.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 From 6d479b27588bc36041234b3f08722e3ea36d3cc8 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Tue, 10 Mar 2026 07:14:42 +0000 Subject: [PATCH 11/17] =?UTF-8?q?c+=E3=81=AE=E4=BD=8D=E7=BD=AE=E3=81=8C?= =?UTF-8?q?=E7=9C=9F=E4=B8=8A=E3=81=AB=E3=81=AA=E3=81=84=E3=80=81=E5=B7=A6?= =?UTF-8?q?=E3=81=AB=E5=AF=84=E3=82=8A=E3=81=99=E3=81=8E=E3=82=8B=20#6=20?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/viscot/core/nodes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/viscot/core/nodes.py b/src/viscot/core/nodes.py index cc8a2db..afcc52b 100644 --- a/src/viscot/core/nodes.py +++ b/src/viscot/core/nodes.py @@ -397,6 +397,7 @@ def draw(self, info: DrawContext | None = None, *args: Any, **kwargs: Any) -> No 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( @@ -405,7 +406,6 @@ def draw(self, info: DrawContext | None = None, *args: Any, **kwargs: Any) -> No center, False, cfg.a2_margin, - parent_length=self.len_of_circ / 2, config=cfg, ) self.head.draw(for_plus_children) @@ -763,12 +763,12 @@ def show(self) -> str: class C_plus(C): """c+ node.""" - dir = 1 + dir = -1 _show_prefix = "c+" class C_minus(C): """c- node.""" - dir = -1 + dir = 1 _show_prefix = "c-" From d2322ef56d19a70fd92cf6da94af4904fc48213b Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Tue, 10 Mar 2026 07:20:36 +0000 Subject: [PATCH 12/17] =?UTF-8?q?=E5=87=BA=E5=8A=9B=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E5=BD=A2=E5=BC=8F=E3=81=ABsvg,=20pdf,=20eps=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++++++- src/viscot/cli.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28c2e80..970c7a5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # visCOT: visualizing the tree representations of structurally stable incompressible flows in two dimensional multiply-connected domains このプログラムは2次元多重連結領域内における構造安定な非圧縮流れの木表現の入力に対して,同一のトポロジーを表す2次元上の図を作図するものです. -想定されている入力は,Consで繋がれた木を同一の高さとして見た場合の,深さが3までの木です. +想定されている入力は,Consで繋がれた木を同一の高さとして見た場合のCOT木表現です.深さに制限はなく,再帰的に任意の深さの木を可視化できます. ## Requirements + Python3 (>= 3.10) @@ -38,6 +38,14 @@ echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot -o output.png ``` +ベクター形式(SVG, PDF, EPS)で出力する場合: + +``` +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 +echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot -o output.eps +``` + 対話モードで起動する場合: ``` diff --git a/src/viscot/cli.py b/src/viscot/cli.py index e52d73a..2d84ac7 100644 --- a/src/viscot/cli.py +++ b/src/viscot/cli.py @@ -28,7 +28,7 @@ def main() -> None: "-i", "--interactive", help="interactive mode", action="store_true" ) arg_parser.add_argument( - "-o", "--output", help="specify an output file (.png, .pdf, .svg)." + "-o", "--output", help="specify an output file (.png, .pdf, .svg, .eps)." ) arg_parser.add_argument( "-f", "--file", From cd3fa7eba7146f158ae757fe281c9a1eaba730f7 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Wed, 1 Apr 2026 07:46:05 +0900 Subject: [PATCH 13/17] =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E6=9C=80=E9=81=A9=E5=8C=96:=20=E3=82=B9=E3=83=97?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=B3=E4=BA=A4=E5=B7=AE=E3=83=BB=E9=9A=9C?= =?UTF-8?q?=E5=AE=B3=E7=89=A9=E8=B2=AB=E9=80=9A=E3=81=AE=E8=A7=A3=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cノードのスプライン描画における複数の問題を修正: - 占有幅に高さ比例のクリアランスを追加し、隣接スプラインの交差を防止 - B0半径に内側最小半径制約を追加(内向きスプラインの収束防止) - B0直下の子配置を高さ比例の重み付け配分に変更 - 子C開始位置に最小paddingを導入(親Cとの近接交差を解消) - B0の子をCircle全周に均等配置 - Cのhigh_pointをhead bノードの外側に保証 - 事後補正(avoid_obstacles)でスプラインの障害物貫通を解消 - a2の最小center_rを1.0に設定 ノード1〜5の全有効COT式109種を網羅テスト(交差0件)。 102ノードのストレステスト式でも交差0件。 テスト349件全通過。 --- Makefile | 10 ++- src/viscot/core/__init__.py | 1 + src/viscot/core/canvas.py | 79 +++++++++++++++++++-- src/viscot/core/config.py | 5 +- src/viscot/core/nodes.py | 121 ++++++++++++++++++++++++++------- tests/conftest.py | 132 +++++++++++++++++++++++++++++++++++- tests/test_canvas.py | 127 ++++++++++++++++++++++++++++++++++ tests/test_nodes.py | 5 +- 8 files changed, 444 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index f6a447c..7c140f7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all test clean install lint +.PHONY: all test clean install lint svg all: test @@ -8,6 +8,13 @@ install: 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/ @@ -17,3 +24,4 @@ clean: $(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/src/viscot/core/__init__.py b/src/viscot/core/__init__.py index 24a2e79..108734d 100644 --- a/src/viscot/core/__init__.py +++ b/src/viscot/core/__init__.py @@ -50,6 +50,7 @@ def render_expression(expression: str, config: LayoutConfig | None = None) -> Ca tree = parse(expression, config=config) tree.set_canvas(canvas) tree.draw() + canvas.avoid_obstacles() return canvas diff --git a/src/viscot/core/canvas.py b/src/viscot/core/canvas.py index dfc9a84..7425804 100644 --- a/src/viscot/core/canvas.py +++ b/src/viscot/core/canvas.py @@ -25,6 +25,7 @@ class DrawnCircle: @dataclass class DrawnSpline: points: np.ndarray # (N, 2) + line: object = None # matplotlib Line2D reference for updating @dataclass @@ -65,13 +66,24 @@ def _init_figure(self) -> None: 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.fig.tight_layout() + self._fit_figure() plt.show() - def save_canvas(self, file_name: str, dpi: int = 150) -> None: - self.fig.tight_layout() - self.fig.savefig(file_name, dpi=dpi) + 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.""" @@ -84,6 +96,61 @@ def __enter__(self) -> Canvas: 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() @@ -114,9 +181,9 @@ def draw_spline(self, xy: list[list[float] | tuple[float, float]]) -> None: num_points, min(len(xy), 4) - 1, ) - self.ax.plot(a, b, color="black") + lines = self.ax.plot(a, b, color="black") pts = np.column_stack([a, b]) - self._drawn_elements.append(DrawnSpline(points=pts)) + self._drawn_elements.append(DrawnSpline(points=pts, line=lines[0])) def draw_circle( self, diff --git a/src/viscot/core/config.py b/src/viscot/core/config.py index 6542336..424b4ea 100644 --- a/src/viscot/core/config.py +++ b/src/viscot/core/config.py @@ -34,11 +34,14 @@ class LayoutConfig: # C (c+, c-) c_margin: float = 1.0 c_circ_margin: float = 0.5 - c_height_spacing_factor: float = 0.5 # extra arc-length per unit height + 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 diff --git a/src/viscot/core/nodes.py b/src/viscot/core/nodes.py index afcc52b..32c34e2 100644 --- a/src/viscot/core/nodes.py +++ b/src/viscot/core/nodes.py @@ -90,10 +90,14 @@ def make_list_for_c( result: list[DrawContext] = [] length = parent_length if parent_type and first_child: - # B0 の最初の子は親円周上で少しオフセットして配置開始 - length += config.b0_first_child_offset - share = margin / len(children) # circumference / n_children - for _ in children: + # 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, @@ -102,8 +106,7 @@ def make_list_for_c( parent_type=parent_type, ) ) - # Advance by share to distribute children evenly around the circle - length += share + length += child.width + gap / 2 # width + half-gap after else: for child in children: length += margin @@ -256,9 +259,16 @@ def __init__(self, head: Node, tail: Node) -> None: super().__init__(head, tail) cfg = self._config high_children = c_list_highest(tail.occupation) - children_length = c_list_circ_length(tail.occupation, cfg.b0_margin) + # 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 + children_length / (2 * math.pi), + head.r + high_children + cfg.b0_margin, + high_children + min_inner, ) def draw(self, *args: Any, **kwargs: Any) -> None: @@ -378,7 +388,7 @@ def __init__(self, head: Node, tail: Node) -> None: 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 = self.len_of_circ / (2 * math.pi) + 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)] @@ -671,16 +681,32 @@ def __init__(self, head: Node, tail: Node) -> None: 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) - self.high: float = 2 * head.r + self.high_children + 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, ) - bottom_length = max(head.r * 2, self.effective_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) ] @@ -704,40 +730,87 @@ def draw(self, c_data: DrawContext | None = None, *args: Any, **kwargs: Any) -> 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 * (high_children + cfg.c_circ_margin + self.head.r), + center_r + sign * b_offset, center, ) self.plot_arrow(high_point, high_theta) - if self.head.r != 0: - b_r_theta = math.pi - (math.pi / 2 + high_theta) - b_r_center = theta_point( - -b_r_theta, self.head.r + cfg.c_circ_margin, b_center + + # 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, ) - b_l_center = theta_point( - math.pi - b_r_theta, self.head.r + cfg.c_circ_margin, b_center + guide_end = theta_point( + end_theta - (end_theta - high_theta) * nudge_frac, + nudge_r, center, ) - if self.head.r * 2 < self.children_length / 2: + + 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, high_point, end_point] + [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: - self._cv.draw_spline([start_point, high_point, end_point]) + 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 - padding = (self.effective_extent - self.children_length) / 2 + # 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, - cfg.c_margin / 1.5, + adaptive_margin, parent_length=length + padding, config=cfg, ) diff --git a/tests/conftest.py b/tests/conftest.py index 967e58e..277e51d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,9 @@ from __future__ import annotations +import re +from pathlib import Path + import matplotlib import pytest @@ -11,6 +14,38 @@ 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.""" @@ -19,11 +54,17 @@ def canvas() -> Canvas: 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(): +def render(save_image_dir: Path | None, image_format: str): """Factory fixture: parse expression, set canvas, draw, return (tree, canvas). - Automatically closes all created canvases to prevent figure leaks. + If --save-images is given, each rendered expression is saved in the + format specified by --image-format (default: png). """ canvases: list[Canvas] = [] @@ -33,6 +74,9 @@ def _render(expression: str) -> tuple: 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 @@ -55,3 +99,87 @@ def _render(expression: str) -> tuple: "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 index afedb04..626c68c 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -123,3 +123,130 @@ class TestFullRendering: 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_nodes.py b/tests/test_nodes.py index 676ed75..93b0bbe 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -171,8 +171,9 @@ def test_config_restored_after_context(self) -> None: def test_parse_with_config_affects_b0(self) -> None: """B0 radius should change with b0_margin config.""" - r_default = parse("B0+(l+,)").r - r_big = parse("B0+(l+,)", config=LayoutConfig(b0_margin=2.0)).r + 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: From f2b4bbd0a47ee42e1f9942fedeabdb302823b237 Mon Sep 17 00:00:00 2001 From: CFDMakino Date: Thu, 2 Apr 2026 00:40:54 +0900 Subject: [PATCH 14/17] =?UTF-8?q?B0=E3=81=AE=E5=AD=90=E3=81=AEC=E7=B3=BB?= =?UTF-8?q?=E5=88=97=E3=81=AE=E5=9B=9E=E8=BB=A2=E6=96=B9=E5=90=91=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/viscot/core/nodes.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/viscot/core/nodes.py b/src/viscot/core/nodes.py index 32c34e2..8d74113 100644 --- a/src/viscot/core/nodes.py +++ b/src/viscot/core/nodes.py @@ -741,7 +741,7 @@ def draw(self, c_data: DrawContext | None = None, *args: Any, **kwargs: Any) -> center_r + sign * b_offset, center, ) - self.plot_arrow(high_point, high_theta) + 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 @@ -821,12 +821,17 @@ 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, - high_theta + math.pi * 0.5 + self.dir2rad(), + theta, ) def show(self) -> str: From ede37276c7a55d20228a679560b48d9386ce353a Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Fri, 10 Apr 2026 15:42:40 +0900 Subject: [PATCH 15/17] =?UTF-8?q?docs:=20README=E3=81=AB=E5=87=BA=E5=8A=9B?= =?UTF-8?q?=E4=BE=8B=E3=81=AE=E7=94=BB=E5=83=8F=E3=81=A8=E7=89=B9=E5=BE=B4?= =?UTF-8?q?=E3=82=BB=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit COT式の単純な例から複雑な多重連結領域まで7枚の出力画像を examples/ に配置し、README冒頭で表示するよう構成を刷新。 --- README.md | 72 +++++++++++++++++++------- examples/example_closed_orbit.png | Bin 0 -> 10545 bytes examples/example_complex_multiply.png | Bin 0 -> 26650 bytes examples/example_complex_nested.png | Bin 0 -> 41444 bytes examples/example_saddle.png | Bin 0 -> 19981 bytes examples/example_three_saddle.png | Bin 0 -> 23248 bytes examples/example_two_orbits.png | Bin 0 -> 10928 bytes examples/example_uniform.png | Bin 0 -> 821 bytes 8 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 examples/example_closed_orbit.png create mode 100644 examples/example_complex_multiply.png create mode 100644 examples/example_complex_nested.png create mode 100644 examples/example_saddle.png create mode 100644 examples/example_three_saddle.png create mode 100644 examples/example_two_orbits.png create mode 100644 examples/example_uniform.png diff --git a/README.md b/README.md index 970c7a5..77f34af 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,53 @@ -# visCOT: visualizing the tree representations of structurally stable incompressible flows in two dimensional multiply-connected domains +# visCOT -このプログラムは2次元多重連結領域内における構造安定な非圧縮流れの木表現の入力に対して,同一のトポロジーを表す2次元上の図を作図するものです. -想定されている入力は,Consで繋がれた木を同一の高さとして見た場合のCOT木表現です.深さに制限はなく,再帰的に任意の深さの木を可視化できます. +**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つの鞍点結合 | 多重連結領域(障害物) | +|:---:|:---:|:---:| +| ![鞍点結合](examples/example_saddle.png) | ![3つの鞍点結合](examples/example_three_saddle.png) | ![多重連結](examples/example_complex_multiply.png) | + +
+さらに複雑な例 + +| 入れ子の二分岐・障害物・鞍点結合 | +|:---:| +| ![複雑な例](examples/example_complex_nested.png) | + +**COT式:** `B0-(b-+(b-+(l-,b++(l+,l+)),B+{}),c+(B+{},).c+(l+,).c+(b+-(l+,l-),).c+(l+,))` + +入れ子の二分岐ノード、複数の障害物(内側境界成分)、連続する鞍点結合を含む複雑な流れ場も正確に可視化できます。 + +
+ +## 特徴 + +- **COT表記のパース** — PLY(Python Lex-Yacc)ベースのパーサで木構造を構文解析 +- **自動レイアウト** — 高さベースのスペーシングにより流線の重なりを防止 +- **スプライン補間** — scipy による滑らかな流線の描画 +- **レイアウト最適化** — Nelder-Mead 法による自動パラメータ最適化 +- **多彩な出力形式** — PNG, SVG, PDF, EPS に対応 +- **対話モード** — 式を入力して即座にプレビュー ## Requirements -+ Python3 (>= 3.10) -+ matplotlib (>= 3.7) -+ numpy (>= 1.24) -+ scipy (>= 1.10) -+ PLY (>= 3.11) + +- Python 3.10+ +- matplotlib 3.7+ +- numpy 1.24+ +- scipy 1.10+ +- PLY 3.11+ ## インストール -``` +```bash git clone https://github.com/yokoyama-lab/visCOT.git cd visCOT pip install -e . --break-system-packages @@ -20,46 +55,45 @@ pip install -e . --break-system-packages 開発用の依存パッケージ(pytest, ruff, mypy)も合わせてインストールする場合: -``` +```bash pip install -e ".[dev]" --break-system-packages ``` -## 実行例 +## 使い方 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 ``` -ベクター形式(SVG, PDF, EPS)で出力する場合: +ベクター形式で出力する場合: -``` +```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 -echo "A0(a2(c+(l+,).c+(l+,),c-(l-,).c-(l-,)))" | viscot -o output.eps ``` 対話モードで起動する場合: -``` +```bash viscot -i ``` ## テスト -``` +```bash make test ``` ## Lint -``` +```bash make lint ``` diff --git a/examples/example_closed_orbit.png b/examples/example_closed_orbit.png new file mode 100644 index 0000000000000000000000000000000000000000..880cf6571eac9a2f1b2d1b3e92b0e2436c474b7f GIT binary patch literal 10545 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 AHvj+t literal 0 HcmV?d00001 diff --git a/examples/example_complex_multiply.png b/examples/example_complex_multiply.png new file mode 100644 index 0000000000000000000000000000000000000000..28eb57de3ae2ac5805df3cc3a687bc3d85c8cdce GIT binary patch literal 26650 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 literal 0 HcmV?d00001 diff --git a/examples/example_complex_nested.png b/examples/example_complex_nested.png new file mode 100644 index 0000000000000000000000000000000000000000..64e0e108a0629ee6b5c94608a29ef61f57fb0568 GIT binary patch literal 41444 zcmb@uhd-8Y{|9^_l9B9@Y_hwMl8|JNva+&gDv@X+WJmUF7*UkUYG_C{DI+OTAxWr^ zNL0`J_};(g`3s)=b${>sTj{#a^E{5@^Lek&8E?MZfR#yri9(^U8X4+ZQYbVrF%^XadG<6Nk`KU z|5{yCzFKk(7#Vh?W#|^Kv+u|YH4z(l&8BrXIrIaku@~<^wxI;O!#Z{^xm3fpw7a<< zg#V0(e81_Ne{)L!Sgui1S-uf|fSztbX_kS1l z>9Uf!^xWF~E`!~>*H%?mr)FkGPfs6`mXo_;oGEK=X_n9?{aTzkjy7ySuOBv= z&aK*WS>tI0-D+1iw}`)gmal{^FKq5e77HeSYXdIx#NU-|4ULVn!%yj&T3fRxf*l+j z_7B}x5>P!5<#6Xfq4)E=Qu}JD1AV0tvU{&<+uQSZbaa?oTkG1`@cdm_I=kb{-(fs} zUo#_g@1LAWKK}jv$d{Mbe*O5&R;!rYy35Xvuc4vA_0XZj_VkU7-QDJn{`opGJO}us z19u)fwna=_oSlOsvZ8{zva(d&_T$c-J3ZI;%GDei;Q0OfH@S|Qni}Jf9g>ogr8jQS zQG$Yld+z$}3zXlwm3Na%UDMgsYyn>fRf~&@Q#^WldIF9fJ^T2v=Frek!S(A754IW? z#!;}ApR<29*VWbS?<-}0?orStVQ+6AmypnKbKkvy#cxh4fB!t}J{lYpUq4EkcZ*>BK#`cHwi&;ASWls@R2{)QuGTx>C8ynq6n3`?)5Dvxu97)Jw19J9-fK8 zJ96ql6X945!~5CI>6b3C`uqD|R67zmZDncMF!$-1o3F1-^JNQJrl1z>)PvZ>M?SyU z;8+_-Z((7vZc7V0AKk%&2ZJWMca9%josp42Jvryew|>1xa{_l;XQwV!@DP*zsrZzJzMc%WW$ z=T2c^p(xMRzL(ebRPOH)8(n+D`LVi&hKBjI-5XLroU=T+t~oJ6fFZ$@bKSb|=NGnS z8}gZ2Sg<=P@?t$XCI^#d*>iqarZelAwQ|b6OM^7Ig zI*LQh{)l%EkMG*Kb7!CbSxcjWTN1`4QC*+KRaLp!+1cMbI$3Sbn@SA~3~U-w8Npu> zmz0d=K9-k%eeaDTeB}N&6{1V`+!|xLU%gVW+*1}j)t|HdxMKfp7dFa?*-`r4mo?(q z#r5M>Ld~UVam)<4BiK(Y%}I`qj<$Wc=l`K2^y}oS8@qPxI=iy>rbFhPn^&(g;E6~} zOQ+axLg6`lSP>hms;X*c>h&fZi}aOeLx2DNIUVn_@k!X9hI71|W=5Nt`@?W1CcbdfkzidDDb?{ChiqGi#YWBFPP?Waz_IAVF zyS@E3N=fl7|HZC~SK<<(@-Q$k?04A7QuJ=0@MWeA3T^Y@9Q?R z6cY01cc#WluF4gZ#k4zgQl=h9W=HiXBDb#f^;uKuu?Ljgn;17A>^BYZPZ7J<-RzUl zW@Kuri=rbYCN?uYpyKK6&9j`RpCs_KuP@{0;-BT?d;$VYnKd@%=Guo2$#N|@x9i$5n{HFoBzkcv~;bzYi#wLqqB>YBi#N7+%TQL}~~ z9=~lDY^kcMYUkk4(9yw!(j8k?=37|xQcFuK;K&g-{?%C76DLn*PMzCygPPQgea9{+ znAp_RB<6Aj>#m}r5;WPjX2bSlx_1t|lIYu)Ag_ZZI)Ya+H*@jx^G9Dk zc<2zz>C>m@rw2I4$H&brEK*(f$ZbNA`!W8!vH#{inz5TlxqfnPQC8MT7SeR}@HmT2 zZBm-exYhrC)9c$V(U-3k7q42se!ZHST7k=xFdUxDfIXjLo0@d{ zKTm!8c7EfY%fqA1=YIW~z0&nS6P}`t0jJJUL^atxdV*^e`qW zYE|Lq{rmUVKYdE`XL-@!w)5kHvND+w=Z=j@0;)R83tw@TX+-vD2Yi43_)WDBn~+vW z9L~-t3T?S%5i0xQ_YbZ3n}t`e#wa>HAlJ12)phQhdi zo;YQ0=mc(eleNAEdfmOl?;7ysBj3MosyXG9DZ97(hV3TuB#(S-8J$+w>jbEh;T7Hy z7WL%G4q7_8Xr3CJ#^~lXIu3gJ>-Qj?T2i?}KTjznJ3?sHC z%mW5Qm6S-{a(>K2IWaT5GoIyaUY-DOR}88nDKSDqLTV>wMC69-Qc7;!xN&ZzF&3ps z$lSU(ndxNY+SOxmIXQgt&X4$Bym)~k!0=2OE9yE}<9lLZnxE`k(Oog-S42M69DIHL z5S~%?@1HR-F>h**Zqy3-6^_MgeDfwJSVL5lX8o2!^%Ff;j*NHmvR4Y>gk1PTK&SKpI_V$SyC3-5tfLDXLZFqqUYsH zaq-ha$+r9JjO_RK6wyC={CIZ!`TEX_+tRsb=a(1#1=Rv5xcruAX5L~Ok$5pJJP2Ka zPH))WjyZRZ8F0_Om279gv8b4sqCNRqo6E{L3d+k9v36+-fOC3b$@o`yoFalvdP_{~ zX+zQG-`w?E-;ykxd||79R_TgtiMuxIE=dCautiq4oZoP6{no=3=j|OF#85_TZ#nM# z{Nhqc->!uF-MHwtqf{1JH0StCIonaxZo3Ls8WYQpjj?QYcMfnnIytfP^UK`TGM8Ab z%EFy9G~{@3+ffFOmc*yEM?c&3_5qAEG@s*T7Z5OJ)sei4zV7+3H1B1R&c1!?vHIE} zAsX231qB7#=bKwwJ%CN^?jGXT3jHmyP-osmJ~QLmo_zP-Vxu>A4)V%vGqbhjja|E; z0g$=pw#$x5Ic+Im;V13RMd6{JX+{J7~_F zIiqo6CR|nJd1q(Ty?a|PUc4Ca?{8@Q;yFvQN+M(9;|(7^2z#`iZ~i&;+P)QkzK)kS z*1p=i=fUd07#!dMK!JySzukaU@!UUl7w#&z?`%yLCXi`m`TG;}=q!hS(j{jFVwmc& zPE@ZBuKN!kRuAb*Nl7s>GoQt`9>f+F-n@Bp+ua(@E^wiiM1I0N+yR`*?GK`RlFRx% z?YV@xMz5J+SG?lnMIXFS>LTX3TQ?|M1=DG{rhrZe!V(3 zW7jr!2Ibhsr){a?U_UA>8`LwVuT`%eh!J4m7gJJVx4O1lfoc+P?3li(Da-Wqbn%<* z3GxjYn^W}~MYIhJR)>X!sRd7FbPqIQtC_DI*pd)e7_(EKk%gsctUaBbm)BrnjrSU7 zXJ>gCS|&YhJtY>}7#4o9poI#L*0u63b&lghZgWhkES6Fzsc53D4}z!cPAvvyY0IqX zT-A~=dVABMg>?b~3Gtj8nfB6MH3a`DZo?0bOim^oe0?i56i-y`^n&~)Nps^>FZrx% zT%%C=hJivV?%a`FwzRaQVvhmi7d7xb=wQS*g?D}a*bvPO!n4cWU2Nb(nbk&8i|80w zQk*|ab2DAlve7F)XZi4$pX1|G-1rX37zOkZ9B9(CO7dzpc3D62x1|4!T5k9Flq$Au zOK+-u&3^X7?;b=0Fd0=I&q!c+Nwu!wKTA=}KBsq%M^r*0N>cnU@W}o9_sw6Yaba&n zhWuUOSMly7V60th!-fsY?nSCuG9{8KD(Q!8gSK&*n3#|YTf26xr^|r@vY?aAf%hz% z*i>0K#H6GcZr!?7KC}vWM%TdLitTM@%W3`+vGml`opt5&UYeQ|NyuZ6Gc2e#q~IX(&700f)q-X+bw!CPW|^@-hj zF^waxDF39rp!gUqgCr+G*C}2&Pz%DTbeO%AXt4JE}fGrODs@1C- z2L|jDG_P1oOJ zpM9L1oVvQY^}W58OfBq`|)o>g@vVDsa69%EVTC>6|*+EoCohAx&>uULL0h6G=6DPdpwiv0H3a*qod%} zJ^AMW$J0WOj(0xA-fYXx)nS+15w;@oHZ!=T-VdMN`|n)%`u_2HbUN#(JyE!7 zv7(|P>cM__o3Wmjr1h0v&$5_uQ;o~`s89T{l`W^^$0oW9>Bv3jMF?*{#&ThcU(rap zSfo~^F4-xS0V6sbR<{ZY!a?BOQK~J!@t0JIC&$oGz+=|&@WkM1Q$uNJXzU&Y$;d@z zTW%?Jlio;vjlG(xam%uVNzw6sc%?_Hajg&6No{9VItn1IFev0iZwd1NWC%@phnl#| zUj{OoebkJzCfSN^XuVDM%5L5SQa)UOriU-pbN==Fr`&+|?{}Y~fnPtOn#+&0qnvt- zG(?vRvKfCVh$&2TydOZb@9rVduj;{*B4`9);g-|fdhZR*%p~8=?dJ=B)TD*Vp16MN z`RUVhV{3Ti?ZScGOe%f2c@OY))T3=x`n}!Kd&~B=5RMnUxVX5?LV}q8*w|Qwqmk(n z<4KTF8dA^3dVIPrkK~{ETc{GNtgXs#tZiw@am%qb5|=vyz{<$Rwy{-U^ZxMRVOPR? z5;Tk3#L<|`*O{HXq{KKJSo`M5nH^l*+?DZAL6B--H;Q&*nDw6BMF~KqNV)$^~?NIAARO>UF>`t6>U%_nfYodlqrsH{LyneaK+mI*IMyU;f)*2 zl;IyscJ~f%P*|{H?4yoB?(o1v1~%L_ur>5|P?p_mz$B%xl@l`a=XI8ZJ5{L%_!6!G z$lO2nRiJ$l2>mJ48{b@WQS-)>WY7o^6x;|SWw%FGM@hX;0SSq*} zTRIt~$PG`++}u1R@Vh770lvTapa`z`t>Sm(_9|#mv*Bv|tC$sY`)4WXc!2p)5~b6{`K*FkRdfe9>obXeS4fz6C^HsvD0#f^=P zBVWF(v9-1Ra?Dh}^w+q{s+C`}bS7Eyq5|Ip1O)B}jLqe4d4 z7d#NP>^?j%B9Ha={Fk%nIppk@bTlvhoZ?k=c9mo9CfrKM%^{AW{UN&h@gb=S4smk2e+p??Z_prxgyyQfDc;19?vsuLq1 z?ez*aD|A9MKqaB&W}W5BxPjQ z96NRlIH)B{VV^m^J@AoS`;}X_&V!9MLYDlWa?^rMi02+p`254umb-5Guq=Li*9bmo zczUcY6|jA0;peF@UsAt9%u2Z9&t+_pp}R32yB)k#G zNxAz7LO0&uU(QeH;Xt*|6)>Cf=n1;jYYTS8t~~(|yzN8oSkJ3h%|Jcvr`&-Rps2YN z?{@GqILz7zcv=zm=Tz@uP;?DV&2uXejAo#FZ$sA67gEtZ5QL9?<@oic-{BH&Z z+^}2r1Mla)vV%ke`pjmUw|Nclf#foZT|5qnts$!t`ud^wk8*2-HBQtEZ0=Ye=*tI% zr}EU#15jP)+Dvj6fBX=yd>-9{GFtTqNVakzRYxXa)SCq;BIe=4?Nb+JtghBKH0XGW zUPJYH+7!=eudW*(9}n!NU=wc1ldI&xASftk`1a*XtHZC?kM&XoHBPL9E-&hL{P-eA z<|ohd=hM>D%R)Bk9Y8VqTj7gl(tqfF*q`44fq@(*(OXfjg1*1sfW`Ct01`(H9BX6x zoG=ogHZwabN(Xt_vD~qi15$iOV3$Pl5PuA0EWvGoQ8ggjPVOg}$1^nLIQfe|B9XBHb2qhzUiPZ6kVu5DaQf=sAUBaI0mIt4XEvi- zdoBaOi+vG}wiK9qNX6bb4}&N5PBrYCiZmrj*4W0KD7Hq+^$~QsvE@Z06BCTkQF<5V z=QEhIElaktEIAnx_M9$d))Ar!@LP2$J>BY7orSd5$nxTM&xikWz1$@|a&vQyoP^$@ zO0C!cvjzY=lr3D-0JltU+jR1X%-#cyBAwBjy*e@ioznTAO8AQ&;u;9Ix7N3^;>4cu zSengE-Px;y77P3(^)kUBkh$HY@nf~GSyvc|xRYbnZ?ukY0>dp0MW z+g$cDxGkZ9^NEQW?t41AQbc!AgC@+$Ey&kMg?JV8Y7GF5Lc%f~{1hy@2HLaKThn_S z(PA+ar;w zrdvpD@9dO#yZ5@aTWzZGzIL;V)T7dAX=$=S7Q!N3B{c-d)tz2c{01|zStvf)*q09_ zI5j7SQN-V@{@pt#&%^mES5%qwdZ{L-WaZ?l8RR-kxA~@KWyRFjYXiB2-+2Pnj#vhx zA3v7#RnMtFQnItRPhEf-W%o}wGuqgPuP$UM5_K{4?u{FAIBgrNrc}grWF0Ixj6aDy zadREtZ1^mx>EkRB2LKdYp|gP7%dLCy2NE~2ZUC}l(TxC->nBg^g@Xg3hU=Pqfrc^j z<41bnZQVU3DlEWwW6btf^jXWDA3J!ye$m$En)Jfh!6zFupfgq``&l$JXI~S!D0K+A z*xHR)3FDx@cMiVpesbmvq;Z2zO-@aab#KWwl#UvmjznI)5@i;gNp}Uc+b&O>i`o3W zFF6$y3@Js6t(l88RneQzQE@OTDl1!qA`O|hUziiSu0J1Nbfd*cZ}iE(zv5Hdu-VE3 z`a85s%9bu(xbVy&V9VC6tx&ghPqBHoc%ZrvZ=!d~N?KC%i5pc(N%SkAo$(cuii(Qx z=xF+bAJ91Km53XIBbw230HyoPoB@$~6i88~RAMa@E+45AM$J$B(-P zA=Aa+UfaI^KufPuFjEjn2x*kxxr`b{07_n7UU7gm2!7sHf5n^MXxjEAdao_^Y^Q=b z41ao-9e4h`eo)Y6;$2`PpsHjYR9b;>#J73>I(UQ>Fv^_V+{&R4?s$AD9)yAM23Z-I z5jfar9Oe z;##5m8lCRQka1uCe8=g9#6!;_g;@2p+>D(WCzVYvYvl69+`q3D^6#(H^x`*Y6vB9jGP-K1ad+jg4Fkfsd9_`2rB`g?OYEa_x@V^aHcshA2z?nnZFln z_T4+IkhN?s-Tbuk@NP3RJ(we>eooSBYHB_TUFJcR9vK;#7^uEx{X07=t0m7#<)Hbw zv-rzV(b1xirLYTHv8)N)_pMkb(u#G~$JEqp&KjB-sy%7}ZJ}~#3eZV6LEFe^4d5Hc zvry@bQC9&5q2yR}nJa*qIm%v4pq~`V{WJfx&{v-K%B8J>*#TN3!sd;8n4f%QGPY=B zX4Z`Tb7X1G3yqXV?w-YMXH7KZvIDJ?A_ z^y8+Pkw)@2bF&Y1cXzvf`pg(92JN?})Iywcl5GO0&`4uMK~^?F_Y0*yPxYW-R=~-V zKPS1tS`&#)17nhJN0`R$OX`w+(tS7g@j@Yv$j;_PElthHh=N+^4n=69uPnVS_yu9g zfRpJ95I4FZ`*-H=5GG{|-LSCq*OMbBK!=G40-rsuM!2Y`L4Z7Oc-px&+4ASi3L&NMz^xEQsLIf07J3i?c05WPI4UMqk&LW!Zpr5?b~Q< zWaK6pl5hd*B2J~I)T~}B7zgno7CDUkFzaxY?0K4M2u<``}g=?z4|{-6K<>8kJRLgg0f zGP9VbjMYPbZ|+Gfb9%T%YR_f)yQgQDLi*Xtp@g_UAX<{$Yr;|80aQ`^}yIXl}nUa}rWAhhv-xiO<(%gf@TqJ`Z@aYsosbPNS& z{{7wm`qrh+zRT-~q}@z}SaH8|SXlJa&H6x4?_ zezzC6M|5><{nM|m@8z_=8Qnl+fPpQLx!=-mEgAFl^c;pHcmKq!7}1Ro+K68ig%ppX zMZ}|JYisN75))o{hxPB@U*7xPGK2bQBQbwb7}I{Q2CXJHn62m(cVTq=`31DN$j6T# zw>jQI5lKx=EpOgSd2`QSn1O}QeC(i!2MhQeb$_=Ai=f&%v}I1C56#i%&lBS@P9quB zZ2Ie0`zdA$feX?yGH$39lyHav9zeJdb03@XJ!HDql7b2Y%F}yUJy>?Ls%rD8J>%2G zXaMeyc}oHyeh+{B`rpF?t*p3ruNC{<#ET36Ax9cF!ZpRQdCL~`QTwtw7j#obCZ>i& zeig(aR-^8M+;gF$h$)DCxN5e3^i|4S9ld&w^cIN_)A8!RPdiE|Gk6KVY zh!J9ysD1=M)4#ueUaNeS6zCIaV;dtfGr6%y@wlUp?;oGe z*>R?>b3Hu`ikM`Kh@>R_*26>VPB|knLxt+oyOxb@)673<1+|qx>zc)%KlQHYCrMmq z4g5B=4P=>)g3M9^l!40g!|14Sa5++D9>&EnR<`+>N2MbRrsDGg2Zs(Cr(Vl7NX@Ju zeDX8hHz0=M$XK4{1@GptMwFxq5j;r=+CNekP6~1ZD$#)WIiko`@s_(!>#KI(I??+7JVt zYTUVV9)Dc=N-gN1&Cz=^(RvJj|DFuLgNYm;Lz((u>3XI7nSRm3avPg6R)WgrQBB>xxPdJOJEFzz=p5@!EnP!MY%A z5&q=9{X7-OrC@j3wUCE{i`F?~hk`6BDoPwJaII&`t8fO0uY$ghh7$$Tmhth%>}Ps_ zF~j5I4Ey%IRsI)7LT$djMuSVM6_C>qQM|V8=y?6hm+9qnjBv$uAvamvJM2UfH2}*o zRaMF$jWD}r0ME&Dh4-xM?p|bW{~h-ZKlW_74D>fKd3hF}7Z;zR+|9x$DE#v|K#j=R zF>5zC$RyIF<5KF|+T!@SaF@gx*6A`cF*%!_&S}(lr?T>CmDjV55dT^g7VMBQIxB1I zCct&wtOFk&9``3M5FSyE+7Yqve?|gW7&;1sq9|Nl!dO^FB!ChzPf$zm2YpF1KW+dy zYh-wsvU>GuBG0X66KX2AueKZ93ayPC7qBp6&!&$bKVmtPptcthN<($iiLa?ZB#!ub z{Q_JRh%h5?<)M?4BX?w^fxb{Qi_%@=Yh{|NOuu{g?vh&@8b1H%fs5$APC-H8$nt_8 z@l*c&UEXxCdmif7eqUcV>{_+s-!2*sIhf^beu_>=J@kf(WDluN#5F@XG%r=+{oi&I z(ztKQe|80?Egv2wP0GD3wcvm{U5|nArv|D}f0d^y+b_B0Z1oocZKP0eqm6(Ey2t#G zujKfPonsC<%DiUH8tT@q8$UO-v{)udC=m!M?|46@q*7cPO&aGS7R-!nv$e5wTKUPi z;@jb_qp)KuPcI&h=e8LAvDBod#Cg-YXX*EE9`%$@vnNP|0q!qlOz+Z7NcX0TnR1OOIXQ7yrql(* zl=SlU5C(6O$9Rf0ElQCZ`edYaIb<0D8=VoQYTAhU^hL9R4VF7MO$h z7*%`VnIfL>lx%{K@132d$TJP&$C3Ke#;L)Pwm@mKG{_GHfKgObET*!iY%QvTl{Dp6 z57LVK3s4S8`_Suu!wzhLxH2+2YUy67ij8Tb&5({~BC4pkd*U>>xUQigqg+#V?j4q~ z+9^|0Q^GJn+ugkmVBY}1=-i`?`CYBhH3BC4Wd1KdR!)7kvUz_u1zAsW zVL3`31tleWr+;QE>}$EM@*YqMkJDhk=@ZutUI>or7C8_RrUJSn%la+lZ$8*U0R`=I z9u}Zf9R0jI%X-%l#*AG@0|Qgi)6YWRBXk2^6waRHDYpi5TU!!=A_!t0%loFwM2I*+ zMs<5_ZCfGTGG%^*I{`USjCmfLcazoVts%Y`$o^{T>S<+@U~Elk4`+US-cxhnRl;7^ zRc#lvAe!-F@q$KbF9KA!e(5cNePIy6co{N z`M1`^g&3skmg0JgkgY;+pyBy*Ry2IMxgmvpcU|8|q=PBbbNc;W`gUIthlgeqso+Br2& z&6jGArBX>g7)eHgZUzS(q-0y$IZ(CL9T~1S6Oc;JR z0tnd6&26Vx9F$bjICXgafd3H6QSx~qIM>*j92rScFKbA3iT{DM(fRX9`~9vHSaZDx z20;3)fy{u}jnAIl=>8G$sJj>On1*N1&T&hYl$ORxnq;~56zN4j$Ohu7cQBAJ<_wa+%%E2!x?+)5Go|gbbmhg0m&=uBXHhK%?EX>9)>}>iPK~veG|apcry* zGv|nLsz+|adg2cr9_o5)g6iKmm zklyXvx4Q>+fsD4F;!9bibba_-1&<+^BqFqa6khtVPoE@%9^@me&CJY9Dy;VVJUtY%_@4ohk&y_vbO!qIY&lJ$w3oF) znMC|;{vgSMPiBIJ)PO^PG;6h(4+Ak#25V#xTm{5qB>GNHP64_|5JiF(VO~&WrVq@Lx7SJ30c>X%5;_kID~`w> z$*_}D9Jn1K%9RUOiW)!8Rv?**P=&#Hzzs}6a7@nG92GJtfYIssWG79}A z5(Us3;^O0<0%se=90kR)DYuItiA~%&$y`$^iZ7se#sKRUqCQXb-&`O5i2pSX^9-y9 zm^Hh=oS2xH2w8dw5f_OJZR#pYeU+MPm6oAUnRo!0>$+F~LRw8+;o}N_Q zebaCV%I5~bk{}_9uvJ0b^O)=_bARaK^$AagfMmI-D*5VFB3<>6fU^=~E+E{A9m$vp zCcmVN)2XfBg4LOLd5tYGDM@c~^&X_dNOVnn3B+%>ugr?RsW<$QyDIT7zmf!Bcr-v+qC@OYmfrpw8+t% zxbtc(Bjyt%^mjrJI)5Gtg)JT%)RcG&M14!_lFYp%k&T4!B7dcuG@Qq$d9fg}!Jo=O zemJjCNQO!$siE4Zi-Z>4x%&G0NL~~-Xft;1%HZH2-}Ynj2m4Qep+KZS<@~qugU!^; z%;+>2P)VH+blw^06ls}kW9?3;KGohLKnvIOcQTZLl#pcOpy{4CYR(4*?>~o(L|?{4 zd@t2HmYyepk+14~TzJ_M6Q>27y; zNz&+uJ;Dm66BZT*At`0~9wW8}xm&Zl@R*|~cdwyRsUmOts8k>r*FSAJ!q-dT-w`+7 z=xuxZn*RQNvJsk|J!6LK;b9~X<$EaC1P;Aw05~XP~*!< zcuid>+Am~<636OW(3?5iNp3=I!XxeV`xrVDA026rMSP8RzEc_ zjqsS^X~Z3oHMnQ@Z7;t)41SZXb#UuYs>8}uRHJ7ndc?)vl@}G!Zrpf{_A|=LM_|Tc zKMxP9$!0%`9_ZkKz#eBv@{;~VPj7Dn{@7?4J?>MTKl*UMXww=?l~rv=lPo`qT)ged`O7S)b@`2Uw-aq^l>S6p2m%V%UPH$7xD=aQ<3Yvw!*XjLiPD>oSP+Q75LRIB5*E^1GN)dFWB8Jovw&x40pa zR>IM7B`zC+LTc$Yq2$(6@0HxVC;$EaX(ATNDRMN1jIMcdl^_TWK%6i=2pu#A^JH>v z9q-<4Kuh2q(3Dkyc+02a&Eks)#0d6fd1{h0DI4*8Oe!qI)MtCR!W`Tiwe29sxa??P zt$cQR>5vgEa#1$7ou4}#y^k0}Vf%5ecJb0Bh43$;ZVlyQ8y90Dz8tNttYpu9w1CZ} z1Q>6_eHUtP@ANna$`CIy_xaMtYykWal2ovYE*p(YN|LO4dHwozo#3(@wmz7Iyyk|P zJf?SEHwgVU-PM{H$H^V38jB=;YADt)_TIgFZQu1|)|>7z)YXmZ)}X)YCT+~%EZCOe z24-*{j;8Oqj->g|UBWDU)bLEBufQahQ^j1nF&u-mtB$P6ky^)JOH1(zF5zVf4sat1 z51MxJN6Wuj=e-c^JIu8GT7^3Mr|8C!`nPW{sBS+J9PIU{PP+FBG`3Q71N7>u3gxn( zmYIK#&iuQ~{d%8u$(6J@P!9C7?2>0YkcyvvdoP)XPln8D0*?9k{_!8IF)Cl8?QFW; z(6A?0T>2>@95Ya#!u|+EL_~NB=Xb?oeK3H~as6v&$evF|fpX_D7&i=R)$}~;(xoKK zYjKyF8>VU;IB-CIYi-4Z#Q35v*Bw>kv!?tE<2sILhjz>-y+kqMRdROie_{a6nODnC zk*O1>PkTWBE7n9u>>sUGbKQM|`P^pr%i_=fJp*wfNgR@I#*fcVzLM*oMJ|n?Zydl@ zv^HcIT2AfJdu7f;Ps&xUicj3#UZ}-Npr2v~ovbA2td#cJt)o>btQHh|BiM0O;@k?=7y1thQXxotmWJg`#bZvMryfs|IDwGB z-9rPB2>FUCDBNs2ib<9vOobIf3TIriMs!({UChtdx7~OwF(IMour+_U%;U=`Ddxl5 zCCrjzItdTxviu~U9QO|O9aQG2Wd)d$R7gljsfDyCmV$Uyb!L!f@Z0)g&F^y;rOVTV zRHS$IG9RTyfwrBbLf`!de$88~5Z}eww$I8#DhP|>Q0j6)Y#T^O!(0N_&wrv{_|JA~ zNOE#;7>c1F$ynW00c*yyn(5l{uOj3D)}jFm_a#q9hH%%zL-%(M645)(T_(lW$4(nD z6pWoQXT0$A-e#gMXRVD`0h!I}KmHX7Jc&88J$u+NcUArxqjbn}C1)(l`{wSkAo)V* zB?yA28fF^k>M|;r7_;vXT)P@=UAJFST|MuUjGNZ&vHr}aAn`lsjeR=DAi#zrcL<4Q zPgp6o&dlOsDQfA8#sQz z2eoGlZy3{RibnKB$}CTJu&+eIDQAiW)zZi4stO&a#HTx0g3`7 z#izZ!X?HHW9z1xCbS0LpC7#GBRfIDe_2oX7j99AY%96f{uU3!UR*}L3@5C*7AG1|% zC`KFYIY$C(IS@d70~7*(XO+nJtz(4K#9BxgKnrdEM_=#kBW?8N?){WCM29krDuy5m z55;o&AWCfv(3|@}m6wEU^s`SS6vW8Ls107yJovf8P+yAN7W#u)eUGX8g2oPf>LD?^mNFZvwMTo3 z@10{@(mBXts()V4D5?eaeUsQG3rkr2_E$7Pp9}LhsL-5W5|T7SFCLzlFdPKRPd%|@ zXe`tzwx`oa^d+_JQz9g-^M`mp9es=}IyeH!bN6Sz0l-AtAfqzPgIyh+=z;dksYF7z z_lvHAC1qMHj%^=3xq=C34@BCEL(z!KqHh5mrgz$5j3bIC+Zf^^xV#au%{iv>sc1E3 z3>L5i-4sl%Bv${(UBrYCM?J=MlwwBE$Pm-h#g*Fe+(CgP14hs*>^bga^j07Fs80sY zwcnpjl6fc3N}`nKib>VUYuj>k+*J8F+?TEJswDaiXh5XosorV}?gb znpgZ!3qBOWSU8uyw;gLONAonej)~}Dw%C4?SBGocB{$eGUhRz3Yp-$C>M=FdU8Kis z6Y2;AgU|a2m)lY}crOEDag{)Zy+Cz!rsX}DJ4j*o1LTIaWjTBio4dkWT}eqvK0@;d zSxhj=)Vryv?9l8Sh9BWvfYxQ#I5|2NJKHf*5QyI`gqVj6S<;Nt5@$fyD<$1JH5~H$o{)_D$56Mk;9n% z_HKLSPsARXH}1aZ8f~=6@%}mt9MZqQOj1)MJ+tB4r%!jv2g`SK;1_LYH)?8L+N+!? z#vg@-nYi;73(1fpf@!oJM)3`sn_^yDBQ(tB$T>Q4N8 z@x&6oz`gd~_=`(&y?--!@ep7fMAu*lWWV#-DE7OQ=bKqTQ#rmS_g$`nm5t%;qlIPf zjg5_SR1fmtgq+1_f|2C=hgC$yAdxy`D6>vH3Od5b^n#J+Gi-0WzzwEEIa!#tpCciX zBR?niqVAbXd`ra!BC^qit3((}ojzROPjT^;woX^S85Kw697aN z_uI5-Q=y_H113me2#1-ABuX&|MBbGjX zhgvZ|KVSUjLeUb24!dtTZSOr67$_UWO7hA?3xc5DR)<$fK-Gp%MTZpvXJ`cQiLx>O zID6vP+$ZLuD_4lMLt>> zUded*VXMEZUs9Wzn=4oG)9?Ky5#8r`@7&7|zC-sbJ=MjoNk~eLKn^N?9wOZ)hDC)L z9}TXAa+rGYA`1iuj4v|%QG^rDOk^_X+|=MnYoxKG1x-v%Ju~ki*QO4~yZ%#PD!?FI zH!k~;P83{VQU&Fxfc7-W@|ix8)x6OXCxN`K9@@z)qFBI~#8f=kU!HpG-WbDGH`iM( zz6TB%KMJ|o=gkuOdtS!+Fod*-lC!(mHA$@D7EXj_Wq$wfM&C^@Jo!0pKf5&JAU=hqbyIO8c znv@4K*rO{}@f3!JgiMIltWqLcg{hK$1Z}uPYMvYLBn^hT?OdNgZh%u%aQpVAXy_FI~LoSQ;*>H&1=C&-7l&=Dws7 zho?#Q-|ynw5w9MZy;6&HD&(aRCr+sOqPe*wJeJWUIua(KXFilWu>3yD`Q=1HaQV9~X`U znxxPHJ0YScL%x{PQgnV42e-?l)^?8OC6#(-cZNnlm`)0N8YYHHSRs;-gI zD;#6{;B-1#T4KbJw*{0AU8|49Sk%!%rdxXK>(?`5Kn|Ja^pYsV-r~sk5=79F2baqc zeFq)gtZt-(7bg^U?ex8$R_x7(Cu=tu(J8CXH9HGFXEVp~L)4s|AMz}cg57X~t2SwB zZh%aWC)$V}c8)cPrvlPAQY_VNaF_xEFwcyj7_+{Ui6M=R;6qWzj$KOyJruY6y^}XXYWwYg1-M zdOF>X9Xr5Z5$mYOOG@BfP*qhGDugP1UrOO+C^T4cZH$5gT+hI=*74!M&D+%mNERP&19 zKgS|HPhtZIHo}OuX%mS~(Esb>hTu6mm&C0WIaOPZqb=s_;#+0K;0O^h_-lu}i!+2{ zG82SEdNZJun~%?GR@NkCm*mEgl!2YdlOnqY!)UKe5^c)o2J+eiUzMpFR!s(fAABPG z2E&pP&bj7N-k6#)B+1NC4@436zrA}iXuAXr5nbW2LMEW#J7;HSF-RnK25FFT5k`^k zg{8>dkQ7y39(mOb_*ACvCGjJ~wS_hed(GEUm3EdxSa?%SBqW$d?5Caw!3X1?3u5Qa z@s1S6313}bhRjCbV!_A2S}3#O5fL=N-D@bMNZu$f!?XcrmK2Vtn%Fn>^>G30Qz&^f z3N$FMA~mWKF@5rsAMR<+g8Z!?|r%I^<^6J`|c(RME{Gm7m-m{ zf1rC(G^5JOvH59QjMF*VGX5B-FX?{2cV)1cHrB87Sj{1?8Q*!M(>M*QxXVJRtBkA# za-xnMn%9T8u1PDj)6`gV4z0@Yu}HBD{4Y`qdyq_%730v55ph!g?K0+Fkrev%`#00g8L2(gCmZ!%J%&QKapOjtck5tIk=Yc0B#y7K)oe+cuFp25zop3V#~?jm<0U<5%(0`h=dwm84;o{@a6Jto z7&nG`1ZhtuLQyBfxd5@m&PQ6)*;gf>p87-%Ta#h@N6NPFED6galArcS7pXAJHm+FM z+BDXG{(<)dB*3LC01){v-^($YjXOssh{Rd24ggZpHko&dbexRRVwW^J|CMKOSt_xZ z%C(IIjJ4bpHOtvC*^yUo2NuQQv*Pgc}iAM zj>^ddpn+F5#bx1yG-BQkn)p-TyXea=VS8hYk%bf|^v}EgAI<|?TtP#enV;u6Ac%h7 z{rL28$fN5K4r(G^7~F7-IT=m-Za|_BX%o^bNnkD7yTWF3PT(&xElOd4R(}1B@ziMv z?#6mYmz0>|a;OW3Zn9XuMdY1iQwK3g25qA7GhA6N^caK(TjSWp?NnRn)wXTJ%RRF8 zR>Xm<19;NE@O$?_6@4}qZmtI^vd+0#0$1^-m|esr(HlAwR`#{Sea15aHZ({TK~6vK z;YkI)fI&uXp#K9tdj>jZ<${^)9>K!|t za2V;~gjAS+6vI>=tLcR;tfV2;5H)I%gw&XJoNoK)R9MAV^bx8D0jYozQ=9&k%m16j zZ-~punUsg7|4xi}kSam?K22J&w73Dv+W}*9G;FS93HusvVCIw>rF_`LrKgS3yM11+W z?d{zRTp0TWO|1taFb;YQi4}e~8D(i67mA9CA^?GJ>tVJ8T?x$X?{?K6W4wO9U7bV?L0X|G=q%g;8AAiu z_!ZlTpghS3@4-|N8tF<4dEMPD-S2W5VP9m+?&~t_H8Ej^2HoV-`Ej-zvw_Nk%a|_3 z+a+WZ_r)*SLuSBXUzHMFYF0Ls$*-7;v8qD-3U2PJSq~+c!!o#0NWoX2P$f@SS64S; z#SBm5Hj&VCkH57zGqQ_CSnDE-E&|kK6bxzYk6=ROsPc-a0ISIxHojv~L33^5zwO0) z9*F*O4AKa39FHIua`xs;zshKPPa1E_^$Mku{5bNzV-@3tUWY!qt-> zu`S5V*j=cFc(;g&egz0Msg6fP8i@hQQwG-&UKDwe2o$JBbX>@q?w9;AE{I^8P0&k_ zRJ)imY1m8Wp+TiD;D5-g?38Iuckgb&5%3I4G7~Dc_GBIZLH$>4X=&I@9m}pSEmv2m zT4gn;-LLA8qEGc3+}wM&qM{xm0hvp$^2aJm3O&WB)mmhevJCpMtYLBkoe*ByK?9bweNAa6FXgfa_ zI`T7M?x>$>yDH0QtS=vVJxsOuUaSRXIEO*$DRYimz-%71-mr_$Lfg;&l?VR6F;$PG zoAW*rxYxHALnbZCmH-~oN0Jy&fU-o9IgZ5MM+`)NLBaWv-PNC&IVv$7S1ToNQ1fGZ zdsEW~jX8d>TG^)kay!2O{Mdfhwjb?e75I(GAP~JSf^*>g`U}r79cf)l#ikc|3l%3? zaRPZBMzBgnbDbkY_m6mvEg zlVJOQ`g-%In)mjPdn+Z0hO-+aR0@qM!y!|%CK^;KqEZo#nM0=1pb{D>LzHqvQHf)g z6itRg#$zn=bTXZc&+FsCY-&*%M?(Mt3`!ihEd%E5)ANXfN%m_zuHBKxE z9NIF&eEmz|`5QKD*n9|@4aa$UIL#a0(gZwx&cJZkNjfTc!FJ!4zbCQnkUJQ8=!BC#*1{yyOjFfsb%G@J`;fN|;n z+=>!`l+HEy{rA7UFKTdznAF<`1)ZvW-|fV(L(5zvXB@>b)BoVle;;?5uzh%pAZ_Lw z)#cBTf4@S1ZclmFJ`4ZQdX{3_4xO6u8gSa^Q-LmL6QM2A$axe8QBhGa9cM8>Nm@r= zyO7dz;=Y#{g*KlaJ-AE$5QQ%io4xyHd5LT(1eOJ~?~#(_-JZz=yoATe**0|Lmk&v8 zuCpj_KUnu0SNl<0_WcFJxm^PQZ=JVOe~7=%{ZG{Is%R@Zm43a&3$qGeJ8aI))H&0R z&dcLHRMou)E4k+l4*2lrBquUI-px;!qxBO~h2{uzmBD={blAJQ)l-t!eiF*}5|tOv zpS!3%7BKtl*)P36$K3kNbtQ6?B+)__F@JEKqWkVCEthk>t=k9e-0Y*`ea)E$ZGWQI zIo+OMg5!>$iAzQ|ZS?8M8ag8N7v^g5A*gp{>b(B@@ccK53fFJ9`OHHRgK;8+r<>hq zY6`hHrSn^pGu!HLI7TQP88~Kz>+|3--7sht`uVugDU|2V_YDxlw)XFH-EWtdU#d*G zLNbg}&X_g0Anf1)DNy8~jK-c!zRmRQ&U150kn*vwE`xGpEB=dI20OA^3kH{Txk zUay0`xjg7l#KnbG9)9Xb6S$kZMrqqH!|#?`-0( zYbcDL7aD4J1oJ@MQVL)HBCjNXrSaK!ERQqQq_Y#3j?FkoBItb{zScrx8633O; zW7f?X=j4>`=oxudCr!DhRooJH-KZ{!{d4codXZ(<0r+FV=-~^ zM<`eX8;aj>+M^q&P%9B@spt6kWxut(skFDWCemdQcg3R@`yiMJUFv`MbZswwgZ_8Y z_r9K@W$Z7fv%p>V=h|`h zcT>Oic>ZvQG30lgyLfSf;hRYs%~!A19Wm{dK-ZrBn(NMW{ASyGlzkDa#<~qYq&}1# zOg4=O|BNJF@9dQ;saek)RF#8{FV^c>4K?UJInLZ%$J?_@r`knBH;+?YO4e%e!mT1g z5HbdEz*lz=MX`v0A;niW;1V^TfWo$@LDUSn)pFy;p!wT7dLo!~D=gRBd3nY~{hU~Z zS;awSxgLTg2By@0aKD#OLY;{s({z1WPJpH|Isxv~XeT7s$B`(q{R+--b@ejlL; zI|4}+WNZ}abylfMctVllgZabt7aY_XyL$W4eCVuFSizPB_~HcL!9xF)rnP%HI$N;5 zn2Lt2Iooq{FMWB~ZLku{Ob8Mq-RwJU$(b`VJlxQC?B6H<1xYFo(im;-rLTU@bBg7c zWKV-r`HvqwunE0-sJy%&G$m(!S$tA_QkBKBsHpVUe6#Xver{`Rt|&`#M=h^%tu-;? zsL{Uzu51q=9G`pPn~p=-gQ?mGkf@@^-EIuCPyR2Yhfcw<(-P-83+Z3YwV8kHO6-D@ zncgd@f}3=*Zj;+xi|HLXs?P4&dc_z_2-h|JxGH<>QQFeXUqOb&#oEzK?pznG?@MTQxEH-S`}BQ)>iF=ijS;60R1ED6Yw2ILirzjxw;@H6 zUJ&x&-(|(AQ#3U-i@qFfQ{7|YVm?EuyY>(j<3XrhAD~zr5*n)?^#Y$13dYnU1w*4c zcPo)7?kK_4e8{Pz@O8g+=R!t5sQ*$Le)>S^t??m*b!|NAfs-y-=Rb>nY1unB9qp>5 zWRRCmO+Iv6LE=tvsid!jX)V0j%P3*LAjoX8cTqTzDmZ9Ik2)jw4Y?HgNltND*Z!z_ z^%#{G4Q+<^KkIUAx2{?BfMbhzN|f0Cc?Sh@_A&ky0#W&7uYUoA@B-F{#qyBE7&NW>6HvRZ` z;c?=mY`%xmz39zVaskUPrX26oZnp#s2I@J^F8(EMf0rJZjsuIVVqg?fQd3o$BeX|^ zWX_22_5&q`^igl#v;I;#^`-D_TXoGY*B^RL*WGIxz3jV#x!;bqUCo}v$s%L4##d1>WXW6ws-k;;=uE}tX+Sv~NAw0ItxaQZDl$6|j>lTu6-@>^UC}qTs86xGu z%%o;|YZBW0d<29A7l%k6#T9Mvx%=}geK?7j4!3{n?>?|Ba~!H~;oodI9M|0){`6Ad z-Y00dp^E(D7r74JdJlC&OjHlY4V{tiRMrm&ak5KV&kqPMll}>*V&Ry;tZ{oo{#(P& zsT+2v@`R~$Q`+z72Swg*^J#?BIM56~ecjxDoGrE*Lb}6(gc`BXDp<&5l}`$55#C#2 zI&WB#vvq5-kQ~3J!@ zCcu<*#rm(wS~`3Fyi@V#0%t5*; zH`;EaG96H4c^0=vi>cU0l<^v0t)J`~c&78Q;lr1{(wgnHW5n=X&I$khw+s|xLxrfP zPQO#Ctxbu^xuT(}YOA@cP_piv-tKVS%bIU#%Uf1hjl0j+Hc9*Ng=HIHUWmeS^D+zJ zHh@9d&}JOc!!!Ei_E^xVQnlw4HP~%@W-c9;fkj;;BVHfi;c;25MANza{6e11O*@S2 zcC`gTUO(1hjT1*c9u`3m1U>3-s7g)h8OvSSQMMXVE$f+Wnhpy4(4xpo*_N{GEM&2u z9{!5^43}5#-@Et0gR`*)p--WjN@;1i!Q2*KrbiRDco^WC+{pr$=2gLfnx;)2Fmhxs z>i!7zu?v?hF>8tM+*&fjx!FRbF*#ozXlxwH`5StwZ~XZGMSI3Qc`Jf?6neUL%gMGJ zGV7WGRDs@WlF%X-C#^jxH_=w39c)C{YRm3z>X4X57GPZ`4)(i?j6bAgaY@Mn4raAe zr{M{3S)B)~0iB%kU>W`F1G-==2}0iQ@KFH*ML20Td%wmh&JpUU+PClJz-1bL3Bly_ zV@E`aq16@Xzl*>K>@uvEkLn{75qJ9y13~NvR|L7ZlvwLEJ^u6{rZu1cXbv|c#s14At=Dv6e`;2Gb9v6g3Kjp3=vRO{UlYVfT5=x;(e6P@q7V1UU$*!ElJ?N z;Y#q+4%)3dXytYO$D?l2v&SK)OKzKrQS+A)bIXx?wp1g6x%WG~pFTiJovL5rhP<|efiRwsd=A@)V4D&bK#@66y$Zuw?jNEVB9EuWxdglr?zXrgPu!4I53`i6(E@^=962pxbBmtRy9snPJRAz*5S z8fT`@GTP8*^wz8!{ft>I3H5k(U9N1udq;#K6A?P#Ysa~ z{D-gy+@u?A5C2{c{b42Np!4g9J>N!Ba<;iZio?`DgjLQSwcJV^XRL+tS>t3YAwLD~ z477gu+V_o#iNQVX79353xDrlHA=Z>E(M!reml(p=7gHU?gn`WtCz6vD`%l;|LZCTN zWqEl97a$QixD%jRHH;d}jST*J*$^G6)%i!buE%j3m62=kVG@4g>=Tt;gw zNY|m47nGFndVh{Ne(acPWCF^?VAyJCPLq#I$WU8Px}tal!to+X^x(a_&>?k7-SRhP z8h@LUgE*(?IM|G11qY}fO(jk|sS4 zAL2Z>E38S&!frbX_v|UZ>%^ivkEXt1$;EGIY&W%Cn0x1lnNXuT=O&6c0#r1(@&evC z@|v;asz7p@4XHPy5egXze*`1FkIeli@WB+$Ri7enM}zM< zn9=KgOqw{65amDf+U7O@i^@=>J4Zal7Cn5;k5Qntj!Tu7$6t7ixwJRHg6S=)Jy||M zARX*)(}0d|7F|wJ#bh3eU2-!)$O7x9*D4@MMOPEi^)&(?kD1+L+wbB|i8a{q zoy6xU=7m+HdxmLLt{iKV+vymRy@<=X-GPDq=EvIjY#UlY>S?v8oD)F_{!*AGpMvn9cBxuVL(B@&)6aVaP}1X$8RF4 ztxl$JZ;&(Ws92w~HxoA*F?{66^{rpp>)F@hKte@Wnrz>_r&VYAT+$HDum5}qNR&7- z2gl;b>Fo z+t=fh*|gI-!o==;V9-I4;z{JFK~jv()VO=#xRwYctb*Q)(!t9$U~#`rR>4}?Bf5)} zVYfj)IV#hiX%uFPsXg?$#WhDDi_VgPY_PmX|Ni}jytM8L@qn6S&YOAv-OS<>0|(j_ zOgd4>!c&+*oYszEli*u&Gzi%ZmIAROYZ5h^9aCJMpV>?&TLx8@qGc%qW(DS7_VfGa zzUX}day}Nep*#<3I!;oZ;FX=fn9dR>z{2x{LfTCAD~e`QC7aGk(+?zDSg3Pa)>csI zqWN>s@6GC)v3=~CT&;Bw(IUizYfr_cBAqxg>ctwX9h;JpM9K#Ul*gwHIwRB|0|x!= zg=0b#`*b)L(o64?iVu3}e_(dQVAxYplI)+)jiiADJ{hJi>+1>v8&%8yKyeW&H`S#d zoa?ojM(%5HYVA~?I;lD8gtYD|StcT`LcL=l*~J`6Qw?sra{VaKKhG^hiHKuWw5-LE zr;CLHoCPbqw}ur@$q=X3*fRM^8&w-@m<|xoxRl*wsS|`0OZlDk@^*V8|1Ja`tKAb9 zHQ_We)R3m1Kb~3HQDuDR7FGDdX%nYF%vcQWl%qe<9b9g93j!6@z}>b9MJ#Q^_%Wm; zq_09``RwkoL(V<{P%A0aDPG4z-j%qmSRM zS&f)p(e&H*j`v+?isJB#(Fgh-c=?muFDL|`oR(gcD#GI`@>7Po%>r$uo)p04PDbRbH)_=OS8wsLi{N`)bym|R-vb#FzWc;VeTg8-S-FPhw|&&I2dD9L4Pisl zB}G~Z-=f~T_ch7@p#0TskJro#hboChcc{m7C+N1YpGd%%?d9{vDvz7Qz$Z1}hZW|2 z8UZcK(9U-D_HLt3e52$>gfi*H56)F_LklA?7T&7B!P~&@DZT5>cB8>a0jvoo($n;? zKY=b&&aB?y@XpJXy}RZLct(r$6~VB_?xcDKVR^NN_^kDPjOx-zr zWl_#*!O19Y2+gnU!!ERUaOmuIZv+6ws0Z~US7=#xL4-iXe$hs{Y`^Yi-_-+f*dZX&w!Sn?Z%h^`!2VKt}46d+<)1mU=A zsFTqMOzmKvVezUbjvo)@c+l^Ak!04?IWJk}jiU|C+1iq&B0uKclw82A`7lH>Vz z8{BK@>>K9I8b^BQ`VXIiZ%q_M^z;v?b&GqhMTCX5(!t72CGkPqPD9?UEH(X*ZO66~ zMu2(zSC;2}===w7-k2|R$)wcxYoEcg2%GxQZ&pG~&jmg{A#*V9b184AFf7_;Zfzx*z<3Xw&1)fK z;r+$f2_e>@nXX3Sso3a%1|kvF>2$hMG3yK=PCUQubX2Z#^>Hm^(TGEn6zNi|D+@2G ziHu2(2Qfj)^zX?u+aff)v%I|Bq)C4_-uSCkDB}dH{E4oNgc-LSV_WplCM)e;;qLwdD8yd^H*9;hCfI!4>)F+yPmfNz9*5It61m9T<8DiQz zTtP%Ifu@v&)uqd1U8Smxr~W%zU0rbZdlXlaXhQn--D!8@po1;7B?=n(6)V0jJh7P9 zkVN4e0`wzyciQNv577j_4OP$=?_UTqb4m!G*B?00dCmwq@CGPB@?VaWk~36xcO2lE z7?rSJJ}NTpc1ldqSmA+cJqEcNT>6MPkXc%ya;QB-Tc!+QD}XV@9l)KYxC3Z!AdK zux9IkZA%M!;}wYMgu*0&6)22ujT-eMwWW>PSV|J89nm$0kT23^rN}$3;gAzyH^Mp} zE+vdkLbGM>UvKumZ`tECw<3@l zH!+gWw^oBX?}NQZ`%LVPq+Q<=)wU`Fub?-@dvi3G#9Dd!Eg?F zl=OOPzQe2pI3#7-2hn4q#DQgK;@#L?eoKu8m;6plI{Wgm8>QuWcC6ve$Ms?i8Y;gH zd|p4214T?7BL->6lbljm|BD&d_rCxHl2i4tC>Zwi#G-8V&l#LEf(u46RLV${QLDa4 zZSGYC<5}QHhrRE{nJ9RKy(ODz;P>qU`Y1l!L)YVeBpRvWRc$*CEA*p^10sqi+gX^{ zf*+ouv=B8DcCcv&mMA9A7s&*`3?hS8y5H>!&uBX03)pnSxBNqrq7qBV^1@znKfNdr zFTmR-kGlWQKRX7N09OE1Iu$$m8uy|XcBJFrl6%KI4MOQuWj($3`IRJdNtE~5PBm`w zcWDssU}N908*x!Q05F!@$i@q)P1P$nKR5ElFwF}b5Mp`xeb}_O{1pN`A;d?yOQZLQ z(p>J6>a{&TxZ@h;gc&(5qN-T@=59_{t;hD^UIo{?#0%8HYT5sr5A!iOFZ&Px+Y^Ak#1Vu)jPN9URA`nALy`)Qs0}vFu&2h_4eI<6$pJyL7_)O?w=@ zet6TBb90=hI~AcxN>Gw*cJ-Goi7aW+(jtZa4ZTSXC)0YRCFVwK+3J4`6cGpru1QF_ z@GcPPdKRm?qLv&hTZE(a9ka5MlcmUO-~cE(*>&eSG4F-CPhdV24*f{D1F_b163Rmn zEHitWxYDWpo6$&YaY$LaH+8E?S4rc=ix_ZZCCgCo6JqRttGpvp=;!}@bB+>*!konR%IFZo|BZ` z=+4xzSeh2;*R3E852&Xk?R#dr8%`4sEE|t5+}bd&F`P@eB9^h;abp9WL-cItm5xH@B96Id!t=>xFM_)p`6Vw>E_9zq-^Dm#b_ zrdggN;!Dv%{^Ir#zwxEc0wv*8Noj2jW07w8Zz@*OXSWUwfb+&#aNjB`x*&4{05->` z`%IT$ui|y;7l>sQ^QfA6tOSI;{%`9FytNYeDGueY=XW53^N{{D(s|>#9LjDxIu7yG zSpDc-D~79NXfSI63n)Kpy z7JxtNN>OJ8?p3x~&C<7MH6OEQxoqCL?mlOzXdEw`<|1`IZ6Hc{<0CRz!S!e}-6XvRdkDOXv?R-M{6E@ zY(LpLk#XBoMAF0E)2mKY{vb%<0i9mxu^CAJ>6e}Fpn-XGtXo!@O zx~j%`vRTSjb}W=EA9I&-fLRfl`}>w-=oyE%Kzt`4NvU=HK^6`&m}KE7tE!UW#l!du zuK?^*z{4%!HRz4`lhyp!P7<1=YOL4J)t;;I%{;v7HF@=14G-p%7MuMdjZpuZZV%76AI@`j78TPYah(5TnGy@M6x(QV+h z5{J9{JBo%R=>3n8>91T#@f*yBc08Q`lm+&!@UA=U4LEt*i**%3b|AhBfNkn6&(+9g z6ZGs8g7Gtm;j|_N?|}?Zmzn)}G4>l?>|n|UoD|_P>u{Qnqf2n^+5iAqnttC{2K-h; zqd9MDXwx_r?UA-B>eDNi2syeq5kMvG3qK?OKAQ$K?=9sd+=YXr0Qanu=#Rs03^Gz5 zv&l9Zg#+ce^GF&dG+I0|ZmHFcq>o5lR)x3_x%Ip7av{cuA-zgiLn{X|&v`m!n!wGV z6FLJ!iPAh=3jM#99j`Gy2i-)BYFEG zLvZQ7ioP+|u-Ft#DfX)C2ApF$sR_ML6ZT}RbLD-mRjC;@C~};Q{rS!QHjSNH(7fM< z!aGXZ$K2%j%&5ty`l`Bm^-yhd6yOrd`u>K!#p(?AdN%dk z0s0Gp2NYDd64Qv_;Ap#Rcm(Z5js%mb!JTBA${XVRdSoP2R8zN-rE9Yp__ zwXEQv5JOj#W(ij>!07sqxsG0uQ(R;?0W6tyCjwh9&~bO$(A1^fsnosUB%FHM^ShZ_ z@I&4`x%@W~)5~XyQHtDK>K2MZ0ZzvTq$+S^=$--xCq06OL0|QqYzb8P1?yRJ&I~-9 zM^>HT9n>;}@`c7rX?0IjYNs+z^Hzk${)5g!n1=g&NT(8+MNBXXm7*pzN=tc6S)jIC zgpm;kRd#>kUE(_=?Z!q+DD5h6TCWvXp!He=Oa%f_b{R%_L_i{Tz9^!?q*OOxb)`aJ#f+l3@oIZYz zHcymdZ6y1}Ahr9bt4rrUtg;*I!3ZM%CJ6}Syu8&b7Nq9p_Q0z_imyCCmlLhDC;?=0 zw(v@{EP^F~-KluTcfwrOs0a)n9=FN0eobE`i9X!lvqfdvXOZ+K|RTNn+vj+QvNWQ`E%?;Lx9%_R2Lo?^l9?s~ktpdgO( zrSigsE@1H@=1UyloIm6^JstK3HOTlT0h=Riey!eh&61Ln607V<^wm=&Ky5^SeR z^OGM~%=-VVktPohcB)389{8$l4THXgbu#S;7aR4*fR+`miaA4+h!vcPrc4~dw?22g zpCPDr3KrAU7Ytu*aIFZ$-3azJu>1!c&jaueq4`8d8b|XQ%6*E6+1O$vUp$75Ln)cY zD~50up#zRi{#gm!ufoIq>jZ8&oTKVq?$h9D7fwz^#T2l9IcmvHk``tC zkt0t;e^QCppDRSpM+ks3{`A$15Y=l^ny?wi0!^%~)UynfxW~BbXr@m@5A#HCDQ4T! zs`LcP5LBE6Hp@o~wE>b$w(coVaJ5NTx9%~{cc^%iBj$%m{SnDtUAlBZ`7ya5KCJx^ zWxvQ&r|FEqd>VseJ)bq|aXN7ZGjhuoNhd*Xvbr8K_7IL57W5QV zg$Qw5F4Q*!yxQCr=?py9t^)@qX#BwKQ@`(T+q}dKin^8u4tMXLf#S%c>ns=zAOnQ# zIp?!U_xwcH^p3l$LHQ`Tckyo#V2%{U?#7zxYF*?iFmbUU2_ha=)JY&1l6J*Eg?eo0 zOG$gmMOM?l_+cRU`-twF-bgy3PLZN@Q{BXa;;((Znn9XI4*cjj<&lHvpb2j-e+jXUoPR)7U@$i14p4nXM>9= z&cB`0)pGF~m>!*TrhuWnERN*YQ?W^yKK}{osWI7nSH9l+1Y0^5a|p6z@LNR0swImg z1rZgcGqZWoqCtYKLLupd#~)hF_S^fyJo*8#Gy&eRW=?*iT^&v^dB(?k>y&zWKhUA( zCjRU(=dC0fF7y#wiOHt)v2|Z08LWdtEheb1yJ?N$ol7fREkXamwgI;QFMPJe1Qj9jSCq$|4xYm~w%GKzggynJyZxBSqk?z!}kasEw~Ck^)I% z*pT*fLzDz60j0TrQ2h1eU7il(sRizxS>tiL@yZgfL&0Zd)Ryl(aNv?_ZHc>&|2f@g z#5vyv-~d~?^8l+J!p_bQ3l0{;vi0*1TD51-$$k4~r9EAEY>KD? zIdy}R?x{QSKIQ`*q?=BXHY$dL)v0g&YLl1P>xpUL6f2QQ5^Q8covgz^Rh5QQVKGek z>CE}E&9`bEx)FN%Sm_F%)!FBZ4eaK@+ji41trR@A=u-#hp~Xl*1PA52<>t7C?dl}o zmwoBCe(rGBHUn$xt#j79jadaZ(|U^(w>YjqzZY+p6%JG4b3iuxtrX6VKIXG$&q_nZsEZU9xJ%i%w%DHWA2_T{-*CsZmN)tilrA`Hdc6oYuo`MJgNS!vhBvs@5WBADVDWmth}1V&{Nn8Oud)q@)31A zy17J?W4mjYn-TMg_-Ru^H`Q;1PrP-HV=?DI9)bEG%URD4v#@{V<4pGKy)oyS)31-p znB-`*1rWG0{mN!d@i3ecxo*guk}Gg6CR47smW>Oa&d{1LT8m7>G%syH-;Hb^p_eM$ zIvUGfYUwTfG6M0HRPJ-1bZ@AVVxfV8fxjftJx7;j3Sw?R(j4#xR3u5P5O_UZe4LhYFkO36!o&(pWxJQrBQ3qiKUaH+CTay{P2asLq>aR4 z#z_4{sU2{j_W~<%Ft|?i^Xnh(`+ofsvBf&O@2KEF+59z1R8+^Z$$#@dFnPJ{8|oj; z=JQInCm1QQ^wmwhnm$>%qWQRedus*tZ2eY0O?mnnRQ|4+I$L%MgapE%;5!A1s;2bN z!rtso3S-w*s}u_>EYAGdxYj%TA-}nB-9_sSr@d~)NJ5r`IY*>O& z*r+uNMg{G^Qd>f8-Y2+mW-PS=e_86`MB|rCJL)N(;57~EE)H^f(8Xm3caZd*Hrqs) zZ?TMHrJLXs!TCwaI48zhr7_1f+;@9HZ69|p7j^Y4t02;%yMe>#zZRD*i@WX3WJjk1 z-DOiJT@9uhD(DSj1CQamx8@n!Dpbf;zf(;B#&%{&ir-3c)j!;=0dm*m;IPwYpjAa(i z(~^kcCdFGzI0qw+%?#_!l0)Vq%$lY<_c97ZWERhuv5o_#o7vwTM82tP*v)%6dg7O# zS*SHk#^zI)P=(r&7ZUd=rf0j`3?-?{S)IzPPa(Oa*MkJ9KF()=9yO4r$&5kDo`r{g z&H(viO1-YyX-?tjv^1|gWxIedYC3_m!H4=Mt)MJD$9te{QuGO1!89QlVFLR6p52ZE zIzpFG>Am!#@>Po~J!KueEjHLjMs#prJF$Q2LJt8ifDQT1tg%}jdr9j@U*ywFM_Ox8 z$BPq!1Vkj>ZvviQxp8A0S3BetQscnVjZE7?40Eh{58wC^(5n$M(Z8q?=2wMf`nsQ?4hAa~=srxybaY4McDf7);t|+iI`Mo$(JU%ff zwd&HP38vro?-dhfDds|P7>6ZqWq94lkt6+vM>vXf3;;v+brk?5Q^hW*$^RyjH0T2! zQ${nWb2MAUd(PVzE-~~nkXtlv=>w3z(fvCx$zPpB}Tr(zBq`)tU})Tm8#oKG`qG zMC2+$3ADQE94uh&uR^SR_bPfl=dYB{_{Wwdea}{p=SNO9IFS($k$J$<(?D)wR2y$K zKKD=NT)A%_o>uV^u$E3=%)}0TJm9BpK6gS}9-}`Z`~Kl?_w98DJvzXqt?oXh&{dsA-!|-UFJtlyq+la5)Gg-~ z!zV*RQ;zqmnVQu*M*9UaYMG@Kq;a_?>h5dgpb#NCiRycoumsw!P!Z;zA$i4xF^bQ;3LJkN?}t=C?+%qicv1g}u08m( z1?=pb$aiKNbD9RyeHAquo>lK+)@+|9kHNI~_G=-9K@DJXtB0>XzjGJl8KRXl39RD? zwogzhy3*dWFutgtBVWf`hv|+6C2gV@*44GC8oh56-+cnrFNIhSzua>~!Z!(!-00*a zjqJA>5gFbFj$n&v7uN4vHNi-mvFy>hnOQk{tDau!4X^1tyzWi84IiA&Q3HvrLu>As zd!cc`uFM|%**J7e~nPaXeYZCCVxrlIosb#;?T2Ta>X zk?#D(%v-!$eI}}Q;&+%)RE|Ci4l>{S>&TqL1#W>ViFQV2a9_d=hnmLY({;93^gYcj z4K=vWn|9gCy8KzzY%_bzdSPNg5M)PKFqIMoz__R=YXQQGGoSmo&^*w*0mbmrf>!igwvDDETZ6nHcFOzC81< z#O3_gUo#3-w>C`EakRI$cO9MQ;|C@ihmECNUKhq+7*m*{q&R;3MZ0r;M&Yr?)2p2+ z=1Q5#(xQKpKPToGZhkPyF(JpZvjjrg`$gBna-l9Gm6zu$O%cP^MC%YKKdD@SXW4^F z=O*o>pqx(A6P5^28j);gG$xlkGl(v6=bE0%OLpL-y0zz!oa=HZ7U&PAVf7S}f=}DB zvn$DM!y)bcYR5|3?y+&t@1GgV5ng3Ha}kqd$a>d+!c7akTrYa{TN>dTx+JgsmsnoJ zQ`(m2z}eCJWHz}x~$AnkWqAc<%8(^c=uxR`La>D(iM}e_lE4s z+yY#k2aGRFF1P+dgwN3|=71z84_W5j?0HVt5SPS8@ei5ucyu$tdtx3GeT}b{(FSU> z^6_gbGn@Y}TnoR~1FrnAH9}(4)>f0Ao#(8q{!b~TfIIShNEpa_#6pVsG^MXds-F=kE^3sm=h={GrwO&VXt z*cmCFH4Pz0t@zs(bPkU=zQxox>56e7cHLu@7?XL0vlwoHu$S$xG#mQ>Y!6s;sLPXQ z6Yd$`@N#tuIV56qLeQ>@L^9vDEX%ziskTDU!|9xd2o)WyG!uBJ&_h1X8iRjMdK zNFbmnyBS>pZxQ-#p89lPJbz`1_sv&=ILjV9qX)H*|L#GNdHMNvM{?@sN;~?SnRN#* z5<2>T=0S@$ds?#E&WBv;lbw!@od~L@>962=V`msF<>mY@$Uj0p6$1NVmPjTSRF8F3|M@4h^%)3@&? z*jna1>kwI%>O8HzR|4D7t@X6@c?QTQ{gMa@1IZ2P?hL~#JPDytAD}Dgp@WRVW3&?# z75_IEZ2q$=dgAmb-^gmBH&VWN`!-|OU5mdn3@i>PBa)jZp!oA{=bbQi;v}c=7wRB? zb9ZtBMe-fYR^hD%b*))i#>3fY=Ws@f(OMjDr5lNdIZJs?+g?K%R@2gD&vp?=S-rGx z^5VMM+DfV}q1DE`lbsu0>C8n zGwf`Jk$AR#j?hl1_b$(D2YUD~hBao7UPs)kBFu3QyH_A`{G?Q(yBBI?b>=C1f}e`L zMD-j^*PkYGYM{kXF*^!;OoYXodH+81O7t86(g_AP3Y>-eb2@3~WD~i35>L&-4l_xJ(WD_ZNXn;iu(?DlK#c z86-E^Z|08Yl)zZkyR$&eTh@Hz%JeA-%u2DE&P~c*N&B3mZ!MMXIu+^B>AU4b|5Np2mGC#18`MfaDczHA6rG z7V3y96E<2HcqElHmPH&=BEf+p=po1w2>owS8~L7JKEK_>qc$t_8<9XUEopc9PA|kz zHy!3T#pO=_Kjx3mCT^|k%WPaD&<~v?U|v;FIMx4zyLKgGzY-DD0?4q=ojWUBzq&8* zurW{n0GR`hA=kVuQZK^1q-A1`sSv{f-w(<^&YOW9q%+R`Gb;wu~s;_ux%JJq`^FCj+X zlAb3H*c#Cwy7|1D@BbRVEdhaJ(=!9hblP1p{D!UsucEon(jX?M3l1B2ixd3WZCk^2 z+VaWO%oABgLrXug5j=PED7)2kCRa)({|=Zgq#x|k#c_c>#Kd-_?{|+c=_cOGVvR1X zNxil*=(MFpgW@ayfc}WJe4Gy-$4p1T9#a-3kDq(BsVRo@-%=`uN+skg2oD;QS^x|L zQcqBfx%YVoY!ggejE1|!UFZL?%BWqInlI;(kO3@ObdE;Sb@6FnVE%1zM1=hR!U6N) z2o^?~A}EU`XAGv215=R#7fo(JS(G0>bn$^?Y$j6Udi|~%|HRTWY&~OggwB9Ax=ZTQ zJ4X&3vUhh^f{XTjp!XSogO#DHs~i1lKazDzHHuqq9vhS(CMhNc;OQjxRB}RyOm;jW z_Q>ybo?>SvyoGhB^4o9rDV3DPXCMEYeCjm%eQLgt&`=qsENM@5RqeL;yp5_6n@2d1 zRHrRky7WBG2T>`FTHf9nqUt=L7U8ik)%Kqte1@4=`H&?+e*L7qXKg0YoUat{$qyMdERz2i7&h=|66v=5tx zE>dhA9vspk>El~&0=L#JgOnEZ)Cr;E)#4bogAnjyhRi-6*R)6q(!_YG_Frpu!v;Bt zh?bxbD7=*5M@&CzOaAD0nF~DSzsN;Uz^nhfaqrYggVKZ$$r|m!L!T!v)pLF3WJY+& zY*kA~{WrI}Hsz1sKT}=KR;6N&`X3p?zRQ|fY09aEY^%=aZp)NU(CcqCEaY*3&PI#H zXQo|xFRPl+loL?+ero*%iXKi-c?QRN=iqfWMPYv(+e6cp+VGQp6QtC8F zdxL!cjIRANp4aTGi#XJ=i+_J?N{{6H*86g{wP%KCcUO{^?QC0#MxAo4&#M55gnAZy zc=@}g3|$3|w0Wl|GS8ZQ>QkdF?LR0Jg@JL30=ITmZL_cJb4mVKqX+%=JO26S?+#vJde&d2 zJ?t&Js_60K=@{*GHP#m$*>*94MURbs^l;hdC3EM^Td;I#0tW_?zXzBc>H;V56VLtI zUh2q2vmnsuyY-X@PG`KWcO=|+Spy~(J8=vau%=tLl0JP}N*W5Ra}V++Hyx0iK5@cc zKh0)Tp9>^&gqCSd@bFj=6_xz{{red=tfiWoyAd~2Hu0afwzj?tavbijJ!^C3c1?}k z$*F^6Vk!^KmZ=CdV+|y)U!QmNdFsUoy#^~JI=U{+`!$ab9X70L?%%t~ZXLGm^{sX- zhcj}qF+buQ^gErouYBhI&-^#@4;EgZ)pYZw!nTVE9OnKdewmv$ul{dA;%b>5W3KM> z3<&7D>4NO03$e>aZS))%H-C7_`}d0+c6$|g7iq5YU_x1A#9$=oA!Ytou3d}cpt^SB z#-W;lO(ySd*o{rTH7DWf^SG^~*AFVT8S6^RUURG7L$U-YI(K-ICje~=+lDxjYcZh|GYMNp!s%{@GB?pIs gs^I_sKUzQb50gt5iW1gDNccY&#|aJx?1I<+Kk4u4(*OVf literal 0 HcmV?d00001 diff --git a/examples/example_saddle.png b/examples/example_saddle.png new file mode 100644 index 0000000000000000000000000000000000000000..bd489f1e6a7560100a4a46dd68422da5a2724c5b GIT binary patch literal 19981 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 R&1@9f8i&;%t6ELfope2z=^r|WfS|k#g zJMk~|7W|1(+{i2Z-w78*eV2>&mM(6lP8OuIrY;V4_AYidW_w&MoSbd!Z-@(v9ubxj z+GFkF;^2HjMCAJa^Ap1MPF5mSS8lfAQ)nDiE;*A(?99Z!WL60s_^!(`stTvI-JehQ zyBV}hZ*HCIvN;shB%qzd__VnBiEN%za}kY2_O~BD4%`m;Hn6wv-QlSBA$cs9qLLI< z_v{G5Gb`ln*KE=JXT%rw-z{`_1=2dEh3sSk0p z&iOMYz2qOF;Z;!8tYnc7icIo7=+C%ck?cG*uUas9Jfr+$Rw^0%${|{Jd1_6q|L*&a^g`IO+&VM@L5~uT=@73U7KA78a=!C#t`EdF*z{ zzk!oVMpMA5_4%E9_wJc&8A?2J@5s$bZI_w8r{)&^4cz!!TITzd5fKq9)m#m6Gw+?g zt6jCUOwxENX6Hd4p+ZSdPyblx67z`@C)(QEPd7&H*UY=BYHi)-L!Y$#^L0$a;rj}H z3_NmsczMb0-@otvcZO@A!bfCzq2?Zg%GtAumoIbdV{UXawrzQKlxm~**|TTF$Gp)> zx$sKksl9hhOw1{D^>pfuLVs3STK8&0H8t9ikrBq-y9JlUg@w1jdGp5LT2oV#ysqw! z$VZQu*xCQKjNez_+qrvp&6h8%mX?-Bjvfto^XAaUmKMz{gX5{`>CHLU^urcz^4rcD51MjvYI`te?3W=-r``+xYS0herp` z&;4xQ>mLxX?i3R(phL?hsH+e_%QnA$DN&wkCkKc8`ST1kG&I%&WyhVIoaWyLmXw?z zr=ZZxyj)=K9XB;)=ee==lxpMk`GoNBaNp%+_p)1m1O^kbjVm@LyCOIK-3tullaP2E z_xZ~g|CE&dX?oeAmkX{Xdath@Id*JgVwQ%N>NMH;U=5=eR9m->R^8p|zP2nXCN565 zSiE2T$yL2#51ymfzMLw!_SNL-RqYpDId9*7SepJO>bX4USyk$}GCvzAO|rJOu54># z3=9mUTD{fN(-RaLYFSbC@??2|>KQzDIxk~mW6~+y)`e^%SqbYl5}uOs%KUe_#XmGF zOtkJ={O#@S)Z4aw=r47nKCbb^bvKB{NVP&|3^1lQ)DoSZzA`{j$ajQ85k)2B~QjSkn`YiMn4 z{d?l~@83aTVf8)vR%9gNDX*{oX)kg#@9yb2edS7NgYChz)B9Ei+V1kqEiU>O7ayPg z`BMIanW+4kGfa$(zW9gd#=%#~$#*SVo+YY2Iv~_0sGGt6=sNei8~wqW$x37m(@8Y+ z^Z`*(I=Mdm_NwR4*JB4Wv9kJ0yUh$ueoYSw3R<2Sf6T8*Ua`Kk`aJ;@_2iD7dd^W<=eZrV#VO-(W779T-@3c zE2+Hjqs7$BjITzGhJkhNrT)v8FNZoauTWD{S7u&$S1xVP-r2bWi=d1rY-(k7k9ogZ zLr%x+w|pyxf1h}n?=vzp`}G#sE*zeeHZwOL9v{D#Z}n+ptnrb`xpNyooHH{r+R8kf zC2TvlVL8M+^c1d!kjJyWvHBEWRbRiAUBpO1NvTdRAvr56^vU5X)s2lbvbPpbeQ%1g zFtDilJ~sB^T-*uw)x|@k7q|b;%>Dj7Y@kzxVp9F-6T`s3z}(W3lBegIWP+`Y&3$g! zTQ3YtT~Q1oOru^(X;waxZ>D4B;o*_-m>*03gbj82^|`pAu`z#P!;%-MMq*j>T^=4D ziF(;a8C-a>SVzZ%XzJ)8Io8X24^5a6s=6Kdt-xMVxJie@oYwmzRfdx{M7g zQXEI?c8XhWyY+V_Q{v3x+S*!M$xYkNrQg3#|DEYSuB+`5FXQ2N_)4M4r+8W8bw$HM zyGt(9y=;ahE~k6)ERJidc(YN-910KPIXUw2srcNVKefNQvKM5tu!G4cwwNsbY|kku z_;}r7 zK76Ei0G&eeMqlNtv#}0i4H4PrVZh>dxIe zNUncshCc>tZSpZj)cW(QhiZn6^js%cOI@eQNsmR0xpDVr&dahToOm=Oil$psQj&xlc>Cl%=L=sw$Jv4RHRWFa999=6 zJn1g=^!A#edH!rmysLJzW=#csjwt5X&159}#N9LZwmW92Ee*WCeeZj7w0TV+y~_FX z>@vE=P8QpCiBTTD{PslxS19f7xyK)A1r8mexl(9XKD7mz_W2J`bnPPl|muiEQ9+&LDUa8j;b9=;y-bM!+E^5HdQm( zVZ1eN_QpMxF?Omlzs9DfYOKDfNhOK%gp0}%`6Q?BO(>`zPz&4B^+afwW_L>)~f zXZ#V5Vu#$lxl>#y+P!L<-u`}fL%l^j)Mf)ZIaiP$?H3qMRJzq|>$f8TWc;e!V` zc0FP!S7z9UP6ffqeAc~h4{h1Dv$EG`bN#Pdw7f1`fo+$i`kuG;gYj|fA+=Js7Wuc( zbNDZef4cbYA^8j6XmcEj?h2oiTNt>v`S|#_8tr2?#koTxs5JIjS7gzlHLVS1Yy13} zg0%0z0kh7GOV0%(d6t%zqzbMx3iT&Krr-9E7aJO)Iqnp@O8x$>&=v^|Et==Yowsmvb2En24tky|cKV*TrZHR5G<)rax0KbI62ufb(~za`G5s8F+y z&A`B*&#lG~x7}CdNKHp~H#T;6z&X*jj*ed9V*#90M2dcTC&%_6)IMEuj}7N z-Em*|L66V6f-U@esER@(_Si#17nOa?oX4Fgi8W7F4wKSTAnw8FTzeklnK0nTaKKpM*-|Xhin>eHjfl(3d$;vgYt)T_V4>@Xezhl>gFwqPz+80Tr9l)txi`9IPv%ukH|IsM+eS2;8z}S%RY-!KYO+|Nik$-csRmd zQ(UWzf5>9r!Gl}lJkiZ3I#PgSkD@|9@@g%0n{|&;4==U!91!Vy{+x3g*NMkB+&A}GSy}br)Z$!B z8E9*>Dur=B3}F{>%FwuZp^`lxpn_;-euAI@>IN0wTr3AP7&H2;u3v8nt*NfQdifxe zz@@x>#6#$`R$#(UJsj$MF4sEPAW=D6C8WM zMXPb5wgQaMJV|TWEBj?ix#pniZ`;qW=&^z8dg*ouRidvS*L@lD_U&6d#ybG?NiGu| zQ)?UR|AuE~g!Oktu^y&fHro@SGB;bXS>4|sm-!7yil{D}GVXJ+u`=p|7rZt%WHasf zx5~)KTzI3kAFYC;sJQt1x8otK8qvar1T^UG?#^*0dS`d8xedRCXrix(*~G+zevw0> z$#q9BFR$g*)sgIqjn$b0oM-^|(EMyq-d@Vkx7~lz(!^xX&$MFa@$D$$jaVy7RE*t{ zHwct9W#Hl>K|IqJFZP5+9Xd$6%(pmPvz6#ydRYdh;~5zl4huid0$cpKmjJeDVqsC+ zi^Er|b#Uc)XlN*E?-AQiankI(yu8@>cB`wa{{bnRAK*o%czRMdCt2&89y3sRGI{)& zWZPI>ytH;?wm!`fUz~=8#`I$RrzNW41LfiBb zs=zan4(007bcnAnNjvqzow#oWw(R&~<-K;nBq~o{upaYjf12gFVG8MYSd)w^?`EQx zlt8Zpq+j_)i*QN1en6AczYS1T82W>KxRx0 z(7pUCyQ2H@{>jP7ZWIUx`&ZuQvD3O8wY9Z1AFBqpp(giuG3V-UL!-k1DpM2yQ{3hq3f z{QC7huYXIq1=n!Q?))CD$44E1eE7-^)EFfI-|pg}Arp;f5~_3CpR#gNt%=Od&sU$^ zT&uZ386|AUflVcke`(iy!KtS7g?8)}7S3wRRt?rgT^9w?=^Oy&-_9e4ll?t0J)I8F zcN?cvcw7jY7x2D~QG>_}-%VBJP|m8=Kff-01D7TvBeU*!sTxt{eV=*1iJ4h-uknqN zvtu<=J^2LD>f{zRqePLwVUdwIdUP0Y>CMI0dCr=enlTbKktT_9^X9B`Cx_>TYr5as zu-Ibv?E<<{M5zc_{H0=CTmRt$>xAv2B$T2%n;ZYa&T6QusZ|5RSU<64(!6-FCYVj| zh*c}6tMGvXf$|#%H7nWMP)>6_7A!lnv{Go-*4L>d6+?obKHVk#N|<9sr{J1SmB<;>C-u z8e}4a=x~(lwP>b^DGnjs=$({T4%L15U@||}=rUSB;ve!E!tLw7yTN69MPGji%kP&y zm2`k>?k)ABwQu;`EBxp9A_)El#{R<~%4y}_Y(=g~6?rF)R^3~h&(_r687@@%V24Xm z(y2dl)Ct+4|*|bBlleI4;kb66}&R_oHR^;_P68rP&TqH#fJ_ z37%K4?(6ICx8eWohL5U2i6Zc5A;qKITv1`;a-p5CjSTpQ*v&1Uk{`$araKbdwexS^>;%boR z?xWXOapIbLLv|dl#@|cnzVuY!W75sg=PjBYo}CS&-+io&+`{-Y*=B&hKcz;3T-@1{ zT^${M@7_srNLcU5=XgmWCoLTt7;uO>LdAdj+dH~WZOBuJ`ws@k5WQ`t?|y5XbiOk< z+cIjZl~;hS2uU=+=;#rQm`<|JAdBTm6 z+{A;24=?=#krvCovsQBA<|MW2of5ZMZh((7hSZ80dV1_Qd(2Z7P+_k1?oy+rTH6F0 zU_leD@6EStsV;IHE&6(~+|$E#ZKhxP#?5f!(UB3p<&~cIwVGNUl6U|90B4J}H=dOMB#gG58K%?dVrIDMin93I zni1RGOZ@aSE?>SZ~ zRO$}cR8p;V=*p`p@`icZ@}*sqDoQ$u50XO>}e@7dU6Y(o48 zM6jC=9lCGK`?3K#*-%^J89&EwSy@>{9t#KR(;h#9Dz`M#-`tBj2zEnJAH00wjqfLX z;@ZkWkG<30y?Z&O97r4-9M1)tKUG%xa*ZF3J;j=kn8=I8?_OmDd;FYS!tn`Nu9|p1sf4}#fx_S+6LdTNcBZ_(%A}V0_-Gb}aujhhO zgtD#c4_LOInpw2vkj(TXm=gd>-V#u*sQaABW$uz7T&v<-^CA?x?qcVI^}$z_Cn$_i zE%7O5uXJ{G)kBN!o{^qc)6>&ydu7wrWHZxO{2w0SIpIPL%1)dDCs)@xqJjTwh}i4C z_>&1Ab7q2K12m-wc*r*U%De5ly1JZ#0x=?1pS3ClA>#OhR3xIgPW7R@=3e_M5UO+N zHLfe4Ndx)?WWNim!lEhLi94dTWOSYAP;@BR_vy=*FE27PTY7D5Y&g?-YgHlsf{3u# z35Y{3pZfML%v`5QSy_22U~b^E%QvQK>*@s7wYI43o-=lBL7gI`fIfDnf4e-=N6E0` z&Y@gE)>R_#!*NMT$==GOprbY&lqlbp*DYq#G<$tF^K7AF9Zl7K!(1Qi9lyv{CeT`a z@#ujA2c+C)IKR>D0hT9D@COH`D>){2nq0o}TT~w0w~ybh>w(0??y9C$3Kf4K_&}Vz zJWXxD$)CLi=_cYFT2g6Wbdn#FRXC0+1$8FYVX=VY-V*Rb~@HDlKf{Tkw&W-*Po!$_!Ix@MkwaI*NO&{v&$Q!sU zQFxU)KfMZiNn!N$9rr~YpS9mx|KkJ@3jfoUGGA*wM?t4Nc<^B7K%NC1l>Lv{FZf;+ zP5dRgRMq^8WTl794vq0YHn#Z1QijV33I*#c;|YF$9>;^z;Uo6S$;D%jR8?2Y z=R~JRM@M%E4Q8;CLH+ewURlXq%Cl&O2LB;@k9=?yYAkV7JGV11DC4mHR4b zN^%l;bN2o218L8I%o|S`T9xP}pmeauMhZY~|*mNXj1&uDN{*F0n z`()1;80i-1EeguY_F zEuEoZVagB{MZMRSJ-r&xp`vek(At3MPjNv!eJgMOB{S!Z<4oW2r6(5+4Y@!HhYU2^ zn_F8g>pPlvF!Egomn13}UCM_K3U)nt`v(`G=USW3lBXJ$77oP+yng+SR5mX_e4gEf9=4rRYOX)443^Jj<8ohMlX}a@`uh4&RI=xSt4C>f z&6SsztEi}u!b^9b8+f?SVsF1sT1rZ7E^`t!;252i)%dL3y%HPRI!I!W2hFEZ;tq|) zZ=+?OgG)vrDgE1E(h$AJ^)-J-7*QAvNYWf!>o0cR3fQzh%TMtw zPmZW)X!rxP4f=WCsirE6j%JthUZdXQWe2&1zbJhg`j2nNR8}8mmmL> z5Ur^boT-(fwo}?w$d*US!FO6bk)KsyC_gW6===BD|JpCGX?<{T^rBow$G?2$`TcHg zf648s=abVt=TCwq`Nqd{`}RH5W1WspmZw^S6bIh0l~kWUHYWIotOEeKX zn3(Qh10yyk`PU!0Kt= z|4?(p?!Q`-TWgkEt+DfBkZ%P(qjVS`CkQ9WF)MX>|Ni~CqgZFh41fMuoG`-2fi__^ z4jF<5g_dBz85-Viz%OqU!q3pkc^p(0`Oo*$x12p;z+Y-lgrHXRgl~60bLNb90AtFR z|Fu_cPJS^RKNl}^AFA3Y@BydbBV!2Q7V3dzxBqUxbLS3DglI9ta(FnSf}&z>;iA2! z1xVMHEn5JVo6?|3!);W6k(GTifcjw#)>U266U{{KJ(eR1>k85SWbn71iS#ZdO)+W1#z4 zT({uiICS>J(3_Az)`Rd-gf73OzPFvrtUm!&7gDn1^SHP-#zt!B5t{FA7wVGmH(Sj38_+D`*Zkmn5lQ6Nj z7tycPiK5?mgoK+UIo)$;UI;z!w0jPklgTd&tSWVqJ!`-b6me)Ou5|nOGZ1fd7PZi-o91#^A)_*vqy44e+vXrj8 zb_AqJV#@>1(B7E0?=Oi8)Z%mF36^1EVhVcjU`)T|U86-MO5-SMb;dLS0Ct`mDD$WZ zO*#0Ikxv~?R~0lCO7**t(MW}bg#!H+7t?hQcYphqUsx7;Kf2ks;`U$RZ!LgFT4?d~ zIq; z=Z>O>suFR zVzV?gy_2q&{U5PPFR`?+Fa;$gfRUM*nUo&CbVt9$d^dxLTO;rKl^noZf#S%K=4g=~ zM{pP{MnjhkL|JIqMV>2S8fVY;c)+h9JYzuqsBejP6;(^_@$TEFkQ4Y=COq9YT4kS~ zuU_rUE>ciPh&kwyLOcQjv`_$letRb(9sT_I1DHU`SatV*ONZ+xgA?N7xCFFQNqgMl z+$Ux%Jof03@$A~ws1z2Yz*i;UmbUNMu_s)yPoJVtg!71tiB;dB*hWbJiAwqOvLNY07^nsY~SO1a3te60wn;y zKfAIMOy=XoB4%s=_(Kg^7NgRAD_o~Jq;yp6^I4J1EYjgU^PAnPYxBasznS|KlW8=O|U^YiCq)pH7osIP>^xw87>(W6JAmLGRY*bi{RbRE$* zZFg{RFsX-c8yVYv#))MP?aHJc;^&ASwN*#IHX5e|iz>C|Rs2V7Z3ug=dHwdP?G`8r zr(j|qasHw4^3|((!JKd+Jp(zd^GU0C{21R5Amd%Eer}<_R!;>5O6kJ9yb*mGUPX0u z6F8WJ#g86R(+X+d9IzO7^u!=Lb7OCZEW>=q*}d#!BK@-F78d(scD=s#va9En@4=>q z4<9H|H>;r;QvQvBjyMdQiMwr_UY#7=>t(8@H>U+~AC)~oW?Gp(`Oq?ngOiKvH^Mc9 z&jOY^wT53cF*m=9ez6xyh>H~wzYFfdpVo4IFVa+2^`-RFkx z3>~mT%m>(W8{v^kIh^7xx&HCvM_6u+y&ddXWeyC>2rp6Vo+of!{k3H^4UG?Itom1O z9nzA2dWFJu;?1Lv!ujb^4W+SKzLo;f| zhk=gHydItEZTnWuL#S6kX^Pg?`~`Yd&E&s+dJy z4j>>PD>MhpJ5_`RC9wW}PntTaG4@(5PSL_)sUmPDB84On!r}S5Y0~mE)BPTLkbC2+!aLImA0{E}CY~z#2>fDb@FQO^A85gs7e z4q^H>@3>_=VEw)4=7*z0DQ?1Ae}ri!8jWuL>eQJqcJ*h~M)7t40VU!fP>vX7T(J=( zuJ^Lykw)6IsaO1K{rGXLB=2P(r6)Yd8G7$qmAtU+{S8e6F)-OWC47l@9jL*Ks0hfkMU_4OIqWx8uf|hlEXs z!dy2_?(onM3D^Dnq))lNeVy9)u21(b^^rrEqG@bw%*bpaONfsb-g!ECGWkCBp>5DW z;LYyEWUr$r?Bvc0TXZV`F+fZ_zj?~4*_)SP>M;XE?i&)_2)Q^$#Isx00!N+IQ zGpw|<)H&gya*&v9+Y2(-Q$Yx)V;LblwhdYXOS=U!9 zg>H>FsUx`HT74$oF?E>bJCE7zL_&uMJ|x?AW%-fqNs#j* zu1&Ml^|}irD8XA@-ZB@sgsZn`eyOw665-DI%b>-5fVcQN`pE&dxGp0}L!0Y%srZM5)-|Lns5TAbtA9Q_@0biPmU!5?L)ni@M5pHlMe^ zNh0w~ zJf;#mCOk5duiFU*N~}Skx)Y82#D*JkIaL?->+FG$g5I&6>qL&A3yue*N1$f;xjxtv z&Z*&y^ZRinu;I6XRMepB4^K`C=tpytkRl-HyqHCk3Hap{Ar2!B@&ImS!&}Q4y}7iu z&5gqybUZ}72Pe!O3JCIIxoGxl8~@vms) zPUhz3!Y86+qJ5*eue}uL7dM7j=OH)*2K*&elLBsdCAu~sYlJ!W$xg`|oZK?*zHqzE z5y0}xdMbhd+*6}k+lDDYRn@a+E$V^(Wz>e?N>Lv;?p1tl3$KDBP#Q{ZRcq^ZP>?|` zvV+a4=MA!kK6i9<=caA()Yp$O+3PFPm2<$K|A4xB^Q_zrWDJwlq_#i$2%oY(nQ{g; z)5lOCs1Q5|hm1f*E$zVVT&peZj&OKac1R1JbD{cl-=z||-ZPZH@FZ=d0~G$jUk=sZXOR4|VYj@&=bmK?h*p{DWA!R0U2S&n0>7OHlJk8Qz!6s^hVX z0ht*NSouFXYLEG=k}0!ZQj`M$f^+%9xZPRfldF*m94cARP(99p&xYw_DkmaJj&|F!3WEKmO1u&;IPRbNI^w z!Y!vOVahNnnnTDl1 z&`}4yT4N>ck61LZBF>xG{cD(cu48>Bjdkwex#uSJ2weD`^`PMmz7C!gBjspzCw}+t z-67~gZ0;vc;5JYfsWG8tD}w+NpQV%iu^X0e}!S+#1qNaYmO4UhvMLz6~w63SN`!Bt^5A{?@uPf+G;V{uRx_@6$_wg;&4C1 zVKzx~Np5*;>?&d96~x8G9oIcW9lKW!%qGY{T5qL7z=7d>d_bi0=GnaSokw;*G8p!qZb&-vb z0J^YY2?w&r5m79H zX%Gkh>R}m~=UjM>v_Cc)nBVy#er8_ZF}O`JIzB!WAD2@|czD;Sd1X|ro+0A@PWWW&Tp zD7A+2pb;)Ex`cjZZN--DA>yRPXU`t+pJQWX{ScZW9=a9n$78q2RSSz@fNYuO6$@Mm zApqEhaU2acKkCmIk1>YkP@NM)MmYbUXH!+?@C!z$>7}7tzL6CNW{} z%eQ3>@M>scqOsS!?m=Q~x_@da!YCs+?HQT#WX436E+dGn!s-)uVQToDJ7maCNa-n1 z4=bLifvryU&uFevb|}TCMi%rv@y6TWS~G-kLJL6pXwh3kiMxz^z2SChJNW>#8`#t& zUx?uWCBY}1e{73wLx(gD?CHSK+Gms)dw@rLCx^HX@!jb2V%|a-DdhuV;04?uQBwf zZReOl1m_k{#Hzr4R@g3{f(K((4|nVod;;R651(Bf`BGOGcvE&^A3K>b!0u9u3hYok zjN^KXUI_89+D(e0@N#gd1_hTQsxSSd-1@t!Fu4(Y!(+#eWgMREmjIel1d8+Riy9&5 zaxtGg^?Bo^ivLK$(M`}~2L5v=J+-u$K~b7|31hJNFr&QoXM9T!<1E7a)Ia{t?UMvC z+bx17)t?bA2{%|C-W`dU*MJEy3XtwEIYGt_0sTn`9&RZ4KyUA}!|6|Pgou<~7Jmic zEDV~!0%TB>ugo*aBQo3^3ZLk>E+0NS`^_5~bhb|5ZmIffo$^l1cu0l?N8a;+oPL? zejQu=JLvO!ZN-`73xN`>y9$pzNJh$IejlbmNW>(;f8-`2ypPHgqHWJ_Jfku!*Wym)oMI6}bqD~bO~VTMTr_dv1@TKv6+1{D|#+!B%>XecflCFea4{355e!VNNu) z%u{xZ$qmg8R)isjD}0lvA70m?`=c$!s&n}>uI%;t-IJ=hx7Z+0L6~BrIrk-G;0ORhs|Ij=J|O@-YZQ zlJkk`|THa|Le;2oL0nVBC*0>m@Pw?!WA?t4T z6W1J&sh|B&D3^42M1-cDv51h+HlSv!>Eq5nUI0SYF8!hA4YoiXx0@=3<6v?mNG9?Z zHaC7FuU`{^ayk)_Y_orteb)c5W4wbzA~ZDk-XYVrO!BND$lUeO!LoiF8y8!yI72Z| zSxLIy`_^=Nq@-tZc2-2c%L8cDX){cjIwB6>h_E?GBrp#=6348c^;mZ@)029qPLbs0 z<%1Xh)S8JNI3NG97c@a0r9NcQmQkKHcCpq1R@VgtanJ`I$WxXT6cm_5J57{!E1H7* zLJ;D%1q9&Wi%3D3tgid2m!Tg~II`ED8s|F0 zu=Gj6m3KGjDJUqAJmCzWT~><{pwYeV?B=%lJ$;!lOgf#75Ds2kT%^6i$E$Gvx$Ny7 zUD?J~*A(9S6S6`?* z=`*Mj2>OVA`=^UVzS)MQ$DT>pRQE#Q-^utxVX>!m_v9>Lz50RO1S7%_qv#n#AZ2k+-Kh{yEP= zy~==7F|n%D*Vxp;BH-Bd-XTbCBqw$%XIEF@w)h}`VdZPr1V{w^_q)CVxbr9?;toI| z7Ize^V|i^woS0^O{FoJWh)-6Q3q=C7{gkEU0W`UYxVRQMVgj-ME-a=CdV2S;a1s&{ zFHru0(TLz7ED>L5df3x15L%Diua2|Me(TmP3^))YIs|NiDxQ1v&3wZ`WaVITIGQbT zP;(0lcQH%}nOH_vmY6G?^=?JRgfhP!X=oE`YyFQ=%t>n)yhYK*P71@CjG{*q&H^(t zGcomu7ztD$CM-ccFe!KyfqMol*kfM5K8W-lJ;Is;DQm0&^zP!~;)L*rfE0OnY3M%) z6N#Cb>tDxTzkF!|1tg=Nc#iZ}i*$ehDP!9;0DQIQled4Z@$1Ef1c z%UmXmeK`O4$RNV^{bBVM8PYZm32N+HWi)N0a?ksyoaRWx8hg!P1_%-k#U8gjV$x-z zPJkC9ELw~^b_7D@kh#6IjhJHs1V^m$H-5nEUkUg(@$9;~?!oDX-gikd7!UVtK|x|l z3Kbk_r0DZz*fBUzXfGcAPgq`P#~Gw}5%}rp=|SeftX~?^8nJ!=Egc;l;m@8mmG~1u zz^SRJ+0LhuJ*pA>E|mai5KI;;5w;*BA;wyTz(aTB77sw}gWZAlAl|f+efPtY<}oWn zj8wuksK!qEjlnO>*-;`V8{j<+pz%UJfEEL#fpG1cqD8_GRY}4=qJZ62TZ&LrzjsVjK1Y=u16RrU;-9tg0!_e~hF( z_iY(O5%(%RFgbGwcTU8U&wXX)lZJr`W<=C51OpurlkxZW-yK&DVT3)LqaB=dVVPiDL;>v?F-O(C zm!2+o<)S4LNz}BoW}gNzdxZV%{#Z>VS4~Bw67`D6=A&tE#jxqg{(_{;%!lxd5^w#P zOx4ZgfTyzsV+s0~FDIGR2Kxy!S>yxLWyk9wtqd;DI(!c8;($oPBt;9`XdLWOl=a3MV8@&Dlj)UX+>P_rm z3Mif;bK}t%xCWvNeE5*w&d%-v;OCb#9Xb$YHLj6CX3V5QAmHa|JErAv{%F&+m!PugSf#z`79W^+mexh0*(4se0MmFszdzPj4Ir2ZAtc(XWf! zsRM_4k(jvrJ9XLT?j}A#`nilpE0x3F1OPd12l1`tI(X5HE7xy;BCnZO<`1gZM^yn-7D<4}{Lq1#*{=>W<>; zBim2ATo$`;us8cAjQ$uI4>2s*fzhU*G%W5ZkhyR|qcKEOIQCN*g6|kca~!UwGAQ?4 zxmWrGwp#M(yIY94uxH1v2LsF0^z|9=+B?n9BmTAar^`5_KTSsv#xO#0)`!kkQdD9B zseDBr(PB870f=ZeB2#17Us;Bk)a64u=4DBlk57aEsAHA!3yG z-!tea$k@{mAt`|97pSK=PKR470z1&i>OpNtW8OaX4|dfQCncq5lWjuHN3JyQkbkNK zv?MT7z9UEG(h2#RfOJKOzPgnfB8}e;-}&wJZ#%Fr0eG6SQ@kOj?r zET|WXz}ERU)w2<(3qHpwJjr&H(Gq!)1Lz=Nv`e%ntVX^UT09u_wDK&H`rpylJo$E$JgJE&yBZ|_H@+QF{( zt!j?y-w*d$2%$MNipc^yh)A##*O8NxQ;AXB-v}xt+@gn#V3rn2I0@2-#geUM+bTV^ zHxRU_9A~<(KulkiYDVFAv1A*I+*$r(5Iq4~mq)`+Vzk1K7y-kjti*R=cM~Gm#a9|D zH+)wA*3~>3!QA+sn3k-MxW>mPhVJz3r^S3vOD~vN@_ss%JBo^mrf>srIj5j(IL-}K zc}g?sz!rpTri{@`kp2*uHMlAYNRz(V*&-+0W}e&6w_`9Iu$iy9B3MlwHYbnITA~>7 z9dh`N_M?Dp7SjOlBj{hCaPvaYJ&_wDd?P}z0f}LRqTx3V#J$C5W91;0`!1$*@I(#{ z3fW*Mxcus*hk`&LfsNH)BE&GusTnOw1T@9|Nxu=huRxBR?~;8gOu=2^mi#xnG`J(i z^mYVp_+Va*Hhpb5 zVit&UI4dP307tI|6&K1BAq>-U%_tN`Mn)3m7Bn3)5>Z3πk-9*8RT`?OHc<1>U` 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 NPFhK-NYcpXe*x3W&=LRu literal 0 HcmV?d00001 diff --git a/examples/example_uniform.png b/examples/example_uniform.png new file mode 100644 index 0000000000000000000000000000000000000000..dbd13e5b13ef77b97551172bef5ade436c997b81 GIT binary patch literal 821 zcmeAS@N?(olHy`uVBq!ia0y~yV0;f`n{u!L$&Bb^10ZEp;u=vBoS#-wo>-L1;G0-d zkdt4MlbNJYmReMtnV)B@XK0{jp`(ydQc_TCrLUh0SD}|*l&*g;y7U~-6wU&V$YKTt zaUl?9OrL)VXcyB$PZ!6KiaBrZ?Dl&cCDQOPb&3azlAxZDxu8PXABQh{*jUfExH!A} z&o;~b<>BBs_0V$X9*;t|biX&IHSsfJQ%oF>4togF^4xZR`vTb^R604n~i7 z8BTVXGpH!vW0=rGroi59><#nZJ$$J6?*046KYm!e+ST{6M5;8_J@$Iwv{W9p=56jQ zR?C2PXI69kIGLB1_vqut!naX@B8GDP_R0TD-%OuA{o?DL|JvrC(3iJblQ}CwM{Hw+ z&ZD0{e@@?8w_iRrvaY)-_IhDem6U7X78BDC^}_icSzDuy*4Xv`tofIlTV7uNcK_F1 z>yqp1_qS#?+I#MKsQ=~TN5RcEb<#H9+`e`3;>BAN*RRqlD=UkdA@(D7ZfE&rlaK%Y z-TNl7p>Xxpti|`Y$LJ;Rj5&7t^y%$eKY#wbVwINKWY3UL(bPz{&`_W=7vF!MU*h*- z(cZm#{c=1@pB=b$`}XYHl?)}fUcb)naa+9Y_=h>>3@M;c^hwW~ef#sX<-7Lo?Oncn zd04tcubbgqzr{MI&)2@&r1SXKFRQl0iK{m|PA^G4zuTDoTGyf%N9>q#ZY|?$;$>@& zmy`Tmb?eWE4+YA`4E1j5k9hx-?~T(}n|$(?!~D5(k6xQL*XW0Ihq+2gVc|xz3*q76 zaxa_Y9ri3ty?y(3w*CKKMpM^M{AMHy5&?#bp$y;SCr_RjU%33zgpZH!mK^Vk73b!d zu`~RWT^`P$Ao-E4A?1iP!z96c1`oGAjARS^W4y7r;;4 literal 0 HcmV?d00001 From ed43cf1506a7ae2ca8bce26559d0659848d019f0 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Fri, 10 Apr 2026 15:52:52 +0900 Subject: [PATCH 16/17] =?UTF-8?q?docs:=20=E9=9A=9C=E5=AE=B3=E7=89=A9?= =?UTF-8?q?=E3=81=AE=E4=BE=8B=E3=82=92A0=E3=83=AB=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=81=AE=E7=94=BB=E5=83=8F=E3=81=AB=E5=B7=AE=E3=81=97=E6=9B=BF?= =?UTF-8?q?=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B0ルートの多重連結領域の例を A0(a+(b++(B+{},B+{}))) に変更。 二分岐の中に2つの障害物を含むA0ルートの例に統一。 --- README.md | 4 ++-- examples/example_complex_multiply.png | Bin 26650 -> 31469 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 77f34af..43a725b 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ |:---:|:---:|:---:| | ![一様流](examples/example_uniform.png) | ![閉軌道](examples/example_closed_orbit.png) | ![2つの閉軌道](examples/example_two_orbits.png) | -| 鞍点結合 `B0+(l+,c-(l-,).c-(l-,))` | 3つの鞍点結合 | 多重連結領域(障害物) | +| 鞍点結合 `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) | +| ![鞍点結合](examples/example_saddle.png) | ![3つの鞍点結合](examples/example_three_saddle.png) | ![障害物を含む流れ](examples/example_complex_multiply.png) |
さらに複雑な例 diff --git a/examples/example_complex_multiply.png b/examples/example_complex_multiply.png index 28eb57de3ae2ac5805df3cc3a687bc3d85c8cdce..d82babad25b00b64244b88b81844279ecb284089 100644 GIT binary patch literal 31469 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 TjZ4@(Epf7Uop8!-e(3)I*ET#L literal 26650 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 From 7e69d63ed6147308805006cd0d01ab00133528e4 Mon Sep 17 00:00:00 2001 From: Tetsuo Yokoyama Date: Fri, 10 Apr 2026 15:57:07 +0900 Subject: [PATCH 17/17] =?UTF-8?q?docs:=20=E5=85=A5=E3=82=8C=E5=AD=90?= =?UTF-8?q?=E3=81=AE=E8=A4=87=E9=9B=91=E3=81=AA=E4=BE=8B=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ------------ examples/example_complex_nested.png | Bin 41444 -> 0 bytes 2 files changed, 12 deletions(-) delete mode 100644 examples/example_complex_nested.png diff --git a/README.md b/README.md index 43a725b..72c6a23 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,6 @@ |:---:|:---:|:---:| | ![鞍点結合](examples/example_saddle.png) | ![3つの鞍点結合](examples/example_three_saddle.png) | ![障害物を含む流れ](examples/example_complex_multiply.png) | -
-さらに複雑な例 - -| 入れ子の二分岐・障害物・鞍点結合 | -|:---:| -| ![複雑な例](examples/example_complex_nested.png) | - -**COT式:** `B0-(b-+(b-+(l-,b++(l+,l+)),B+{}),c+(B+{},).c+(l+,).c+(b+-(l+,l-),).c+(l+,))` - -入れ子の二分岐ノード、複数の障害物(内側境界成分)、連続する鞍点結合を含む複雑な流れ場も正確に可視化できます。 - -
## 特徴 diff --git a/examples/example_complex_nested.png b/examples/example_complex_nested.png deleted file mode 100644 index 64e0e108a0629ee6b5c94608a29ef61f57fb0568..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41444 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