osqp 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +17 -5
- data/lib/osqp/matrix.rb +72 -0
- data/lib/osqp/solver.rb +22 -39
- data/lib/osqp/utils.rb +15 -0
- data/lib/osqp/version.rb +1 -1
- data/lib/osqp.rb +1 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f94b0363a8ee43efba253d1138642c54832aed26cd2bc7d8e4bfb064453754fd
|
|
4
|
+
data.tar.gz: 673f1fa4d56747e26e0ab36cdd642c21d2c0d93c6233bb37502cfff805421d54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4af98ca5b0b140d3f20e82b64220583f3a0b720a45da74ea25ff4ca6b1921188d14ef12c8f4fb87e171c55f3e166ac8572c6856e94aae484687b8193e90ee6c2
|
|
7
|
+
data.tar.gz: df8999d0333e4467223931e89e5ad1b88567dca38f502a387e1077f21a652fdf86ab6c5171c56d030122eeba85f4265eba9d1737481a7f79a77cc8e6a1fe410e
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
The [OSQP](https://osqp.org/) (Operator Splitting Quadratic Program) solver for Ruby
|
|
4
4
|
|
|
5
|
+
Check out [Opt](https://github.com/ankane/opt) for a high-level interface
|
|
6
|
+
|
|
5
7
|
[](https://github.com/ankane/osqp-ruby/actions)
|
|
6
8
|
|
|
7
9
|
## Installation
|
|
@@ -17,9 +19,9 @@ gem "osqp"
|
|
|
17
19
|
Prep the problem - here’s how it should be [setup](https://osqp.org/docs/examples/setup-and-solve.html)
|
|
18
20
|
|
|
19
21
|
```ruby
|
|
20
|
-
p = [[4, 1], [0, 2]]
|
|
22
|
+
p = OSQP::Matrix.from_dense([[4, 1], [0, 2]])
|
|
21
23
|
q = [1, 1]
|
|
22
|
-
a = [[1, 1], [1, 0], [0, 1]]
|
|
24
|
+
a = OSQP::Matrix.from_dense([[1, 1], [1, 0], [0, 1]])
|
|
23
25
|
l = [1, 0, 0]
|
|
24
26
|
u = [1, 0.7, 0.7]
|
|
25
27
|
```
|
|
@@ -41,16 +43,26 @@ solver.warm_start(x, y)
|
|
|
41
43
|
|
|
42
44
|
## Data
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
Matrices can be a sparse matrix
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
a = OSQP::Matrix.new(3, 2)
|
|
50
|
+
a[0, 0] = 1
|
|
51
|
+
a[1, 0] = 2
|
|
52
|
+
# or
|
|
53
|
+
OSQP::Matrix.from_dense([[1, 0], [2, 0], [0, 0]])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Arrays can be Ruby arrays
|
|
45
57
|
|
|
46
58
|
```ruby
|
|
47
|
-
[
|
|
59
|
+
[1, 2, 3]
|
|
48
60
|
```
|
|
49
61
|
|
|
50
62
|
Or Numo arrays
|
|
51
63
|
|
|
52
64
|
```ruby
|
|
53
|
-
Numo::NArray.cast([
|
|
65
|
+
Numo::NArray.cast([1, 2, 3])
|
|
54
66
|
```
|
|
55
67
|
|
|
56
68
|
## Resources
|
data/lib/osqp/matrix.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module OSQP
|
|
2
|
+
class Matrix
|
|
3
|
+
attr_reader :m, :n
|
|
4
|
+
|
|
5
|
+
def initialize(m, n)
|
|
6
|
+
@m = m
|
|
7
|
+
@n = n
|
|
8
|
+
@data = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def []=(row_index, column_index, value)
|
|
12
|
+
raise IndexError, "row index out of bounds" if row_index < 0 || row_index >= @m
|
|
13
|
+
raise IndexError, "column index out of bounds" if column_index < 0 || column_index >= @n
|
|
14
|
+
# dictionary of keys, optimized for converting to CSC
|
|
15
|
+
# TODO try COO for performance
|
|
16
|
+
if value == 0
|
|
17
|
+
(@data[column_index] ||= {}).delete(row_index)
|
|
18
|
+
else
|
|
19
|
+
(@data[column_index] ||= {})[row_index] = value
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_csc
|
|
24
|
+
cx = []
|
|
25
|
+
ci = []
|
|
26
|
+
cp = []
|
|
27
|
+
|
|
28
|
+
# CSC format
|
|
29
|
+
# https://www.gormanalysis.com/blog/sparse-matrix-storage-formats/
|
|
30
|
+
cp << 0
|
|
31
|
+
n.times do |j|
|
|
32
|
+
(@data[j] || {}).sort_by { |k, v| k }.each do |k, v|
|
|
33
|
+
cx << v
|
|
34
|
+
ci << k
|
|
35
|
+
end
|
|
36
|
+
# cumulative column values
|
|
37
|
+
cp << cx.size
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
start: cp,
|
|
42
|
+
index: ci,
|
|
43
|
+
value: cx
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# private, for tests
|
|
48
|
+
def nnz
|
|
49
|
+
@data.sum { |_, v| v.count }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def initialize_copy(other)
|
|
53
|
+
super
|
|
54
|
+
@data = @data.transform_values(&:dup)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.from_dense(data)
|
|
58
|
+
data = data.to_a
|
|
59
|
+
m = data.size
|
|
60
|
+
n = m > 0 ? data.first.size : 0
|
|
61
|
+
|
|
62
|
+
mtx = Matrix.new(m, n)
|
|
63
|
+
data.each_with_index do |row, i|
|
|
64
|
+
raise ArgumentError, "row has different number of columns" if row.size != n
|
|
65
|
+
row.each_with_index do |v, j|
|
|
66
|
+
mtx[i, j] = v if v != 0
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
mtx
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/osqp/solver.rb
CHANGED
|
@@ -10,7 +10,6 @@ module OSQP
|
|
|
10
10
|
|
|
11
11
|
# data
|
|
12
12
|
# do not assign directly to struct to keep refs
|
|
13
|
-
m, n = shape(a)
|
|
14
13
|
p = csc_matrix(p, upper: true)
|
|
15
14
|
q = float_array(q)
|
|
16
15
|
a = csc_matrix(a)
|
|
@@ -18,11 +17,11 @@ module OSQP
|
|
|
18
17
|
u = float_array(u)
|
|
19
18
|
|
|
20
19
|
data = FFI::Data.malloc
|
|
21
|
-
data.n = n
|
|
22
|
-
data.m = m
|
|
23
|
-
data.p = p
|
|
20
|
+
data.n = a.n
|
|
21
|
+
data.m = a.m
|
|
22
|
+
data.p = matrix_ptr(p)
|
|
24
23
|
data.q = q
|
|
25
|
-
data.a = a
|
|
24
|
+
data.a = matrix_ptr(a)
|
|
26
25
|
data.l = l
|
|
27
26
|
data.u = u
|
|
28
27
|
|
|
@@ -134,36 +133,30 @@ module OSQP
|
|
|
134
133
|
char_ptr[0, idx].map(&:chr).join
|
|
135
134
|
end
|
|
136
135
|
|
|
137
|
-
# TODO add support sparse matrices
|
|
138
136
|
def csc_matrix(mtx, upper: false)
|
|
139
|
-
mtx = mtx.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# CSC format
|
|
148
|
-
# https://www.gormanalysis.com/blog/sparse-matrix-storage-formats/
|
|
149
|
-
cp << 0
|
|
150
|
-
n.times do |j|
|
|
151
|
-
mtx.each_with_index do |row, i|
|
|
152
|
-
if row[j] != 0 && (!upper || i <= j)
|
|
153
|
-
cx << row[j]
|
|
154
|
-
ci << i
|
|
137
|
+
mtx = Matrix.from_dense(mtx) unless mtx.is_a?(Matrix)
|
|
138
|
+
|
|
139
|
+
if upper
|
|
140
|
+
# TODO improve performance
|
|
141
|
+
mtx = mtx.dup
|
|
142
|
+
mtx.m.times do |i|
|
|
143
|
+
mtx.n.times do |j|
|
|
144
|
+
mtx[i, j] = 0 if i > j
|
|
155
145
|
end
|
|
156
146
|
end
|
|
157
|
-
# cumulative column values
|
|
158
|
-
cp << cx.size
|
|
159
147
|
end
|
|
160
148
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
149
|
+
mtx
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def matrix_ptr(mtx)
|
|
153
|
+
csc = mtx.to_csc
|
|
154
|
+
nnz = csc[:value].size
|
|
155
|
+
cx = float_array(csc[:value])
|
|
156
|
+
ci = int_array(csc[:index])
|
|
157
|
+
cp = int_array(csc[:start])
|
|
165
158
|
|
|
166
|
-
ptr = FFI.csc_matrix(m, n, nnz, cx, ci, cp)
|
|
159
|
+
ptr = FFI.csc_matrix(mtx.m, mtx.n, nnz, cx, ci, cp)
|
|
167
160
|
# save refs
|
|
168
161
|
ptr.instance_variable_set(:@osqp_refs, [cx, ci, cp])
|
|
169
162
|
ptr
|
|
@@ -174,16 +167,6 @@ module OSQP
|
|
|
174
167
|
[data.m, data.n]
|
|
175
168
|
end
|
|
176
169
|
|
|
177
|
-
def shape(a)
|
|
178
|
-
if defined?(Matrix) && a.is_a?(Matrix)
|
|
179
|
-
[a.row_count, a.column_count]
|
|
180
|
-
elsif defined?(Numo::NArray) && a.is_a?(Numo::NArray)
|
|
181
|
-
a.shape
|
|
182
|
-
else
|
|
183
|
-
[a.size, a.first.size]
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
170
|
def create_settings(settings)
|
|
188
171
|
set = FFI::Settings.malloc
|
|
189
172
|
FFI.osqp_set_default_settings(set)
|
data/lib/osqp/utils.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module OSQP
|
|
2
|
+
module Utils
|
|
3
|
+
class << self
|
|
4
|
+
def float_array(arr)
|
|
5
|
+
# OSQP float = double
|
|
6
|
+
Fiddle::Pointer[arr.to_a.pack("d*")]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def int_array(arr)
|
|
10
|
+
# OSQP int = long long
|
|
11
|
+
Fiddle::Pointer[arr.to_a.pack("q*")]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/osqp/version.rb
CHANGED
data/lib/osqp.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: osqp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-01-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email: andrew@ankane.org
|
|
@@ -22,7 +22,9 @@ files:
|
|
|
22
22
|
- README.md
|
|
23
23
|
- lib/osqp.rb
|
|
24
24
|
- lib/osqp/ffi.rb
|
|
25
|
+
- lib/osqp/matrix.rb
|
|
25
26
|
- lib/osqp/solver.rb
|
|
27
|
+
- lib/osqp/utils.rb
|
|
26
28
|
- lib/osqp/version.rb
|
|
27
29
|
- vendor/aarch64-linux/LICENSE-amd.txt
|
|
28
30
|
- vendor/aarch64-linux/LICENSE-osqp.txt
|
|
@@ -68,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
68
70
|
- !ruby/object:Gem::Version
|
|
69
71
|
version: '0'
|
|
70
72
|
requirements: []
|
|
71
|
-
rubygems_version: 3.
|
|
73
|
+
rubygems_version: 3.4.1
|
|
72
74
|
signing_key:
|
|
73
75
|
specification_version: 4
|
|
74
76
|
summary: OSQP (Operator Splitting Quadratic Program) solver for Ruby
|