見出し画像

高校向けPython入門(10)実習課題例:論理回路

 AND,OR,NOT の論理回路を描き,それぞれの演算を行う。さらに,これを組み合わせて 半加算器を作る。
 まず,真理値について,教科書では次のように書かれているだろう。左が以前の数学の教科書(最近はこの真理値表ではなくベン図で表している),右が情報の教科書だ。

画像1

 コンピュータでは情報を 1/0 で扱うため,論理回路の入力/出力に合わせて右のようになっている。しかし,プログラミングで扱うのは左である。
 まず,AND 回路を作ってみよう。AND 回路は線分と弧を使って描く。弧は OR 回路でも 使うので,弧を描く関数を drawarc(cx,cy,r,th1,th2) として作っておこう。

import numpy as np
import matplotlib.pyplot as plt
from cycler import cycler
plt.rcParams['axes.prop_cycle'] = cycler(color=["black"]) # 描画色を黒にする plt.figure(figsize=(6,4))
plt.axis([0,6,0,4])
plt.grid()
# 中心(cx,xy),半径rで角th1からth2までの弧を黒で描く
def drawarc(cx,cy,r,th1,th2):
    t = np.linspace(th1,th2,50)
    x = cx + r * np.cos(t)
    y = cy + r * np.sin(t)
    plt.plot(x,y)
# AND回路 引数は点の座標(リスト)戻り値は端子の座標 in1,in2,out
def MILand(x,y):
    px = [x + 1.5,x,x,x + 1.5]
    py = [y + 1,y + 1,y - 1,y -1]
    plt.plot(px,py)
    drawarc(x + 1.5,y,1,-np.pi/2,np.pi/2)
    plt.plot([x + 2.5,x + 3],[y,y])                  # 端子
    plt.plot([x - 0.5,x],[y + 0.5 ,y + 0.5])  
    plt.plot([x - 0.5,x],[y - 0.5 ,y - 0.5])
    return([[x - 0.5,y + 0.5],[x - 0.5,y - 0.5],[x + 3,y]])
MILand(2,2)
plt.show()

画像2

 MILand でやっていることは,上の図を見ながら考えていただきたい。引数 x, y に渡すの は左の縦線の中央だ。図では (2, 2) にしている。
 次に,入力値 In1 , In2 を 1 か 0 にし,出力値 out を決める方法を考える。これを戻り値 を使って端子の位置に表示する。たとえば,次のようにすると AND の演算ができる。これ を plt.show() の前の MILand(2,2) と置き換える。

# -- in1 と in2 の AND 演算をして out を決める方法を考える ---
in1 = 0
in2 = 1
if in1 == 1 and in2 == 1:
    out = 1
else:
    out = 0
#---------------------------------------------------------
term = MILand(2,2)
plt.text(term[0][0] - 0.3,term[0][1] - 0.1,in1,size = 18) # 端子に表示
plt.text(term[1][0] - 0.3,term[1][1] - 0.1,in2,size = 18)
plt.text(term[2][0] + 0.1,term[2][1] - 0.1,out,size = 18)

画像3

このように if による条件分岐を使うのが初歩的な発想だ。他の方法も考えてみよう。 まず,計算で行う方法。AND の計算を 1 / 0 で行うなら次の計算でできる

out = in1 * in2

しかし,プログラミングでは真理値は 1/0 ではなく,True/False で表す(言語により,小 文字 true/false で表すものもある)。このことは教科書には書かれていないが,プログラミン グでは基本的知識としたい。
というのは,if による条件分岐や,while による繰り返しで使う条件判断は,実は True/False で行っているのだ。成り立てば True,成り立たなければ Fase の値をとる。
次のコードで実験してみよう。

print(1 == 1)
print(1 == 0)

 結果は,True と False が表示されるはずだ。
 では,この True/False についての論理演算はどうするか。それが,条件分岐で使う and, or, not だ。

画像4

同じように or,not もやってみるとよい。

print(True or True)
print(True or False)
print(False or True)
print(False or False)
print(not True)
print(not False)

では,AND 回路で,True/False の演算をするにはどうするか。入力と出力は 1/0 で行うものとし,その方法をいろいろ考えさせるのがよいだろう。次は一例である。

t1 = (in1 == 1)
t2 = (in2 == 1)
if t1 and t2:
    out = 1 
else:
    out = 0

 最初に示したものと同じだが,t1,t2 が True / False になることをちょっと意識している。

 次に OR 回路。次の関数を定義して,MILor で表示し,論理演算をしてみよう。
OR 回路では弧を 3 つ描く。次のコードで 0.625 などの値は,作図して適当に決めたもの だ。中心と半径を変えれば弧の曲がり具合が変わる。その際は,th1 と th2 の値も適宜変更 する。


# OR回路 引数は点の座標(リスト)戻り値は端子の座標 in1,in2,out 
def MILor(x,y):
    drawarc(x - 1,y,np.sqrt(2),-np.pi/4,np.pi/4)
    drawarc(x + 1,y - 0.625,1.625,0.41,np.pi/2)
    drawarc(x + 1,y + 0.625,1.625,-np.pi/2,-0.41)
    plt.plot([x,x + 1],[y + 1,y + 1])
    plt.plot([x,x + 1],[y - 1,y - 1])
    plt.plot([x + 2.5,x + 3],[y,y])
    plt.plot([x - 0.5,x + 0.3],[y + 0.5 ,y + 0.5])
    plt.plot([x - 0.5,x + 0.3],[y - 0.5 ,y - 0.5])
    return([[x - 0.5,y + 0.5],[x - 0.5,y - 0.5],[x + 3,y]])

