파이썬수학 글에서 다루는 코드들은 개발자를 위한 수학 깃헙 베이스로 함.

 

https://github.com/orlandpm/Math-for-Programmers

 

GitHub - orlandpm/Math-for-Programmers: Source code for the book, Math for Programmers

Source code for the book, Math for Programmers. Contribute to orlandpm/Math-for-Programmers development by creating an account on GitHub.

github.com

 

 

공룡 그리기(점)

- 벡터리스트는 공룡 모양을 나타내는 점들이지만 선을 안그려서 공룡처럼 보이진 않음

from vector_drawing import *

dino_vecs = [
    (6,  4), (3, 1), (1, 2), (-1, 5), (-2, 5), (-3, 4), (-4, 4),
    (-5, 3), (-5, 2), (-2, 2), (-5, 1), (-4, 0), (-2, 1), (-1, 0), (0, -3),
    (-1, -4), (1, -4), (2, -3), (1, -2), (3, -1), (5, 1)
]

print(dino_vecs)
print(*dino_vecs)
draw(Points(*dino_vecs))

 

 

 

 

 

*의 의미

 그렇게 파이썬 쓰면서 어려운걸 안하니 변수앞에 *의미가 뭔지도 모르고, 포인턴가 하고 있었는데 아래 글에서 의미를 찾아봄. 가변인자 매개변수를 나타낸다고 한다.

 

https://hcnoh.github.io/2019-01-27-python-arguments-asterisk

 

[Python] 함수의 매개변수 앞의 *(Asterisk)의 의미

이번 포스팅은 이전에 작성된 포스팅을 참고하여 작성하였다.

hcnoh.github.io

 

 

 

이번엔 선하나 긋기

 코드 작성자가 draw 함수안에 matplotlib으로 다 구현해놔서, 골치아픈걸 신경안쓰고 이렇게 코드만 넣으면 동작되는데, 이번에는 points 뒤에 segment로 선 그을 두 좌표 넣으면 됨

draw(
    Points(*dino_vecs),
    Segment((6, 4), (3, 1))
)

 

 

폴리곤으로 공룡 그리기

 방금 segment 함수로 선하나 그렸지만 좌표 벡터를 그대로 폴리곤으로 그려주도록 기능이 이미 있다. 대충 polygon쓰면 공룡이 그려진다.

draw(
    Points(*dino_vecs),
    Polygon(*dino_vecs)
)

 

 

포물선 그리기

 x가 -5, 5인 구간에서 y = x^2인 포물선을 그려보자

 깃헙 자료에는 리스트 컴프리헨션으로 한줄에 점들 구하지만, 난 잘 못해서 그냥 풀어씀

거기다 선도 그려주려고 폴리곤 썻는데, 포물선이 닫히고 말았지만 그냥 넘어가자

vecs = []
for x in range(-5, 6):
    y = pow(x, 2)
    vecs.append((x, y))
print(vecs)
draw(
    Points(*vecs),
    Polygon(*vecs),
    grid=(1, 10),
    nice_aspect_ratio=False
)

 

벡터 합 연산으로 공룡을 평행 이동시키자

 두 벡터간 합 연산하는 함수를 정의하고

 기존 공룡 벡터리스트를 -2, -5 한뒤

 기존 공룡과 새 공룡 벡터로 드로잉하면 이렇게 나온다.

 

def add(v1, v2):
    return (v1[0] + v2[0], v1[1] + v2[1])
    
dino_vecs2 = []
for vec in dino_vecs:
    new_vec = add((-2, -5), vec)
    dino_vecs2.append(new_vec)

draw(
    Points(*dino_vecs),
    Polygon(*dino_vecs),
    Points(*dino_vecs2, color="red"),
    Polygon(*dino_vecs2, color="red")
)

 

 

 

두점 사이 거리 계산하기

 피타고라스 정리로 배운 두 점사이 거리 계산하는 코드

from math import sqrt

def length(v):
    return sqrt(v[0]**2 + v[1]**2)

 

여러개 좌표 벡터 더하기

아까 구현한 덧셈 연산은 좌표 벡터가 2개만 들어온경우를 생각하는데

이번엔 2개가 아니라 여러개 들어온 경우 덧셈하는 코드를 만들어보자

한줄 쓰기는 좋아하진 않지만 그냥 만든 사람 따라해야지

def add(*vecs):
    return (sum(v[0] for v in vecs), sum(v[1] for v in vecs))

 

여러개 평행이동 연산 하기

아까 구현한 공룡 이동은 -2, -5 한번 하고 끝냈는데

(-1, -1), (-2, -2), (-3, -3) 처럼 좌표 벡터 여러개 주면 여러개 연산 결과 나오게 하자

