• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37527

pending completion
#37527

push

local

web-flow
make `IRShow.method_name` inferrable (#49607)

18 of 18 new or added lines in 3 files covered. (100.0%)

68710 of 81829 relevant lines covered (83.97%)

33068903.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.24
/stdlib/LinearAlgebra/src/diagonal.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
## Diagonal matrices
4

5
struct Diagonal{T,V<:AbstractVector{T}} <: AbstractMatrix{T}
6
    diag::V
7

8
    function Diagonal{T,V}(diag) where {T,V<:AbstractVector{T}}
4,380✔
9
        require_one_based_indexing(diag)
4,380✔
10
        new{T,V}(diag)
4,380✔
11
    end
12
end
13
Diagonal{T,V}(d::Diagonal) where {T,V<:AbstractVector{T}} = Diagonal{T,V}(d.diag)
×
14
Diagonal(v::AbstractVector{T}) where {T} = Diagonal{T,typeof(v)}(v)
4,380✔
15
Diagonal{T}(v::AbstractVector) where {T} = Diagonal(convert(AbstractVector{T}, v)::AbstractVector{T})
103✔
16

17
function Base.promote_rule(A::Type{<:Diagonal{<:Any,V}}, B::Type{<:Diagonal{<:Any,W}}) where {V,W}
×
18
    X = promote_type(V, W)
×
19
    T = eltype(X)
×
20
    isconcretetype(T) && return Diagonal{T,X}
×
21
    return typejoin(A, B)
×
22
end
23

24
"""
25
    Diagonal(V::AbstractVector)
26

27
Construct a lazy matrix with `V` as its diagonal.
28

29
See also [`UniformScaling`](@ref) for the lazy identity matrix `I`,
30
[`diagm`](@ref) to make a dense matrix, and [`diag`](@ref) to extract diagonal elements.
31

32
# Examples
33
```jldoctest
34
julia> d = Diagonal([1, 10, 100])
35
3×3 Diagonal{$Int, Vector{$Int}}:
36
 1   ⋅    ⋅
37
 ⋅  10    ⋅
38
 ⋅   ⋅  100
39

40
julia> diagm([7, 13])
41
2×2 Matrix{$Int}:
42
 7   0
43
 0  13
44

45
julia> ans + I
46
2×2 Matrix{Int64}:
47
 8   0
48
 0  14
49

50
julia> I(2)
51
2×2 Diagonal{Bool, Vector{Bool}}:
52
 1  ⋅
53
 ⋅  1
54
```
55

56
Note that a one-column matrix is not treated like a vector, but instead calls the
57
method `Diagonal(A::AbstractMatrix)` which extracts 1-element `diag(A)`:
58

59
```jldoctest
60
julia> A = transpose([7.0 13.0])
61
2×1 transpose(::Matrix{Float64}) with eltype Float64:
62
  7.0
63
 13.0
64

65
julia> Diagonal(A)
66
1×1 Diagonal{Float64, Vector{Float64}}:
67
 7.0
68
```
69
"""
70
Diagonal(V::AbstractVector)
71

72
"""
73
    Diagonal(A::AbstractMatrix)
74

75
Construct a matrix from the diagonal of `A`.
76

77
# Examples
78
```jldoctest
79
julia> A = permutedims(reshape(1:15, 5, 3))
80
3×5 Matrix{Int64}:
81
  1   2   3   4   5
82
  6   7   8   9  10
83
 11  12  13  14  15
84

85
julia> Diagonal(A)
86
3×3 Diagonal{$Int, Vector{$Int}}:
87
 1  ⋅   ⋅
88
 ⋅  7   ⋅
89
 ⋅  ⋅  13
90

91
julia> diag(A, 2)
92
3-element Vector{$Int}:
93
  3
94
  9
95
 15
96
```
97
"""
98
Diagonal(A::AbstractMatrix) = Diagonal(diag(A))
900✔
99
Diagonal{T}(A::AbstractMatrix) where T = Diagonal{T}(diag(A))
×
100
function convert(::Type{T}, A::AbstractMatrix) where T<:Diagonal
×
101
    checksquare(A)
×
102
    isdiag(A) ? T(A) : throw(InexactError(:convert, T, A))
×
103
end
104

105
Diagonal(D::Diagonal) = D
2✔
106
Diagonal{T}(D::Diagonal{T}) where {T} = D
56✔
107
Diagonal{T}(D::Diagonal) where {T} = Diagonal{T}(D.diag)
37✔
108

109
AbstractMatrix{T}(D::Diagonal) where {T} = Diagonal{T}(D)
1✔
110
Matrix(D::Diagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(D)
2,475✔
111
Array(D::Diagonal{T}) where {T} = Matrix(D)
273✔
112
function Matrix{T}(D::Diagonal) where {T}
2,491✔
113
    n = size(D, 1)
2,491✔
114
    B = zeros(T, n, n)
93,758✔
115
    @inbounds for i in 1:n
4,918✔
116
        B[i,i] = D.diag[i]
13,021✔
117
    end
23,615✔
118
    return B
2,491✔
119
end
120

121
"""
122
    Diagonal{T}(undef, n)
123

124
Construct an uninitialized `Diagonal{T}` of length `n`. See `undef`.
125
"""
126
Diagonal{T}(::UndefInitializer, n::Integer) where T = Diagonal(Vector{T}(undef, n))
×
127

128
similar(D::Diagonal, ::Type{T}) where {T} = Diagonal(similar(D.diag, T))
293✔
129
similar(D::Diagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(D.diag, T, dims)
994✔
130

131
copyto!(D1::Diagonal, D2::Diagonal) = (copyto!(D1.diag, D2.diag); D1)
551✔
132

133
size(D::Diagonal) = (n = length(D.diag); (n,n))
130,870,824✔
134

135
function size(D::Diagonal,d::Integer)
8,148✔
136
    if d<1
8,148✔
137
        throw(ArgumentError("dimension must be ≥ 1, got $d"))
×
138
    end
139
    return d<=2 ? length(D.diag) : 1
8,148✔
140
end
141

142
@inline function getindex(D::Diagonal, i::Int, j::Int)
65,405,432✔
143
    @boundscheck checkbounds(D, i, j)
65,405,432✔
144
    if i == j
65,405,432✔
145
        @inbounds r = D.diag[i]
101,126✔
146
    else
147
        r = diagzero(D, i, j)
65,304,306✔
148
    end
149
    r
65,405,432✔
150
end
151
diagzero(::Diagonal{T}, i, j) where {T} = zero(T)
65,304,304✔
152
diagzero(D::Diagonal{<:AbstractMatrix{T}}, i, j) where {T} = zeros(T, size(D.diag[i], 1), size(D.diag[j], 2))
×
153

154
function setindex!(D::Diagonal, v, i::Int, j::Int)
30✔
155
    @boundscheck checkbounds(D, i, j)
30✔
156
    if i == j
30✔
157
        @inbounds D.diag[i] = v
8✔
158
    elseif !iszero(v)
22✔
159
        throw(ArgumentError("cannot set off-diagonal entry ($i, $j) to a nonzero value ($v)"))
2✔
160
    end
161
    return v
28✔
162
end
163

164

165
## structured matrix methods ##
166
function Base.replace_in_print_matrix(A::Diagonal,i::Integer,j::Integer,s::AbstractString)
66✔
167
    i==j ? s : Base.replace_with_centered_mark(s)
66✔
168
end
169

170
parent(D::Diagonal) = D.diag
552✔
171

172
ishermitian(D::Diagonal{<:Real}) = true
×
173
ishermitian(D::Diagonal{<:Number}) = isreal(D.diag)
×
174
ishermitian(D::Diagonal) = all(ishermitian, D.diag)
×
175
issymmetric(D::Diagonal{<:Number}) = true
2✔
176
issymmetric(D::Diagonal) = all(issymmetric, D.diag)
×
177
isposdef(D::Diagonal) = all(isposdef, D.diag)
×
178

179
factorize(D::Diagonal) = D
×
180

181
real(D::Diagonal) = Diagonal(real(D.diag))
×
182
imag(D::Diagonal) = Diagonal(imag(D.diag))
×
183

184
iszero(D::Diagonal) = all(iszero, D.diag)
15✔
185
isone(D::Diagonal) = all(isone, D.diag)
12✔
186
isdiag(D::Diagonal) = all(isdiag, D.diag)
×
187
isdiag(D::Diagonal{<:Number}) = true
12✔
188
istriu(D::Diagonal, k::Integer=0) = k <= 0 || iszero(D.diag) ? true : false
32✔
189
istril(D::Diagonal, k::Integer=0) = k >= 0 || iszero(D.diag) ? true : false
32✔
190
function triu!(D::Diagonal{T}, k::Integer=0) where T
×
191
    n = size(D,1)
×
192
    if !(-n + 1 <= k <= n + 1)
×
193
        throw(ArgumentError(string("the requested diagonal, $k, must be at least ",
×
194
            "$(-n + 1) and at most $(n + 1) in an $n-by-$n matrix")))
195
    elseif k > 0
×
196
        fill!(D.diag, zero(T))
×
197
    end
198
    return D
×
199
end
200

201
function tril!(D::Diagonal{T}, k::Integer=0) where T
×
202
    n = size(D,1)
×
203
    if !(-n - 1 <= k <= n - 1)
×
204
        throw(ArgumentError(string("the requested diagonal, $k, must be at least ",
×
205
            "$(-n - 1) and at most $(n - 1) in an $n-by-$n matrix")))
206
    elseif k < 0
×
207
        fill!(D.diag, zero(T))
×
208
    end
209
    return D
×
210
end
211

212
(==)(Da::Diagonal, Db::Diagonal) = Da.diag == Db.diag
48✔
213
(-)(A::Diagonal) = Diagonal(-A.diag)
14✔
214
(+)(Da::Diagonal, Db::Diagonal) = Diagonal(Da.diag + Db.diag)
69✔
215
(-)(Da::Diagonal, Db::Diagonal) = Diagonal(Da.diag - Db.diag)
122✔
216

217
for f in (:+, :-)
218
    @eval function $f(D::Diagonal, S::Symmetric)
×
219
        return Symmetric($f(D, S.data), sym_uplo(S.uplo))
×
220
    end
221
    @eval function $f(S::Symmetric, D::Diagonal)
×
222
        return Symmetric($f(S.data, D), sym_uplo(S.uplo))
×
223
    end
224
    @eval function $f(D::Diagonal{<:Real}, H::Hermitian)
×
225
        return Hermitian($f(D, H.data), sym_uplo(H.uplo))
×
226
    end
227
    @eval function $f(H::Hermitian, D::Diagonal{<:Real})
×
228
        return Hermitian($f(H.data, D), sym_uplo(H.uplo))
×
229
    end
230
end
231

232
(*)(x::Number, D::Diagonal) = Diagonal(x * D.diag)
157✔
233
(*)(D::Diagonal, x::Number) = Diagonal(D.diag * x)
×
234
(/)(D::Diagonal, x::Number) = Diagonal(D.diag / x)
×
235
(\)(x::Number, D::Diagonal) = Diagonal(x \ D.diag)
×
236
(^)(D::Diagonal, a::Number) = Diagonal(D.diag .^ a)
×
237
(^)(D::Diagonal, a::Real) = Diagonal(D.diag .^ a) # for disambiguation
×
238
(^)(D::Diagonal, a::Integer) = Diagonal(D.diag .^ a) # for disambiguation
×
239
Base.literal_pow(::typeof(^), D::Diagonal, valp::Val) =
×
240
    Diagonal(Base.literal_pow.(^, D.diag, valp)) # for speed
241
Base.literal_pow(::typeof(^), D::Diagonal, ::Val{-1}) = inv(D) # for disambiguation
×
242

243
function _muldiag_size_check(A, B)
2,529✔
244
    nA = size(A, 2)
2,529✔
245
    mB = size(B, 1)
2,529✔
246
    @noinline throw_dimerr(::AbstractMatrix, nA, mB) = throw(DimensionMismatch("second dimension of A, $nA, does not match first dimension of B, $mB"))
2,539✔
247
    @noinline throw_dimerr(::AbstractVector, nA, mB) = throw(DimensionMismatch("second dimension of D, $nA, does not match length of V, $mB"))
×
248
    nA == mB || throw_dimerr(B, nA, mB)
2,539✔
249
    return nothing
2,519✔
250
end
251
# the output matrix should have the same size as the non-diagonal input matrix or vector
252
@noinline throw_dimerr(szC, szA) = throw(DimensionMismatch("output matrix has size: $szC, but should have size $szA"))
8✔
253
_size_check_out(C, ::Diagonal, A) = _size_check_out(C, A)
754✔
254
_size_check_out(C, A, ::Diagonal) = _size_check_out(C, A)
973✔
255
_size_check_out(C, A::Diagonal, ::Diagonal) = _size_check_out(C, A)
387✔
256
function _size_check_out(C, A)
2,106✔
257
    szA = size(A)
2,106✔
258
    szC = size(C)
2,106✔
259
    szA == szC || throw_dimerr(szC, szA)
2,114✔
260
    return nothing
2,098✔
261
end
262
function _muldiag_size_check(C, A, B)
2,116✔
263
    _muldiag_size_check(A, B)
2,126✔
264
    _size_check_out(C, A, B)
2,114✔
265
end
266

267
function (*)(Da::Diagonal, Db::Diagonal)
85✔
268
    _muldiag_size_check(Da, Db)
85✔
269
    return Diagonal(Da.diag .* Db.diag)
85✔
270
end
271

272
function (*)(D::Diagonal, V::AbstractVector)
72✔
273
    _muldiag_size_check(D, V)
72✔
274
    return D.diag .* V
72✔
275
end
276

277
(*)(A::AbstractMatrix, D::Diagonal) =
1,271✔
278
    mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag))), A, D)
