Repository navigation
Expand file tree
/
Copy patherrors_test.go
More file actions
152 lines (129 loc) · 3.98 KB
/
Copy patherrors_test.go
File metadata and controls
152 lines (129 loc) · 3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package zfactor_test
import (
"errors"
"fmt"
"math/cmplx"
"strings"
"testing"
"github.com/rickykimani/zfactor"
)
// TestRangeErrorMessage checks both forms of the message.
//
// The type is shared by correlations that know which substance they
// describe and by those that do not, so the name is omitted rather than
// rendered empty when it is absent.
func TestRangeErrorMessage(t *testing.T) {
t.Run("with a name", func(t *testing.T) {
err := &zfactor.RangeError{
Name: "Carbon dioxide",
T: 293.15,
Low: 298.15,
High: 2000,
}
got := err.Error()
for _, want := range []string{"293.15", "298.15", "2000", "Carbon dioxide"} {
if !strings.Contains(got, want) {
t.Errorf("message %q does not mention %q", got, want)
}
}
})
t.Run("without a name", func(t *testing.T) {
err := &zfactor.RangeError{T: 250, Low: 300, High: 400}
got := err.Error()
if strings.Contains(got, "fitted for") {
t.Errorf("message %q names a substance it was not given", got)
}
for _, want := range []string{"250", "300", "400"} {
if !strings.Contains(got, want) {
t.Errorf("message %q does not mention %q", got, want)
}
}
})
}
// TestRangeErrorIsDiscoverable checks that the shared type can be
// recovered from a wrapped error, which is how callers distinguish an
// extrapolation from a failure.
func TestRangeErrorIsDiscoverable(t *testing.T) {
wrapped := fmt.Errorf("computing a property: %w", &zfactor.RangeError{
Name: "Water", T: 250, Low: 273.15, High: 373.15,
})
var rangeErr *zfactor.RangeError
if !errors.As(wrapped, &rangeErr) {
t.Fatalf("errors.As did not recover the range error from %v", wrapped)
}
if rangeErr.Name != "Water" || rangeErr.T != 250 {
t.Errorf("recovered %+v; want the original values", rangeErr)
}
}
// TestSolveCubicRepeatedRoots checks the polynomials whose derivative
// vanishes at a root.
//
// Newton's method cannot refine a repeated root, since the derivative it
// divides by is zero there, and the refinement is abandoned rather than
// producing an infinity. The roots Cardano supplies must therefore
// survive unchanged.
func TestSolveCubicRepeatedRoots(t *testing.T) {
testCases := []struct {
name string
a, b, c, d float64
want []complex128
}{
{"triple root at -1", 1, 3, 3, 1, []complex128{-1, -1, -1}},
{"triple root at 0", 1, 0, 0, 0, []complex128{0, 0, 0}},
{"double root at 1 with a simple root at -2", 1, 0, -3, 2, []complex128{1, 1, -2}},
}
const tol = 1e-6
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got, err := zfactor.SolveCubic(tc.a, tc.b, tc.c, tc.d)
if err != nil {
t.Fatalf("SolveCubic returned an unexpected error: %v", err)
}
for _, want := range tc.want {
var found bool
for _, g := range got {
if cmplx.Abs(g-want) < tol {
found = true
break
}
}
if !found {
t.Errorf("root %v is missing from %v", want, got)
}
}
})
}
}
// TestSolveCubicRefinesTheRoots checks that the refinement is applied,
// by comparing against the accuracy the closed form alone reaches.
//
// The polynomial below has roots spread over six orders of magnitude,
// which is where the substitution removing the quadratic term loses the
// most precision. Cardano's estimate leaves a residual far above
// rounding; refined, it should sit near it.
func TestSolveCubicRefinesTheRoots(t *testing.T) {
const (
a, b, c, d = 1.0, -1000001.0, 1000000.0, 0.0
tol = 1e-9
)
roots, err := zfactor.SolveCubic(a, b, c, d)
if err != nil {
t.Fatalf("SolveCubic returned an unexpected error: %v", err)
}
// The roots are 0, 1 and 1000000.
for _, want := range []complex128{0, 1, 1000000} {
var closest float64 = 1e300
for _, got := range roots {
scale := cmplx.Abs(want)
if scale < 1 {
scale = 1
}
if rel := cmplx.Abs(got-want) / scale; rel < closest {
closest = rel
}
}
if closest > tol {
t.Errorf("root %v is not reproduced; nearest is %.3e away relatively (roots %v)", want, closest, roots)
}
}
}