monotonic.rb 0.9.0 → 0.10.0
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 +4 -4
- data/CHANGELOG +15 -0
- data/README.md +39 -0
- data/lib/Monotonic/Measurement.rb +58 -0
- data/lib/Monotonic/VERSION.rb +1 -1
- data/monotonic.rb.gemspec +1 -1
- data/test/Monotonic/Measurement_test.rb +61 -0
- data/test/gemspec_test.rb +10 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31e7ee27a90e0a41a51e5739b7024b16a2d7e10f71002bde23362bc90ccef9e5
|
|
4
|
+
data.tar.gz: 48b392a44f8d69bfe6ce2ecaee02b99d47645a148317c97c304188b0584ba3d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32e4581c05f382de37da5794488759f1c7206a54f79f1ad593d1809b9a53dde4407a17e4d3ab4eab8b4eaeaa18caea85dfc4300ab219db1e3291bcacb79987a9
|
|
7
|
+
data.tar.gz: 308211ccff21283d9ff5a851396ff660df6f605d3ffe607176c26c53075bfc930f3bbfacc5edfee758f11b6e702d7c5bf46de88e3b3193aa3636ef956dcd7664
|
data/CHANGELOG
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 20260817
|
|
4
|
+
|
|
5
|
+
0.10.0: + Monotonic::Measurement arithmetic, so that a speedup is a ratio which knows how well it is known.
|
|
6
|
+
|
|
7
|
+
1. + Monotonic::Measurement#+ and #-: two intervals give an interval, their doubts combining in quadrature through Measurand rather than by addition. A duration joins as exact, having none of its own, and a bare number is refused, having no unit to be added to.
|
|
8
|
+
2. + Monotonic::Measurement#*: scales by a number, the uncertainty scaling with it. Two intervals are refused, there being no unit of time squared, as Duration::Common refuses them.
|
|
9
|
+
3. + Monotonic::Measurement#/: by a number it stays an interval; by another interval the units cancel and a Measurand is left, dimensionless and carrying the doubt of both. That ratio is t1/tn, and the reason for the release: a speedup stated without knowing whether it is real is worth little.
|
|
10
|
+
4. + Monotonic::Measurement.from: builds from a measurand whose uncertainty has been propagated rather than measured, so that what comes back from arithmetic carries neither an instrument's floor nor a drift still to be applied a second time.
|
|
11
|
+
5. ~ monotonic.rb.gemspec: /measurand >= 0.1.0/measurand >= 0.1.2/. Measurand#/ divided two integer values as integers until 0.1.2, so a ratio of two nanosecond counts came back as 0 rather than 0.8788. #/ is the point of this release and does not work below 0.1.2, which the added tests demonstrate: they fail twice against 0.1.0 and pass against 0.1.2.
|
|
12
|
+
6. + test/Monotonic/Measurement_test.rb: each operator and what it refuses, and the ratio — that it is dimensionless, that it divides exactly, and that it carries the doubt of both operands.
|
|
13
|
+
7. + test/gemspec_test.rb: that each runtime dependency declares a minimum, and which minimum. The existing test asserted the names alone, which is what let 5 above happen: the gemspec named measurand and said nothing about needing a measurand that works. Asserting the whole map means a minimum cannot move without the test being made to agree.
|
|
14
|
+
8. ~ README.md: + the arithmetic to the Description, + a Monotonic::Measurement arithmetic section to Usage, and + a blank line after require in two examples, the other three having had one.
|
|
15
|
+
9. ~ Monotonic::VERSION: /0.9.0/0.10.0/
|
|
16
|
+
|
|
17
|
+
|
|
3
18
|
## 20260816
|
|
4
19
|
|
|
5
20
|
0.9.0: Use measurand gem, + Monotonic::Measurement and Monotonic::Timer.floor.
|
data/README.md
CHANGED
|
@@ -51,6 +51,15 @@ measurement.to_duration.to_microseconds.to_f
|
|
|
51
51
|
|
|
52
52
|
Nothing is capped: a day resolved to a hundred nanoseconds prints every figure it has, the absurdity being the mismatch between what the instrument resolves and what was asked of it, which is better seen than rounded away. The floor is a floor and not an error bar — the tail is unbounded and one-sided, the scheduler being free to take the processor away between the two readings — so repeat and take a robust statistic where that matters.
|
|
53
53
|
|
|
54
|
+
Measurements combine, and what may combine with what follows from the units. Two intervals add and subtract to an interval, their doubts going in quadrature; a duration joins in as exact, having none of its own; a bare number is refused, having no unit to be added to. Scaling by a number keeps the unit and scales the doubt with it, while multiplying two intervals is refused, there being no unit of time squared. Dividing one interval by another cancels the units and leaves a `Measurand` — dimensionless, and still knowing how well it is known, which is what a speedup is.
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
serial = Monotonic::Timer.time{run_on_one}
|
|
58
|
+
parallel = Monotonic::Timer.time{run_on_eight}
|
|
59
|
+
serial / parallel
|
|
60
|
+
# => Measurand(6.83, 0.00012), dimensionless
|
|
61
|
+
```
|
|
62
|
+
|
|
54
63
|
|
|
55
64
|
## Installation
|
|
56
65
|
|
|
@@ -143,6 +152,7 @@ measurement.to_s
|
|
|
143
152
|
|
|
144
153
|
```ruby
|
|
145
154
|
require 'monotonic.rb'
|
|
155
|
+
|
|
146
156
|
measurement = Monotonic::Timer.time do |timer|
|
|
147
157
|
i = 0
|
|
148
158
|
500_000.times{puts i += 1}
|
|
@@ -157,6 +167,7 @@ measurement.to_s
|
|
|
157
167
|
|
|
158
168
|
```ruby
|
|
159
169
|
require 'monotonic.rb'
|
|
170
|
+
|
|
160
171
|
timer = Monotonic::Timer.new
|
|
161
172
|
measurement = timer.time do
|
|
162
173
|
i = 0
|
|
@@ -167,6 +178,34 @@ measurement.to_s
|
|
|
167
178
|
```
|
|
168
179
|
|
|
169
180
|
|
|
181
|
+
### Monotonic::Measurement arithmetic
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
require 'monotonic.rb'
|
|
185
|
+
|
|
186
|
+
a = Monotonic::Timer.time{sleep 0.001}
|
|
187
|
+
b = Monotonic::Timer.time{sleep 0.002}
|
|
188
|
+
|
|
189
|
+
(a + b).to_s
|
|
190
|
+
# => "3.01543 ms"
|
|
191
|
+
|
|
192
|
+
(a * 3).to_s
|
|
193
|
+
# => "3.04629 ms"
|
|
194
|
+
|
|
195
|
+
a + Duration::Microseconds.new(500)
|
|
196
|
+
# => #<Monotonic::Measurement 1.51543 ms>, a duration being exact
|
|
197
|
+
|
|
198
|
+
b / a
|
|
199
|
+
# => Measurand(1.97, 0.00011), dimensionless
|
|
200
|
+
|
|
201
|
+
a * b
|
|
202
|
+
# => TypeError: there is no unit of time squared
|
|
203
|
+
|
|
204
|
+
a + 5
|
|
205
|
+
# => TypeError: expected an interval or a duration
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
|
|
170
209
|
## Contributing
|
|
171
210
|
|
|
172
211
|
1. Fork it ( https://github.com/thoran/monotonic.rb/fork )
|
|
@@ -23,6 +23,16 @@ module Monotonic
|
|
|
23
23
|
# not divide by ten.
|
|
24
24
|
UNITS = [[1, 'ns'], [1_000, 'us'], [1_000_000, 'ms'], [1_000_000_000, 's']]
|
|
25
25
|
|
|
26
|
+
class << self
|
|
27
|
+
# Built from a measurand rather than from a floor, for what comes back
|
|
28
|
+
# from arithmetic: its uncertainty has been propagated rather than
|
|
29
|
+
# measured, so it is neither an instrument's floor nor has a drift left to
|
|
30
|
+
# apply a second time.
|
|
31
|
+
def from(measurand)
|
|
32
|
+
allocate.tap{|measurement| measurement.send(:build, measurand)}
|
|
33
|
+
end
|
|
34
|
+
end # class << self
|
|
35
|
+
|
|
26
36
|
attr_reader :nanoseconds
|
|
27
37
|
attr_reader :drift
|
|
28
38
|
|
|
@@ -51,6 +61,35 @@ module Monotonic
|
|
|
51
61
|
measurand.uncertainty
|
|
52
62
|
end
|
|
53
63
|
|
|
64
|
+
# The arithmetic is Measurand's throughout — this only decides what may be
|
|
65
|
+
# combined with what, and hands back the right kind of thing. Two intervals
|
|
66
|
+
# add and subtract to an interval, their uncertainties going in quadrature.
|
|
67
|
+
def +(addend)
|
|
68
|
+
self.class.from(measurand + as_measurand(addend, :add))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def -(subtrahend)
|
|
72
|
+
self.class.from(measurand - as_measurand(subtrahend, :subtract))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Scaled by a number it stays an interval, the uncertainty scaling with it.
|
|
76
|
+
# Multiplied by another interval it would be time squared, which has no unit
|
|
77
|
+
# here, so it is refused as Duration::Common refuses it.
|
|
78
|
+
def *(multiplier)
|
|
79
|
+
if interval?(multiplier)
|
|
80
|
+
raise TypeError, "can't multiply #{self.class} by #{multiplier.class}: there is no unit of time squared"
|
|
81
|
+
end
|
|
82
|
+
self.class.from(measurand * multiplier)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Divided by a number it stays an interval. Divided by another interval the
|
|
86
|
+
# units cancel and a Measurand is left — dimensionless, but still knowing how
|
|
87
|
+
# well it is known, which is what a speedup or a rate is.
|
|
88
|
+
def /(divisor)
|
|
89
|
+
return measurand / divisor.measurand if divisor.respond_to?(:measurand)
|
|
90
|
+
self.class.from(measurand / divisor)
|
|
91
|
+
end
|
|
92
|
+
|
|
54
93
|
def unit
|
|
55
94
|
@unit ||= UNITS.reverse.find{|scale, _| @nanoseconds.abs >= scale} || UNITS.first
|
|
56
95
|
end
|
|
@@ -114,5 +153,24 @@ module Monotonic
|
|
|
114
153
|
@floor = floor
|
|
115
154
|
@drift = drift
|
|
116
155
|
end
|
|
156
|
+
|
|
157
|
+
# An interval is anything which knows its own doubt, or any duration, which
|
|
158
|
+
# is exact. A bare number is neither: it has no unit, so there is nothing to
|
|
159
|
+
# add it to.
|
|
160
|
+
def as_measurand(other, verb)
|
|
161
|
+
return other.measurand if other.respond_to?(:measurand)
|
|
162
|
+
return Measurand.new(other.to_nanoseconds.to_i) if other.respond_to?(:to_nanoseconds)
|
|
163
|
+
raise TypeError, "can't #{verb} #{other.class} #{verb == :add ? "to" : "from"} #{self.class}: expected an interval or a duration"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def interval?(other)
|
|
167
|
+
other.respond_to?(:measurand) || other.respond_to?(:to_nanoseconds)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def build(measurand)
|
|
171
|
+
@nanoseconds = measurand.value.round
|
|
172
|
+
@measurand = measurand
|
|
173
|
+
@floor = measurand.uncertainty
|
|
174
|
+
end
|
|
117
175
|
end
|
|
118
176
|
end
|
data/lib/Monotonic/VERSION.rb
CHANGED
data/monotonic.rb.gemspec
CHANGED
|
@@ -96,6 +96,67 @@ describe Monotonic::Measurement do
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
describe "arithmetic" do
|
|
100
|
+
it "adds two intervals to an interval, the doubt going in quadrature" do
|
|
101
|
+
sum = measurement(1_000_000) + measurement(2_000_000)
|
|
102
|
+
_(sum).must_be_instance_of(Monotonic::Measurement)
|
|
103
|
+
_(sum.nanoseconds).must_equal(3_000_000)
|
|
104
|
+
_(sum.uncertainty).must_be_close_to(Math.sqrt(84**2 + 84**2), 0.0001)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "subtracts likewise, the doubt growing rather than cancelling" do
|
|
108
|
+
difference = measurement(2_000_000) - measurement(1_000_000)
|
|
109
|
+
_(difference.nanoseconds).must_equal(1_000_000)
|
|
110
|
+
_(difference.uncertainty).must_be_close_to(Math.sqrt(84**2 + 84**2), 0.0001)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "takes a duration as exact, it having no doubt of its own" do
|
|
114
|
+
sum = measurement(1_000_000) + Duration::Microseconds.new(500)
|
|
115
|
+
_(sum.nanoseconds).must_equal(1_500_000)
|
|
116
|
+
_(sum.uncertainty).must_equal(84)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "refuses a bare number, which has no unit to add" do
|
|
120
|
+
_{measurement(1_000_000) + 5}.must_raise(TypeError)
|
|
121
|
+
_{measurement(1_000_000) - 5}.must_raise(TypeError)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "scales by a number, the uncertainty scaling with it" do
|
|
125
|
+
product = measurement(1_000_000) * 3
|
|
126
|
+
_(product.nanoseconds).must_equal(3_000_000)
|
|
127
|
+
_(product.uncertainty).must_equal(252)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "refuses to multiply two intervals, there being no unit of time squared" do
|
|
131
|
+
_{measurement(1_000_000) * measurement(2_000_000)}.must_raise(TypeError)
|
|
132
|
+
_{measurement(1_000_000) * Duration::Seconds.new(1)}.must_raise(TypeError)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "divides by a number and stays an interval" do
|
|
136
|
+
quotient = measurement(1_000_000) / 2
|
|
137
|
+
_(quotient).must_be_instance_of(Monotonic::Measurement)
|
|
138
|
+
_(quotient.nanoseconds).must_equal(500_000)
|
|
139
|
+
_(quotient.uncertainty).must_equal(42)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
describe "the ratio of two intervals" do
|
|
144
|
+
it "is dimensionless, the units having cancelled" do
|
|
145
|
+
ratio = measurement(9_579_583) / measurement(10_900_458)
|
|
146
|
+
_(ratio).must_be_instance_of(Measurand)
|
|
147
|
+
_(ratio.value.to_f).must_be_close_to(0.878824, 0.000001)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "divides exactly rather than as integers, which measurand 0.1.2 settled" do
|
|
151
|
+
_((measurement(1_000_000) / measurement(3_000_000)).value).must_equal(Rational(1, 3))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "carries the doubt of both, which is what a speedup is worth knowing" do
|
|
155
|
+
ratio = measurement(9_579_583) / measurement(10_900_458)
|
|
156
|
+
_(ratio.uncertainty).must_be_close_to(1.026e-05, 1e-8)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
99
160
|
describe "#to_duration" do
|
|
100
161
|
it "hands the interval to duration.rb, whose business the units are" do
|
|
101
162
|
_(measurement(1_500_000_000).to_duration).must_be_instance_of(Duration::Nanoseconds)
|
data/test/gemspec_test.rb
CHANGED
|
@@ -22,6 +22,16 @@ describe 'monotonic.rb.gemspec' do
|
|
|
22
22
|
_(spec.runtime_dependencies.map(&:name).sort).must_equal(%w{duration.rb measurand sys-uptime})
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# The minimum is what makes a dependency true, and naming the gem alone does
|
|
26
|
+
# not say that it will work. 0.9.0 constrained measurand to >= 0.1.0, where
|
|
27
|
+
# #place became public; #/ then became load-bearing at 0.1.2 without the
|
|
28
|
+
# constraint following it, and Measurement#/ returned 0 against a version the
|
|
29
|
+
# gemspec called satisfactory.
|
|
30
|
+
it "declares a minimum version for each runtime dependency" do
|
|
31
|
+
_(spec.runtime_dependencies.to_h{|dependency| [dependency.name, dependency.requirement.to_s]}) \
|
|
32
|
+
.must_equal({'duration.rb' => '>= 0.4.0', 'measurand' => '>= 0.1.2', 'sys-uptime' => '>= 0'})
|
|
33
|
+
end
|
|
34
|
+
|
|
25
35
|
it "declares its development dependencies" do
|
|
26
36
|
_(spec.development_dependencies.map(&:name).sort).must_equal(%w{minitest minitest-spec-context rake})
|
|
27
37
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: monotonic.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- thoran
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.1.
|
|
32
|
+
version: 0.1.2
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.1.
|
|
39
|
+
version: 0.1.2
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: sys-uptime
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|