279
(*)(A::HermOrSym, D::Diagonal) =
×
280
    mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag)), size(A)), A, D)
281
(*)(D::Diagonal, A::AbstractMatrix) =
475✔
282
    mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag))), D, A)
283
(*)(D::Diagonal, A::HermOrSym) =
×
284
    mul!(similar(A, promote_op(*, eltype(A), eltype(D.diag)), size(A)), D, A)
285

286
rmul!(A::AbstractMatrix, D::Diagonal) = @inline mul!(A, A, D)
37✔
287
lmul!(D::Diagonal, B::AbstractVecOrMat) = @inline mul!(B, D, B)
276✔
288

289
function (*)(A::AdjOrTransAbsMat, D::Diagonal)
28✔
290
    Ac = copy_similar(A, promote_op(*, eltype(A), eltype(D.diag)))
56✔
291
    rmul!(Ac, D)
28✔
292
end
293
function (*)(D::Diagonal, A::AdjOrTransAbsMat)
255✔
294
    Ac = copy_similar(A, promote_op(*, eltype(A), eltype(D.diag)))
510✔
295
    lmul!(D, Ac)
255✔
296
end
297

298
@inline function __muldiag!(out, D::Diagonal, B, alpha, beta)
738✔
299
    require_one_based_indexing(B)
