Dynamic immutable type

Hello Julians,

I’m trying to design a type MyType having a certain number of fields amongst which matrix::AbstractMatrix. In production MyType is intended to have its field matrix modified (hundreds times a day)… I’ve encountered some difficulties in building MyType’s updaters resizing/reshaping matrix.
some constraints:
MyType should be immutable.
matrix should be a 2-d array.

Questions

  1. Is there a way to resize!(matrix) (empty, update…)?
  2. If not, I thought about building the field matrix as a reshaped view on another MyType’s field/property, say kern::AbstractVector for which operations like resize! are more “natural” so that genuinely built updaters on kern will lead matrix (the rehaped view of kern) reflect those changes. Is there a way to work around with this idea? A better idea? What are some drawbacks regarding performance etc. ? Should I just let MyType be a mutable struct and move on?
1 Like

A field of a strictly immutable composite type does not need to reference other immutables, and you can make mutation API forward to that field. That’s how many wrappers of mutable arrays work. A mutable composite type would only add the ability to reassign a different matrix to the field, which is not mutation of the matrix and is likely more expensive.

The real problem is that matrices, or rather any array that isn’t a vector, aren’t straightforwardly resizable. Some examples:

  • push!-ing 1 element to a MxN matrix where N != 1 can’t result in a matrix
  • resize!-ing an MxN matrix to P elements isn’t guaranteed to result in a matrix
  • resize!-ing an MxN matrix to a PxQ matrix has no unique result and would need to copy around most of the elements in general

A matrix type that supports more resizing is in ElasticArrays.jl, but that only supports resizing along the last dimension to avoid those issues.

2 Likes

Do you have a bound on the maximum size of the matrix?

1 Like

Thank you for your reply.
Please, precize what you mean by “…and you can make mutation API forward to that field.” is there a way to make a specific field mutable?
By the way, you say that viewing another field is not a good idea?

No sir, I do not have any limit. It can grow to any size… but in practice the number of columns will unlikely go above 40.
It’s the number of rows that can go to the thousands…
Though my updaters are there to ensure that sizes/data are correct.

Yes, but read on. Whenever you create a matrix, the system allocates you a block of memory for storing this matrix. The reason why you can’t resize!(matrix) is because there is other data near this piece of memory, and easily expanding this matrix might overwrite important data in the surrounding memory. But shrinking this matrix is possible. So if you know the maximum size of the matrix, you can preallocate a maximum block of memory to store it. The following code gives you the freedom to change the size of the matrix with 0 cost:

using Buffers
buffer1 = MAllocBuffer(100 * 100); # preallocate 100*100 momery
A = alloc!(buffer1, 10, 10) # a 10*10 matrix `A`
# then change the size of `A`.
drop!(buffer, A)
A = alloc!(buffer1, 50, 60)

However, according to your description, the change in the size of that array is very dynamic and can get very large. So, preallocation is not a good idea. There are still two ways to do this:

  1. create your MyType as a mutable struct and reallocate the matrix each time its size changes, this method is best suited to your dynamic size characteristics.
  2. use a buffer that automatically expands when you run out of buffers, for example:
using Buffers
buffer = Buffer(100)
A = alloc!(buffer, 10, 10)
drop!(buffer, A)
A = alloc!(buffer, 100, 100)

Note that alloc!(buffer, 10, 10) returns a matrix view, which makes matrix A slightly slower to compute than MAllocBuffer. It’s best to actually test which method faster.

But I think both methods are definitely faster than the second point you made, which is to create a vector and view it as a matrix and engage the operation.

2 Likes

Some workarounds:

  • introduce a wrapper type for matrix:
    mutable struct ReplaceableMatrix
        matrix::Matrix{Float64}
    end
    
    struct MyType
        some_field::SomeType
        matrix::ReplaceableMatrix
    end
    
  • make MyType mutable with all fields immutable, except for matrix:
    mutable struct MyType
        const some_field::SomeType
        matrix::ReplaceableMatrix
    end
    

It would be possible to add a resize! method for Array, however, as some have pointed out, the desired semantics of such a method would not be obvious (what should happen to the existing elements).

2 Likes

My idea was to have a maximal fixed-size matrix and just store integers telling you which submatrix is currently in use

4 Likes

Buffers, ok thanks. But what I don’t want is to had more dependencies…

Yes sir, I got you.

Haha, mutable struct ReplaceableMatrix… was my first idea but thought was too easy… definitely what I’m gonna do!! Thank you!!

1 Like