Skip to content

Commit e8630bc

Browse files
authored
Merge pull request #1552 from SciML/myb/nullspace
Add nullspace computation
2 parents 09913b9 + 0b83eb5 commit e8630bc

File tree

4 files changed

+141
-6
lines changed

4 files changed

+141
-6
lines changed

src/structural_transformation/bareiss.jl

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,21 +162,135 @@ swap_strategy is an optional argument that determines how the swapping of rows a
162162
bareiss_colswap (the default) swaps the columns and rows normally.
163163
bareiss_virtcolswap pretends to swap the columns which can be faster for sparse matrices.
164164
"""
165-
function bareiss!(M::AbstractMatrix, swap_strategy=bareiss_colswap;
166-
find_pivot=find_pivot_any)
167-
swapcols!, swaprows!, update!, zero! = swap_strategy;
165+
function bareiss!(M::AbstractMatrix{T}, swap_strategy=bareiss_colswap;
166+
find_pivot=find_pivot_any, column_pivots=nothing) where T
167+
swapcols!, swaprows!, update!, zero! = swap_strategy
168168
prev = one(eltype(M))
169169
n = size(M, 1)
170+
pivot = one(T)
171+
column_permuted = false
170172
for k in 1:n
171173
r = find_pivot(M, k)
172-
r === nothing && return k - 1
174+
r === nothing && return (k - 1, pivot, column_permuted)
173175
(swapto, pivot) = r
176+
if column_pivots !== nothing && k != swapto[2]
177+
column_pivots[k] = swapto[2]
178+
column_permuted |= true
179+
end
174180
if CartesianIndex(k, k) != swapto
175181
swapcols!(M, k, swapto[2])
176182
swaprows!(M, k, swapto[1])
177183
end
178184
update!(zero!, M, k, swapto, pivot, prev)
179185
prev = pivot
180186
end
181-
return n
187+
return (n, pivot, column_permuted)
188+
end
189+
190+
function nullspace(A)
191+
column_pivots = collect(1:size(A, 2))
192+
B = copy(A)
193+
(rank, d, column_permuted) = bareiss!(B; column_pivots)
194+
reduce_echelon!(B, rank, d)
195+
N = ModelingToolkit.reduced_echelon_nullspace(rank, B)
196+
apply_inv_pivot_rows!(N, column_pivots)
197+
end
198+
199+
function apply_inv_pivot_rows!(M, ipiv)
200+
for i in size(M, 1):-1:1
201+
swaprows!(M, i, ipiv[i])
202+
end
203+
M
204+
end
205+
206+
###
207+
### Modified from AbstractAlgebra.jl
208+
###
209+
### https://github.com/Nemocas/AbstractAlgebra.jl/blob/4803548c7a945f3f7bd8c63f8bb7c79fac92b11a/LICENSE.md
210+
function reduce_echelon!(A::AbstractMatrix{T}, rank, d) where T
211+
m, n = size(A)
212+
for i = rank + 1:m
213+
for j = 1:n
214+
A[i, j] = zero(T)
215+
end
216+
end
217+
if rank > 1
218+
t = zero(T)
219+
q = zero(T)
220+
d = -d
221+
pivots = zeros(Int, n)
222+
np = rank
223+
j = k = 1
224+
for i = 1:rank
225+
while iszero(A[i, j])
226+
pivots[np + k] = j
227+
j += 1
228+
k += 1
229+
end
230+
pivots[i] = j
231+
j += 1
232+
end
233+
while k <= n - rank
234+
pivots[np + k] = j
235+
j += 1
236+
k += 1
237+
end
238+
for k = 1:n - rank
239+
for i = rank - 1:-1:1
240+
t = A[i, pivots[np + k]] * d
241+
for j = i + 1:rank
242+
t += A[i, pivots[j]] * A[j, pivots[np + k]] + q
243+
end
244+
A[i, pivots[np + k]] = exactdiv(-t, A[i, pivots[i]])
245+
end
246+
end
247+
d = -d
248+
for i = 1:rank
249+
for j = 1:rank
250+
if i == j
251+
A[j, pivots[i]] = d
252+
else
253+
A[j, pivots[i]] = zero(T)
254+
end
255+
end
256+
end
257+
end
258+
return A
259+
end
260+
261+
function reduced_echelon_nullspace(rank, A::AbstractMatrix{T}) where T
262+
n = size(A, 2)
263+
nullity = n - rank
264+
U = zeros(T, n, nullity)
265+
if rank == 0
266+
for i = 1:nullity
267+
U[i, i] = one(T)
268+
end
269+
elseif nullity != 0
270+
pivots = zeros(Int, rank)
271+
nonpivots = zeros(Int, nullity)
272+
j = k = 1
273+
for i = 1:rank
274+
while iszero(A[i, j])
275+
nonpivots[k] = j
276+
j += 1
277+
k += 1
278+
end
279+
pivots[i] = j
280+
j += 1
281+
end
282+
while k <= nullity
283+
nonpivots[k] = j
284+
j += 1
285+
k += 1
286+
end
287+
d = -A[1, pivots[1]]
288+
for i = 1:nullity
289+
for j = 1:rank
290+
U[pivots[j], i] = A[j, nonpivots[i]]
291+
end
292+
U[nonpivots[i], i] = d
293+
end
294+
end
295+
return U
182296
end

src/systems/alias_elimination.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ function aag_bareiss!(graph, var_to_diff, mm_orig::SparseMatrixCLIL)
250250
swaprows!(M, i, j)
251251
end
252252
bareiss_ops = ((M,i,j)->nothing, myswaprows!, bareiss_update_virtual_colswap_mtk!, bareiss_zero!)
253-
rank3 = bareiss!(M, bareiss_ops; find_pivot=find_and_record_pivot)
253+
rank3, = bareiss!(M, bareiss_ops; find_pivot=find_and_record_pivot)
254254
rank1 = something(rank1, rank3)
255255
rank2 = something(rank2, rank3)
256256
(rank1, rank2, rank3, pivots)

test/linalg.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using ModelingToolkit
2+
using LinearAlgebra
3+
using Test
4+
5+
A = [
6+
0 1 1 2 2 1 1 2 1 2
7+
0 1 -1 -3 -2 2 1 -5 0 -5
8+
0 1 2 2 1 1 2 1 1 2
9+
0 1 1 1 2 1 1 2 2 1
10+
0 2 1 2 2 2 2 1 1 1
11+
0 1 1 1 2 2 1 1 2 1
12+
0 2 1 2 2 1 2 1 1 2
13+
0 1 7 17 14 2 1 19 4 23
14+
0 1 -1 -3 -2 1 1 -4 0 -5
15+
0 1 1 2 2 1 1 2 2 2
16+
]
17+
N = ModelingToolkit.nullspace(A)
18+
@test size(N, 2) == 3
19+
@test rank(N) == 3
20+
@test iszero(A * N)

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using SafeTestsets, Test
22

3+
@safetestset "Linear Algebra Test" begin include("linalg.jl") end
34
@safetestset "AbstractSystem Test" begin include("abstractsystem.jl") end
45
@safetestset "Variable scope tests" begin include("variable_scope.jl") end
56
@safetestset "Symbolic parameters test" begin include("symbolic_parameters.jl") end

0 commit comments

Comments
 (0)