Skip to content

Add the Python dq transform, its tests, and the Ansys/Python boundary rule - #31

Merged
sadda merged 2 commits into
mainfrom
ansys-python-boundary
Sep 22, 2026
Merged

sadda merged 2 commits into
mainfrom
ansys-python-boundary

Conversation

@smidl

@smidl smidl commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Addresses #25 (the additive half — see "Not in this PR" below).

Base is 26-rename-anchors, not main — stacked on #27, which renames motors/motor2.py and drops the compatibility shims. This PR imports motors.synrm_5f_40s, so it cannot land before #27. GitHub retargets the base to main automatically when #27 merges. Review the four files below; #27's rename is not part of this diff.

Why

The same five-phase Park/Clarke transform is written out by hand three times in the 5-phase anchor's output variables — once for flux linkage, once for input current, once for terminal voltage — off one pair of cos/sin tables. None of it needs the field solution; it is algebra on quantities the solve already produced (#25).

What this adds — and it removes nothing

file
machine_design/transforms.py the transform once: to_dq / to_phases / electrical_angle, matching the Ansys convention exactly, including the sin(-h*(...)) sign that makes q the negative sine sum
tests/test_transforms.py pins that convention offline, no Ansys: round-trip for dq1 and dq3, dq1/dq3 orthogonality, the 2/5 scaling, the q sign, and both angle-unit paths
tests/test_ansys_transforms.py live cross-check on one solve, both subspaces excited (marked ansys)
CONTRIBUTING.md "What runs in Ansys, what runs in Python"

339 insertions, 0 deletions. pytest 42 passed / 5 deselected; ruff clean.

Verified against a real solve

On bayes, AEDT 2024.2, 1 core: the Python transform reproduces Ansys's own dq output variables to 2.3e-15 (dq1) and 2.5e-16 (dq3) — floating-point exact, not within a tolerance. All four cross-checks pass.

Two things that verification turned up, both now recorded in the code:

1. Ansys reports theta_el in degrees, not radians. The - pi in its expression is evaluated as −180° in an angular context, so theta_el comes back as e.g. 540 (= 3π rad). The Ansys side is correct and internally consistent — theta_el is only ever used inside cos/sin as angle-minus-angle — but a Python consumer reading the raw number and calling numpy.cos gets silent nonsense. electrical_angle() therefore takes an explicit degrees= flag and always returns radians, with a test asserting the measured value 3π. This is a second instance of the units class of problem from #16: Ansys is self-consistent, but its reported units surprise the consumer.

2. Flux_d1..q3 are computed and then discarded. solution_expressions requests Flux_e_* (excitation flux), not Flux_* — so the flux dq transforms never reach Python at present. Relevant to #25's removal list: they are the transforms we intend to keep in Ansys.

Why the cross-check covers flux and voltage

Deliberate, and they have different lifetimes:

  • flux — permanent. Flux linkage needs the field solution, so it stays in Ansys; this half is the standing guard that keeps the boundary honest.
  • voltage — one-shot. The Ansys-side voltage variables are slated to move to Python, which deletes both sides of the comparison. It can therefore only be run while they still exist. When they go, drop the voltage half and keep the flux half.

That is also why this PR comes before the removal: the equivalence proof is only executable beforehand.

Not in this PR

The removal itself — @saxik13's list in #25 (Rstat, Lew, Vind_*, dI*_dt, V_A..V_E, V_AC.., Vterm_*, V_d1..V_q3: 30 output variables) plus trimming solution_expressions. That is a breaking change to what extract_results returns, so it wants its own PR once this is merged and consumers have migrated — justified by the numbers above.

Per #25 I am keeping I_d1..I_q3. They are redundant as a computation, but they are the only end-to-end check that the excitation Ansys applied is the one we commanded — which is exactly the failure mode #16 turned out to be. Worth reclassifying as an assertion rather than deleting.

@sadda

sadda commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

@smidl Do we want to have N_PHASES = 5 hard-coded? Would it make sense to pass it as an argument and use it also for the three-phase machine?

@sadda

sadda commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

@saxik13 Do I remember correctly that you once told me that you generate output_vars in some script? If so, could you please upload it somewhere, as this may be a duplicate effort?

@smidl
smidl changed the base branch from 26-rename-anchors to main September 8, 2026 17:18
… rule

Addresses the additive half of #25.

The same Park/Clarke transform is written out by hand three times in
`motors/synrm_5f_40s.py`'s output variables -- once for flux linkage, once for input
current, once for terminal voltage -- off one pair of cos/sin tables. None of it needs
the field solution; it is algebra on quantities the solve has already produced.