738✔
300
    require_one_based_indexing(out)
738✔
301
    if iszero(alpha)
738✔
302
        _rmul_or_fill!(out, beta)
×
303
    else
304
        if iszero(beta)
738✔
305
            @inbounds for j in axes(B, 2)
1,114✔
306
                @simd for i in axes(B, 1)
26,379✔
307
                    out[i,j] = D.diag[i] * B[i,j] * alpha
1,950,810✔
308
                end
×
309
            end
51,982✔
310
        else
311
            @inbounds for j in axes(B, 2)
125✔
312
                @simd for i in axes(B, 1)
148✔
313
                    out[i,j] = D.diag[i] * B[i,j] * alpha + out[i,j] * beta
434✔
314
                end
×
315
            end
154✔
316
        end
317
    end
318
    return out
738✔
319
end
320
@inline function __muldiag!(out, A, D::Diagonal, alpha, beta)
973✔
321
    require_one_based_indexing(A)
973✔
322
    require_one_based_indexing(out)
973✔
323
    if iszero(alpha)
973✔
324
        _rmul_or_fill!(out, beta)
×
325
    else
326
        if iszero(beta)
973✔
327
            @inbounds for j in axes(A, 2)
1,896✔
328
                dja = D.diag[j] * alpha
5,222✔
329
                @simd for i in axes(A, 1)
5,222✔
330
                    out[i,j] = A[i,j] * dja
95,181✔
331
                end
×
332
            end
9,340✔
333
        else
334
            @inbounds for j in axes(A, 2)
50✔
335
                dja = D.diag[j] * alpha
73✔
336
                @simd for i in axes(A, 1)
73✔
337
                    out[i,j] = A[i,j] * dja + out[i,j] * beta
209✔
338
                end
×
339
            end
79✔
340
        end
341
    end
342
    return out
973✔
343
end
344
@inline function __muldiag!(out::Diagonal, D1::Diagonal, D2::Diagonal, alpha, beta)
207✔
345
    d1 = D1.diag
207✔
346
    d2 = D2.diag
207✔
347
    if iszero(alpha)
207✔
348
        _rmul_or_fill!(out.diag, beta)
90✔
349
    else
350
        if iszero(beta)
162✔
351
            @inbounds @simd for i in eachindex(out.diag)
132✔
352
                out.diag[i] = d1[i] * d2[i] * alpha
396✔
353
            end
×
354
        else
355
            @inbounds @simd for i in eachindex(out.diag)
30✔
356
                out.diag[i] = d1[i] * d2[i] * alpha + out.diag[i] * beta
90✔
357
            end
×
358
        end
359
    end
360
    return out
207✔
361
end
362
@inline function __muldiag!(out, D1::Diagonal, D2::Diagonal, alpha, beta)
180✔
363
    require_one_based_indexing(out)
180✔
364
    mA = size(D1, 1)
180✔
365
    d1 = D1.diag
180✔
366
    d2 = D2.diag
180✔
367
    _rmul_or_fill!(out, beta)
360✔
368
    if !iszero(alpha)
180✔
369
        @inbounds @simd for i in 1:mA
126✔
370
            out[i,i] += d1[i] * d2[i] * alpha
378✔
371
        end
×
372
    end
373
    return out
180✔
374
end
375

376
@inline function _muldiag!(out, A, B, alpha, beta)
2,116✔
377
    _muldiag_size_check(out, A, B)
2,124✔
378
    __muldiag!(out, A, B, alpha, beta)
31,876✔
379
    return out
2,098✔
380
end
381

382
function (*)(Da::Diagonal, A::AbstractMatrix, Db::Diagonal)
80✔
383
    _muldiag_size_check(Da, A)
