最小二乘-矩阵运算
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace kcsj.Models
|
||||||
|
{
|
||||||
|
public class LeastSquaresResult
|
||||||
|
{
|
||||||
|
public int ObservationCount { get; set; }
|
||||||
|
public int UnknownCount { get; set; }
|
||||||
|
public int Redundancy { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单位权中误差
|
||||||
|
/// </summary>
|
||||||
|
public double Sigma0 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 未知点平差高程
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, double> UnknownElevations { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 所有点平差后高程,包括已知点和未知点
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, double> AdjustedElevations { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 未知点高程中误差
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, double> UnknownElevationErrors { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每条观测的改正数 v
|
||||||
|
/// </summary>
|
||||||
|
public List<double> Residuals { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每条观测的平差后高差
|
||||||
|
/// </summary>
|
||||||
|
public List<double> AdjustedHeightDiffs { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每条观测的详细结果
|
||||||
|
/// </summary>
|
||||||
|
public List<ObservationAdjustmentResult> ObservationResults { get; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ObservationAdjustmentResult
|
||||||
|
{
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
public string FromPoint { get; set; } = "";
|
||||||
|
public string ToPoint { get; set; } = "";
|
||||||
|
|
||||||
|
public double ObservedHeightDiff { get; set; }
|
||||||
|
public double Distance { get; set; }
|
||||||
|
public double Weight { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 改正数 v
|
||||||
|
/// </summary>
|
||||||
|
public double Residual { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 平差后高差 h + v
|
||||||
|
/// </summary>
|
||||||
|
public double AdjustedHeightDiff { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,无法进行精度评定。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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("数据已清空。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace kcsj.Services
|
||||||
|
{
|
||||||
|
public class MatrixOperations
|
||||||
|
{
|
||||||
|
private const double eps = 1e-2;
|
||||||
|
private readonly double[,] values;
|
||||||
|
|
||||||
|
private int RowCount { get; }
|
||||||
|
private int ColumnCount { get; }
|
||||||
|
|
||||||
|
public double this[int row, int column]
|
||||||
|
{
|
||||||
|
get => values[row, column];
|
||||||
|
set => values[row, column] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化矩阵大小
|
||||||
|
public MatrixOperations(int rowCount, int columnCount)
|
||||||
|
{
|
||||||
|
if (rowCount <= 0 || columnCount <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("矩阵行列数必须大于 0。");
|
||||||
|
}
|
||||||
|
|
||||||
|
RowCount = rowCount;
|
||||||
|
ColumnCount = columnCount;
|
||||||
|
values = new double[rowCount, columnCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从二维数组初始化矩阵
|
||||||
|
public MatrixOperations(double[,] source)
|
||||||
|
{
|
||||||
|
RowCount = source.GetLength(0);
|
||||||
|
ColumnCount = source.GetLength(1);
|
||||||
|
values = new double[RowCount, ColumnCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < RowCount; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < ColumnCount; j++)
|
||||||
|
{
|
||||||
|
values[i, j] = source[i, j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatrixOperations Add(MatrixOperations other)
|
||||||
|
{
|
||||||
|
if (RowCount != other.RowCount || ColumnCount != other.ColumnCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("矩阵维度不匹配,无法相加。");
|
||||||
|
}
|
||||||
|
MatrixOperations result = new MatrixOperations(RowCount, ColumnCount);
|
||||||
|
for (int i = 0; i < RowCount; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < ColumnCount; j++)
|
||||||
|
{
|
||||||
|
result[i, j] = this[i, j] + other[i, j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatrixOperations Sub (MatrixOperations other)
|
||||||
|
{
|
||||||
|
if (RowCount != other.RowCount || ColumnCount != other.ColumnCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("矩阵维度不匹配,无法相减。");
|
||||||
|
}
|
||||||
|
MatrixOperations result = new MatrixOperations(RowCount, ColumnCount);
|
||||||
|
for (int i = 0; i < RowCount; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < ColumnCount; j++)
|
||||||
|
{
|
||||||
|
result[i, j] = this[i, j] - other[i, j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatrixOperations Multiply(MatrixOperations other)
|
||||||
|
{
|
||||||
|
if (ColumnCount != other.RowCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("矩阵维度不匹配,无法相乘。");
|
||||||
|
}
|
||||||
|
MatrixOperations result = new MatrixOperations(RowCount, other.ColumnCount);
|
||||||
|
for (int i = 0; i < RowCount; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < other.ColumnCount; j++)
|
||||||
|
{
|
||||||
|
double sum = 0;
|
||||||
|
for (int k = 0; k < ColumnCount; k++)
|
||||||
|
{
|
||||||
|
sum += this[i, k] * other[k, j];
|
||||||
|
}
|
||||||
|
result[i, j] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatrixOperations Transpose()
|
||||||
|
{
|
||||||
|
MatrixOperations result = new MatrixOperations(ColumnCount, RowCount);
|
||||||
|
for (int i = 0; i < RowCount; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < ColumnCount; j++)
|
||||||
|
{
|
||||||
|
result[j, i] = this[i, j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatrixOperations Inverse()
|
||||||
|
{
|
||||||
|
if (RowCount != ColumnCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("只有方阵才有逆矩阵。");
|
||||||
|
}
|
||||||
|
int n = RowCount;
|
||||||
|
MatrixOperations augmented = new MatrixOperations(n, 2 * n);
|
||||||
|
// 构造增广矩阵 [A | I]
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < n; j++)
|
||||||
|
{
|
||||||
|
augmented[i, j] = this[i, j];
|
||||||
|
}
|
||||||
|
augmented[i, n + i] = 1; // 添加单位矩阵部分
|
||||||
|
}
|
||||||
|
// 使用高斯消元法将左侧变为单位矩阵
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
// 寻找主元素
|
||||||
|
int pivotRow = i;
|
||||||
|
for (int row = i + 1; row < n; row++)
|
||||||
|
{
|
||||||
|
if (Math.Abs(augmented[row, i]) > Math.Abs(augmented[pivotRow, i]))
|
||||||
|
{
|
||||||
|
pivotRow = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Math.Abs(augmented[pivotRow, i]) < eps)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("矩阵不可逆。");
|
||||||
|
}
|
||||||
|
// 交换行
|
||||||
|
if (pivotRow != i)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < 2 * n; col++)
|
||||||
|
{
|
||||||
|
double temp = augmented[i, col];
|
||||||
|
augmented[i, col] = augmented[pivotRow, col];
|
||||||
|
augmented[pivotRow, col] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 将主元素归一化
|
||||||
|
double pivotValue = augmented[i, i];
|
||||||
|
for (int col = 0; col < 2 * n; col++)
|
||||||
|
{
|
||||||
|
augmented[i, col] /= pivotValue;
|
||||||
|
}
|
||||||
|
// 消去其他行的当前列
|
||||||
|
for (int row = 0; row < n; row++)
|
||||||
|
{
|
||||||
|
if (row != i)
|
||||||
|
{
|
||||||
|
double factor = augmented[row, i];
|
||||||
|
for (int col = 0; col < 2 * n; col++)
|
||||||
|
{
|
||||||
|
augmented[row, col] -= factor * augmented[i, col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 提取右侧的逆矩阵
|
||||||
|
MatrixOperations inverse = new MatrixOperations(n, n);
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < n; j++)
|
||||||
|
{
|
||||||
|
inverse[i, j] = augmented[i, n + j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user