initial commit

This commit is contained in:
2026-08-07 19:59:27 +05:30
commit 7bd45971fd
23 changed files with 1924 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
,,A,,
,,120,,
1,3,100,,
2,4,90,,
,,80,5,3
,,B,,
,,C,,
,,30,,
4,6,20,,
,,D,,
1 A
2 120
3 1 3 100
4 2 4 90
5 80 5 3
6 B
7 C
8 30
9 4 6 20
10 D
Binary file not shown.
+78
View File
@@ -0,0 +1,78 @@
import unittest
import numpy as np
from surveyladder.geo import chainage_to_xy, chainage_offset_to_xy, poly_area, poly_centroid, trilaterate_xy
class TestPolyArea(unittest.TestCase):
def test_poly_area(self):
polygon = np.array([[0, 0], [4, 0], [4, 3]])
self.assertEqual(poly_area(polygon), 6)
class TestTrilaterateXY(unittest.TestCase):
def test_trilaterate_xy_on_line(self):
A = np.array([0, 0])
B = np.array([10, 0])
a = 5
b = 5
C = trilaterate_xy(A, a, B, b)
np.testing.assert_almost_equal(C, [5, 0])
def test_trilaterate_xy_right_triangle(self):
A = np.array([0, 0])
B = np.array([4, 0])
a = 5
b = 3
C = trilaterate_xy(A, a, B, b)
np.testing.assert_almost_equal(C, [4, 3])
def test_trilaterate_xy_right_triangle_swapped(self):
A = np.array([4, 0])
B = np.array([0, 0])
a = 3
b = 5
C = trilaterate_xy(A, a, B, b)
np.testing.assert_almost_equal(C, [4, -3])
class TestChainageToXY(unittest.TestCase):
def test_positive_distance(self):
np.testing.assert_almost_equal(
chainage_to_xy(np.array([3, 5]), np.array([6, 9]), 10),
[9, 13])
def test_negative_distance(self):
np.testing.assert_almost_equal(
chainage_to_xy(np.array([3, 5]), np.array([6, 9]), -5),
[0, 1])
class TestChainageOffsetToXY(unittest.TestCase):
def test_positive_offset(self):
A = np.array([0, 0])
B = np.array([0, 1])
a_distance = 5
perp_distance = 2
P = chainage_offset_to_xy(A, B, a_distance, perp_distance)
np.testing.assert_almost_equal(P, [-2, 5])
class TestPolyCentroid(unittest.TestCase):
def test_pos_square(self):
# mathematically positive point ordering
poly = np.array([[0,0], [1,0], [1,2], [0,2]])
np.testing.assert_almost_equal(poly_centroid(poly), [0.5, 1])
def test_neg_square(self):
# mathematically negative point ordering
poly = np.array([[0,0], [1,0], [1,-2], [0,-2]])
np.testing.assert_almost_equal(poly_centroid(poly), [0.5, -1])
unittest.main()
+42
View File
@@ -0,0 +1,42 @@
import unittest
from surveyladder.ladder import load_ladder_csv, load_ladder_xls, get_seed_points, LadderSegment, OffsetPoint
class TestGetSeedPoints(unittest.TestCase):
def test_get_seed_points(self):
ladder_segments = ([
LadderSegment("D", "F", 161.3, []),
LadderSegment("C", "G", 133.2, []),
LadderSegment("C", "F", 138.5, []),
LadderSegment("G", "F", 157.6, []),
LadderSegment("C", "D", 130.5, [])
])
points = get_seed_points(ladder_segments)
self.assertEqual(len(points), 4)
for seg in ladder_segments:
start_point = points[seg.start_label]
end_point = points[seg.end_label]
actual_distance = ((start_point[0] - end_point[0]) ** 2 + (start_point[1] - end_point[1]) ** 2) ** 0.5
self.assertAlmostEqual(actual_distance, seg.distance, places=1)
class TestLadder(unittest.TestCase):
def test_load_ladder_xls(self):
self.maxDiff = None
segments = load_ladder_xls("tests/data/ladder.xlsx")
self.assertEqual(segments, ([
LadderSegment('A', 'B', 120.0, [OffsetPoint("1", 100.0, 3.0), OffsetPoint("2", 90.0, 4.0), OffsetPoint("3", 80.0, -5.0)]),
LadderSegment('C', 'D', 30.0, [OffsetPoint("4", 20.0, 6.0)])
]))
def test_load_ladder_csv(self):
self.maxDiff = None
segments = load_ladder_csv("tests/data/ladder.csv")
self.assertEqual(segments, ([
LadderSegment('A', 'B', 120.0, [OffsetPoint("1", 100.0, 3.0), OffsetPoint("2", 90.0, 4.0), OffsetPoint("3", 80.0, -5.0)]),
LadderSegment('C', 'D', 30.0, [OffsetPoint("4", 20.0, 6.0)])
]))
unittest.main()
+121
View File
@@ -0,0 +1,121 @@
import unittest
import numpy
from solver import OptimizationSolver, DirectSolver
from surveyladder.geo import distance, chainage_offset_to_xy, poly_area
class TestDirectSolver(unittest.TestCase):
def test_sanity_check_no_distances(self):
solver = DirectSolver(['A', 'B'])
with self.assertRaises(ValueError):
solver.sanity_check()
def test_sanity_check_not_enough_neighbors(self):
solver = DirectSolver(['A', 'B', 'C'])
solver.add_distance('A', 'B', 3)
with self.assertRaises(ValueError):
solver.sanity_check()
def test_simple_triangle_only_distances(self):
solver = DirectSolver(['A', 'B', 'C'])
solver.add_distance('A', 'B', 3)
solver.add_distance('B', 'C', 4)
solver.add_distance('A', 'C', 5)
points = solver.solve()
numpy.testing.assert_almost_equal(distance(points['A'], points['B']), 3, decimal=2)
numpy.testing.assert_almost_equal(distance(points['B'], points['C']), 4, decimal=2)
numpy.testing.assert_almost_equal(distance(points['A'], points['C']), 5, decimal=2)
class TestSolver(unittest.TestCase):
def test_distances_cost_optimal_solution(self):
solver = OptimizationSolver(['A', 'B', 'C'])
solver.add_distance('A', 'B', 3)
solver.add_distance('B', 'C', 4)
solver.add_distance('A', 'C', 5)
optimal_solution = numpy.array([[0, 0], [3, 0], [3, 4]]).flatten()
cost = solver.distances_cost(optimal_solution)
self.assertAlmostEqual(cost, 0.0, places=3)
def test_distances_cost_suboptimal_solution(self):
solver = OptimizationSolver(['A', 'B', 'C'])
solver.add_distance('A', 'B', 3)
solver.add_distance('B', 'C', 4)
solver.add_distance('A', 'C', 5)
suboptimal_solution = numpy.array([[0, 0], [6, 0], [6, 8]]).flatten()
# distance(A, B) = 6 instead of 3 -> (6-3)^2 = 9
# distance(B, C) = 8 instead of 4 -> (8-4)^2 = 16
# distance(A, C) = 10 instead of 5 -> (10-5)^2 = 25
# total cost = 9 + 16 + 25 = 50
cost = solver.distances_cost(suboptimal_solution)
self.assertAlmostEqual(cost, 50.0, places=3)
def test_offsets_cost_optimal_solution(self):
solver = OptimizationSolver(['A', 'B', 'C'])
solver.add_distance('A', 'B', 10)
solver.add_offset('C', 'A', 'B', 5, 2)
optimal_solution = numpy.array([[0, 0], [10, 0], [5, 2]]).flatten()
cost = solver.offsets_cost(optimal_solution)
self.assertAlmostEqual(cost, 0.0, places=3)
def test_offsets_cost_suboptimal_solution(self):
solver = OptimizationSolver(['A', 'B', 'C'])
solver.add_distance('A', 'B', 10)
solver.add_offset('C', 'A', 'B', 5, 2)
suboptimal_solution = numpy.array([[0, 0], [10, 0], [5, 4]]).flatten()
cost = solver.offsets_cost(suboptimal_solution)
# The expected position of C is (5, 2), but the actual position is (5, 4),
# so the cost should be (distance((5, 4), (5, 2)))^2 = 2^2 = 4
self.assertAlmostEqual(cost, 4.0, places=3)
def test_simple_triangle_only_distances_ccw(self):
solver = OptimizationSolver(['A', 'B', 'C'])
solver.add_distance('A', 'B', 3)
solver.add_distance('B', 'C', 4)
solver.add_distance('A', 'C', 5)
points = solver.solve()
numpy.testing.assert_almost_equal(distance(points['A'], points['B']), 3, decimal=2)
numpy.testing.assert_almost_equal(distance(points['B'], points['C']), 4, decimal=2)
numpy.testing.assert_almost_equal(distance(points['A'], points['C']), 5, decimal=2)
triangle = numpy.array([points['A'], points['B'], points['C']])
self.assertAlmostEqual(poly_area(triangle), 6.0, places=3)
def test_simple_triangle_only_distances_cw(self):
solver = OptimizationSolver(['A', 'C', 'B'])
solver.add_distance('A', 'C', 3)
solver.add_distance('B', 'C', 4)
solver.add_distance('A', 'B', 5)
points = solver.solve()
numpy.testing.assert_almost_equal(distance(points['A'], points['C']), 3, decimal=2)
numpy.testing.assert_almost_equal(distance(points['B'], points['C']), 4, decimal=2)
numpy.testing.assert_almost_equal(distance(points['A'], points['B']), 5, decimal=2)
triangle = numpy.array([points['A'], points['C'], points['B']])
self.assertAlmostEqual(poly_area(triangle), -6.0, places=3)
def test_simple_triangle_with_offset(self):
solver = OptimizationSolver(['A', 'B', 'C'])
solver.add_distance('A', 'B', 10)
solver.add_offset('C', 'A', 'B', 5, 2)
points = solver.solve()
numpy.testing.assert_almost_equal(distance(points['A'], points['B']), 10, decimal=2)
numpy.testing.assert_almost_equal(points['C'], chainage_offset_to_xy(points['A'], points['B'], 5, 2), decimal=2)
unittest.main()