80✔
384
    _muldiag_size_check(A, Db)
80✔
385
    return broadcast(*, Da.diag, A, permutedims(Db.diag))
80✔
386
end
387

388
function (*)(Da::Diagonal, Db::Diagonal, Dc::Diagonal)
48✔
389
    _muldiag_size_check(Da, Db)
48✔
390
    _muldiag_size_check(Db, Dc)
48✔
391
    return Diagonal(Da.diag .* Db.diag .* Dc.diag)
48✔
392
end
393

394
# Get ambiguous method if try to unify AbstractVector/AbstractMatrix here using AbstractVecOrMat
395
@inline mul!(out::AbstractVector, D::Diagonal, V::AbstractVector, alpha::Number, beta::Number) =
237✔
396
    _muldiag!(out, D, V, alpha, beta)
397
@inline mul!(out::AbstractMatrix, D::Diagonal, B::AbstractMatrix, alpha::Number, beta::Number) =
26,122✔
398
    _muldiag!(out, D, B, alpha, beta)
399
@inline mul!(out::AbstractMatrix, D::Diagonal, B::Adjoint{<:Any,<:AbstractVecOrMat},
400
             alpha::Number, beta::Number) = _muldiag!(out, D, B, alpha, beta)
×
401
@inline mul!(out::AbstractMatrix, D::Diagonal, B::Transpose{<:Any,<:AbstractVecOrMat},
402
             alpha::Number, beta::Number) = _muldiag!(out, D, B, alpha, beta)
×
403

404
@inline mul!(out::AbstractMatrix, A::AbstractMatrix, D::Diagonal, alpha::Number, beta::Number) =
5,103✔
405
    _muldiag!(out, A, D, alpha, beta)
406
@inline mul!(out::AbstractMatrix, A::Adjoint{<:Any,<:AbstractVecOrMat}, D::Diagonal,
407
             alpha::Number, beta::Number) = _muldiag!(out, A, D, alpha, beta)
×
408
@inline mul!(out::AbstractMatrix, A::Transpose{<:Any,<:AbstractVecOrMat}, D::Diagonal,
409
             alpha::Number, beta::Number) = _muldiag!(out, A, D, alpha, beta)
×
410
@inline mul!(C::Diagonal, Da::Diagonal, Db::Diagonal, alpha::Number, beta::Number) =
207✔
411
    _muldiag!(C, Da, Db, alpha, beta)
412

413
mul!(C::AbstractMatrix, Da::Diagonal, Db::Diagonal, alpha::Number, beta::Number) =
180✔
414
    _muldiag!(C, Da, Db, alpha, beta)
415

416
/(A::AbstractVecOrMat, D::Diagonal) = _rdiv!(similar(A, _init_eltype(/, eltype(A), eltype(D))), A, D)
71✔
417
/(A::HermOrSym, D::Diagonal) = _rdiv!(similar(A, _init_eltype(/, eltype(A), eltype(D)), size(A)), A, D)
×
418
rdiv!(A::AbstractVecOrMat, D::Diagonal) = @inline _rdiv!(A, A, D)
107✔
419
# avoid copy when possible via internal 3-arg backend
420
function _rdiv!(B::AbstractVecOrMat, A::AbstractVecOrMat, D::Diagonal)
177✔
421
    require_one_based_indexing(A)
177✔
422
    dd = D.diag
177✔
423
    m, n = size(A, 1), size(A, 2)
177✔
424
    if (k = length(dd)) != n
177✔
425
        throw(DimensionMismatch("left hand side has $n columns but D is $k by $k"))
×
426
    end
427
    @inbounds for j in 1:n
354✔
428
        ddj = dd[j]
1,084✔
429
        iszero(ddj) && throw(SingularException(j))
1,084✔
430
        for i in 1:m
2,168✔
431
            B[i, j] = A[i, j] / ddj
8,900✔
432
        end
13,836✔
433
    end
1,991✔
434
    B
177✔
435
end
436

437
function \(D::Diagonal, B::AbstractVector)
58✔
438
    j = findfirst(iszero, D.diag)
359✔
439
    isnothing(j) || throw(SingularException(j))
59✔
440
    return D.diag .\ B
57✔
441
end
442
\(D::Diagonal, B::AbstractMatrix) = ldiv!(similar(B, _init_eltype(\, eltype(D), eltype(B))), D, B)
268✔
443
\(D::Diagonal, B::HermOrSym) = ldiv!(similar(B, _init_eltype(\, eltype(D), eltype(B)), size(B)), D, B)
24✔
444

445
ldiv!(D::Diagonal, B::AbstractVecOrMat) = @inline ldiv!(B, D, B)
110✔
446
function ldiv!(B::AbstractVecOrMat, D::Diagonal, A::AbstractVecOrMat)
420✔
447
    require_one_based_indexing(A, B)
420✔
448
    dd = D.diag
420✔
449
    d = length(dd)
420✔
450
    m, n = size(A, 1), size(A, 2)
420✔
451
    m′, n′ = size(B, 1), size(B, 2)
420✔
452
    m == d || throw(DimensionMismatch("right hand side has $m rows but D is $d by $d"))
424✔
453
    (m, n) == (m′, n′) || throw(DimensionMismatch("expect output to be $m by $n, but got $m′ by $n′"))
416✔
454
    j = findfirst(iszero, D.diag)
2,623✔
455
    isnothing(j) || throw(SingularException(j))
416✔
456
    @inbounds for j = 1:n, i = 1:m
2,538✔
457
        B[i, j] = dd[i] \ A[i, j]
15,774✔
458
    end
15,980✔
459
    B
416✔
460
end
461

462
# Optimizations for \, / between Diagonals
463
\(D::Diagonal, B::Diagonal) = ldiv!(similar(B, promote_op(\, eltype(D), eltype(B))), D, B)
40✔
464
/(A::Diagonal, D::Diagonal) = _rdiv!(similar(A, promote_op(/, eltype(A), eltype(D))), A, D)
×
465
function _rdiv!(Dc::Diagonal, Db::Diagonal, Da::Diagonal)
×
466
    n, k = length(Db.diag), length(Da.diag)
×
467
    n == k || throw(DimensionMismatch("left hand side has $n columns but D is $k by $k"))
×
468
    j = findfirst(iszero, Da.diag)
