Skip to content

Commit d56c4aa

Browse files
authored
Add support for one-indexing (#22)
1 parent e039723 commit d56c4aa

File tree

2 files changed

+41
-22
lines changed

2 files changed

+41
-22
lines changed

src/sparse_matrix.jl

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1-
mutable struct SparseMatrixCSRtoCSC{Tv,Ti<:Integer}
1+
abstract type AbstractIndexing end
2+
struct ZeroBasedIndexing <: AbstractIndexing end
3+
struct OneBasedIndexing <: AbstractIndexing end
4+
5+
first_index(::ZeroBasedIndexing) = 0
6+
first_index(::OneBasedIndexing) = 1
7+
shift(x, ::ZeroBasedIndexing, ::ZeroBasedIndexing) = x
8+
shift(x::Integer, ::ZeroBasedIndexing, ::OneBasedIndexing) = x + 1
9+
shift(x::Array{<:Integer}, ::ZeroBasedIndexing, ::OneBasedIndexing) = x .+ 1
10+
shift(x::Integer, ::OneBasedIndexing, ::ZeroBasedIndexing) = x - 1
11+
shift(x, ::OneBasedIndexing, ::OneBasedIndexing) = x
12+
13+
mutable struct SparseMatrixCSRtoCSC{Tv,Ti<:Integer,I<:AbstractIndexing}
14+
indexing::I
215
m::Int # Number of rows
316
n::Int # Number of columns
417
colptr::Vector{Ti}
518
rowval::Vector{Ti}
619
nzval::Vector{Tv}
7-
function SparseMatrixCSRtoCSC{Tv, Ti}(n) where {Tv, Ti<:Integer}
8-
A = new()
20+
function SparseMatrixCSRtoCSC{Tv, Ti, I}(n) where {Tv, Ti<:Integer, I}
21+
A = new{Tv, Ti, I}()
922
A.n = n
1023
A.colptr = zeros(Ti, n + 1)
1124
return A
@@ -20,9 +33,9 @@ function allocate_nonzeros(A::SparseMatrixCSRtoCSC{Tv, Ti}) where {Tv, Ti}
2033
end
2134
function final_touch(A::SparseMatrixCSRtoCSC)
2235
for i in length(A.colptr):-1:2
23-
A.colptr[i] = A.colptr[i - 1]
36+
A.colptr[i] = shift(A.colptr[i - 1], ZeroBasedIndexing(), A.indexing)
2437
end
25-
A.colptr[1] = 0
38+
A.colptr[1] = first_index(A.indexing)
2639
end
2740
function _allocate_terms(colptr, indexmap, terms)
2841
for term in terms
@@ -35,27 +48,28 @@ end
3548
function _load_terms(colptr, rowval, nzval, indexmap, terms, offset)
3649
for term in terms
3750
ptr = colptr[indexmap[term.scalar_term.variable_index].value] += 1
38-
rowval[ptr] = offset + term.output_index - 1
51+
rowval[ptr] = offset + term.output_index
3952
nzval[ptr] = -term.scalar_term.coefficient
4053
end
4154
end
4255
function load_terms(A::SparseMatrixCSRtoCSC, indexmap, func, offset)
43-
_load_terms(A.colptr, A.rowval, A.nzval, indexmap, func.terms, offset)
56+
_load_terms(A.colptr, A.rowval, A.nzval, indexmap, func.terms, shift(offset, OneBasedIndexing(), A.indexing))
4457
end
4558

4659
"""
47-
Base.convert(::Type{SparseMatrixCSC{Tv, Ti}}, A::SparseMatrixCSRtoCSC{Tv, Ti}) where {Tv, Ti}
60+
Base.convert(::Type{SparseMatrixCSC{Tv, Ti}}, A::SparseMatrixCSRtoCSC{Tv, Ti, I}) where {Tv, Ti, I}
4861
4962
Converts `A` to a `SparseMatrixCSC`. Note that the field `A.nzval` is **not
5063
copied** so if `A` is modified after the call of this function, it can still
51-
affect the value returned.
64+
affect the value returned. Moreover, if `I` is `OneBasedIndexing`, `colptr`
65+
and `rowval` are not copied either, i.e., the conversion is allocation-free.
5266
"""
5367
function Base.convert(::Type{SparseMatrixCSC{Tv, Ti}}, A::SparseMatrixCSRtoCSC{Tv, Ti}) where {Tv, Ti}
5468
return SparseMatrixCSC{Tv, Ti}(
5569
A.m,
5670
A.n,
57-
A.colptr .+ one(Ti),
58-
A.rowval .+ one(Ti),
59-
A.nzval
71+
shift(A.colptr, A.indexing, OneBasedIndexing()),
72+
shift(A.rowval, A.indexing, OneBasedIndexing()),
73+
A.nzval,
6074
)
6175
end

test/conic_form.jl

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ function _test_matrix_equal(A::SparseMatrixCSC, B::SparseMatrixCSC)
55
@test A.rowval == B.rowval
66
@test A.colptr == B.colptr
77
end
8-
function _test_matrix_equal(A::MatOI.SparseMatrixCSRtoCSC, B::SparseMatrixCSC)
8+
function _test_matrix_equal(A::MatOI.SparseMatrixCSRtoCSC{Tv, Ti, I}, B::SparseMatrixCSC) where {Tv, Ti, I}
99
@test A.m == B.m
1010
@test A.n == B.n
1111
@test A.nzval B.nzval atol=ATOL rtol=RTOL
12-
@test A.rowval == B.rowval .- 1
13-
@test A.colptr == B.colptr .- 1
12+
if I <: MatOI.OneBasedIndexing
13+
@test A.rowval == B.rowval
14+
@test A.colptr == B.colptr
15+
else
16+
@test A.rowval == B.rowval .- 1
17+
@test A.colptr == B.colptr .- 1
18+
end
1419
sA = convert(typeof(B), A)
1520
@test typeof(sA) == typeof(B)
1621
_test_matrix_equal(sA, B)
1722
end
1823

1924
# _psd1test: https://github.com/jump-dev/MathOptInterface.jl/blob/master/src/Test/contconic.jl#L2417
20-
function psd1(::Type{T}) where T
25+
function psd1(::Type{T}, ::Type{I}) where {T, I}
2126
# We use `MockOptimizer` to have indices xor'ed so that it tests that we don't assumes they are `1:n`.
2227
model = MOIU.MockOptimizer(MOIU.Model{T}())
2328

@@ -66,7 +71,7 @@ function psd1(::Type{T}) where T
6671
)
6772
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
6873

