Skip to content

Commit 148d4d9

Browse files
Sébastien LoiselSébastien Loisel
authored andcommitted
Code-quality audit: fix three bugs, tighten types, deduplicate
Three real bugs, each a case of drifted duplication: - spectral1d chebfun's x < -1 branch was a stale copy of evaluation's, with a length-n sign vector where n+1 was needed, so left extrapolation through interpolate() threw DimensionMismatch while right extrapolation worked. - The spectral1d plot method splatted its keyword arguments positionally (missing semicolon), so plot(geom, z; color="red") sent Pair objects to matplotlib as positional arguments. Its spectral2d sibling was correct. - A stale "export plot" survived in AlgebraicMultiGridBarrier.jl after the plotting extension moved plot onto PyPlot.plot; it left plot in names(MultiGridBarrier) as an UndefVarError trap without PyPlot. Error handling. The broad catch in the feasibility probe and the line searches is deliberate and stays: a barrier has no fixed protocol for signalling domain escape (DomainError from log, non-finite values, InexactError, complex results, division by zero). Both now rethrow InterruptException so Ctrl-C is not swallowed and retried through a thousand step halvings, and the comments record the rationale. Correctness-bearing @asserts, which may be compiled out at higher optimization levels, become equally terse `cond || error(...)` one-liners. throw(e) becomes rethrow() so backtraces keep their origin. Dead defensive branches are removed: a tautological L_x <= L_max guard, two BlockMatrices guards that would have silently truncated a scan (replaced by one upfront DimensionMismatch check), and an unreachable CUDA early return. Duplicate validations spanning two layers of one call chain now have a single owner. Types. Barrier gains type parameters instead of three abstract ::Function fields. barrier(Q)'s gradient and Hessian seeded their accumulators with a wrong-typed sentinel (an Int, and a dead store) making them Union-typed; the first term is hoisted instead. mgb_step's Newton closures captured a variable that eta reassigns, boxing it and untyping every objective, gradient and Hessian call in the innermost loop; they now capture a snapshot. MGBSOL, MGBConvergenceFailure, the Zoo D-table containers and the progress-bar locals are typed. The CUDA AssemblyPlan and CuDSSCacheEntry are parameterized on concrete array types (CuArray{T,N} is a UnionAll under CUDA.jl 5), with a function barrier where the plan leaves its cache. In the JuMP extension settag becomes a closed union and geometry is typed; objexpr must stay Any because MGBExpr and MGBModel are mutually recursive. Defaults. newton's stopping_criterion and line_search sentinels become real keyword defaults (T is in scope, as it is everywhere else in the package). The Clenshaw-Curtis weights turned out to be a plain trigonometric sum rather than FFT acrobatics, so _tf_nodes and _tf_weights are now T-generic via cospi instead of computing in Float64 and converting; the Gmsh extension's _cheb is kept bit-matched. Waste and duplication. _tf_hierarchy computed a sparse triple product and threw it away whenever amg_input was supplied, which both call sites always do. convex_linear sampled A (a device-to-host copy on GPU) even when A_grid was given. The Euclidian-power cobarrier gradient inlined twenty lines of scatter logic that already existed as a helper, and that its Hessian sibling reused. The three PiecewiseCobarrier structs were byte-clones of the barrier trio, which the callables never distinguish. The Zoo problems shared forty-five lines of state setup across two pairs of files, now _vector_state_setup and _scalar_fg. fem2d_P2's continuous() reimplemented three helpers with a drifted tolerance and mutated its input; it delegates now, verified bit-identical against the old implementation. The AMG ladder scaffold, pasted in three FEM families, is _assemble_amg_ladder. blockdiag-of-n-copies splats become kron; one such splat was an obfuscated identity multiply. plot3d's three plotter epilogues and the 2D/3D animation timelines share helpers. CUDA's Vector{T}(Array(x)) double copies drop the outer constructor where the eltypes already match (the Int32-to-Int sites are genuine conversions and stay). Slop. The GPU AssemblyPlan uploaded col_indices and c_counts to the device and never read them, holding Int32 matrices in VRAM for the whole solve. Dead identifiers go: a "hi" string placeholder for a Matrix, an unused VTK_LINE, an unused KindedVariable field that was the struct's only reason to be parametric, unused hierarchy parameters, unused imports, a stale commented-out formula. Comments that narrated a past diff for a reviewer are rewritten to describe the code as it stands. Tests. New regression tests cover spectral left extrapolation and the plot keyword forwarding. Full Pkg.test passes, including the Zoo reference values, the JuMP and Gmsh suites, and the PyVista plotting tests. Held back pending discussion: the mesh constructors' unused rest... kwarg slurp, the objectid-keyed plan caches, the RR2 component-selection matrices, and a purely stylistic @eval rewrite of the CUDA map_rows arities.
1 parent 6c19afc commit 148d4d9