×
469
    isnothing(j) || throw(SingularException(j))
×
470
    Dc.diag .= Db.diag ./ Da.diag
×
471
    Dc
×
472
end
473
ldiv!(Dc::Diagonal, Da::Diagonal, Db::Diagonal) = Diagonal(ldiv!(Dc.diag, Da, Db.diag))
20✔
474

475
# optimizations for (Sym)Tridiagonal and Diagonal
476
@propagate_inbounds _getudiag(T::Tridiagonal, i) = T.du[i]
×
477
@propagate_inbounds _getudiag(S::SymTridiagonal, i) = S.ev[i]
×
478
@propagate_inbounds _getdiag(T::Tridiagonal, i) = T.d[i]
×
479
@propagate_inbounds _getdiag(S::SymTridiagonal, i) = symmetric(S.dv[i], :U)::symmetric_type(eltype(S.dv))
×
480
@propagate_inbounds _getldiag(T::Tridiagonal, i) = T.dl[i]
×
481
@propagate_inbounds _getldiag(S::SymTridiagonal, i) = transpose(S.ev[i])
×
482

483
function (\)(D::Diagonal, S::SymTridiagonal)
×
484
    T = promote_op(\, eltype(D), eltype(S))
×
485
    du = similar(S.ev, T, max(length(S.dv)-1, 0))
×
486
    d  = similar(S.dv, T, length(S.dv))
×
487
    dl = similar(S.ev, T, max(length(S.dv)-1, 0))
×
488
    ldiv!(Tridiagonal(dl, d, du), D, S)
×
489
end
490
(\)(D::Diagonal, T::Tridiagonal) = ldiv!(similar(T, promote_op(\, eltype(D), eltype(T))), D, T)
×
491
function ldiv!(T::Tridiagonal, D::Diagonal, S::Union{SymTridiagonal,Tridiagonal})
×
492
    m = size(S, 1)
×
493
    dd = D.diag
×
494
    if (k = length(dd)) != m
×
495
        throw(DimensionMismatch("diagonal matrix is $k by $k but right hand side has $m rows"))
×
496
    end
497
    if length(T.d) != m
×
498
        throw(DimensionMismatch("target matrix size $(size(T)) does not match input matrix size $(size(S))"))
×
499
    end
500
    m == 0 && return T
×
501
    j = findfirst(iszero, dd)
×
502
    isnothing(j) || throw(SingularException(j))
×
503
    ddj = dd[1]
×
504
    T.d[1] = ddj \ _getdiag(S, 1)
×
505
    @inbounds if m > 1
×
506
        T.du[1] = ddj \ _getudiag(S, 1)
×
507
        for j in 2:m-1
×
508
            ddj = dd[j]
×
509
            T.dl[j-1] = ddj \ _getldiag(S, j-1)
×
510
            T.d[j]  = ddj \ _getdiag(S, j)
×
511
            T.du[j] = ddj \ _getudiag(S, j)
×
512
        end
×
513
        ddj = dd[m]
×
514
        T.dl[m-1] = ddj \ _getldiag(S, m-1)
×
515
        T.d[m] = ddj \ _getdiag(S, m)
×
516
    end
517
    return T
×
518
end
519

520
function (/)(S::SymTridiagonal, D::Diagonal)
×
521
    T = promote_op(\, eltype(D), eltype(S))
×
522
    du = similar(S.ev, T, max(length(S.dv)-1, 0))
×
523
    d  = similar(S.dv, T, length(S.dv))
×
524
    dl = similar(S.ev, T, max(length(S.dv)-1, 0))
×
525
    _rdiv!(Tridiagonal(dl, d, du), S, D)
×
526
end
527
(/)(T::Tridiagonal, D::Diagonal) = _rdiv!(similar(T, promote_op(/, eltype(T), eltype(D))), T, D)
×
528
function _rdiv!(T::Tridiagonal, S::Union{SymTridiagonal,Tridiagonal}, D::Diagonal)
×
529
    n = size(S, 2)
×
530
    dd = D.diag
×
531
    if (k = length(dd)) != n
×
532
        throw(DimensionMismatch("left hand side has $n columns but D is $k by $k"))
×
533
    end
534
    if length(T.d) != n
×
535
        throw(DimensionMismatch("target matrix size $(size(T)) does not match input matrix size $(size(S))"))
×
536
    end
537
    n == 0 && return T
×
538
    j = findfirst(iszero, dd)
×
539
    isnothing(j) || throw(SingularException(j))
×
540
    ddj = dd[1]
×
541
    T.d[1] = _getdiag(S, 1) / ddj
×
542
    @inbounds if n > 1
×
543
        T.dl[1] = _getldiag(S, 1) / ddj
×
544
        for j in 2:n-1
×
545
            ddj = dd[j]
×
546
            T.dl[j] = _getldiag(S, j) / ddj
×
547
            T.d[j] = _getdiag(S, j) / ddj
×
548
            T.du[j-1] = _getudiag(S, j-1) / ddj
×
549
        end
×
550
        ddj = dd[n]
×
551
        T.d[n] = _getdiag(S, n) / ddj
×
552
        T.du[n-1] = _getudiag(S, n-1) / ddj
×
553
    end
554
    return T
×
555
end
556

557
# Optimizations for [l/r]mul!, l/rdiv!, *, / and \ between Triangular and Diagonal.
558
# These functions are generally more efficient if we calculate the whole data field.
559
# The following code implements them in a unified pattern to avoid missing.
560
@inline function _setdiag!(data, f, diag, diag′ = nothing)
189✔
561
    @inbounds for i in 1:length(diag)
378✔
562
        data[i,i] = isnothing(diag′) ? f(diag[i]) : f(diag[i],diag′[i])
567✔
563
    end
945✔
564
    data
189✔
565
end
566
for Tri in (:UpperTriangular, :LowerTriangular)
567
    UTri = Symbol(:Unit, Tri)
568
    # 2 args
569
    for (fun, f) in zip((:*, :rmul!, :rdiv!, :/), (:identity, :identity, :inv, :inv))
570
        @eval $fun(A::$Tri, D::Diagonal) = $Tri($fun(A.data, D))
4✔
571
        @eval $fun(A::$UTri, D::Diagonal) = $Tri(_setdiag!($fun(A.data, D), $f, D.diag))
×
572
    end
