midas-edge 0.5.0 → 0.5.1

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: 26505328048e0f4f78f7bba8bf8a9362fa2937623c199770b252ccead21e1cef
4
- data.tar.gz: 52b93dd03d32c353533b10efa459a149f67242d1430e786d0487a29974231807
3
+ metadata.gz: 0d21103c3d31faae0c5a22a3909c2594b9d406ca6d971927b59955821517b477
4
+ data.tar.gz: 07450a43b2ad27bda97545b813cc0766c55802e2ef565d1387de0a5f7307c04c
5
5
  SHA512:
6
- metadata.gz: 1075c67b8cbe336ac737d694464877a461251984ed3e22704cd87a3e9d04f29be262c38d030960243a28d9b6e327d7ea1615987c757976534526f7cedb265a84
7
- data.tar.gz: efba3c9e2def1feae8ced454c46a3a68d1285fd8fca53f230d967f57da61c47176e6319ed21b0925ff85750f12bd1e215382e87fe3eb592902e61ba481700414
6
+ metadata.gz: 0db11f6d7de5d528d81b180b5e759ec36e794ebce62ddcf748c2db9f502ac0c4b6f95673b197f30c48564baeb36a6089b4f3ef599249070088a4057f03b57b51
7
+ data.tar.gz: a7a886546d177686b173e8fe2b70af54c67ce265095304190442d5bb740386082f673c58d43a1416540d5c8e2fb31440b863fe9447be621e73e727debd2dd22d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.5.1 (2026-02-07)
2
+
3
+ - Added `partial_fit_predict` method
4
+ - Reduced memory usage
5
+
1
6
  ## 0.5.0 (2025-04-03)
2
7
 
3
8
  - Dropped support for Ruby < 3.2
data/NOTICE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  Copyright 2020 Rui Liu (liurui39660) and Siddharth Bhatia (bhatiasiddharth)
2
- Copyright 2020-2023 Andrew Kane
2
+ Copyright 2020-2026 Andrew Kane
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
data/README.md CHANGED
@@ -32,7 +32,7 @@ midas = Midas.new
32
32
  scores = midas.fit_predict(data)
