111 lines
3.5 KiB
Julia
111 lines
3.5 KiB
Julia
|
|
using Random
|
||
|
|
using LinearAlgebra
|
||
|
|
|
||
|
|
function genQf(n::Integer; fseed::Integer=0, rank::Real=1.1, conv::Real=1, ecc::Real=0.99, dom::Real=1, box::Real=1, q::Union{Vector, Nothing}=nothing, v::Union{Real, Nothing}=nothing)::Tuple{Matrix, Union{Vector, Nothing}, Union{Real, Nothing}}
|
||
|
|
if n <= 0
|
||
|
|
throw(ArgumentError(n, "n must be > 0"))
|
||
|
|
end
|
||
|
|
if rank <= 0
|
||
|
|
throw(ArgumentError(rank, "rank must be > 0"))
|
||
|
|
end
|
||
|
|
if !(0 <= conv <= 1)
|
||
|
|
throw(ArgumentError(conv, "conv must be in [0, 1]"))
|
||
|
|
end
|
||
|
|
if !(0 <= ecc < 1)
|
||
|
|
throw(ArgumentError(ecc, "ecc must be in [0, 1)"))
|
||
|
|
end
|
||
|
|
if dom < 0
|
||
|
|
throw(ArgumentError(dom, "dom must be >= 0"))
|
||
|
|
end
|
||
|
|
if box == 0
|
||
|
|
throw(ArgumentError(box, "box must not be 0"))
|
||
|
|
end
|
||
|
|
|
||
|
|
Random.seed!(fseed)
|
||
|
|
|
||
|
|
# generate Q- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||
|
|
# first step: generate the appropriate rank and positive / negative
|
||
|
|
# definiteness
|
||
|
|
|
||
|
|
r = round(Int, rank * n)
|
||
|
|
p = round(Int, r * conv)
|
||
|
|
|
||
|
|
if p > 0
|
||
|
|
G = rand(p, n)
|
||
|
|
Q = G' * G
|
||
|
|
else
|
||
|
|
Q = zeros(n, n)
|
||
|
|
end
|
||
|
|
|
||
|
|
if r > p
|
||
|
|
G = rand(r - p, n)
|
||
|
|
Q = Q - (G' * G)
|
||
|
|
end
|
||
|
|
|
||
|
|
# second step: if dom ~= 1, modify the diagonal
|
||
|
|
# increase or decrease randomly each element by [ - 1/3 , 1/3 ] of its
|
||
|
|
# initial value, then multiply it by dom
|
||
|
|
|
||
|
|
if dom != 1
|
||
|
|
D = diag(Q)
|
||
|
|
D = D .* (1 .+ (2 .* rand(n, 1) .- 1) ./ 3)
|
||
|
|
D = dom * D
|
||
|
|
@view(Q[diagind(Q)]) .= D
|
||
|
|
end
|
||
|
|
|
||
|
|
# compute eigenvalue decomposition
|
||
|
|
F = eigen(Q); # V * D * V' = Q , D( 1 , 1 ) = \lambda_n
|
||
|
|
V = F.vectors
|
||
|
|
D = F.values
|
||
|
|
|
||
|
|
if D[1] > 1e-14
|
||
|
|
# modify eccentricity only if \lambda_n > 0, for when \lambda_n = 0 the
|
||
|
|
# eccentricity is 1 by default
|
||
|
|
#
|
||
|
|
# the formula is:
|
||
|
|
#
|
||
|
|
# \lambda_i - \lambda_n 2 * ecc
|
||
|
|
# \lambda_i = \lambda_n + --------------------- * \lambda_n -------
|
||
|
|
# \lambda_1 - \lambda_n 1 - ecc
|
||
|
|
#
|
||
|
|
# This leaves \lambda_n unchanged, and modifies all the other ones
|
||
|
|
# proportionally so that
|
||
|
|
#
|
||
|
|
# \lambda_1 - \lambda_n
|
||
|
|
# --------------------- = ecc (exercise: check)
|
||
|
|
# \lambda_1 - \lambda_n
|
||
|
|
l = D[1] .* ones(n) + ((D[1] / (D[n] - D[1])) * (2 * ecc / (1 - ecc))) .* (D - D[1] .* ones(n))
|
||
|
|
Q = V * diagm(l) * V';
|
||
|
|
end
|
||
|
|
|
||
|
|
if q != nothing || v != nothing
|
||
|
|
# if so required generate q- - - - - - - - - - - - - - - - - - - - - - -
|
||
|
|
#
|
||
|
|
# we first generate the unconstrained minimum x_* of the problem in the
|
||
|
|
# box [ - abs( box ) , abs( box ) ] and then we set q = - Q * x_*
|
||
|
|
|
||
|
|
x = 2 * abs(box) .* rand(n) .- abs(box)
|
||
|
|
q = -Q * x
|
||
|
|
|
||
|
|
# if so required, we now randomly destroy the alignment between q and
|
||
|
|
# the image of Q so as to make it hard to solve Q x = q
|
||
|
|
|
||
|
|
if ( box < 0 ) && (D[1] <= 1e-14 )
|
||
|
|
q = q .* ((4/3) .* rand(n) .- (2/3))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
if v != nothing
|
||
|
|
# if so required compute v. - - - - - - - - - - - - - - - - - - - - -
|
||
|
|
# v is finite-valued only if either Q is strictly positive definite
|
||
|
|
# or it is positive semidefinite but q has been constructed in such
|
||
|
|
# a was that Q * x + q = 0 has a solution (that is the x we have)
|
||
|
|
if (D[1] > 1e-14) || ((D[1] > -1e-14) && (box > 0))
|
||
|
|
v = dot(q', x) / 2
|
||
|
|
else
|
||
|
|
v = -Inf
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return (Q, q, v)
|
||
|
|
end
|