scs 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.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +98 -0
  5. data/ext/scs/extconf.rb +29 -0
  6. data/lib/scs.rb +17 -0
  7. data/lib/scs/ffi.rb +117 -0
  8. data/lib/scs/solver.rb +173 -0
  9. data/lib/scs/version.rb +3 -0
  10. data/vendor/scs/LICENSE.txt +21 -0
  11. data/vendor/scs/Makefile +164 -0
  12. data/vendor/scs/README.md +222 -0
  13. data/vendor/scs/include/aa.h +56 -0
  14. data/vendor/scs/include/cones.h +46 -0
  15. data/vendor/scs/include/ctrlc.h +33 -0
  16. data/vendor/scs/include/glbopts.h +177 -0
  17. data/vendor/scs/include/linalg.h +26 -0
  18. data/vendor/scs/include/linsys.h +64 -0
  19. data/vendor/scs/include/normalize.h +18 -0
  20. data/vendor/scs/include/rw.h +17 -0
  21. data/vendor/scs/include/scs.h +161 -0
  22. data/vendor/scs/include/scs_blas.h +51 -0
  23. data/vendor/scs/include/util.h +65 -0
  24. data/vendor/scs/linsys/amatrix.c +305 -0
  25. data/vendor/scs/linsys/amatrix.h +36 -0
  26. data/vendor/scs/linsys/amatrix.o +0 -0
  27. data/vendor/scs/linsys/cpu/direct/private.c +366 -0
  28. data/vendor/scs/linsys/cpu/direct/private.h +26 -0
  29. data/vendor/scs/linsys/cpu/direct/private.o +0 -0
  30. data/vendor/scs/linsys/cpu/indirect/private.c +256 -0
  31. data/vendor/scs/linsys/cpu/indirect/private.h +31 -0
  32. data/vendor/scs/linsys/cpu/indirect/private.o +0 -0
  33. data/vendor/scs/linsys/external/amd/LICENSE.txt +934 -0
  34. data/vendor/scs/linsys/external/amd/SuiteSparse_config.c +469 -0
  35. data/vendor/scs/linsys/external/amd/SuiteSparse_config.h +254 -0
  36. data/vendor/scs/linsys/external/amd/SuiteSparse_config.o +0 -0
  37. data/vendor/scs/linsys/external/amd/amd.h +400 -0
  38. data/vendor/scs/linsys/external/amd/amd_1.c +180 -0
  39. data/vendor/scs/linsys/external/amd/amd_1.o +0 -0
  40. data/vendor/scs/linsys/external/amd/amd_2.c +1842 -0
  41. data/vendor/scs/linsys/external/amd/amd_2.o +0 -0
  42. data/vendor/scs/linsys/external/amd/amd_aat.c +184 -0
  43. data/vendor/scs/linsys/external/amd/amd_aat.o +0 -0
  44. data/vendor/scs/linsys/external/amd/amd_control.c +64 -0
  45. data/vendor/scs/linsys/external/amd/amd_control.o +0 -0
  46. data/vendor/scs/linsys/external/amd/amd_defaults.c +37 -0
  47. data/vendor/scs/linsys/external/amd/amd_defaults.o +0 -0
  48. data/vendor/scs/linsys/external/amd/amd_dump.c +179 -0
  49. data/vendor/scs/linsys/external/amd/amd_dump.o +0 -0
  50. data/vendor/scs/linsys/external/amd/amd_global.c +16 -0
  51. data/vendor/scs/linsys/external/amd/amd_global.o +0 -0
  52. data/vendor/scs/linsys/external/amd/amd_info.c +119 -0
  53. data/vendor/scs/linsys/external/amd/amd_info.o +0 -0
  54. data/vendor/scs/linsys/external/amd/amd_internal.h +304 -0
  55. data/vendor/scs/linsys/external/amd/amd_order.c +199 -0
  56. data/vendor/scs/linsys/external/amd/amd_order.o +0 -0
  57. data/vendor/scs/linsys/external/amd/amd_post_tree.c +120 -0
  58. data/vendor/scs/linsys/external/amd/amd_post_tree.o +0 -0
  59. data/vendor/scs/linsys/external/amd/amd_postorder.c +206 -0
  60. data/vendor/scs/linsys/external/amd/amd_postorder.o +0 -0
  61. data/vendor/scs/linsys/external/amd/amd_preprocess.c +118 -0
  62. data/vendor/scs/linsys/external/amd/amd_preprocess.o +0 -0
  63. data/vendor/scs/linsys/external/amd/amd_valid.c +92 -0
  64. data/vendor/scs/linsys/external/amd/amd_valid.o +0 -0
  65. data/vendor/scs/linsys/external/amd/changes +11 -0
  66. data/vendor/scs/linsys/external/qdldl/LICENSE +201 -0
  67. data/vendor/scs/linsys/external/qdldl/README.md +120 -0
  68. data/vendor/scs/linsys/external/qdldl/changes +4 -0
  69. data/vendor/scs/linsys/external/qdldl/qdldl.c +298 -0
  70. data/vendor/scs/linsys/external/qdldl/qdldl.h +177 -0
  71. data/vendor/scs/linsys/external/qdldl/qdldl.o +0 -0
  72. data/vendor/scs/linsys/external/qdldl/qdldl_types.h +21 -0
  73. data/vendor/scs/linsys/gpu/gpu.c +41 -0
  74. data/vendor/scs/linsys/gpu/gpu.h +85 -0
  75. data/vendor/scs/linsys/gpu/indirect/private.c +304 -0
  76. data/vendor/scs/linsys/gpu/indirect/private.h +36 -0
  77. data/vendor/scs/scs.mk +181 -0
  78. data/vendor/scs/src/aa.c +224 -0
  79. data/vendor/scs/src/aa.o +0 -0
  80. data/vendor/scs/src/cones.c +802 -0
  81. data/vendor/scs/src/cones.o +0 -0
  82. data/vendor/scs/src/ctrlc.c +77 -0
  83. data/vendor/scs/src/ctrlc.o +0 -0
  84. data/vendor/scs/src/linalg.c +84 -0
  85. data/vendor/scs/src/linalg.o +0 -0
  86. data/vendor/scs/src/normalize.c +93 -0
  87. data/vendor/scs/src/normalize.o +0 -0
  88. data/vendor/scs/src/rw.c +167 -0
  89. data/vendor/scs/src/rw.o +0 -0
  90. data/vendor/scs/src/scs.c +978 -0
  91. data/vendor/scs/src/scs.o +0 -0
  92. data/vendor/scs/src/scs_version.c +5 -0
  93. data/vendor/scs/src/scs_version.o +0 -0
  94. data/vendor/scs/src/util.c +196 -0
  95. data/vendor/scs/src/util.o +0 -0
  96. data/vendor/scs/test/data/small_random_socp +0 -0
  97. data/vendor/scs/test/minunit.h +13 -0
  98. data/vendor/scs/test/problem_utils.h +93 -0
  99. data/vendor/scs/test/problems/rob_gauss_cov_est.h +85 -0
  100. data/vendor/scs/test/problems/small_lp.h +50 -0
  101. data/vendor/scs/test/problems/small_random_socp.h +33 -0
  102. data/vendor/scs/test/random_socp_prob.c +171 -0
  103. data/vendor/scs/test/run_from_file.c +69 -0
  104. data/vendor/scs/test/run_tests +2 -0
  105. data/vendor/scs/test/run_tests.c +32 -0
  106. metadata +203 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cf0730fd042983c36ce96ea46ad28c0a330c5765e1521f8cfb28eb3321507df9
