You may use the following command: np.fill_diagonal(a, 0)
(https://numpy.org/doc/stable/reference/generated/numpy.fill_diagonal.html)
Following your clarifications: if I understand well what you want to do, then you can distinguish between two cases.
- a invertible ==> use
X = np.linalg.solve(a, A)
- a not invertible ==> in this case there can be either no solution or infinitely many solutions.
For example, if a is not invertible but A is invertible, then there is no solution (otherwise X*A^-1 would provide an inverse for a). In general, a necessary condition for a solution to exist is that rk(A) <= rk(a) (by the rank theorem).
In this other case, there are infinitely many solutions
a = array([[0, 0, 0],
[0, 2, 0],
[0, 0, 1]])
A = array([[0., 0., 0.],
[0., 0., 1.],
[0., 1., 0.]])
since
array([[0. , 0. , 0. ], array([[1., 1., 1.],
X = [0. , 0. , 0.5], + lbda * [0., 0., 0.],
[0. , 1. , 0. ]]) [0., 0., 0.]])
solves np.dot(a,X) = A
for each real value of lbda.
If you are in this second case (infinitely many solutions), you can use
X = np.linalg.lstsq(a,A)[0]
which provides a solution even in the case when a is not invertible (and returns the same result as np.linalg.solve if a is invertible).
If no solution exists, this command returns a matrix such that np.dot(a,X) is "as close as possible" to A. You can realize that this is the case by adding a final check like assert np.max(np.abs(np.dot(a,X) - A)) < 1E-5
.