=> 3,3 에서  각각 순서대로 연산하면 이런 식으로 나온다

def translate(start, vecs):
    return [add(start, vec) for vec in vecs]
translate((3, 3), [(-1, -1), (-2, -2), (-3, -3)])

 

공룡 백마리 그리기

방금 구현한 평행이동 코드로 공룡 100마리 그려보자.

아깐 길이가 3인 평행이동 벡터리스트 만들었지만

길이가 100인 평행이동 벡터리스트 만들고

평행이동 함수로 공룡 폴리곤 100개 만들어 draw에 집어넣으면 됨

def hundred_dinos():
    translations = [
        (12 * x, 10 * y) for x in range(-5, 5) for y in range(-5, 5)
    ]

    dinos = [Polygon(*translate(t, dino_vecs)) for t in translations]
    draw(*dinos, grid=None, axes=None, origin=None)
hundred_dinos()

 

 

 

네모 그리고, 네모 폴리곤 길이 구하기

1. 두 점 벡터 차연산

2. 두 점 사이 거리 계산

3. 벡터들 들어오면 모든 벡터 사이 길이 계산 구현

 

def subtract(v1, v2):
    return (v1[0] - v2[0], v1[1] - v2[1])

def distance(v1, v2):
    #length는 피타고라스 정리로 계산했던 대각 길이
    return length(subtract(v1, v2))

def perimeter(vecs):
    dists = [distance(vecs[i], vecs[(i + 1) % len(vecs)]) for i in range(0, len(vecs))]
    return sum(dists)

square = [(0, 0), (0, 2), (1, 2), (1, 0)]
print(perimeter(square))
draw(Polygon(*square), grid=(0.5, 0.5), axes=None)

 

 

지금까지 직교좌표 다뤘고 이젠 극좌표 다루자

 

데카르트 좌표(카티지안) : (x, y)

극좌표 : (반지름 r, 각도 theta)

 

극좌표 -> 직교좌표

여기선 반지름 10, 45도(라디안 pi /4)로 직교 좌표로 변환

from math import tan, pi, sin, cos

def to_cartesian(polar_vec):
    len, ang = polar_vec[0], polar_vec[1]
    return (len * cos(ang), len * sin(ang))

angle = pi / 4
to_cartesian((10, angle))

 

 

tan, atan, 직교->극좌표 변환

tan(라디안) 시 기울기 반환

atan(y/x) 시 라디안 반환

atan2(y, x)시 라디안 반환

직교 -> 극좌표 바꾸는 to_polar() : 유클리디안거리(피타고라스정리), atan2로 극좌표 반환

 

 

 

 

극좌표 -> 직교 변환 및 드로잉

len : cos( 5 * 0 ~ 2pi)

ang : 0 ~ 2pi

사이 1000개 극좌표를 직교좌표로 변환하여 폴리곤 드로잉

polar_coords = [(cos(5 * x * pi / 500.0), 2 * pi * x / 1000.0) for x in range(0, 1000)]
#to_cartesian(p) p = (len, ang)
vecs = [to_cartesian(p) for p in polar_coords]
draw(Polygon(*vecs))

 

 

 

 

atan 드로잉 해보기

2, -3 극좌표 변환시 len 3.6, -0.98 rad(=-56 deg)

유클리디안 거리 2, -3 시 3.6

atan(-3/2)시 -0.98 rad

print(to_polar((2, -3))) #-0.98 rad = -56
print(length((2, -3)))
print(atan(-3/2)) # y/x
draw(Arrow((2, -3)), Points((2, -3)))

 

직교 -> 극좌표 -> 회전 덧셈 -> 직교 -> 드로잉 순으로 공룡 회전시키기

음.. 내가 알기론 직교좌표계에서 회전할때 회전행렬 사용했는데

여기선 회전행렬 얘기를 아직 안다루어서인지

직교 -> 극좌표 변환

극좌표(거리, 각도)의 각도 + 회전각 => 극좌표(거리, 원래각도 + 회전각)

바뀐극좌표 -> 직교 변환 => 폴리곤 드로잉

순으로 구현

rot_ang = pi / 4
dino_polar = [to_polar(v) for v in dino_vecs]
dino_rot_polar = [ (len, ang + rot_ang) for len, ang in dino_polar]
dino_rot_cart = [to_cartesian(p) for p in dino_rot_polar]

draw(
    Polygon(*dino_vecs, color=gray),
    Polygon(*dino_rot_cart, color=red)
)

 

공룡 회전 + 평행이동시키기

rot -> translate 하여 공룡 회전, 이동 구현

def rotate(rot_ang, vecs):
    polars = [to_polar(v) for v in vecs]
    return [to_cartesian((len, ang + rot_ang)) for len, ang in polars]

