Skip to content

Commit fa68274

Browse files
committed
Updated version to 1.2.11
Copied src/test/scala/com/phasmidsoftware/number/cats/ErrorCommutativeMonoidSpec.scala to src/it/scala/com/phasmidsoftware/number/cats/ErrorCommutativeMonoidFuncSpec.scala and changed number of repetitions from 2000 to 500 in the test directory.
1 parent 72ea2b0 commit fa68274

File tree

3 files changed

+142
-6
lines changed

3 files changed

+142
-6
lines changed

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ organization := "com.phasmidsoftware"
22

33
name := "Number"
44

5-
version := "1.2.10"
5+
version := "1.2.11"
66

77
scalaVersion := "2.13.16"
88

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright (c) 2025. Phasmid Software
3+
*/
4+
5+
package com.phasmidsoftware.number.cats
6+
7+
import com.phasmidsoftware.number.cats.ErrorCommutativeMonoid._
8+
import com.phasmidsoftware.number.core.inner.{PureNumber, Value}
9+
import com.phasmidsoftware.number.core.{AbsoluteFuzz, FuzzyNumber, Gaussian, Number, RelativeFuzz}
10+
11+
/**
12+
* Usage-focused examples for the error metric Monoid instances.
13+
*
14+
* These tests are intentionally lightweight demonstrations rather than law checks.
15+
*/
16+
class ErrorCommutativeMonoidFuncSpec extends AnyFlatSpec with Matchers {
17+
18+
behavior of "Abstracting advocacy communication into lawful scalar folding"
19+
20+
21+
22+
it should "match decoupled parallel error folding with direct Number addition (all addition)" in {
23+
implicit val ec: ExecutionContext = ExecutionContext.global
24+
25+
// Build many fuzzy addends: same nominal 1.2 with absolute Gaussian sigma 0.05
26+
val terms: List[Number] = List.fill(2000) {
27+
FuzzyNumber(Value.fromDouble(Some(1.2)), PureNumber, Some(AbsoluteFuzz(0.05, Gaussian)))
28+
}
29+
30+
// 1) Traditional: add fuzzy Numbers directly (measure time)
31+
val (accumulated: Number, tSeqMs) = 1.times {
32+
terms.tail.foldLeft(terms.head)(_ doAdd _)
33+
}
34+
35+
val actualNominal = accumulated.toNominalDouble.getOrElse(Double.NaN)
36+
val actualAbs = accumulated match {
37+
case f: FuzzyNumber => f.fuzz.collect { case AbsoluteFuzz(m: Double, Gaussian) => m }.getOrElse(Double.NaN)
38+
case _ => Double.NaN
39+
}
40+
41+
// 2) Decoupled parallel: nominal sum and sigma folding run independently
42+
val ((decoupledNominal, decoupledSigma), tParMs) = 1.times {
43+
val fNominal: Future[Double] = Future { terms.flatMap(_.toNominalDouble).sum }
44+
val fSigma: Future[Double] = Future {
45+
val sigmas = terms.map {
46+
case f: FuzzyNumber => f.fuzz.collect { case AbsoluteFuzz(m: Double, Gaussian) => m }.getOrElse(0.0)
47+
case _ => 0.0
48+
}
49+
sigmas.foldLeft(AbsSigma.zero)(_ |+| AbsSigma(_)).value
50+
}
51+
val a = Await.result(fNominal, 5.seconds)
52+
val b = Await.result(fSigma, 5.seconds)
53+
(a, b)
54+
}
55+
56+
val decoupled: Number = FuzzyNumber(Value.fromDouble(Some(decoupledNominal)), PureNumber, Some(AbsoluteFuzz(decoupledSigma, Gaussian)))
57+
58+
val decoupledAbs = decoupled match {
59+
case f: FuzzyNumber => f.fuzz.collect { case AbsoluteFuzz(m: Double, Gaussian) => m }.getOrElse(Double.NaN)
60+
case _ => Double.NaN
61+
}
62+
63+
// Compare nominal and absolute sigma
64+
actualNominal shouldBe decoupledNominal +- 1e-9
65+
actualAbs shouldBe decoupledAbs +- 1e-9
66+
67+
// And new method should be faster (or equal) than traditional
68+
// NOTE: simple wall-clock comparison; if flaky in CI, relax or increase data size
69+
// tParMs: 72.449458, tSeqMs: 194.883
70+
println(s"tParMs: $tParMs, tSeqMs: $tSeqMs")
71+
assert(tParMs <= tSeqMs, s"decoupled parallel folding should be faster: par=${tParMs}ms vs seq=${tSeqMs}ms")
72+
}
73+
74+
// CONSIDER this test is slow. We might want to tag it as Slow (or perhaps try to speed it up)
75+
it should "match decoupled parallel error folding with direct Number multiplication (all multiplication)" in {
76+
implicit val ec: ExecutionContext = ExecutionContext.global
77+
78+
// Build many fuzzy addends: same nominal 1.2 with absolute Gaussian sigma 0.05
79+
val terms: List[Number] = List.fill(2000) {
80+
FuzzyNumber(Value.fromDouble(Some(1.1)), PureNumber, Some(RelativeFuzz(0.05, Gaussian)))
81+
}
82+
83+
// 1) Traditional: multiply fuzzy Numbers directly (measure time)
84+
val (accumulated: Number, tSeqMs) = 1.times {
85+
terms.tail.foldLeft(terms.head)(_ doMultiply _)
86+
}
87+
88+
val actualNominal = accumulated.toNominalDouble.getOrElse(Double.NaN)
89+
val actualRel = accumulated match {
90+
case f: FuzzyNumber => f.fuzz.collect { case RelativeFuzz(m: Double, Gaussian) => m }.getOrElse(Double.NaN)
91+
case _ => Double.NaN
92+
}
93+
94+
// 2) Decoupled parallel: nominal product and sigma folding run independently
95+
val ((decoupledNominal, decoupledSigma), tParMs) = 1.times {
96+
val fNominal: Future[Double] = Future {
97+
val headNominal = terms.headOption.flatMap(_.toNominalDouble).getOrElse(Double.NaN)
98+
terms.tail.foldLeft(headNominal) { (acc, n) =>
99+
acc * n.toNominalDouble.getOrElse(1.0)
100+
}
101+
}
102+
val fSigma: Future[Double] = Future {
103+
// Sequential combination, relative basis propagation: r_xy^2 = r_x^2 + r_y^2 + r_x r_y
104+
def combineRel(r1: Double, r2: Double): Double = {
105+
val a = r1; val b = r2
106+
math.sqrt(a * a + b * b + a * b)
107+
}
108+
val sigmas = terms.map {
109+
case f: FuzzyNumber => f.fuzz.collect { case RelativeFuzz(m: Double, Gaussian) => m }.getOrElse(0.0)
110+
case _ => 0.0
111+
}
112+
sigmas.foldLeft(0.0)(combineRel)
113+
}
114+
val a = Await.result(fNominal, 5.seconds)
115+
val b = Await.result(fSigma, 5.seconds)
116+
(a, b)
117+
}
118+
119+
val decoupled: Number = FuzzyNumber(Value.fromDouble(Some(decoupledNominal)), PureNumber, Some(RelativeFuzz(decoupledSigma, Gaussian)))
120+
121+
val decoupledRel = decoupled match {
122+
case f: FuzzyNumber => f.fuzz.collect { case RelativeFuzz(m: Double, Gaussian) => m }.getOrElse(Double.NaN)
123+
case _ => Double.NaN
124+
}
125+
126+
// Compare nominal and relative sigma
127+
math.abs(actualNominal - decoupledNominal) / math.abs(actualNominal) should be < 1e-12
128+
actualRel shouldBe decoupledRel +- 1e-1
129+
130+
// And new method should be faster (or equal) than traditional
131+
// NOTE: simple wall-clock comparison; if flaky in CI, relax or increase data size
132+
// tParMs: 5.240125, tSeqMs: 7731.776417
133+
println(s"tParMs: $tParMs, tSeqMs: $tSeqMs")
134+
assert(tParMs <= tSeqMs, s"decoupled parallel folding should be faster: par=${tParMs}ms vs seq=${tSeqMs}ms")
135+
}
136+
137+
}
138+
139+

