我一直致力于用几种不同语言实现Mandelbrot Set。我有一个C ++,C#,Java和Python的工作实现,但Common Lisp实现有一些我无法弄清楚的错误。它生成集合,但在管道中的某个地方集合变形。我已经测试并知道接近确定文件I / O CLO不是问题 - 它不太可能,但我可以非常彻底地测试它。
请注意,这些实现的目的是将它们相互比较 - 所以我试图保持代码实现尽可能相似,以便它们具有可比性。
Mandelbrot集(这里由Python实现生成):
http://www.freeimagehosting.net/uploads/65cb71a873.png http://www.freeimagehosting.net/uploads/65cb71a873.png“Mandelbrot Set(由Python生成)”
但我的Common Lisp程序产生了这个:
http://www.freeimagehosting.net/uploads/50bf29bcc9.png http://www.freeimagehosting.net/uploads/50bf29bcc9.png“Common Lisp版本的扭曲Mandelbrot集”
Clisp和SBCL中的错误相同。
CODE:
Common Lisp:
(defun mandelbrot (real cplx num_iter)
(if (> (+ (* real real) (* cplx cplx)) 4)
1
(let ((tmpreal real) (tmpcplx cplx) (i 1))
(loop
(setq tmpcplx (+ (* (* tmpreal tmpcplx) 2) cplx))
(setq tmpreal (+ (- (* tmpreal tmpreal) (* tmpcplx tmpcplx))
real))
(setq i (+ i 1))
(cond
((> (+ (* tmpreal tmpreal)
(* tmpcplx tmpcplx)) 4) (return i))
((= i num_iter) (return 0)))))))
(defun floordiv (dend sor) (/ (- dend (mod dend sor)) sor))
(defclass xbm () (
(data :accessor data :initarg :data)
(dim :reader dim :initarg :dim)
(arrsize :reader arrsize :initarg :arrsize)))
(defmethod width ((self xbm)) (third (dim self)))
(defmethod height ((self xbm)) (second (dim self)))
(defun generate (width height)
(let ((dims (list 0 0 0)) (arrsize_tmp 0))
(setq dims (list 0 0 0))
(setf (second dims) height)
(setf (third dims) width)
(setf (first dims) (floordiv (third dims) 8))
(unless (= (mod width 8) 0) (setf (first dims) (+ (first dims) 1)))
(setq arrsize_tmp (* (first dims) (second dims)))
(make-instance 'xbm
:data (make-array arrsize_tmp :initial-element 0)
:dim dims
:arrsize arrsize_tmp)))
(defun writexbm (self f)
(with-open-file (stream f :direction :output :if-exists :supersede)
(let ((fout stream))
(format fout "#define mandelbrot_width ~d~&" (width self))
(format fout "#define mandelbrot_height ~d~&" (height self))
(format fout "#define mandelbrot_x_hot 1~&")
(format fout "#define mandelbrot_y_hot 1~&")
(format fout "static char mandelbrot_bits[] = {")
(let ((i 0))
(loop
(if (= (mod i 8) 0)
(format fout "~& ")
(format fout " "))
(format fout "0x~x," (svref (data self) i))
(unless (< (setf i (+ i 1)) (arrsize self))
(return t)))))))
(defmethod setpixel ((self xbm) (x integer) (y integer))
(if (and (< x (third (dim self))) (< y (second (dim self))))
(let ((val (+ (floordiv x 8) (* y (first (dim self))))))
(setf (svref (data self) val) (boole boole-ior (svref (data self) val) (ash 1 (mod x 8)))))))
(defmethod unsetpixel ((self xbm) (x integer) (y integer))
(if (and (< x (third (dim self))) (< y (second (dim self))))
(let ((val (+ (floordiv x 8) (* y (first (dim self))))))
(setf (svref (data self) val) (boole boole-xor (boole boole-ior
(svref (data self) val) (ash 1 (mod x 8))) (ash 1 (mod x 8)))))))
(defmethod draw_mandelbrot ((xbm xbm) (num_iter integer) (xmin number)
(xmax number) (ymin number) (ymax number))
(let ((img_width (width xbm)) (img_height (height xbm)) (xp 0))
(loop
(if (< xp img_width)
(let ((xcoord (+ (* (/ xp img_width) (- xmax xmin)) xmin)) (yp 0))
(loop
(if (< yp img_height)
(let (
(ycoord (+ (* (/ yp img_height) (- ymax ymin)) ymin)))
(let ((val (mandelbrot xcoord ycoord num_iter)))
(if (> val 0) (unsetpixel xbm xp yp) (setpixel xbm xp yp)))
(setq yp (+ yp 1)))
(return 0)))
(setq xp (+ xp 1)))
(return 0)))))
(defun main ()
(let ((maxiter 0) (xmin 0) (xmax 0) (ymin 0) (ymax 0) (file nil) (xsize 0) (ysize 0) (picture nil))
(format t "maxiter? ")
(setq maxiter (read))
(format t "xmin? ")
(setq xmin (read))
(format t "xmax? ")
(setq xmax (read))
(format t "ymin? ")
(setq ymin (read))
(format t "ymax? ")
(setq ymax (read))
(format t "file path: ")
(setq file (read-line))
(format t "picture width? ")
(setq xsize (read))
(format t "picture height? ")
(setq ysize (read))
(format t "~&")
(setq picture (generate xsize ysize))
(draw_mandelbrot picture maxiter xmin xmax ymin ymax)
(writexbm picture file)
(format t "File Written.")
0))
(main)
最接近它的是Python:
from xbm import *
def mandelbrot(real_old,cplx_old,i):
real = float(real_old)
cplx = float(cplx_old)
if (real*real+cplx*cplx) > 4:
return 1
tmpreal = real
tmpcplx = cplx
for rep in range(1,i):
tmpb = tmpcplx
tmpcplx = tmpreal*tmpcplx*2
tmpreal = tmpreal*tmpreal - tmpb*tmpb
tmpcplx += cplx
tmpreal += real
tmpb = tmpcplx*tmpcplx + tmpreal*tmpreal
if tmpb > 4:
return rep+1
else:
return 0
def draw_mandelbrot(pic, num_iter, xmin, xmax, ymin, ymax):
img_width = pic.width()
img_height = pic.height()
for xp in range(img_width):
xcoord = (((float(xp)) / img_width) * (xmax - xmin)) + xmin
for yp in range(img_height):
ycoord = (((float(yp)) / img_height) * (ymax - ymin)) + ymin
val = mandelbrot(xcoord, ycoord, num_iter)
if (val):
pic.unsetpixel(xp, yp)
else:
pic.setpixel(xp, yp)
def main():
maxiter = int(raw_input("maxiter? "))
xmin = float(raw_input("xmin? "))
xmax = float(raw_input("xmax? "))
ymin = float(raw_input("ymin? "))
ymax = float(raw_input("ymax? "))
file = raw_input("file path: ")
xsize = int(raw_input("picture width? "))
ysize = int(raw_input("picture height? "))
print
picture = xbm(xsize, ysize)
draw_mandelbrot(picture, maxiter, xmin, xmax, ymin, ymax)
picture.writexbm(file)
print "File Written. "
return 0;
main()
[xbm.py]
from array import *
class xbm:
def __init__(self, width, height):
self.dim = [0, 0, 0]
self.dim[1] = height
self.dim[2] = width
self.dim[0] = self.dim[2] / 8
if width % 8 != 0:
self.dim[0] += 1
self.arrsize = self.dim[0] * self.dim[1]
self.data = array('B', (0 for x in range(self.arrsize)))
self.hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
def __nibbletochar__(self, a):
if a < 0 or a > 16:
return '0'
else:
return self.hex[a]
def setpixel(self, x, y):
if x < self.dim[2] and y < self.dim[1]:
self.data[(x / 8) + (y * self.dim[0])] |= 1 << (x % 8)
def unsetpixel(self, x, y):
if x < self.dim[2] and y < self.dim[1]:
self.data[(x / 8) + (y * self.dim[0])] |= 1 << (x % 8)
self.data[(x / 8) + (y * self.dim[0])] ^= 1 << (x % 8)
def width(self):
return self.dim[2]
def height(self):
return self.dim[1]
def writexbm(self, f):
fout = open(f, 'wt')
fout.write("#define mandelbrot_width ")
fout.write(str(self.dim[2]))
fout.write("\n#define mandelbrot_height ")
fout.write(str(self.dim[1]))
fout.write("\n#define mandelbrot_x_hot 1")
fout.write("\n#define mandelbrot_y_hot 1")
fout.write("\nstatic char mandelbrot_bits[] = {")
for i in range(self.arrsize):
if (i % 8 == 0): fout.write("\n\t")
else: fout.write(" ")
fout.write("0x")
fout.write(self.__nibbletochar__(((self.data[i] >> 4) & 0x0F)))
fout.write(self.__nibbletochar__((self.data[i] & 0x0F)))
fout.write(",")
fout.write("\n};\n")
fout.close();
我可以发布C ++,C#或Java代码。
谢谢!
编辑:感谢Edmund的回复,我发现了这个错误 - 只是在移植时出现了裂缝。修改后的代码:(defun mandelbrot (real cplx num_iter)
(if (> (+ (* real real) (* cplx cplx)) 4)
1
(let ((tmpreal real) (tmpcplx cplx) (i 1) (tmpb cplx))
(loop
(setq tmpb tmpcplx)
(setq tmpcplx (+ (* (* tmpreal tmpcplx) 2) cplx))
(setq tmpreal (+ (- (* tmpreal tmpreal) (* tmpb tmpb))
real))
(setq i (+ i 1))
(cond
((> (+ (* tmpreal tmpreal)
(* tmpcplx tmpcplx)) 4) (return i))
((= i num_iter) (return 0)))))))
(defun floordiv (dend sor) (/ (- dend (mod dend sor)) sor))
(defclass xbm () (
(data :accessor data :initarg :data)
(dim :reader dim :initarg :dim)
(arrsize :reader arrsize :initarg :arrsize)))
(defun width (self) (third (dim self)))
(defun height (self) (second (dim self)))
(defun generate (width height)
(let ((dims (list 0 0 0)) (arrsize_tmp 0))
(setq dims (list 0 0 0))
(setf (second dims) height)
(setf (third dims) width)
(setf (first dims) (floordiv (third dims) 8))
(unless (= (mod width 8) 0) (setf (first dims) (+ (first dims) 1)))
(setq arrsize_tmp (* (first dims) (second dims)))
(make-instance 'xbm
:data (make-array arrsize_tmp :initial-element 0)
:dim dims
:arrsize arrsize_tmp)))
(defun writexbm (self f)
(with-open-file (stream f :direction :output :if-exists :supersede)
(let ((fout stream))
(format fout "#define mandelbrot_width ~d~&" (width self))
(format fout "#define mandelbrot_height ~d~&" (height self))
(format fout "#define mandelbrot_x_hot 1~&")
(format fout "#define mandelbrot_y_hot 1~&")
(format fout "static char mandelbrot_bits[] = {")
(let ((i 0))
(loop
(if (= (mod i 8) 0)
(format fout "~& ")
(format fout " "))
(format fout "0x~x," (svref (data self) i))
(unless (< (setf i (+ i 1)) (arrsize self))
(return t)))))))
(defun setpixel (self x y)
(if (and (< x (third (dim self))) (< y (second (dim self))))
(let ((val (+ (floordiv x 8) (* y (first (dim self))))))
(setf (svref (data self) val) (boole boole-ior (svref (data self) val) (ash 1 (mod x 8)))))))
(defun unsetpixel (self x y)
(if (and (< x (third (dim self))) (< y (second (dim self))))
(let ((val (+ (floordiv x 8) (* y (first (dim self))))))
(setf (svref (data self) val) (boole boole-xor (boole boole-ior
(svref (data self) val) (ash 1 (mod x 8))) (ash 1 (mod x 8)))))))
(defun draw_mandelbrot (xbm num_iter xmin xmax ymin ymax)
(let ((img_width (width xbm)) (img_height (height xbm)) (xp 0))
(loop
(if (< xp img_width)
(let ((xcoord (+ (* (/ xp img_width) (- xmax xmin)) xmin)) (yp 0))
(loop
(if (< yp img_height)
(let (
(ycoord (+ (* (/ yp img_height) (- ymax ymin)) ymin)))
(let ((val (mandelbrot xcoord ycoord num_iter)))
(if (> val 0) (unsetpixel xbm xp yp) (setpixel xbm xp yp)))
(setq yp (+ yp 1)))
(return 0)))
(setq xp (+ xp 1)))
(return 0)))))
(defun main ()
(let ((maxiter 0) (xmin 0) (xmax 0) (ymin 0) (ymax 0) (file nil) (xsize 0) (ysize 0) (picture nil))
(format t "maxiter? ")
(setq maxiter (read))
(format t "xmin? ")
(setq xmin (read))
(format t "xmax? ")
(setq xmax (read))
(format t "ymin? ")
(setq ymin (read))
(format t "ymax? ")
(setq ymax (read))
(format t "file path: ")
(setq file (read-line))
(format t "picture width? ")
(setq xsize (read))
(format t "picture height? ")
(setq ysize (read))
(format t "~&")
(setq picture (generate xsize ysize))
(draw_mandelbrot picture maxiter xmin xmax ymin ymax)
(writexbm picture file)
(format t "File Written.")
0))
(main)
虽然代码不是很LISP-ish(这是一个单词吗?)但它有效。感谢所有发布/评论/回答的人:)
答案 0 :(得分:10)
关于您的代码的一些评论:
mandelbrot:缺少声明,正方形在循环中计算两次
mandelbrot:在TMPREAL的计算中,你使用的是TMPCLX的新值,而不是旧值
您不想使用METHODS来设置像素。 SLOW。
FLOORDIV是Common Lisp中的FLOOR或TRUNCATE之一(取决于你想要的),见(FLOOR 10 3)
使用类型声明
不会重复调用DATA和ARRSIZE
setpixel,unsetpixel看起来非常昂贵,再次反复引用结构
draw-mandelbrot有很多重复计算可以做一次
Common Lisp有2d数组,简化了代码
Common Lisp有复数,也简化了代码
变量名'self'在Common Lisp中毫无意义。将其命名为它是什么。
一般来说,代码充满了浪费。对代码进行基准测试没有多大意义,因为它是以一种希望没有人在Common Lisp中使用的样式编写的。 Common Lisp的设计具有大型数学软件(如Macsyma)的经验,允许以直接的方式编写数学代码(没有对象,只是函数而不是数字,数组......)。更好的编译器可以利用原始类型,基本操作和类型声明。因此,样式不同于Python中可能编写的样式(通常是面向对象的Python或调用某些C代码)或Ruby。在繁重的数字代码中,使用CLOS进行动态调度通常不是一个好主意。通过紧密LOOP中的CLOS调用在位图中设置像素实际上是一个人们想要避免的事情(除非你知道如何优化它)。
更好的Lisp编译器将编译数字函数以引导机器代码。在编译期间,他们给出了一些提示,哪些操作是通用的,无法进行优化(直到开发人员添加更多类型信息)。开发人员还可以“拆卸”函数并检查通用代码或不必要的函数调用。 “TIME”提供运行时信息,并通知开发人员'consed'的内存量。在数字代码中,'浮动'是一个常见的性能问题。
所以,总结一下:
如果您编写代码并认为它在不同语言中执行相同操作,当代码看起来相似或具有类似结构时,情况可能并非如此 - 除非您确实知道这两种语言和两种语言实现。
如果您使用一种语言编写代码并以类似于不同语言的方式移植它,您可能会错过整个现有文化,以不同的方式编写这些问题的解决方案。例如,可以用面向对象的样式在C ++中编写代码,并以类似于FORTRAN的方式将其移植。但是没有人在FORTRAN中编写这样的代码。以FORTRAN样式编写,通常会产生更快的代码 - 特别是因为编译器针对惯用的FORTRAN代码进行了大量优化。
“在罗马时,像罗马人一样说话”
示例:
在SETPIXEL中有一个调用(first(dim self))。为什么不首先将该值作为结构中的一个插槽,而不是一直进行列表访问? 但是在计算过程中该值是恒定的。仍然传递结构,并且始终检索值。为什么不直接获取主循环外的值并直接传递它?而不是对它进行多次计算?为了让您了解如何编写代码(使用类型声明,循环,复数,...),这里是mandelbrot计算的略有不同的版本。
核心算法:
(defvar *num-x-cells* 1024)
(defvar *num-y-cells* 1024)
(defvar *depth* 60)
(defun m (&key (left -1.5) (top -1.0) (right 0.5) (bottom 1.0) (depth *depth*))
(declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
(loop with delta-x-cell float = (/ (- right left) *num-x-cells*)
and delta-y-cell float = (/ (- bottom top) *num-y-cells*)
and field = (make-array (list *num-x-cells* *num-y-cells*))
for ix fixnum below *num-x-cells*
for x float = (+ (* (float ix) delta-x-cell) left)
do (loop for iy fixnum below *num-y-cells*
for y = (+ (* (float iy) delta-y-cell) top)
do (loop for i fixnum below depth
for z of-type complex = (complex x y)
then (+ (complex x y) (* z z))
for exit = (> (+ (* (realpart z) (realpart z))
(* (imagpart z) (imagpart z)))
4)
finally (setf (aref field ix iy) i)
until exit))
finally (return field)))
上面的函数返回一个2d数组。
编写xbm文件:
(defun writexbm (array pathname &key (black *depth*))
(declare (fixnum black)
(optimize (speed 3) (safety 2) (debug 0) (space 0)))
(with-open-file (stream pathname :direction :output :if-exists :supersede)
(format stream "#define mandelbrot_width ~d~&" (array-dimension array 0))
(format stream "#define mandelbrot_height ~d~&" (array-dimension array 1))
(format stream "#define mandelbrot_x_hot 1~&")
(format stream "#define mandelbrot_y_hot 1~&")
(format stream "static char mandelbrot_bits[] = {")
(loop for j fixnum below (array-dimension array 1) do
(loop for i fixnum below (truncate (array-dimension array 0) 8)
for m fixnum = 0 then (mod (1+ m) 8) do
(when (zerop m) (terpri stream))
(format stream "0x~2,'0x, "
(let ((v 0))
(declare (fixnum v))
(dotimes (k 8 v)
(declare (fixnum k))
(setf v (logxor (ash (if (= (aref array
(+ (* i 8) k) j)
black)
1 0)
k)
v)))))))
(format stream "~&}~&")))
上面的函数接受一个数组和一个路径名,并将该数组写为XBM文件。 一个数字“黑色”将为“黑色”,其他数字为“白色”
呼叫
(writexbm (m) "/tmp/m.xbm")
答案 1 :(得分:5)
我不确定这部分是否正确:
(setq tmpcplx (+ (* (* tmpreal tmpcplx) 2) cplx))
(setq tmpreal (+ (- (* tmpreal tmpreal) (* tmpcplx tmpcplx))
real))
是不是tempcplx被第一行的新值覆盖了,这意味着第二行是使用新值而不是原始值?
在Python版本中,您通过使用tmpb来避免此问题:
tmpb = tmpcplx
tmpcplx = tmpreal*tmpcplx*2
tmpreal = tmpreal*tmpreal - tmpb*tmpb
tmpcplx += cplx
tmpreal += real
在我看来,Lisp版本应该做类似的事情,即首先存储tmpcplx的原始值,并使用该存储来计算tmpreal:
(setq tmpb cplx)
(setq tmpcplx (+ (* (* tmpreal tmpcplx) 2) cplx))
(setq tmpreal (+ (- (* tmpreal tmpreal) (* tmpb tmpb))
real))