| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- """Topology-aware Motor-CAD variable name mapping.
- Single source of truth for mapping logical parameter names to actual
- Motor-CAD variable names, per motor topology (AFM-SSSR, AFM-AFIR, RFM).
- Also provides cross-topology alias mapping: when a plan uses RFM-style
- variable names (e.g. Stator_Lam_Outer_Dia) on an AFM topology, this
- module maps them to the correct AFM variable name (Stator_Outer_Diameter).
- All variable names here are verified against the MARS-12S10P_SSSR .mot
- model and by pymotorcad get/set_variable probes (2026-09-03, TEST-037).
- """
- from typing import Dict, Optional, Set, Tuple, List
- # ---------------------------------------------------------------------------
- # Topology identifiers
- # ---------------------------------------------------------------------------
- TOPOLOGY_SSSR = "SSSR" # Single Stator Single Rotor axial flux
- TOPOLOGY_AFIR = "AFIR" # Axial Flux Integrated Rotor
- TOPOLOGY_RFM = "RFM" # Radial Flux Motor (legacy)
- ALL_TOPOLOGIES = (TOPOLOGY_SSSR, TOPOLOGY_AFIR, TOPOLOGY_RFM)
- # ---------------------------------------------------------------------------
- # Known Motor-CAD variable names per topology.
- # Verified against MARS-12S10P_SSSR_D76-C150_V5.0-0819.mot and the
- # successful plan 21 run (2026-08-28).
- # ---------------------------------------------------------------------------
- _KNOWN_VARIABLES: Dict[str, Set[str]] = {
- TOPOLOGY_SSSR: {
- # --- Geometry ---
- "RotorOuterDiameter",
- "Stator_Lam_Dia",
- "Stator_Bore",
- "Back_Iron_Thickness",
- "Airgap",
- # --- Stator slots ---
- "Slot_Depth",
- "Slot_Width",
- "Tooth_Width",
- "Slot_Number",
- "Slot_Fill",
- "Slot_Type",
- # --- Rotor / magnets ---
- "Pole_Number",
- "Magnet_Length",
- "Magnet_Thickness",
- "Magnet_Arc_[ED]",
- "Material_Magnet",
- "Magnet_Br_at_RefTemp",
- "Magnet_Br_at_20",
- "InitialMagnetTemperature",
- # --- Materials ---
- # --- Electrical ---
- "DCBusVoltage",
- "RMSCurrent",
- "Shaft_Speed",
- "WindageGraph_MaxSpeed",
- "CurrentDefinition",
- "PhaseAdvance",
- # --- Winding ---
- "ConductorsPerSlot",
- "ParallelPaths",
- "Wire_Diameter",
- "WindingConnection",
- "Copper_Diameter",
- # --- Thermal ---
- "Ambient_Temperature",
- "Cooling_Type",
- # --- Simulation control ---
- "TorquePointsPerCycle",
- "AirgapMeshPoints_mesh",
- "AirgapMeshPoints_layers",
- "MessageDisplayState",
- # --- Axial force calculation (optional) ---
- "ElectromagneticForcesCalc_Load",
- "ElectromagneticForcesCalc_OC",
- },
- # AFIR shares most SSSR variables; AFIR-specific ones added below.
- TOPOLOGY_AFIR: set(),
- # RFM (radial flux) variable names -- legacy, kept for alias resolution.
- TOPOLOGY_RFM: {
- "Stator_Lam_Outer_Dia",
- "Stator_Lam_Inner_Dia",
- "Stator_Yoke_Width",
- "Rotor_Lam_Outer_Dia",
- "Rotor_Lam_Inner_Dia",
- "Airgap",
- "Slot_Depth",
- "Slot_Width",
- "Tooth_Width",
- "Slot_Number",
- "Pole_Number",
- "Magnet_Thickness",
- "Magnet_Width",
- "DCBusVoltage",
- "RMSCurrent",
- "Shaft_Speed",
- "ConductorsPerSlot",
- "ParallelPaths",
- "Wire_Diameter",
- "WindingConnection",
- "Material_Magnet",
- "Material_Stator_Lam_Yoke",
- "InitialMagnetTemperature",
- "Ambient_Temperature",
- "Cooling_Type",
- "TorquePointsPerCycle",
- "MessageDisplayState",
- "CurrentDefinition",
- "Current_Advance_Angle",
- "Slot_Fill",
- "Magnet_Br_at_RefTemp",
- "WindageGraph_MaxSpeed",
- },
- }
- # AFIR inherits all SSSR known variables.
- _KNOWN_VARIABLES[TOPOLOGY_AFIR] = set(_KNOWN_VARIABLES[TOPOLOGY_SSSR])
- # ---------------------------------------------------------------------------
- # Cross-topology alias mapping.
- # Key: alias name (logical template name OR RFM variable name).
- # Value: dict of topology -> actual Motor-CAD variable name.
- #
- # This serves two purposes:
- # 1. Maps template logical names (e.g. "Number_of_Slots") to Motor-CAD
- # actual names (e.g. "Slot_Number").
- # 2. Maps RFM variable names (e.g. "Stator_Lam_Outer_Dia") to AFM names
- # (e.g. "Stator_Outer_Diameter") when used on an AFM topology.
- # ---------------------------------------------------------------------------
- _ALIAS_MAP: Dict[str, Dict[str, str]] = {
- # --- RFM -> AFM geometry aliases (the root cause of plan 23 failure) ---
- "Stator_Lam_Outer_Dia": {
- TOPOLOGY_SSSR: "Stator_Lam_Dia",
- TOPOLOGY_AFIR: "Stator_Lam_Dia",
- TOPOLOGY_RFM: "Stator_Lam_Outer_Dia",
- },
- "Stator_Lam_Inner_Dia": {
- TOPOLOGY_SSSR: "Stator_Bore",
- TOPOLOGY_AFIR: "Stator_Bore",
- TOPOLOGY_RFM: "Stator_Lam_Inner_Dia",
- },
- "Stator_Yoke_Width": {
- TOPOLOGY_SSSR: "Stator_Yoke_Thickness",
- TOPOLOGY_AFIR: "Stator_Yoke_Thickness",
- TOPOLOGY_RFM: "Stator_Yoke_Width",
- },
- "Rotor_Lam_Outer_Dia": {
- TOPOLOGY_SSSR: "RotorOuterDiameter",
- TOPOLOGY_AFIR: "RotorOuterDiameter",
- TOPOLOGY_RFM: "Rotor_Lam_Outer_Dia",
- },
- "Rotor_Lam_Inner_Dia": {
- TOPOLOGY_SSSR: "Inner_Rotor_Diameter",
- TOPOLOGY_AFIR: "Inner_Rotor_Diameter",
- TOPOLOGY_RFM: "Rotor_Lam_Inner_Dia",
- },
- # --- Template logical name -> Motor-CAD actual name ---
- "Number_of_Slots": {
- TOPOLOGY_SSSR: "Slot_Number",
- TOPOLOGY_AFIR: "Slot_Number",
- TOPOLOGY_RFM: "Slot_Number",
- },
- "Number_of_Poles": {
- TOPOLOGY_SSSR: "Pole_Number",
- TOPOLOGY_AFIR: "Pole_Number",
- TOPOLOGY_RFM: "Pole_Number",
- },
- "DC_Link_Voltage": {
- TOPOLOGY_SSSR: "DCBusVoltage",
- TOPOLOGY_AFIR: "DCBusVoltage",
- TOPOLOGY_RFM: "DCBusVoltage",
- },
- "Turns_per_Coil": {
- TOPOLOGY_SSSR: "ConductorsPerSlot",
- TOPOLOGY_AFIR: "ConductorsPerSlot",
- TOPOLOGY_RFM: "ConductorsPerSlot",
- },
- "Parallel_Paths": {
- TOPOLOGY_SSSR: "ParallelPaths",
- TOPOLOGY_AFIR: "ParallelPaths",
- TOPOLOGY_RFM: "ParallelPaths",
- },
- "Copper_Fill_Factor": {
- TOPOLOGY_SSSR: "Slot_Fill",
- TOPOLOGY_AFIR: "Slot_Fill",
- TOPOLOGY_RFM: "Slot_Fill",
- },
- "Magnet_Material": {
- TOPOLOGY_SSSR: "Material_Magnet",
- TOPOLOGY_AFIR: "Material_Magnet",
- TOPOLOGY_RFM: "Material_Magnet",
- },
- "Steel_Grade": {
- TOPOLOGY_SSSR: "Material_Stator_Lam_Yoke",
- TOPOLOGY_AFIR: "Material_Stator_Lam_Yoke",
- TOPOLOGY_RFM: "Material_Stator_Lam_Yoke",
- },
- "Magnet_Temperature": {
- TOPOLOGY_SSSR: "InitialMagnetTemperature",
- TOPOLOGY_AFIR: "InitialMagnetTemperature",
- TOPOLOGY_RFM: "InitialMagnetTemperature",
- },
- "Magnet_Remanence": {
- TOPOLOGY_SSSR: "Magnet_Br_at_RefTemp",
- TOPOLOGY_AFIR: "Magnet_Br_at_RefTemp",
- TOPOLOGY_RFM: "Magnet_Br_at_RefTemp",
- },
- "Cooling_Method": {
- TOPOLOGY_SSSR: "Cooling_Type",
- TOPOLOGY_AFIR: "Cooling_Type",
- TOPOLOGY_RFM: "Cooling_Type",
- },
- "Max_Speed": {
- TOPOLOGY_SSSR: "WindageGraph_MaxSpeed",
- TOPOLOGY_AFIR: "WindageGraph_MaxSpeed",
- TOPOLOGY_RFM: "WindageGraph_MaxSpeed",
- },
- "Winding_Connection": {
- TOPOLOGY_SSSR: "WindingConnection",
- TOPOLOGY_AFIR: "WindingConnection",
- TOPOLOGY_RFM: "WindingConnection",
- },
- }
- # ---------------------------------------------------------------------------
- # Public API
- # ---------------------------------------------------------------------------
- def normalize_topology(topology: Optional[str]) -> str:
- """Normalize topology string to canonical form.
- Defaults to SSSR for None/empty/unknown values.
- Args:
- topology: Raw topology string from plan/project.
- Returns:
- Canonical topology identifier (SSSR / AFIR / RFM).
- """
- if not topology:
- return TOPOLOGY_SSSR
- t = topology.strip().upper()
- if t in ALL_TOPOLOGIES:
- return t
- # Common informal aliases
- if t in ("AFM", "AXIAL", "AXIAL_FLUX", "AXIALFLUX"):
- return TOPOLOGY_SSSR
- if t in ("RADIAL", "RADIAL_FLUX", "RADIALFLUX"):
- return TOPOLOGY_RFM
- return TOPOLOGY_SSSR
- def resolve_variable(
- name: str, topology: Optional[str] = None
- ) -> Tuple[str, bool]:
- """Resolve a logical/alias variable name to the actual Motor-CAD name.
- Checks the alias map first (covers both template logical names and
- RFM-to-AFM remapping). If not found, returns the name stripped.
- Args:
- name: Variable name as provided (logical, alias, or direct).
- topology: Motor topology. Defaults to SSSR.
- Returns:
- Tuple of (resolved_name, was_alias).
- was_alias=True means the input was remapped to a topology-specific
- name. was_alias=False means the input was used as-is (caller should
- verify with is_known_variable()).
- """
- topo = normalize_topology(topology)
- name_stripped = name.strip()
- if name_stripped in _ALIAS_MAP:
- mapped = _ALIAS_MAP[name_stripped].get(topo, name_stripped)
- return (mapped, mapped != name_stripped)
- return (name_stripped, False)
- def is_known_variable(name: str, topology: Optional[str] = None) -> bool:
- """Check if a (resolved) variable name is known for the given topology.
- Args:
- name: Actual Motor-CAD variable name (after resolve_variable).
- topology: Motor topology.
- Returns:
- True if the variable is in the verified known-variable set.
- """
- topo = normalize_topology(topology)
- return name.strip() in _KNOWN_VARIABLES.get(topo, set())
- def validate_parameters(
- params: Dict[str, float], topology: Optional[str] = None
- ) -> Dict[str, List]:
- """Validate a parameter dict against the topology's known variables.
- Resolves aliases, then classifies each parameter as valid or unknown.
- Args:
- params: Dict of variable_name -> value (as produced by plan expansion).
- topology: Motor topology.
- Returns:
- Dict with keys:
- - 'valid': list of (resolved_name, value) known to exist
- - 'unknown': list of (resolved_name, value) NOT in known set
- - 'aliases_resolved': list of (original_name, resolved_name) remapped
- - 'resolved_params': dict of resolved_name -> value (all parameters)
- """
- topo = normalize_topology(topology)
- result: Dict[str, List] = {
- "valid": [],
- "unknown": [],
- "aliases_resolved": [],
- "resolved_params": {},
- }
- for name, value in params.items():
- resolved, was_alias = resolve_variable(name, topo)
- if was_alias:
- result["aliases_resolved"].append((name, resolved))
- result["resolved_params"][resolved] = value
- if is_known_variable(resolved, topo):
- result["valid"].append((resolved, value))
- else:
- result["unknown"].append((resolved, value))
- return result
- def get_known_variables(topology: Optional[str] = None) -> Set[str]:
- """Return the verified set of Motor-CAD variable names for a topology."""
- topo = normalize_topology(topology)
- return set(_KNOWN_VARIABLES.get(topo, set()))
- def suggest_alternative(
- name: str, topology: Optional[str] = None
- ) -> Optional[str]:
- """Suggest a known alternative variable name for an unknown one.
- Uses character-overlap scoring to find the closest known variable.
- Returns None if no sufficiently close match is found.
- Args:
- name: Unknown variable name.
- topology: Motor topology.
- Returns:
- Best matching known variable name, or None.
- """
- topo = normalize_topology(topology)
- known = _KNOWN_VARIABLES.get(topo, set())
- name_lower = name.lower().replace("_", "").replace("-", "")
- best = None
- best_score = 0.0
- for candidate in known:
- cand_lower = candidate.lower().replace("_", "").replace("-", "")
- common = sum(1 for c in name_lower if c in cand_lower)
- score = common / max(len(name_lower), len(cand_lower))
- if score > best_score and score > 0.5:
- best_score = score
- best = candidate
- return best
|