これに,AND 回路でやった論理演算を書き直して次のようになる。

画像5

 次に NOT 回路。次の関数を定義して,MILnot で表示し,論理演算をしてみよう。こんどは入力端子が 1 つなので,表示部分も変える必要がある。

# NOT回路 戻り値は端子の座標 in,out
def MILnot(x,y):
    plt.plot([x + 2,x,x,x + 2],[y,y + 1,y - 1,y])
    drawarc(x + 2.15,y,0.15,0,15 * np.pi)
    plt.plot([x - 0.5,x],[y,y])
    plt.plot([x + 2.3,x + 3],[y,y])
    return([[x - 0.5,y],[x + 3,y]])

画像6

 各回路の働きがわかったら,半加算器の回路を描き,その働きを確かめる。
まず回路を描く。AND 回路を 2 つと,OR,NOT 回路を 1 つずつ配置してそれぞれの端子 をつなぐ。
 はじめに描画エリアを広げておく。目盛も 1 ごとにしておこう。自動にすると 1 ごとにな らない。次のコードに書き換え,また追加する。

plt.figure(figsize=(10,4))
plt.axis([0,20,0,8])
plt.xticks(np.linspace(0,20,21))
plt.yticks(np.linspace(0,8,9)


 今までに,AND,OR,NOT の 3 つの回路が def で定義されているはずだ。そのあとに書 いた演算のところは一旦削除し,次のコードを書く(書き換える)。方眼を描いたので,各回路の戻り値は使わずに,直接座標を書いた。

MILand(3,5)
MILor(3,2)
MILnot(8,3.5)
MILand(13,3)
plt.plot([1,2.5],[5.5,5.5])                # AND In1
plt.plot([2.5,2.5],[1.5,4.5])             # OR In2 to AND In2
plt.plot([1.5],[5.5],'o')                    # 分岐の点
plt.plot([1.5,1.5,2.5],[5.5,2.5,2.5]) # AND In2 to OR In1
plt.plot([1,2.5],[1.5,1.5])                 # OR In2
plt.plot([2.5],[1.5],'o')                    # 分岐点
plt.plot([6,6,7.5],[5,3.5,3.5])         # NOT In
plt.plot([6],[5],'o')                         # 分岐点
plt.plot([11,12.5],[3.5,3.5])           # NOT out to AND In1
plt.plot([6,17],[5,5])                      # Carry
plt.plot([6,12.5],[2,2.5])               # OR to AND In2
plt.plot([16,17],[3,3])                    # Sum
plt.text(3.5,6.2,'AND',size = 16)
plt.text(3.7,3.2,'OR',size = 16)
plt.text(9.1,4,'NOT',size = 16)
plt.text(13.5,4.2,'AND',size = 16)
plt.text(17.2,4.8,'Caryy',size = 16)
plt.text(17.2,2.8,'Sum',size = 16)

画像7

 入力値を in1,in2 とし,初めの AND と OR の出力を andfout, orout,NOT の出力 を notout,Carry と Sum を Carry, Sum として,出力部分を計算するプログラムを書く。 実習ではここを課題にするのがいいだろう。次のコードの #--- で はさまれた部分だ。
 その後に,これらの値を表示するコードを書いておく。

# --- 入力 in1,in2 に対し,とそれぞれの出力を計算 ---- in1 = 1
in2 = 1
andout = in1 * in2
orout = 1 - (1-in1) * (1-in2)
notout = 1- andout
Carry = andout
Sum = orout * notout
# ----------------------------------------------
plt.text(0.4,5.3,in1,size = 18,color = 'b')
plt.text(0.4,1.3,in2,size = 18,color = 'b')
plt.text(5.6,5.2,andout,size = 18,color = 'b')
plt.text(6.1,2.2,orout,size = 18,color = 'b')
plt.text(10.4,3.7,notout,size = 18,color = 'b')
plt.text(19,4.8,Carry,size = 18,color = 'b')
plt.text(19,2.8,Sum,size = 18,color = 'b')


うまく描けたら,始めの設定で,目盛の設定と方眼の表示を


#plt.xticks(np.linspace(0,20,21))
#plt.yticks(np.linspace(0,8,9))
plt.xticks([])
plt.yticks([])
#plt.grid()

として,非表示にしておく。

画像8


さて,出力の Carry と Sum の意味だが,これは 2 進数の足し算の結果を表す。Carry が 繰り上がりだ。教科書によっては「桁上がり」と書いてある。
表示領域をさらに横に広げて,この計算も示そう。

plt.figure(figsize=(12,4))
plt.axis([0,24,0,8])
plt.text(21.5,4.5,in1,size = 20,color = 'b')
plt.text(21.5,3.5,in2,size = 20,color = 'b')
plt.text(20.5,3.5,'+',size = 20,color = 'b')
plt.plot([20.5,22],[3.2,3.2],color = 'b')
if Carry == 1:
    plt.text(21,2.5,Carry,size = 18,color = 'b')
plt.text(21.5,2.5,Sum,size = 20,color = 'b')

画像9

 以上,すべてを書くと 100 行くらいになる。これを生徒に打ち込ませるのは非現実的なので,入力と出力の部分だけを空欄にしたものを配布し,完成させるのがよいだろう。それぞれの回路ごとに論理計算をさせるのであれば,関数定義だけを書いたものを渡すことも考えられる。そこは授業計画に応じて行えばよい。

 なお,これらの関数をモジュール化することも考えられるが,それはまたいずれ。