573
    for (fun, f) in zip((:*, :lmul!, :ldiv!, :\), (:identity, :identity, :inv, :inv))
574
        @eval $fun(D::Diagonal, A::$Tri) = $Tri($fun(D, A.data))
4✔
575
        @eval $fun(D::Diagonal, A::$UTri) = $Tri(_setdiag!($fun(D, A.data), $f, D.diag))
×
576
    end
577
    # 3-arg ldiv!
578
    @eval ldiv!(C::$Tri, D::Diagonal, A::$Tri) = $Tri(ldiv!(C.data, D, A.data))
×
579
    @eval ldiv!(C::$Tri, D::Diagonal, A::$UTri) = $Tri(_setdiag!(ldiv!(C.data, D, A.data), inv, D.diag))
×
580
    # 3-arg mul!: invoke 5-arg mul! rather than lmul!
581
    @eval mul!(C::$Tri, A::Union{$Tri,$UTri}, D::Diagonal) = mul!(C, A, D, true, false)
×
582
    # 5-arg mul!
583
    @eval @inline mul!(C::$Tri, D::Diagonal, A::$Tri, α::Number, β::Number) = $Tri(mul!(C.data, D, A.data, α, β))
×
584
    @eval @inline function mul!(C::$Tri, D::Diagonal, A::$UTri, α::Number, β::Number)
90✔
585
        iszero(α) && return _rmul_or_fill!(C, β)
90✔
586
        diag′ = iszero(β) ? nothing : diag(C)
111✔
587
        data = mul!(C.data, D, A.data, α, β)
90✔
588
        $Tri(_setdiag!(data, MulAddMul(α, β), D.diag, diag′))
90✔
589
    end
590
    @eval @inline mul!(C::$Tri, A::$Tri, D::Diagonal, α::Number, β::Number) = $Tri(mul!(C.data, A.data, D, α, β))
×
591
    @eval @inline function mul!(C::$Tri, A::$UTri, D::Diagonal, α::Number, β::Number)
108✔
592
        iszero(α) && return _rmul_or_fill!(C, β)
108✔
593
        diag′ = iszero(β) ? nothing : diag(C)
120✔
594
        data = mul!(C.data, A.data, D, α, β)
99✔
595
        $Tri(_setdiag!(data, MulAddMul(α, β), D.diag, diag′))
99✔
596
    end
597
end
598

599
@inline function kron!(C::AbstractMatrix, A::Diagonal, B::Diagonal)
×
600
    valA = A.diag; nA = length(valA)
×
601
    valB = B.diag; nB = length(valB)
×
602
    nC = checksquare(C)
×
603
    @boundscheck nC == nA*nB ||
×
604
        throw(DimensionMismatch("expect C to be a $(nA*nB)x$(nA*nB) matrix, got size $(nC)x$(nC)"))
605
    isempty(A) || isempty(B) || fill!(C, zero(A[1,1] * B[1,1]))
×
606
    @inbounds for i = 1:nA, j = 1:nB
×
607
        idx = (i-1)*nB+j
×
608
        C[idx, idx] = valA[i] * valB[j]
×
609
    end
×
610
    return C
×
611
end
612

613
kron(A::Diagonal, B::Diagonal) = Diagonal(kron(A.diag, B.diag))
×
614

615
function kron(A::Diagonal, B::SymTridiagonal)
×
616
    kdv = kron(diag(A), B.dv)
×
617
    # We don't need to drop the last element
618
    kev = kron(diag(A), _pushzero(_evview(B)))
×
619
    SymTridiagonal(kdv, kev)
×
620
end
621
function kron(A::Diagonal, B::Tridiagonal)
×
622
    # `_droplast!` is only guaranteed to work with `Vector`
623
    kd = _makevector(kron(diag(A), B.d))
×
624
    kdl = _droplast!(_makevector(kron(diag(A), _pushzero(B.dl))))
×
625
    kdu = _droplast!(_makevector(kron(diag(A), _pushzero(B.du))))
×
626
    Tridiagonal(kdl, kd, kdu)
×
627
end
628

629
@inline function kron!(C::AbstractMatrix, A::Diagonal, B::AbstractMatrix)
×
630
    require_one_based_indexing(B)
×
631
    (mA, nA) = size(A)
×
632
    (mB, nB) = size(B)
×
633
    (mC, nC) = size(C)
×
634
    @boundscheck (mC, nC) == (mA * mB, nA * nB) ||
×
635
        throw(DimensionMismatch("expect C to be a $(mA * mB)x$(nA * nB) matrix, got size $(mC)x$(nC)"))
636
    isempty(A) || isempty(B) || fill!(C, zero(A[1,1] * B[1,1]))
×
637
    m = 1
×
638
    @inbounds for j = 1:nA
×
639
        A_jj = A[j,j]
×
640
        for k = 1:nB
×
641
            for l = 1:mB
×
642
                C[m] = A_jj * B[l,k]
×
643
                m += 1
×
644
            end
×
645
            m += (nA - 1) * mB
×
646
        end
×
647
        m += mB
×
648
    end
×
649
    return C
×
650
end
651

652
@inline function kron!(C::AbstractMatrix, A::AbstractMatrix, B::Diagonal)
×
653
    require_one_based_indexing(A)
×
654
    (mA, nA) = size(A)
×
655
    (mB, nB) = size(B)
×
656
    (mC, nC) = size(C)
×
657
    @boundscheck (mC, nC) == (mA * mB, nA * nB) ||
×
658
        throw(DimensionMismatch("expect C to be a $(mA * mB)x$(nA * nB) matrix, got size $(mC)x$(nC)"))
659
    isempty(A) || isempty(B) || fill!(C, zero(A[1,1] * B[1,1]))
×
660
    m = 1
×
661
    @inbounds for j = 1:nA
×
662
        for l = 1:mB
×
663
            Bll = B[l,l]
×
664
            for k = 1:mA
×
665
                C[m] = A[k,j] * Bll
×
666
                m += nB
×
667
            end
×
668
            m += 1
×
669
        end
×
670
        m -= nB
×
671
    end
×
672
    return C
×
673
end
674

