cmfrec 0.1.6 → 0.2.1

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: 34e8dc08914cbc418470cd7eb3adf3d33013b786319f4510212c80bf3629f3ca
4
- data.tar.gz: c1b91a1f77b4b51a5ca4491376f8a02230ea54873f8c1b2b06f4761d6ddd0686
3
+ metadata.gz: 3bd946bc2c7425ba3550a9cd5bf346b0cac6de5597d0d38ff0dfb49db32e754d
4
+ data.tar.gz: 20038dc0c401389d75dc3a35415919f6721dab7e32459eeb34c9dcb23569c49a
5
5
  SHA512:
6
- metadata.gz: a3c57734379199196a4e3f51d9ec02b19ef1abac13d57a10ca3c20e9b76c9ee5db4b17d790330d41a9576c2ba28a9eeccafeb5760b54cfdf80a7431368895068
7
- data.tar.gz: 5a24a77a6665854abb38916a22e8141a6cae637a51f98e3df3762566f2e73cb60b9bd9a25303df0411ea53dec3211bf7534711dc55c510311620341cbe4e4ac3
6
+ metadata.gz: 1d0019e89fe0ca946cd83d60c052bff2e3dcc3990e3bf37de92b620870b6bf8a31010aec87bb2e5a352c446441b683f0148da83497b5d44ef89d62396f4b7d3c
7
+ data.tar.gz: f4d65f294b9a313c2c86111eaf66e734999924392fedc4715c51919a4ca4de3864ba3eec732f3d707dd06ea62ddb931d9b28b8436a09a807c01e14ac2c542e44
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.2.1 (2022-07-11)
2
+
3
+ - Added support for JSON serialization
4
+
5
+ ## 0.2.0 (2022-06-14)
6
+
7
+ - Updated cmfrec to 3.4.2
8
+ - Fixed missing item ids with `load_movielens`
9
+ - Dropped support for Ruby < 2.7
10
+
11
+ ## 0.1.7 (2022-03-22)
12
+
13
+ - Improved ARM detection
14
+ - Fixed error with `load_movielens`
15
+ - Fixed duplicates in `item_info` with `load_movielens`
16
+
1
17
  ## 0.1.6 (2021-08-12)
2
18
 
3
19
  - Added `user_ids` and `item_ids` methods
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # cmfrec
1
+ # cmfrec Ruby
2
2
 
