Skip to content

Commit 7b85efa

Browse files
authored
Remove SparseVarArray (#31)
* Removing SparseVarArray * Remove kwargs for inservar! * Delete most commented-out removals * update JuliaFormatter for CI * In progress: updating tests * formatting fixes * x86 fix * Update tests Remove tables awaiting #30 * Test starting from 1.7 for destructuring * Test coverage * formatting fixes * Delete unused for coverage * SparseArray coverage * Formatting fixes * x86 fixes (Int) * Formatting fix * Update readme and prep for v0.7 * Remove README entry on rowtables for now * Prep release Co-authored-by: Lars Hellemo <[email protected]>
1 parent 1072681 commit 7b85efa

14 files changed

+216
-567
lines changed

.github/workflows/ci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ jobs:
2222
- version: '1' # The latest point-release (Windows)
2323
os: windows-latest
2424
arch: x64
25-
- version: '1.6' # 1.6 LTS (64-bit Linux)
25+
- version: '1.7' # 1.7 for destructuring, not 1.6 LTS (64-bit Linux)
2626
os: ubuntu-latest
2727
arch: x64
28-
- version: '1.6' # 1.6 LTS (32-bit Linux)
28+
- version: '1.7' # 1.7 for destructuring, not 1.6 LTS (32-bit Linux)
2929
os: ubuntu-latest
3030
arch: x86
3131
steps:

.github/workflows/format_check.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
run: |
2020
using Pkg
2121
# If you update the version, also update the style guide docs.
22-
Pkg.add(PackageSpec(name="JuliaFormatter", version="1.0.8"))
22+
Pkg.add(PackageSpec(name="JuliaFormatter", version="1.0.13"))
2323
using JuliaFormatter
2424
format("."; verbose = true)
2525
out = String(read(Cmd(`git diff`)))

NEWS.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
TimeStructures release notes
22
===================================
33

4+
5+
Version 0.7.0 (2022-10-25)
6+
--------------------------
7+
* Major cleanup and improved test coverage
8+
* Breaking changes:
9+
- Remove `SparseVarArray' in favor of `IndexedVarArray`
10+
- Remove custom macros and constructors in favor of exending standard JuMP macros
11+
- Remove support for `DataFrame` and custom `Tables.jl` interface in favor of exending upstreamed Tables support in JuMP (will be added back when ready)
12+
413
Version 0.6.2 (2022-09-19)
514
--------------------------
615
* Add IndexedVarArray that checks for valid indices on insert and has improved performance

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SparseVariables"
22
uuid = "2749762c-80ed-4b14-8f33-f0736679b02b"
33
authors = ["Truls Flatberg <[email protected]>", "Lars Hellemo <[email protected]>"]
4-
version = "0.6.2"
4+
version = "0.7.0"
55

66
[deps]
77
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"

README.md

+21-72
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
[![Build Status](https://github.com/hellemo/SparseVariables.jl/workflows/CI/badge.svg?branch=main)](https://github.com/hellemo/SparseVariables.jl/actions?query=workflow%3ACI)
44
[![codecov](https://codecov.io/gh/hellemo/SparseVariables.jl/branch/main/graph/badge.svg?token=2LXGVU04YS)](https://codecov.io/gh/hellemo/SparseVariables.jl)
55

6-
This package contains routines for improved performance and easier handling of sparse data
7-
and sparse arrays of optimizaton variables in JuMP.
6+
Add container type(s) for improved performance and easier handling of sparse data
7+
and sparse arrays of optimizaton variables in [JuMP](https://jump.dev/JuMP.jl/stable/).
88

99
Watch the JuliaCon/JuMP-dev 2022 lightning talk and check out the [notebook with examples and bencmarks]("docs/notebook_juliacon2022.jl"):
1010

@@ -29,7 +29,7 @@ const SV = SparseVariables
2929
m = Model()
3030

3131
cars = ["ford", "bmw", "opel"]
32-
year = [2000, 2001, 2002, 2003]
32+
years = [2000, 2001, 2002, 2003]
3333

3434
car_cost = SparseArray(Dict(
3535
("ford", 2000) => 100,
@@ -38,91 +38,40 @@ car_cost = SparseArray(Dict(
3838
("bmw", 2002) => 300
3939
))
4040

41-
# Variable defined for a given set of tuples
42-
@sparsevariable(m, y[car, year] for (car,year) in keys(car_cost))
43-
44-
# Empty variable with 2 indices
45-
@sparsevariable(m, z[car, year])
4641

42+
# Empty variables with 2 indices and allowed index values specified
43+
# by `car` and `year`, using `container=IndexedVarArray`
44+
@variable(m, y[car=cars, year=years]; container=IndexedVarArray)
45+
@variable(m, z[car=cars, year=years]; container=IndexedVarArray)
4746
# Dynamic creation of variables
47+
for (cr, yr) in keys(car_cost)
48+
insertvar!(y, cr, yr)
49+
end
50+
51+
# Inserting values not in the defined value sets errors:
4852
for c in ["opel", "tesla", "nikola"]
4953
insertvar!(z, c, 2002)
5054
end
5155

56+
# Skip tests for allowed values for maximum performance.
57+
# Note that this will allow creating values outside the defined
58+
# sets, as long as the type is correct.
59+
for c in ["opel", "tesla", "nikola"]
60+
unsafe_insertvar!(z, c, 2002)
61+
end
62+
5263
# Inefficient iteration, but 0 contribution for non-existing variables
53-
@constraint(m, sum(y[c,i] + z[c,i] for c in cars, i in year) <= 300)
64+
@constraint(m, sum(y[c,i] + z[c,i] for c in cars, i in years) <= 300)
5465

5566
# Slicing over selected indices
5667
@constraint(m, sum(y[:, 2000]) <= 300)
5768

5869
# Efficient filtering using select syntax
59-
for i in year
70+
for i in years
6071
@constraint(m, sum(car_cost[c,i] * y[c,i] for (c,i) in SV.select(y, :, i)) <= 300)
6172
end
6273

6374
# Filter using functions on indices
6475
@constraint(m, sum(z[endswith("a"), iseven]) >= 1)
6576
```
6677

67-
## IndexedVarArray
68-
69-
Use IndexedVarArrays to check for valid indices and to warn against duplicate
70-
indices, as well as improved performance:
71-
72-
```julia
73-
w = IndexedVarArray(m, "w", (car=cars, year=year))
74-
m[:w] = w
75-
76-
for c in cars, y in year
77-
insertvar!(w, c, y)
78-
end
79-
```
80-
81-
82-
83-
## Solution information
84-
85-
SparseVariables.jl provides `SolutionTable` that supports the [Tables.jl](https://github.com/JuliaData/Tables.jl) interface, allowing
86-
easy output of solution values to e.g. a `DataFrame` or a csv-file
87-
```julia
88-
using CSV
89-
using DataFrames
90-
using HiGHS
91-
92-
# Solve m
93-
set_optimizer(m, HiGHS.Optimizer)
94-
optimize!(m)
95-
96-
# Fetch solution
97-
tab = table(y)
98-
99-
# Save to CSV
100-
CSV.write("result.csv", tab)
101-
102-
# Convert to DataFrame
103-
df_y = dataframe(y)
104-
df_z = DataFrame(table(z))
105-
```
106-
The Tables interface is also implemented for `DenseAxisArray`, allowing the functionality to be used also for normal
107-
dense JuMP-variables. Since the container does not provide index names, these have to be given as explicit arguments:
108-
109-
110-
```julia
111-
# Add dense variable u
112-
@variable(m, u[cars, year])
113-
114-
for c in cars, y in year
115-
@constraint(m, u[c, y] <= 1)
116-
end
117-
118-
# Solve
119-
set_optimizer(m, HiGHS.Optimizer)
120-
optimize!(m)
121-
122-
# Read solution values for u
123-
tab = table(u, :u, :car, :year)
124-
df = DataFrame(tab)
125-
```
126-
127-
Note that output to a DataFrame through the `dataframe` function is only possible if `DataFrames` is loaded
128-
before `SparseVariables`.

src/SparseVariables.jl

-4
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,12 @@ using JuMP
55
using LinearAlgebra
66

77
include("sparsearray.jl")
8-
include("sparsevararray.jl")
9-
include("macros.jl")
108
include("dictionaries.jl")
119
include("indexedarray.jl")
1210
include("tables.jl")
1311

1412
export SparseArray
15-
export SparseVarArray
1613
export IndexedVarArray
17-
export @sparsevariable
1814
export insertvar!
1915
export unsafe_insertvar!
2016

src/dictionaries.jl

+1-27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
function variable_name(var::String, index)
2-
return var * "[" * join(index, ", ") * "]"
3-
end
4-
51
"""
62
make_filter_fun(c, pos)
73
@@ -196,35 +192,13 @@ function select(dict::Dictionary, indices)
196192
return getindices(dict, select(keys(dict), indices))
197193
end
198194
select(dict, f::Function) = filter(f, dict)
199-
function kselect(sa::SparseVarArray, sh_pat::NamedTuple)
200-
return select(keys(sa.data), sh_pat, get_index_names(sa))
201-
end
202-
function select(sa::SparseVarArray, sh_pat::NamedTuple)
203-
return Dictionaries.getindices(sa, kselect(sa, sh_pat))
204-
end
205195

206196
function select_test(dict, indices, cache)
207197
return cache ? _select_cached(dict, indices) :
208198
_select_gen(keys(dict), indices)
209199
end
210200

211-
function _select_cached(sa, pat)
212-
indices = Tuple(i for (i, v) in enumerate(pat) if v !== Colon())
213-
vals = Tuple(v for v in pat if v !== Colon())
214-
215-
if !(indices in keys(sa.index_cache))
216-
index = Dict()
217-
for v in keys(sa)
218-
vred = Tuple(val for (i, val) in enumerate(v) if i in indices)
219-
if !(vred in keys(index))
220-
index[vred] = []
221-
end
222-
push!(index[vred], v)
223-
end
224-
sa.index_cache[indices] = index
225-
end
226-
return get(sa.index_cache[indices], vals, [])
227-
end
201+
function _select_cached(sa, pat) end
228202

229203
function permfromnames(names::NamedTuple, patnames)
230204
perm = (names[i] for i in patnames)

src/indexedarray.jl

+11-75
Original file line numberDiff line numberDiff line change
@@ -10,67 +10,6 @@ struct IndexedVarArray{V<:AbstractVariableRef,N,T} <: AbstractSparseArray{V,N}
1010
index_cache::Vector{Dictionary}
1111
end
1212

13-
function IndexedVarArray(
14-
model::Model,
15-
name::AbstractString,
16-
index_names::NamedTuple{Ns,Ts};
17-
lower_bound = 0,
18-
kw_args...,
19-
) where {Ns,Ts}
20-
T = Tuple{eltype.(fieldtypes(Ts))...}
21-
N = length(fieldtypes(Ts))
22-
dict = Dictionary{T,VariableRef}()
23-
return model[Symbol(name)] = IndexedVarArray{VariableRef,N,T}(
24-
(ix...) -> createvar(model, name, ix; lower_bound, kw_args...),
25-
dict,
26-
index_names,
27-
Vector{Dictionary}(undef, 2^N),
28-
)
29-
end
30-
31-
function IndexedVarArray(
32-
model::Model,
33-
name::AbstractString,
34-
index_names::NamedTuple{Ns,Ts},
35-
indices::Vector{T};
36-
lower_bound = 0,
37-
kw_args...,
38-
) where {Ns,Ts,T}
39-
@assert T == Tuple{eltype.(fieldtypes(Ts))...}
40-
N = length(fieldtypes(Ts))
41-
# TODO: Check if each index is valid
42-
dict = Dictionary(
43-
indices,
44-
(createvar(model, name, k; lower_bound, kw_args...) for k in indices),
45-
)
46-
return model[Symbol(name)] = IndexedVarArray{VariableRef,N,T}(
47-
(ix...) -> createvar(model, name, ix; lower_bound, kw_args...),
48-
dict,
49-
index_names,
50-
Vector{Dictionary}(undef, 2^N),
51-
)
52-
end
53-
54-
function IndexedVarArray(
55-
model::Model,
56-
name::AbstractString,
57-
index_names::NamedTuple{Ns,Ts},
58-
indices::Dictionaries.Indices{T};
59-
lower_bound = 0,
60-
kw_args...,
61-
) where {Ns,Ts,T}
62-
@assert T == Tuple{eltype.(fieldtypes(Ts))...}
63-
N = length(fieldtypes(Ts))
64-
return IndexedVarArray(
65-
model,
66-
name,
67-
index_names,
68-
collect(indices);
69-
lower_bound,
70-
kw_args...,
71-
)
72-
end
73-
7413
_data(sa::IndexedVarArray) = sa.data
7514

7615
already_defined(var, index) = haskey(_data(var), index)
@@ -93,16 +32,11 @@ function clear_cache!(var)
9332
end
9433

9534
"""
96-
insertvar!(var::IndexedVarArray{V,N,T}, index...; lower_bound = 0, kw_args...)
35+
insertvar!(var::IndexedVarArray{V,N,T}, index...)
9736
9837
Insert a new variable with the given index only after checking if keys are valid and not already defined.
9938
"""
100-
function insertvar!(
101-
var::IndexedVarArray{V,N,T},
102-
index...;
103-
lower_bound = 0,
104-
kw_args...,
105-
) where {V,N,T}
39+
function insertvar!(var::IndexedVarArray{V,N,T}, index...) where {V,N,T}
10640
!valid_index(var, index) && throw(BoundsError(var, index))# "Not a valid index for $(var.name): $index"g
10741
already_defined(var, index) && error("$index already defined for array")
10842

@@ -113,17 +47,12 @@ function insertvar!(
11347
end
11448

11549
"""
116-
unsafe_insertvar!(var::indexedVarArray{V,N,T}, index...; lower_bound = 0, kw_args...)
50+
unsafe_insertvar!(var::indexedVarArray{V,N,T}, index...)
11751
11852
Insert a new variable with the given index withouth checking if the index is valid or
11953
already assigned.
12054
"""
121-
function unsafe_insertvar!(
122-
var::IndexedVarArray{V,N,T},
123-
index...;
124-
lower_bound = 0,
125-
kw_args...,
126-
) where {V,N,T}
55+
function unsafe_insertvar!(var::IndexedVarArray{V,N,T}, index...) where {V,N,T}
12756
return var[index] = var.f(index...)
12857
end
12958

@@ -264,3 +193,10 @@ function Containers.container(f::Function, indices, D::Type{IndexedVarArray})
264193
index_vars = Symbol.("i$i" for i in 1:length(indices.prod.iterators))
265194
return Containers.container(f, indices, D, index_vars)
266195
end
196+
197+
function Base.firstindex(sa::IndexedVarArray, d)
198+
return first(sort(sa.index_names[d]))
199+
end
200+
function Base.lastindex(sa::IndexedVarArray, d)
201+
return last(sort(sa.index_names[d]))
202+
end

0 commit comments

Comments
 (0)