33
33
  ```
34
34
 
35
- Higher scores are more anomalous. There is [not currently](https://github.com/bhatiasiddharth/MIDAS/issues/4) a defined threshold for anomalies.
35
+ Higher scores are more anomalous. There is [not currently](https://github.com/Stream-AD/MIDAS/issues/4) a defined threshold for anomalies.
36
36
 
37
37
  ## Parameters
38
38
 
data/ext/midas/ext.cpp CHANGED
@@ -1,4 +1,5 @@
1
1
  // stdlib
2
+ #include <cstdio>
2
3
  #include <iostream>
3
4
  #include <vector>
4
5
 
@@ -14,39 +15,42 @@
14
15
  // numo
15
16
  #include "numo.hpp"
16
17
 
17
- void load_array(std::vector<int>& src, std::vector<int>& dst, std::vector<int>& times, numo::Int32 input, bool directed) {
18
+ template<typename T>
19
+ void load_array(T& midas, Rice::Array input, bool directed, std::vector<float>& result) {
20
+ for (auto r : input) {
21
+ Rice::Array row(r);
22
+ if (row.size() != 3) {
23
+ throw Rice::Exception(rb_eArgError, "Bad shape");
24
+ }
25
+ int s = Rice::detail::From_Ruby<int>().convert(row[0].value());
26
+ int d = Rice::detail::From_Ruby<int>().convert(row[1].value());
27
+ int t = Rice::detail::From_Ruby<int>().convert(row[2].value());
28
+ result.push_back(midas(s, d, t));
29
+ if (!directed) {
30
+ result.push_back(midas(d, s, t));
31
+ }
32
+ }
33
+ }
34
+
35
+ template<typename T>
36
+ void load_numo_array(T& midas, numo::Int32 input, bool directed, std::vector<float>& result) {
18
37
  auto shape = input.shape();
38
+ if (input.ndim() == 1 && shape[0] == 0) {
39
+ return;
40
+ }
19
41
  if (input.ndim() != 2 || shape[1] != 3) {
20
42
  throw Rice::Exception(rb_eArgError, "Bad shape");
21
43
  }
22
44
 
23
- auto input_ptr = input.read_ptr();
24
- auto n = shape[0];
25
45
  auto sz = input.size();
26
-
27
- if (directed) {
28
- src.reserve(n);
29
- dst.reserve(n);
30
- times.reserve(n);
31
-
32
- for (size_t i = 0; i < sz; i += 3) {
33
- src.push_back(input_ptr[i]);
34
- dst.push_back(input_ptr[i + 1]);
35
- times.push_back(input_ptr[i + 2]);
36
- }
37
- } else {
38
- src.reserve(n * 2);
39
- dst.reserve(n * 2);
40
- times.reserve(n * 2);
41
-
42
- for (size_t i = 0; i < sz; i += 3) {
43
- src.push_back(input_ptr[i]);
44
- dst.push_back(input_ptr[i + 1]);
45
- times.push_back(input_ptr[i + 2]);
46
-
47
- src.push_back(input_ptr[i + 1]);
48
- dst.push_back(input_ptr[i]);
49
- times.push_back(input_ptr[i + 2]);
46
+ auto input_ptr = input.read_ptr();
47
+ for (size_t i = 0; i < sz; i += 3) {
48
+ int s = input_ptr[i];
49
+ int d = input_ptr[i + 1];
50
+ int t = input_ptr[i + 2];
51
+ result.push_back(midas(s, d, t));
52
+ if (!directed) {
53
+ result.push_back(midas(d, s, t));
50
54
  }
51
55
  }
52
56
  }
@@ -54,59 +58,42 @@ void load_array(std::vector<int>& src, std::vector<int>& dst, std::vector<int>&
54
58
  // load_data from main.cpp
55
59
  // modified to throw std::runtime_error when cannot find file
56
60
  // instead of exiting
57
- void load_file(std::vector<int>& src, std::vector<int>& dst, std::vector<int>& times, Rice::String input_file, bool directed) {
61
+ template<typename T>
62
+ void load_file(T& midas, Rice::String input_file, bool directed, std::vector<float>& result) {
58
63
  FILE* infile = fopen(input_file.c_str(), "r");
59
64
  if (infile == NULL) {
60
65
  throw std::runtime_error("Could not read file: " + input_file.str());
61
66
  }
62
67
 
63
68
  int s, d, t;
64
-
65
- if (directed) {
66
- while (fscanf(infile, "%d,%d,%d", &s, &d, &t) == 3) {
67
- src.push_back(s);
68
- dst.push_back(d);
69
- times.push_back(t);
70
- }
71
- } else {
72
- while (fscanf(infile, "%d,%d,%d", &s, &d, &t) == 3) {
73
- src.push_back(s);
74
- dst.push_back(d);
75
- times.push_back(t);
76
-
77
- src.push_back(d);
78
- dst.push_back(s);
79
- times.push_back(t);
69
+ while (fscanf(infile, "%d,%d,%d", &s, &d, &t) == 3) {
70
+ result.push_back(midas(s, d, t));
71
+ if (!directed) {
72
+ result.push_back(midas(d, s, t));
80
73
  }
81
74
  }
82
75
 
83
76
  fclose(infile);
84
77
  }
85
78
 
86
- Rice::Object fit_predict(std::vector<int>& src, std::vector<int>& dst, std::vector<int>& times, int num_rows, int num_buckets, float factor, float threshold, bool relations, int seed) {
87
- srand(seed);
88
- size_t n = src.size();
89
-
90
- auto ary = numo::SFloat({n});
91
- auto result = ary.write_ptr();
92
-
93
- if (!std::isnan(threshold)) {
94
- MIDAS::FilteringCore midas(num_rows, num_buckets, threshold, factor);
95
- for (size_t i = 0; i < n; i++) {
96
- result[i] = midas(src[i], dst[i], times[i]);
97
- }
98
- } else if (relations) {
99
- MIDAS::RelationalCore midas(num_rows, num_buckets, factor);
100
- for (size_t i = 0; i < n; i++) {
101
- result[i] = midas(src[i], dst[i], times[i]);
102
- }
79
+ template<typename T>
80
+ Rice::Object fit_predict(T& midas, Rice::Object input, bool directed) {
81
+ std::vector<float> result;
82
+ if (input.is_a(rb_cString)) {
83
+ load_file(midas, input, directed, result);
84
+ // TODO uncomment in 0.6.0
85
+ // } else if (input.is_instance_of(rb_cArray)) {
86
+ // load_array(midas, input, directed, result);
103
87
  } else {
104
- MIDAS::NormalCore midas(num_rows, num_buckets);
105
- for (size_t i = 0; i < n; i++) {
106
- result[i] = midas(src[i], dst[i], times[i]);
107
- }
88
+ load_numo_array(midas, input, directed, result);
108
89
  }
109
90
 
91
+ size_t n = result.size();
92
+ auto ary = numo::SFloat({n});
93
+ auto out = ary.write_ptr();
94
+ for (size_t i = 0; i < n; i++) {
95
+ out[i] = result[i];
96
+ }
110
97
  return ary;
111
98
  }
112
99
 
@@ -114,16 +101,35 @@ extern "C"
114
101
  void Init_ext() {
115
102
  auto rb_mMidas = Rice::define_module("Midas");
116
103
 
104
+ // TODO make seed part of Core classes
117
105
  Rice::define_class_under(rb_mMidas, "Detector")
118
106
  .define_function(
119
- "_fit_predict",
120
- [](Rice::Object input, int num_rows, int num_buckets, float factor, float threshold, bool relations, bool directed, int seed) {
121
- std::vector<int> src, dst, times;
122
- if (input.is_a(rb_cString)) {
123
- load_file(src, dst, times, input, directed);
124
- } else {
125
- load_array(src, dst, times, input, directed);
126
- }
127
- return fit_predict(src, dst, times, num_rows, num_buckets, factor, threshold, relations, seed);
107
+ "_set_seed",
108
+ [](int seed) {
109
+ srand(seed);
110
+ });
111
+
112
+ Rice::define_class_under<MIDAS::NormalCore>(rb_mMidas, "NormalCore")
113
+ .define_constructor(Rice::Constructor<MIDAS::NormalCore, int, int>())
114
+ .define_method(
115
+ "fit_predict",
116
+ [](MIDAS::NormalCore& self, Rice::Object input, bool directed) {
117
+ return fit_predict(self, input, directed);
118
+ });
119
+
120
+ Rice::define_class_under<MIDAS::RelationalCore>(rb_mMidas, "RelationalCore")
121
+ .define_constructor(Rice::Constructor<MIDAS::RelationalCore, int, int, float>())
122
+ .define_method(
123
+ "fit_predict",
124
+ [](MIDAS::RelationalCore& self, Rice::Object input, bool directed) {
125
+ return fit_predict(self, input, directed);
126
+ });
127
+
128
+ Rice::define_class_under<MIDAS::FilteringCore>(rb_mMidas, "FilteringCore")
129
+ .define_constructor(Rice::Constructor<MIDAS::FilteringCore, int, int, float, float>())
130
+ .define_method(
131
+ "fit_predict",
132
+ [](MIDAS::FilteringCore& self, Rice::Object input, bool directed) {
133
+ return fit_predict(self, input, directed);
128
134
  });
129
135
  }
@@ -1,6 +1,14 @@
1
1
  module Midas
2
2
  class Detector
3
- def initialize(rows: 2, buckets: 769, alpha: 0.5, threshold: nil, relations: true, directed: true, seed: 0)
3
+ def initialize(
4
+ rows: 2,
5
+ buckets: 769,
6
+ alpha: 0.5,
7
+ threshold: nil,
8
+ relations: true,
9
+ directed: true,
10
+ seed: 0
11
+ )
4
12
  @rows = rows
5
13
  @buckets = buckets
6
14
  @alpha = alpha
@@ -11,7 +19,27 @@ module Midas
11
19
  end
12
20
 
13
21
  def fit_predict(x)
14
- _fit_predict(x, @rows, @buckets, @alpha, @threshold || Float::NAN, @relations, @directed, @seed)
22
+ @core = nil # reset core
23
+ partial_fit_predict(x)
24
+ end
25
+
26
+ # TODO better name
27
+ def partial_fit_predict(x)
28
+ @core ||= core
29
+ @core.fit_predict(x, @directed)
30
+ end
31
+
32
+ private
33
+
34
+ def core
35
+ _set_seed(@seed)
36
+ if @threshold && !@threshold.to_f.nan?
37
+ FilteringCore.new(@rows, @buckets, @threshold, @alpha)
38
+ elsif @relations
39
+ RelationalCore.new(@rows, @buckets, @alpha)
40
+ else
41
+ NormalCore.new(@rows, @buckets)
42
+ end
15
43
  end
16
44
  end
17
45
  end
data/lib/midas/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Midas
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.1"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: midas-edge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rice
@@ -77,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
77
  - !ruby/object:Gem::Version
78
78
  version: '0'
79
79
  requirements: []
80
- rubygems_version: 3.6.2
80
+ rubygems_version: 4.0.3
81
81
  specification_version: 4
82
82
  summary: Edge stream anomaly detection for Ruby
83
83
  test_files: []