2011-04-26 28 views
8

我正在編寫一個Python程序,以從着名的Heinlein小說The Moon is a Harsh Mistress中生成Luna Free State標誌,作爲個人項目。我一直在破壞紋章規則和匹配網絡上的數學公式,但是在我的例程中顯然是錯誤的,因爲在取消註釋時斷言失敗。彎曲區域的面積應該是標誌總面積的三分之一,而不是。我做的唯一真正狡猾的事情是猜測梯形高度的公式,但我猜錯誤可能在任何地方。我已經刪除了大部分代碼,只留下了顯示問題的必要條件。希望沒有數學挑戰的人可以發現錯誤!數學不好或編程不好,也許都是?

#!/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面積:

 
[email protected]:~/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.


對於完成的腳本和標誌(使用Michael Anderson提供的更正),請參閱 http://unternet.net/tanstaafl/。感謝所有的幫助!

+0

也許這更適合[代碼評論](http://codereview.stackexchange.com/)? – 2011-04-26 06:47:18

+0

@Tim,你爲什麼這麼認爲?這是一個編程問題,我試圖解決,而不是有效的,我試圖改善。 – 2011-04-26 06:50:54

+0

只是一個預感,基於迄今爲止缺乏答案和近距離投票(而不是我)。 – 2011-04-26 07:04:02

回答

8

將矩形分成兩個三角形。它們將是相同的。

黑色三角形+藍色梯形三角A. 黑色三角形自身是三角乙

三角形和三角B是相似三角形所以他們的面積是由比例因子與他們相關方。

我們希望藍色梯形是三角形A面積的三分之一(這樣彎曲將佔整個矩形的三分之一)。這意味着三角形B必須是三角形A的2/3區域。因此比例因子必須是sqrt(2/3)。

然後,您應該可以將其轉換爲非常容易地爲您提供彎曲幾何體的座標。

+0

+1,這是關鍵點:爲了按'2/3'縮放區域,你必須按'sqrt(2/3)'縮放長度。這是您需要的唯一的「二次方」操作。 – AakashM 2011-04-26 10:19:31

+0

謝謝,這可能會讓我更有意義後,我睡了一覺! +1現在無論如何 – 2011-04-26 10:22:50

+0

無法入睡,只好嘗試一下。非常感謝你,解決了它。我仍然想知道我的方法出了什麼問題,但我可以在我死後繼續工作:^) – 2011-04-26 10:37:50

2

我執行下面的代碼在空閒會話

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 
+0

我們都使用同一個公式來計算三角形面積和梯形面積,但是您的推導結果是正確的,而我的推導結果並不正確。儘管我昨晚得到了邁克爾安德森需要的答案,但仍給你+1。謝謝! – 2011-04-26 22:07:45

+0

@jcomeau_ictx你是完全正確的:你使用了好的公式。當我對你的問題感興趣的時候,當我沒有獲得考慮的時候,我對它們的表面看起來過於表面化,而且我一定是搞了個混亂。我想知道我是不是應該給我的腦子休息一下。 – eyquem 2011-04-26 22:29:23

相關問題