Search code examples
vectorcommon-lispnumerical-methodssbcl

Computing linear combination of vectors in Common Lisp


I'm working on some numerical computations in Common Lisp and I need to compute a linear combination of several vectors with given numerical coefficients. I'm rewriting a piece of Fortran code, where this can be accomplished by res = a1*vec1 + a2*vec2 + ... + an*vecn. My initial take in CL was to simply write each time something like:

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

But I soon noticed that this pattern would recur over and over again, and so started writing some code to abstract it away. Because the number of vectors and hence the number of lambda's arguments would vary from place to place, I figured a macro would be required. I came up with the following:

(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 the expression:

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

yields the seemingly correct expansion:

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

So far, so good... Now, when I try to use it inside a function like this:

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

I get a compilation error stating essentially that The value VECTORS is not of type LIST. I'm not sure how to approach this. I feel I'm missing something obvious. Any help will be greatly appreciated.


Solution

  • You've gone into the literal trap. Macros are syntax rewriting so when you pass 3 literal vectors in a syntax list you can iterate on them at compile time, but replacing it with a bindnig to a list is not the same. The macro only gets to see the code and it doesn't know what vectors will eventually be bound to at runtime when it does its thing. You should perhaps make it a function instead:

    (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))
    

    Now you initial test won't work since you passed syntax and not lists. you need to quote literals:

    (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)
    

    Now you could make this a macro, but most of the job would have been done by the expansion and not the macro so basically you macro would expand to similar code to what my function is doing.