最小二乘-矩阵运算

This commit is contained in:
biss committed 2026-06-16 11:43:41 +08:00
1 parent 5cfb336f46
commit a44a29e853
5 files changed
+860

No files matched your search

+297
View File
@@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.Linq;
using kcsj.Models;
namespace kcsj.Services
{
public class DataCheckResult
{
public List<string> Errors { get; } = new();
public List<string> Warnings { get; } = new();
public List<string> Infos { get; } = new();
public bool IsValid => Errors.Count == 0;
public string ToLogText()
{
var lines = new List<string>();
lines.Add("========== 数据检查结果 ==========");
foreach (string info in Infos)
{
lines.Add("[信息] " + info);
}
foreach (string warning in Warnings)
{
lines.Add("[警告] " + warning);
}
foreach (string error in Errors)
{
lines.Add("[错误] " + error);
}
lines.Add(IsValid ? "数据检查通过。" : "数据检查未通过。");
lines.Add("================================");
return string.Join(Environment.NewLine, lines);
}
}
public static class DataCheck
{
public static DataCheckResult Check(
List<KnownPoint> knownPoints,
List<Observation> observations)
{
var result = new DataCheckResult();
if (knownPoints == null || knownPoints.Count == 0)
{
result.Errors.Add("未导入已知点数据。");
}
else
{
result.Infos.Add($"已知点数量:{knownPoints.Count}");
}
if (observations == null || observations.Count == 0)
{
result.Errors.Add("未导入观测数据。");
}
else
{
result.Infos.Add($"观测数据数量:{observations.Count}");
}
if (!result.IsValid)
{
return result;
}
CheckKnownPoints(knownPoints, result);
CheckObservations(observations, result);
CheckNetwork(knownPoints, observations, result);
CheckRedundancy(knownPoints, observations, result);
return result;
}
private static void CheckKnownPoints(
List<KnownPoint> knownPoints,
DataCheckResult result)
{
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < knownPoints.Count; i++)
{
KnownPoint point = knownPoints[i];
if (string.IsNullOrWhiteSpace(point.Name))
{
result.Errors.Add($"第 {i + 1} 个已知点点名为空。");
continue;
}
string name = point.Name.Trim();
if (!names.Add(name))
{
result.Errors.Add($"已知点点名重复:{name}");
}
if (double.IsNaN(point.Elevation) || double.IsInfinity(point.Elevation))
{
result.Errors.Add($"已知点 {name} 的高程不是有效数字。");
}
}
}
private static void CheckObservations(
List<Observation> observations,
DataCheckResult result)
{
var edges = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < observations.Count; i++)
{
Observation obs = observations[i];
if (string.IsNullOrWhiteSpace(obs.FromPoint))
{
result.Errors.Add($"第 {i + 1} 条观测起点为空。");
continue;
}
if (string.IsNullOrWhiteSpace(obs.ToPoint))
{
result.Errors.Add($"第 {i + 1} 条观测终点为空。");
continue;
}
string from = obs.FromPoint.Trim();
string to = obs.ToPoint.Trim();
if (string.Equals(from, to, StringComparison.OrdinalIgnoreCase))
{
result.Errors.Add($"第 {i + 1} 条观测起点和终点相同:{from}");
}
if (double.IsNaN(obs.HeightDiff) || double.IsInfinity(obs.HeightDiff))
{
result.Errors.Add($"第 {i + 1} 条观测高差不是有效数字。");
}
if (double.IsNaN(obs.Distance) || double.IsInfinity(obs.Distance))
{
result.Errors.Add($"第 {i + 1} 条观测距离不是有效数字。");
}
else if (obs.Distance <= 0)
{
result.Errors.Add($"第 {i + 1} 条观测距离必须大于 0。");
}
string edge1 = from + "-" + to;
string edge2 = to + "-" + from;
if (edges.Contains(edge1) || edges.Contains(edge2))
{
result.Warnings.Add($"第 {i + 1} 条观测边可能重复:{from} - {to}");
}
else
{
edges.Add(edge1);
}
}
}
private static void CheckNetwork(
List<KnownPoint> knownPoints,
List<Observation> observations,
DataCheckResult result)
{
if (!result.IsValid)
{
return;
}
var knownNames = new HashSet<string>(
knownPoints.Select(p => p.Name.Trim()),
StringComparer.OrdinalIgnoreCase);
var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (KnownPoint point in knownPoints)
{
allNames.Add(point.Name.Trim());
}
foreach (Observation obs in observations)
{
allNames.Add(obs.FromPoint.Trim());
allNames.Add(obs.ToPoint.Trim());
}
var graph = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
foreach (string name in allNames)
{
graph[name] = new List<string>();
}
foreach (Observation obs in observations)
{
string from = obs.FromPoint.Trim();
string to = obs.ToPoint.Trim();
graph[from].Add(to);
graph[to].Add(from);
}
var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var queue = new Queue<string>();
foreach (string knownName in knownNames)
{
if (graph.ContainsKey(knownName))
{
visited.Add(knownName);
queue.Enqueue(knownName);
}
}
while (queue.Count > 0)
{
string current = queue.Dequeue();
foreach (string next in graph[current])
{
if (!visited.Contains(next))
{
visited.Add(next);
queue.Enqueue(next);
}
}
}
foreach (string name in allNames)
{
if (!visited.Contains(name))
{
result.Errors.Add($"点 {name} 没有与任何已知点连通。");
}
}
int unknownCount = allNames.Count(name => !knownNames.Contains(name));
result.Infos.Add($"总点数:{allNames.Count}");
result.Infos.Add($"未知点数:{unknownCount}");
}
private static void CheckRedundancy(
List<KnownPoint> knownPoints,
List<Observation> observations,
DataCheckResult result)
{
if (!result.IsValid)
{
return;
}
var knownNames = new HashSet<string>(
knownPoints.Select(p => p.Name.Trim()),
StringComparer.OrdinalIgnoreCase);
var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (KnownPoint point in knownPoints)
{
allNames.Add(point.Name.Trim());
}
foreach (Observation obs in observations)
{
allNames.Add(obs.FromPoint.Trim());
allNames.Add(obs.ToPoint.Trim());
}
int unknownCount = allNames.Count(name => !knownNames.Contains(name));
int observationCount = observations.Count;
int redundancy = observationCount - unknownCount;
result.Infos.Add($"多余观测数:r = {observationCount} - {unknownCount} = {redundancy}");
if (observationCount < unknownCount)
{
result.Errors.Add("观测数少于未知点数,无法平差。");
}
else if (redundancy == 0)
{
result.Warnings.Add("多余观测数为 0,无法进行精度评定。");
}
}
}
}
+56
View File
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using kcsj.Models;
namespace kcsj.Services
{
public static class DataStore
{
public static List<KnownPoint> KnownPoints { get; private set; } = new();
public static List<Observation> Observations { get; private set; } = new();
public static string DataSource { get; private set; } = "";
public static bool HasData
{
get
{
return KnownPoints.Count > 0 && Observations.Count > 0;
}
}
public static void SetData(
List<KnownPoint> knownPoints,
List<Observation> observations,
string dataSource)
{
if (knownPoints == null || knownPoints.Count == 0)
{
throw new ArgumentException("已知点数据为空,不能保存数据。");
}
if (observations == null || observations.Count == 0)
{
throw new ArgumentException("观测数据为空,不能保存数据。");
}
KnownPoints = knownPoints.ToList();
Observations = observations.ToList();
DataSource = dataSource;
LogService.AddLog($"数据已更新,来源:{dataSource}");
LogService.AddLog($"已知点数量:{KnownPoints.Count}");
LogService.AddLog($"观测数据数量:{Observations.Count}");
}
public static void Clear()
{
KnownPoints.Clear();
Observations.Clear();
DataSource = "";
LogService.AddLog("数据已清空。");
}
}
}
+242
View File
@@ -0,0 +1,242 @@
using System;
using System.Collections.Generic;
using System.Linq;
using kcsj.Models;
namespace kcsj.Services
{
public static class LeastSquaresAdjustmentService
{
/// <summary>
/// 水准网间接平差
///
/// 观测关系:
/// h_AB = H_B - H_A
///
/// 误差方程:
/// v = Bx - L
///
/// 法方程:
/// N x = W
/// N = B^T P B
/// W = B^T P L
/// x = N^-1 W
/// </summary>
public static LeastSquaresResult Adjust(
List<KnownPoint> knownPoints,
List<Observation> observations)
{
if (knownPoints == null || knownPoints.Count == 0)
{
throw new ArgumentException("已知点不能为空。");
}
if (observations == null || observations.Count == 0)
{
throw new ArgumentException("观测数据不能为空。");
}
Dictionary<string, double> knownElevations =
knownPoints.ToDictionary(
p => p.Name.Trim(),
p => p.Elevation,
StringComparer.OrdinalIgnoreCase);
HashSet<string> allPointNames = new(StringComparer.OrdinalIgnoreCase);
foreach (Observation obs in observations)
{
if (string.IsNullOrWhiteSpace(obs.FromPoint) ||
string.IsNullOrWhiteSpace(obs.ToPoint))
{
throw new ArgumentException("观测数据中存在空点名。");
}
allPointNames.Add(obs.FromPoint.Trim());
allPointNames.Add(obs.ToPoint.Trim());
}
List<string> unknownNames = allPointNames
.Where(name => !knownElevations.ContainsKey(name))
.OrderBy(name => name)
.ToList();
if (unknownNames.Count == 0)
{
throw new InvalidOperationException("没有未知点,不需要进行间接平差。");
}
int observationCount = observations.Count;
int unknownCount = unknownNames.Count;
if (observationCount < unknownCount)
{
throw new InvalidOperationException("观测数小于未知数,无法进行最小二乘平差。");
}
Dictionary<string, int> unknownIndex = new(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < unknownNames.Count; i++)
{
unknownIndex[unknownNames[i]] = i;
}
MatrixOperations B = new MatrixOperations(observationCount, unknownCount);
MatrixOperations L = new MatrixOperations(observationCount, 1);
MatrixOperations P = new MatrixOperations(observationCount, observationCount);
double[] weights = new double[observationCount];
for (int i = 0; i < observationCount; i++)
{
Observation obs = observations[i];
string from = obs.FromPoint.Trim();
string to = obs.ToPoint.Trim();
// h_AB = H_B - H_A
// FromPoint 是未知点,系数为 -1
if (unknownIndex.ContainsKey(from))
{
B[i, unknownIndex[from]] = -1.0;
}
// ToPoint 是未知点,系数为 +1
if (unknownIndex.ContainsKey(to))
{
B[i, unknownIndex[to]] = 1.0;
}
// 已知点贡献:H_B(已知) - H_A(已知)
double knownContribution = 0.0;
if (knownElevations.ContainsKey(to))
{
knownContribution += knownElevations[to];
}
if (knownElevations.ContainsKey(from))
{
knownContribution -= knownElevations[from];
}
// L = h观测 - 已知点贡献
L[i, 0] = obs.HeightDiff - knownContribution;
// 水准测量常用定权:p = 1 / S
// 距离越长,权越小
double weight;
if (obs.Distance <= 0)
{
weight = 1.0;
}
else
{
weight = 1.0 / obs.Distance;
}
P[i, i] = weight;
weights[i] = weight;
}
MatrixOperations Bt = B.Transpose();
MatrixOperations N = Bt.Multiply(P).Multiply(B);
MatrixOperations W = Bt.Multiply(P).Multiply(L);
MatrixOperations Qxx = N.Inverse();
MatrixOperations X = Qxx.Multiply(W);
// v = Bx - L
MatrixOperations V = B.Multiply(X).Sub(L);
// V^T P V
MatrixOperations VtPV = V.Transpose().Multiply(P).Multiply(V);
int redundancy = observationCount - unknownCount;
double sigma0;
if (redundancy > 0)
{
sigma0 = Math.Sqrt(VtPV[0, 0] / redundancy);
}
else
{
sigma0 = double.NaN;
}
LeastSquaresResult result = new LeastSquaresResult
{
ObservationCount = observationCount,
UnknownCount = unknownCount,
Redundancy = redundancy,
Sigma0 = sigma0
};
foreach (KnownPoint point in knownPoints)
{
result.AdjustedElevations[point.Name.Trim()] = point.Elevation;
}
for (int i = 0; i < unknownCount; i++)
{
string pointName = unknownNames[i];
double elevation = X[i, 0];
result.UnknownElevations[pointName] = elevation;
result.AdjustedElevations[pointName] = elevation;
if (double.IsNaN(sigma0))
{
result.UnknownElevationErrors[pointName] = double.NaN;
}
else
{
result.UnknownElevationErrors[pointName] =
sigma0 * Math.Sqrt(Math.Abs(Qxx[i, i]));
}
}
for (int i = 0; i < observationCount; i++)
{
Observation obs = observations[i];
double residual = V[i, 0];
double adjustedHeightDiff = obs.HeightDiff + residual;
result.Residuals.Add(residual);
result.AdjustedHeightDiffs.Add(adjustedHeightDiff);
result.ObservationResults.Add(new ObservationAdjustmentResult
{
Index = i + 1,
FromPoint = obs.FromPoint,
ToPoint = obs.ToPoint,
ObservedHeightDiff = obs.HeightDiff,
Distance = obs.Distance,
Weight = weights[i],
Residual = residual,
AdjustedHeightDiff = adjustedHeightDiff
});
}
LogService.AddLog("间接平差计算完成。");
LogService.AddLog($"观测数:{observationCount}");
LogService.AddLog($"未知点数:{unknownCount}");
LogService.AddLog($"多余观测数:{redundancy}");
if (double.IsNaN(sigma0))
{
LogService.AddLog("单位权中误差:无法计算,多余观测数为 0。");
}
else
{
LogService.AddLog($"单位权中误差:{sigma0:F6}");
}
return result;
}
}
}
Loaded 3 of 5 files, more files were not shown because too many files have changed in this diff. Show more