Skip to content

Commit

Permalink
deprecate convert-to-construct fallback
Browse files Browse the repository at this point in the history
[ci skip]
  • Loading branch information
JeffBezanson committed Sep 8, 2017
1 parent 6b7c895 commit 8163010
Show file tree
Hide file tree
Showing 41 changed files with 378 additions and 394 deletions.
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Language changes
* The syntax for parametric methods, `function f{T}(x::T)`, has been
changed to `function f(x::T) where {T}` ([#11310]).

* The fallback constructor that calls `convert` is deprecated. Instead, new types should
prefer to define constructors, and add `convert` methods that call those constructors
only as necessary ([#15120]).

* The syntax `1.+2` is deprecated, since it is ambiguous: it could mean either
`1 .+ 2` (the current meaning) or `1. + 2` ([#19089]).

Expand Down
19 changes: 11 additions & 8 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ Supertype for `N`-dimensional arrays (or array-like types) with elements of type
"""
AbstractArray

convert(::Type{T}, a::T) where {T<:AbstractArray} = a
convert(::Type{T}, a::AbstractArray) where {T<:AbstractArray} = T(a)

if module_name(@__MODULE__) === :Base # avoid method overwrite
# catch undefined constructors before the deprecation kicks in
# TODO: remove when deprecation is removed
function (::Type{T})(arg) where {T<:AbstractArray}
throw(MethodError(T, (arg,)))
end
end

"""
size(A::AbstractArray, [dim...])
Expand Down Expand Up @@ -839,14 +850,6 @@ isempty(a::AbstractArray) = (_length(a) == 0)
# keys with an IndexStyle
keys(s::IndexStyle, A::AbstractArray, B::AbstractArray...) = eachindex(s, A, B...)

## Conversions ##

convert(::Type{AbstractArray{T,N}}, A::AbstractArray{T,N}) where {T,N } = A
convert(::Type{AbstractArray{T,N}}, A::AbstractArray{S,N}) where {T,S,N} = copy!(similar(A,T), A)
convert(::Type{AbstractArray{T}}, A::AbstractArray{S,N}) where {T,S,N} = convert(AbstractArray{T,N}, A)

convert(::Type{Array}, A::AbstractArray{T,N}) where {T,N} = convert(Array{T,N}, A)

"""
of_indices(x, y)
Expand Down
17 changes: 4 additions & 13 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -549,23 +549,14 @@ end
one(x::AbstractMatrix{T}) where {T} = _one(one(T), x)
oneunit(x::AbstractMatrix{T}) where {T} = _one(oneunit(T), x)

## Conversions ##

convert(::Type{Vector}, x::AbstractVector{T}) where {T} = convert(Vector{T}, x)
convert(::Type{Matrix}, x::AbstractMatrix{T}) where {T} = convert(Matrix{T}, x)

convert(::Type{Array{T}}, x::Array{T,n}) where {T,n} = x
convert(::Type{Array{T,n}}, x::Array{T,n}) where {T,n} = x

convert(::Type{Array{T}}, x::AbstractArray{S,n}) where {T,n,S} = convert(Array{T,n}, x)
convert(::Type{Array{T,n}}, x::AbstractArray{S,n}) where {T,n,S} = copy!(Array{T,n}(size(x)), x)

promote_rule(a::Type{Array{T,n}}, b::Type{Array{S,n}}) where {T,n,S} = el_same(promote_type(T,S), a, b)

# constructors should make copies
## Constructors ##

if module_name(@__MODULE__) === :Base # avoid method overwrite
(::Type{T})(x::T) where {T<:Array} = copy(x)
# constructors should make copies
Array{T,N}(x::AbstractArray{S,N}) where {T,N,S} = copy!(Array{T,N}(size(x)), x)
AbstractArray{T,N}(A::AbstractArray{S,N}) where {T,N,S} = copy!(similar(A,T), A)
end

## copying iterators to containers
Expand Down
23 changes: 8 additions & 15 deletions base/bitarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -497,21 +497,21 @@ function _bitreshape(B::BitArray, dims::NTuple{N,Int}) where N
return Br
end

## Conversions ##
## Constructors ##

convert(::Type{Array{T}}, B::BitArray{N}) where {T,N} = convert(Array{T,N}, B)
convert(::Type{Array{T,N}}, B::BitArray{N}) where {T,N} = _convert(Array{T,N}, B) # see #15801
function _convert(::Type{Array{T,N}}, B::BitArray{N}) where {T,N}
A = Array{T}(size(B))
#convert(::Type{Array{T}}, B::BitArray{N}) where {T,N} = convert(Array{T,N}, B)
#convert(::Type{Array{T,N}}, B::BitArray{N}) where {T,N} = _convert(Array{T,N}, B) # see #15801
function Array{T,N}(B::BitArray{N}) where {T,N}
A = Array{T,N}(size(B))
Bc = B.chunks
@inbounds for i = 1:length(A)
A[i] = unsafe_bitgetindex(Bc, i)
end
return A
end

convert(::Type{BitArray}, A::AbstractArray{T,N}) where {T,N} = convert(BitArray{N}, A)
function convert(::Type{BitArray{N}}, A::AbstractArray{T,N}) where N where T
BitArray(A::AbstractArray{<:Any,N}) where {N} = BitArray{N}(A)
function BitArray{N}(A::AbstractArray{T,N}) where N where T
B = BitArray(size(A))
Bc = B.chunks
l = length(B)
Expand All @@ -536,7 +536,7 @@ function convert(::Type{BitArray{N}}, A::AbstractArray{T,N}) where N where T
return B
end

function convert(::Type{BitArray{N}}, A::Array{Bool,N}) where N
function BitArray{N}(A::Array{Bool,N}) where N
B = BitArray(size(A))
Bc = B.chunks
l = length(B)
Expand All @@ -545,16 +545,9 @@ function convert(::Type{BitArray{N}}, A::Array{Bool,N}) where N
return B
end

convert(::Type{BitArray{N}}, B::BitArray{N}) where {N} = B
convert(::Type{AbstractArray{T,N}}, B::BitArray{N}) where {T,N} = convert(Array{T,N}, B)

reinterpret(::Type{Bool}, B::BitArray, dims::NTuple{N,Int}) where {N} = reinterpret(B, dims)
reinterpret(B::BitArray, dims::NTuple{N,Int}) where {N} = reshape(B, dims)

## Constructors from generic iterables ##

BitArray(A::AbstractArray{<:Any,N}) where {N} = convert(BitArray{N}, A)

if module_name(@__MODULE__) === :Base # avoid method overwrite
(::Type{T})(x::T) where {T<:BitArray} = copy(x)
end
Expand Down
8 changes: 8 additions & 0 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,13 @@ Array{T}(m::Int, n::Int, o::Int) where {T} = Array{T,3}(m, n, o)

Array{T,1}() where {T} = Array{T,1}(0)

(::Type{Array{T,N} where T})(x::AbstractArray{S,N}) where {S,N} = Array{S,N}(x)

Array(A::AbstractArray{T,N}) where {T,N} = Array{T,N}(A)
Array{T}(A::AbstractArray{S,N}) where {T,N,S} = Array{T,N}(A)

AbstractArray{T}(A::AbstractArray{S,N}) where {T,S,N} = AbstractArray{T,N}(A)

# primitive Symbol constructors
function Symbol(s::String)
return ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int),
Expand Down Expand Up @@ -604,6 +611,7 @@ UInt128(x::BuiltinInts) = toUInt128(x)::UInt128
UInt32(x::Char) = bitcast(UInt32, x)
Char(x::UInt32) = bitcast(Char, x)
Char(x::Number) = Char(UInt32(x))
Char(x::Char) = x
(::Type{T})(x::Char) where {T<:Number} = T(UInt32(x))

(::Type{T})(x::T) where {T<:Number} = x
Expand Down
7 changes: 6 additions & 1 deletion base/dates/periods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,13 @@ function divexact(x, y)
return q
end

# TODO: this is needed to prevent undefined Period constructors from
# hitting the deprecated construct-to-convert fallback.
(::Type{T})(p::Period) where {T<:Period} = convert(T, p)::T

# FixedPeriod conversions and promotion rules
const fixedperiod_conversions = [(Week, 7), (Day, 24), (Hour, 60), (Minute, 60), (Second, 1000), (Millisecond, 1000), (Microsecond, 1000), (Nanosecond, 1)]
const fixedperiod_conversions = [(:Week, 7), (:Day, 24), (:Hour, 60), (:Minute, 60), (:Second, 1000),
(:Millisecond, 1000), (:Microsecond, 1000), (:Nanosecond, 1)]
for i = 1:length(fixedperiod_conversions)
T, n = fixedperiod_conversions[i]
N = Int64(1)
Expand Down
9 changes: 9 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,15 @@ import .Iterators.enumerate
@deprecate enumerate(i::IndexLinear, A::AbstractArray) pairs(i, A)
@deprecate enumerate(i::IndexCartesian, A::AbstractArray) pairs(i, A)

function (::Type{T})(arg) where {T}
if applicable(convert, T, arg)
# if `convert` call would not work, just let the method error happen
depwarn("Constructors no longer fall back to `convert`. A constructor `$T(::$(typeof(arg)))` should be defined instead.", :Type)
end
convert(T, arg)::T
end
# related items to remove in: abstractarray.jl, dates/periods.jl

# END 0.7 deprecations

# BEGIN 1.0 deprecations
Expand Down
1 change: 1 addition & 0 deletions base/libgit2/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct GitHash <: AbstractGitHash
GitHash(val::NTuple{OID_RAWSZ, UInt8}) = new(val)
end
GitHash() = GitHash(ntuple(i->zero(UInt8), OID_RAWSZ))
GitHash(h::GitHash) = h

"""
GitShortHash(hash::GitHash, len::Integer)
Expand Down
20 changes: 11 additions & 9 deletions base/linalg/bidiag.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ function Bidiagonal(A::AbstractMatrix, uplo::Symbol)
Bidiagonal(diag(A, 0), diag(A, uplo == :U ? 1 : -1), uplo)
end

Bidiagonal(A::Bidiagonal) = A

function getindex(A::Bidiagonal{T}, i::Integer, j::Integer) where T
if !((1 <= i <= size(A,2)) && (1 <= j <= size(A,2)))
throw(BoundsError(A,(i,j)))
Expand Down Expand Up @@ -131,7 +133,7 @@ function Base.replace_in_print_matrix(A::Bidiagonal,i::Integer,j::Integer,s::Abs
end

#Converting from Bidiagonal to dense Matrix
function convert(::Type{Matrix{T}}, A::Bidiagonal) where T
function Matrix{T}(A::Bidiagonal) where T
n = size(A, 1)
B = zeros(T, n, n)
for i = 1:n - 1
Expand All @@ -145,14 +147,14 @@ function convert(::Type{Matrix{T}}, A::Bidiagonal) where T
B[n,n] = A.dv[n]
return B
end
convert(::Type{Matrix}, A::Bidiagonal{T}) where {T} = convert(Matrix{T}, A)
convert(::Type{Array}, A::Bidiagonal) = convert(Matrix, A)
full(A::Bidiagonal) = convert(Array, A)
Matrix(A::Bidiagonal{T}) where {T} = Matrix{T}(A)
Array(A::Bidiagonal) = Matrix(A)
full(A::Bidiagonal) = Array(A)
promote_rule(::Type{Matrix{T}}, ::Type{<:Bidiagonal{S}}) where {T,S} = Matrix{promote_type(T,S)}

#Converting from Bidiagonal to Tridiagonal
Tridiagonal(M::Bidiagonal{T}) where {T} = convert(Tridiagonal{T}, M)
function convert(::Type{Tridiagonal{T}}, A::Bidiagonal) where T
Tridiagonal(M::Bidiagonal{T}) where {T} = Tridiagonal{T}(M)
function Tridiagonal{T}(A::Bidiagonal) where T
dv = convert(AbstractVector{T}, A.dv)
ev = convert(AbstractVector{T}, A.ev)
z = fill!(similar(ev), zero(T))
Expand All @@ -161,12 +163,12 @@ end
promote_rule(::Type{<:Tridiagonal{T}}, ::Type{<:Bidiagonal{S}}) where {T,S} = Tridiagonal{promote_type(T,S)}

# No-op for trivial conversion Bidiagonal{T} -> Bidiagonal{T}
convert(::Type{Bidiagonal{T}}, A::Bidiagonal{T}) where {T} = A
Bidiagonal{T}(A::Bidiagonal{T}) where {T} = A
# Convert Bidiagonal to Bidiagonal{T} by constructing a new instance with converted elements
convert(::Type{Bidiagonal{T}}, A::Bidiagonal) where {T} =
Bidiagonal{T}(A::Bidiagonal) where {T} =
Bidiagonal(convert(AbstractVector{T}, A.dv), convert(AbstractVector{T}, A.ev), A.uplo)
# When asked to convert Bidiagonal to AbstractMatrix{T}, preserve structure by converting to Bidiagonal{T} <: AbstractMatrix{T}
convert(::Type{AbstractMatrix{T}}, A::Bidiagonal) where {T} = convert(Bidiagonal{T}, A)
AbstractMatrix{T}(A::Bidiagonal) where {T} = convert(Bidiagonal{T}, A)

broadcast(::typeof(big), B::Bidiagonal) = Bidiagonal(big.(B.dv), big.(B.ev), B.uplo)

Expand Down
34 changes: 17 additions & 17 deletions base/linalg/cholesky.jl
Original file line number Diff line number Diff line change
Expand Up @@ -345,32 +345,32 @@ function cholfact(x::Number, uplo::Symbol=:U)
end


function convert(::Type{Cholesky{T}}, C::Cholesky) where T
function Cholesky{T}(C::Cholesky) where T
Cnew = convert(AbstractMatrix{T}, C.factors)
Cholesky{T, typeof(Cnew)}(Cnew, C.uplo, C.info)
end
convert(::Type{Factorization{T}}, C::Cholesky{T}) where {T} = C
convert(::Type{Factorization{T}}, C::Cholesky) where {T} = convert(Cholesky{T}, C)
convert(::Type{CholeskyPivoted{T}},C::CholeskyPivoted{T}) where {T} = C
convert(::Type{CholeskyPivoted{T}},C::CholeskyPivoted) where {T} =
Factorization{T}(C::Cholesky{T}) where {T} = C
Factorization{T}(C::Cholesky) where {T} = Cholesky{T}(C)
CholeskyPivoted{T}(C::CholeskyPivoted{T}) where {T} = C
CholeskyPivoted{T}(C::CholeskyPivoted) where {T} =
CholeskyPivoted(AbstractMatrix{T}(C.factors),C.uplo,C.piv,C.rank,C.tol,C.info)
convert(::Type{Factorization{T}}, C::CholeskyPivoted{T}) where {T} = C
convert(::Type{Factorization{T}}, C::CholeskyPivoted) where {T} = convert(CholeskyPivoted{T}, C)
Factorization{T}(C::CholeskyPivoted{T}) where {T} = C
Factorization{T}(C::CholeskyPivoted) where {T} = CholeskyPivoted{T}(C)

convert(::Type{AbstractMatrix}, C::Cholesky) = C.uplo == 'U' ? C[:U]'C[:U] : C[:L]*C[:L]'
convert(::Type{AbstractArray}, C::Cholesky) = convert(AbstractMatrix, C)
convert(::Type{Matrix}, C::Cholesky) = convert(Array, convert(AbstractArray, C))
convert(::Type{Array}, C::Cholesky) = convert(Matrix, C)
full(C::Cholesky) = convert(AbstractArray, C)
AbstractMatrix(C::Cholesky) = C.uplo == 'U' ? C[:U]'C[:U] : C[:L]*C[:L]'
AbstractArray(C::Cholesky) = AbstractMatrix(C)
Matrix(C::Cholesky) = Array(AbstractArray(C))
Array(C::Cholesky) = Matrix(C)
full(C::Cholesky) = AbstractArray(C)

function convert(::Type{AbstractMatrix}, F::CholeskyPivoted)
function AbstractMatrix(F::CholeskyPivoted)
ip = invperm(F[:p])
(F[:L] * F[:U])[ip,ip]
end
convert(::Type{AbstractArray}, F::CholeskyPivoted) = convert(AbstractMatrix, F)
convert(::Type{Matrix}, F::CholeskyPivoted) = convert(Array, convert(AbstractArray, F))
convert(::Type{Array}, F::CholeskyPivoted) = convert(Matrix, F)
full(F::CholeskyPivoted) = convert(AbstractArray, F)
AbstractArray(F::CholeskyPivoted) = AbstractMatrix(F)
Matrix(F::CholeskyPivoted) = Array(AbstractArray(F))
Array(F::CholeskyPivoted) = Matrix(F)
full(F::CholeskyPivoted) = AbstractArray(F)

copy(C::Cholesky) = Cholesky(copy(C.factors), C.uplo, C.info)
copy(C::CholeskyPivoted) = CholeskyPivoted(copy(C.factors), C.uplo, C.piv, C.rank, C.tol, C.info)
Expand Down
12 changes: 6 additions & 6 deletions base/linalg/diagonal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ Diagonal(V::AbstractVector{T}) where {T} = Diagonal{T,typeof(V)}(V)
Diagonal{T}(V::AbstractVector{T}) where {T} = Diagonal{T,typeof(V)}(V)
Diagonal{T}(V::AbstractVector) where {T} = Diagonal{T}(convert(AbstractVector{T}, V))

convert(::Type{Diagonal{T}}, D::Diagonal{T}) where {T} = D
convert(::Type{Diagonal{T}}, D::Diagonal) where {T} = Diagonal{T}(convert(AbstractVector{T}, D.diag))
convert(::Type{AbstractMatrix{T}}, D::Diagonal) where {T} = convert(Diagonal{T}, D)
convert(::Type{Matrix}, D::Diagonal) = diagm(D.diag)
convert(::Type{Array}, D::Diagonal) = convert(Matrix, D)
full(D::Diagonal) = convert(Array, D)
Diagonal{T}(D::Diagonal{T}) where {T} = D
Diagonal{T}(D::Diagonal) where {T} = Diagonal{T}(convert(AbstractVector{T}, D.diag))
AbstractMatrix{T}(D::Diagonal) where {T} = Diagonal{T}(D)
Matrix(D::Diagonal) = diagm(D.diag)
Array(D::Diagonal) = Matrix(D)
full(D::Diagonal) = Array(D)

function similar(D::Diagonal, ::Type{T}) where T
return Diagonal{T}(similar(D.diag, T))
Expand Down
10 changes: 5 additions & 5 deletions base/linalg/eigen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,8 @@ eigvecs(A::AbstractMatrix, B::AbstractMatrix) = eigvecs(eigfact(A, B))
# Conversion methods

## Can we determine the source/result is Real? This is not stored in the type Eigen
convert(::Type{AbstractMatrix}, F::Eigen) = F.vectors * Diagonal(F.values) / F.vectors
convert(::Type{AbstractArray}, F::Eigen) = convert(AbstractMatrix, F)
convert(::Type{Matrix}, F::Eigen) = convert(Array, convert(AbstractArray, F))
convert(::Type{Array}, F::Eigen) = convert(Matrix, F)
full(F::Eigen) = convert(AbstractArray, F)
AbstractMatrix(F::Eigen) = F.vectors * Diagonal(F.values) / F.vectors
AbstractArray(F::Eigen) = AbstractMatrix(F)
Matrix(F::Eigen) = Array(AbstractArray(F))
Array(F::Eigen) = Matrix(F)
full(F::Eigen) = AbstractArray(F)
5 changes: 4 additions & 1 deletion base/linalg/factorization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ function det(F::Factorization)
return exp(d)*s
end

convert(::Type{T}, f::T) where {T<:Factorization} = f
convert(::Type{T}, f::Factorization) where {T<:Factorization} = T(f)

### General promotion rules
convert(::Type{Factorization{T}}, F::Factorization{T}) where {T} = F
Factorization{T}(F::Factorization{T}) where {T} = F
inv(F::Factorization{T}) where {T} = A_ldiv_B!(F, eye(T, size(F,1)))

Base.hash(F::Factorization, h::UInt) = mapreduce(f -> hash(getfield(F, f)), hash, h, 1:nfields(F))
Expand Down
12 changes: 6 additions & 6 deletions base/linalg/givens.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ mutable struct Rotation{T} <: AbstractRotation{T}
rotations::Vector{Givens{T}}
end

convert(::Type{Givens{T}}, G::Givens{T}) where {T} = G
convert(::Type{Givens{T}}, G::Givens) where {T} = Givens(G.i1, G.i2, convert(T, G.c), convert(T, G.s))
convert(::Type{Rotation{T}}, R::Rotation{T}) where {T} = R
convert(::Type{Rotation{T}}, R::Rotation) where {T} = Rotation{T}([convert(Givens{T}, g) for g in R.rotations])
convert(::Type{AbstractRotation{T}}, G::Givens) where {T} = convert(Givens{T}, G)
convert(::Type{AbstractRotation{T}}, R::Rotation) where {T} = convert(Rotation{T}, R)
Givens{T}(G::Givens{T}) where {T} = G
Givens{T}(G::Givens) where {T} = Givens(G.i1, G.i2, convert(T, G.c), convert(T, G.s))
Rotation{T}(R::Rotation{T}) where {T} = R
Rotation{T}(R::Rotation) where {T} = Rotation{T}([Givens{T}(g) for g in R.rotations])
AbstractRotation{T}(G::Givens) where {T} = Givens{T}(G)
AbstractRotation{T}(R::Rotation) where {T} = Rotation{T}(R)

adjoint(G::Givens) = Givens(G.i1, G.i2, conj(G.c), -G.s)
adjoint(R::Rotation{T}) where {T} = Rotation{T}(reverse!([adjoint(r) for r in R.rotations]))
Expand Down
16 changes: 8 additions & 8 deletions base/linalg/hessenberg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ function getindex(A::HessenbergQ, i::Integer, j::Integer)
end

## reconstruct the original matrix
convert(::Type{Matrix}, A::HessenbergQ{<:BlasFloat}) = LAPACK.orghr!(1, size(A.factors, 1), copy(A.factors), A.τ)
convert(::Type{Array}, A::HessenbergQ) = convert(Matrix, A)
full(A::HessenbergQ) = convert(Array, A)
convert(::Type{AbstractMatrix}, F::Hessenberg) = (fq = Array(F[:Q]); (fq * F[:H]) * fq')
convert(::Type{AbstractArray}, F::Hessenberg) = convert(AbstractMatrix, F)
convert(::Type{Matrix}, F::Hessenberg) = convert(Array, convert(AbstractArray, F))
convert(::Type{Array}, F::Hessenberg) = convert(Matrix, F)
full(F::Hessenberg) = convert(AbstractArray, F)
Matrix(A::HessenbergQ{<:BlasFloat}) = LAPACK.orghr!(1, size(A.factors, 1), copy(A.factors), A.τ)
Array(A::HessenbergQ) = Matrix(A)
full(A::HessenbergQ) = Array(A)
AbstractMatrix(F::Hessenberg) = (fq = Array(F[:Q]); (fq * F[:H]) * fq')
AbstractArray(F::Hessenberg) = AbstractMatrix(F)
Matrix(F::Hessenberg) = Array(AbstractArray(F))
Array(F::Hessenberg) = Matrix(F)
full(F::Hessenberg) = AbstractArray(F)

A_mul_B!(Q::HessenbergQ{T}, X::StridedVecOrMat{T}) where {T<:BlasFloat} =
LAPACK.ormhr!('L', 'N', 1, size(Q.factors, 1), Q.factors, Q.τ, X)
Expand Down
Loading

0 comments on commit 8163010

Please sign in to comment.