Updating attributes within a class

  • Context: Python 
  • Thread starter Thread starter schniefen
  • Start date Start date
  • Tags Tags
    Class
Click For Summary

Discussion Overview

The discussion revolves around the implementation and updating of attributes within a sparse matrix class, specifically focusing on the internal representation of a sparse matrix in CSR (Compressed Sparse Row) format. Participants explore how to ensure that changes to the input matrix are reflected in the internal attributes of the class, including A, IA, and JA, as well as the implications of method design for updating these attributes.

Discussion Character

  • Technical explanation
  • Conceptual clarification
  • Debate/contested

Main Points Raised

  • One participant describes the structure of their sparse matrix class and notes that changes to the input matrix do not automatically update the internal attributes A, IA, and JA.
  • Another participant explains that the input matrix reference remains the same, which is why updates to the matrix are reflected, but the sparse matrix class does not automatically know when to recalculate its attributes.
  • There is a suggestion that methods should be added to the sparse matrix class to handle updates to the matrix and recalculate the internal attributes accordingly.
  • A participant expresses a desire to avoid redundancy in recalculating attributes when changing matrix values and seeks a more efficient method.
  • Concerns are raised about the behavior of the toCSC method, where returning the same instance leads to unexpected changes in the original instance's attributes.
  • One participant notes that the calculations for updating attributes are straightforward but questions the necessity of repeating them in the change method.
  • Another participant points out that the toCSC method returns the same instance, which leads to confusion regarding attribute changes.

Areas of Agreement / Disagreement

Participants generally agree on the need for methods to handle updates to the matrix and recalculations of attributes. However, there is no consensus on the best approach to avoid redundancy in calculations or how to manage instance behavior in the toCSC method.

Contextual Notes

Participants discuss the limitations of the current implementation, particularly regarding the need to recalculate attributes every time the matrix changes and the implications of returning the same instance in method calls.

schniefen
Messages
177
Reaction score
4
TL;DR
I'm creating a sparse matrix class and am stuck on how to update the attributes within the class.
I'm working on a sparse matrix class, where I'm creating an internal representation of a random matrix a la CSR (Sparse Matrix). 'A' signifies the non-zero elements in the matrix, 'IA' are the rowpointers and 'JA' the column indices of the non-zero elements.
Python:
import numpy as np
class SparseMatrix:
    def __init__(self,Matrix):
        if not isinstance(Matrix,np.ndarray):
            raise TypeError('Matrix should be of type np.ndarray')
        self._matrix=Matrix
        #setting up an internal representation of CSR, A and JA
        self._A=self._matrix[self._matrix!=0]
        self._JA=np.nonzero(self._matrix)[1]
        #IA
        rowpointer=[0]
        for i in range(self._matrix.shape[0]):
            indices=len(np.nonzero(self._matrix[i])[0])
            rowpointer.append(indices+rowpointer[i])
        self._IA=np.asarray(rowpointer)
        # more attributes
        self._number_of_nonzero=len(np.nonzero(self._matrix)[0])
        self.intern_represent='CSR'
    def __repr__(self):
        return 'A={}\nIA={}\nJA={}'.format(self._A,self._IA,self._JA)
When I change a value in the input matrix, this is registered and updated in the attribute self._matrix. But neither A, IA, or JA are.
Python:
d=np.array([[0,0,0,0],
            [5,8,0,0],
            [0,0,3,0],
            [0,6,0,0]])
f=SparseMatrix(d)
f._matrix[0,0]=9 #change first element of matrix to 9
print(f._matrix)
print(f)
Code:
array([[9,0,0,0],
       [5,8,0,0],
       [0,0,3,0],
       [0,6,0,0]])
A=[5 8 3 6]
IA=[0 0 2 3 4]
JA=[0 1 2 1]
4
A should be equal to [9 5 8 3 6] and _number_of_nonzero should be equal to 5. IA and JA should then also change. How can I update self._A, self._IA and self._JA? I've been experimenting with setter and getter methods, but without success. Furthermore, I'm confused why self._matrix updates automatically.
 
Technology news on Phys.org
schniefen said:
How can I update self._A, self._IA and self._JA?

You would have to recalculate them every time the matrix changes. But you don't have any way of automatically catching when the matrix changes. See below.

schniefen said:
I'm confused why self._matrix updates automatically.

It doesn't "update"; the variable self._matrix refers to the same object as the Matrix that you pass to the class constructor. Since it's a reference to the same object, looking at its attributes will show you the same attributes, including any attributes that get updated. But references to objects don't get notified when the objects change, unless you explicitly set up some way for that to happen, so your sparse matrix class won't know when to look for updated attributes to use in recalculating self._A, self._IA, and self._JA.

If the only times that the matrix ever changes is when you change it in your own code, then you could just add methods to your sparse matrix class that do the changes to everything: in other words, instead of changing Matrix directly, you would call a method on your sparse matrix class instance that updates self._matrix (which will change the matrix that you passed to the class constructor) and then updates self._A, self._IA, and self._JA.

If there is other code you don't control that can update the matrix, then you won't have any way of knowing when the matrix gets updated. The only way I could see to solve your problem in that case would be to subclass the matrix class and override the update methods; but then you would have to have a way of telling the other code that you don't control to use your subclass instead of the usual matrix class.
 
  • Like