69-
conic_form = MatOI.GeometricConicForm{T, MatOI.SparseMatrixCSRtoCSC{T, Int}, Vector{T}}([MOI.PositiveSemidefiniteConeTriangle, MOI.SecondOrderCone, MOI.Zeros])
74+
conic_form = MatOI.GeometricConicForm{T, MatOI.SparseMatrixCSRtoCSC{T, Int, I}, Vector{T}}([MOI.PositiveSemidefiniteConeTriangle, MOI.SecondOrderCone, MOI.Zeros])
7075
index_map = MOI.copy_to(conic_form, model)
7176

7277
@test conic_form.c' T[2 2 2 0 2 2 1 0 0]
@@ -84,7 +89,7 @@ end
8489

8590
# Taken from `MOI.Test.psdt2test`.
8691
# find equivalent diffcp program here - https://github.com/AKS1996/jump-gsoc-2020/blob/master/diffcp_sdp_3_py.ipynb
87-
function psd2(::Type{T}, η::T = T(10), α::T = T(4)/T(5), δ::T = T(9)/T(10)) where T
92+
function psd2(::Type{T}, ::Type{I}, η::T = T(10), α::T = T(4)/T(5), δ::T = T(9)/T(10)) where {T, I}
8893
# We use `MockOptimizer` to have indices xor'ed so that it tests that we don't assumes they are `1:n`.
8994
model = MOIU.MockOptimizer(MOIU.Model{T}())
9095

@@ -126,7 +131,7 @@ function psd2(::Type{T}, η::T = T(10), α::T = T(4)/T(5), δ::T = T(9)/T(10)) w
126131
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(one(T), x[7])], zero(T)))
127132
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
128133

129-
conic_form = MatOI.GeometricConicForm{T, MatOI.SparseMatrixCSRtoCSC{T, Int}, Vector{T}}([MOI.Nonnegatives, MOI.Zeros, MOI.PositiveSemidefiniteConeTriangle])
134+
conic_form = MatOI.GeometricConicForm{T, MatOI.SparseMatrixCSRtoCSC{T, Int, I}, Vector{T}}([MOI.Nonnegatives, MOI.Zeros, MOI.PositiveSemidefiniteConeTriangle])
130135
index_map = MOI.copy_to(conic_form, model)
131136

132137
@test conic_form.c [zeros(T, 6); one(T)]
@@ -142,7 +147,7 @@ function psd2(::Type{T}, η::T = T(10), α::T = T(4)/T(5), δ::T = T(9)/T(10)) w
142147
)
143148
end
144149

145-
@testset "PSD $T" for T in [Float64, BigFloat]
146-
psd1(T)
147-
psd2(T)
150+
@testset "PSD $T, $I" for T in [Float64, BigFloat], I in [MatOI.ZeroBasedIndexing, MatOI.OneBasedIndexing]
151+
psd1(T, I)
152+
psd2(T, I)
148153
end

0 commit comments

Comments
 (0)