| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- """P5-M5: unit tests for simulation tool adapters (maxwell / jmag mock).
- Covers: registry (register/get/listed), maxwell mock full pipeline,
- jmag mock full pipeline, tool differentiation (same params -> different
- metrics), set_parameter read-back verification, boundary (empty params /
- unknown tool / run before connect), anomaly (real-mode RuntimeError),
- and executor dynamic adapter import by tool name.
- All source is ASCII only. Run: python scripts/test_adapters.py
- exit 0 = PASS.
- """
- import os
- import sys
- import unittest
- _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- _SRC = os.path.join(_ROOT, "src")
- if _SRC not in sys.path:
- sys.path.insert(0, _SRC)
- from afmcore.adapters import ( # noqa: E402
- SimulationAdapter,
- get_adapter,
- register_adapter,
- registered_tools,
- )
- import afmcore.adapters.maxwell # noqa: E402,F401 (registers "maxwell")
- import afmcore.adapters.jmag # noqa: E402,F401 (registers "jmag")
- class TestAdapterRegistry(unittest.TestCase):
- def test_maxwell_registered(self):
- self.assertIn("maxwell", registered_tools())
- def test_jmag_registered(self):
- self.assertIn("jmag", registered_tools())
- def test_get_adapter_returns_instance(self):
- a = get_adapter("maxwell", model_path="/tmp/x.mot", mock=True)
- self.assertIsInstance(a, SimulationAdapter)
- self.assertEqual(a.tool_name, "maxwell")
- def test_unknown_tool_raises_keyerror(self):
- with self.assertRaises(KeyError):
- get_adapter("nonexistent_tool_xyz")
- def test_register_adapter_rejects_non_string(self):
- with self.assertRaises(ValueError):
- register_adapter("", SimulationAdapter)
- class TestMaxwellMockPipeline(unittest.TestCase):
- def setUp(self):
- self.adapter = get_adapter(
- "maxwell", model_path="/tmp/test.mot",
- output_dir="output/p5m5_test", mock=True,
- )
- def test_connect_disconnect(self):
- self.adapter.connect()
- self.assertTrue(self.adapter._connected)
- self.adapter.disconnect()
- self.assertFalse(self.adapter._connected)
- def test_load_model_clears_params(self):
- self.adapter._params["old"] = 1.0
- self.adapter.load_model("/tmp/new.mot")
- self.assertEqual(self.adapter._loaded_model, "/tmp/new.mot")
- self.assertEqual(self.adapter._params, {})
- def test_set_parameter_readback(self):
- self.adapter.connect()
- self.adapter.set_parameter("airgap_mm", 1.5)
- self.assertEqual(self.adapter._params["airgap_mm"], 1.5)
- def test_run_simulation_before_connect_raises(self):
- with self.assertRaises(RuntimeError):
- self.adapter.run_simulation("electromagnetic")
- def test_run_simulation_unsupported_mode_raises(self):
- self.adapter.connect()
- with self.assertRaises(ValueError):
- self.adapter.run_simulation("structural")
- def test_extract_metrics_deterministic(self):
- self.adapter.connect()
- self.adapter.set_parameter("airgap_mm", 1.0)
- self.adapter.set_parameter("magnet_thickness_mm", 5.0)
- ext = self.adapter.extract_metrics("output/p5m5_test", "t1")
- self.assertEqual(ext["status"], "OK")
- # tavg = 0.50*1.0 + 0.30*5.0 + 0.10 = 2.10
- self.assertAlmostEqual(ext["metrics"]["tavg_nm"], 2.10, places=4)
- self.assertAlmostEqual(ext["metrics"]["efficiency_pct"], 85.10, places=2)
- self.assertIn("winding_temp_c", ext["metrics"]) # thermal domain
- def test_run_point_full_pipeline(self):
- result = self.adapter.run_point(
- model_path="/tmp/test.mot",
- params={"airgap_mm": 1.0, "magnet_thickness_mm": 5.0},
- output_dir="output/p5m5_test",
- tag="full",
- )
- self.assertEqual(result["status"], "OK")
- self.assertIn("tavg_nm", result["metrics"])
- self.assertEqual(result["params"]["airgap_mm"], 1.0)
- def test_run_point_empty_params(self):
- result = self.adapter.run_point(
- model_path="/tmp/test.mot", params={},
- output_dir="output/p5m5_test", tag="empty",
- )
- self.assertEqual(result["status"], "OK")
- # all params default to 0 -> tavg = 0.10
- self.assertAlmostEqual(result["metrics"]["tavg_nm"], 0.10, places=4)
- def test_real_mode_raises(self):
- a = get_adapter("maxwell", model_path="/tmp/x.mot", mock=False)
- with self.assertRaises(RuntimeError):
- a.connect()
- class TestJMAGMockPipeline(unittest.TestCase):
- def setUp(self):
- self.adapter = get_adapter(
- "jmag", model_path="/tmp/test.jmag",
- output_dir="output/p5m5_test", mock=True,
- )
- def test_tool_label_and_domains(self):
- self.assertEqual(self.adapter.tool_label, "JMAG Designer (mock)")
- self.assertEqual(self.adapter.capability_domains, ("electromagnetic",))
- def test_extract_metrics_deterministic(self):
- self.adapter.connect()
- self.adapter.set_parameter("airgap_mm", 1.0)
- self.adapter.set_parameter("magnet_thickness_mm", 5.0)
- ext = self.adapter.extract_metrics("output/p5m5_test", "t1")
- # tavg = 0.45*1.0 + 0.32*5.0 + 0.12 = 2.17
- self.assertAlmostEqual(ext["metrics"]["tavg_nm"], 2.17, places=4)
- self.assertNotIn("winding_temp_c", ext["metrics"]) # jmag has no thermal domain
- def test_run_point_full_pipeline(self):
- result = self.adapter.run_point(
- model_path="/tmp/test.jmag",
- params={"airgap_mm": 2.0, "magnet_thickness_mm": 4.0},
- output_dir="output/p5m5_test", tag="full",
- )
- self.assertEqual(result["status"], "OK")
- # tavg = 0.45*2.0 + 0.32*4.0 + 0.12 = 0.90+1.28+0.12 = 2.30
- self.assertAlmostEqual(result["metrics"]["tavg_nm"], 2.30, places=4)
- def test_unsupported_mode_raises(self):
- self.adapter.connect()
- with self.assertRaises(ValueError):
- self.adapter.run_simulation("thermal")
- class TestToolDifferentiation(unittest.TestCase):
- """Same input params must produce different metrics for maxwell vs jmag."""
- def test_same_params_different_tavg(self):
- params = {"airgap_mm": 1.0, "magnet_thickness_mm": 5.0}
- mx = get_adapter("maxwell", mock=True)
- jm = get_adapter("jmag", mock=True)
- r_mx = mx.run_point("/tmp/x.mot", params, "output/p5m5_test", "diff")
- r_jm = jm.run_point("/tmp/x.jmag", params, "output/p5m5_test", "diff")
- self.assertNotEqual(r_mx["metrics"]["tavg_nm"], r_jm["metrics"]["tavg_nm"])
- self.assertNotEqual(r_mx["metrics"]["efficiency_pct"], r_jm["metrics"]["efficiency_pct"])
- class TestExecutorDynamicImport(unittest.TestCase):
- """MotorCADTaskExecutor must dynamically import the adapter matching tool."""
- def test_executor_tool_maxwell_creates_maxwell_adapter(self):
- sys.path.insert(0, _ROOT)
- from scripts.task_executor import MotorCADTaskExecutor
- ex = MotorCADTaskExecutor(
- web_base_url="http://127.0.0.1:9",
- model_path="/tmp/test.mot",
- tool="maxwell",
- enable_mock=False,
- )
- adapter = ex._ensure_adapter()
- self.assertEqual(adapter.tool_name, "maxwell")
- self.assertIn("maxwell", registered_tools())
- def test_executor_tool_jmag_creates_jmag_adapter(self):
- sys.path.insert(0, _ROOT)
- from scripts.task_executor import MotorCADTaskExecutor
- ex = MotorCADTaskExecutor(
- web_base_url="http://127.0.0.1:9",
- model_path="/tmp/test.jmag",
- tool="jmag",
- enable_mock=False,
- )
- adapter = ex._ensure_adapter()
- self.assertEqual(adapter.tool_name, "jmag")
- self.assertIn("jmag", registered_tools())
- def test_executor_tool_unknown_raises_keyerror(self):
- sys.path.insert(0, _ROOT)
- from scripts.task_executor import MotorCADTaskExecutor
- ex = MotorCADTaskExecutor(
- web_base_url="http://127.0.0.1:9",
- model_path="/tmp/test.mot",
- tool="nonexistent_tool_xyz",
- enable_mock=False,
- )
- with self.assertRaises(KeyError):
- ex._ensure_adapter()
- if __name__ == "__main__":
- unittest.main(verbosity=2)
|