osqp 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a723c2590dd5dea89dd98525009db82f6e3bf740e7a47032a822e23bab790eb0
4
- data.tar.gz: 4515ead926d38e72843c8193ec67f072d24189108ddcc538ad0c021ccae4c35c
3
+ metadata.gz: f94b0363a8ee43efba253d1138642c54832aed26cd2bc7d8e4bfb064453754fd
4
+ data.tar.gz: 673f1fa4d56747e26e0ab36cdd642c21d2c0d93c6233bb37502cfff805421d54
5
5
  SHA512:
6
- metadata.gz: ac250fdfa3927134aff7d609456382d3bd9d79e1252e42a2adeba7d79e33db4361c504aa06701bea0cf0fa7d92a843726115af0fcba93734cb619af0883b1068
7
- data.tar.gz: 84a1e39d8bbd810e49a0c2ab5dafd897208d87476dbfede8a292482cd74ee078d2e88b11424100b8ae72c6bdfb6c7dc76301ae5d40f513e575d6dea1bb4d851e
6
+ metadata.gz: 4af98ca5b0b140d3f20e82b64220583f3a0b720a45da74ea25ff4ca6b1921188d14ef12c8f4fb87e171c55f3e166ac8572c6856e94aae484687b8193e90ee6c2
7
+ data.tar.gz: df8999d0333e4467223931e89e5ad1b88567dca38f502a387e1077f21a652fdf86ab6c5171c56d030122eeba85f4265eba9d1737481a7f79a77cc8e6a1fe410e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.2.2 (2023-01-29)
2
+
3
+ - Fixed mutating `p` argument when not upper triangle
4
+
5
+ ## 0.2.1 (2022-07-05)
6
+
7
+ - Added `Matrix` class
8
+
1
9
  ## 0.2.0 (2022-06-12)
2
10
 
3
11
  - Added ARM shared library for Linux
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
  [![Build Status](https://github.com/ankane/osqp-ruby/workflows/build/badge.svg?branch=master)](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
- Arrays and matrices can be Ruby arrays
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
- [[1, 2, 3], [4, 5, 6]]
59
+ [1, 2, 3]
48
60
  ```
49
61
 
50
62
  Or Numo arrays
51
63
 
52
64
  ```ruby
53
- Numo::NArray.cast([[1, 2, 3], [4, 5, 6]])
65
+ Numo::NArray.cast([1, 2, 3])
54
66
  ```
55
67
 
56
68
  ## Resources
@@ -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.to_a
140
-
141
- m, n = shape(mtx)
142
-
143
- cx = []
144
- ci = []
145
- cp = []
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
- nnz = cx.size
162
- cx = float_array(cx)
163
- ci = int_array(ci)
164
- cp = int_array(cp)
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
@@ -1,3 +1,3 @@
1
1
  module OSQP
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.2"
3
3
  end
data/lib/osqp.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  require "fiddle/import"
3
3
 
4
4
  # modules
5
+ require "osqp/matrix"
5
6
  require "osqp/solver"
6
7
  require "osqp/version"
7
8
 
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.0
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: 2022-06-12 00:00:00.000000000 Z
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.3.7
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