Download FEMM/test_dense_waveforms.py from Lislaam/motor-renders: direct link, hf CLI and curl.
- Browser
- Download file 8.2 kB
-
https://huggingface.co/Lislaam/motor-renders/resolve/main/FEMM/test_dense_waveforms.py
- Command line
-
hf download hf://Lislaam/motor-renders/FEMM/test_dense_waveforms.py
-
curl -L -o test_dense_waveforms.py https://huggingface.co/Lislaam/motor-renders/resolve/main/FEMM/test_dense_waveforms.py
8.2 kB
| """Independent mathematical and geometry guards for the overnight study.""" | |
| import numpy as np | |
| import pytest | |
| from ans_delta import decoded_chunks, encode | |
| from dense_waveforms import completed, same_solver_inputs, spectral_derivative | |
| from pipeline.femm_utils.build_model import simplify_collinear_segments | |
| def test_linkage_derivative_matches_analytic_periodic_signal(count): | |
| period = 2 * np.pi / 7 | |
| angle = (np.arange(count) + 0.23) * period / count | |
| values = np.column_stack( | |
| [np.sin(7 * angle), np.cos(21 * angle), 0.1 * np.sin(84 * angle)] | |
| ) | |
| expected = np.column_stack( | |
| [7 * np.cos(7 * angle), -21 * np.sin(21 * angle), 8.4 * np.cos(84 * angle)] | |
| ) | |
| np.testing.assert_allclose( | |
| spectral_derivative(values, period), expected, atol=2e-10, rtol=1e-10 | |
| ) | |
| def test_compact_femm_keeps_exact_straight_geometry_and_junctions(junction): | |
| nodes = np.array( | |
| [[0.0, 0.0], [0.5, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.5, 0.5]] | |
| ) | |
| segments = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] + ([(1, 5)] if junction else []) | |
| source = { | |
| "nodes_mm": nodes, | |
| "segments": segments, | |
| "used_nodes": list(range(len(nodes))), | |
| "boundary_edges": {"bore": [], "outer": []}, | |
| } | |
| result = simplify_collinear_segments(source) | |
| if junction: | |
| assert (0, 1) in result["segments"] and (1, 2) in result["segments"] | |
| else: | |
| assert (0, 2) in result["segments"] and result[ | |
| "collinear_vertices_removed" | |
| ] == 1 | |
| length = lambda edges: sum(np.linalg.norm(nodes[a] - nodes[b]) for a, b in edges) | |
| assert length(result["segments"]) == pytest.approx(length(segments), abs=1e-14) | |
| assert source["segments"] == segments | |
| def test_prescribed_boundary_vertices_are_never_removed(): | |
| source = { | |
| "nodes_mm": np.array([[0.0, 0.0], [0.5, 0.0], [1.0, 0.0]]), | |
| "segments": [(0, 1), (1, 2)], | |
| "used_nodes": [0, 1, 2], | |
| "boundary_edges": {"bore": [(0, 1), (1, 2)], "outer": []}, | |
| } | |
| assert simplify_collinear_segments(source)["segments"] == source["segments"] | |
| def test_answer_archive_reconstructs_exact_bytes(before, after): | |
| from io import BytesIO | |
| delta = BytesIO() | |
| encode(BytesIO(before), BytesIO(after), delta) | |
| delta.seek(0) | |
| assert b"".join(decoded_chunks(BytesIO(before), delta)) == after | |
| def test_femm_intermediate_readback_is_not_a_completed_dense_sample(tmp_path): | |
| import json | |
| directory = tmp_path / "tasks/zero-0000" | |
| directory.mkdir(parents=True) | |
| (directory / "result.json").write_text( | |
| json.dumps( | |
| { | |
| "status": "accepted", | |
| "execution_sha256": "execution", | |
| "request_sha256": "request", | |
| "outer_rotor_torque_nm": 1.0, | |
| } | |
| ) | |
| ) | |
| assert ( | |
| completed( | |
| tmp_path, {"id": "zero-0000", "request_sha256": "request"}, "execution" | |
| ) | |
| is None | |
| ) | |
| def test_archiver_waits_for_final_driver_publication(tmp_path): | |
| import json | |
| from ans_delta import convert | |
| state = tmp_path / "femm/tasks/loaded-0000" | |
| state.mkdir(parents=True) | |
| (state / "result.json").write_text(json.dumps({"status": "accepted"})) | |
| (state / "base.ans.gz").write_bytes(b"not yet a finished gzip") | |
| assert not convert(tmp_path, state) | |
| assert (state / "base.ans.gz").exists() | |
| def test_completed_data_renders_all_requested_comparison_charts(tmp_path): | |
| import json | |
| from dense_waveform_report import write_report | |
| from dense_waveforms import task | |
| count = 96 | |
| period = 2 * np.pi / 7 | |
| angles = (np.arange(count) + 0.5) * period / count | |
| patterns = np.array([[1.0, -1.0, 0.0]] * count) | |
| plan = { | |
| "identity": "test-plan", | |
| "max_angles": count, | |
| "target_angles": count, | |
| "angular_levels": [count], | |
| "electrical_period_mechanical_rad": period, | |
| "angles_mechanical_rad": angles.tolist(), | |
| "currents_abc_a": patterns.tolist(), | |
| "derivative_step_rad": period * 1e-4, | |
| "n_theta": 1344, | |
| "harmonics": 630, | |
| "boundary_subdivisions": 64, | |
| "max_area_mm2": 0.0125, | |
| "femm_mesh_size_mm": 0.125, | |
| "femm_precision": 1e-10, | |
| "targets": { | |
| "kt_relative": 0.0025, | |
| "ke_relative": 0.0025, | |
| "torque_extrema_absolute_nm": 0.0001, | |
| "derivative_relative": 0.0025, | |
| }, | |
| } | |
| (tmp_path / "plan.json").write_text(json.dumps(plan)) | |
| for solver in ("jax", "femm"): | |
| directory = tmp_path / solver | |
| directory.mkdir() | |
| (directory / "execution.json").write_text( | |
| json.dumps({"identity": "test-execution"}) | |
| ) | |
| (directory / "status.json").write_text( | |
| json.dumps( | |
| { | |
| "phase": "complete", | |
| "current_grid": count, | |
| "accepted_tasks": 2 * count, | |
| } | |
| ) | |
| ) | |
| for index, angle in enumerate(angles): | |
| for mode in ("zero", "loaded"): | |
| request = task(plan, mode, index) | |
| target = directory / "tasks" / request["id"] | |
| target.mkdir(parents=True) | |
| data = { | |
| **request, | |
| "execution_sha256": "test-execution", | |
| "status": "accepted", | |
| "torque_nm": float( | |
| 0.002 * np.sin(84 * angle) - (0.015 if mode == "loaded" else 0) | |
| ), | |
| "phase_linkage_wb_turn": [ | |
| float(0.001 * np.cos(7 * angle + phase)) | |
| for phase in (0, 2 * np.pi / 3, 4 * np.pi / 3) | |
| ], | |
| "saved_artifacts": {}, | |
| } | |
| (target / "result.json").write_text(json.dumps(data)) | |
| summary = write_report(tmp_path) | |
| page = (tmp_path / "report.html").read_text() | |
| for label in ( | |
| "NEW JAX and FEMM scalar values", | |
| "NEW FEMM and JAX sample overlays", | |
| "NEW back-EMF waveform comparison", | |
| "Loaded torque", | |
| "Zero-current torque", | |
| ): | |
| assert label in page | |
| with np.load(tmp_path / "waveforms.npz") as arrays: | |
| assert arrays["jax/torque"].shape == (2, count) | |
| assert arrays["femm/phase_ke"].shape == (count, 3) | |
| assert summary["complete"] | |
| assert not summary["resolution_checks_passed"] | |
| def test_checkpoint_compatibility_separates_solver_physics_from_sampling_policy(): | |
| from copy import deepcopy | |
| original = { | |
| "reference_sha256": "reference", | |
| "numerical_source_sha256": "source", | |
| "n_theta": 1344, | |
| "boundary_subdivisions": 64, | |
| "harmonics": 630, | |
| "max_area_mm2": 0.0125, | |
| "controls": {"tolerance": 1e-9}, | |
| "angles_mechanical_rad": [0.1, 0.2], | |
| "currents_abc_a": [[1, -1, 0], [1, 0, -1]], | |
| "derivative_step_rad": 1e-4, | |
| "femm_mesh_size_mm": 0.125, | |
| "femm_precision": 1e-10, | |
| "target_angles": 384, | |
| } | |
| changed = deepcopy(original) | |
| changed["target_angles"] = 768 | |
| assert same_solver_inputs(original, changed, "jax") and same_solver_inputs( | |
| original, changed, "femm" | |
| ) | |
| changed["femm_mesh_size_mm"] = 0.0625 | |
| assert same_solver_inputs(original, changed, "jax") and not same_solver_inputs( | |
| original, changed, "femm" | |
| ) | |
| changed = deepcopy(original) | |
| changed["max_area_mm2"] /= 2 | |
| assert not same_solver_inputs(original, changed, "jax") and same_solver_inputs( | |
| original, changed, "femm" | |
| ) | |
| changed = deepcopy(original) | |
| changed["numerical_source_sha256"] = "different physics" | |
| assert not same_solver_inputs(original, changed, "jax") and not same_solver_inputs( | |
| original, changed, "femm" | |
| ) | |