Skip to content

Commit 1779363

Browse files
committed
better AbstractArray interface support
1 parent be4286e commit 1779363

File tree

8 files changed

+111
-37
lines changed

8 files changed

+111
-37
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# News
22

3+
## v0.10.16 - 2025-04-21
4+
5+
- `IGVector*` and `IGMatrix*` support the corresponding julia `AbstractArray` interfaces now.
6+
37
## v0.10.15 - 2025-04-17
48

59
- First release of IGraphs.jl closely wrapping the C library igraph v0.10.15

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "IGraphs"
22
uuid = "647e90d3-2106-487c-adb4-c91fc07b96ea"
33
authors = ["Stefan Krastanov <stefan@krastanov.org>"]
4-
version = "0.10.15"
4+
version = "0.10.16"
55

66
[deps]
77
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Most C types (like `igraph_vector_int_t`) are directly available in `IGraphs.Lib
66

77
### High-level Julian interfaces
88

9-
The Julian `IGraph` wrapper of the C `igraph_t` follows the `Graphs.jl` interface. Similarly the `IGVector*` which wrap `igraph_vector_*_t` follow the Julia array interface. Other Julian interfaces are not implemented yet.
9+
The Julian `IGraph` wrapper of the C `igraph_t` follows the `Graphs.jl` interface. Similarly the (`IGVector*` which wrap `igraph_vector_*_t`) and `IGMatrix*` (which wrap `igraph_matrix_*_t`) follow the Julia array interface. Other Julian interfaces are not implemented yet.
1010

1111
By default, all of these types are initialized, but empty. If you want to create unsafe uninitialized types (i.e. wrappers around uninitialized C structs) use `T(;_uninitialized=Var(true))` -- but be careful, uninitialized structs can cause segfaults on garbage collection.
1212

@@ -65,11 +65,11 @@ julia> IGraphs.allbindings |> length
6565
6666
# how many functions are wrapped in a more Julian call interface and accessible through `LibIGraph.functionname`
6767
julia> IGraphs.translatedbindings |> length
68-
1181
68+
1243
6969
7070
# the difference between those two numbers
7171
julia> IGraphs.untranslatedbindings |> length
72-
876
72+
814
7373
7474
# the number of C types with Julian wrappers
7575
julia> IGraphs.wrappedtypes |> length

src/array_api.jl

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,23 @@
1-
const vtypes = [
2-
(:IGVectorInt, LibIGraph.igraph_integer_t, "_int"),
3-
(:IGVectorFloat, LibIGraph.igraph_real_t, ""),
4-
(:IGVectorComplex, Complex{LibIGraph.igraph_real_t}, "_complex"),
5-
(:IGVectorBool, LibIGraph.igraph_bool_t, "_bool"),
6-
(:IGVectorChar, Cchar, "_char")
7-
]
8-
91
for (VT, ET, suffix) in vtypes
102
api = quote
113
# iteration
12-
Base.iterate(v::$VT) = length(v)==0 ? nothing : (v[1], 1)
13-
Base.iterate(v::$VT, state) = length(v)==state ? nothing : (v[state+1], state+1)
14-
# iteration (optional)
15-
Base.eltype(v::$VT) = $ET
4+
#Base.iterate(v::$VT) # default
5+
#Base.iterate(v::$VT, state) # default
6+
#Base.eltype(v::$VT) # default
167
# indexing
17-
Base.getindex(v::$VT, i::Number) = LibIGraph.$(Symbol(:vector,suffix,:_get))(v,i-1)::$ET
18-
Base.getindex(v::$VT, I) = [S[i] for i in I]
8+
Base.getindex(v::$VT, i::Number) = LibIGraph.$(Symbol(:vector,suffix,:_get))(v,i[1]-1)::$ET
199
function Base.setindex!(v::$VT, x, i::Number)
2010
LibIGraph.$(Symbol(:vector,suffix,:_set))(v,i-1,x)
2111
x
2212
end
23-
function Base.setindex!(v::$VT, X, I)
24-
for (x,i) in zip(X,I)
25-
v[i] = x
26-
end
27-
X
28-
end
29-
Base.firstindex(v::$VT) = 1
30-
Base.lastindex(v::$VT) = length(v)
13+
#Base.firstindex(v::$VT) # default
14+
#Base.lastindex(v::$VT) # default
3115
# abstract arrays
32-
Base.size(v::$VT) = (length(v), )
33-
#Base.getindex(v::$VT, I::Vararg{Int, 1})
34-
# abstract arrays (optional)
35-
Base.length(v::$VT) = LibIGraph.$(Symbol(:vector,suffix,:_size))(v)
36-
16+
Base.size(v::$VT) = (LibIGraph.$(Symbol(:vector,suffix,:_size))(v), )
17+
Base.IndexStyle(::Type{$VT}) = Base.IndexLinear()
3718
# other
3819
Base.push!(v::$VT, x) = LibIGraph.$(Symbol(:vector,suffix,:_push_back))(v, x)
39-
4020
# constructor conversion
41-
Vector(v::$VT) = v[begin:end]
4221
function $VT(v::Vector)
4322
vout = $VT(_uninitialized=Val(false))
4423
LibIGraph.$(Symbol(:vector,suffix,:_init))(vout, length(v))
@@ -47,6 +26,41 @@ for (VT, ET, suffix) in vtypes
4726
end
4827
return vout
4928
end
29+
Vector(v::$VT) = v[begin:end]::Vector{$ET}
30+
end
31+
eval(api)
32+
end
33+
34+
35+
for (MT, ET, suffix) in mtypes
36+
api = quote
37+
# iteration
38+
#Base.iterate(m::$MT) # default
39+
#Base.iterate(m::$MT, state) # default
40+
#Base.eltype(m::$MT) # default
41+
# indexing
42+
function Base.getindex(m::$MT, i::Number, j::Number)
43+
LibIGraph.$(Symbol(:matrix,suffix,:_get))(m,(i-1),(j-1))::$ET
44+
end
45+
function Base.setindex!(m::$MT, x, i::Number, j::Number)
46+
LibIGraph.$(Symbol(:matrix,suffix,:_set))(m,i-1,j-1,x)
47+
x
48+
end
49+
#Base.firstindex(m::$MT) # default
50+
#Base.lastindex(m::$MT) # default
51+
# abstract arrays
52+
Base.size(m::$MT) = (LibIGraph.$(Symbol(:matrix,suffix,:_nrow))(m), LibIGraph.$(Symbol(:matrix,suffix,:_ncol))(m))
53+
Base.IndexStyle(::Type{$MT}) = Base.IndexCartesian()
54+
# constructor conversion
55+
function $MT(m::Matrix)
56+
mout = $MT(_uninitialized=Val(false))
57+
LibIGraph.$(Symbol(:matrix,suffix,:_init))(mout, size(m,1), size(m,2))
58+
for (i,x) in enumerate(m)
59+
mout[i]=x
60+
end
61+
return mout
62+
end
63+
Matrix(m::$MT) = m[begin:end, begin:end]::Matrix{$ET}
5064
end
5165
eval(api)
5266
end

src/types.jl

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,31 @@ function initializer(ctype)
2020
end
2121
end
2222

23+
const vtypes = [
24+
(:IGVectorInt, LibIGraph.igraph_integer_t, "_int"),
25+
(:IGVectorFloat, LibIGraph.igraph_real_t, ""),
26+
(:IGVectorComplex, Complex{LibIGraph.igraph_real_t}, "_complex"),
27+
(:IGVectorBool, LibIGraph.igraph_bool_t, "_bool"),
28+
(:IGVectorChar, Cchar, "_char")
29+
]
30+
const mtypes = [
31+
(:IGMatrixInt, LibIGraph.igraph_integer_t, "_int"),
32+
(:IGMatrixFloat, LibIGraph.igraph_real_t, ""),
33+
(:IGMatrixComplex, Complex{LibIGraph.igraph_real_t}, "_complex"),
34+
(:IGMatrixBool, LibIGraph.igraph_bool_t, "_bool"),
35+
(:IGMatrixChar, Cchar, "_char")
36+
]
37+
38+
const parent_types = Dict(
39+
[jtype=>:(AbstractVector{$eltype}) for (jtype, eltype, _) in vtypes]...,
40+
[jtype=>:(AbstractMatrix{$eltype}) for (jtype, eltype, _) in mtypes]...
41+
)
42+
2343
for (ptr_ctype, jtype) in pairs(wrappedtypes)
2444
ctype = ptr_ctype.args[2]
45+
ptype = get(parent_types, jtype, Any)
2546
expr = quote
26-
struct $jtype
47+
struct $jtype <: $ptype
2748
objref::Ref{LibIGraph.$ctype}
2849
end
2950
function $jtype(;_uninitialized::Val{B}=Val(false)) where {B}
@@ -38,6 +59,5 @@ for (ptr_ctype, jtype) in pairs(wrappedtypes)
3859
return $jtype(cinstance)
3960
end
4061
end
41-
#println(expr)
4262
eval(expr)
4363
end

src/wrapccall.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ function modifymodule(mod)
9292
end
9393

9494
const nativetypes = Set([ # cenums are elsewhere, because we are dynamically gathering them (and because we want to exclude them from nativepointertypes)
95-
:igraph_integer_t, :igraph_bool_t, :igraph_real_t, :igraph_complex_t
95+
:igraph_integer_t, :igraph_bool_t, :igraph_real_t, :igraph_complex_t, :Cchar
9696
])
9797
const nativepointertypes = Set([:(Ptr{$t}) for t in nativetypes])
9898
const wrappedtypes = Dict(
@@ -152,7 +152,7 @@ const returntypes = Dict(
152152
:igraph_real_t => :Float64,
153153
:igraph_complex_t => :ComplexF64,
154154
:Cint => :Int,
155-
:Cchar => :Char,
155+
:Cchar => :Cchar,
156156
)
157157
allreturntypes = union(Set([:igraph_error_t,:Cvoid]), keys(returntypes))
158158

test/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
[deps]
22
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
3+
BaseInterfaces = "0b829d9d-522b-4e79-acc1-14feb8e2795d"
34
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
45
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
56
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
67
IGraphs = "647e90d3-2106-487c-adb4-c91fc07b96ea"
8+
Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87"
79
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
810
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
911
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

test/test_interfaces.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
@testitem "Interface tests" begin
2+
3+
import Interfaces, BaseInterfaces
4+
using IGraphs
5+
using Test
6+
7+
for (VTs, ET, _) in IGraphs.vtypes
8+
VT = eval(VTs)
9+
Interfaces.test(BaseInterfaces.IterationInterface, VT, [VT(rand(ET,10))])
10+
Interfaces.test(BaseInterfaces.IterationInterface{(:indexing)}, VT, [VT(rand(ET,10))])
11+
12+
Interfaces.test(BaseInterfaces.ArrayInterface, VT, [VT(rand(ET,10))])
13+
Interfaces.test(BaseInterfaces.ArrayInterface{(:logical,:setindex!)}, VT, [VT(rand(ET,10))])
14+
15+
v = VT(rand(ET,10))
16+
@test Vector(VT(Vector(v))) == Vector(v)
17+
@test VT(Vector(v)) == v
18+
end
19+
20+
for (MTs, ET, _) in IGraphs.mtypes
21+
MT = eval(MTs)
22+
Interfaces.test(BaseInterfaces.IterationInterface, MT, [MT(rand(ET,10,5))])
23+
Interfaces.test(BaseInterfaces.IterationInterface{(:indexing)}, MT, [MT(rand(ET,10,5))])
24+
25+
26+
Interfaces.test(BaseInterfaces.ArrayInterface, MT, [MT(rand(ET,10,5))])
27+
Interfaces.test(BaseInterfaces.ArrayInterface{(:logical,:setindex!)}, MT, [MT(rand(ET,10,5))])
28+
29+
m = MT(rand(ET,10,5))
30+
@test Matrix(MT(Matrix(m))) == Matrix(m)
31+
@test MT(Matrix(m)) == m
32+
end
33+
34+
end

0 commit comments

Comments
 (0)