geometry: check overlap on final segments and class junction nodes - #127
Merged
Conversation
added 3 commits
August 5, 2026 19:18
The pre-solve wire intersection check read m_wires, a shadow list of the original GW card inputs, instead of the segment arrays the solver actually uses. That shadow list carried no segmentation topology, was never updated by GM, GX, GR, or GS, and stored one radius per card even when GC tapered each segment along its length. Valid NEC-2 models were rejected at crossed nodes, translated wires, and tapered cones, while some genuinely overlapping models went undetected because parallel axes drove the whole-wire solve into a sentinel return. - delete m_wires and its writes in wire(), so geometry has one owner: the segment arrays populated after connect_segments() establishes topology - add check_segment_intersections(), sweeping final segment centers, lengths, radii, and direction cosines along the structure's widest axis, bounded by each segment's own half-length-plus-radius reach - exclude segment pairs NEC already recorded as joined via icon1/icon2, so a shared node reads as a connection rather than an intersection - call the new check from geometry_complete() after the SEGMENT DATA ERROR gate, so no zero-length axis reaches the point-in-cylinder solve - add permanent regressions for crossed wires sharing a node, a wire translated by move(), a tapered chain, coincident wires, and interior penetration away from any node Signed-off-by: Eric Wheeler <necpp@z.ewheeler.org>
…ng channel The segment overlap check rejected legal NEC-2 junctions. It excluded a pair of segments from the volume test by reading icon1/icon2, which record at most one contact per segment end, so a node where six ends meet is recorded as a cycle rather than a clique and pairs not adjacent in that cycle looked unconnected. A ground-plane vertical on a mast reported GEOMETRY DATA ERROR -- SEGMENT tmolteno#14 (TAG ID tmolteno#1) MIDPOINT LIES WITHIN SEGMENT tmolteno#66 (TAG ID tmolteno#3) at a measured 0.01308 against a radius of 0.02500. Where an end meets the ground the record names the ground rather than a neighbour, so two legs meeting at z = 0 had no link at all. Classing segment ends into nodes from the final coordinates recovers the clique whatever order build_connections() linked them in, and the exclusion now applies to pairs sharing exactly one node: two segments holding both nodes are laid one along the other, not joined. - add nec_node_partition, a union-find over the 2*n_segments segment ends united on the larger of the two owning thresholds, the symmetric closure of the contact relation - replace segments_joined() and connected_segment_index() with shares_one_node(), so the check no longer reads the solver connection record - generalize widest_extent_axis() and extract sort_along_axis() over both the endpoint sweep and the segment sweep, which order positions by the same rule - record nec_overlap_finding per violation, carrying the distance and radius measured, so the diagnostic reports how far inside the conductor the center falls - add set_intersection_fatal(bool) and overlap_findings() beside the preserved set_intersection_check(bool), letting a caller read the measurement either policy produces - move the ROM2 step-size explanation latch from a file static to a context member, so a second context in one process still receives it - add four [segment_junction] regressions: the six-way node, the junction on the ground plane, the intrusion past a shared node, and the warning policy reading its findings back Signed-off-by: Eric Wheeler <necpp@z.ewheeler.org>
The overlap policy introduced with the node classing was reachable only from the C++ library, so a command-line user with a model that trips the volume test had no way to see the measurement and continue. This exposes that policy as a command-line option, leaving the default unchanged: without -w the first finding still rejects the geometry. - accept -w in the option string and clear the geometry fatal flag when it is given, so each finding is reported after the segment table and the solve proceeds - name the option in the usage text, so its effect on a rejected model is discoverable Signed-off-by: Eric Wheeler <necpp@z.ewheeler.org>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The pre-solve wire intersection check read
m_wires, a shadow list of the originalGWcardinputs. That list has no segmentation topology, is never updated by
GM,GX,GR, orGS, andstores one radius per card even when
GCtapers each segment, so legal models were rejected whileoverlapping ones were admitted. Parallel axes escaped the test altogether, because
nec_wire::int_solve()returns a9.0e9sentinel when the two axes are parallel.The check now derives from the final segment arrays, and it recognises a junction from the geometry
rather than from the solver connection record.
build_connections()stops at the first contact foundfor each segment end, so a node where six ends meet is recorded as a cycle of six links rather than
as the complete graph of fifteen pairs; where an end meets the ground it records the ground rather
than a neighbour. Classing segment ends into nodes from the final coordinates recovers the full
clique in both cases.
Validation capability is retained: a segment center inside the volume of a segment it meets at no
node is still rejected, now with the measurement in the diagnostic.
the solver path, output formats,
card semantics, and
c_geometry::set_intersection_check(bool)ard unchanged.Type of Change
The invariant
NEC-2 Part 3, Program Description (https://www.nec2.org/other/nec2prt3.pdf) states that there is no
restriction on the angle between two connected wires, but accuracy is lost when the center of a
segment falls within the volume of the wire that segment connects to; separately, wires that
intersect away from their ends are not connected, and errors occur when one wire occupies the space
of another.
The two cases differ in kind, which is why one is a caution and the other an error. At a shared node
the meeting segments carry continuous current solved as one unknown set, so an intrusion degrades the
local field integral at the joint while the topology stays correct and the error stays bounded.
Without a shared node the segments carry independent unknowns, and the thin-wire kernel, which places
the source current on the axis and the observation point on the surface at
r >= a, is evaluated atr < a, inside a conductor whose interior the model does not represent.Sharing both nodes is not a junction. Two segments holding the same pair of nodes are not joined
end to end but laid one along the other, and their volumes coincide over their whole length. The
exclusion therefore applies to pairs meeting at exactly one node.
Implementation Details
m_wiresis deleted; the check derives from the final segment arrays(
x,y,z,segment_length,cab,sab,salp,segment_radius,segment_tags) afterconnect_segments(), and stores nothing.nec_node_partitionis a union-find over the2 * n_segmentssegment ends, built from the finalcoordinates after ground contacts have been snapped. Ends unite when they are within the larger of
the two owning contact thresholds, the symmetric closure of the relation
build_connections()evaluates asymmetrically. Clique completeness is then structural, and
icon1/icon2are no longerread by the check; they remain the solver connection record.
reach,
0.5 * length + radius, so a larger separation along that axis closes the window.radius reach the diagnostic.
place still reads the measurement after catching the exception.
nec_contextmember, so asecond context in one process still receives it.
Warning channel
Default behavior is unchanged: the first finding still throws. New, additive:
The C interface and its
0success and1error contract are untouched.Reproduction
Each deck below is an unmodified published model, one per behavior under change.
A junction of six segment ends, admitted
Four radials from
GR, a radiator, and a mast all contribute an end to the node at the origin.Before: rejected at geometry completion with
GEOMETRY DATA ERROR -- SEGMENT #14 (TAG ID #1) MIDPOINT LIES WITHIN SEGMENT #66 (TAG ID #3),because the connection record named one of the five other ends at that node and segment 66 was not
that one. The measurement behind that rejection is
0.01308against a radius of0.02500.After: the six ends form one node class, the pair shares exactly that node, no finding is produced,
and the solve completes with
TOTAL SEGMENTS USED: 140andTOTAL RUN TIME: 2190 msec.An intrusion past a shared node, rejected
The feed leaves the hub at a shallow angle and comes to rest inside the second segment of a radial.
That segment spans
xfrom0.0045to0.009and holds no endpoint at the hub, so the pair sharesno node and carries independent unknowns.
Before: admitted. The whole-wire model compared cards, and the card-order midpoint pass examined only
the first and last segment of each card, so the pair was never measured.
After: rejected at geometry completion, exit status 1, with the measurement in the diagnostic:
The same deck under the warning policy
Run as
nec2++ -w -i 23cm_helix+radials.nec -o 23cm_helix+radials.out, a caller who accepts thedegraded joint obtains the solve and the measurement together.
Before: no such state existed. A geometry finding either threw or did not exist.
After:
TOTAL SEGMENTS USED: 565, the finding written after the segment table, andTOTAL RUN TIME: 29680 msec:Testing
606 assertions in 63 test cases pass;
ctestreports 2 of 2. Both pre-existing intersectionregressions, the shared-endpoint case and the bypass case, are preserved byte for byte. Five
[segment_intersection]cases are added (crossed wires sharing a node, a wire translated bymove(), a tapered chain, coincident wires, interior penetration) and four[segment_junction]cases (the six-way node, the junction on the ground plane, the intrusion past a shared node, and the
warning policy reading its findings back). The coincident-wire and interior-penetration cases assert
a throw, so the change cannot be read as a removal of checking.