32 files changed

Lines changed: 577 additions & 815 deletions

ext/MultiGridBarrierCUDAExt/block_ops.jl

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,9 @@ function _make_assembly_plan(R::CuSparseMatrixCSR{T, Ti}, H::CuBlockHessian{T})
328328
out_colval = CuVector{Ti}(undef, 0)
329329
scatter_idx = Dict{Tuple{Int,Int}, CuArray{Int32, 3}}()
330330
panels_gpu = [CuArray{T}(panels_cpu[k]) for k in 1:nu]
331-
col_indices_gpu = [CuArray{Ti}(col_indices_cpu[k]) for k in 1:nu]
332-
c_counts_gpu = [CuVector{Int32}(c_counts_cpu[k]) for k in 1:nu]
333331
return AssemblyPlan{T, Ti}(
334332
out_rowptr, out_colval, ncols_R, ncols_R, out_nnz,
335-
panels_gpu, col_indices_gpu, c_counts_gpu,
336-
scatter_idx, p, N, nu, c_max)
333+
panels_gpu, scatter_idx, p, N, nu, c_max)
337334
end
338335

339336
indicator = sparse(out_rows, out_cols, ones(Float32, length(out_rows)), ncols_R, ncols_R)
@@ -379,14 +376,11 @@ function _make_assembly_plan(R::CuSparseMatrixCSR{T, Ti}, H::CuBlockHessian{T})
379376
end
380377

381378
panels_gpu = [CuArray{T}(panels_cpu[k]) for k in 1:nu]
382-
col_indices_gpu = [CuArray{Ti}(col_indices_cpu[k]) for k in 1:nu]
383-
c_counts_gpu = [CuVector{Int32}(c_counts_cpu[k]) for k in 1:nu]
384379

385380
AssemblyPlan{T, Ti}(
386381
CuVector{Ti}(out_rowptr_cpu), CuVector{Ti}(out_colval_cpu),
387382
ncols_R, ncols_R, out_nnz,
388-
panels_gpu, col_indices_gpu, c_counts_gpu,
389-
scatter_idx, p, N, nu, c_max)
383+
panels_gpu, scatter_idx, p, N, nu, c_max)
390384
end
391385

392386
function _get_assembly_plan(R::CuSparseMatrixCSR{T, Ti}, H::CuBlockHessian{T}) where {T, Ti}
@@ -399,8 +393,12 @@ function _get_assembly_plan(R::CuSparseMatrixCSR{T, Ti}, H::CuBlockHessian{T}) w
399393
end
400394

401395
function _assemble_RtHR(R::CuSparseMatrixCSR{T, Ti}, H::CuBlockHessian{T}) where {T, Ti}
402-
plan = _get_assembly_plan(R, H)
396+
# Function barrier: the cache hands back an abstractly-typed plan; dispatch
397+
# on the concrete AssemblyPlan specializes the assembly body.
398+
_assemble_RtHR_impl(_get_assembly_plan(R, H), H)
399+
end
403400

401+
function _assemble_RtHR_impl(plan::AssemblyPlan{T, Ti}, H::CuBlockHessian{T}) where {T, Ti}
404402
if plan.out_nnz == 0
405403
return CuSparseMatrixCSR{T}(plan.out_rowptr, plan.out_colval,
406404
CuVector{T}(undef, 0), (plan.out_m, plan.out_n))
@@ -424,10 +422,8 @@ function _assemble_RtHR(R::CuSparseMatrixCSR{T, Ti}, H::CuBlockHessian{T}) where
424422
break
425423
end
426424
end
427-
if first_blk === nothing
428-
return CuSparseMatrixCSR{T}(plan.out_rowptr, plan.out_colval, output_nzval,
429-
(plan.out_m, plan.out_n))
430-
end
425+
first_blk === nothing &&
426+
error("internal: assembly plan is non-empty but H has no blocks")
431427

