计算Common Lisp中向量的线性组合

时间:2018-05-13 20:18:37

标签: vector common-lisp numerical-methods sbcl

我正在使用Common Lisp进行一些数值计算,我需要计算具有给定数值系数的几个向量的线性组合。我正在重写一段Fortran代码,这可以通过res = a1*vec1 + a2*vec2 + ... + an*vecn完成。我最初接受CL只是简单地写每次:

(map 'vector 
  (lambda (x1 x2 ... xn)
    (+ (* x1 a1) (* x2 a2) ... (* xn an)))
  vec1 vec2 ... vecn)

但我很快就注意到这种模式会一遍又一遍地重复出现,所以开始编写一些代码来抽象它。因为向量的数量以及因此lambda参数的数量会因地而异,所以我认为需要一个宏。我想出了以下内容:

(defmacro vec-lin-com (coefficients vectors &key (type 'vector))
  (let ((args (loop for v in vectors collect (gensym))))
    `(map ',type
          (lambda ,args
            (+ ,@(mapcar #'(lambda (c a) (list '* c a)) coefficients args)))
          ,@vectors)))

Macroexpanding表达式:

(vec-lin-com (10 100 1000) (#(1 2 3) #(4 5 6) #(7 8 9)))

产生看似正确的扩张:

(MAP 'VECTOR
  (LAMBDA (#:G720 #:G721 #:G722)
    (+ (* 10 #:G720) (* 100 #:G721) (* 1000 #:G722)))
  #(1 2 3) #(4 5 6) #(7 8 9))

到目前为止,这么好...... 现在,当我尝试在这样的函数中使用它时:

(defun vector-linear-combination (coefficients vectors &key (type 'vector))
  (vec-lin-com coefficients vectors :type type))

我收到一个编译错误,主要是The value VECTORS is not of type LIST。我不知道如何处理这个问题。我觉得我错过了一些明显的东西。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:6)

你已经进入了字面陷阱。宏是语法重写,因此当您在语法列表中传递3个文字向量时,您可以在编译时对它们进行迭代,但将其替换为对列表的bindnig则不一样。宏只能看到代码,它不知道什么vectors最终将在运行时绑定到它的东西。你或许应该把它变成一个函数:

(defun vec-lin-com (coefficients vectors &key (type 'vector))
  (apply #'map 
        type
        (lambda (&rest values)
          (loop :for coefficient :in coefficients
                :for value :in values
                :sum (* coefficient value)))
        vectors))

现在,由于您传递了语法而不是列表,因此初始测试将无效。你需要引用文字:

(vec-lin-com '(10 100 1000) '(#(1 2 3) #(4 5 6) #(7 8 9)))
; ==> #(7410 8520 9630)

(defparameter *coefficients* '(10 100 1000))
(defparameter *test* '(#(1 2 3) #(4 5 6) #(7 8 9)))
(vec-lin-com *coefficients* *test*)
; ==> #(7410 8520 9630)

现在你可以把它变成一个宏,但是大部分工作都是由扩展而不是宏完成的,所以基本上你的宏会扩展到我的函数所做的类似代码。

答案 1 :(得分:3)

请记住,宏在编译时会被扩展,因此表达式,@(mapcar #'(lambda (c a) (list '* c a)) coefficients args)必须在编译时有意义。在这种情况下,mapcar得到coefficientsargs的所有内容都是源代码中的符号coefficientsvectors

如果您希望能够使用一组未知的参数调用vec-lin-com(在编译时未知,那就是),您需要将其定义为函数。听起来你遇到的主要问题是让+的参数正确排序。有一个trick使用applymap来转置可能有用的矩阵。

(defun vec-lin-com (coefficients vectors)
  (labels
      ((scale-vector (scalar vector)
         (map 'vector #'(lambda (elt) (* scalar elt)) vector))
       (add-vectors (vectors)
         (apply #'map 'vector #'+ vectors)))
    (let ((scaled-vectors (mapcar #'scale-vector coefficients vectors)))
      (add-vectors scaled-vectors))))

这不是世界上最有效的代码;它做了很多不必要的工作。但它是有效的,如果你发现这是一个瓶颈,你可以编写更高效的版本,包括一些可以利用编译时常量的版本。