Likes   Reactions: schniefen
PeterDonis said:
If the only times that the matrix ever changes is when you change it in your own code, then you could just add methods to your sparse matrix class that do the changes to everything: in other words, instead of changing Matrix directly, you would call a method on your sparse matrix class instance that updates self._matrix (which will change the matrix that you passed to the class constructor) and then updates self._A, self._IA, and self._JA.
Thanks for a clarifying answer. How would I go about implementing this? I suspect this is what I was after in the first place, i.e. call on a method in the class that changes the value in the matrix for me.
Python:
f._matrix[0,0]=9 # instead of this
f._matrix_change([0,0],9) #maybe something like this?
 
schniefen said:
I suspect this is what I was after in the first place, i.e. call on a method in the class that changes the value in the matrix for me.

Yes, that's the sort of thing you need to do; the code in the method _matrix_change would update the matrix and also recalculate the other attributes.
 
  • Like
Likes   Reactions: schniefen
Is there any clever way of shortening the recalculations of self._A, self._IA and self._JA when changing the value of the matrix? I've defined my method 'change', but I currently need to repeat the calculations from the __init__ method.

Python:
import numpy as np
class SparseMatrix:
    def __init__(self,A):
        if not isinstance(A,np.ndarray):
            raise TypeError('Matrix should be of type np.ndarray')
        self._matrix=A
        self._A=self._matrix[self._matrix!=0]
        self._JA=np.nonzero(self._matrix)[1]
        rp=[0]
        for i in range(self._matrix.shape[0]):
            indices=len(np.nonzero(self._matrix[i])[0])
            rp.append(indices+rp[i])
        self._IA=np.asarray(rp)
        self.intern_represent='CSR'
        self.number_of_nonzero=len(np.nonzero(self._matrix)[0])
    def change(self,i,j,val):
        self._matrix[i,j]=val
        self._A=self._matrix[self._matrix!=0] # this feels redundant, but probably necessary
        self._JA=np.nonzero(self._matrix)[1]
        rp=[0]
        for i in range(self._matrix.shape[0]):
            indices=len(np.nonzero(self._matrix[i])[0])
            rp.append(indices+rp[i])
        self._IA=np.asarray(rp)
        self.number_of_nonzero=len(np.nonzero(self._matrix)[0])
    def __repr__(self):
        return 'A={}\nIA={}\nJA={}'.format(self._A,self._IA,self._JA)

I'd also like to add a method toCSC where I want to convert my matrix to CSC format. Mathematically this is only CSR of the transpose of the input matrix. However, I'm facing a bit of an odd problem with the intern_represent attribute, which I'd like to change when this method is activated.

Python:
class SparseMatrix:
    ...

    ...
    def toCSC(self):
        m=np.transpose(self._matrix)
        self._matrix=m
        self._A=self._matrix[self._matrix!=0]
        self._JA=np.nonzero(self._matrix)[1]
        rp=[0]
        for i in range(self._matrix.shape[0]):
            indices=len(np.nonzero(self._matrix[i])[0])
            rp.append(indices+rp[i])
        self._IA=np.asarray(rp)
        self.intern_represent='CSC'
        self.number_of_nonzero=len(np.nonzero(self._matrix)[0])
        return self
    def __repr__(self):
        return 'A={}\nIA={}\nJA={}'.format(self._A,self._IA,self._JA)
r=SparseMatrix(d) #d is some matrix
s=r.toCSC()
print(r.intern_represent)
print(s.intern_represent)
Code:
CSC
CSC
Whereas...
Python:
r=SparseMatrix(d) 
print(r.intern_represent)
s=r.toCSC()
print(s.intern_represent)
Code:
CSR
CSC
It seems like I'm changing the instance r as I'm creating s. Can this be avoided?
 
schniefen said:
I've defined my method 'change', but I currently need to repeat the calculations from the __init__ method.

Those calculations are already pretty short, so I don't see any urgent need to make them shorter. Also, doing them the same way every time makes the code easier to understand.

schniefen said:
It seems like I'm changing the instance r as I'm creating s.

No, you're returning the same instance r as instance s. Your toCSC method returns self; that means it returns the same instance you called it on.

If you want to avoid that, you need to construct a new SparseMatrix instance in the toCSC method and return it.
 
  • Like
Likes   Reactions: schniefen
I'm facing another issue when writing the __mul__ method, where I aim to define multiplication with a vector.

Python:
class SparseMatrix(SparseMatrix):
 
    def __mul__(self,other):
        if isinstance(other,np.ndarray):
            x=SparseMatrix(np.dot(self._matrix,other))
            return x
    def __rmul__(self,other):
        if isinstance(other,np.ndarray):
            x=SparseMatrix(np.dot(other,self._matrix))
            return x
matrix=np.array([[0,0,0,0],
                 [5,8,0,0],
                 [0,0,3,0],
                 [0,6,0,0]])
vector=np.array([2,2,2,2])
print(SparseMatrix(matrix)*vector)
print(vector*SparseMatrix(matrix))
And I'm getting a wrong output for the last print statement. It seems something is not working with __rmul__ method.
Code:
A=[26  6 12]
IA=[0 0 1 2 3]
JA=[0 0 0]
[None None None None]
 
Obviously matrix multiplication isn't possible as indicated in the second print statement, and that's probably the cause of the wrong output. But why doesn't np.dot catch this?
 
I'm not sure your __rmul__ method is even being called in the second print statement. The numpy array object already has a __mul__ method itself, so I think that's what will be called there. I think __rmul__ only gets called on the right object of a multiplication if the left object doesn't have a __mul__ method.
 

Similar threads

  • · Replies 43 ·
2
Replies
43
Views
4K
  • · Replies 1 ·
Replies
1
Views
3K