# 5 * pi / 3 = 5.2 rad = 297 deg
new_dino_vecs_notmove = translate((0, 0), rotate(5 * pi / 3, dino_vecs))
new_dino_vecs = translate((5, 5), rotate(5 * pi / 3, dino_vecs))
draw(
    Polygon(*dino_vecs),
    Polygon(*new_dino_vecs_notmove, color="red"),
    Polygon(*new_dino_vecs, color="green")
)

 

 

 

 

지금까지 사용한 2차원 드로잉 모듈 만들기

지금까지 구현한 코드는 원래 matplotlib으로 그릴려고하면 엄청 고생해야하는데

이 저장소 만드사람이 드로잉 하기 쉽게 다 만들어 두어서 쉽게 그릴수가 있었다.

하지만 다른데 쓰려면 직접 만들줄 알아야 하므로 이 드로잉 모듈을 따라 만들어봄

 

물론 이 분만큼 다 고려해서 만들기도 힘들고 금방 만들수 있게

자주 쓴것과 꼭 필요한 부분만 커스텀으로 따로 만들었음

 

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.pyplot import xlim, ylim

blue = 'C0'
black = 'k'
red = 'C3'
gray = 'gray'

class Polygon():
    def __init__(self, *vertices, color=blue, fill=None, alpha=0.4):
        self.vertices = vertices
        self.color = color
        self.fill = fill
        self.alpha = alpha

class Points():
    def __init__(self, *vectors, color=black):
        self.vectors = list(vectors)
        self.color = color

# helper function to extract all the vectors from list of objs
def extract_vectors(objs):
    for obj in objs:
        if type(obj) == Polygon:
            for v in obj.vertices:
                yield v
        elif type(obj) == Points:
            for v in obj.vectors:
                yield v
        else:
            raise TypeError("Unrecognized obj : {}".format(obj))

def draw(*objs, origin=True, axes=True,  nice_aspect_ratio=True, width=6):
    all_vecs = list(extract_vectors(objs))
    xs, ys = zip(*all_vecs)
    max_x, max_y, min_x, min_y = max(0, *xs), max(0, *ys), min(0, *xs), min(0, *ys)

    if origin:
        plt.scatter([0], [0], color='k', marker='x')
    if axes:
        plt.gca().axhline(linewidth=2, color='k')
        plt.gca().axvline(linewidth=2, color='k')
    
    for obj in objs:
        if type(obj) == Polygon:
            for i in range(0, len(obj.vertices)):
                x1, y1 = obj.vertices[i]
                x2, y2 = obj.vertices[(i+1)%len(obj.vertices)]
                plt.plot([x1, x2], [y1, y2], color=obj.color)
            if obj.fill:
                xs = [v[0] for v in obj.vertices]
                ys = [v[1] for v in obj.vertices]
                plt.gca().fill(xs, ys, obj.fill, alpha=obj.alpha)
        elif type(obj) == Points:
            xs = [v[0] for v in obj.vectors]
            ys = [v[1] for v in obj.vectors]
            plt.scatter(xs, ys, color=obj.color)
        else:
            raise TypeError("Unrecognized obj : {}".format(obj))
    
    fig = matplotlib.pyplot.gcf()

    if nice_aspect_ratio:
        coords_height = (ylim()[1] - ylim()[0])
        coords_width = (xlim()[1] - xlim()[0])
        fig.set_size_inches(width, width * coords_height / coords_width)
    plt.show()


dino_vecs = [
    (6,  4), (3, 1), (1, 2), (-1, 5), (-2, 5), (-3, 4), (-4, 4),
    (-5, 3), (-5, 2), (-2, 2), (-5, 1), (-4, 0), (-2, 1), (-1, 0), (0, -3),
    (-1, -4), (1, -4), (2, -3), (1, -2), (3, -1), (5, 1)
]

print(dino_vecs)
print(*dino_vecs)
draw(
    Polygon(*dino_vecs, color="red")
)

 

 

간소화 한다고 드로잉 요소로는 아까 많이쓴 포인트랑 폴리곤만 넣었고

드로우 함수에서 plt 그리드 파트가 너무 복잡하게 생겨 그냥 뺏음

 

그렇게 기존의 드로잉 모듈 전체 코드는 140줄 정도 되었는데

내가 수정한 부분은 대충 70줄 정도

맨 아래 공룡 드로잉 포함해서 80줄 정도로 된다.

 

이렇게 70줄 정도 만으로

2차원에서 폴리곤과 포인트를 간편하게 드로잉하는 모듈 만들었다.

 

그리드와 틱은 좀 아쉽긴한데

이 정도 구조면 나도 나중에 필요할때 만들어서 써먹을수 있을듯싶네

 

 

 

 

+ Recent posts