4
+ data.tar.gz: 677ebb1dc72dc2c1e51857c8644a11e91f9a332c95574ee8c452e31f36f4ec88
5
+ SHA512:
6
+ metadata.gz: 889d538aba521314bc093e11fc584eb58f519116f72fedcab865b9a49a44fec44eaffbc20e28d1f2673c294922474c7ccc91f7ee7f0b08243c9e20ae52150e3c
7
+ data.tar.gz: 25ca5f1af62a227f1f05c34031d8f1554e1b9121c2d8c43bb40c4bd12d0af5d106b73a601f8abb8a3849fbdcde310b51a2fecfe17bb9e4bba4d3a70e5fd59fff
@@ -0,0 +1,12 @@
1
+ ## 0.2.2 (2020-07-21)
2
+
3
+ - Updated SCS to 2.1.2
4
+
5
+ ## 0.2.1 (2019-11-29)
6
+
7
+ - Fixed installation on Windows
8
+ - Removed dependency on rake for installation
9
+
10
+ ## 0.2.0 (2019-11-16)
11
+
12
+ - First release
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2019-2020 Andrew Kane
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,98 @@
1
+ # SCS
2
+
3
+ [SCS](https://github.com/cvxgrp/scs) - the splitting conic solver - for Ruby
4
+
5
+ :fire: Supports many different [problem types](https://www.cvxpy.org/tutorial/advanced/index.html#choosing-a-solver)
6
+
7
+ [![Build Status](https://travis-ci.org/ankane/scs.svg?branch=master)](https://travis-ci.org/ankane/scs) [![Build status](https://ci.appveyor.com/api/projects/status/wyn0cbgs0k5vg5tq/branch/master?svg=true)](https://ci.appveyor.com/project/ankane/scs/branch/master)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application’s Gemfile:
12
+
13
+ ```ruby
14
+ gem 'scs'
15
+ ```
16
+
17
+ If installation fails, you may need to install [dependencies](#dependencies).
18
+
19
+ ## Getting Started
20
+
21
+ Prep the problem
22
+
23
+ ```ruby
24
+ data = {a: [[1], [-1]], b: [1, 0], c: [-1]}
25
+ cone = {q: [], l: 2}
26
+ ```
27
+
28
+ And solve it
29
+
30
+ ```ruby
31
+ solver = SCS::Solver.new
32
+ solver.solve(data, cone)
33
+ ```
34
+
35
+ ## Settings
36
+
37
+ Default values shown
38
+
39
+ ```ruby
40
+ solver.solve(data, cone, {
41
+ normalize: true, # heuristic data rescaling
42
+ scale: 1.0, # if normalized, rescales by this factor
43
+ rho_x: 1e-3, # x equality constraint scaling
44
+ max_iters: 5000, # maximum iterations to take
45
+ eps: 1e-5, # convergence tolerance
46
+ alpha: 1.5, # relaxation parameter
47
+ cg_rate: 2.0, # for indirect, tolerance goes down like (1/iter)^cg_rate
48
+ verbose: true, # write out progress
49
+ warm_start: false, # warm start
50
+ acceleration_lookback: 10, # memory for acceleration
51
+ write_data_filename: nil # filename to write data if set
52
+ })
53
+ ```
54
+
55
+ ## Direct vs Indirect
56
+
57
+ SCS comes with two solvers: a direct solver which uses a cached LDL factorization and an indirect solver based on conjugate gradients. For the indirect solver, use:
58
+
59
+ ```ruby
60
+ SCS::Solver.new(indirect: true)
61
+ ```
62
+
63
+ ## Dependencies
64
+
65
+ BLAS and LAPACK are required for SCS.
66
+
67
+ ```sh
68
+ sudo apt-get install libblas-dev liblapack-dev
69
+ ```
70
+
71
+ On Heroku, use the [heroku-apt-buildpack](https://github.com/heroku/heroku-buildpack-apt).
72
+
73
+ ## Resources
74
+
75
+ - [Conic Optimization via Operator Splitting and Homogeneous Self-Dual Embedding](https://web.stanford.edu/~boyd/papers/scs.html)
76
+
77
+ ## History
78
+
79
+ View the [changelog](https://github.com/ankane/scs/blob/master/CHANGELOG.md)
80
+
81
+ ## Contributing
82
+
83
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
84
+
85
+ - [Report bugs](https://github.com/ankane/scs/issues)
86
+ - Fix bugs and [submit pull requests](https://github.com/ankane/scs/pulls)
87
+ - Write, clarify, or fix documentation
88
+ - Suggest or add new features
89
+
90
+ To get started with development:
91
+
92
+ ```sh
93
+ git clone --recursive https://github.com/ankane/scs.git
94
+ cd scs
95
+ bundle install
96
+ bundle exec rake compile
97
+ bundle exec rake test
98
+ ```
@@ -0,0 +1,29 @@
1
+ require "mkmf"
2
+
3
+ def run(command)
4
+ puts ">> #{command}"
5
+ unless system(command)
6
+ raise "Command failed"
7
+ end
8
+ end
9
+
10
+ def inreplace(file, pattern, replacement)
11
+ contents = File.read(file)
12
+ File.write(file, contents.gsub(pattern, replacement))
13
+ end
14
+
15
+ arch = RbConfig::CONFIG["arch"]
16
+ puts "Arch: #{arch}"
17
+
18
+ scs = File.expand_path("../../vendor/scs", __dir__)
19
+ Dir.chdir(scs) do
20
+ case arch
21
+ when /mingw/
22
+ inreplace("scs.mk", "USE_LAPACK = 1", "USE_LAPACK = 0")
23
+ run "ridk exec make"
24
+ else
25
+ run "make"
26
+ end
27
+ end
28
+
29
+ File.write("Makefile", dummy_makefile("scs").join)
@@ -0,0 +1,17 @@
1
+ # stdlib
2
+ require "fiddle/import"
3
+
4
+ # modules
5
+ require "scs/solver"
6
+ require "scs/version"
7
+
8
+ module SCS
9
+ class Error < StandardError; end
10
+
11
+ def self.lib_version
12
+ FFI::Direct.scs_version.to_s
13
+ end
14
+
15
+ # friendlier error message
16
+ autoload :FFI, "scs/ffi"
17
+ end
@@ -0,0 +1,117 @@
1
+ module SCS
2
+ module FFI
3
+ module Direct
4
+ def self.lib_name
5
+ "libscsdir"
6
+ end
7
+ end
8
+
9
+ module Indirect
10
+ def self.lib_name
11
+ "libscsindir"
12
+ end
13
+ end
14
+
15
+ ext =
16
+ if Gem.win_platform?
17
+ "dll"
18
+ elsif RbConfig::CONFIG["host_os"] =~ /darwin/i
19
+ "dylib"
20
+ else
21
+ "so"
22
+ end
23
+
24
+ [Direct, Indirect].each do |m|
25
+ m.module_eval do
26
+ extend Fiddle::Importer
27
+
28
+ dlload File.expand_path("../../vendor/scs/out/#{lib_name}.#{ext}", __dir__)
29
+
30
+ extern "size_t scs_sizeof_int(void)"
31
+ extern "size_t scs_sizeof_float(void)"
32
+
33
+ # TODO support other sizes
34
+ raise Error, "Unsupported int size" if scs_sizeof_int != 4
35
+ raise Error, "Unsupported float size" if scs_sizeof_float != 8
36
+
37
+ typealias "scs_float", "double"
38
+ typealias "scs_int", "int"
39
+
40
+ m::Data = struct [
41
+ "scs_int m",
42
+ "scs_int n",
43
+ "ScsMatrix *a",
44
+ "scs_float *b",
45
+ "scs_float *c",
46
+ "ScsSettings *stgs"
47
+ ]
48
+
49
+ m::Cone = struct [
50
+ "scs_int f",
51
+ "scs_int l",
52
+ "scs_int *q",
53
+ "scs_int qsize",
54
+ "scs_int *s",
55
+ "scs_int ssize",
56
+ "scs_int ep",
57
+ "scs_int ed",
58
+ "scs_float *p",
59
+ "scs_int psize",
60
+ ]
61
+
62
+ m::Solution = struct [
63
+ "scs_float *x",
64
+ "scs_float *y",
65
+ "scs_float *s"
66
+ ]
67
+
68
+ m::Info = struct [
69
+ "scs_int iter",
70
+ "char status[32]",
71
+ "scs_int status_val",
72
+ "scs_float pobj",
73
+ "scs_float dobj",
74
+ "scs_float res_pri",
75
+ "scs_float res_dual",
76
+ "scs_float res_infeas",
77
+ "scs_float res_unbdd",
78
+ "scs_float rel_gap",
79
+ "scs_float setup_time",
80
+ "scs_float solve_time"
81
+ ]
82
+
83
+ m::Settings = struct [
84
+ "scs_int normalize",
85
+ "scs_float scale",
86
+ "scs_float rho_x",
87
+ "scs_int max_iters",
88
+ "scs_float eps",
89
+ "scs_float alpha",
90
+ "scs_float cg_rate",
91
+ "scs_int verbose",
92
+ "scs_int warm_start",
93
+ "scs_int acceleration_lookback",
94
+ "const char* write_data_filename"
95
+ ]
96
+
97
+ m::Matrix = struct [
98
+ "scs_float *x",
99
+ "scs_int *i",
100
+ "scs_int *p",
101
+ "scs_int m",
102
+ "scs_int n"
103
+ ]
104
+
105
+ # scs.h
106
+ extern "ScsWork *scs_init(const ScsData *d, const ScsCone *k, ScsInfo *info)"
107
+ extern "scs_int scs_solve(ScsWork *w, const ScsData *d, const ScsCone *k, ScsSolution *sol, ScsInfo *info)"
108
+ extern "void scs_finish(ScsWork *w)"
109
+ extern "scs_int scs(const ScsData *d, const ScsCone *k, ScsSolution *sol, ScsInfo *info)"
110
+ extern "const char *scs_version(void)"
111
+
112
+ # utils.h
113
+ extern "void scs_set_default_settings(ScsData *d)"
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,173 @@
1
+ module SCS
2
+ class Solver
3
+ def initialize(indirect: false)
4
+ @ffi = indirect ? FFI::Indirect : FFI::Direct
5
+ end
6
+
7
+ def solve(data, cone, **settings)
8
+ cdata = create_data(data)
9
+ set_settings(cdata, settings)
10
+ ccone = create_cone(cone)
11
+
12
+ solution = calloc(ffi::Solution.size) # alloc clear memory
13
+ info = ffi::Info.malloc
14
+
15
+ ffi.scs(cdata, ccone, solution, info)
16
+
17
+ solution = ffi::Solution.new(solution)
18
+ x = read_float_array(solution.x, cdata.n)
19
+ y = read_float_array(solution.y, cdata.m)
20
+ s = read_float_array(solution.s, cdata.m)
21
+
22
+ {
23
+ x: x,
24
+ y: y,
25
+ s: s,
26
+ iter: info.iter,
27
+ status: read_string(info.status),
28
+ status_val: info.status_val,
29
+ pobj: info.pobj,
30
+ dobj: info.dobj,
31
+ res_pri: info.res_pri,
32
+ res_dual: info.res_dual,
33
+ res_infeas: info.res_infeas,
34
+ res_unbdd: info.res_unbdd,
35
+ rel_gap: info.rel_gap,
36
+ setup_time: info.setup_time,
37
+ solve_time: info.solve_time
38
+ }
39
+ end
40
+
41
+ private
42
+
43
+ def check_result(ret)
44
+ raise Error, "Error code #{ret}" if ret != 0
45
+ end
46
+
47
+ def float_array(arr)
48
+ # SCS float = double
49
+ Fiddle::Pointer[arr.to_a.pack("d*")]
50
+ end
51
+
52
+ def read_float_array(ptr, size)
53
+ # SCS float = double
54
+ ptr[0, size * Fiddle::SIZEOF_DOUBLE].unpack("d*")
55
+ end
56
+
57
+ def int_array(arr)
58
+ # SCS int = int
59
+ Fiddle::Pointer[arr.to_a.pack("i!*")]
60
+ end
61
+
62
+ def read_string(char_ptr)
63
+ idx = char_ptr.index { |v| v == 0 }
64
+ char_ptr[0, idx].map(&:chr).join
65
+ end
66
+
67
+ # TODO add support sparse matrices
68
+ def csc_matrix(mtx, upper: false)
69
+ mtx = mtx.to_a
70
+
71
+ m, n = shape(mtx)
72
+
73
+ cx = []
74
+ ci = []
75
+ cp = []
76
+
77
+ # CSC format
78
+ # https://www.gormanalysis.com/blog/sparse-matrix-storage-formats/
79
+ cp << 0
80
+ n.times do |j|
81
+ mtx.each_with_index do |row, i|
82
+ if row[j] != 0 && (!upper || i <= j)
83
+ cx << row[j]
84
+ ci << i
85
+ end
86
+ end
87
+ # cumulative column values
88
+ cp << cx.size
89
+ end
90
+
91
+ # construct matrix
92
+ matrix = ffi::Matrix.malloc
93
+ matrix.x = float_array(cx)
94
+ matrix.i = int_array(ci)
95
+ matrix.p = int_array(cp)
96
+ matrix.m = m
97
+ matrix.n = n
98
+ matrix
99
+ end
100
+
101
+ def shape(a)
102
+ if defined?(Matrix) && a.is_a?(Matrix)
103
+ [a.row_count, a.column_count]
104
+ elsif defined?(Numo::NArray) && a.is_a?(Numo::NArray)
105
+ a.shape
106
+ else
107
+ [a.size, a.first.size]
108
+ end
109
+ end
110
+
111
+ def create_data(data)
112
+ m, n = shape(data[:a])
113
+ cdata = ffi::Data.malloc
114
+ cdata.m = m
115
+ cdata.n = n
116
+ cdata.a = csc_matrix(data[:a])
117
+ cdata.b = float_array(data[:b])
118
+ cdata.c = float_array(data[:c])
119
+ cdata
120
+ end
121
+
122
+ def create_cone(cone)
123
+ ccone = ffi::Cone.malloc
124
+ ccone.f = cone[:f].to_i
125
+ ccone.l = cone[:l].to_i
126
+ ccone.q = int_array(cone[:q])
127
+ ccone.qsize = cone[:q].to_a.size
128
+ ccone.s = int_array(cone[:s])
129
+ ccone.ssize = cone[:s].to_a.size
130
+ ccone.ep = cone[:ep].to_i
131
+ ccone.ed = cone[:ed].to_i
132
+ ccone.p = float_array(cone[:p])
133
+ ccone.psize = cone[:p].to_a.size
134
+ ccone
135
+ end
136
+
137
+ def set_settings(data, settings)
138
+ set = ffi::Settings.malloc
139
+ data.stgs = set
140
+ ffi.scs_set_default_settings(data)
141
+
142
+ # hack for setting members with []=
143
+ # safer than send("#{k}=", v)
144
+ entity = set.to_ptr
145
+ settings.each do |k, v|
146
+ entity[k.to_s] = settings_value(v)
147
+ end
148
+
149
+ set
150
+ end
151
+
152
+ # handle booleans
153
+ def settings_value(v)
154
+ case v
155
+ when true
156
+ 1
157
+ when false
158
+ 0
159
+ else
160
+ v
161
+ end
162
+ end
163
+
164
+ # alloc clear memory
165
+ def calloc(size)
166
+ Fiddle::Pointer["\x00" * size]
167
+ end
168
+
169
+ def ffi
170
+ @ffi
171
+ end
172
+ end
173
+ end