3
3
  :fire: Recommendations for Ruby, powered by [cmfrec](https://github.com/david-cortes/cmfrec)
4
4
 
@@ -6,14 +6,14 @@
6
6
  - Works with explicit and implicit feedback
7
7
  - Uses high-performance matrix factorization
8
8
 
9
- [![Build Status](https://github.com/ankane/cmfrec/workflows/build/badge.svg?branch=master)](https://github.com/ankane/cmfrec/actions)
9
+ [![Build Status](https://github.com/ankane/cmfrec-ruby/workflows/build/badge.svg?branch=master)](https://github.com/ankane/cmfrec-ruby/actions)
10
10
 
11
11
  ## Installation
12
12
 
13
13
  Add this line to your application’s Gemfile:
14
14
 
15
15
  ```ruby
16
- gem 'cmfrec'
16
+ gem "cmfrec"
17
17
  ```
18
18
 
19
19
  For Windows, also follow [these instructions](#windows-installation).
@@ -58,8 +58,8 @@ Get recommendations for a new user
58
58
 
59
59
  ```ruby
60
60
  recommender.new_user_recs([
61
- {item_id: 1, value: 5},
62
- {item_id: 2, value: 3}
61
+ {item_id: 1, rating: 5},
62
+ {item_id: 2, rating: 3}
63
63
  ])
64
64
  ```
65
65
 
@@ -82,11 +82,11 @@ Add side information about users, items, or both
82
82
  ```ruby
83
83
  user_info = [
84
84
  {user_id: 1, cats: 1, dogs: 0},
85
- {user_id: 2, cats: 2, dogs: 1},
85
+ {user_id: 2, cats: 2, dogs: 1}
86
86
  ]
87
87
  item_info = [
88
88
  {item_id: 1, genre_comedy: 1, genre_drama: 0},
89
- {item_id: 2, genre_comedy: 0, genre_drama: 1},
89
+ {item_id: 2, genre_comedy: 0, genre_drama: 1}
90
90
  ]
91
91
  recommender.fit(ratings, user_info: user_info, item_info: item_info)
92
92
  ```
@@ -112,7 +112,7 @@ recommender.new_user_recs([], user_info: {cats: 0, dogs: 2})
112
112
  Add this line to your application’s Gemfile:
113
113
 
114
114
  ```ruby
115
- gem 'ngt'
115
+ gem "ngt"
116
116
  ```
117
117
 
118
118
  Get similar users
@@ -150,11 +150,7 @@ recommender.predict(ratings.last(20000))
150
150
  [Ahoy](https://github.com/ankane/ahoy) is a great source for implicit feedback
151
151
 
152
152
  ```ruby
153
- views = Ahoy::Event.
154
- where(name: "Viewed post").
155
- group(:user_id).
156
- group("properties->>'post_id'"). # postgres syntax
157
- count
153
+ views = Ahoy::Event.where(name: "Viewed post").group(:user_id).group_prop(:post_id).count
158
154
 
159
155
  data =
160
156
  views.map do |(user_id, post_id), count|
@@ -217,19 +213,21 @@ Rover.read_csv("ratings.csv")
217
213
  Store the recommender
218
214
 
219
215
  ```ruby
220
- bin = Marshal.dump(recommender)
221
- File.binwrite("recommender.bin", bin)
216
+ json = recommender.to_json
217
+ File.write("recommender.json", json)
222
218
  ```
223
219
 
224
- > You can save it to a file, database, or any other storage system
220
+ The serialized recommender includes user activity from the training data (to avoid recommending previously rated items), so be sure to protect it. You can save it to a file, database, or any other storage system, or use a tool like [Trove](https://github.com/ankane/trove). Also, user and item IDs should be integers or strings for this.
225
221
 
226
222
  Load a recommender
227
223
 
228
224
  ```ruby
229
- bin = File.binread("recommender.bin")
230
- recommender = Marshal.load(bin)
225
+ json = File.read("recommender.json")
226
+ recommender = Cmfrec::Recommender.load_json(json)
231
227
  ```
232
228
 
229
+ Alternatively, you can store only the factors and use a library like [Neighbor](https://github.com/ankane/neighbor). See the [examples](https://github.com/ankane/neighbor/tree/master/examples) for Disco, which has a similar API. For explicit feedback, you should [disable the bias](#explicit-feedback) with this approach.
230
+
233
231
  ## Reference
234
232
 
235
233
  Get ids
@@ -269,22 +267,22 @@ Cmfrec.ffi_lib = "path/to/cmfrec.dll"
269
267
 
270
268
  ## History
271
269
 
272
- View the [changelog](https://github.com/ankane/cmfrec/blob/master/CHANGELOG.md)
270
+ View the [changelog](https://github.com/ankane/cmfrec-ruby/blob/master/CHANGELOG.md)
273
271
 
274
272
  ## Contributing
275
273
 
276
274
  Everyone is encouraged to help improve this project. Here are a few ways you can help:
277
275
 
278
- - [Report bugs](https://github.com/ankane/cmfrec/issues)
279
- - Fix bugs and [submit pull requests](https://github.com/ankane/cmfrec/pulls)
276
+ - [Report bugs](https://github.com/ankane/cmfrec-ruby/issues)
277
+ - Fix bugs and [submit pull requests](https://github.com/ankane/cmfrec-ruby/pulls)
280
278
  - Write, clarify, or fix documentation
281
279
  - Suggest or add new features
282
280
 
283
281
  To get started with development:
284
282
 
285
283
  ```sh
286
- git clone https://github.com/ankane/cmfrec.git
287
- cd cmfrec
284
+ git clone https://github.com/ankane/cmfrec-ruby.git
285
+ cd cmfrec-ruby
288
286
  bundle install
289
287
  bundle exec rake vendor:all
290
288
  bundle exec rake test
data/lib/cmfrec/data.rb CHANGED
@@ -3,11 +3,11 @@ module Cmfrec
3
3
  def load_movielens
4
4
  require "csv"
5
5
 
6
- data_path = download_file("ml-100k/u.data", "http://files.grouplens.org/datasets/movielens/ml-100k/u.data",
6
+ data_path = download_file("ml-100k/u.data", "https://files.grouplens.org/datasets/movielens/ml-100k/u.data",
7
7
  file_hash: "06416e597f82b7342361e41163890c81036900f418ad91315590814211dca490")
8
- user_path = download_file("ml-100k/u.user", "http://files.grouplens.org/datasets/movielens/ml-100k/u.user",
8
+ user_path = download_file("ml-100k/u.user", "https://files.grouplens.org/datasets/movielens/ml-100k/u.user",
9
9
  file_hash: "f120e114da2e8cf314fd28f99417c94ae9ddf1cb6db8ce0e4b5995d40e90e62c")
10
- item_path = download_file("ml-100k/u.item", "http://files.grouplens.org/datasets/movielens/ml-100k/u.item",
10
+ item_path = download_file("ml-100k/u.item", "https://files.grouplens.org/datasets/movielens/ml-100k/u.item",
11
11
  file_hash: "553841ebc7de3a0fd0d6b62a204ea30c1e651aacfb2814c7a6584ac52f2c5701")
12
12
 
13
13
  # convert u.item to utf-8
@@ -24,9 +24,15 @@ module Cmfrec
24
24
 
25
25
  item_info = []
26
26
  movies = {}
27
+ movie_names = {}
27
28
  genres = %w(unknown action adventure animation childrens comedy crime documentary drama fantasy filmnoir horror musical mystery romance scifi thriller war western)
28
29
  CSV.parse(movies_str, col_sep: "|", converters: [:numeric]) do |row|
29
30
  movies[row[0]] = row[1]
31
+
32
+ # filter duplicates
33
+ next if movie_names[row[1]]
34
+ movie_names[row[1]] = true
35
+
30
36
  item = {item_id: row[1], year: row[2] ? Date.parse(row[2]).year : 1970}
31
37
  genres.each_with_index do |genre, i|
32
38
  item[:"genre_#{genre}"] = row[i + 5]
@@ -49,7 +55,10 @@ module Cmfrec
49
55
  private
50
56
 
51
57
  def download_file(fname, origin, file_hash:)
58
+ require "digest"
52
59
  require "fileutils"
60
+ require "net/http"
61
+ require "tmpdir"
53
62
 
54
63
  # TODO handle this better
55
64
  raise "No HOME" unless ENV["HOME"]
@@ -58,10 +67,6 @@ module Cmfrec
58
67
 
59
68
  return dest if File.exist?(dest)
60
69
 
61
- require "digest"
62
- require "net/http"
63
- require "tmpdir"
64
-
65
70
  temp_path = "#{Dir.tmpdir}/cmfrec-#{Time.now.to_f}" # TODO better name
66
71
 
67
72
  digest = Digest::SHA2.new
data/lib/cmfrec/ffi.rb CHANGED
@@ -17,12 +17,13 @@ module Cmfrec
17
17
  typealias "int_t", "int"
18
18
  typealias "real_t", "double"
19
19
 
20
- extern "int_t fit_collective_explicit_als(real_t *restrict biasA, real_t *restrict biasB, real_t *restrict A, real_t *restrict B, real_t *restrict C, real_t *restrict D, real_t *restrict Ai, real_t *restrict Bi, bool add_implicit_features, bool reset_values, int_t seed, real_t *restrict glob_mean, real_t *restrict U_colmeans, real_t *restrict I_colmeans, int_t m, int_t n, int_t k, int_t ixA[], int_t ixB[], real_t *restrict X, size_t nnz, real_t *restrict Xfull, real_t *restrict weight, bool user_bias, bool item_bias, bool center, real_t lam, real_t *restrict lam_unique, real_t l1_lam, real_t *restrict l1_lam_unique, bool scale_lam, bool scale_lam_sideinfo, real_t *restrict U, int_t m_u, int_t p, real_t *restrict II, int_t n_i, int_t q, int_t U_row[], int_t U_col[], real_t *restrict U_sp, size_t nnz_U, int_t I_row[], int_t I_col[], real_t *restrict I_sp, size_t nnz_I, bool NA_as_zero_X, bool NA_as_zero_U, bool NA_as_zero_I, int_t k_main, int_t k_user, int_t k_item, real_t w_main, real_t w_user, real_t w_item, real_t w_implicit, int_t niter, int_t nthreads, bool verbose, bool handle_interrupt, bool use_cg, int_t max_cg_steps, bool finalize_chol, bool nonneg, int_t max_cd_steps, bool nonneg_C, bool nonneg_D, bool precompute_for_predictions, bool include_all_X, real_t *restrict B_plus_bias, real_t *restrict precomputedBtB, real_t *restrict precomputedTransBtBinvBt, real_t *restrict precomputedBtXbias, real_t *restrict precomputedBeTBeChol, real_t *restrict precomputedBiTBi, real_t *restrict precomputedTransCtCinvCt, real_t *restrict precomputedCtCw)"
21
- extern "int_t fit_collective_implicit_als(real_t *restrict A, real_t *restrict B, real_t *restrict C, real_t *restrict D, bool reset_values, int_t seed, real_t *restrict U_colmeans, real_t *restrict I_colmeans, int_t m, int_t n, int_t k, int_t ixA[], int_t ixB[], real_t *restrict X, size_t nnz, real_t lam, real_t *restrict lam_unique, real_t l1_lam, real_t *restrict l1_lam_unique, real_t *restrict U, int_t m_u, int_t p, real_t *restrict II, int_t n_i, int_t q, int_t U_row[], int_t U_col[], real_t *restrict U_sp, size_t nnz_U, int_t I_row[], int_t I_col[], real_t *restrict I_sp, size_t nnz_I, bool NA_as_zero_U, bool NA_as_zero_I, int_t k_main, int_t k_user, int_t k_item, real_t w_main, real_t w_user, real_t w_item, real_t *restrict w_main_multiplier, real_t alpha, bool adjust_weight, bool apply_log_transf, int_t niter, int_t nthreads, bool verbose, bool handle_interrupt, bool use_cg, int_t max_cg_steps, bool finalize_chol, bool nonneg, int_t max_cd_steps, bool nonneg_C, bool nonneg_D, bool precompute_for_predictions, real_t *restrict precomputedBtB, real_t *restrict precomputedBeTBe, real_t *restrict precomputedBeTBeChol)"
22
- extern "int_t factors_collective_explicit_single(real_t *restrict a_vec, real_t *restrict a_bias,real_t *restrict u_vec, int_t p,real_t *restrict u_vec_sp, int_t u_vec_ixB[], size_t nnz_u_vec,real_t *restrict u_bin_vec, int_t pbin,bool NA_as_zero_U, bool NA_as_zero_X,bool nonneg,real_t *restrict C, real_t *restrict Cb,real_t glob_mean, real_t *restrict biasB,real_t *restrict U_colmeans,real_t *restrict Xa, int_t ixB[], size_t nnz,real_t *restrict Xa_dense, int_t n,real_t *restrict weight,real_t *restrict B,real_t *restrict Bi, bool add_implicit_features,int_t k, int_t k_user, int_t k_item, int_t k_main,real_t lam, real_t *restrict lam_unique,real_t l1_lam, real_t *restrict l1_lam_unique,bool scale_lam, bool scale_lam_sideinfo,real_t w_main, real_t w_user, real_t w_implicit,int_t n_max, bool include_all_X,real_t *restrict BtB,real_t *restrict TransBtBinvBt,real_t *restrict BtXbias,real_t *restrict BeTBeChol,real_t *restrict BiTBi,real_t *restrict CtCw,real_t *restrict TransCtCinvCt,real_t *restrict B_plus_bias)"
23
- extern "int_t factors_collective_implicit_single(real_t *restrict a_vec,real_t *restrict u_vec, int_t p,real_t *restrict u_vec_sp, int_t u_vec_ixB[], size_t nnz_u_vec,bool NA_as_zero_U,bool nonneg,real_t *restrict U_colmeans,real_t *restrict B, int_t n, real_t *restrict C,real_t *restrict Xa, int_t ixB[], size_t nnz,int_t k, int_t k_user, int_t k_item, int_t k_main,real_t lam, real_t l1_lam, real_t alpha, real_t w_main, real_t w_user,real_t w_main_multiplier,bool apply_log_transf,real_t *restrict BeTBe,real_t *restrict BtB,real_t *restrict BeTBeChol)"
24
- extern "int_t predict_X_old_collective_explicit(int_t row[], int_t col[], real_t *restrict predicted, size_t n_predict, real_t *restrict A, real_t *restrict biasA, real_t *restrict B, real_t *restrict biasB, real_t glob_mean, int_t k, int_t k_user, int_t k_item, int_t k_main, int_t m, int_t n_max, int_t nthreads)"
25
- extern "int_t predict_X_old_collective_implicit(int_t row[], int_t col[], real_t *restrict predicted, size_t n_predict, real_t *restrict A, real_t *restrict B, int_t k, int_t k_user, int_t k_item, int_t k_main, int_t m, int_t n, int_t nthreads)"
26
- extern "int_t topN(real_t *restrict a_vec, int_t k_user, real_t *restrict B, int_t k_item, real_t *restrict biasB, real_t glob_mean, real_t biasA, int_t k, int_t k_main, int_t *restrict include_ix, int_t n_include, int_t *restrict exclude_ix, int_t n_exclude, int_t *restrict outp_ix, real_t *restrict outp_score, int_t n_top, int_t n, int_t nthreads)"
20
+ extern "int_t fit_collective_explicit_als(real_t *restrict biasA, real_t *restrict biasB, real_t *restrict A, real_t *restrict B, real_t *restrict C, real_t *restrict D, real_t *restrict Ai, real_t *restrict Bi, bool add_implicit_features, bool reset_values, int_t seed, real_t *restrict glob_mean, real_t *restrict U_colmeans, real_t *restrict I_colmeans, int_t m, int_t n, int_t k, int_t ixA[], int_t ixB[], real_t *restrict X, size_t nnz, real_t *restrict Xfull, real_t *restrict weight, bool user_bias, bool item_bias, bool center, real_t lam, real_t *restrict lam_unique, real_t l1_lam, real_t *restrict l1_lam_unique, bool scale_lam, bool scale_lam_sideinfo, bool scale_bias_const, real_t *scaling_biasA, real_t *scaling_biasB, real_t *restrict U, int_t m_u, int_t p, real_t *restrict II, int_t n_i, int_t q, int_t U_row[], int_t U_col[], real_t *restrict U_sp, size_t nnz_U, int_t I_row[], int_t I_col[], real_t *restrict I_sp, size_t nnz_I, bool NA_as_zero_X, bool NA_as_zero_U, bool NA_as_zero_I, int_t k_main, int_t k_user, int_t k_item, real_t w_main, real_t w_user, real_t w_item, real_t w_implicit, int_t niter, int nthreads, bool verbose, bool handle_interrupt, bool use_cg, int_t max_cg_steps, bool precondition_cg, bool finalize_chol, bool nonneg, int_t max_cd_steps, bool nonneg_C, bool nonneg_D, bool precompute_for_predictions, bool include_all_X, real_t *restrict B_plus_bias, real_t *restrict precomputedBtB, real_t *restrict precomputedTransBtBinvBt, real_t *restrict precomputedBtXbias, real_t *restrict precomputedBeTBeChol, real_t *restrict precomputedBiTBi, real_t *restrict precomputedTransCtCinvCt, real_t *restrict precomputedCtCw, real_t *precomputedCtUbias)"
21
+ extern "int_t fit_collective_implicit_als(real_t *restrict A, real_t *restrict B, real_t *restrict C, real_t *restrict D, bool reset_values, int_t seed, real_t *restrict U_colmeans, real_t *restrict I_colmeans, int_t m, int_t n, int_t k, int_t ixA[], int_t ixB[], real_t *restrict X, size_t nnz, real_t lam, real_t *restrict lam_unique, real_t l1_lam, real_t *restrict l1_lam_unique, real_t *restrict U, int_t m_u, int_t p, real_t *restrict II, int_t n_i, int_t q, int_t U_row[], int_t U_col[], real_t *restrict U_sp, size_t nnz_U, int_t I_row[], int_t I_col[], real_t *restrict I_sp, size_t nnz_I, bool NA_as_zero_U, bool NA_as_zero_I, int_t k_main, int_t k_user, int_t k_item, real_t w_main, real_t w_user, real_t w_item, real_t *restrict w_main_multiplier, real_t alpha, bool adjust_weight, bool apply_log_transf, int_t niter, int nthreads, bool verbose, bool handle_interrupt, bool use_cg, int_t max_cg_steps, bool precondition_cg, bool finalize_chol, bool nonneg, int_t max_cd_steps, bool nonneg_C, bool nonneg_D, bool precompute_for_predictions, real_t *restrict precomputedBtB, real_t *restrict precomputedBeTBe, real_t *restrict precomputedBeTBeChol, real_t *precomputedCtUbias)"
22
+ extern "int_t predict_X_old_collective_explicit(int_t row[], int_t col[], real_t *restrict predicted, size_t n_predict, real_t *restrict A, real_t *restrict biasA, real_t *restrict B, real_t *restrict biasB, real_t glob_mean, int_t k, int_t k_user, int_t k_item, int_t k_main, int_t m, int_t n_max, int nthreads)"
23
+ extern "int_t predict_X_old_collective_implicit(int_t row[], int_t col[], real_t *restrict predicted, size_t n_predict, real_t *restrict A, real_t *restrict B, int_t k, int_t k_user, int_t k_item, int_t k_main, int_t m, int_t n, int nthreads)"
24
+ extern "int_t topN_old_collective_explicit(real_t *a_vec, real_t a_bias, real_t *A, real_t *biasA, int_t row_index, real_t *B, real_t *biasB, real_t glob_mean, int_t k, int_t k_user, int_t k_item, int_t k_main, int_t *include_ix, int_t n_include, int_t *exclude_ix, int_t n_exclude, int_t *outp_ix, real_t *outp_score, int_t n_top, int_t n, int_t n_max, bool include_all_X, int nthreads)"
25
+ extern "int_t topN_old_collective_implicit(real_t *a_vec, real_t *A, int_t row_index, real_t *B, int_t k, int_t k_user, int_t k_item, int_t k_main, int_t *include_ix, int_t n_include, int_t *exclude_ix, int_t n_exclude, int_t *outp_ix, real_t *outp_score, int_t n_top, int_t n, int nthreads)"
26
+ extern "int_t topN_new_collective_explicit(bool user_bias, real_t *u_vec, int_t p, real_t *u_vec_sp, int_t u_vec_X_col[], size_t nnz_u_vec, real_t *u_bin_vec, int_t pbin, bool NA_as_zero_U, bool NA_as_zero_X, bool nonneg, real_t *C, real_t *Cb, real_t glob_mean, real_t *biasB, real_t *U_colmeans, real_t *Xa, int_t X_col[], size_t nnz, real_t *Xa_dense, int_t n, real_t *weight, real_t *B, real_t *Bi, bool add_implicit_features, int_t k, int_t k_user, int_t k_item, int_t k_main, real_t lam, real_t *lam_unique, real_t l1_lam, real_t *l1_lam_unique, bool scale_lam, bool scale_lam_sideinfo, bool scale_bias_const, real_t scaling_biasA, real_t w_main, real_t w_user, real_t w_implicit, int_t n_max, bool include_all_X, real_t *BtB, real_t *TransBtBinvBt, real_t *BtXbias, real_t *BeTBeChol, real_t *BiTBi, real_t *CtCw, real_t *TransCtCinvCt, real_t *CtUbias, real_t *B_plus_bias, int_t *include_ix, int_t n_include, int_t *exclude_ix, int_t n_exclude, int_t *outp_ix, real_t *outp_score, int_t n_top, int nthreads)"
27
+ extern "int_t topN_new_collective_implicit(int_t n, real_t *u_vec, int_t p, real_t *u_vec_sp, int_t u_vec_X_col[], size_t nnz_u_vec, bool NA_as_zero_U, bool nonneg, real_t *U_colmeans, real_t *B, real_t *C, real_t *Xa, int_t X_col[], size_t nnz, int_t k, int_t k_user, int_t k_item, int_t k_main, real_t lam, real_t l1_lam, real_t alpha, real_t w_main, real_t w_user, real_t w_main_multiplier, bool apply_log_transf, real_t *BeTBe, real_t *BtB, real_t *BeTBeChol, real_t *CtUbias, int_t *include_ix, int_t n_include, int_t *exclude_ix, int_t n_exclude, int_t *outp_ix, real_t *outp_score, int_t n_top, int nthreads)"
27
28
  end
28
29
  end
@@ -70,7 +70,7 @@ module Cmfrec
70
70
  a_vec = @a[user * @k * Fiddle::SIZEOF_DOUBLE, @k * Fiddle::SIZEOF_DOUBLE]
71
71
  a_bias = @bias_a ? @bias_a[user * Fiddle::SIZEOF_DOUBLE, Fiddle::SIZEOF_DOUBLE].unpack1("d") : 0
72
72
  # @rated[user] will be nil for recommenders saved before 0.1.5
73
- top_n(a_vec: a_vec, a_bias: a_bias, count: count, rated: (@rated[user] || {}).keys, item_ids: item_ids)
73
+ top_n(a_vec: a_vec, a_bias: a_bias, count: count, rated: (@rated[user] || {}).keys, item_ids: item_ids, row_index: user)
74
74
  else
75
75
  # no items if user is unknown
76
76
  # TODO maybe most popular items
@@ -81,8 +81,137 @@ module Cmfrec
81
81
  def new_user_recs(data, count: 5, user_info: nil, item_ids: nil)
82
82
  check_fit
83
83
 
84
- a_vec, a_bias, rated = factors_warm(data, user_info: user_info)
85
- top_n(a_vec: a_vec, a_bias: a_bias, count: count, rated: rated, item_ids: item_ids)
84
+ data = to_dataset(data)
85
+ user_info = to_dataset(user_info) if user_info
86
+
87
+ # remove unknown items
88
+ data, unknown_data = data.partition { |d| @item_map[d[:item_id]] }
89
+
90
+ if unknown_data.any?
91
+ # TODO warn for unknown items?
92
+ # warn "[cmfrec] Unknown items: #{unknown_data.map { |d| d[:item_id] }.join(", ")}"
93
+ end
94
+
95
+ rated_ids = data.map { |d| @item_map[d[:item_id]] }
96
+
97
+ nnz = data.size
98
+
99
+ u_vec_sp = []
100
+ u_vec_x_col = []
101
+ if user_info
102
+ user_info.each do |k, v|
103
+ next if k == :user_id
104
+
105
+ uc = @user_info_map[k]
106
+ raise "Bad key: #{k}" unless uc
107
+
108
+ u_vec_x_col << uc
109
+ u_vec_sp << v
110
+ end
111
+ end
112
+ p_ = @user_info_map.size
113
+ nnz_u_vec = u_vec_sp.size
114
+ u_vec_x_col = int_ptr(u_vec_x_col)
115
+ u_vec_sp = real_ptr(u_vec_sp)
116
+
117
+ u_vec = nil
118
+ u_bin_vec = nil
119
+ pbin = 0
120
+
121
+ weight = nil
122
+ lam_unique = nil
123
+ l1_lam_unique = nil
124
+ n_max = @n
125
+
126
+ if data.any?
127
+ if @implicit
128
+ ratings = data.map { |d| d[:value] || 1 }
129
+ else
130
+ ratings = data.map { |d| d[:rating] }
131
+ check_ratings(ratings)
132
+ end
133
+ xa = real_ptr(ratings)
134
+ x_col = int_ptr(rated_ids)
135
+ else
136
+ xa = nil
137
+ x_col = nil
138
+ end
139
+ xa_dense = nil
140
+
141
+ rated = rated_ids.uniq
142
+
143
+ prep = prepare_top_n(count: count, rated: rated, item_ids: item_ids)
144
+ return [] if prep.empty?
145
+ include_ix, n_include, exclude_ix, n_exclude, outp_ix, outp_score, count = prep
146
+
147
+ if @implicit
148
+ args = [
149
+ @n,
150
+ u_vec, p_,
151
+ u_vec_sp, u_vec_x_col, nnz_u_vec,
152
+ @na_as_zero_user,
153
+ @nonneg,
154
+ @u_colmeans,
155
+ @b, @c,
156
+ xa, x_col, nnz,
157
+ @k, @k_user, @k_item, @k_main,
158
+ @lambda_, @l1_lambda, @alpha, @w_main, @w_user,
159
+ @w_main_multiplier,
160
+ @apply_log_transf,
161
+ nil, #BeTBe,
162
+ nil, #BtB,
163
+ nil, #BeTBeChol,
164
+ nil, #CtUbias,
165
+ include_ix, n_include,
166
+ exclude_ix, n_exclude,
167
+ outp_ix, outp_score,
168
+ count, @nthreads
169
+ ]
170
+ check_status FFI.topN_new_collective_implicit(*fiddle_args(args))
171
+ else
172
+ cb = nil
173
+ scaling_bias_a = 0
174
+
175
+ args = [
176
+ @user_bias,
177
+ u_vec, p_,
178
+ u_vec_sp, u_vec_x_col, nnz_u_vec,
179
+ u_bin_vec, pbin,
180
+ @na_as_zero_user, @na_as_zero,
181
+ @nonneg,
182
+ @c, cb,
183
+ @global_mean, @bias_b,
184
+ @u_colmeans,
185
+ xa, x_col, nnz,
186
+ xa_dense, @n,
187
+ weight,
188
+ @b,
189
+ @bi, @add_implicit_features,
190
+ @k, @k_user, @k_item, @k_main,
191
+ @lambda_, lam_unique,
192
+ @l1_lambda, l1_lam_unique,
193
+ @scale_lam, @scale_lam_sideinfo,
194
+ @scale_bias_const, scaling_bias_a,
195
+ @w_main, @w_user, @w_implicit,
196
+ n_max, @include_all_x,
197
+ nil, #BtB,
198
+ nil, #TransBtBinvBt,
199
+ nil, #BtXbias,
200
+ nil, #BeTBeChol,
201
+ nil, #BiTBi,
202
+ nil, #CtCw,
203
+ nil, #TransCtCinvCt,
204
+ nil, #CtUbias,
205
+ nil, #B_plus_bias,
206
+ include_ix, n_include,
207
+ exclude_ix, n_exclude,
208
+ outp_ix, outp_score,
209
+ count, @nthreads
210
+ ]
211
+ check_status FFI.topN_new_collective_explicit(*fiddle_args(args))
212
+ end
213
+
214
+ top_n_output(outp_ix, outp_score)
86
215
  end
87
216
 
88
217
  def user_ids
@@ -120,6 +249,68 @@ module Cmfrec
120
249
  similar(user_id, @user_map, user_factors, count, user_index)
121
250
  end
122
251
 
252
+ def to_json
253
+ require "base64"
254
+ require "json"
255
+
256
+ obj = {
257
+ implicit: @implicit
258
+ }
259
+
260
+ # options
261
+ obj[:factors] = @k
262
+ obj[:epochs] = @niter
263
+ obj[:verbose] = @verbose
264
+
265
+ # factors
266
+ obj[:user_ids] = @user_map.keys
267
+ obj[:item_ids] = @item_map.keys
268
+ obj[:rated] = @user_map.map { |_, u| (@rated[u] || {}).keys }
269
+ obj[:user_factors] = json_dump_ptr(@a)
270
+ obj[:item_factors] = json_dump_ptr(@b)
271
+
272
+ # bias
273
+ obj[:user_bias] = json_dump_ptr(@bias_a)
274
+ obj[:item_bias] = json_dump_ptr(@bias_b)
275
+
276
+ # mean
277
+ obj[:global_mean] = @global_mean
278
+
279
+ unless (@user_info_map.keys + @item_info_map.keys).all? { |v| v.is_a?(Symbol) }
280
+ raise "Side info keys must be symbols to save"
281
+ end
282
+
283
+ # side info
284
+ obj[:user_info_ids] = @user_info_map.keys
285
+ obj[:item_info_ids] = @item_info_map.keys
286
+ obj[:user_info_factors] = json_dump_ptr(@c)
287
+ obj[:item_info_factors] = json_dump_ptr(@d)
288
+
289
+ # implicit features
290
+ obj[:add_implicit_features] = @add_implicit_features
291
+ obj[:user_factors_implicit] = json_dump_ptr(@ai)
292
+ obj[:item_factors_implicit] = json_dump_ptr(@bi)
293
+
294
+ unless @implicit
295
+ obj[:min_rating] = @min_rating
296
+ obj[:max_rating] = @max_rating
297
+ end
298
+
299
+ obj[:user_means] = json_dump_ptr(@u_colmeans)
300
+
301
+ JSON.generate(obj)
302
+ end
303
+
304
+ def self.load_json(json)
305
+ require "json"
306
+
307
+ obj = JSON.parse(json)
308
+
309
+ recommender = new
310
+ recommender.send(:json_load, obj)
311
+ recommender
312
+ end
313
+
123
314
  private
124
315
 
125
316
  def user_index
@@ -210,7 +401,6 @@ module Cmfrec
210
401
  x_full = nil
211
402
  weight = nil
212
403
  lam_unique = nil
213
- l1_lambda = 0
214
404
  l1_lam_unique = nil
215
405
 
216
406
  uu = nil
@@ -247,7 +437,7 @@ module Cmfrec
247
437
  @m, @n, @k,
248
438
  x_row, x_col, x, nnz,
249
439
  @lambda_, lam_unique,
250
- l1_lambda, l1_lam_unique,
440
+ @l1_lambda, l1_lam_unique,
251
441
  uu, @m_u, p_,
252
442
  ii, @n_i, q,
253
443
  u_row, u_col, u_sp, nnz_u,
@@ -257,12 +447,13 @@ module Cmfrec
257
447
  @w_main, @w_user, @w_item, real_ptr([@w_main_multiplier]),
258
448
  @alpha, @adjust_weight, @apply_log_transf,
259
449
  @niter, @nthreads, @verbose, @handle_interrupt,
260
- @use_cg, @max_cg_steps, @finalize_chol,
450
+ @use_cg, @max_cg_steps, @precondition_cg, @finalize_chol,
261
451
  @nonneg, @max_cd_steps, @nonneg_c, @nonneg_d,
262
452
  @precompute_for_predictions,
263
453
  nil, #precomputedBtB,
264
454
  nil, #precomputedBeTBe,
265
- nil #precomputedBeTBeChol
455
+ nil, #precomputedBeTBeChol
456
+ nil #precomputedCtUbias
266
457
  ]
267
458
  check_status FFI.fit_collective_implicit_als(*fiddle_args(args))
268
459
 
@@ -281,9 +472,9 @@ module Cmfrec
281
472
 
282
473
  glob_mean = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE)
283
474
 
284
- center = true
285
- scale_lam = false
286
- scale_lam_sideinfo = false
475
+ # TODO add
476
+ scaling_bias_a = nil
477
+ scaling_bias_b = nil
287
478
 
288
479
  args = [
289
480
  @bias_a, @bias_b,
@@ -298,10 +489,11 @@ module Cmfrec
298
489
  x_row, x_col, x, nnz,
299
490
  x_full,
300
491
  weight,
301
- @user_bias, @item_bias, center,
492
+ @user_bias, @item_bias, @center,
302
493
  @lambda_, lam_unique,
303
- l1_lambda, l1_lam_unique,
304
- scale_lam, scale_lam_sideinfo,
494
+ @l1_lambda, l1_lam_unique,
495
+ @scale_lam, @scale_lam_sideinfo,
496
+ @scale_bias_const, scaling_bias_a, scaling_bias_b,
305
497
  uu, @m_u, p_,
306
498
  ii, @n_i, q,
307
499
  u_row, u_col, u_sp, nnz_u,
@@ -310,7 +502,7 @@ module Cmfrec
310
502
  @k_main, @k_user, @k_item,
311
503
  @w_main, @w_user, @w_item, @w_implicit,
312
504
  @niter, @nthreads, @verbose, @handle_interrupt,
313
- @use_cg, @max_cg_steps, @finalize_chol,
505
+ @use_cg, @max_cg_steps, @precondition_cg, @finalize_chol,
314
506
  @nonneg, @max_cd_steps, @nonneg_c, @nonneg_d,
315
507
  @precompute_for_predictions,
316
508
  @include_all_x,
@@ -321,7 +513,8 @@ module Cmfrec
321
513
  nil, #precomputedBeTBeChol,
322
514
  nil, #precomputedBiTBi,
323
515
  nil, #precomputedTransCtCinvCt,
324
- nil #precomputedCtCw
516
+ nil, #precomputedCtCw
517
+ nil, #precomputedCtUbias
325
518
  ]
326
519
  check_status FFI.fit_collective_explicit_als(*fiddle_args(args))
327
520
 
@@ -336,21 +529,20 @@ module Cmfrec
336
529
  end
337
530
 
338
531
  def set_params(
339
- k: 40, lambda_: 1e+1, method: "als", use_cg: true, user_bias: true,
340
- item_bias: true, add_implicit_features: false,
532
+ k: 40, lambda_: 10.0, method: "als", use_cg: true,
533
+ user_bias: true, item_bias: true, center: true, add_implicit_features: false,
534
+ scale_lam: false, scale_lam_sideinfo: false, scale_bias_const: false,
341
535
  k_user: 0, k_item: 0, k_main: 0,
342
536
  w_main: 1.0, w_user: 1.0, w_item: 1.0, w_implicit: 0.5,
537
+ l1_lambda: 0.0, center_u: true, center_i: true,
343
538
  maxiter: 800, niter: 10, parallelize: "separate", corr_pairs: 4,
344
- max_cg_steps: 3, finalize_chol: true,
539
+ max_cg_steps: 3, precondition_cg: false, finalize_chol: true,
345
540
  na_as_zero: false, na_as_zero_user: false, na_as_zero_item: false,
346
541
  nonneg: false, nonneg_c: false, nonneg_d: false, max_cd_steps: 100,
347
542
  precompute_for_predictions: true, include_all_x: true,
348
- use_float: false,
349
- random_state: 1, verbose: true, print_every: 10,
350
- handle_interrupt: true, produce_dicts: false,
351
- copy_data: true, nthreads: -1
543
+ use_float: true, random_state: 1, verbose: true, print_every: 10,
544
+ handle_interrupt: true, produce_dicts: false, nthreads: -1
352
545
  )
353
-
354
546
  @k = k
355
547
  @k_user = k_user
356
548
  @k_item = k_item
@@ -386,9 +578,17 @@ module Cmfrec
386
578
  @random_state = random_state.to_i
387
579
  @produce_dicts = !!produce_dicts
388
580
  @handle_interrupt = !!handle_interrupt
389
- @copy_data = !!copy_data
390
581
  nthreads = Etc.nprocessors if nthreads < 0
391
582
  @nthreads = nthreads
583
+
584
+ @center = center
585
+ @scale_lam = scale_lam
586
+ @scale_lam_sideinfo = scale_lam_sideinfo
587
+ @scale_bias_const = scale_bias_const
588
+ @l1_lambda = l1_lambda
589
+ @precondition_cg = precondition_cg
590
+
591
+ # TODO center_u, center_i
392
592
  end
393
593
 
394
594
  def update_maps(train_set)
@@ -462,7 +662,7 @@ module Cmfrec
462
662
  end
463
663
  end
464
664
 
465
- def top_n(a_vec:, a_bias:, count:, rated: nil, item_ids: nil)
665
+ def prepare_top_n(count: nil, rated: nil, item_ids: nil)
466
666
  if item_ids
467
667
  # remove missing ids
468
668
  item_ids = item_ids.map { |v| @item_map[v] }.compact
@@ -471,8 +671,7 @@ module Cmfrec
471
671
  include_ix = int_ptr(item_ids)
472
672
  n_include = item_ids.size
473
673
 
474
- # TODO uncomment in 0.2.0
475
- count = n_include # if n_include < count
674
+ count = n_include if n_include < count
476
675
  else
477
676
  include_ix = nil
478
677
  n_include = 0
@@ -494,145 +693,54 @@ module Cmfrec
494
693
  outp_ix = Fiddle::Pointer.malloc(count * Fiddle::SIZEOF_INT)
495
694
  outp_score = Fiddle::Pointer.malloc(count * Fiddle::SIZEOF_DOUBLE)
496
695
 
497
- check_status FFI.topN(
498
- a_vec, @k_user,
499
- @b, @k_item,
500
- @bias_b, @global_mean, a_bias,
501
- @k, @k_main,
502
- include_ix, n_include,
503
- exclude_ix, n_exclude,
504
- outp_ix, outp_score,
505
- count, @n,
506
- @nthreads
507
- )
508
-
509
- imap = @item_map.map(&:reverse).to_h
510
- item_ids = int_array(outp_ix).map { |v| imap[v] }
511
- scores = real_array(outp_score)
512
-
513
- item_ids.zip(scores).map do |item_id, score|
514
- {item_id: item_id, score: score}
515
- end
696
+ [include_ix, n_include, exclude_ix, n_exclude, outp_ix, outp_score, count]
516
697
  end
517
698
 
518
- def factors_warm(data, user_info: nil)
519
- data = to_dataset(data)
520
- user_info = to_dataset(user_info) if user_info
521
-
522
- # remove unknown items
523
- data, unknown_data = data.partition { |d| @item_map[d[:item_id]] }
524
-
525
- if unknown_data.any?
526
- # TODO warn for unknown items?
527
- # warn "[cmfrec] Unknown items: #{unknown_data.map { |d| d[:item_id] }.join(", ")}"
528
- end
529
-
530
- item_ids = data.map { |d| @item_map[d[:item_id]] }
531
-
532
- nnz = data.size
533
- a_vec = Fiddle::Pointer.malloc((@k_user + @k + @k_main) * Fiddle::SIZEOF_DOUBLE)
534
- bias_a = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE)
535
-
536
- u_vec_sp = []
537
- u_vec_x_col = []
538
- if user_info
539
- user_info.each do |k, v|
540
- next if k == :user_id
541
-
542
- uc = @user_info_map[k]
543
- raise "Bad key: #{k}" unless uc
544
-
545
- u_vec_x_col << uc
546
- u_vec_sp << v
547
- end
548
- end
549
- p_ = @user_info_map.size
550
- nnz_u_vec = u_vec_sp.size
551
- u_vec_x_col = int_ptr(u_vec_x_col)
552
- u_vec_sp = real_ptr(u_vec_sp)
553
-
554
- u_vec = nil
555
- u_bin_vec = nil
556
- pbin = 0
557
-
558
- weight = nil
559
- lam_unique = nil
560
- l1_lambda = 0
561
- l1_lam_unique = nil
562
- n_max = @n
563
-
564
- if data.any?
565
- if @implicit
566
- ratings = data.map { |d| d[:value] || 1 }
567
- else
568
- ratings = data.map { |d| d[:rating] }
569
- check_ratings(ratings)
570
- end
571
- xa = real_ptr(ratings)
572
- x_col = int_ptr(item_ids)
573
- else
574
- xa = nil
575
- x_col = nil
576
- end
577
- xa_dense = nil
699
+ def top_n(a_vec:, a_bias:, count:, rated: nil, item_ids: nil, row_index:)
700
+ prep = prepare_top_n(count: count, rated: rated, item_ids: item_ids)
701
+ return [] if prep.empty?
702
+ include_ix, n_include, exclude_ix, n_exclude, outp_ix, outp_score, count = prep
578
703
 
579
704
  if @implicit
580
- args = [
705
+ check_status FFI.topN_old_collective_implicit(
581
706
  a_vec,
582
- u_vec, p_,
583
- u_vec_sp, u_vec_x_col, nnz_u_vec,
584
- @na_as_zero_user,
585
- @nonneg,
586
- @u_colmeans,
587
- @b, @n, @c,
588
- xa, x_col, nnz,
707
+ @a, row_index,
708
+ @b,
589
709
  @k, @k_user, @k_item, @k_main,
590
- @lambda_, l1_lambda, @alpha,
591
- @w_main, @w_user, @w_main_multiplier,
592
- @apply_log_transf,
593
- nil, #BeTBe,
594
- nil, #BtB
595
- nil #BeTBeChol
596
- ]
597
- check_status FFI.factors_collective_implicit_single(*fiddle_args(args))
710
+ include_ix, n_include,
711
+ exclude_ix, n_exclude,
712
+ outp_ix, outp_score,
713
+ count, @n, @nthreads
714
+ )
598
715
  else
599
- cb = nil
600
-
601
- scale_lam = false
602
- scale_lam_sideinfo = false
603
-
604
- args = [
605
- a_vec, bias_a,
606
- u_vec, p_,
607
- u_vec_sp, u_vec_x_col, nnz_u_vec,
608
- u_bin_vec, pbin,
609
- @na_as_zero_user, @na_as_zero,
610
- @nonneg,
611
- @c, cb,
612
- @global_mean, @bias_b, @u_colmeans,
613
- xa, x_col, nnz, xa_dense,
614
- @n, weight, @b, @bi,
615
- @add_implicit_features,
716
+ # TODO add param
717
+ n_max = @n
718
+
719
+ check_status FFI.topN_old_collective_explicit(
720
+ a_vec, a_bias,
721
+ @a, @bias_a, row_index,
722
+ @b,
723
+ @bias_b,
724
+ @global_mean,
616
725
  @k, @k_user, @k_item, @k_main,
617
- @lambda_, lam_unique,
618
- l1_lambda, l1_lam_unique,
619
- scale_lam, scale_lam_sideinfo,
620
- @w_main, @w_user, @w_implicit,
621
- n_max,
622
- @include_all_x,
623
- nil, #BtB,
624
- nil, #TransBtBinvBt,
625
- nil, #BtXbias,
626
- nil, #BeTBeChol,
627
- nil, #BiTBi,
628
- nil, #CtCw,
629
- nil, #TransCtCinvCt,
630
- nil #B_plus_bias
631
- ]
632
- check_status FFI.factors_collective_explicit_single(*fiddle_args(args))
726
+ include_ix, n_include,
727
+ exclude_ix, n_exclude,
728
+ outp_ix, outp_score,
729
+ count, @n, n_max, @include_all_x ? 1 : 0, @nthreads
730
+ )
633
731
  end
634
732
 
635
- [a_vec, real_array(bias_a).first, item_ids.uniq]
733
+ top_n_output(outp_ix, outp_score)
734
+ end
735
+
736
+ def top_n_output(outp_ix, outp_score)
737
+ imap = @item_map.map(&:reverse).to_h
738
+ item_ids = int_array(outp_ix).map { |v| imap[v] }
739
+ scores = real_array(outp_score)
740
+
741
+ item_ids.zip(scores).map do |item_id, score|
742
+ {item_id: item_id, score: score}
743
+ end
636
744
  end
637
745
 
638
746
  # convert boolean to int
@@ -810,5 +918,70 @@ module Cmfrec
810
918
 
811
919
  @fit = @m > 0
812
920
  end
921
+
922
+ def json_dump_ptr(ptr)
923
+ Base64.strict_encode64(ptr.to_s(ptr.size)) if ptr
924
+ end
925
+
926
+ def json_load_ptr(str)
927
+ Fiddle::Pointer[Base64.strict_decode64(str)] if str
928
+ end
929
+
930
+ def json_load(obj)
931
+ require "base64"
932
+
933
+ @implicit = obj["implicit"]
934
+
935
+ # options
936
+ set_params(
937
+ k: obj["factors"],
938
+ niter: obj["epochs"],
939
+ verbose: obj["verbose"],
940
+ user_bias: !obj["user_bias"].nil?,
941
+ item_bias: !obj["item_bias"].nil?,
942
+ add_implicit_features: obj["add_implicit_features"]
943
+ )
944
+
945
+ # factors
946
+ @user_map = obj["user_ids"].map.with_index.to_h
947
+ @item_map = obj["item_ids"].map.with_index.to_h
948
+ @rated = obj["rated"].map.with_index.to_h { |r, i| [i, r.to_h { |v| [v, true] }] }
949
+ @a = json_load_ptr(obj["user_factors"])
950
+ @b = json_load_ptr(obj["item_factors"])
951
+
952
+ # bias
953
+ @bias_a = json_load_ptr(obj["user_bias"])
954
+ @bias_b = json_load_ptr(obj["item_bias"])
955
+
956
+ # mean
957
+ @global_mean = obj["global_mean"]
958
+
959
+ # side info
960
+ @user_info_map = obj["user_info_ids"].map(&:to_sym).map.with_index.to_h
961
+ @item_info_map = obj["item_info_ids"].map(&:to_sym).map.with_index.to_h
962
+ @c = json_load_ptr(obj["user_info_factors"])
963
+ @d = json_load_ptr(obj["item_info_factors"])
964
+
965
+ # implicit features
966
+ @add_implicit_features = obj["add_implicit_features"]
967
+ @ai = json_load_ptr(obj["user_factors_implicit"])
968
+ @bi = json_load_ptr(obj["item_factors_implicit"])
969
+
970
+ unless @implicit
971
+ @min_rating = obj["min_rating"]
972
+ @max_rating = obj["max_rating"]
973
+ end
974
+
975
+ @u_colmeans = json_load_ptr(obj["user_means"])
976
+
977
+ @m = @user_map.size
978
+ @n = @item_map.size
979
+ @m_u = @user_info_map.size
980
+ @n_i = @item_info_map.size
981
+
982
+ set_implicit_vars if @implicit
983
+
984
+ @fit = @m > 0
985
+ end
813
986
  end
814
987
  end
@@ -1,3 +1,3 @@
1
1
  module Cmfrec
2
- VERSION = "0.1.6"
2
+ VERSION = "0.2.1"
3
3
  end
data/lib/cmfrec.rb CHANGED
@@ -15,19 +15,19 @@ module Cmfrec
15
15
  class << self
16
16
  attr_accessor :ffi_lib
17
17
  end
18
- lib_name =
18
+ lib_path =
19
19
  if Gem.win_platform?
20
- "cmfrec.dll"
20
+ "x64-mingw/cmfrec.dll"
21
21
  elsif RbConfig::CONFIG["host_os"] =~ /darwin/i
22
- if RbConfig::CONFIG["host_cpu"] =~ /arm/i
23
- "libcmfrec.arm64.dylib"
22
+ if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i
23
+ "arm64-darwin/libcmfrec.dylib"
24
24
  else
25
- "libcmfrec.dylib"
25
+ "x86_64-darwin/libcmfrec.dylib"
26
26
  end
27
27
  else
28
- "libcmfrec.so"
28
+ "x86_64-linux/libcmfrec.so"
29
29
  end
30
- vendor_lib = File.expand_path("../vendor/#{lib_name}", __dir__)
30
+ vendor_lib = File.expand_path("../vendor/#{lib_path}", __dir__)
31
31
  self.ffi_lib = [vendor_lib]
32
32
 
33
33
  # friendlier error message
@@ -0,0 +1,57 @@
1
+ For the included LFBGS library (files "lbfgs.h", "lbfgs.c", "arithmetic_ansi.h"):
2
+
3
+ The MIT License
4
+
5
+ Copyright (c) 1990 Jorge Nocedal
6
+ Copyright (c) 2007-2010 Naoaki Okazaki
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a
9
+ copy of this software and associated documentation files (the "Software"),
10
+ to deal in the Software without restriction, including without limitation
11
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
+ and/or sell copies of the Software, and to permit persons to whom the
13
+ Software is furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+ For the included ziggurat tables (file "ziggurat.h"):
27
+
28
+ Copyright (c) 2005-2022, NumPy Developers.
29
+ All rights reserved.
30
+
31
+ Redistribution and use in source and binary forms, with or without
32
+ modification, are permitted provided that the following conditions are
33
+ met:
34
+
35
+ * Redistributions of source code must retain the above copyright
36
+ notice, this list of conditions and the following disclaimer.
37
+
38
+ * Redistributions in binary form must reproduce the above
39
+ copyright notice, this list of conditions and the following
40
+ disclaimer in the documentation and/or other materials provided
41
+ with the distribution.
42
+
43
+ * Neither the name of the NumPy Developers nor the names of any
44
+ contributors may be used to endorse or promote products derived
45
+ from this software without specific prior written permission.
46
+
47
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
48
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
49
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
50
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
51
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
52
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
53
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
54
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
55
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
56
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
57
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2022 David Cortes
4
+
5
+ All rights reserved.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to
9
+ deal in the Software without restriction, including without limitation the
10
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11
+ sell copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23
+ IN THE SOFTWARE.
Binary file
@@ -0,0 +1,57 @@
1
+ For the included LFBGS library (files "lbfgs.h", "lbfgs.c", "arithmetic_ansi.h"):
2
+
3
+ The MIT License
4
+
5
+ Copyright (c) 1990 Jorge Nocedal
6
+ Copyright (c) 2007-2010 Naoaki Okazaki
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a
9
+ copy of this software and associated documentation files (the "Software"),
10
+ to deal in the Software without restriction, including without limitation
11
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
+ and/or sell copies of the Software, and to permit persons to whom the
13
+ Software is furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+ For the included ziggurat tables (file "ziggurat.h"):
27
+
28
+ Copyright (c) 2005-2022, NumPy Developers.
29
+ All rights reserved.
30
+
31
+ Redistribution and use in source and binary forms, with or without
32
+ modification, are permitted provided that the following conditions are
33
+ met:
34
+
35
+ * Redistributions of source code must retain the above copyright
36
+ notice, this list of conditions and the following disclaimer.
37
+
38
+ * Redistributions in binary form must reproduce the above
39
+ copyright notice, this list of conditions and the following
40
+ disclaimer in the documentation and/or other materials provided
41
+ with the distribution.
42
+
43
+ * Neither the name of the NumPy Developers nor the names of any
44
+ contributors may be used to endorse or promote products derived
45
+ from this software without specific prior written permission.
46
+
47
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
48
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
49
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
50
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
51
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
52
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
53
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
54
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
55
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
56
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
57
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2022 David Cortes
4
+
5
+ All rights reserved.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to
9
+ deal in the Software without restriction, including without limitation the
10
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11
+ sell copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23
+ IN THE SOFTWARE.
@@ -0,0 +1,57 @@
1
+ For the included LFBGS library (files "lbfgs.h", "lbfgs.c", "arithmetic_ansi.h"):
2
+
3
+ The MIT License
4
+
5
+ Copyright (c) 1990 Jorge Nocedal
6
+ Copyright (c) 2007-2010 Naoaki Okazaki
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a
9
+ copy of this software and associated documentation files (the "Software"),
10
+ to deal in the Software without restriction, including without limitation
11
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
+ and/or sell copies of the Software, and to permit persons to whom the
13
+ Software is furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+ For the included ziggurat tables (file "ziggurat.h"):
27
+
28
+ Copyright (c) 2005-2022, NumPy Developers.
29
+ All rights reserved.
30
+
31
+ Redistribution and use in source and binary forms, with or without
32
+ modification, are permitted provided that the following conditions are
33
+ met:
34
+
35
+ * Redistributions of source code must retain the above copyright
36
+ notice, this list of conditions and the following disclaimer.
37
+
38
+ * Redistributions in binary form must reproduce the above
39
+ copyright notice, this list of conditions and the following
40
+ disclaimer in the documentation and/or other materials provided
41
+ with the distribution.
42
+
43
+ * Neither the name of the NumPy Developers nor the names of any
44
+ contributors may be used to endorse or promote products derived
45
+ from this software without specific prior written permission.
46
+
47
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
48
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
49
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
50
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
51
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
52
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
53
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
54
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
55
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
56
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
57
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2022 David Cortes
4
+
5
+ All rights reserved.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to
9
+ deal in the Software without restriction, including without limitation the
10
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11
+ sell copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23
+ IN THE SOFTWARE.
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmfrec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-12 00:00:00.000000000 Z
11
+ date: 2022-07-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: andrew@ankane.org
@@ -24,11 +24,16 @@ files:
24
24
  - lib/cmfrec/ffi.rb
25
25
  - lib/cmfrec/recommender.rb
26
26
  - lib/cmfrec/version.rb
27
- - vendor/LICENSE.txt
28
- - vendor/libcmfrec.arm64.dylib
29
- - vendor/libcmfrec.dylib
30
- - vendor/libcmfrec.so
31
- homepage: https://github.com/ankane/cmfrec
27
+ - vendor/arm64-darwin/COPYRIGHTS
28
+ - vendor/arm64-darwin/LICENSE
29
+ - vendor/arm64-darwin/libcmfrec.dylib
30
+ - vendor/x86_64-darwin/COPYRIGHTS
31
+ - vendor/x86_64-darwin/LICENSE
32
+ - vendor/x86_64-darwin/libcmfrec.dylib
33
+ - vendor/x86_64-linux/COPYRIGHTS
34
+ - vendor/x86_64-linux/LICENSE
35
+ - vendor/x86_64-linux/libcmfrec.so
36
+ homepage: https://github.com/ankane/cmfrec-ruby
32
37
  licenses:
33
38
  - MIT
34
39
  metadata: {}
@@ -40,14 +45,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
40
45
  requirements:
41
46
  - - ">="
42
47
  - !ruby/object:Gem::Version
43
- version: '2.5'
48
+ version: '2.7'
44
49
  required_rubygems_version: !ruby/object:Gem::Requirement
45
50
  requirements:
46
51
  - - ">="
47
52
  - !ruby/object:Gem::Version
48
53
  version: '0'
49
54
  requirements: []
50
- rubygems_version: 3.2.22
55
+ rubygems_version: 3.3.7
51
56
  signing_key:
52
57
  specification_version: 4
53
58
  summary: Recommendations for Ruby using collective matrix factorization
data/vendor/LICENSE.txt DELETED
@@ -1,74 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2020 David Cortes
4
-
5
- All rights reserved.
6
-
7
- Permission is hereby granted, free of charge, to any person obtaining a copy
8
- of this software and associated documentation files (the "Software"), to
9
- deal in the Software without restriction, including without limitation the
10
- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11
- sell copies of the Software, and to permit persons to whom the Software is
12
- furnished to do so, subject to the following conditions:
13
-
14
- The above copyright notice and this permission notice shall be included in
15
- all copies or substantial portions of the Software.
16
-
17
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23
- IN THE SOFTWARE.
24
-
25
- ---
26
-
27
- ANSI C implementation of vector operations.
28
-
29
- Copyright (c) 2007-2010 Naoaki Okazaki
30
- All rights reserved.
31
-
32
- Permission is hereby granted, free of charge, to any person obtaining a copy
33
- of this software and associated documentation files (the "Software"), to deal
34
- in the Software without restriction, including without limitation the rights
35
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36
- copies of the Software, and to permit persons to whom the Software is
37
- furnished to do so, subject to the following conditions:
38
-
39
- The above copyright notice and this permission notice shall be included in
40
- all copies or substantial portions of the Software.
41
-
42
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
48
- THE SOFTWARE.
49
-
50
- ---
51
-
52
- C library of Limited memory BFGS (L-BFGS).
53
-
54
- Copyright (c) 1990, Jorge Nocedal
55
- Copyright (c) 2007-2010 Naoaki Okazaki
56
- All rights reserved.
57
-
58
- Permission is hereby granted, free of charge, to any person obtaining a copy
59
- of this software and associated documentation files (the "Software"), to deal
60
- in the Software without restriction, including without limitation the rights
61
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
62
- copies of the Software, and to permit persons to whom the Software is
63
- furnished to do so, subject to the following conditions:
64
-
65
- The above copyright notice and this permission notice shall be included in
66
- all copies or substantial portions of the Software.
67
-
68
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
69
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
70
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
71
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
72
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
73
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
74
- THE SOFTWARE.
Binary file
Binary file
data/vendor/libcmfrec.so DELETED
Binary file