Search code examples
matrixclojure

clojure.core.matrix:: Mutate element in matrix


I am using a matrix as follows

(require '[clojure.core.matrix :as ccm])
(def M (ccm/matrix [[1 2] [3 4]]))
(ccm/mset! M 0 0 10)

However this throws an error

IllegalArgumentException No implementation of method: :set-2d! of protocol: #'clojure.core.matrix.protocols/PIndexedSettingMutable found for class: clojure.lang.PersistentVector clojure.core/-cache-protocol-fn (core_deftype.clj:568)

However this should work according to the wiki https://mikera.github.io/core.matrix/doc/clojure.core.matrix.html#var-mset.21


Solution

  • core.matrix can use different underlying matrix implementations. By default it uses the persistent-vector implementation, which means that matrices are simply Clojure vectors containing Clojure vectors. However, Clojure vectors are immutable, so mset! won't work on them. This is the expected behavior.

    To use mset!, you need to use one of the core.matrix implementations that can make mutable matrices. ndarray is the matrix implementation that's always available by default in core.matrix, but you have to tell core.matrix that you want to use it.

    user=> (use 'clojure.core.matrix)
    nil
    user=> (matrix [[1 2] [3 4]])
    [[1 2] [3 4]]
    

    That's what a persistent-vector matrix looks like.

    You can create an ndarray matrix by passing an extra keyword to the matrix function:

    user=> (def M (matrix :ndarray [[1 2] [3 4]]))
    #'user/M
    user=> M
    #object[clojure.core.matrix.impl.ndarray_object.NDArray 0x561ddbde "[[1 2] [3 4]]"]
    

    Now mset! will work:

    user=> (mset! M 0 0 10)
    10
    user=> M
    #object[clojure.core.matrix.impl.ndarray_object.NDArray 0x561ddbde "[[10 2] [3 4]]"]
    

    Notice the elements over on the right. You can also use pm to create a nicer representation:

    user=> (pm M)
    [[10 2]
     [ 3 4]]
    

    To make ndarray the default, use set-current-implementation:

    user=> (set-current-implementation :ndarray)
    :ndarray
    
    user=> (def M (matrix [[1 2] [3 4]]))
    #'user/M
    
    user=> M
    #object[clojure.core.matrix.impl.ndarray_object.NDArray 0x5cd44e57 "[[1 2] [3 4]]"]
    
    user=> (mset! M 0 0 10)
    10
    
    user=> M
    #object[clojure.core.matrix.impl.ndarray_object.NDArray 0x5cd44e57 "[[10 2] [3 4]]"]
    

    There are several other implementations available if you set them up as dependencies (e.g. in your Leiningen project.clj). Some of them support both mutable and immutable matrices.

    By the way, as amalloy suggested, you can get a mutable matrix from an immutable matrix by passing the immutable matrix to mutable. If you do this with a persistent-vector matrix, the returned matrix will not be a persistent-vector matrix, since there are no mutable persistent-vector matrices. The new matrix will be a matrix from a different implementation. Which implementation is used depends on your current environment, but if you haven't done anything special, an ndarray matrix should be what is returned.