-
Notifications
You must be signed in to change notification settings - Fork 283
Expand file tree
/
Copy pathSynchronousAccelTests.cs
More file actions
204 lines (172 loc) · 7.11 KB
/
SynchronousAccelTests.cs
File metadata and controls
204 lines (172 loc) · 7.11 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
namespace wrapper_tests
{
[TestClass]
public class SynchronousAccelTests
{
[TestMethod]
public void GivenSpeeds_SynchronousAccel_YieldsCorrectSens()
{
double syncSpeed = 20;
double gamma = 0.5;
double motivity = 1.3;
double smooth = 0.5;
var profile = new Profile();
profile.outputDPI = 1000;
profile.argsX.mode = AccelMode.synchronous;
profile.argsX.gain = false;
profile.argsX.syncSpeed = syncSpeed;
profile.argsX.gamma = gamma;
profile.argsX.motivity = motivity;
profile.argsX.smooth = smooth;
var accel = new ManagedAccel(profile);
var accelSimulator = new SynchronousAccelSimulator(syncSpeed, motivity, gamma, smooth);
List<int> inputs = new List<int>()
{
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181,
};
foreach (int input in inputs)
{
Tuple<double, double> output = accel.Accelerate(input, 0, 1, 10);
double expectedOutput = input * accelSimulator.Accelerate(input / 10.0);
Assert.AreEqual(expectedOutput, output.Item1, expectedOutput * 0.0001);
}
}
[TestMethod]
public void GivenSpeeds_SynchronousAccelWithNonDefaultSmooth_YieldsCorrectSens()
{
double syncSpeed = 20;
double gamma = 0.5;
double motivity = 1.3;
double smooth = 1.0;
var profile = new Profile();
profile.outputDPI = 1000;
profile.argsX.mode = AccelMode.synchronous;
profile.argsX.gain = false;
profile.argsX.syncSpeed = syncSpeed;
profile.argsX.gamma = gamma;
profile.argsX.motivity = motivity;
profile.argsX.smooth = smooth;
var accel = new ManagedAccel(profile);
var accelSimulator = new SynchronousAccelSimulator(syncSpeed, motivity, gamma, smooth);
List<int> inputs = new List<int>()
{
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181,
};
foreach (int input in inputs)
{
Tuple<double, double> output = accel.Accelerate(input, 0, 1, 10);
double expectedOutput = input * accelSimulator.Accelerate(input / 10.0);
Assert.AreEqual(expectedOutput, output.Item1, expectedOutput * 0.0001);
}
}
[TestMethod]
public void SynchronousAccel_ThresholdCrossing_IsContinuous()
{
double syncSpeed = 20;
double gamma = 0.5;
double motivity = 1.3;
// smooth = 0.03125 exactly results in sharpness = 16
double[] smoothValues = { 0.03126, 0.03125, 0.03124, 0.0 };
foreach (double smooth in smoothValues)
{
var profile = new Profile();
profile.outputDPI = 1000;
profile.argsX.mode = AccelMode.synchronous;
profile.argsX.gain = false;
profile.argsX.syncSpeed = syncSpeed;
profile.argsX.gamma = gamma;
profile.argsX.motivity = motivity;
profile.argsX.smooth = smooth;
var accel = new ManagedAccel(profile);
var pureSimulator = new UnclampedSynchronousSimulator(syncSpeed, motivity, gamma, smooth);
for (int input = 1; input < 100; input += 5)
{
Tuple<double, double> output = accel.Accelerate(input, 0, 1, 10);
double inputSpeed = (double)input / 10.0;
double expectedOutput = (double)input * pureSimulator.Accelerate(inputSpeed);
double delta = Math.Abs(output.Item1 - expectedOutput);
double relativeError = delta / expectedOutput;
Assert.IsTrue(relativeError < 0.001,
$"Discontinuity found at smooth={smooth}, input={input}. " +
$"Driver: {output.Item1}, Pure Math: {expectedOutput}, Error: {relativeError:P}");
}
}
}
}
/// <summary>
/// A simulator that follows the raw math WITHOUT the sharpness >= 16 optimization.
/// </summary>
public class UnclampedSynchronousSimulator
{
public UnclampedSynchronousSimulator(double syncSpeed, double motivity, double gamma, double smooth)
{
SyncSpeed = syncSpeed;
Motivity = motivity;
Gamma = gamma;
Sharpness = smooth <= 0 ? 100 : 0.5 / smooth;
}
public double SyncSpeed { get; }
public double Motivity { get; }
public double Gamma { get; }
public double Sharpness { get; }
public double Accelerate(double inputSpeed)
{
if (inputSpeed == SyncSpeed) return 1.0;
double logSyncSpeed = Math.Log(SyncSpeed);
double logMotivity = Math.Log(Motivity);
double gammaConst = Gamma / logMotivity;
double logX = Math.Log(inputSpeed);
double logDiff = logX - logSyncSpeed;
double logSpace = Math.Abs(gammaConst * logDiff);
double exponent = Math.Pow(Math.Tanh(Math.Pow(logSpace, Sharpness)), 1 / Sharpness);
exponent *= Math.Sign(logDiff);
return Math.Exp(exponent * logMotivity);
}
}
/// <summary>
/// Contains definition of how synchronous accel is expected to accelerate inputs
/// No optimization tricks are used for clarity of behavior.
/// </summary>
public class SynchronousAccelSimulator
{
public SynchronousAccelSimulator(
double syncSpeed,
double motivity,
double gamma,
double smooth)
{
SyncSpeed = syncSpeed;
Motivity = motivity;
Gamma = gamma;
Sharpness = smooth <= 0 ? 16 : 0.5 / smooth;
}
public double SyncSpeed { get; }
public double Motivity { get; }
public double Gamma { get; }
public double Sharpness { get; }
public double Accelerate(double inputSpeed)
{
double logSpace = CalculateLogSpace(inputSpeed);
double activation = ActivationFunction(logSpace);
return Math.Pow(Motivity, activation);
}
public double CalculateLogSpace(double x)
{
double syncRatio = x / SyncSpeed;
double logSpaceUnadjusted = Math.Log(syncRatio, Motivity);
double gammaAdjusted = Gamma * logSpaceUnadjusted;
return gammaAdjusted;
}
public double ActivationFunction(double x)
{
if (Sharpness >= 16)
{
return Math.Min(1, Math.Max(-1, x));
}
return Math.Sign(x) * Math.Pow(Math.Tanh(Math.Pow(Math.Abs(x), Sharpness)), 1 / Sharpness);
}
}
}