плохая математика или плохое программирование, может и то и другое? - PullRequest
8 голосов
/ 26 апреля 2011

Я пишу программу на Python для генерации флага Свободного государства Луна из знаменитого романа Хайнлайна Луна - суровая любовница , как личный проект. Я писал в Интернете правила геральдики и сопоставлял математические формулы, но что-то явно не так в моей подпрограмме bendsinister, поскольку утверждение не комментируется. Площадь зловещего изгиба должна составлять 1/3 общей площади флага, и это не так. Единственная действительно хитрая вещь, которую я сделал, - это угадать формулу для высоты трапеции, но я предполагаю, что ошибки могут быть где угодно. Я сократил большую часть кода, оставив только то, что необходимо, чтобы показать проблему. Надеюсь, кто-то, у кого проблемы с математикой, сможет обнаружить ошибку!

#!/usr/bin/python
'generate bend sinister according to rules of heraldry'
import sys, os, random, math, Image, ImageDraw
FLAG = Image.new('RGB', (900, 600), 'black')
CANVAS = ImageDraw.Draw(FLAG)
DEBUGGING = True

def bendsinister(image = FLAG, draw = CANVAS):
 '''a bend sinister covers 1/3 of the field, sinister chief to dexter base

    (some sources on the web say 1/5 of the field, but we'll use 1/3)
    the "field" in this case being the area of the flag, so we need to
    find a trapezoid which is 1/6 the total area (width * height).

    we need to return only the width of the diagonal, which is double
    the height of the calculated trapezoid
 '''
 x, y = image.size
 b = math.sqrt((x ** 2) + (y ** 2))
 A = float(x * y)
 debug('%d * %d = %d' % (x, y, A))
 H = triangle_height(A / 2, b)  # height of triangular half of flag
 width = trapezoid_height(b, H, A / 6) * 2
 if command == 'bendsinister':
  show_bendsinister(x, y, width, image, draw)
 return width

def show_bendsinister(x, y, width, image = FLAG, draw = CANVAS):
 'for debugging formula'
 dexter_base, sinister_chief = (0, y), (x, 0)
 draw.line((dexter_base, sinister_chief), 'blue', int(width))
 image.show()
 debug(image.getcolors(2))  # should be twice as many black pixels as blue

def triangle_height(a, b):
 'a=bh/2'
 h = float(a) / (float(b) / 2)
 debug('triangle height: %.2f' % h)
 return h

def trapezoid_height(b, H, a):
 '''calculate trapezoid height (h) given the area (a) of the trapezoid and
    base b, the longer base, when it is known that the trapezoid is a section
    of a triangle of height H, such that the top, t, equals b when h=0 and
    t=0 when h=H. h is therefore inversely proportional to t with the formula
    t=(1-(h/H))*b, found simply by looking for what fit the two extremes.
    the area of a trapezoid is simply the height times the average length of
    the two bases, b and t, i.e.: a=h*((b+t)/2). the formula reduces
    then to (2*a)/b=(2*h)+(h**2)/H, which is the quadratic equation
    (1/H)*(h**2)+(2*h)-((2*a)/b)=0; solve for h using the quadratic formula
 '''
 try:
  h = (-2 + math.sqrt(4 - 4 * (1.0 / H) * -((2 * a) / b))) / (2 * (1.0 / H))
  debug('trapezoid height with plus: %.2f' % h)
 except:  # must be imaginary, so try minus instead
  h = (-2 - math.sqrt(4 - 4 * (1.0 / H) * -((2 * a) / b))) / (2 * (1.0 / H))
  debug('trapezoid height with minus: %.2f' % h)
 t = (1 - (float(h) / H)) * b
 debug('t=%d, a=%d, check=%d' % (t, round(a), round(h * ((b + t) / 2))))
 #assert round(a) == round(h * ((b + t) / 2))
 return h

def debug(message):
 if DEBUGGING:
  print >>sys.stderr, message

if __name__ == '__main__':
 command = os.path.splitext(os.path.basename(sys.argv[0]))[0]
 print eval(command)(*sys.argv[1:]) or ''

Вот результат отладки, показывающий, что я далеко от области 1/3:

jcomeau@intrepid:~/rentacoder/jcomeau/tanstaafl$ ./bendsinister.py 
900 * 600 = 540000
triangle height: 499.23
trapezoid height with plus: 77.23
t=914, a=90000, check=77077
[(154427, (0, 0, 255)), (385573, (0, 0, 0))]
154.462354191

Вот изображение с некоторыми добавленными строками: bend sinister Красная линия делит два треугольника, каждый из которых может быть использован для расчета трапеции. Я использую тот, который начинается в левом верхнем углу. Зеленая линия - это высота этого треугольника, переменная H в программе.

<ч /> Законченный сценарий и флаг (с использованием исправления, предоставленного Майклом Андерсоном), см. http://unternet.net/tanstaafl/. Спасибо всем за помощь!

