Common Lisp 本身就支持多维数组,比如说一维数组叫做 vectors。一般的数组可以包含任意类型 (element-type t),或者也可以指定数组只包含指定的类型,如浮点型(single-float)整型(integer)Practical Common Lisp Chapter 11, Collections 比较适合新手入门。

本章主要讲解的是 数组和向量 的一些基本操作。

以下是一些quicklisp上操作数组的包:

  • array-operationsgenerate, permute, displace, flatten, split, combine, reshape, each。该库的作者已经不再进行维护了,但有比较活跃的库在维护着。
  • cmu-infix:多维数组的索引
  • lla:线性代数库。

本章涉及的是一些内建的对数组的操作,但这也有局限性:

  • 无法与其他语言的数组之间的交互,比如说调用 BLAS, LAPACK 或 GSL 这些库时。
  • 无法进行数组之间的运算,比如说当 a b 至少又一个为数组时,(+ a b) 能够返回正常结果。

以上的问题都可以通过在 CLOS 中定义一个额外的数组类来解决,或者使用 quicklisp 上的一些库:

  • matlisp:下文会介绍。
  • MGL-MAT:在机器学习库 MGL 中使用,提供了 BLAS 和 CUDA 的一些绑定。
  • cl-ana:数据分析库
  • AntikGSLL,GNU Scientific Library 库的绑定

同时还一个新出的且很活跃的库 — MAGICL,这个库包括了 BLAS 和 LAPACK 这两个库。目前这个库不支持使用 quicklisp,只在 SBCL 和 CCL 下可以使用。这个库不只是专注与复杂的数组操作。你可以在 quicklisp 中的 local-projects 目录来安装这个库:

  1. $ cd ~/quicklisp/local-projects
  2. $ git clone https://github.com/rigetticomputing/magicl.git

在其相对应的 github web pages 上有这个库的详细介绍。

更深入点,操作特定语言也在 Common Lisp 中实现了,该特定支持多种数组间的操作。同时,也有一些比较收欢迎的库支持这种操作:

CLASP,一个针对 Common Lisp 与其他语言(尤其是C++)之间的交互的项目,由 LLVM 实现。该项目主要应用在数学/科学计算上。

创建

