Two major changes in this version. First stroid now embeds a vacuum coordinate as part of its StroidMesh file (this is a packed set of mfem meshes and GridFunction). This is a logical coordinate from 0 at the stellar surface to 1 at the mesh surface / compactified infinity which can be used by consumers to much more stablly infer position in the vacuum region. Second, there is a new topology backend, multi_block, which has been made the default. See the readme for more information but the basic jist is that multi_block addes 6 transition blocks onto the edge of the core domain. This allows for a much more well conditioned transition from the internal cartesien region to the external spherical region. The mesh conditioning improves by roughly a factor of 1000 for the same refinement level when compared to the legacy topology. The legacy topology is maintained as a option if core_mapping is set to spherified in the config.
127 lines
5.0 KiB
Python
127 lines
5.0 KiB
Python
"""Focused core-mapping smoke checks for a built or staged STROID Python module.
|
|
|
|
Example:
|
|
python core_mapping_smoke.py --module _stroid --module-dir build/build-python
|
|
python core_mapping_smoke.py --module-dir /path/to/staged/site-packages
|
|
"""
|
|
|
|
import argparse
|
|
import importlib
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
def element_counts(stroid, mesh):
|
|
result = stroid.stats.ComputeMeshStats(
|
|
mesh, stroid.stats.MeshStatFeatures.ELEMENT_COUNT
|
|
)
|
|
assert not result.errors, result.errors
|
|
assert result.element_counts is not None
|
|
return result.element_counts
|
|
|
|
|
|
def run(stroid):
|
|
default = stroid.config.MeshConfig()
|
|
assert default.core_mapping == "spherified"
|
|
default.core_mapping = "multi_block"
|
|
assert default.core_mapping == "multi_block"
|
|
assert "core_mapping: multi_block" in repr(default)
|
|
|
|
summaries = []
|
|
with tempfile.TemporaryDirectory(prefix="stroid-python-smoke-") as output:
|
|
output_path = Path(output)
|
|
config_path = output_path / "multi_block.toml"
|
|
config_path.write_text(
|
|
'[main]\ncore_mapping = "multi_block"\nrefinement_levels = 0\n'
|
|
'order = 3\ninclude_external_domain = false\n'
|
|
'r_core = 0.25\nr_star = 1.0\nr_infinity = 6.0\n'
|
|
'flattening = 0.0\nr_instability = 1e-14\n'
|
|
'core_steepness = 1.0\ncontinuity_order = 2\n'
|
|
'surface_bdr_id = 1\ninf_bdr_id = 2\n'
|
|
'core_id = 1\nenvelope_id = 2\nvacuum_id = 3\n'
|
|
'[main.optimization_methods]\ntmop = false\nsmoothstep = true\n'
|
|
)
|
|
configured_mesh = stroid.GenerateMesh(str(config_path))
|
|
assert configured_mesh.config.core_mapping == "multi_block"
|
|
assert element_counts(stroid, configured_mesh).total == 13
|
|
|
|
for mapping in ("spherified", "multi_block"):
|
|
for external in (False, True):
|
|
config = stroid.config.MeshConfig(
|
|
core_mapping=mapping,
|
|
refinement_levels=0,
|
|
order=3,
|
|
include_external_domain=external,
|
|
optimization_methods=stroid.config.OptimizationMethods(
|
|
tmop=False, smoothstep=True
|
|
),
|
|
)
|
|
mesh = stroid.GenerateMesh(config)
|
|
assert mesh.has_mesh() and mesh.has_rmesh()
|
|
counts = element_counts(stroid, mesh)
|
|
expected_core = 7 if mapping == "multi_block" else 1
|
|
expected_total = expected_core + 6 + (6 if external else 0)
|
|
assert counts.total == expected_total
|
|
assert counts.core == expected_core
|
|
assert counts.envelope == 6
|
|
assert counts.vacuum == (6 if external else 0)
|
|
|
|
path = output_path / f"{mapping}-{external}.smesh"
|
|
stroid.IO.SaveStroidMesh(mesh, str(path), "Python core-mapping smoke")
|
|
loaded = stroid.IO.LoadStroidMesh(str(path))
|
|
assert loaded.config.core_mapping == mapping
|
|
assert element_counts(stroid, loaded).total == expected_total
|
|
|
|
stroid.refinement.UniformRefinement(loaded, 1)
|
|
assert loaded.config.core_mapping == mapping
|
|
assert loaded.refinement_levels == 1
|
|
refined_counts = element_counts(stroid, loaded)
|
|
assert refined_counts.total == expected_total * 8
|
|
assert refined_counts.core == expected_core * 8
|
|
|
|
if mapping == "spherified":
|
|
legacy = "\n".join(
|
|
line for line in path.read_text().splitlines()
|
|
if not line.startswith("core_mapping:")
|
|
)
|
|
legacy_mesh = stroid.IO.ParseStroidMesh(legacy)
|
|
assert legacy_mesh.config.core_mapping == "spherified"
|
|
assert element_counts(stroid, legacy_mesh).total == expected_total
|
|
|
|
summaries.append({
|
|
"mapping": mapping,
|
|
"external": external,
|
|
"initial_elements": expected_total,
|
|
"refined_elements": refined_counts.total,
|
|
})
|
|
|
|
invalid = stroid.config.MeshConfig(
|
|
core_mapping="unknown", refinement_levels=0
|
|
)
|
|
try:
|
|
stroid.GenerateMesh(invalid)
|
|
except (ValueError, RuntimeError):
|
|
pass
|
|
else:
|
|
raise AssertionError("Unsupported core_mapping was accepted")
|
|
|
|
return summaries
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--module", default="stroid")
|
|
parser.add_argument("--module-dir", type=Path)
|
|
args = parser.parse_args()
|
|
if args.module_dir is not None:
|
|
sys.path.insert(0, str(args.module_dir.resolve()))
|
|
stroid = importlib.import_module(args.module)
|
|
summaries = run(stroid)
|
|
print(json.dumps({"module": stroid.__file__, "cases": summaries}, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|