Ответы [ 2 ]

8 голосов
/ 26 апреля 2011

Разбейте прямоугольник на два треугольника.Они будут идентичны.

Черный треугольник + Синяя трапеция - это Треугольник А. Сам по себе черный треугольник - это Треугольник B

Треугольник A и Треугольник B - это похожие треугольники, поэтому их площадь связана сквадрат масштабного коэффициента, связывающего их.

Мы хотим, чтобы Голубая трапеция была одной третью площади треугольника А. (Таким образом, изгиб займет одну треть от общего прямоугольника).Это означает, что Треугольник B должен быть площадью 2/3 от Треугольника А. Таким образом, коэффициент масштабирования должен быть sqrt (2/3).

После этого вы сможете преобразовать его, чтобы получить координаты геометрии изгиба.довольно легко.

2 голосов
/ 26 апреля 2011

Я выполнил следующий код в сеансе IDLE

from PIL import Image, ImageDraw
from math import sqrt

'generate bend sinister according to rules of heraldry'
import sys, os, random, math
FLAG = Image.new('RGB', (900, 600), 'black')
CANVAS = ImageDraw.Draw(FLAG)
DEBUGGING = True

def debug(message):
    if DEBUGGING:
        print >>sys.stderr, message


def show_bendsinister(x, y, width, image = FLAG, draw = CANVAS):
 'for debugging formula'
 dexter_base, sinister_chief = (0, y), (x, 0)
 print 'dexter_base==',dexter_base,'sinister_chief==',sinister_chief
 draw.line((dexter_base, sinister_chief), 'blue', int(width))
 image.show()
 debug(image.getcolors(2))  # should be twice as many black pixels as blue

def trapezoid_height(x, y, P):
 '''Given a rectangle whose width and length are (x) and (y)

The half of this rectangle is a large triangle A
whose base (b) is the diagonal of the rectangle
and its height (H) goes from its base (b) to
the right angle of the large triangle.
(x) and (y) are the side-lengths of the triangle.
The area of this large triangle is (x*y)/2 = (H*b)/2

Given a trapezoid whose base is the diagonal (b) of the rectangle
and base (b) of the large triangle, its height is (h)
and its top is (t).
Given (S) as the area of the trapezoid.
In general, the trapezoid is disymtric because the triangle have x != y.
So the area is S = h*(b + t)/2

This function trapezoid_height() calculates the height (h) of the trapezoid
in order that the trapezoid have an area (S) which must be
the percentage (P) of the area of the large triangle A. So:
h*(b + t)/2 = S = P*[H*b /2]  ==> h*(b + t) = P*H*b
==> h*t = P*H*b - h*b ==> h*t*(H-h) = [P*H - h]*b*(H-h)

The large triangle is the sum of the trapezoid and of a little triangle B
having an height equal to (H-h) and a base which is the top (t)
of the trapezoid.
The area of this little triangle B is t*(H-h)/2 and must be equal to (1-P)*[H*b / 2]
==> t*(H-h) = (1-P)*H*b ==> h*t*(H-h) = h*(1-P)*H*b

From h*t*(H-h) = [P*H - h]*b*(H-h)  and  h*t*(H-h) = h*(1-P)*H*b
we obtain [P*H - h]*b*(H-h) = h*(1-P)*H*b
==> b*h**2 - (b*H + xy)*h + P*x*y*H = 0
==> h**2 - 2*H*h + P*(H**2) = 0
That leads to the solution H*(1 - sqrt(1-P)), the other H*(1 + sqrt(1-P))
being bigger than H
'''

 H = math.sqrt( (x*x*y*y) / (x*x + y*y) )
 return H*(1 - sqrt(1-P))



def bendsinister(image = FLAG, draw = CANVAS):
 '''a bend sinister covers 1/3 of the field, sinister chief to dexter base

    (some sources on the web say 1/5 of the field, but we'll use 1/3)
    the "field" in this case being the area of the flag, so we need to
    find a trapezoid which is 1/6 the total area (width * height).

    we need to return only the width of the diagonal, which is double
    the height of the calculated trapezoid
 '''
 x, y = image.size
 print 'x ==',x,'y ==',y
 percentage = float(1)/3
 width = 2 * trapezoid_height(x, y , percentage)
 print 'height ==',width/2
 print 'width==',width


 if command == 'bendsinister':
  show_bendsinister(x, y, width, image, draw)
 return width

command = 'bendsinister'
print bendsinister()

результат

x == 900 y == 600
height == 91.6103029364
width== 183.220605873
dexter_base== (0, 600) sinister_chief== (900, 0)
[(180340, (0, 0, 255)), (359660, (0, 0, 0))]
183.220605873

Отображаемая синяя полоса не производит впечатление 1/3 от площади поля, но цифры говорят:

359660 / 180340 = 1.994344
...