使用函数 CLHS:make-array 进行创建。

  1. (defparameter *my-array* (make-array '(3 2) :initial-element 1.0))
  2. *MY-ARRAY*
  3. *my-array*
  4. #2A((1.0 1.0) (1.0 1.0) (1.0 1.0))

再复杂的数组的值也可以在创建是进行初始化。

array-operations 库提供了 generate,创建数组时很方便,该函数会将迭代进行打包。

  1. (ql:quickload :array-operations)
  2. To load "array-operations":
  3. Load 1 ASDF system:
  4. array-operations
  5. ; Loading "array-operations"
  6. (:ARRAY-OPERATIONS)
  7. (aops:generate #'identity 7 :position)
  8. #(0 1 2 3 4 5 6)

其中 array-operations 的别名是 aopsgenerate 函数可以使用关键词 :subscripts 来指定从数组某个下标进行迭代赋值。具体操作见其 github repository

随机数

初始化数组时使用随机数进行填充

  1. (aops:generate (lambda () (random 1.0)) '(3 3))
  2. #2A((0.2837726 0.009566903 0.84352255)
  3. (0.22492898 0.83147097 0.2795267)
  4. (0.5844146 0.7568612 0.9189848))

另一个使用随机数进行初始化数组的包是:alexandria

  1. (ql:quickload :alexandria)
  2. To load "alexandria":
  3. Load 1 ASDF system:
  4. alexandria
  5. ; Loading "alexandria"
  6. (:ALEXANDRIA)
  7. (aops:generate #'alexandria:gaussian-random 4)
  8. #(1.6430992263819566d0 -0.5448730501612253d0 -0.027017529457142562d0
  9. 0.974829500480401d0)

访问数组中的元素

aref

  1. (defparameter *a* #(1 2 3 4))
  2. *A*
  3. (aref *a* 0)
  4. 1
  5. (aref *a* 3)
  6. 4
  7. (defparameter *b* #2A((1 2 3) (4 5 6)))
  8. *B*
  9. (aref *b* 1 0)
  10. 4
  11. (aref *b* 0 2)
  12. 3

array-dimensions, array-rank

  1. (array-dimensions *a*)
  2. (4)
  3. (array-dimensions *b*)
  4. (2 3)
  5. (array-rank *a*)
  6. 1
  7. (array-dimension *a* 0)
  8. 4
  9. (array-rank *b*)
  10. 2
  11. (array-dimension *b* 0)
  12. 2
  13. (array-dimension *b* 1)
  14. 3
  15. ;; loop over an array nested loops
  16. (defparameter a #2A((1 2 3) (4 5 6)))
  17. A
  18. (destructuring-bind (n m) (array-dimensions a)
  19. (loop for i from 0 below n do
  20. (loop for j from 0 below m do
  21. (format t "a[~a ~a] = ~a~%" i j (aref a i j)))))
  22. a[0 0] = 1
  23. a[0 1] = 2
  24. a[0 2] = 3
  25. a[1 0] = 4
  26. a[1 1] = 5
  27. a[1 2] = 6
  28. NIL

nested-loop

  1. (defmacro nested-loop (syms dimensions &body body)
  2. "Iterates over a multidimensional range of indices.
  3. SYMS must be a list of symbols, with the first symbol
  4. corresponding to the outermost loop.
  5. DIMENSIONS will be evaluated, and must be a list of
  6. dimension sizes, of the same length as SYMS.
  7. Example:
  8. (nested-loop (i j) '(10 20) (format t '~a ~a~%' i j))
  9. "
  10. (unless syms (return-from nested-loop `(progn ,@body))) ; No symbols
  11. ;; Generate gensyms for dimension sizes
  12. (let* ((rank (length syms))
  13. (syms-rev (reverse syms)) ; Reverse, since starting with innermost
  14. (dims-rev (loop for i from 0 below rank collecting (gensym))) ; innermost dimension first
  15. (result `(progn ,@body))) ; Start with innermost expression
  16. ;; Wrap previous result inside a loop for each dimension
  17. (loop for sym in syms-rev for dim in dims-rev do
  18. (unless (symbolp sym) (error "~S is not a symbol. First argument to nested-loop must be a list of symbols" sym))
  19. (setf result
  20. `(loop for ,sym from 0 below ,dim do
  21. ,result)))
  22. ;; Add checking of rank and dimension types, and get dimensions into gensym list
  23. (let ((dims (gensym)))
  24. `(let ((,dims ,dimensions))
  25. (unless (= (length ,dims) ,rank) (error "Incorrect number of dimensions: Expected ~a but got ~a" ,rank (length ,dims)))
  26. (dolist (dim ,dims)
  27. (unless (integerp dim) (error "Dimensions must be integers: ~S" dim)))
  28. (destructuring-bind ,(reverse dims-rev) ,dims ; Dimensions reversed so that innermost is last
  29. ,result)))))

然后可以输出二维数组了:

  1. (defparameter a #2A((1 2 3) (4 5 6)))
  2. A
  3. (nested-loop (i j) (array-dimensions a)
  4. (format t "a[~a ~a] = ~a~%" i j (aref a i j)))
  5. a[0 0] = 1
  6. a[0 1] = 2
  7. a[0 2] = 3
  8. a[1 0] = 4
  9. a[1 1] = 5
  10. a[1 2] = 6
  11. NIL

行索引

某些场合下,尤其是进行元素相乘的时候,数组的维度不是重要。当要写一些与维度无关的代码时,可以使用 row-major-aref 来获取相对应的元素,访问是就像将整个数组展开成一个一维数组,然后进行访问,该一维数组的大小为 array-total-size

  1. (defparameter a #2A((1 2 3) (4 5 6)))
  2. A
  3. (array-total-size a)
  4. 6
  5. (loop for i from 0 below (array-total-size a) do
  6. (setf (row-major-aref a i) (+ 2.0 (row-major-aref a i))))
  7. NIL
  8. a
  9. #2A((3.0 4.0 5.0) (6.0 7.0 8.0))

插入语法

cmu-infix 提供了别样的语法,让数学表达式更易读:

  1. (ql:quickload :cmu-infix)
  2. To load "cmu-infix":
  3. Load 1 ASDF system:
  4. cmu-infix
  5. ; Loading "cmu-infix"
  6. (:CMU-INFIX)
  7. (named-readtables:in-readtable cmu-infix:syntax)
  8. (("COMMON-LISP-USER" . #<NAMED-READTABLE CMU-INFIX:SYNTAX {10030158B3}>)
  9. ...)
  10. (defparameter arr (make-array '(3 2) :initial-element 1.0))
  11. ARR
  12. #i(arr[0 1] = 2.0)
  13. 2.0
  14. arr
  15. #2A((1.0 2.0) (1.0 1.0) (1.0 1.0))

矩阵相乘可以这样实现:

  1. (let ((A #2A((1 2) (3 4)))
  2. (B #2A((5 6) (7 8)))
  3. (result (make-array '(2 2) :initial-element 0.0)))
  4. (loop for i from 0 to 1 do
  5. (loop for j from 0 to 1 do
  6. (loop for k from 0 to 1 do
  7. #i(result[i j] += A[i k] * B[k j]))))
  8. result)

Element-wise 操作

each

  1. (aops:each #'* #(1 2 3) #(2 3 4))
  2. #(2 6 12)

aops:each*

  1. (defparameter *a* #(1 2 3 4))
  2. *A*
  3. (aops:each (lambda (it) (+ 42 it)) *a*)
  4. #(43 44 45 46)
  5. *a*
  6. #(1 2 3 4)

向量操作

  1. (defmacro vectorize (variables &body body)
  2. ;; Check that variables is a list of only symbols
  3. (dolist (var variables)
  4. (if (not (symbolp var))
  5. (error "~S is not a symbol" var)))
  6. ;; Get the size of the first variable, and create a new array
  7. ;; of the same type for the result
  8. `(let ((size (array-total-size ,(first variables))) ; Total array size (same for all variables)
  9. (result (make-array (array-dimensions ,(first variables)) ; Returned array
  10. :element-type (array-element-type ,(first variables)))))
  11. ;; Check that all variables have the same sizeo
  12. ,@(mapcar (lambda (var) `(if (not (equal (array-dimensions ,(first variables))
  13. (array-dimensions ,var)))
  14. (error "~S and ~S have different dimensions" ',(first variables) ',var)))
  15. (rest variables))
  16. (defparameter *a* #(1 2 3 4))
  17. *A*
  18. (vectorize (*a*) (* 2 *a*))
  19. #(2 4 6 8)
  20. (defparameter a #(1 2 3 4)
  21. A
  22. (defparameter b #(2 3 4 5))
  23. B
  24. (vectorize (a b) (* a (sin b)))
  25. #(0.9092974 0.28224 -2.2704074 -3.8356972)
  26. ;; combined with cmu-infix
  27. (vectorize (a b) #i(a * sin(b)))
  28. #(0.9092974 0.28224 -2.2704074 -3.8356972)

调用 BLAS

数组的缩放

  1. (defparameter a #(1 2 3))
  2. (lla:scal! 2.0 a)
  3. a
  4. ;; #(2.0d0 4.0d0 6.0d0)

AXPY

  1. (defparameter x #(1 2 3))
  2. ;; A
  3. (defparameter y #(2 3 4))
  4. ;; B
  5. (lla:axpy! 0.5 x y)
  6. ;; #(2.5d0 4.0d0 5.5d0)
  7. x
  8. ;; #(1.0d0 2.0d0 3.0d0)
  9. y
  10. ;; #(2.5d0 4.0d0 5.5d0)

y 是复数数组时:

  1. (defparameter x #(1 2 3))
  2. (defparameter y (make-array 3 :element-type '(complex double-float)
  3. :initial-element #C(1d0 1d0)))
  4. y
  5. ;; => #(#C(1.0d0 1.0d0) #C(1.0d0 1.0d0) #C(1.0d0 1.0d0))
  6. (lla:axpy! #C(0.5 0.5) a b)
  7. ;; => #(#C(1.5d0 1.5d0) #C(2.0d0 2.0d0) #C(2.5d0 2.5d0))

点乘

  1. (defparameter x #(1 2 3))
  2. (defparameter y #(1 2 3))
  3. (lla:dot x y)
  4. ;; => 20.0d0

Reductions

  1. (defparameter a #2A((1 2) (3 4)))
  2. ;; A
  3. (reduce #'max (make-array (array-total-size a) :displaced-to a))
  4. ;; => 4

array-operations 中包括 flatten

  1. (reduce #'max (aops:flatten a)

array-storage-vector

  1. (reduce #'max (array-storage-vector a))
  2. ;; => 4
  3. (defparameter a #2A((1 2) (3 4)))
  4. ;; A
  5. (defparameter b #2A((1 3) (5 4)))
  6. ;; B
  7. (reduce #'max (aops:flatten
  8. (aops:each
  9. (lambda (a b) (abs (- a b))) a b)))
  10. ;; 2

vectorize-reduce

  1. defmacro vectorize-reduce (fn variables &body body)
  2. "Performs a reduction using FN over all elements in a vectorized expression
  3. on array VARIABLES.
  4. VARIABLES must be a list of symbols bound to arrays.
  5. Each array must have the same dimensions. These are
  6. checked at compile and run-time respectively.
  7. "
  8. ;; Check that variables is a list of only symbols
  9. (dolist (var variables)
  10. (if (not (symbolp var))
  11. (error "~S is not a symbol" var)))
  12. (let ((size (gensym)) ; Total array size (same for all variables)
  13. (result (gensym)) ; Returned value
  14. (indx (gensym))) ; Index inside loop from 0 to size
  15. ;; Get the size of the first variable
  16. `(let ((,size (array-total-size ,(first variables))))
  17. ;; Check that all variables have the same size
  18. ,@(mapcar (lambda (var) `(if (not (equal (array-dimensions ,(first variables))
  19. (array-dimensions ,var)))
  20. (error "~S and ~S have different dimensions" ',(first variables) ',var)))
  21. (rest variables))
  22. ;; Apply FN with the first two elements (or fewer if size < 2)
  23. (let ((,result (apply ,fn (loop for ,indx below (min ,size 2) collecting
  24. (let ,(map 'list (lambda (var) (list var `(row-major-aref ,var ,indx))) variables)
  25. (progn ,@body))))))
  26. ;; Loop over the remaining indices
  27. (loop for ,indx from 2 below ,size do
  28. ;; Locally redefine variables to be scalars at a given index
  29. (let ,(mapcar (lambda (var) (list var `(row-major-aref ,var ,indx))) variables)
  30. ;; User-supplied function body now evaluated for each index in turn
  31. (setf ,result (funcall ,fn ,result (progn ,@body)))))
  32. ,result))))

上面的宏只能在这个 array-operations 中有用,而不是 quicklisp 中。

  1. (vectorize-reduce #'max (a) a)

比较两个数组的大小时需要数组维度大小相同

  1. (vectorize-reduce #'max (a b) (abs (- a b)))

线性代数

一些包含 BLAS 和 LAPACK 的包有:

更完整的库列表见:CLiki’s linear algebra page
加载 lla 包:

  1. (ql:quickload :lla)
  2. To load "lla":
  3. Load 1 ASDF system:
  4. lla
  5. ; Loading "lla"
  6. (:LLA)

矩阵相乘

向量点乘

  1. (lla:mm #(1 2 3) #(2 3 4))
  2. ;; 20

矩阵与向量相乘(叉乘)

  1. (lla:mm #2A((1 1 1) (2 2 2) (3 3 3)) #(2 3 4))
  2. ;; #(9.0d0 18.0d0 27.0d0)

矩阵相乘

  1. (lla:mm #2A((1 2 3) (1 2 3) (1 2 3)) #2A((2 3 4) (2 3 4) (2 3 4)))
  2. ;; #2A((12.0d0 18.0d0 24.0d0) (12.0d0 18.0d0 24.0d0) (12.0d0 18.0d0 24.0d0))

注意,矩阵运算后的结果都是 double-float 类型。

Outer Product

  1. (ql:quickload :array-operations)
  2. To load "array-operations":
  3. Load 1 ASDF system:
  4. array-operations
  5. ; Loading "array-operations"
  6. (:ARRAY-OPERATIONS)
  7. (aops:outer #'* #(1 2 3) #(2 3 4))
  8. ;; #2A((2 3 4) (4 6 8) (6 9 12))

A[i j] = B[i] * C[j]

转置

  1. (lla:invert #2A((1 0 0) (0 1 0) (0 0 1)))
  2. ;; #2A((1.0d0 0.0d0 -0.0d0) (0.0d0 1.0d0 -0.0d0) (0.0d0 0.0d0 1.0d0))
  3. (defparameter a #2A((1 2 3) (0 2 1) (1 3 2)))
  4. ;; A
  5. (defparameter b (lla:invert a))
  6. ;; B
  7. (lla:mm a b)
  8. ;; #2A((1.0d0 2.220446049250313d-16 0.0d0)
  9. (0.0d0 1.0d0 0.0d0)
  10. (0.0d0 1.1102230246251565d-16 0.9999999999999998d0))
  11. (defparameter a #2A((1 2 3) (0 2 1) (1 3 2)))
  12. ;; A
  13. (defparameter b (lla:mm a #(1 2 3)))
  14. ;; B
  15. (lla:solve (lla:lu a) b)
  16. ;; #(1.0d0 2.0d0 3.0d0)

Singular value decomposition

  1. * (defparameter a #2A((1 2 3) (0 2 1) (1 3 2)))
  2. A
  3. * (defparameter a-svd (lla:svd a))
  4. A-SVD
  5. * a-svd
  6. #S(LLA:SVD
  7. :U #2A((-0.6494608633564334d0 0.7205486773948702d0 0.24292013188045855d0)
  8. (-0.3744175632000917d0 -0.5810891192666799d0 0.7225973455785591d0)
  9. (-0.6618248071322363d0 -0.3783451320875919d0 -0.6471807210432038d0))
  10. :D #S(CL-NUM-UTILS.MATRIX:DIAGONAL-MATRIX
  11. :ELEMENTS #(5.593122609997059d0 1.2364443401235103d0
  12. 0.43380279311714376d0))
  13. :VT #2A((-0.2344460799312531d0 -0.7211054639318696d0 -0.6519524104506949d0)
  14. (0.2767642134809678d0 -0.6924017945853318d0 0.6663192365460215d0)
  15. (-0.9318994611765425d0 -0.02422116311440764d0 0.3619070730398283d0)))
  16. (lla:svd-u a-svd)
  17. #2A((-0.6494608633564334d0 0.7205486773948702d0 0.24292013188045855d0)
  18. (-0.3744175632000917d0 -0.5810891192666799d0 0.7225973455785591d0)
  19. (-0.6618248071322363d0 -0.3783451320875919d0 -0.6471807210432038d0))
  20. * (lla:svd-d a-svd)
  21. #S(CL-NUM-UTILS.MATRIX:DIAGONAL-MATRIX
  22. :ELEMENTS #(5.593122609997059d0 1.2364443401235103d0 0.43380279311714376d0))
  23. * (lla:svd-vt a-svd)
  24. #2A((-0.2344460799312531d0 -0.7211054639318696d0 -0.6519524104506949d0)
  25. (0.2767642134809678d0 -0.6924017945853318d0 0.6663192365460215d0)
  26. (-0.9318994611765425d0 -0.02422116311440764d0 0.3619070730398283d0))

Matlisp

  1. (ql:quickload :matlisp)
  2. (defpackage :my-new-code
  3. (:use :common-lisp :matlisp))
  4. #<PACKAGE "MY-NEW-CODE">
  5. (in-package ;my-new-code)
  6. ;; use the #i infix reader(note the same name as for cmu-infix)
  7. (named-readtables:in-readtable :infix-dispatch-table)

张量(tensor)的创建

  1. * (matlisp:zeros '(2 2))
  2. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 2)
  3. 0.000 0.000
  4. 0.000 0.000
  5. >
  6. * (matlisp:zeros '(2 2) '((complex double-float)))
  7. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: (COMPLEX DOUBLE-FLOAT)>| #(2 2)
  8. 0.000 0.000
  9. 0.000 0.000
  10. >
  11. * (matlisp:eye '(3 3) '((complex double-float)))
  12. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: (COMPLEX DOUBLE-FLOAT)>| #(3 3)
  13. 1.000 0.000 0.000
  14. 0.000 1.000 0.000
  15. 0.000 0.000 1.000
  16. >

Ranges

  1. * (matlisp:range 1 10)
  2. #<|<SIMPLE-DENSE-TENSOR: (INTEGER 0 4611686018427387903)>| #(9)
  3. 1 2 3 4 5 6 7 8 9
  4. >
  5. * (matlisp:range 1 -3.5)
  6. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: SINGLE-FLOAT>| #(5)
  7. 1.000 0.000 -1.000 -2.000 -3.000
  8. >
  9. * (matlisp:range 1 3.3)
  10. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: SINGLE-FLOAT>| #(3)
  11. 1.000 2.000 3.000
  12. >
  13. * (matlisp:linspace 1 10)
  14. #<|<SIMPLE-DENSE-TENSOR: (INTEGER 0 4611686018427387903)>| #(10)
  15. 1 2 3 4 5 6 7 8 9 10
  16. >
  17. * (matlisp:linspace 0 (* 2 pi) 5)
  18. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(5)
  19. 0.000 1.571 3.142 4.712 6.283
  20. >

随机数

  1. * (matlisp:random-uniform '(2 2))
  2. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 2)
  3. 0.7287 0.9480
  4. 2.6703E-2 0.1834
  5. >
  6. (matlisp:random-normal '(2 2))
  7. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 2)
  8. 0.3536 -1.291
  9. -0.3877 -1.371
  10. >

Reader macros

  1. * #d[1,2,3]
  2. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(3)
  3. 1.000 2.000 3.000
  4. >
  5. * #d[[1,2,3],[4,5,6]]
  6. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 3)
  7. 1.000 2.000 3.000
  8. 4.000 5.000 6.000
  9. >

Tensors from arrays

  1. * (copy #2A((1 2 3)
  2. (4 5 6))
  3. '#.(tensor 'double-float))
  4. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 3)
  5. 1.000 2.000 3.000
  6. 4.000 5.000 6.000
  7. >
  8. (make-instance (tensor 'double-float)
  9. :dimensions (coerce '(2) '(simple-array index-type (*)))
  10. :store (make-array 2 :element-type 'double-float))

Arrays from tensors

  1. * (defparameter vec (m:range 0 5))
  2. * vec
  3. #<|<SIMPLE-DENSE-TENSOR: (INTEGER 0 4611686018427387903)>| #(5)
  4. 0 1 2 3 4
  5. >
  6. * (slot-value vec 'm:store)
  7. #(0 1 2 3 4)
  8. * (let ((tens (m:ones '(2 3))))
  9. (m:copy tens 'array))
  10. #2A((1.0d0 1.0d0 1.0d0) (1.0d0 1.0d0 1.0d0))
  11. * (m:copy (m:ones '(2 3)) 'cons)
  12. ((1.0d0 1.0d0 1.0d0) (1.0d0 1.0d0 1.0d0))

访问元素

  1. * (defparameter a (matlisp:ones '(2 3)))
  2. * (setf (ref a 1 1) 2.0)
  3. 2.0d0
  4. * a
  5. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 3)
  6. 1.000 1.000 1.000
  7. 1.000 2.000 1.000
  8. >

位操作

  1. * (matlisp-user:* 2 (ones '(2 3)))
  2. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 3)
  3. 2.000 2.000 2.000
  4. 2.000 2.000 2.000
  5. >
  6. * (let ((a (ones '(2 2)))
  7. (b (random-normal '(2 2))))
  8. #i( 2 * a + b ))
  9. #<|<BLAS-MIXIN SIMPLE-DENSE-TENSOR: DOUBLE-FLOAT>| #(2 2)
  10. 0.9684 3.250
  11. 1.593 1.508
  12. >
  13. * (let ((a (ones '(2 2)))
  14. (b (random-normal '(2 2))))
  15. (macroexpand-1 '#i( 2 * a + b )))
  16. (MATLISP-USER:+ (MATLISP-USER:* 2 A) B)