432428
gemm_kern = @cuda launch=false _batched_gemm_kernel!(
433429
tmp, first_blk.data, plan.panels[1], Val(false),

ext/MultiGridBarrierCUDAExt/block_types.jl

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,34 @@ Cached plan for computing R' * H * R via element-wise assembly.
3131
Precomputes R panels (dense sub-blocks of R per element), output CSR pattern,
3232
and scatter maps. R is fixed across Newton iterations; only H changes.
3333
"""
34-
struct AssemblyPlan{T, Ti}
34+
struct AssemblyPlan{T, Ti, VTi<:AbstractVector{Ti}, P3<:AbstractArray{T,3}, S3<:AbstractArray{Int32,3}}
3535
# Output CSR structure
36-
out_rowptr::CuVector{Ti}
37-
out_colval::CuVector{Ti}
36+
out_rowptr::VTi
37+
out_colval::VTi
3838
out_m::Int # output rows = ncols(R)
3939
out_n::Int # output cols = ncols(R)
4040
out_nnz::Int
4141

4242
# Per block k: dense panels of R, shape (p, c_max_k, N)
43-
panels::Vector{CuArray{T, 3}}
44-
45-
# Per block k: column indices, shape (c_max_k, N)
46-
col_indices::Vector{CuArray{Ti, 2}}
47-
48-
# Per block k: actual column count per element, shape (N,)
49-
c_counts::Vector{CuVector{Int32}}
43+
panels::Vector{P3}
5044

5145
# Per block pair (i,j): scatter map, shape (c_max_i, c_max_j, N)
52-
scatter_idx::Dict{Tuple{Int,Int}, CuArray{Int32, 3}}
46+
scatter_idx::Dict{Tuple{Int,Int}, S3}
5347

5448
# Block partitioning
5549
p::Int # element block size
5650
N::Int # number of elements
5751
nu::Int # number of block groups
5852
c_max::Vector{Int} # max columns per block
5953
end
54+
55+
# Infer the concrete array parameters from the construction-time arguments
56+
# (CuArray types are UnionAlls under CUDA.jl 5, so spelled-out field types
57+
# would leave every field read dynamically dispatched).
58+
AssemblyPlan{T, Ti}(out_rowptr::VTi, out_colval::VTi, out_m::Int, out_n::Int,
59+
out_nnz::Int, panels::Vector{P3},
60+
scatter_idx::Dict{Tuple{Int,Int}, S3},
61+
p::Int, N::Int, nu::Int, c_max::Vector{Int}) where
62+
{T, Ti, VTi<:AbstractVector{Ti}, P3<:AbstractArray{T,3}, S3<:AbstractArray{Int32,3}} =
63+
AssemblyPlan{T, Ti, VTi, P3, S3}(out_rowptr, out_colval, out_m, out_n,
64+
out_nnz, panels, scatter_idx, p, N, nu, c_max)

ext/MultiGridBarrierCUDAExt/conversion.jl

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
# conversion.jl -- native_to_cuda / cuda_to_native type conversion
22

33
using CUDA.CUSPARSE: CuSparseMatrixCSR
4-
using StaticArrays: SVector
5-
import MultiGridBarrier: MGBSOL, Geometry, MultiGrid, FEM1D, FEM2D_P1, FEM2D_P2, FEM3D,
6-
Convex, map_rows, AMG, MGBProblem,
7-
native_to_device, device_to_native, CPUDevice, CUDADevice
4+
import MultiGridBarrier: MGBSOL, Geometry, Convex, AMG, MGBProblem,
5+
native_to_device, device_to_native, CUDADevice
86

97
# Device-agnostic CuSparseMatrixCSR → SparseMatrixCSC conversion.
108
function _cusparse_to_cpu(A::CuSparseMatrixCSR{T,Ti}) where {T,Ti}
119
m, n = size(A)
1210
CUDA.synchronize()
13-
rp = Vector{Ti}(Array(A.rowPtr))
14-
cv = Vector{Ti}(Array(A.colVal))
15-
nz = Vector{T}(Array(A.nzVal))
11+
rp = Array(A.rowPtr)
12+
cv = Array(A.colVal)
13+
nz = Array(A.nzVal)
1614
if length(nz) == 0
1715
return spzeros(T, m, n)
1816
end
1917
# CSR of A ≡ CSC of Aᵀ
2018
At_csc = SparseMatrixCSC{T,Ti}(n, m, rp, cv, nz)
21-
return SparseMatrixCSC{T,Ti}(sparse(At_csc'))
19+
return sparse(At_csc')
2220
end
2321

2422
# ============================================================================
@@ -84,12 +82,12 @@ end
8482
function MultiGridBarrier.cuda_to_native(g::Geometry{T, <:CuArray{T,3}, <:CuVector{T},
8583
<:CuMatrix{T},
8684
Discretization}) where {T, Discretization}
87-
x_native = Array{T,3}(Array(g.x))
88-
w_native = Vector{T}(Array(g.w))
85+
x_native = Array(g.x)
86+
w_native = Array(g.w)
8987

9088
operators_native = Dict{Symbol, Matrix{T}}()
9189
for key in sort(collect(keys(g.operators)))
92-
operators_native[key] = Matrix{T}(Array(g.operators[key]))
90+
operators_native[key] = Array(g.operators[key])
9391
end
9492

9593
Geometry{T, Array{T,3}, Vector{T}, Matrix{T}, Discretization}(
@@ -99,8 +97,8 @@ end
9997
# Structured FEM Geometry (block ops).
10098
function MultiGridBarrier.cuda_to_native(g::Geometry{T, <:CuArray{T,3}, <:CuVector{T},
10199
<:Any, Discretization}) where {T, Discretization}
102-
x_native = Array{T,3}(Array(g.x))
103-
w_native = Vector{T}(Array(g.w))
100+
x_native = Array(g.x)
101+
w_native = Array(g.w)
104102

105103
Ti = Int
106104
convert_to_native = function(op)
@@ -208,8 +206,8 @@ MultiGridBarrier.native_to_cuda(prob::MGBProblem{T}) where {T} =
208206
native_to_cuda(prob.geometry))
209207

210208
# MGBSOL cuda → native.
211-
_convert_cuda_to_native(x::CuMatrix) = Matrix(Array(x))
212-
_convert_cuda_to_native(x::CuVector) = Vector(Array(x))
209+
_convert_cuda_to_native(x::CuMatrix) = Array(x)
210+
_convert_cuda_to_native(x::CuVector) = Array(x)
213211
_convert_cuda_to_native(x::CuSparseMatrixCSR) = _cusparse_to_cpu(x)
214212
_convert_cuda_to_native(x) = x
215213

ext/MultiGridBarrierCUDAExt/cudss_solver.jl

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -160,21 +160,28 @@ Cached cuDSS state: handles, descriptors, and persistent GPU buffers.
160160
The cuDSS descriptors hold pointers into the persistent buffers, so the
161161
buffers must stay alive as long as the cache entry exists.
162162
"""
163-
mutable struct CuDSSCacheEntry{T}
163+
mutable struct CuDSSCacheEntry{T, Vi<:AbstractVector{Int32}, Vt<:AbstractVector{T}}
164164
handle::cudssHandle_t
165165
config::cudssConfig_t
166166
data::cudssData_t
167167
matrix_desc::cudssMatrix_t
168168
solution_desc::cudssMatrix_t
169169
rhs_desc::cudssMatrix_t
170-
# Persistent GPU buffers (cuDSS descriptors point into these)
171-
rowPtr_0::CuVector{Int32}
172-
colVal_0::CuVector{Int32}
173-
nzVal_buf::CuVector{T}
174-
x_buf::CuVector{T}
175-
rhs_buf::CuVector{T}
170+
# Persistent GPU buffers (cuDSS descriptors point into these); concrete
171+
# array parameters because CuVector{...} is a UnionAll under CUDA.jl 5.
172+
rowPtr_0::Vi
173+
colVal_0::Vi
174+
nzVal_buf::Vt
175+
x_buf::Vt
176+
rhs_buf::Vt
176177
end
177178

179+
CuDSSCacheEntry{T}(handle, config, data, matrix_desc, solution_desc, rhs_desc,
180+
rowPtr_0::Vi, colVal_0::Vi, nzVal_buf::Vt, x_buf::Vt,
181+
rhs_buf::Vt) where {T, Vi<:AbstractVector{Int32}, Vt<:AbstractVector{T}} =
182+
CuDSSCacheEntry{T, Vi, Vt}(handle, config, data, matrix_desc, solution_desc,
183+
rhs_desc, rowPtr_0, colVal_0, nzVal_buf, x_buf, rhs_buf)
184+
178185
const _cudss_cache = Dict{CuDSSCacheKey, Any}()
179186

180187
"""

ext/MultiGridBarrierCUDAExt/mgb_interface.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ end
5252
# map_rows: CPU fallback for arbitrary closures, reached via the map_rows_gpu
5353
# fallback below. Transfers to CPU, runs default map_rows, transfers back.
5454
function MultiGridBarrier.map_rows(f, A::CuMatrix{T}, rest::CuMatrix...) where T
55-
A_cpu = Matrix{T}(Array(A))
56-
rest_cpu = map(m -> Matrix{T}(Array(m)), rest)
55+
A_cpu = Array(A)
56+
rest_cpu = map(Array, rest)
5757
result_cpu = MultiGridBarrier.map_rows(f, A_cpu, rest_cpu...)
5858
if result_cpu isa AbstractMatrix
5959
return CuMatrix{T}(result_cpu)

ext/MultiGridBarrierGmshExt/MultiGridBarrierGmshExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import MultiGridBarrier: Geometry, fem2d_P1, fem2d_P2, fem2d, fem3d, tensor_dofm
3939
_gmsh_err(msg) = throw(ArgumentError(msg))
4040

4141
# 1D Chebyshev-Lobatto reference nodes, matching TensorFEM's `_tf_nodes`.
42-
_cheb(k::Int) = Float64[-cos(i * π / k) for i in 0:k]
42+
_cheb(k::Int) = Float64[-cospi(i / k) for i in 0:k]
4343

4444
# ---------------------------------------------------------------------------
4545
# Tensor-grid helpers (MultiGridBarrier local order: axis-1 fastest)

ext/MultiGridBarrierJuMPExt/MultiGridBarrierJuMPExt.jl

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,24 @@ struct Row{T}
8080
constant::CoefVal{T}
8181
end
8282

83-
# settag: (:eq, rhs::CoefVal) | (:nonneg,) | (:power, p)
83+
# settag: (:eq, rhs::CoefVal) | (:nonneg,) | (:power, p::CoefVal)
8484
struct ConRecord{T}
8585
name::String
8686
rows::Vector{Row{T}}
87-
settag::Tuple
87+
settag::Union{Tuple{Symbol},Tuple{Symbol,CoefVal{T}}}
8888
pairs::Union{Nothing,Vector{Tuple{Int,Int}}}
8989
end
9090

9191
mutable struct MGBModel{T} <: JuMP.AbstractModel
92-
geometry::Any # Geometry{T,...}
92+
geometry::Geometry # concrete Geometry{T,...} of the model
9393
coords::Matrix{T} # (V*N) x d broken-node coordinates
9494
nnodes::Int
9595
comps::Vector{CompInfo{T}}
9696
compnames::Dict{Symbol,Int}
9797
cons::Vector{ConRecord{T}}
9898
objsense::MOI.OptimizationSense
99-
objexpr::Any # MGBExpr{T} inside the integral, or nothing
99+
objexpr::Any # Union{Nothing,MGBExpr{T}}; Any because MGBExpr
100+
# references MGBModel back (mutually recursive)
100101
attrs::Dict{String,Any}
101102
objdict::Dict{Symbol,Any}
102103
lowered::Any # NamedTuple from _lower, set by optimize!
@@ -328,21 +329,17 @@ Base.show(io::IO, m::MGBModel) =
328329
print(io, "MGBModel over $(typeof(m.geometry.discretization)) with ",
329330
length(m.comps), " variable(s), ", length(m.cons), " constraint(s)")
330331

331-
struct KindedVariable{T}
332+
struct KindedVariable
332333
info::JuMP.VariableInfo
333334
kind::Symbol
334-
_tag::T
335335
end
336336

337-
function JuMP.build_variable(err::Function, info::JuMP.VariableInfo, tag::Broken)
338-
KindedVariable(info, :broken, tag)
339-
end
340-
function JuMP.build_variable(err::Function, info::JuMP.VariableInfo, tag::Continuous)
341-
KindedVariable(info, :conforming, tag)
342-
end
343-
function JuMP.build_variable(err::Function, info::JuMP.VariableInfo, tag::Uniform)
344-
KindedVariable(info, :uniform, tag)
345-
end
337+
JuMP.build_variable(err::Function, info::JuMP.VariableInfo, ::Broken) =
338+
KindedVariable(info, :broken)
339+
JuMP.build_variable(err::Function, info::JuMP.VariableInfo, ::Continuous) =
340+
KindedVariable(info, :conforming)
341+
JuMP.build_variable(err::Function, info::JuMP.VariableInfo, ::Uniform) =
342+
KindedVariable(info, :uniform)
346343

347344
function _check_info(info::JuMP.VariableInfo)
348345
(info.has_lb || info.has_ub) &&
@@ -403,34 +400,17 @@ JuMP.moi_set(rc::RegionConstraint) = JuMP.moi_set(rc.con)
403400
JuMP.build_constraint(err::Function, f, set, on::On) =
404401
RegionConstraint(JuMP.build_constraint(err, f, set), on.pairs)
405402

406-
# JuMP's macro maps generic scalar comparisons (non-Number rhs) to its
407-
# Zeros/Nonnegatives/Nonpositives shortcut sets through VARIADIC
408-
# build_constraint methods, which are ambiguous with the On method above;
409-
# disambiguate by normalizing to the MOI scalar/vector sets ourselves.
410-
function JuMP.build_constraint(err::Function, f, set::JuMP.Zeros, on::On)
411-
inner = f isa AbstractVector ?
412-
JuMP.build_constraint(err, f, MOI.Zeros(length(f))) :
413-
JuMP.build_constraint(err, f, MOI.EqualTo(0.0))
414-
RegionConstraint(inner, on.pairs)
415-
end
416-
function JuMP.build_constraint(err::Function, f, set::JuMP.Nonnegatives, on::On)
417-
inner = f isa AbstractVector ?
418-
JuMP.build_constraint(err, f, MOI.Nonnegatives(length(f))) :
419-
JuMP.build_constraint(err, f, MOI.GreaterThan(0.0))
420-
RegionConstraint(inner, on.pairs)
421-
end
422-
function JuMP.build_constraint(err::Function, f, set::JuMP.Nonpositives, on::On)
423-
inner = f isa AbstractVector ?
424-
JuMP.build_constraint(err, f, MOI.Nonpositives(length(f))) :
425-
JuMP.build_constraint(err, f, MOI.LessThan(0.0))
426-
RegionConstraint(inner, on.pairs)
427-
end
428-
# Newer JuMP versions route comparisons with non-Number sides through internal
429-
# *Zero marker sets, again variadically; cover them when they exist. Vector
430-
# comparisons arrive here too (f is then a Vector), so branch like the
431-
# Zeros/Nonnegatives/Nonpositives methods above.
403+
# JuMP's macro maps comparisons with non-Number sides to shortcut sets through
404+
# VARIADIC build_constraint methods, which are ambiguous with the On method
405+
# above: classic Zeros/Nonnegatives/Nonpositives, and (on newer JuMP versions)
406+
# the internal *Zero marker sets. Cover whichever exist, normalizing to the
407+
# MOI scalar/vector sets ourselves; vector comparisons arrive here too (f is
408+
# then a Vector), hence the branch.
432409
for (marker, moiset, vecset) in
433-
((:GreaterThanZero, :(MOI.GreaterThan(0.0)), :(MOI.Nonnegatives)),
410+
((:Zeros, :(MOI.EqualTo(0.0)), :(MOI.Zeros)),
411+
(:Nonnegatives, :(MOI.GreaterThan(0.0)), :(MOI.Nonnegatives)),
412+
(:Nonpositives, :(MOI.LessThan(0.0)), :(MOI.Nonpositives)),
413+
(:GreaterThanZero, :(MOI.GreaterThan(0.0)), :(MOI.Nonnegatives)),
434414
(:LessThanZero, :(MOI.LessThan(0.0)), :(MOI.Nonpositives)),
435415
(:EqualToZero, :(MOI.EqualTo(0.0)), :(MOI.Zeros)))
436416
if isdefined(JuMP, marker)

0 commit comments

Comments
 (0)