fix(integrate): sort boundary edges as pairs
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -38,6 +38,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
standard errors, t and F statistics read factored sums of squares
|
standard errors, t and F statistics read factored sums of squares
|
||||||
instead of reporting infinity beside zero evidence.
|
instead of reporting infinity beside zero evidence.
|
||||||
|
|
||||||
|
**Integration and optimisation.**
|
||||||
|
|
||||||
|
- `TriangleMesh2D.BoundaryEdges` returns the mesh's own edges: the
|
||||||
|
boundary pairs sort as pairs, never as one flat index list that
|
||||||
|
interleaves the endpoints of unrelated edges.
|
||||||
|
|
||||||
## [1.0.0] - 2026-09-03
|
## [1.0.0] - 2026-09-03
|
||||||
|
|
||||||
The initial release of Tensor, a scientific computing library in pure
|
The initial release of Tensor, a scientific computing library in pure
|
||||||
|
|||||||
+15
-3
@@ -114,13 +114,25 @@ func (m *TriangleMesh2D) BoundaryEdges() []int {
|
|||||||
count[key(b, c)]++
|
count[key(b, c)]++
|
||||||
count[key(c, a)]++
|
count[key(c, a)]++
|
||||||
}
|
}
|
||||||
edges := make([]int, 0, 8)
|
sets := make([][2]int, 0, len(count))
|
||||||
for e, n := range count {
|
for e, n := range count {
|
||||||
if n == 1 {
|
if n == 1 {
|
||||||
|
sets = append(sets, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The pairs sort as pairs, never as one flat index list: a flat sort
|
||||||
|
// interleaves the endpoints of unrelated edges and hands back pairs
|
||||||
|
// the mesh does not carry.
|
||||||
|
slices.SortFunc(sets, func(x, y [2]int) int {
|
||||||
|
if x[0] != y[0] {
|
||||||
|
return x[0] - y[0]
|
||||||
|
}
|
||||||
|
return x[1] - y[1]
|
||||||
|
})
|
||||||
|
edges := make([]int, 0, 2*len(sets))
|
||||||
|
for _, e := range sets {
|
||||||
edges = append(edges, e[0], e[1])
|
edges = append(edges, e[0], e[1])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
slices.Sort(edges)
|
|
||||||
return edges
|
return edges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -434,6 +434,34 @@ func TestTriangleMesh2DBoundaryEdges(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTriangleMesh2DBoundaryEdgesAreMeshEdges pins the pair contract of
|
||||||
|
// BoundaryEdges: every returned pair must be an edge the mesh actually
|
||||||
|
// carries, and the pairs must come out sorted, which a sort of the flat
|
||||||
|
// index list cannot deliver (it interleaves unrelated endpoints).
|
||||||
|
func TestTriangleMesh2DBoundaryEdgesAreMeshEdges(t *testing.T) {
|
||||||
|
mesh, err := GridTriangleMesh2D(0, 0, 1, 1, 1, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GridTriangleMesh2D: %v", err)
|
||||||
|
}
|
||||||
|
edges := mesh.BoundaryEdges()
|
||||||
|
// The square's four sides: (0,1), (0,2), (1,3), (2,3) in sorted
|
||||||
|
// pair order.
|
||||||
|
want := []int{0, 1, 0, 2, 1, 3, 2, 3}
|
||||||
|
if len(edges) != len(want) {
|
||||||
|
t.Fatalf("boundary edge count %d, want %d", len(edges)/2, len(want)/2)
|
||||||
|
}
|
||||||
|
for p := 0; p < len(edges); p += 2 {
|
||||||
|
if edges[p] > edges[p+1] {
|
||||||
|
t.Fatalf("edge [%d,%d] is not sorted as a pair", edges[p], edges[p+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for p := range len(want) {
|
||||||
|
if edges[p] != want[p] {
|
||||||
|
t.Fatalf("boundary edges %v, want %v", edges, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestSolvePoissonFEM2DDuplicateDirichletNode pins the documented rule
|
// TestSolvePoissonFEM2DDuplicateDirichletNode pins the documented rule
|
||||||
// for a node listed more than once: the last value is the prescribed
|
// for a node listed more than once: the last value is the prescribed
|
||||||
// one and the node enters the assembled system exactly once, so the
|
// one and the node enters the assembled system exactly once, so the
|
||||||
|
|||||||
Reference in New Issue
Block a user