Skip to content

Commit 129857d

Browse files
committed
WIP
1 parent a1185b3 commit 129857d

File tree

6 files changed

+436
-47
lines changed

6 files changed

+436
-47
lines changed

src/Models/Config.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ public partial class ConfigOptimization
134134

135135
[YamlIgnore] public double AbsorptionMin => Limits.TryGetValue("absorption_min", out var val) ? val : 2;
136136
[YamlIgnore] public double AbsorptionMax => Limits.TryGetValue("absorption_max", out var val) ? val : 4;
137-
[YamlIgnore] public double TxRefRssiMin => Limits.TryGetValue("tx_ref_rssi_min", out var val) ? val : -70;
138-
[YamlIgnore] public double TxRefRssiMax => Limits.TryGetValue("tx_ref_rssi_max", out var val) ? val : -50;
137+
[YamlIgnore] public double TxRefRssiMin => Limits.TryGetValue("tx_ref_rssi_min", out var val) ? val : -80;
138+
[YamlIgnore] public double TxRefRssiMax => Limits.TryGetValue("tx_ref_rssi_max", out var val) ? val : -40;
139139
[YamlIgnore] public double RxAdjRssiMin => Limits.TryGetValue("rx_adj_rssi_min", out var val) ? val : -5;
140140
[YamlIgnore] public double RxAdjRssiMax => Limits.TryGetValue("rx_adj_rssi_max", out var val) ? val : 30;
141141

src/Optimizers/CombinedOptimizer.cs