This adds the Python side and the rule, and removes nothing:

  machine_design/transforms.py   the transform once: to_dq / to_phases /
                                 electrical_angle, matching the Ansys convention
                                 exactly, including the sin(-h*(...)) sign that makes
                                 q the negative sine sum
  tests/test_transforms.py       pins that convention offline (no Ansys)
  tests/test_ansys_transforms.py live cross-check on one solve, both subspaces excited
  CONTRIBUTING.md                "What runs in Ansys, what runs in Python"

Phase count is not hard-coded. `to_dq` reads it off the last axis of its input, so the
same function serves the 3-phase anchor and the 5-phase one; `to_phases` takes it
explicitly, having no input array to read it from. The offline tests are parametrised
over (phase count, harmonic), and the m=3/h=3 triplen case -- zero-sequence, so not
invertible -- is pinned as such so nobody "fixes" it.

Verified on a live AEDT 2024.2 solve (bayes, 1 core): the Python transform reproduces
Ansys's own dq output variables to 2.3e-15 (dq1) and 2.5e-16 (dq3) -- floating-point
exact, not within a tolerance.

Two things that verification turned up, both now recorded:

* Ansys returns `theta_el` in DEGREES, not radians -- the `- pi` in its expression is
  evaluated as -180 in an angular context. The Ansys side is correct and internally
  consistent (theta_el is only ever used inside cos/sin as angle-minus-angle), but a
  Python consumer reading the raw number gets silent nonsense. `electrical_angle()`
  therefore takes an explicit `degrees=` flag and always returns radians.
* `Flux_d1..q3` are defined as output variables but never extracted --
  `solution_expressions` requests `Flux_e_*` (excitation flux) instead. So the flux
  transforms are computed by Ansys and discarded.

The cross-check covers flux AND voltage deliberately. Flux stays in Ansys (it needs the
solve), so that half is a permanent guard on the boundary. The voltage half is a
one-shot equivalence proof: the Ansys-side voltage variables are slated to move to
Python, which deletes both sides of the comparison, so it can only be run while they
still exist.

No behaviour change: three new files plus a CONTRIBUTING section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@smidl
smidl force-pushed the ansys-python-boundary branch from 7a0474e to f42372e Compare September 8, 2026 17:32
@smidl

smidl commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Do we want to have N_PHASES = 5 hard-coded? Would it make sense to pass it as an argument and use it also for the three-phase machine?

Agreed — done, and the phase count turned out not to need to be an argument in the main case: to_dq reads it off the last axis of the input it is already given.

d1, q1 = to_dq(phase_values, theta_el, harmonic=1)   # 3-phase or 5-phase, same call

to_phases (the inverse) does take it explicitly, since it has to synthesise that many values and has no input array to infer from. The constant is gone.

The offline tests are now parametrised over (phase count, harmonic) — (3,1), (5,1), (5,3) — covering the 3-phase anchor with the same transform. The 2/m scaling and the q-sign convention are each checked at both phase counts. 23 offline tests, up from 16.

One case is deliberately excluded and pinned rather than supported: m=3, h=3. 3*(2*pi*k/3) is a whole number of turns, so every phase sees the same angle — the triplen harmonic is zero-sequence and the transform is not invertible there. test_triplen_harmonic_is_zero_sequence_in_three_phase asserts the round-trip fails, so nobody later "fixes" the transform to make it succeed. That is physics, not a defect.

Rebased onto current main (over #30/#32/#33) and retargeted from 26-rename-anchors to main now that #27 has merged — GitHub had not retargeted automatically because the base branch still exists on origin. pytest 49 passed / 5 deselected; ruff clean.

On the other question: I have not seen @saxik13's output_vars generator script either. Worth waiting for it before anyone writes the removal PR — if it already emits these transforms for arbitrary phase/harmonic counts, then the Python side here and that script should agree on one convention rather than two.

separate files applicable to different number of phases, excitation (current vs. voltage), rotation direction, names of phases, ...
@saxik13

saxik13 commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

I have added the Ansys_transforms folder, which I use to create the output variables. It is in the original state, so it is independent of the Ansys environment, and it generates the .aoutvar file, which can be opened in Ansys. It is created as a general tool applicable to different existing Ansys models. So you can define the number of phases and harmonics, but also the excitation type (current vs. voltage), naming of the phases, rotation direction, inductance matrix (full or diagonal), or winding configuration, which defines the initial angle.

When working on the new machine design, many of the features are not necessary. The initial angular position will be derived from the winding design directly - I am working on it now.

@sadda
sadda merged commit 0dcf29a into main Sep 22, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants