-
Notifications
You must be signed in to change notification settings - Fork 63
/
Solve.jl
1378 lines (1162 loc) · 44.3 KB
/
Solve.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
module Solve
using AbstractAlgebra
using AbstractAlgebra.PrettyPrinting: @show_name
using AbstractAlgebra.PrettyPrinting: Dedent
using AbstractAlgebra.PrettyPrinting: Indent
using AbstractAlgebra.PrettyPrinting: pretty
using AbstractAlgebra: _can_solve_with_solution_fflu
using AbstractAlgebra: _can_solve_with_solution_interpolation
using AbstractAlgebra: _solve_fflu_precomp
using AbstractAlgebra: echelon_form!
using AbstractAlgebra: howell_form!
import AbstractAlgebra: kernel
import AbstractAlgebra: matrix
import Base: show
export solve
export solve_init
export solve_context_type
export can_solve
export can_solve_with_solution
export can_solve_with_solution_and_kernel
################################################################################
#
# "Lazy" transpose of a matrix
#
################################################################################
mutable struct LazyTransposeMatElem{T, MatT} <: MatElem{T} where {MatT <: MatElem{T}}
M::MatT
end
data(M::LazyTransposeMatElem) = M.M
# The entries of M and the result are SHARED, so e.g. a setindex! will modify
# 'both' matrices. But this is the point: we don't want to actually transpose
# the matrix.
lazy_transpose(M::MatElem{T}) where T = LazyTransposeMatElem{T, typeof(M)}(M)
lazy_transpose(M::LazyTransposeMatElem) = data(M)
# Change the order of rows and columns in nrows, ncols, getindex and setindex!
AbstractAlgebra.nrows(M::LazyTransposeMatElem) = ncols(data(M))
AbstractAlgebra.ncols(M::LazyTransposeMatElem) = nrows(data(M))
Base.getindex(M::LazyTransposeMatElem, r::Int, c::Int) = data(M)[c, r]
function Base.setindex!(M::LazyTransposeMatElem{T}, d::T, r::Int, c::Int) where T
setindex!(M.M, d, c, r)
return M
end
function AbstractAlgebra.view(M::LazyTransposeMatElem, r::Union{Colon, AbstractVector{Int}}, c::Union{Colon, AbstractVector{Int}})
return lazy_transpose(view(data(M), c, r))
end
# If one of the arguments is an Int, then the result is 1-dimensional so wrapping
# it in a lazy_transpose does not make sense (and does not work, if we don't add
# something like lazy_transpose(::AbstractVector))
function AbstractAlgebra.view(M::LazyTransposeMatElem, r::Int, c::Union{Colon, AbstractVector{Int}})
return view(data(M), c, r)
end
function AbstractAlgebra.view(M::LazyTransposeMatElem, r::Union{Colon, AbstractVector{Int}}, c::Int)
return view(data(M), c, r)
end
AbstractAlgebra.base_ring(M::LazyTransposeMatElem) = base_ring(data(M))
Base.zero(M::LazyTransposeMatElem) = lazy_transpose(zero(data(M)))
Base.zero(M::LazyTransposeMatElem, i::Int, j::Int) = lazy_transpose(zero(data(M), j, i))
Base.similar(M::LazyTransposeMatElem) = lazy_transpose(similar(data(M)))
Base.similar(M::LazyTransposeMatElem, i::Int, j::Int) = lazy_transpose(similar(data(M), j, i))
################################################################################
#
# Type traits for the matrix normal forms
#
################################################################################
abstract type MatrixNormalFormTrait end
struct HowellFormTrait <: MatrixNormalFormTrait end # Howell form for PIR
struct HermiteFormTrait <: MatrixNormalFormTrait end # Hermite form for PID
struct RREFTrait <: MatrixNormalFormTrait end # Row-reduced echelon form for fields
struct LUTrait <: MatrixNormalFormTrait end # LU factoring for fields
struct FFLUTrait <: MatrixNormalFormTrait end # "fraction free" LU factoring for fraction fields
struct MatrixInterpolateTrait <: MatrixNormalFormTrait end # interpolate in fraction fields of polynomial rings
function matrix_normal_form_type(R::Ring)
if is_domain_type(elem_type(R))
return HermiteFormTrait()
else
return HowellFormTrait()
end
end
matrix_normal_form_type(::Field) = RREFTrait()
# The fflu approach is the fastest over a fraction field (see benchmarks on PR 661)
matrix_normal_form_type(::FracField) = FFLUTrait()
matrix_normal_form_type(::AbstractAlgebra.Rationals{BigInt}) = FFLUTrait()
matrix_normal_form_type(::FracField{T}) where {T <: PolyRingElem} = MatrixInterpolateTrait()
matrix_normal_form_type(A::MatElem) = matrix_normal_form_type(base_ring(A))
################################################################################
#
# Linear solving context object
#
################################################################################
# T: element type of the base ring
# NFTrait: the used MatrixNormalFormTrait
# MatT: type of the matrix
# RedMatT: type of the reduced/canonical form of the matrix
# Usually RedMatT == MatT, but not for fraction fields where we do fflu
# TranspRedMatT: type of the reduced/canonical form of the matrix
# Usually TranspRedMatT == LazyTransposeMatElem{T, RedMatT}, but not
# for, e.g. Flint types
@attributes mutable struct SolveCtx{T, NFTrait, MatT, RedMatT, TranspRedMatT}
A::MatT # matrix giving the linear system
red::RedMatT # reduced/canonical form of A (rref, hnf, lu)
red_transp::TranspRedMatT # reduced/canonical form of transpose(A)
# Used for rref / hnf
trafo::RedMatT # transformation: trafo*A == red
trafo_transp::TranspRedMatT # transformation: trafo_transp*transpose(A) == red_transp
# Used for rref
pivots::Vector{Int} # pivot and non-pivot columns of red
pivots_transp::Vector{Int} # pivot and non-pivot columns of red_transp
# Used for lu / fflu
lu_perm::Generic.Perm{Int} # permutation used for the lu factorization of A
lu_perm_transp::Generic.Perm{Int} # permutation used for the lu factorization of transpose(A)
permuted_matrix::MatT # precomputed data for solving
permuted_matrix_transp::MatT # precomputed data for solving
# Used for fflu
scaling_factor::T # factor by which any solution needs to be multiplied
scaling_factor_transp::T # factor by which any solution needs to be multiplied
rank::Int # rank of A
kernel_left::MatT
kernel_right::MatT
function SolveCtx{T, NFTrait, MatT, RedMatT, TranspRedMatT}(A::MatT) where {T, NFTrait <: MatrixNormalFormTrait, MatT <: MatElem{T}, RedMatT <: MatElem, TranspRedMatT <: MatElem}
z = new{T, NFTrait, MatT, RedMatT, TranspRedMatT}()
z.A = A
z.rank = -1 # not known yet
return z
end
end
function Base.show(io::IO, ::MIME"text/plain", C::SolveCtx)
io = pretty(io)
println(io, "Linear solving context of matrix")
print(io, Indent())
show(io, MIME"text/plain"(), matrix(C))
print(io, Dedent())
end
function Base.show(io::IO, C::SolveCtx)
@show_name(io, C)
print(io, "Linear solving context")
end
@doc raw"""
solve_init(A::MatElem)
Return a context object `C` that allows to efficiently solve linear systems
$Ax = b$ or $xA = b$ for different $b$.
# Example
```jldoctest; setup = :(using AbstractAlgebra)
julia> A = QQ[1 2 3; 0 3 0; 5 0 0];
julia> C = solve_init(A)
Linear solving context of matrix
[1//1 2//1 3//1]
[0//1 3//1 0//1]
[5//1 0//1 0//1]
julia> solve(C, [QQ(1), QQ(1), QQ(1)], side = :left)
3-element Vector{Rational{BigInt}}:
1//3
1//9
2//15
julia> solve(C, [QQ(1), QQ(1), QQ(1)], side = :right)
3-element Vector{Rational{BigInt}}:
1//5
1//3
2//45
```
"""
function solve_init(A::MatElem)
return solve_context_type(base_ring(A))(A)
end
function solve_init(NF::MatrixNormalFormTrait, A::MatElem)
return solve_context_type(NF, base_ring(A))(A)
end
# For a ring R, the following signatures of `solve_context_type` need to be
# implemented:
# 1) solve_context_type(R)
# 2) solve_context_type(::MatrixNormalFormTrait, elem_type(R))
# Version 1 should pick a matrix_normal_form_type and call 2
function solve_context_type(R::NCRing)
return solve_context_type(matrix_normal_form_type(R), elem_type(R))
end
function solve_context_type(K::Field)
# matrix_normal_form_type(K) would be RREFTrait, but we want to use
# LU in solve contexts
return solve_context_type(LUTrait(), elem_type(K))
end
function solve_context_type(K::Union{AbstractAlgebra.Rationals{BigInt}, FracField})
# In this case, we use FFLU
return solve_context_type(FFLUTrait(), elem_type(K))
end
function solve_context_type(A::MatElem)
return solve_context_type(base_ring(A))
end
function solve_context_type(NF::MatrixNormalFormTrait, ::Type{T}) where {T <: NCRingElement}
MatType = dense_matrix_type(T)
return SolveCtx{T, typeof(NF), MatType, MatType, LazyTransposeMatElem{T, MatType}}
end
function solve_context_type(::FFLUTrait, ::Type{T}) where {T <: NCRingElement}
# We assume that the ring in question is a fraction field and have to get the
# type of "integral" matrices, that is, matrices over the base ring of this
# fraction field
# Whether this works/makes sense is up to the user
IntMatT = dense_matrix_type(base_ring_type(T))
return SolveCtx{T, FFLUTrait, dense_matrix_type(T), IntMatT, IntMatT}
end
solve_context_type(NF::MatrixNormalFormTrait, ::T) where {T <: NCRingElement} = solve_context_type(NF, T)
solve_context_type(NF::MatrixNormalFormTrait, ::Type{T}) where {T <: NCRing} = solve_context_type(NF, elem_type(T))
solve_context_type(NF::MatrixNormalFormTrait, ::T) where {T <: NCRing} = solve_context_type(NF, elem_type(T))
solve_context_type(NF::MatrixNormalFormTrait, ::Type{<: MatElem{T}}) where T = solve_context_type(NF, T)
solve_context_type(NF::MatrixNormalFormTrait, ::MatElem{T}) where T = solve_context_type(NF, T)
matrix_normal_form_type(C::SolveCtx{T, NF}) where {T, NF} = NF()
matrix(C::SolveCtx) = C.A
function _init_reduce(C::SolveCtx{T, RREFTrait}) where T
if isdefined(C, :red) && isdefined(C, :trafo)
return nothing
end
B, U = echelon_form_with_transformation(matrix(C))
r = nrows(B)
while r > 0 && is_zero_row(B, r)
r -= 1
end
set_rank!(C, r)
C.red = B
C.trafo = U
return nothing
end
function _init_reduce(C::SolveCtx{T, LUTrait}) where T
if isdefined(C, :red) && isdefined(C, :lu_perm)
return nothing
end
B = deepcopy(matrix(C))
p = Perm(1:nrows(B))
r = lu!(p, B)
set_rank!(C, r)
C.red = B
C.lu_perm = p
if r < nrows(C)
pA = p*matrix(C)
C.permuted_matrix = pA[r + 1:nrows(C), :]
else
C.permuted_matrix = zero(matrix(C), 0, ncols(C))
end
return nothing
end
function _init_reduce(C::SolveCtx{T, FFLUTrait}) where T
if isdefined(C, :red)
return nothing
end
A = matrix(C)
dA = _common_denominator(A)
Aint = matrix(parent(dA), nrows(A), ncols(A), [numerator(A[i, j]*dA) for i in 1:nrows(A) for j in 1:ncols(A)])
p = one(SymmetricGroup(nrows(A)))
r, dLU = fflu!(p, Aint)
set_rank!(C, r)
C.lu_perm = p
d = divexact(dA, base_ring(C)(dLU))
C.red = Aint
C.scaling_factor = d
if r < nrows(A)
A2 = p*A
A3 = A2[r + 1:nrows(A), :]
C.permuted_matrix = A3
else
C.permuted_matrix = zero(A, 0, ncols(A))
end
return nothing
end
function _init_reduce(C::SolveCtx{T, HermiteFormTrait}) where T
if isdefined(C, :red) && isdefined(C, :trafo)
return nothing
end
R, U = hermite_form_with_transformation(matrix(C))
C.red = R
C.trafo = U
return nothing
end
# For this type, we store the Howell form of
# ( A | I_n)
# ( 0 | 0 )
# in C.red. From this one can recover the Howell form of A with transformation,
# but also additional information for the kernel.
function _init_reduce(C::SolveCtx{T, HowellFormTrait}) where T
if isdefined(C, :red)
return nothing
end
A = matrix(C)
B = hcat(A, identity_matrix(A, nrows(A)))
if nrows(B) < ncols(B)
B = vcat(B, zero(A, ncols(B) - nrows(B), ncols(B)))
end
howell_form!(B)
C.red = B
return nothing
end
function _init_reduce_transpose(C::SolveCtx{T, RREFTrait}) where T
if isdefined(C, :red_transp) && isdefined(C, :trafo_transp)
return nothing
end
B, U = echelon_form_with_transformation(lazy_transpose(matrix(C)))
r = nrows(B)
while r > 0 && is_zero_row(B, r)
r -= 1
end
set_rank!(C, r)
C.red_transp = B
C.trafo_transp = U
return nothing
end
function _init_reduce_transpose(C::SolveCtx{T, LUTrait}) where T
if isdefined(C, :red_transp) && isdefined(C, :lu_perm_transp)
return nothing
end
B = lazy_transpose(deepcopy(matrix(C)))
p = Perm(1:nrows(B))
r = lu!(p, B)
set_rank!(C, r)
C.red_transp = B
C.lu_perm_transp = p
if r < ncols(C)
Ap = matrix(C)*p
C.permuted_matrix_transp = Ap[:, r + 1:ncols(C)]
else
C.permuted_matrix_transp = zero(matrix(C), nrows(C), 0)
end
return nothing
end
function _init_reduce_transpose(C::SolveCtx{T, FFLUTrait}) where T
if isdefined(C, :red_transp)
return nothing
end
A = matrix(C)
dA = _common_denominator(A)
# We transpose A at this point!
Aint = matrix(parent(dA), ncols(A), nrows(A), [numerator(A[i, j]*dA) for j in 1:ncols(A) for i in 1:nrows(A)])
p = one(SymmetricGroup(nrows(Aint)))
r, dLU = fflu!(p, Aint)
set_rank!(C, r)
C.lu_perm_transp = p
d = divexact(dA, base_ring(C)(dLU))
C.red_transp = Aint
C.scaling_factor_transp = d
if r < ncols(A)
A2 = A*p
A3 = A2[:, r + 1:ncols(A)]
C.permuted_matrix_transp = A3
else
C.permuted_matrix_transp = zero(A, nrows(A), 0)
end
return nothing
end
function _init_reduce_transpose(C::SolveCtx{T, HermiteFormTrait}) where T
if isdefined(C, :red_transp) && isdefined(C, :trafo_transp)
return nothing
end
R, U = hermite_form_with_transformation(lazy_transpose(matrix(C)))
C.red_transp = R
C.trafo_transp = U
return nothing
end
function _init_reduce_transpose(C::SolveCtx{T, HowellFormTrait}) where T
if isdefined(C, :red_transp)
return nothing
end
A = matrix(C)
AT = lazy_transpose(A)
B = hcat(AT, identity_matrix(AT, nrows(AT)))
if nrows(B) < ncols(B)
B = vcat(B, zero(AT, ncols(B) - nrows(B), ncols(B)))
end
howell_form!(B)
C.red_transp = B
return nothing
end
function reduced_matrix(C::SolveCtx)
_init_reduce(C)
return C.red
end
function reduced_matrix_of_transpose(C::SolveCtx)
_init_reduce_transpose(C)
return C.red_transp
end
# Only for FFLU.
# Factor by which any solution needs to be multiplied.
# This is the chosen denominator of matrix(C) divided by the denominator returned
# by fflu!(matrix(C)).
function scaling_factor(C::SolveCtx)
_init_reduce(C)
return C.scaling_factor
end
function scaling_factor_of_transpose(C::SolveCtx)
_init_reduce_transpose(C)
return C.scaling_factor_transp
end
# Only for LU and FFLU
# Let A = matrix(C).
# Return the matrix (p*A)[rank(A) + 1:nrows(A), :] where p is lu_permutation(C).
function permuted_matrix(C::SolveCtx)
_init_reduce(C)
return C.permuted_matrix
end
# Only for LU and FFLU
# Let A = matrix(C).
# Return the matrix (A*p)[:, rank(A) + 1:ncols(A)] where p is lu_permutation_of_transpose(C).
function permuted_matrix_of_transpose(C::SolveCtx)
_init_reduce_transpose(C)
return C.permuted_matrix_transp
end
function lu_permutation(C::SolveCtx)
_init_reduce(C)
return C.lu_perm
end
function lu_permutation_of_transpose(C::SolveCtx)
_init_reduce_transpose(C)
return C.lu_perm_transp
end
function transformation_matrix(C::SolveCtx)
_init_reduce(C)
return C.trafo
end
function transformation_matrix_of_transpose(C::SolveCtx)
_init_reduce_transpose(C)
return C.trafo_transp
end
function set_rank!(C::SolveCtx, r::Int)
if C.rank >= 0
@assert C.rank == r
end
C.rank = r
return nothing
end
function AbstractAlgebra.rank(C::SolveCtx)
if C.rank < 0
_init_reduce(C)
end
return C.rank
end
AbstractAlgebra.nrows(C::SolveCtx) = nrows(matrix(C))
AbstractAlgebra.ncols(C::SolveCtx) = ncols(matrix(C))
AbstractAlgebra.base_ring(C::SolveCtx) = base_ring(matrix(C))
function pivot_and_non_pivot_cols(C::SolveCtx)
if !isdefined(C, :pivots)
R = reduced_matrix(C)
r = rank(C)
C.pivots = pivot_and_non_pivot_cols(R, r)
end
return C.pivots
end
function pivot_and_non_pivot_cols_of_transpose(C::SolveCtx)
if !isdefined(C, :pivots_transp)
R = reduced_matrix_of_transpose(C)
r = rank(C)
C.pivots_transp = pivot_and_non_pivot_cols(R, r)
end
return C.pivots_transp
end
################################################################################
#
# User facing functions for linear solving
#
################################################################################
@doc raw"""
solve(A::MatElem{T}, b::Vector{T}; side::Symbol = :left) where T
solve(A::MatElem{T}, b::MatElem{T}; side::Symbol = :left) where T
solve(C::SolveCtx{T}, b::Vector{T}; side::Symbol = :left) where T
solve(C::SolveCtx{T}, b::MatElem{T}; side::Symbol = :left) where T
Return $x$ of same type as $b$ solving the linear system $xA = b$, if `side == :left`
(default), or $Ax = b$, if `side == :right`.
If no solution exists, an error is raised.
If a context object `C` is supplied, then the above applies for `A = matrix(C)`.
See also [`can_solve_with_solution`](@ref).
"""
function solve(A::Union{MatElem{T}, SolveCtx{T}}, b::Union{Vector{T}, MatElem{T}}; side::Symbol = :left) where T
return solve(matrix_normal_form_type(A), A, b; side)
end
function solve(NF::MatrixNormalFormTrait, A::Union{MatElem{T}, SolveCtx{T}}, b::Union{Vector{T}, MatElem{T}}; side::Symbol = :left) where T
fl, x = can_solve_with_solution(NF, A, b, side = side)
fl || throw(ArgumentError("Unable to solve linear system"))
return x
end
@doc raw"""
can_solve(A::MatElem{T}, b::Vector{T}; side::Symbol = :left) where T
can_solve(A::MatElem{T}, b::MatElem{T}; side::Symbol = :left) where T
can_solve(C::SolveCtx{T}, b::Vector{T}; side::Symbol = :left) where T
can_solve(C::SolveCtx{T}, b::MatElem{T}; side::Symbol = :left) where T
Return `true` if the linear system $xA = b$ or $Ax = b$ with `side == :left`
(default) or `side == :right`, respectively, has a solution and `false` otherwise.
If a context object `C` is supplied, then the above applies for `A = matrix(C)`.
See also [`can_solve_with_solution`](@ref).
"""
function can_solve(A::Union{MatElem{T}, SolveCtx{T}}, b::Union{Vector{T}, MatElem{T}}; side::Symbol = :left) where T
return can_solve(matrix_normal_form_type(A), A, b; side)
end
function can_solve(NF::MatrixNormalFormTrait, A::Union{MatElem{T}, SolveCtx{T}}, b::Union{Vector{T}, MatElem{T}}; side::Symbol = :left) where T
return _can_solve_internal(NF, A, b, :only_check; side = side)[1]
end
@doc raw"""
can_solve_with_solution(A::MatElem{T}, b::Vector{T}; side::Symbol = :left) where T
can_solve_with_solution(A::MatElem{T}, b::MatElem{T}; side::Symbol = :left) where T
can_solve_with_solution(C::SolveCtx{T}, b::Vector{T}; side::Symbol = :left) where T
can_solve_with_solution(C::SolveCtx{T}, b::MatElem{T}; side::Symbol = :left) where T
Return `true` and $x$ of same type as $b$ solving the linear system $xA = b$, if
such a solution exists. Return `false` and an empty vector or matrix, if the
system has no solution.
If `side == :right`, the system $Ax = b$ is solved.
If a context object `C` is supplied, then the above applies for `A = matrix(C)`.
See also [`solve`](@ref).
"""
function can_solve_with_solution(A::Union{MatElem{T}, SolveCtx{T}}, b::Union{Vector{T}, MatElem{T}}; side::Symbol = :left) where T
return can_solve_with_solution(matrix_normal_form_type(A), A, b; side)
end
function can_solve_with_solution(NF::MatrixNormalFormTrait, A::Union{MatElem{T}, SolveCtx{T}}, b::Union{Vector{T}, MatElem{T}}; side::Symbol = :left) where T
return _can_solve_internal(NF, A, b, :with_solution; side = side)[1:2]
end
@doc raw"""
can_solve_with_solution_and_kernel(A::MatElem{T}, b::Vector{T}; side::Symbol = :left) where T
can_solve_with_solution_and_kernel(A::MatElem{T}, b::MatElem{T}; side::Symbol = :left) where T
can_solve_with_solution_and_kernel(C::SolveCtx{T}, b::Vector{T}; side::Symbol = :left) where T
can_solve_with_solution_and_kernel(C::SolveCtx{T}, b::MatElem{T}; side::Symbol = :left) where T
Return `true`, $x$ of same type as $b$ solving the linear system $xA = b$,
together with a matrix $K$ giving the kernel of $A$ (i.e. $KA = 0$), if such
a solution exists. Return `false`, an empty vector or matrix and an empty matrix,
if the system has no solution.
If `side == :right`, the system $Ax = b$ is solved.
If a context object `C` is supplied, then the above applies for `A = matrix(C)`.
See also [`solve`](@ref) and [`kernel`](@ref).
"""
function can_solve_with_solution_and_kernel(A::Union{MatElem{T}, SolveCtx{T}}, b::Union{Vector{T}, MatElem{T}}; side::Symbol = :left) where T
return can_solve_with_solution_and_kernel(matrix_normal_form_type(A), A, b; side)
end
function can_solve_with_solution_and_kernel(NF::MatrixNormalFormTrait, A::Union{MatElem{T}, SolveCtx{T}}, b::Union{Vector{T}, MatElem{T}}; side::Symbol = :left) where T
return _can_solve_internal(NF, A, b, :with_kernel; side = side)
end
@doc raw"""
kernel(A::MatElem; side::Symbol = :left)
kernel(C::SolveCtx; side::Symbol = :left)
Return a matrix $K$ whose rows generate the left kernel of $A$, that
is, $KA$ is the zero matrix.
If `side == :right`, the columns of $K$ generate the right kernel of $A$, that
is, $AK$ is the zero matrix.
If the base ring is a principal ideal domain, the rows or columns respectively of $K$
are a basis of the respective kernel.
If a context object `C` is supplied, then the above applies for `A = matrix(C)`.
"""
function kernel(A::Union{MatElem, SolveCtx}; side::Symbol = :left)
return kernel(matrix_normal_form_type(A), A; side)
end
################################################################################
#
# Kernel methods
#
################################################################################
# Catch non-matching normal forms in solve context
function kernel(::NF, ::SolveCtx{T, NF2}; side::Symbol = :left) where {T, NF <: MatrixNormalFormTrait, NF2 <: MatrixNormalFormTrait}
error("Cannot use normal form of type $NF with solve context of type $NF2")
end
### HowellFormTrait
function kernel(::HowellFormTrait, A::MatElem; side::Symbol = :left)
check_option(side, [:right, :left], "side")
if side === :right
AT = lazy_transpose(A)
B = hcat(AT, identity_matrix(AT, nrows(AT)))
if nrows(B) < ncols(B)
B = vcat(B, zero(AT, ncols(B) - nrows(B), ncols(B)))
end
howell_form!(B)
return lazy_transpose(_kernel_of_howell_form(B, nrows(A), ncols(A)))
else
B = hcat(A, identity_matrix(A, nrows(A)))
if nrows(B) < ncols(B)
B = vcat(B, zero(A, ncols(B) - nrows(B), ncols(B)))
end
howell_form!(B)
return _kernel_of_howell_form(B, ncols(A), nrows(A))
end
end
function kernel(::HowellFormTrait, C::SolveCtx{T, HowellFormTrait}; side::Symbol = :left) where T
check_option(side, [:right, :left], "side")
if side === :right
if !isdefined(C, :kernel_right)
B = reduced_matrix_of_transpose(C)
C.kernel_right = lazy_transpose(_kernel_of_howell_form(B, nrows(C), ncols(C)))
end
return C.kernel_right
else
if !isdefined(C, :kernel_left)
B = reduced_matrix(C)
C.kernel_left = _kernel_of_howell_form(B, ncols(C), nrows(C))
end
return C.kernel_left
end
end
### HermiteFormTrait
function kernel(::HermiteFormTrait, A::MatElem; side::Symbol = :left)
check_option(side, [:right, :left], "side")
if side === :right
A = hnf(A)
HH, UU = hermite_form_with_transformation(lazy_transpose(A))
return lazy_transpose(_kernel_of_hnf(HH, UU))
else
A = lazy_transpose(hnf(lazy_transpose(A)))
H, U = hermite_form_with_transformation(A)
return _kernel_of_hnf(H, U)
end
end
function kernel(::HermiteFormTrait, C::SolveCtx{T, HermiteFormTrait}; side::Symbol = :left) where T
check_option(side, [:right, :left], "side")
if side === :right
if !isdefined(C, :kernel_right)
C.kernel_right = lazy_transpose(_kernel_of_hnf(reduced_matrix_of_transpose(C), transformation_matrix_of_transpose(C)))
end
return C.kernel_right
else
if !isdefined(C, :kernel_left)
C.kernel_left = _kernel_of_hnf(reduced_matrix(C), transformation_matrix(C))
end
return C.kernel_left
end
end
### RREFTrait
function kernel(::RREFTrait, A::MatElem; side::Symbol = :left)
check_option(side, [:right, :left], "side")
if side === :left
KK = kernel(RREFTrait(), lazy_transpose(A), side = :right)
return lazy_transpose(KK)::typeof(A)
end
n, K = AbstractAlgebra.nullspace(A)
if ncols(K) > n
# For compatibility with `nullspace` methods in Nemo which add zero columns
K = sub(K, 1:nrows(K), 1:n)
end
return K
end
# This does not really use the solve context, so the normal form type of C is
# not important
function kernel(::RREFTrait, C::SolveCtx{T, <: MatrixNormalFormTrait}; side::Symbol = :left) where T
check_option(side, [:right, :left], "side")
if side === :right
if !isdefined(C, :kernel_right)
C.kernel_right = kernel(RREFTrait(), matrix(C), side = :right)
end
return C.kernel_right
else
if !isdefined(C, :kernel_left)
C.kernel_left = kernel(RREFTrait(), matrix(C), side = :left)
end
return C.kernel_left
end
end
### LUTrait / FFLUTrait / MatrixNormalFormTrait
# We can't compute a kernel using a LU/FFLU factoring, so we have to fall back to RREF
function kernel(::LUTrait, A::Union{MatElem, SolveCtx{<: NCRingElement, LUTrait}}; side::Symbol = :left)
return kernel(RREFTrait(), A; side)
end
function kernel(::FFLUTrait, A::Union{MatElem, SolveCtx{<: NCRingElement, FFLUTrait}}; side::Symbol = :left)
return kernel(RREFTrait(), A; side)
end
function kernel(::MatrixInterpolateTrait, A::Union{MatElem, SolveCtx{<: NCRingElement, MatrixInterpolateTrait}}; side::Symbol = :left)
return kernel(RREFTrait(), A; side)
end
################################################################################
#
# Solving methods (internal)
#
################################################################################
###
# General concept:
# `_can_solve_internal` checks the sanity of the input and then calls
# `_can_solve_internal_no_check` . Only the latter function needs to be
# implemented for a given MatrixNormalFormTrait(). Specifically one needs to implement
# the signature(s)
# _can_solve_internal_no_check(::NormalFormTrait, A::MatrixType, b::MatrixType, task::Symbol; side::Symbol)
# _can_solve_internal_no_check(::NormalFormTrait, C::SolveCtx, b::MatrixType, task::Symbol; side::Symbol)
# Inside these functions one can assume that A (resp. C) and b have compatible
# dimensions and that `task` and `side` are set to a "legal" option.
# These functions should then (try to) solve Ax = b (side == :right) or xA = b
# (side == :left) possibly with kernel.
# They must always return a tuple (Bool, MatrixType, MatrixType).
# task may be:
# * :only_check -> It is only tested whether there is a solution, the second
# and third return value are only for type stability
# * :with_solution -> A solution is computed, the last return value is only
# for type stability
# * :with_kernel -> A solution and the kernel is computed
###
# Transform a right hand side of type Vector into a MatElem and do sanity checks
function _can_solve_internal(::NFTrait, A::Union{MatElem{T}, SolveCtx{T}}, b::Vector{T}, task::Symbol; side::Symbol = :left) where {T, NFTrait <: MatrixNormalFormTrait}
check_option(task, [:only_check, :with_solution, :with_kernel], "task")
check_option(side, [:right, :left], "side")
@assert all(x -> parent(x) === base_ring(A), b) "Base rings do not match"
isright = side === :right
if isright
check_linear_system_dim_right(A, b)
B = matrix(base_ring(A), nrows(A), 1, b)
else # side == :left
check_linear_system_dim_left(A, b)
B = matrix(base_ring(A), 1, ncols(A), b)
end
fl, sol, K = _can_solve_internal_no_check(NFTrait(), A, B, task, side = side)
if isright
x = eltype(b)[ sol[i, 1] for i in 1:nrows(sol) ]
else # side == :left
x = eltype(b)[ sol[1, i] for i in 1:ncols(sol) ]
end
return fl, x, K
end
# Do sanity checks and call _can_solve_internal_no_check
function _can_solve_internal(::NFTrait, A::Union{MatElem{T}, SolveCtx{T}}, b::MatElem{T}, task::Symbol; side::Symbol = :left) where {T, NFTrait <: MatrixNormalFormTrait}
check_option(task, [:only_check, :with_solution, :with_kernel], "task")
check_option(side, [:right, :left], "side")
@assert base_ring(A) === base_ring(b) "Base rings do not match"
if side === :right
check_linear_system_dim_right(A, b)
else
check_linear_system_dim_left(A, b)
end
return _can_solve_internal_no_check(NFTrait(), A, b, task, side = side)
end
# Catch non-matching normal forms in solve context
function _can_solve_internal_no_check(::NFTrait, C::SolveCtx{T, NFTrait2}, b::MatElem{T}, task::Symbol; side::Symbol = :left) where {T, NFTrait <: MatrixNormalFormTrait, NFTrait2 <: MatrixNormalFormTrait}
error("Cannot use normal form of type $NFTrait with solve context of type $NFTrait2")
end
### HowellFormTrait
function _can_solve_internal_no_check(::HowellFormTrait, A::MatElem{T}, b::MatElem{T}, task::Symbol; side::Symbol = :left) where T
if side === :left
# For side == :left, we pretend that A and b are transposed
fl, _sol, _K = _can_solve_internal_no_check(HowellFormTrait(), lazy_transpose(A), lazy_transpose(b), task, side = :right)
return fl, lazy_transpose(_sol), lazy_transpose(_K)
end
AT = lazy_transpose(A)
B = hcat(AT, identity_matrix(AT, nrows(AT)))
if nrows(B) < ncols(B)
B = vcat(B, zero(AT, ncols(B) - nrows(B), ncols(B)))
end
howell_form!(B)
m = max(nrows(AT), ncols(AT))
H = view(B, 1:m, 1:ncols(AT))
U = view(B, 1:m, ncols(AT) + 1:ncols(B))
fl, sol = _can_solve_with_hnf(b, H, U, task)
if !fl || task !== :with_kernel
return fl, sol, zero(A, 0, 0)
end
N = _kernel_of_howell_form(B, nrows(A), ncols(A))
return true, sol, lazy_transpose(N)
end
function _can_solve_internal_no_check(::HowellFormTrait, C::SolveCtx{T, HowellFormTrait}, b::MatElem{T}, task::Symbol; side::Symbol = :left) where T
if side === :right
A = matrix(C)
B = reduced_matrix_of_transpose(C)
m = max(ncols(A), nrows(A))
H = view(B, 1:m, 1:nrows(A))
U = view(B, 1:m, nrows(A) + 1:ncols(B))
fl, sol = _can_solve_with_hnf(b, H, U, task)
if !fl || task !== :with_kernel
return fl, sol, zero(b, 0, 0)
end
return fl, sol, kernel(C, side = :right)
else# side === :left
A = matrix(C)
B = reduced_matrix(C)
m = max(ncols(A), nrows(A))
H = view(B, 1:m, 1:ncols(A))
U = view(B, 1:m, ncols(A) + 1:ncols(B))
fl, sol_transp = _can_solve_with_hnf(lazy_transpose(b), H, U, task)
sol = lazy_transpose(sol_transp)
if !fl || task !== :with_kernel
return fl, sol, zero(b, 0, 0)
end
return fl, sol, kernel(C, side = :left)
end
end
### HermiteFormTrait
function _can_solve_internal_no_check(::HermiteFormTrait, A::MatElem{T}, b::MatElem{T}, task::Symbol; side::Symbol = :left) where T
if side === :left
# For side == :left, we pretend that A and b are transposed
fl, _sol, _K = _can_solve_internal_no_check(HermiteFormTrait(), lazy_transpose(A), lazy_transpose(b), task, side = :right)
return fl, lazy_transpose(_sol), lazy_transpose(_K)
end
H, S = hermite_form_with_transformation(lazy_transpose(A))
fl, sol = _can_solve_with_hnf(b, H, S, task)
if !fl || task !== :with_kernel
return fl, sol, zero(A, 0, 0)
end
N = _kernel_of_hnf(H, S)
return true, sol, lazy_transpose(N)
end
function _can_solve_internal_no_check(::HermiteFormTrait, C::SolveCtx{T, HermiteFormTrait}, b::MatElem{T}, task::Symbol; side::Symbol = :left) where T
if side === :right
fl, sol = _can_solve_with_hnf(b, reduced_matrix_of_transpose(C), transformation_matrix_of_transpose(C), task)
else
fl, _sol = _can_solve_with_hnf(lazy_transpose(b), reduced_matrix(C), transformation_matrix(C), task)
sol = lazy_transpose(_sol)
end
if !fl || task !== :with_kernel
return fl, sol, zero(b, 0, 0)
end
return true, sol, kernel(C, side = side)
end
### RREFTrait
function _can_solve_internal_no_check(::RREFTrait, A::MatElem{T}, b::MatElem{T}, task::Symbol; side::Symbol = :left) where T
if side === :left
# For side == :left, we pretend that A and b are transposed
fl, _sol, _K = _can_solve_internal_no_check(RREFTrait(), lazy_transpose(A), lazy_transpose(b), task, side = :right)
return fl, lazy_transpose(_sol), lazy_transpose(_K)
end
mu = hcat(deepcopy(A), deepcopy(b))
rk = echelon_form!(mu)
p = pivot_and_non_pivot_cols(mu, rk)
if any(i -> i > ncols(A), p[1:rk])
return false, zero(A, 0, 0), zero(A, 0, 0)
end
if task === :only_check
return true, zero(A, 0, 0), zero(A, 0, 0)
end
# Compute a solution
sol = zero(A, ncols(A), ncols(b))
for i = 1:rk
for j = 1:ncols(b)
sol[p[i], j] = mu[i, ncols(A) + j]
end
end
if task === :with_solution
return true, sol, zero(A, 0, 0)
end
# Build the kernel
nullity = ncols(A) - rk
X = zero(A, ncols(A), nullity)
for i = 1:nullity
for j = 1:rk
X[p[j], i] = -mu[j, p[rk + i]]
end
X[p[rk + i], i] = one(base_ring(A))