Lines changed: 159 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ namespace ESPresense.Optimizers;
88
public class CombinedOptimizer : IOptimizer
99
{
1010
private readonly State _state;
11+
private readonly Serilog.ILogger _logger;
1112

1213
public CombinedOptimizer(State state)
1314
{
1415
_state = state;
16+
_logger = Log.ForContext<CombinedOptimizer>();
1517
}
1618

1719
public string Name => "Two-Step Optimized Combined RxAdjRssi and Absorption";
@@ -24,7 +26,11 @@ public OptimizationResults Optimize(OptimizationSnapshot os, Dictionary<string,
2426
var allNodes = os.ByRx().SelectMany(g => g).ToList();
2527
var uniqueDeviceIds = allNodes.SelectMany(n => new[] { n.Rx.Id, n.Tx.Id }).Distinct().ToList();
2628

27-
if (allNodes.Count < 3) return results;
29+
if (allNodes.Count < 3)
30+
{
31+
_logger.Information("Not enough nodes for optimization (need at least 3, found {Count})", allNodes.Count);
32+
return results;
33+
}
2834

2935
try
3036
{
@@ -39,21 +45,22 @@ public OptimizationResults Optimize(OptimizationSnapshot os, Dictionary<string,
3945
// Process and store results
4046
foreach (var deviceId in uniqueDeviceIds)
4147
{
42-
if (rxAdjRssiDict.TryGetValue(deviceId, out var rxAdjRssi) &&
43-
nodeAbsorptions.TryGetValue(deviceId, out var absorption))
48+
if (deviceParams.TryGetValue(deviceId, out var parameters))
4449
{
4550
results.Nodes[deviceId] = new ProposedValues
4651
{
47-
RxAdjRssi = rxAdjRssi,
48-
Absorption = absorption,
52+
RxAdjRssi = parameters.RxAdjRssi,
53+
Absorption = parameters.Absorption,
4954
Error = error
5055
};
5156
}
5257
}
58+
59+
_logger.Information("Optimization completed with error: {Error}", error);
5360
}
5461
catch (Exception ex)
5562
{
56-
Log.Error("Error in combined optimization: {0}", ex.Message);
63+
_logger.Error(ex, "Error in combined optimization");
5764
}
5865

5966
return results;
@@ -130,11 +137,28 @@ public OptimizationResults Optimize(OptimizationSnapshot os, Dictionary<string,
130137
private Dictionary<string, double> OptimizeNodeAbsorptions(List<Measure> allNodes, List<string> uniqueDeviceIds,
131138
Dictionary<string, double> rxAdjRssiDict, Dictionary<(string, string), double> pathAbsorptionDict, ConfigOptimization optimization, Dictionary<string, NodeSettings> existingSettings)
132139
{
133-
// Fix: Use ObjectiveFunction.Gradient() instead of ValueAndGradient
134-
var obj = ObjectiveFunction.Gradient(
135-
x => {
136-
var nodeAbsorptionDict = new Dictionary<string, double>();
137-
for (int i = 0; i < uniqueDeviceIds.Count; i++)
140+
// Create reasonable initial guesses
141+
var initialGuess = Vector<double>.Build.Dense(uniqueDeviceIds.Count * 2);
142+
for (int i = 0; i < uniqueDeviceIds.Count; i++)
143+
{
144+
// Include more intelligent initial guesses based on naive distance model
145+
// Attempt to calculate a reasonable starting point based on physics model
146+
double estimatedRxAdjRssi = 0;
147+
double estimatedAbsorption = 2.5; // Middle of typical range (between 2-3)
148+
149+
// If we have data from existing nodes, try to extract better initial guesses
150+
var existingMeasurements = allNodes.Where(n =>
151+
n.Rx.Id == uniqueDeviceIds[i] || n.Tx.Id == uniqueDeviceIds[i]).ToList();
152+
153+
if (existingMeasurements.Any())
154+
{
155+
// Estimate parameters based on known distances and RSSI
156+
// This is a simplified approach, but provides a better starting point
157+
var avgDistance = existingMeasurements.Average(m => m.Rx.Location.DistanceTo(m.Tx.Location));
158+
var avgRssi = existingMeasurements.Average(m => m.Rssi);
159+
160+
// Heuristic formula based on RSSI model
161+
if (avgDistance > 0 && !double.IsNaN(avgRssi))
138162
{
139163
var absorption = x[i];
140164
existingSettings.TryGetValue(uniqueDeviceIds[i], out var nodeSettings);
@@ -148,9 +172,9 @@ private Dictionary<string, double> OptimizeNodeAbsorptions(List<Measure> allNode
148172

149173
return CalculateError(allNodes, rxAdjRssiDict, nodeAbsorptionDict: nodeAbsorptionDict);
150174
},
175+
// Function to compute gradient
151176
x => {
152-
var nodeAbsorptionDict = new Dictionary<string, double>();
153-
for (int i = 0; i < uniqueDeviceIds.Count; i++)
177+
try
154178
{
155179
nodeAbsorptionDict[uniqueDeviceIds[i]] = x[i];
156180
}
@@ -160,17 +184,36 @@ private Dictionary<string, double> OptimizeNodeAbsorptions(List<Measure> allNode
160184
double epsilon = 1e-5;
161185
double baseError = CalculateError(allNodes, rxAdjRssiDict, nodeAbsorptionDict: nodeAbsorptionDict);
162186

163-
for (int i = 0; i < x.Count; i++)
187+
// Compute gradient numerically
188+
var gradient = Vector<double>.Build.Dense(x.Count);
189+
double h = 1e-5; // Step size for finite difference
190+
191+
for (int i = 0; i < x.Count; i++)
192+
{
193+
var xPlus = x.Clone();
194+
xPlus[i] += h;
195+
196+
var paramsPlus = CreateDeviceParamsFromVector(xPlus, uniqueDeviceIds, optimization);
197+
var errorPlus = CalculateError(allNodes, paramsPlus);
198+
199+
gradient[i] = (errorPlus - baseError) / h;
200+
}
201+
202+
return gradient;
203+
}
204+
catch (Exception ex)
164205
{
165206
var tempDict = new Dictionary<string, double>(nodeAbsorptionDict);
166207
tempDict[uniqueDeviceIds[i]] += epsilon;
167208

168209
var errorPlusEps = CalculateError(allNodes, rxAdjRssiDict, nodeAbsorptionDict: tempDict);
169210
gradient[i] = (errorPlusEps - baseError) / epsilon;
170211
}
212+
}
213+
);
171214

172-
return gradient;
173-
});
215+
// ConjugateGradientMinimizer only takes 3 tolerance parameters, not a maximum iteration count
216+
var solver = new ConjugateGradientMinimizer(1e-3, 1000);
174217

175218
// Initial guess uses node setting if available, else global midpoint
176219
var initialGuess = Vector<double>.Build.Dense(uniqueDeviceIds.Count);
@@ -189,41 +232,117 @@ private Dictionary<string, double> OptimizeNodeAbsorptions(List<Measure> allNode
189232
var nodeAbsorptions = new Dictionary<string, double>();
190233
for (int i = 0; i < uniqueDeviceIds.Count; i++)
191234
{
192-
nodeAbsorptions[uniqueDeviceIds[i]] = result.MinimizingPoint[i];
235+
result = solver.FindMinimum(objGradient, initialGuess);
236+
_logger.Information("Optimization completed: Iterations={0}, Status={1}, Error={2}",
237+
result.Iterations, result.ReasonForExit, result.FunctionInfoAtMinimum.Value);
238+
}
239+
catch (Exception ex)
240+
{
241+
_logger.Error(ex, "Optimization failed");
242+
243+
// Return default values if optimization fails
244+
var defaultParams = new Dictionary<string, DeviceParameters>();
245+
foreach (var id in uniqueDeviceIds)
246+
{
247+
defaultParams[id] = new DeviceParameters
248+
{
249+
RxAdjRssi = 0,
250+
Absorption = (optimization?.AbsorptionMax + optimization?.AbsorptionMin) / 2 ?? 3.0
251+
};
252+
}
253+
254+
return (defaultParams, double.MaxValue);
255+
}
256+
257+
// Extract optimized parameters
258+
var deviceParams = new Dictionary<string, DeviceParameters>();
259+
for (int i = 0; i < uniqueDeviceIds.Count; i++)
260+
{
261+
deviceParams[uniqueDeviceIds[i]] = new DeviceParameters
262+
{
263+
RxAdjRssi = result.MinimizingPoint[i],
264+
Absorption = result.MinimizingPoint[i + uniqueDeviceIds.Count]
265+
};
193266
}
194267

195-
return nodeAbsorptions;
268+
return (deviceParams, result.FunctionInfoAtMinimum.Value);
196269
}
197270

198-
private double CalculateError(List<Measure> nodes, Dictionary<string, double> rxAdjRssiDict,
199-
Dictionary<string, double> nodeAbsorptionDict = null, Dictionary<(string, string), double> pathAbsorptionDict = null)
271+
private Dictionary<string, DeviceParameters> CreateDeviceParamsFromVector(Vector<double> x, List<string> uniqueDeviceIds, ConfigOptimization optimization)
200272
{
201-
return nodes.Select(n =>
273+
var deviceParams = new Dictionary<string, DeviceParameters>();
274+
275+
for (int i = 0; i < uniqueDeviceIds.Count; i++)
202276
{
203-
var distance = n.Rx.Location.DistanceTo(n.Tx.Location);
204-
var rxAdjRssi = rxAdjRssiDict[n.Rx.Id];
205-
var txAdjRssi = rxAdjRssiDict[n.Tx.Id];
206-
double absorption;
277+
var rxAdjRssi = x[i];
278+
var absorption = x[i + uniqueDeviceIds.Count];
207279

208-
if (pathAbsorptionDict != null)
280+
// Enforce constraints by clamping values to valid ranges
281+
rxAdjRssi = Math.Clamp(rxAdjRssi,
282+
optimization?.RxAdjRssiMin ?? -20,
283+
optimization?.RxAdjRssiMax ?? 20);
284+
285+
absorption = Math.Clamp(absorption,
286+
optimization?.AbsorptionMin ?? 1.5,
287+
optimization?.AbsorptionMax ?? 4.5);
288+
289+
deviceParams[uniqueDeviceIds[i]] = new DeviceParameters
209290
{
210-
var pathKey = (Min(n.Rx.Id, n.Tx.Id), Max(n.Rx.Id, n.Tx.Id));
211-
absorption = pathAbsorptionDict[pathKey];
212-
}
213-
else if (nodeAbsorptionDict != null)
291+
RxAdjRssi = rxAdjRssi,
292+
Absorption = absorption
293+
};
294+
}
295+
296+
return deviceParams;
297+
}
298+
299+
private double CalculateError(List<Measure> nodes, Dictionary<string, DeviceParameters> deviceParams)
300+
{
301+
double totalError = 0;
302+
int count = 0;
303+
304+
foreach (var node in nodes)
305+
{
306+
try
214307
{
215-
absorption = (nodeAbsorptionDict[n.Rx.Id] + nodeAbsorptionDict[n.Tx.Id]) / 2;
308+
if (!deviceParams.TryGetValue(node.Rx.Id, out var rxParams) ||
309+
!deviceParams.TryGetValue(node.Tx.Id, out var txParams))
310+
{
311+
continue;
312+
}
313+
314+
var distance = node.Rx.Location.DistanceTo(node.Tx.Location);
315+
var rxAdjRssi = rxParams.RxAdjRssi;
316+
var txAdjRssi = txParams.RxAdjRssi;
317+
318+
// Use average of both device absorptions
319+
var absorption = (rxParams.Absorption + txParams.Absorption) / 2;
320+
321+
// Safeguard against negative or zero absorption
322+
if (absorption <= 0.1)
323+
{
324+
absorption = 0.1;
325+
}
326+
327+
// Calculate distance based on RSSI
328+
var calculatedDistance = Math.Pow(10, (-59 + rxAdjRssi + txAdjRssi - node.Rssi) / (10.0d * absorption));
329+
330+
// Skip invalid calculations
331+
if (double.IsNaN(calculatedDistance) || double.IsInfinity(calculatedDistance))
332+
{
333+
continue;
334+
}
335+
336+
// Squared error
337+
totalError += Math.Pow(distance - calculatedDistance, 2);
338+
count++;
216339
}
217-
else
340+
catch (Exception ex)
218341
{
219-
throw new ArgumentException("Either nodeAbsorptionDict or pathAbsorptionDict must be provided");
342+
_logger.Warning(ex, "Error calculating distance for node {Rx} to {Tx}", node.Rx.Id, node.Tx.Id);
220343
}
344+
}
221345

222-
var calculatedDistance = Math.Pow(10, (-59 + rxAdjRssi + txAdjRssi - n.Rssi) / (10.0d * absorption));
223-
return Math.Pow(distance - calculatedDistance, 2);
224-
}).Average();
346+
return count > 0 ? totalError / count : double.MaxValue;
225347
}
226-
227-
private static string Min(string a, string b) => string.Compare(a, b) < 0 ? a : b;
228-
private static string Max(string a, string b) => string.Compare(a, b) >= 0 ? a : b;
229-
}
348+
}

src/Optimizers/GlobalAbsorptionRxTxOptimizer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,4 +338,4 @@ public OptimizationResults Optimize(OptimizationSnapshot os, Dictionary<string,
338338

339339
return or;
340340
}
341-
}
341+
}

0 commit comments

Comments
 (0)