数学不好还是编程不好,可能两者都有?

8 投票
2 回答
767 浏览
提问于 2025-04-16 16:25

我正在写一个Python程序,目的是生成来自著名作家海因莱因小说《月亮是个严酷的情人》的露娜自由州旗帜,这是我个人的项目。我在网上查阅了关于纹章学的规则和一些数学公式,但我的bendsinister函数明显有问题,因为当我取消注释时,断言会失败。这个斜条的面积应该是旗帜总面积的三分之一,但实际上并不是。我唯一做得不太靠谱的就是猜测了梯形的高度公式,不过我想错误可能出现在其他地方。我已经删掉了大部分代码,只保留了显示问题的部分。希望有数学基础更好的人能找到错误!

#!/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 ''

这是调试输出,显示我的结果远远没有达到三分之一的面积:

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 个回答

2

我在一个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

显示的蓝色条纹看起来并不像是整个区域的三分之一,但数字却说明了一切:

359660 / 180340 = 1.994344
8

把这个矩形分成两个三角形,它们是一样的。

黑色的三角形加上蓝色的梯形就是三角形A。单独的黑色三角形是三角形B。

三角形A和三角形B是相似的三角形,所以它们的面积是通过一个比例关系联系在一起的,这个比例是它们的缩放因子的平方。

我们希望蓝色的梯形占三角形A的三分之一的面积。这样一来,弯曲的部分就会占整个矩形的三分之一。这意味着三角形B的面积必须是三角形A的三分之二。因此,缩放因子必须是sqrt(2/3)。

接下来,你应该能很容易地把这个转换成弯曲部分的坐标。

撰写回答