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

Computing linear combination of vectors in Common Lisp

我正在使用 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)))

宏扩展表达式:

(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。我不确定如何处理这个问题。我觉得我错过了一些明显的东西。任何帮助将不胜感激。

你掉进了文字陷阱。宏是语法重写,因此当您在语法列表中传递 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)

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

请记住,宏在编译时展开,因此表达式 ,@(mapcar #'(lambda (c a) (list '* c a)) coefficients args) 在编译时必须有意义。在这种情况下,mapcarcoefficientsargs 得到的所有内容都是源代码中的符号 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))))

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