Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/fasthenry-compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,14 @@ records how each documented field maps.
|---|---|---|
| `G<name> x1= y1= z1= x2= y2= z2= x3= y3= z3=` (three corner points) | Supported, differs | The plane must be axis-aligned and parallel to the xy plane (`z1 = z2 = z3`, each edge along x or y): this engine's plane model is an axis-aligned rectangle. A tilted or rotated plane is rejected by name rather than squared off. The corner points give the plane's mid-thickness surface, as a segment's nodes give its axis. The three points also fix the **plane's own coordinate system** — `p1` the origin, `p1 → p2` its x-direction, `p2 → p3` its y-direction — which is the frame every `x…`/`y…` pair of *lengths* in the clauses below is stated in; see "Decision: a `contact` clause's `x…`/`y…` pair is in plane coordinates" below (issue #118). |
| `thick=` | Supported | Plane thickness. |
| `seg1=`, `seg2=` | Supported, differs | Become the background cell counts of this engine's own cell-centre PEEC mesh, not FastHenry's panel mesh: equal counts mean equal resolution, not an identical node set. |
| `seg1=`, `seg2=` | Supported, differs | Become the background cell counts of this engine's own cell-centre PEEC mesh, not FastHenry's panel mesh: equal counts mean equal resolution, not an identical node set. A uniform plane (no `file=`) missing `seg1`/`seg2` (or either one) is an error in both modes, as in FastHenry; the only exemption is the `--fasthenry-compat` `file=NONE` single-cell case (issue #143, see the `file=NONE` row). |
| `sigma=` | Supported, differs (opt-in) | Per deck unit, falling back to the `.default` conductivity (`.default sigma=` or `.default rho=`). A plane with neither is a line-numbered error by default; under `--fasthenry-compat` it is copper, 5.8e7 S/m physical whatever the `.units`, with a line-numbered warning on the statement (`ground plane 'G1' has no conductivity; using copper (5.8e7 S/m, FastHenry default)`), exactly as for a segment and for the same reason — operator decision: defaults under compat, strict otherwise (issue #142; see `E … sigma` above). The extension `G` form follows the same rule. |
| `nhinc=` | Supported | Filaments through the plane's thickness. |
| `rho=` | Supported | Issue #88. Per deck unit, the exact reciprocal of `sigma=` and accepted on the corner-point statement itself (continuation lines included); naming both `sigma=` and `rho=` on one statement is a line-numbered error, as it is elsewhere. |
| `relx=`/`rely=`/`relz=` | Supported | Issue #146. The documented offset (User's Guide §1.3.9), default 0, in every mode: it is added to every in-plane coordinate on the statement — each `N<name> (x, y, z)` node reference, each `hole` shape's points or centre, and each `contact` clause's points, ends or centre (`rect`, `decay_rect`, `point`, `line`, `trace`, `equiv_rect`, `connection`) — but **not** to the corner points `x1…z3`, so the mesh is unchanged while the connections move. A `relx=0.3` deck reads exactly as the same deck with those coordinates written 0.3 larger. Only coordinates (points, centres) move; widths, cell sizes and radii do not (they are lengths — see the plane-coordinate decision below, issue #118). Position-independent: a key written after the clauses still applies to them, and a repeated key's last value wins for every point. A `relz` that lifts a clause or node out of the plane's slab is the same line-numbered error the shifted coordinate would be. |
| Empty coordinate field in a node reference, `N<name> (, y, z)` | Supported (compat only) | Issue #146. Under `--fasthenry-compat`, one empty field reads as 0 before the `relx`/`rely`/`relz` offset, with a line-numbered warning. Natively it is a line-numbered error, and two or more empty fields are an error in both modes. Clause value lists (`hole …`, `contact …`) are unaffected. |
| `rh=`, `segwid1=`/`segwid2=` | Not supported | Each rejected by name with the reason and the alternative (plane filaments are uniform; bar widths follow the cells). Nothing on a `G` statement is silently ignored. |
| `file=NONE` | Supported | Issue #122. The nonuniform-plane description defines `file=` as an *input* — the file holding the plane's discretization hierarchy — and `NONE` as "no such file": the hierarchy is a single root cell, discretized at run time from the statement's own clauses. That is the only case this reader ever has, so `file=NONE` is accepted as the no-op it is and the documented `file=NONE contact initial_grid (10,12)` spelling reads as written. Matched without regard to case, like every other token this reader interprets. |
| `file=NONE` | Supported | Issue #122. The nonuniform-plane description defines `file=` as an *input* — the file holding the plane's discretization hierarchy — and `NONE` as "no such file": the hierarchy is a single root cell, discretized at run time from the statement's own clauses. That is the only case this reader ever has, so `file=NONE` is accepted as the no-op it is and the documented `file=NONE contact initial_grid (10,12)` spelling reads as written. Matched without regard to case, like every other token this reader interprets. Issue #143: under `--fasthenry-compat`, `file=NONE` with neither `seg1`/`seg2` nor a `contact initial_grid` is the single root cell — it meshes as `contact initial_grid (1, 1)` (the `contact` refinements still apply) with a warning on the `G` line; native mode keeps the `has no 'seg1'` error. |
| `file=<name>` (a named hierarchy file) | Not supported | Issue #122. Rejected by name on the statement's own line: a stored nonuniform-discretization hierarchy is an *input* this reader does not read (not, as the message used to say, an output it does not write), and silently ignoring it would mesh the plane at a resolution the deck never asked for. The error names the file, says only `file=NONE` is accepted, and points at the in-statement alternatives — `seg1`/`seg2` or `contact initial_grid (n1, n2)` plus the `contact` refinement clauses, which is what this reader discretizes from. |
| In-plane node `N<name> (x, y, z)` | Supported | An ordinary deck node belonging to the plane; a reference to it lands on the nearest live cell-centre node of that plane. |
| `hole rect (x1, y1, z1, x2, y2, z2)` | Supported | Two opposite corners, as documented, onto the plane model's rectangular hole. The redundant `z` coordinates are checked against the plane's slab, so a rectangle meant for another plane cannot land here silently. A rectangle (like a `hole point`/`circle`, `contact rect` or `contact decay_rect`) wholly outside the plane's footprint is accepted with a line-numbered warning — see the decision below (issue #105). |
Expand Down
97 changes: 85 additions & 12 deletions fasterhenry-cli/src/inp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1643,6 +1643,8 @@ struct PlaneStatement<'a> {
/// Whether that clause was the meshed form, which additionally punches
/// the documented checkerboard of holes into the initial grid.
meshed_grid: bool,
/// Whether the statement carried `file=NONE`.
file_none: bool,
/// `sigma=` / `rho=` (S/m), or the `.default` conductivity.
sigma: Option<f64>,
/// `nhinc=` (1 unless set).
Expand Down Expand Up @@ -1808,6 +1810,7 @@ impl<'a> PlaneStatement<'a> {
),
));
}
self.file_none = true;
}
"nx" | "ny" => {
return Err(err(
Expand Down Expand Up @@ -2209,6 +2212,16 @@ impl<'a> PlaneStatement<'a> {
}
}

/// Whether, under `--fasthenry-compat`, this `file=NONE` plane gives no
/// initial grid at all (no `seg1`/`seg2`, no `contact initial_grid`) and
/// so meshes as a single root cell (issue #143).
fn single_root_cell(&self) -> bool {
self.compat
&& self.file_none
&& self.segments == [None, None]
&& self.initial_grid.is_none()
}

/// Checks the statement's geometry — three corners of an axis-aligned
/// rectangle parallel to xy, both cell counts, a positive thickness,
/// and a conductivity — and returns the frame the plane's features are
Expand Down Expand Up @@ -2264,18 +2277,23 @@ impl<'a> PlaneStatement<'a> {
}
};
let mut cells = [0usize; 2];
cells[axis1] = self.segments[0].ok_or_else(|| {
err(
line,
format!("ground plane '{head}' has no 'seg1' (cells along p1→p2)"),
)
})?;
cells[axis2] = self.segments[1].ok_or_else(|| {
err(
line,
format!("ground plane '{head}' has no 'seg2' (cells along p2→p3)"),
)
})?;
if self.single_root_cell() {
// `file=NONE` with no grid at all: one root cell (issue #143).
cells = [1, 1];
} else {
cells[axis1] = self.segments[0].ok_or_else(|| {
err(
line,
format!("ground plane '{head}' has no 'seg1' (cells along p1→p2)"),
)
})?;
cells[axis2] = self.segments[1].ok_or_else(|| {
err(
line,
format!("ground plane '{head}' has no 'seg2' (cells along p2→p3)"),
)
})?;
}
let thickness = self.thickness.ok_or_else(|| {
err(
line,
Expand Down Expand Up @@ -2337,6 +2355,13 @@ impl<'a> PlaneStatement<'a> {
/// off the plane), but neither is an error.
fn finish(self) -> Result<(PlaneSpec, Vec<PlaneNode>, Vec<ParseWarning>), ParseError> {
let frame = self.frame()?;
let single_root_cell = self.single_root_cell().then(|| ParseWarning {
line: self.line,
message: format!(
"ground plane '{}' has 'file=NONE' and no 'seg1'/'seg2' or 'contact initial_grid': its initial grid is a single cell (as 'contact initial_grid (1, 1)'), refined only by its 'contact' clauses",
self.head
),
});
let PlaneStatement {
head,
nhinc,
Expand All @@ -2353,6 +2378,9 @@ impl<'a> PlaneStatement<'a> {
mut warnings,
..
} = self;
if let Some(warning) = single_root_cell {
warnings.push((0, warning));
}

// The meshed initial grid's holes are cut into the *initial* grid,
// before anything else refines it, so they come first.
Expand Down Expand Up @@ -6569,6 +6597,51 @@ Gp x1=0 y1=0 z1=0 x2=10 y2=0 z2=0 x3=10 y3=6 z3=0
);
}

/// Issue #143: under compat, `file=NONE` with no initial grid is a single
/// root cell, warned about on the `G` line; native mode and a uniform
/// plane missing `seg1`/`seg2` stay errors.
#[test]
fn compat_file_none_without_a_grid_is_a_single_cell() {
let opts = ParseOptions {
fasthenry_compat: true,
};
let g = "\
Gp x1=0 y1=0 z1=0 x2=10 y2=0 z2=0 x3=10 y3=6 z3=0
+ thick=0.04";
let compat = |statement: &str| {
parse_with_options_reporting(&format!("title\n{}", plane_deck(statement, "")), opts)
};
let (bare, warnings) = compat(&format!("{g} file=NONE")).unwrap();
let explicit = parse_ok(&plane_deck(&format!("{g} contact initial_grid (1, 1)"), ""));
assert_eq!(bare.geometry, explicit.geometry);
assert_eq!(warnings.len(), 1, "{warnings:?}");
assert_eq!(warnings[0].line, 4);
assert!(warnings[0].message.contains("single cell"));

// Contact refinements still apply.
let refine = "contact rect (5, 3, 0, 4, 4, 1, 1)";
let (refined, _) = compat(&format!("{g} file=NONE {refine}")).unwrap();
let by_hand = parse_ok(&plane_deck(
&format!("{g} contact initial_grid (1, 1) {refine}"),
"",
));
assert_eq!(refined.geometry, by_hand.geometry);
assert_ne!(refined.geometry, bare.geometry);

// An explicit grid means no warning.
let (_, warnings) = compat(&format!("{g} file=NONE seg1=2 seg2=2")).unwrap();
assert!(warnings.is_empty(), "{warnings:?}");

// Native mode: unchanged error.
let error = parse(&plane_deck(&format!("{g} file=NONE"), "")).unwrap_err();
assert!(error.message.contains("has no 'seg1'"), "{error}");
// Uniform plane missing seg1/seg2, or only one: error in both modes.
for body in [g.to_string(), format!("{g} seg1=3"), format!("{g} seg2=3")] {
assert!(compat(&body).is_err(), "{body}");
assert!(parse(&plane_deck(&body, "")).is_err(), "{body}");
}
}

/// `seg1` counts cells along `p1 → p2` and `seg2` along `p2 → p3`,
/// whichever axis each of those edges runs along.
#[test]
Expand Down
Loading