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 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