src/test/scala/com/phasmidsoftware/number/cats/ErrorCommutativeMonoidSpec.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@ class ErrorCommutativeMonoidSpec extends AnyFlatSpec with Matchers {
1919

2020
behavior of "Abstracting advocacy communication into lawful scalar folding"
2121

22-
23-
2422
it should "match decoupled parallel error folding with direct Number addition (all addition)" in {
2523
implicit val ec: ExecutionContext = ExecutionContext.global
2624

2725
// Build many fuzzy addends: same nominal 1.2 with absolute Gaussian sigma 0.05
28-
val terms: List[Number] = List.fill(2000) {
26+
val terms: List[Number] = List.fill(500) {
2927
FuzzyNumber(Value.fromDouble(Some(1.2)), PureNumber, Some(AbsoluteFuzz(0.05, Gaussian)))
3028
}
3129

@@ -78,7 +76,7 @@ class ErrorCommutativeMonoidSpec extends AnyFlatSpec with Matchers {
7876
implicit val ec: ExecutionContext = ExecutionContext.global
7977

8078
// Build many fuzzy addends: same nominal 1.2 with absolute Gaussian sigma 0.05
81-
val terms: List[Number] = List.fill(2000) {
79+
val terms: List[Number] = List.fill(500) {
8280
FuzzyNumber(Value.fromDouble(Some(1.1)), PureNumber, Some(RelativeFuzz(0.05, Gaussian)))
8381
}
8482

@@ -135,7 +133,6 @@ class ErrorCommutativeMonoidSpec extends AnyFlatSpec with Matchers {
135133
println(s"tParMs: $tParMs, tSeqMs: $tSeqMs")
136134
assert(tParMs <= tSeqMs, s"decoupled parallel folding should be faster: par=${tParMs}ms vs seq=${tSeqMs}ms")
137135
}
138-
139136
}
140137

141138

0 commit comments

Comments
 (0)