675
conj(D::Diagonal) = Diagonal(conj(D.diag))
×
676
transpose(D::Diagonal{<:Number}) = D
62✔
677
transpose(D::Diagonal) = Diagonal(transpose.(D.diag))
×
678
adjoint(D::Diagonal{<:Number}) = Diagonal(vec(adjoint(D.diag)))
272✔
679
adjoint(D::Diagonal{<:Number,<:Base.ReshapedArray{<:Number,1,<:Adjoint}}) = Diagonal(adjoint(parent(D.diag)))
×
680
adjoint(D::Diagonal) = Diagonal(adjoint.(D.diag))
×
681
permutedims(D::Diagonal) = D
×
682
permutedims(D::Diagonal, perm) = (Base.checkdims_perm(D, D, perm); D)
×
683

684
function diag(D::Diagonal{T}, k::Integer=0) where T
83✔
685
    # every branch call similar(..., ::Int) to make sure the
686
    # same vector type is returned independent of k
687
    if k == 0
83✔
688
        return copyto!(similar(D.diag, length(D.diag)), D.diag)
42✔
689
    elseif -size(D,1) <= k <= size(D,1)
2✔
690
        return fill!(similar(D.diag, size(D,1)-abs(k)), zero(T))
2✔
691
    else
692
        throw(ArgumentError(string("requested diagonal, $k, must be at least $(-size(D, 1)) ",
×
693
            "and at most $(size(D, 2)) for an $(size(D, 1))-by-$(size(D, 2)) matrix")))
694
    end
695
end
696
tr(D::Diagonal) = sum(tr, D.diag)
×
697
det(D::Diagonal) = prod(det, D.diag)
×
698
function logdet(D::Diagonal{<:Complex}) # make sure branch cut is correct
×
699
    z = sum(log, D.diag)
×
700
    complex(real(z), rem2pi(imag(z), RoundNearest))
×
701
end
702

703
# Matrix functions
704
for f in (:exp, :cis, :log, :sqrt,
705
          :cos, :sin, :tan, :csc, :sec, :cot,
706
          :cosh, :sinh, :tanh, :csch, :sech, :coth,
707
          :acos, :asin, :atan, :acsc, :asec, :acot,
708
          :acosh, :asinh, :atanh, :acsch, :asech, :acoth)
709
    @eval $f(D::Diagonal) = Diagonal($f.(D.diag))
×
710
end
711

712
function inv(D::Diagonal{T}) where T
30✔
713
    Di = similar(D.diag, typeof(inv(oneunit(T))))
30✔
714
    for i = 1:length(D.diag)
60✔
715
        if iszero(D.diag[i])
300✔
716
            throw(SingularException(i))
×
717
        end
718
        Di[i] = inv(D.diag[i])
380✔
719
    end
570✔
720
    Diagonal(Di)
30✔
721
end
722

723
function pinv(D::Diagonal{T}) where T
20✔
724
    Di = similar(D.diag, typeof(inv(oneunit(T))))
20✔
725
    for i = 1:length(D.diag)
40✔
726
        if !iszero(D.diag[i])
4,816✔
727
            invD = inv(D.diag[i])
5,980✔
728
            if isfinite(invD)
4,784✔
729
                Di[i] = invD
4,776✔
730
                continue
4,776✔
731
            end
732
        end
733
        # fallback
734
        Di[i] = zero(T)
40✔
735
    end
9,612✔
736
    Diagonal(Di)
20✔
737
end
738
function pinv(D::Diagonal{T}, tol::Real) where T
12✔
739
    Di = similar(D.diag, typeof(inv(oneunit(T))))
12✔
740
    if !isempty(D.diag)
12✔
741
        maxabsD = maximum(abs, D.diag)
12✔
742
        for i = 1:length(D.diag)
24✔
743
            if abs(D.diag[i]) > tol*maxabsD
6,000✔
744
                invD = inv(D.diag[i])
504✔
745
                if isfinite(invD)
480✔
746
                    Di[i] = invD
480✔
747
                    continue
480✔
748
                end
749
            end
750
            # fallback
751
            Di[i] = zero(T)
4,320✔
752
        end
9,588✔
753
    end
754
    Diagonal(Di)
12✔
755
end
756

757
#Eigensystem
758
eigvals(D::Diagonal{<:Number}; permute::Bool=true, scale::Bool=true) = copy(D.diag)
58✔
759
eigvals(D::Diagonal; permute::Bool=true, scale::Bool=true) =
×
760
    [eigvals(x) for x in D.diag] #For block matrices, etc.
761
eigvecs(D::Diagonal) = Matrix{eltype(D)}(I, size(D))
×
762
function eigen(D::Diagonal; permute::Bool=true, scale::Bool=true, sortby::Union{Function,Nothing}=nothing)
64✔
763
    if any(!isfinite, D.diag)
141✔
764
        throw(ArgumentError("matrix contains Infs or NaNs"))
3✔
765
    end
766
    Td = Base.promote_op(/, eltype(D), eltype(D))
58✔
767
    λ = eigvals(D)
29✔
768
    if !isnothing(sortby)
29✔
769
        p = sortperm(λ; alg=QuickSort, by=sortby)
9✔
770
        λ = λ[p]
9✔
771
        evecs = zeros(Td, size(D))
9✔
772
        @inbounds for i in eachindex(p)
18✔
773
            evecs[p[i],i] = one(Td)
9✔
774
        end
9✔
775
    else
776
        evecs = Matrix{Td}(I, size(D))
20✔
777
    end
778
    Eigen(λ, evecs)
29✔
779
end
780
function eigen(Da::Diagonal, Db::Diagonal; sortby::Union{Function,Nothing}=nothing)
40✔
781
    if any(!isfinite, Da.diag) || any(!isfinite, Db.diag)
120✔
782
        throw(ArgumentError("matrices contain Infs or NaNs"))
×
783
    end
784
    if any(iszero, Db.diag)
120✔
785
        throw(ArgumentError("right-hand side diagonal matrix is singular"))
×
786
    end
787
    return GeneralizedEigen(eigen(Db \ Da; sortby)...)
20✔
788
end
789
function eigen(A::AbstractMatrix, D::Diagonal; sortby::Union{Function,Nothing}=nothing)
184✔
790
    if any(iszero, D.diag)
552✔
791
        throw(ArgumentError("right-hand side diagonal matrix is singular"))
×
792
    end
793
    if size(A, 1) == size(A, 2) && isdiag(A)
122✔
794
        return eigen(Diagonal(A), D; sortby)
