feedback_machines 0.1.4-x86_64-darwin
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 +7 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE.txt +21 -0
- data/README.md +200 -0
- data/ext/feedback_machines_native/core/CHANGELOG.md +22 -0
- data/ext/feedback_machines_native/core/README.md +30 -0
- data/ext/feedback_machines_native/extconf.rb +2 -0
- data/lib/feedback_machines/version.rb +4 -0
- data/lib/feedback_machines.rb +17 -0
- data/lib/feedback_machines_native/feedback_machines_native.bundle +0 -0
- data/sig/feedback_machines.rbs +11 -0
- metadata +61 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9bba38af181c11500691318d9cfab4f0e6ee85eb54baf37bdd4de3ac2b88d098
|
|
4
|
+
data.tar.gz: '05282062d7fd1ea79f3a0e92140e7c18e0bd313d2ffdea99ba21a15a09fd3438'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: '0585d4e877d252a734effb90ccc9033653566871d0a9e97b99a29fa88a68f9b25c543a33c3231ebb64cf86ca029e73ccac4b8b16c74221c60e0e3385ce55aeca'
|
|
7
|
+
data.tar.gz: 69b694b2e60ae5f0cf0e89ac5359b7ad91dc9cbbc4d44a9886ee414f9096a1dc27fc1698721997fd5567f16eecbe940d37d7c60d5221a9ebb03c39fbaecdaa93
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.4](https://github.com/seuros/feedback_machines/compare/feedback_machines/v0.1.3...feedback_machines/v0.1.4) (2026-11-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* crates.io keywords and categories for the core ([fa5503e](https://github.com/seuros/feedback_machines/commit/fa5503e226b815de6da68bf3710f3b108c27ce2c))
|
|
9
|
+
|
|
10
|
+
## [0.1.3](https://github.com/seuros/feedback_machines/compare/feedback_machines/v0.1.2...feedback_machines/v0.1.3) (2026-11-06)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* keep test sources out of the packaged gem and crate ([d507afd](https://github.com/seuros/feedback_machines/commit/d507afd8ea54ec317a9b3dccbeb0cb4e3d455f6f))
|
|
16
|
+
|
|
17
|
+
## [0.1.2](https://github.com/seuros/feedback_machines/compare/feedback_machines/v0.1.1...feedback_machines/v0.1.2) (2026-11-06)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* cross builds no longer compile for the host ([c724a14](https://github.com/seuros/feedback_machines/commit/c724a1461d0e5e0b8a710ce12222a14f1c1f4013))
|
|
23
|
+
|
|
24
|
+
## [0.1.1](https://github.com/seuros/feedback_machines/compare/feedback_machines-v0.1.0...feedback_machines/v0.1.1) (2026-11-06)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* initial extraction ([d273a1e](https://github.com/seuros/feedback_machines/commit/d273a1e1a17979e275ded3d02618e316e45d34f3))
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abdelkader Boudih
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# FeedbackMachines
|
|
2
|
+
|
|
3
|
+
> Proportion, memory, and anticipation. The three things a threshold doesn't have. 🎛️
|
|
4
|
+
|
|
5
|
+
Two small control loops for Ruby. A `Pid` turns "how far off are we" into "how much to adjust", bounded so it can never ask for more than the actuator can give. A `Hysteresis` band turns a noisy reading into a clean on/off decision that doesn't flap. You supply the measurements and the clock. They supply the arithmetic.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
gem 'feedback_machines'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# Keep the job queue near 50 pending by adding or removing workers.
|
|
15
|
+
pid = FeedbackMachines::Pid.new(p: 0.5, i: 0.1, min: -4.0, max: 4.0)
|
|
16
|
+
|
|
17
|
+
loop do
|
|
18
|
+
error = queue.depth - 50
|
|
19
|
+
delta = pid.step(error: error, elapsed: seconds_since_last_tick)
|
|
20
|
+
pool.resize(pool.size + delta.round) if delta
|
|
21
|
+
sleep 1
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
# Flip into shed mode above 80% CPU, back out below 60%, nothing in between.
|
|
27
|
+
band = FeedbackMachines::Hysteresis.new(min: 0.6, max: 0.8)
|
|
28
|
+
|
|
29
|
+
shed_mode = band.observe(cpu.utilization) == :high
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## A Message to the Resistance
|
|
33
|
+
|
|
34
|
+
So the queue backed up and you wrote the autoscaler. If depth is over 100, add a worker. If it's under 10, remove one. Ship it.
|
|
35
|
+
|
|
36
|
+
Monday morning the depth hits 101. A worker spins up. It takes 20 seconds to boot, by which time depth is 140 and the rule has fired three more times. Four new workers drain the queue to zero in a minute. Now depth is under 10, so the rule removes them, one per tick, and the queue backs up again. Your dashboard looks like a seismograph and ops has named the pattern after you.
|
|
37
|
+
|
|
38
|
+
The problem isn't the rule. The problem is that a threshold has no memory and no sense of proportion. It can't tell "a little over" from "catastrophically over", and it can't tell "climbing" from "already falling". Control engineers solved this in the 1920s for steering ships. A proportional term scales the response to the size of the error. An integral term remembers how long you've been off. A derivative term notices which way you're heading. Clamp the output so you never ask for forty workers at once, and stop accumulating when you're already clamped so the controller doesn't owe the universe an impossible correction later.
|
|
39
|
+
|
|
40
|
+
That is what `Pid` does. `Hysteresis` is the same cure for on/off decisions: two thresholds instead of one, so a reading hovering at 79.9% doesn't toggle shed mode fifty times a minute.
|
|
41
|
+
|
|
42
|
+
## Chapter 1: Stardate 2025.186, the AMS Maxwell
|
|
43
|
+
|
|
44
|
+
> **AMS Maxwell**, NCC-4208, *Maxwell*-class guidance tender.
|
|
45
|
+
> Correct the heading without teaching the thrusters to oscillate forever.
|
|
46
|
+
|
|
47
|
+
The tender doesn't fly the ship. It takes a heading error and a sampling interval and returns a thruster command the engines can actually execute. The helm reads the compass and fires the thrusters. The tender just makes sure that when the ship is one degree off, the correction is one degree's worth, and when it has been two degrees off for ten minutes, somebody finally does something about it.
|
|
48
|
+
|
|
49
|
+
On the bridge, a cadet proposes:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
def correct_heading
|
|
53
|
+
if compass.error > 0
|
|
54
|
+
thrusters.fire(:port, 100)
|
|
55
|
+
elsif compass.error < 0
|
|
56
|
+
thrusters.fire(:starboard, 100)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
"That," says the navigator, "is how a ship ends up pointing everywhere except where it's going."
|
|
62
|
+
|
|
63
|
+
## The Flight Plan
|
|
64
|
+
|
|
65
|
+
The hysteresis band has two states and refuses to change its mind in the middle.
|
|
66
|
+
|
|
67
|
+
```mermaid
|
|
68
|
+
stateDiagram-v2
|
|
69
|
+
[*] --> low
|
|
70
|
+
low --> high: reading >= max
|
|
71
|
+
low --> low: reading < max
|
|
72
|
+
high --> low: reading <= min
|
|
73
|
+
high --> high: reading > min
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
And one tick of the queue autoscaler:
|
|
77
|
+
|
|
78
|
+
```mermaid
|
|
79
|
+
sequenceDiagram
|
|
80
|
+
participant Tick
|
|
81
|
+
participant Queue
|
|
82
|
+
participant Pid
|
|
83
|
+
participant Pool
|
|
84
|
+
|
|
85
|
+
Tick->>Queue: depth?
|
|
86
|
+
Queue-->>Tick: 70
|
|
87
|
+
Tick->>Pid: step(error: 20, elapsed: 1.0)
|
|
88
|
+
Note over Pid: p: 0.5 × 20 = 10, clamped to 4
|
|
89
|
+
Pid-->>Tick: 4.0
|
|
90
|
+
Tick->>Pool: resize(+4)
|
|
91
|
+
|
|
92
|
+
Tick->>Queue: depth?
|
|
93
|
+
Queue-->>Tick: 54
|
|
94
|
+
Tick->>Pid: step(error: 4, elapsed: 1.0)
|
|
95
|
+
Note over Pid: 2 + integral 0.4
|
|
96
|
+
Pid-->>Tick: 2.4
|
|
97
|
+
Tick->>Pool: resize(+2)
|
|
98
|
+
|
|
99
|
+
Tick->>Queue: depth?
|
|
100
|
+
Queue-->>Tick: 48
|
|
101
|
+
Tick->>Pid: step(error: -2, elapsed: 1.0)
|
|
102
|
+
Pid-->>Tick: -0.8
|
|
103
|
+
Tick->>Pool: resize(-1)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## How the Pid Works
|
|
107
|
+
|
|
108
|
+
### Proportional
|
|
109
|
+
|
|
110
|
+
With only `p` set, the output is the error times the gain, clamped to the limits.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
pid = FeedbackMachines::Pid.new(p: 1.0, min: -10.0, max: 10.0)
|
|
114
|
+
|
|
115
|
+
pid.step(error: 3.0, elapsed: 1.0) # => 3.0
|
|
116
|
+
pid.step(error: -3.0, elapsed: 1.0) # => -3.0
|
|
117
|
+
pid.step(error: 30.0, elapsed: 1.0) # => 10.0, clamped
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Integral, without windup
|
|
121
|
+
|
|
122
|
+
Add `i` and the controller starts remembering. Each step adds `error × elapsed` to a running total. But when the output is already pinned at a limit, the total stops growing. That's the part that keeps a saturated actuator from accumulating a debt it can never repay.
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
pid = FeedbackMachines::Pid.new(p: 1.0, i: 1.0, min: -10.0, max: 10.0)
|
|
126
|
+
|
|
127
|
+
pid.step(error: 100.0, elapsed: 1.0) # => 10.0, clamped, integral frozen
|
|
128
|
+
pid.step(error: 1.0, elapsed: 1.0) # => 2.0, not 101.0
|
|
129
|
+
pid.step(error: 1.0, elapsed: 1.0) # => 3.0, integral climbing normally now
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Without that guard, the second step would try to pay back a hundred units of accumulated error and overshoot for the next minute.
|
|
133
|
+
|
|
134
|
+
### Derivative
|
|
135
|
+
|
|
136
|
+
Add `d` and the controller reacts to the *rate* of change. The first step has nothing to compare against, so the derivative term is zero.
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
pid = FeedbackMachines::Pid.new(p: 0.0, d: 1.0, min: -100.0, max: 100.0)
|
|
140
|
+
|
|
141
|
+
pid.step(error: 5.0, elapsed: 1.0) # => 0.0, no history yet
|
|
142
|
+
pid.step(error: 6.0, elapsed: 0.5) # => 2.0, rose by 1 over 0.5s
|
|
143
|
+
pid.step(error: 6.0, elapsed: 0.5) # => 0.0, holding steady
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Bad samples don't poison the history
|
|
147
|
+
|
|
148
|
+
A zero interval, a NaN reading, or an infinite one returns `nil` and leaves the controller exactly as it was. Skip that tick and carry on.
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
pid = FeedbackMachines::Pid.new(p: 1.0, i: 1.0, min: -10.0, max: 10.0)
|
|
152
|
+
|
|
153
|
+
pid.step(error: 2.0, elapsed: 1.0) # => 4.0
|
|
154
|
+
pid.step(error: 2.0, elapsed: 0.0) # => nil
|
|
155
|
+
pid.step(error: Float::NAN, elapsed: 1.0) # => nil
|
|
156
|
+
pid.step(error: 2.0, elapsed: 1.0) # => 6.0, as if the two bad ticks never happened
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## How the Band Works
|
|
160
|
+
|
|
161
|
+
Two thresholds. The band starts `:low` and only goes `:high` when a reading reaches `max`. It only comes back down when a reading falls to `min`. Everything in between keeps whatever it was.
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
band = FeedbackMachines::Hysteresis.new(min: 0.6, max: 0.8)
|
|
165
|
+
|
|
166
|
+
band.observe(0.7) # => :low, started low, not at max yet
|
|
167
|
+
band.observe(0.85) # => :high
|
|
168
|
+
band.observe(0.7) # => :high, in the dead zone, holds
|
|
169
|
+
band.observe(0.79) # => :high, still holds
|
|
170
|
+
band.observe(0.6) # => :low, reached min
|
|
171
|
+
band.observe(0.7) # => :low, dead zone again, holds
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A non-finite reading returns `nil` and the band keeps its state.
|
|
175
|
+
|
|
176
|
+
## The Rules of the Tender
|
|
177
|
+
|
|
178
|
+
- **Gains are finite and nonnegative.** Negative or infinite `p`, `i`, or `d` raises `ArgumentError`. Flip the sign of your error instead.
|
|
179
|
+
- **Limits need `min < max`.** Equal or reversed raises `ArgumentError`. Same for the band.
|
|
180
|
+
- **`elapsed` is a duration in seconds**, not a timestamp. Negative or NaN raises `ArgumentError`. Zero returns `nil`.
|
|
181
|
+
- **Integers are fine.** `p: 1, min: 0, max: 1` works. Everything is treated as a float.
|
|
182
|
+
- **Frozen means frozen.** `freeze` a controller or a band and every `step` or `observe` raises `FrozenError`.
|
|
183
|
+
- **Each controller is its own loop.** Two `Pid` instances share nothing, and they can't be `dup`ed or `clone`d.
|
|
184
|
+
|
|
185
|
+
## What You Think You're Doing vs Reality
|
|
186
|
+
|
|
187
|
+
### You Think: "I'll just add a worker when the queue is over 100."
|
|
188
|
+
### Reality: You built an oscillator with a twenty-second period.
|
|
189
|
+
|
|
190
|
+
A threshold reacts to where you are. It has no idea how fast you got there or how long you've been stuck. By the time the action lands, the situation has moved, so the action is wrong, so the next action overcorrects. Proportion, memory, and anticipation are the three things a threshold lacks, and they're the three letters in PID.
|
|
191
|
+
|
|
192
|
+
## What's Yours
|
|
193
|
+
|
|
194
|
+
The controller doesn't read sensors, run timers, or touch actuators. You decide what "error" means, how often to sample, what to do with the number that comes back, and what gains make sense for your system. Start with `p` alone, add `i` when the system settles short of the target, add `d` only if it overshoots.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT License. See [LICENSE.txt](LICENSE.txt) for details.
|
|
199
|
+
|
|
200
|
+
**Remember: A threshold tells you that you're off course. A controller tells you by how much, for how long, and which way you're drifting.**
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.2](https://github.com/seuros/feedback_machines/compare/feedback-machines-v0.2.1...feedback-machines-v0.2.2) (2026-11-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* crates.io keywords and categories for the core ([fa5503e](https://github.com/seuros/feedback_machines/commit/fa5503e226b815de6da68bf3710f3b108c27ce2c))
|
|
9
|
+
|
|
10
|
+
## [0.2.1](https://github.com/seuros/feedback_machines/compare/feedback-machines-v0.2.0...feedback-machines-v0.2.1) (2026-11-06)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* keep test sources out of the packaged gem and crate ([d507afd](https://github.com/seuros/feedback_machines/commit/d507afd8ea54ec317a9b3dccbeb0cb4e3d455f6f))
|
|
16
|
+
|
|
17
|
+
## [0.2.0](https://github.com/seuros/feedback_machines/compare/feedback-machines-v0.1.0...feedback-machines-v0.2.0) (2026-11-06)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* initial extraction ([d273a1e](https://github.com/seuros/feedback_machines/commit/d273a1e1a17979e275ded3d02618e316e45d34f3))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# feedback-machines
|
|
2
|
+
|
|
3
|
+
> **AMS Maxwell**, NCC-4208, *Maxwell*-class guidance tender.
|
|
4
|
+
|
|
5
|
+
Engine-room flight manual: turn measured drift into bounded control recommendations.
|
|
6
|
+
|
|
7
|
+
Dependency-free, allocation-free `no_std` controllers.
|
|
8
|
+
|
|
9
|
+
PID accepts explicit error and elapsed time, rejects nonfinite inputs, zero
|
|
10
|
+
sampling intervals, and overflowing intermediate calculations without changing
|
|
11
|
+
state. Derivative is taken on error; the first sample has zero derivative.
|
|
12
|
+
Output is clamped and conditional integration prevents windup while saturation
|
|
13
|
+
and error point in the same direction. There is no derivative filtering,
|
|
14
|
+
auto-tuning, or stability guarantee. Gains must be finite and nonnegative;
|
|
15
|
+
reverse the error convention for inverse-action systems.
|
|
16
|
+
|
|
17
|
+
Hysteresis switches high at/above its upper threshold and low at/below its lower
|
|
18
|
+
threshold. It retains state inside the band. Unlike flow watermarks, these are
|
|
19
|
+
generic signed floating-point measurements, not queue occupancy.
|
|
20
|
+
|
|
21
|
+
```rust
|
|
22
|
+
use core::time::Duration;
|
|
23
|
+
use feedback_machines::{Gains, Limits, Pid};
|
|
24
|
+
# fn example() -> Option<()> {
|
|
25
|
+
let mut controller = Pid::new(Gains::new(2.0, 0.0, 0.0)?, Limits::new(0.0, 10.0)?);
|
|
26
|
+
assert_eq!(controller.step(2.0, Duration::from_secs(1)), Some(4.0));
|
|
27
|
+
# Some(())
|
|
28
|
+
# }
|
|
29
|
+
# assert_eq!(example(), Some(()));
|
|
30
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'feedback_machines/version'
|
|
3
|
+
require 'feedback_machines_native/feedback_machines_native'
|
|
4
|
+
|
|
5
|
+
module FeedbackMachines
|
|
6
|
+
class Pid
|
|
7
|
+
def self.new(p:, min:, max:, i: 0.0, d: 0.0) = _new(p, i, d, min, max)
|
|
8
|
+
private_class_method :_new
|
|
9
|
+
def step(error:, elapsed:) = _step(error, elapsed)
|
|
10
|
+
private :_step
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Hysteresis
|
|
14
|
+
def self.new(min:, max:) = _new(min, max)
|
|
15
|
+
private_class_method :_new
|
|
16
|
+
end
|
|
17
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module FeedbackMachines
|
|
2
|
+
VERSION: String
|
|
3
|
+
class Pid
|
|
4
|
+
def self.new: (p: Float, min: Float, max: Float, ?i: Float, ?d: Float) -> instance
|
|
5
|
+
def step: (error: Float, elapsed: Float) -> Float?
|
|
6
|
+
end
|
|
7
|
+
class Hysteresis
|
|
8
|
+
def self.new: (min: Float, max: Float) -> instance
|
|
9
|
+
def observe: (Float) -> (:low | :high)?
|
|
10
|
+
end
|
|
11
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: feedback_machines
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.4
|
|
5
|
+
platform: x86_64-darwin
|
|
6
|
+
authors:
|
|
7
|
+
- Abdelkader Boudih
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-11-06 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Thin Ruby bindings to the standalone feedback-machines core; sampling,
|
|
14
|
+
tuning, and actuation belong to the caller.
|
|
15
|
+
email:
|
|
16
|
+
- oss@seuros.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE.txt
|
|
23
|
+
- README.md
|
|
24
|
+
- ext/feedback_machines_native/core/CHANGELOG.md
|
|
25
|
+
- ext/feedback_machines_native/core/README.md
|
|
26
|
+
- ext/feedback_machines_native/extconf.rb
|
|
27
|
+
- lib/feedback_machines.rb
|
|
28
|
+
- lib/feedback_machines/version.rb
|
|
29
|
+
- lib/feedback_machines_native/feedback_machines_native.bundle
|
|
30
|
+
- sig/feedback_machines.rbs
|
|
31
|
+
homepage: https://github.com/seuros/feedback_machines
|
|
32
|
+
licenses:
|
|
33
|
+
- MIT
|
|
34
|
+
metadata:
|
|
35
|
+
source_code_uri: https://github.com/seuros/feedback_machines
|
|
36
|
+
rubygems_mfa_required: 'true'
|
|
37
|
+
cargo_crate_name: feedback_machines_native
|
|
38
|
+
cargo_manifest_path: ext/feedback_machines_native/ffi/Cargo.toml
|
|
39
|
+
post_install_message:
|
|
40
|
+
rdoc_options: []
|
|
41
|
+
require_paths:
|
|
42
|
+
- lib
|
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '4.0'
|
|
48
|
+
- - "<"
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: 4.1.dev
|
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '0'
|
|
56
|
+
requirements: []
|
|
57
|
+
rubygems_version: 3.5.23
|
|
58
|
+
signing_key:
|
|
59
|
+
specification_version: 4
|
|
60
|
+
summary: Native PID and hysteresis controllers for Ruby
|
|
61
|
+
test_files: []
|