10✔
795
    elseif ishermitian(A)
82✔
796
        S = promote_type(eigtype(eltype(A)), eltype(D))
72✔
797
        return eigen!(eigencopy_oftype(Hermitian(A), S), Diagonal{S}(D); sortby)
72✔
798
    else
799
        S = promote_type(eigtype(eltype(A)), eltype(D))
10✔
800
        return eigen!(eigencopy_oftype(A, S), Diagonal{S}(D); sortby)
10✔
801
    end
802
end
803

804
#Singular system
805
svdvals(D::Diagonal{<:Number}) = sort!(abs.(D.diag), rev = true)
×
806
svdvals(D::Diagonal) = [svdvals(v) for v in D.diag]
×
807
function svd(D::Diagonal{T}) where {T<:Number}
×
808
    d = D.diag
×
809
    s = abs.(d)
×
810
    piv = sortperm(s, rev = true)
×
811
    S = s[piv]
×
812
    Td  = typeof(oneunit(T)/oneunit(T))
×
813
    U = zeros(Td, size(D))
×
814
    Vt = copy(U)
×
815
    for i in 1:length(d)
×
816
        j = piv[i]
×
817
        U[j,i] = d[j] / S[i]
×
818
        Vt[i,j] = one(Td)
×
819
    end
×
820
    return SVD(U, S, Vt)
×
821
end
822

823
# disambiguation methods: * and / of Diagonal and Adj/Trans AbsVec
824
*(u::AdjointAbsVec, D::Diagonal) = (D'u')'
×
825
*(u::TransposeAbsVec, D::Diagonal) = transpose(transpose(D) * transpose(u))
×
826
*(x::AdjointAbsVec,   D::Diagonal, y::AbstractVector) = _mapreduce_prod(*, x, D, y)
×
827
*(x::TransposeAbsVec, D::Diagonal, y::AbstractVector) = _mapreduce_prod(*, x, D, y)
×
828
/(u::AdjointAbsVec, D::Diagonal) = (D' \ u')'
×
829
/(u::TransposeAbsVec, D::Diagonal) = transpose(transpose(D) \ transpose(u))
×
830
# disambiguation methods: Call unoptimized version for user defined AbstractTriangular.
831
*(A::AbstractTriangular, D::Diagonal) = @invoke *(A::AbstractMatrix, D::Diagonal)
×
832
*(D::Diagonal, A::AbstractTriangular) = @invoke *(D::Diagonal, A::AbstractMatrix)
×
833

834
dot(x::AbstractVector, D::Diagonal, y::AbstractVector) = _mapreduce_prod(dot, x, D, y)
8✔
835

836
dot(A::Diagonal, B::Diagonal) = dot(A.diag, B.diag)
×
837
function dot(D::Diagonal, B::AbstractMatrix)
×
838
    size(D) == size(B) || throw(DimensionMismatch("Matrix sizes $(size(D)) and $(size(B)) differ"))
×
839
    return dot(D.diag, view(B, diagind(B)))
×
840
end
841

842
dot(A::AbstractMatrix, B::Diagonal) = conj(dot(B, A))
×
843

844
function _mapreduce_prod(f, x, D::Diagonal, y)
8✔
845
    if !(length(x) == length(D.diag) == length(y))
8✔
846
        throw(DimensionMismatch("x has length $(length(x)), D has size $(size(D)), and y has $(length(y))"))
×
847
    end
848
    if isempty(x) && isempty(D) && isempty(y)
8✔
849
        return zero(promote_op(f, eltype(x), eltype(D), eltype(y)))
×
850
    else
851
        return mapreduce(t -> f(t[1], t[2], t[3]), +, zip(x, D.diag, y))
88✔
852
    end
853
end
854

855
function cholesky!(A::Diagonal, ::NoPivot = NoPivot(); check::Bool = true)
279✔
856
    info = 0
93✔
857
    for (i, di) in enumerate(A.diag)
186✔
858
        if isreal(di) && real(di) > 0
443✔
859
            A.diag[i] = √di
440✔
860
        elseif check
4✔
861
            throw(PosDefException(i))
2✔
862
        else
863
            info = i
2✔
864
            break
2✔
865
        end
866
    end
788✔
867
    Cholesky(A, 'U', convert(BlasInt, info))
90✔
868
end
869
@deprecate cholesky!(A::Diagonal, ::Val{false}; check::Bool = true) cholesky!(A::Diagonal, NoPivot(); check) false
870
@deprecate cholesky(A::Diagonal, ::Val{false}; check::Bool = true) cholesky(A::Diagonal, NoPivot(); check) false
871

872
inv(C::Cholesky{<:Any,<:Diagonal}) = Diagonal(map(inv∘abs2, C.factors.diag))
1✔
873

874
cholcopy(A::Diagonal) = copymutable_oftype(A, choltype(A))
90✔
875
cholcopy(A::RealHermSymComplexHerm{<:Any,<:Diagonal}) = Diagonal(copy_similar(diag(A), choltype(A)))
2✔
876

877
function getproperty(C::Cholesky{<:Any,<:Diagonal}, d::Symbol)
103✔
878
    Cfactors = getfield(C, :factors)
103✔
879
    if d in (:U, :L, :UL)
103✔
880
        return Cfactors
90✔
881
    else
882
        return getfield(C, d)
13✔
883
    end
884
end
885

886
Base._sum(A::Diagonal, ::Colon) = sum(A.diag)
×
887
function Base._sum(A::Diagonal, dims::Integer)
×
888
    res = Base.reducedim_initarray(A, dims, zero(eltype(A)))
×
889
    if dims <= 2
×
890
        for i = 1:length(A.diag)
×
891
            @inbounds res[i] = A.diag[i]
×
892
        end
×
893
    else
894
        for i = 1:length(A.diag)
×
895
            @inbounds res[i,i] = A.diag[i]
×
896
        end
×
897
    end
898
    res
×
899
end
900

901
function logabsdet(A::Diagonal)
×
902
     mapreduce(x -> (log(abs(x)), sign(x)), ((d1, s1), (d2, s2)) -> (d1 + d2, s1 * s2),
×
903
               A.diag)
904
end
905

906
function Base.muladd(A::Diagonal, B::Diagonal, z::Diagonal)
1✔
907
    Diagonal(A.diag .* B.diag .+ z.diag)
1✔
908
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc