libpower/transform/ipark.rs
1//! # Inverse Park Transformation (dq0 → αβ0)
2//!
3//! This module implements the inverse Park transformation, which converts rotating
4//! reference frame quantities (dq coordinates) back into two-phase orthogonal
5//! stationary reference frame quantities (αβ coordinates). This transformation
6//! is essential for converting control signals from the rotating frame back to
7//! the stationary frame for implementation in three-phase systems.
8//!
9//! ## Theory
10//!
11//! The inverse Park transformation converts rotating dq components back into stationary
12//! αβ components. This is the mathematical inverse of the Park transformation and is
13//! crucial for implementing field-oriented control where control is performed in the
14//! dq frame but actuation must be in the abc frame.
15//!
16//! ### Mathematical Definition
17//!
18//! **Inverse Park Transform (dq0 → αβ0):**
19//! ```text
20//! [α] [cos(θ) -sin(θ) 0] [d]
21//! [β] = [sin(θ) cos(θ) 0] [q]
22//! [0] [0 0 1] [0]
23//! ```
24//!
25//! ### Expanded Form
26//!
27//! ```text
28//! α = d×cos(θ) - q×sin(θ) // Alpha component in stationary frame
29//! β = d×sin(θ) + q×cos(θ) // Beta component in stationary frame
30//! 0 = 0 // Zero-sequence unchanged
31//! ```
32//!
33//! ## Physical Interpretation
34//!
35//! - **d component**: DC magnitude aligned with rotating reference
36//! - **q component**: DC magnitude 90° ahead of rotating reference
37//! - **θ (theta)**: Rotation angle of the reference frame
38//! - **α, β outputs**: Sinusoidal quantities in stationary frame
39//!
40//! ## Control Flow in Field-Oriented Control
41//!
42//! 1. **Measurement**: abc → Clarke → αβ → Park → dq (DC quantities)
43//! 2. **Control**: PI controllers operate on DC dq quantities
44//! 3. **Actuation**: dq → Inverse Park → αβ → Inverse Clarke → abc
45//!
46//! ## Applications
47//!
48//! - **Vector control of AC motors**: Converting dq voltage commands to αβ
49//! - **Grid-tied inverters**: Converting dq current references to αβ
50//! - **Active power filters**: Converting rotating frame commands to stationary
51//! - **Wind turbine control**: Grid synchronization and power control
52//! - **Battery energy storage**: Grid interface voltage/current generation
53//! - **Motor drive PWM generation**: Creating three-phase modulation signals
54
55/// Inverse Park transformation for converting dq0 coordinates to αβ0 stationary coordinates.
56///
57/// This structure maintains the state for inverse Park transformation calculations,
58/// storing both input (dq0) and output (αβ0) values along with the rotation angle.
59/// The transformation converts rotating reference frame quantities back to stationary
60/// reference frame quantities, enabling physical implementation of control commands.
61///
62/// # Examples
63///
64/// ## Basic Motor Control Application
65///
66/// ```rust
67/// use libpower::transform::ipark::IPark;
68///
69/// // Create inverse Park transformer
70/// let mut ipark = IPark::new(0.0, 0.0);
71///
72/// // Set dq control outputs from PI controllers
73/// ipark.set_inputs(10.0, 5.0, 0.0); // id = 10A, iq = 5A (DC values)
74///
75/// // Set rotation angle from rotor position
76/// let theta = std::f32::consts::PI / 6.0; // 30° electrical
77/// ipark.set_angle(theta.sin(), theta.cos());
78/// ipark.calculate();
79///
80/// // Get αβ voltages for PWM generation
81/// let v_alpha = ipark.get_alpha(); // Stationary frame voltage
82/// let v_beta = ipark.get_beta(); // Stationary frame voltage
83///
84/// println!("v_α: {:.3}, v_β: {:.3}", v_alpha, v_beta);
85/// ```
86///
87/// ## Field-Oriented Control Example
88///
89/// ```rust
90/// use libpower::transform::ipark::IPark;
91///
92/// let mut ipark = IPark::new(0.0, 0.0);
93///
94/// // Motor control scenario: pure torque command (q-axis only)
95/// let id_ref = 0.0; // No flux current (field weakening off)
96/// let iq_ref = 15.0; // 15A torque current command
97///
98/// ipark.set_inputs(id_ref, iq_ref, 0.0);
99///
100/// // Rotor position from encoder/estimator
101/// let rotor_angle = 0.0f32; // Aligned with d-axis
102/// ipark.set_angle(rotor_angle.sin(), rotor_angle.cos());
103/// ipark.calculate();
104///
105/// // Pure q-axis current becomes pure β-axis current when θ = 0°
106/// assert_eq!(ipark.get_alpha(), 0.0); // No α component
107/// assert_eq!(ipark.get_beta(), 15.0); // All current in β
108/// ```
109///
110/// ## Sinusoidal Generation
111///
112/// ```rust
113/// use libpower::transform::ipark::IPark;
114///
115/// let mut ipark = IPark::new(0.0, 0.0);
116///
117/// // Generate sinusoidal output by rotating a constant dq vector
118/// let magnitude = 100.0; // 100V amplitude
119/// let d_component = magnitude; // Constant d-axis
120/// let q_component = 0.0; // No q-axis
121///
122/// ipark.set_inputs(d_component, q_component, 0.0);
123///
124/// // Simulate rotation over one electrical cycle
125/// for step in 0..8 {
126/// let angle = step as f32 * std::f32::consts::PI / 4.0; // 45° steps
127/// ipark.set_angle(angle.sin(), angle.cos());
128/// ipark.calculate();
129///
130/// println!("θ: {:.0}° → α: {:.1}, β: {:.1}",
131/// angle * 180.0 / std::f32::consts::PI,
132/// ipark.get_alpha(), ipark.get_beta());
133/// }
134/// // This generates: α = magnitude×cos(θ), β = magnitude×sin(θ)
135/// ```
136///
137/// ## Motor Control Voltage Generation
138///
139/// ```rust
140/// use libpower::transform::ipark::IPark;
141///
142/// let mut ipark = IPark::new(0.0, 0.0);
143///
144/// // Set desired d-q voltages (typical FOC)
145/// ipark.set_inputs(10.0, 5.0, 0.0); // Vd=10V, Vq=5V
146///
147/// let rotor_angle = 0.0f32; // Aligned with d-axis
148/// ipark.set_angle(rotor_angle.sin(), rotor_angle.cos());
149/// ipark.calculate();
150///
151/// // α-axis should equal d-axis when aligned (θ=0)
152/// assert!((ipark.get_alpha() - 10.0).abs() < 0.01);
153/// // β-axis should equal q-axis when aligned
154/// assert!((ipark.get_beta() - 5.0).abs() < 0.01);
155/// ```
156#[derive(Debug, Clone)]
157pub struct IPark {
158 /// Alpha component output (α) - stationary frame
159 alpha: f32,
160 /// Beta component output (β) - stationary frame
161 beta: f32,
162 /// Zero-sequence component (0) - unchanged
163 zero: f32,
164 /// Sine of rotation angle (sin θ)
165 sin: f32,
166 /// Cosine of rotation angle (cos θ)
167 cos: f32,
168 /// Direct axis component input (d) - rotating frame
169 d: f32,
170 /// Quadrature axis component input (q) - rotating frame
171 q: f32,
172 /// Zero-sequence component input (z) - unchanged
173 z: f32,
174}
175
176impl IPark {
177 /// Creates a new inverse Park transformer with specified αβ initial values.
178 ///
179 /// The transformer is initialized with the given alpha and beta components
180 /// for compatibility with the existing API. The rotation angle (sin/cos) and
181 /// dq inputs must be set before performing calculations.
182 ///
183 /// # Parameters
184 ///
185 /// * `alpha` - Initial alpha component (not used for dq → αβ transformation)
186 /// * `beta` - Initial beta component (not used for dq → αβ transformation)
187 ///
188 /// # Examples
189 ///
190 /// ```rust
191 /// use libpower::transform::ipark::IPark;
192 ///
193 /// // Create transformer (initial αβ values are overwritten by calculation)
194 /// let ipark = IPark::new(0.0, 0.0);
195 /// assert_eq!(ipark.get_d(), 0.0);
196 /// assert_eq!(ipark.get_q(), 0.0);
197 /// ```
198 pub fn new(alpha: f32, beta: f32) -> IPark {
199 IPark {
200 alpha,
201 beta,
202 zero: 0.0,
203 sin: 0.0,
204 cos: 1.0, // Initialize to θ = 0° (cos = 1, sin = 0)
205 d: 0.0,
206 q: 0.0,
207 z: 0.0,
208 }
209 }
210
211 /// Sets the input dq0 components for transformation.
212 ///
213 /// This method sets the rotating frame quantities that will be converted
214 /// back to stationary frame quantities. These are typically the outputs
215 /// from dq-frame controllers.
216 ///
217 /// # Parameters
218 ///
219 /// * `d` - Direct axis component (d) - typically flux-related
220 /// * `q` - Quadrature axis component (q) - typically torque-related
221 /// * `z` - Zero-sequence component (z) - unchanged
222 ///
223 /// # Examples
224 ///
225 /// ```rust
226 /// use libpower::transform::ipark::IPark;
227 ///
228 /// let mut ipark = IPark::new(0.0, 0.0);
229 /// ipark.set_inputs(10.0, 5.0, 0.0); // Set dq control outputs
230 ///
231 /// assert_eq!(ipark.get_d(), 10.0);
232 /// assert_eq!(ipark.get_q(), 5.0);
233 /// assert_eq!(ipark.get_z(), 0.0);
234 /// ```
235 pub fn set_inputs(&mut self, d: f32, q: f32, z: f32) {
236 self.d = d;
237 self.q = q;
238 self.z = z;
239 }
240
241 /// Sets the rotation angle using sin and cos values.
242 ///
243 /// The inverse Park transformation requires the same rotation angle θ used
244 /// in the forward Park transformation. This method accepts pre-calculated
245 /// sine and cosine values for computational efficiency.
246 ///
247 /// # Parameters
248 ///
249 /// * `sin_theta` - Sine of rotation angle (sin θ)
250 /// * `cos_theta` - Cosine of rotation angle (cos θ)
251 ///
252 /// # Design Notes
253 ///
254 /// - In motor control: θ is typically the electrical rotor angle
255 /// - In grid applications: θ comes from PLL tracking grid phase
256 /// - Must be the same angle used in forward Park transformation
257 ///
258 /// # Examples
259 ///
260 /// ```rust
261 /// use libpower::transform::ipark::IPark;
262 ///
263 /// let mut ipark = IPark::new(0.0, 0.0);
264 ///
265 /// // Set angle for θ = 60° (π/3 radians)
266 /// let theta = std::f32::consts::PI / 3.0;
267 /// ipark.set_angle(theta.sin(), theta.cos());
268 ///
269 /// assert!((ipark.get_sin() - 0.866).abs() < 1e-3); // sin(60°) ≈ 0.866
270 /// assert!((ipark.get_cos() - 0.5).abs() < 1e-6); // cos(60°) = 0.5
271 /// ```
272 pub fn set_angle(&mut self, sin_theta: f32, cos_theta: f32) {
273 self.sin = sin_theta;
274 self.cos = cos_theta;
275 }
276
277 /// Gets the alpha component output.
278 ///
279 /// # Returns
280 ///
281 /// The calculated alpha component value in the stationary frame.
282 ///
283 /// # Examples
284 ///
285 /// ```rust
286 /// use libpower::transform::ipark::IPark;
287 ///
288 /// let mut ipark = IPark::new(0.0, 0.0);
289 /// ipark.set_inputs(10.0, 0.0, 0.0); // Pure d-axis
290 /// ipark.set_angle(0.0, 1.0); // θ = 0°
291 /// ipark.calculate();
292 /// assert_eq!(ipark.get_alpha(), 10.0); // d maps to α when θ = 0°
293 /// ```
294 pub fn get_alpha(&self) -> f32 {
295 self.alpha
296 }
297
298 /// Gets the beta component output.
299 ///
300 /// # Returns
301 ///
302 /// The calculated beta component value in the stationary frame.
303 ///
304 /// # Examples
305 ///
306 /// ```rust
307 /// use libpower::transform::ipark::IPark;
308 ///
309 /// let mut ipark = IPark::new(0.0, 0.0);
310 /// ipark.set_inputs(0.0, 10.0, 0.0); // Pure q-axis
311 /// ipark.set_angle(0.0, 1.0); // θ = 0°
312 /// ipark.calculate();
313 /// assert_eq!(ipark.get_beta(), 10.0); // q maps to β when θ = 0°
314 /// ```
315 pub fn get_beta(&self) -> f32 {
316 self.beta
317 }
318
319 /// Gets the zero-sequence component.
320 ///
321 /// # Returns
322 ///
323 /// The zero-sequence component value (currently unused).
324 ///
325 /// # Examples
326 ///
327 /// ```rust
328 /// use libpower::transform::ipark::IPark;
329 ///
330 /// let mut ipark = IPark::new(0.0, 0.0);
331 /// ipark.set_inputs(1.0, 1.0, 0.5);
332 /// assert_eq!(ipark.get_zero(), 0.0); // Currently not implemented
333 /// ```
334 pub fn get_zero(&self) -> f32 {
335 self.zero
336 }
337
338 /// Gets the sine of the rotation angle.
339 ///
340 /// # Returns
341 ///
342 /// The sine component of the rotation angle (sin θ).
343 ///
344 /// # Examples
345 ///
346 /// ```rust
347 /// use libpower::transform::ipark::IPark;
348 ///
349 /// let mut ipark = IPark::new(0.0, 0.0);
350 /// ipark.set_angle(0.707, 0.707); // 45° angle
351 /// assert!((ipark.get_sin() - 0.707).abs() < 1e-3);
352 /// ```
353 pub fn get_sin(&self) -> f32 {
354 self.sin
355 }
356
357 /// Gets the cosine of the rotation angle.
358 ///
359 /// # Returns
360 ///
361 /// The cosine component of the rotation angle (cos θ).
362 ///
363 /// # Examples
364 ///
365 /// ```rust
366 /// use libpower::transform::ipark::IPark;
367 ///
368 /// let mut ipark = IPark::new(0.0, 0.0);
369 /// ipark.set_angle(0.707, 0.707); // 45° angle
370 /// assert!((ipark.get_cos() - 0.707).abs() < 1e-3);
371 /// ```
372 pub fn get_cos(&self) -> f32 {
373 self.cos
374 }
375
376 /// Gets the direct axis (d) component input.
377 ///
378 /// The d-axis component represents the component aligned with the rotating
379 /// reference frame. In motor control, this typically corresponds to the
380 /// flux command from the controller.
381 ///
382 /// # Returns
383 ///
384 /// The current direct axis component input.
385 ///
386 /// # Examples
387 ///
388 /// ```rust
389 /// use libpower::transform::ipark::IPark;
390 ///
391 /// let mut ipark = IPark::new(0.0, 0.0);
392 /// ipark.set_inputs(15.0, 10.0, 0.0);
393 /// assert_eq!(ipark.get_d(), 15.0);
394 /// ```
395 pub fn get_d(&self) -> f32 {
396 self.d
397 }
398
399 /// Gets the quadrature axis (q) component input.
400 ///
401 /// The q-axis component represents the component 90° ahead of the rotating
402 /// reference frame. In motor control, this typically corresponds to the
403 /// torque command from the controller.
404 ///
405 /// # Returns
406 ///
407 /// The current quadrature axis component input.
408 ///
409 /// # Examples
410 ///
411 /// ```rust
412 /// use libpower::transform::ipark::IPark;
413 ///
414 /// let mut ipark = IPark::new(0.0, 0.0);
415 /// ipark.set_inputs(15.0, 10.0, 0.0);
416 /// assert_eq!(ipark.get_q(), 10.0);
417 /// ```
418 pub fn get_q(&self) -> f32 {
419 self.q
420 }
421
422 /// Gets the zero-sequence component input.
423 ///
424 /// # Returns
425 ///
426 /// The zero-sequence component input value.
427 ///
428 /// # Examples
429 ///
430 /// ```rust
431 /// use libpower::transform::ipark::IPark;
432 ///
433 /// let mut ipark = IPark::new(0.0, 0.0);
434 /// ipark.set_inputs(1.0, 1.0, 0.2);
435 /// assert_eq!(ipark.get_z(), 0.2);
436 /// ```
437 pub fn get_z(&self) -> f32 {
438 self.z
439 }
440
441 /// Performs the inverse Park transformation calculation.
442 ///
443 /// This method implements the mathematical inverse Park transformation,
444 /// converting the stored dq0 components into αβ0 stationary reference
445 /// frame quantities using the specified rotation angle.
446 ///
447 /// # Mathematical Implementation
448 ///
449 /// ```text
450 /// α = d×cos(θ) - q×sin(θ)
451 /// β = d×sin(θ) + q×cos(θ)
452 /// 0 = z (zero-sequence unchanged)
453 /// ```
454 ///
455 /// # Examples
456 ///
457 /// ## Aligned Reference Frame (θ = 0°)
458 ///
459 /// ```rust
460 /// use libpower::transform::ipark::IPark;
461 ///
462 /// let mut ipark = IPark::new(0.0, 0.0);
463 /// ipark.set_inputs(10.0, 5.0, 0.0); // d = 10, q = 5
464 /// ipark.set_angle(0.0, 1.0); // θ = 0°: sin = 0, cos = 1
465 /// ipark.calculate();
466 ///
467 /// // When θ = 0°: α = d, β = q
468 /// assert_eq!(ipark.get_alpha(), 10.0); // d maps to α
469 /// assert_eq!(ipark.get_beta(), 5.0); // q maps to β
470 /// ```
471 ///
472 /// ## 90° Rotated Reference Frame
473 ///
474 /// ```rust
475 /// use libpower::transform::ipark::IPark;
476 ///
477 /// let mut ipark = IPark::new(0.0, 0.0);
478 /// ipark.set_inputs(10.0, 0.0, 0.0); // Pure d-axis
479 /// ipark.set_angle(1.0, 0.0); // θ = 90°: sin = 1, cos = 0
480 /// ipark.calculate();
481 ///
482 /// // When θ = 90°: α = -q, β = d
483 /// assert_eq!(ipark.get_alpha(), 0.0); // -q = 0
484 /// assert_eq!(ipark.get_beta(), 10.0); // d = 10
485 /// ```
486 ///
487 /// ## Sinusoidal Generation
488 ///
489 /// ```rust
490 /// use libpower::transform::ipark::IPark;
491 ///
492 /// let mut ipark = IPark::new(0.0, 0.0);
493 ///
494 /// // Generate 100V sine wave by rotating constant d-axis vector
495 /// ipark.set_inputs(100.0, 0.0, 0.0); // 100V d-axis, 0V q-axis
496 ///
497 /// // θ = 0°: expect α = 100, β = 0
498 /// ipark.set_angle(0.0, 1.0);
499 /// ipark.calculate();
500 /// assert_eq!(ipark.get_alpha(), 100.0);
501 /// assert_eq!(ipark.get_beta(), 0.0);
502 ///
503 /// // θ = 90°: expect α = 0, β = 100
504 /// ipark.set_angle(1.0, 0.0);
505 /// ipark.calculate();
506 /// assert_eq!(ipark.get_alpha(), 0.0);
507 /// assert_eq!(ipark.get_beta(), 100.0);
508 /// ```
509 ///
510 /// ## Motor Control Voltage Commands
511 ///
512 /// ```rust
513 /// use libpower::transform::ipark::IPark;
514 ///
515 /// let mut ipark = IPark::new(0.0, 0.0);
516 ///
517 /// // PI controller outputs for field-oriented control
518 /// let vd_command = 5.0; // Flux voltage from Id controller
519 /// let vq_command = 12.0; // Torque voltage from Iq controller
520 ///
521 /// ipark.set_inputs(vd_command, vq_command, 0.0);
522 ///
523 /// // Rotor angle from position sensor/estimator
524 /// let theta_electrical = std::f32::consts::PI / 3.0; // 60°
525 /// ipark.set_angle(theta_electrical.sin(), theta_electrical.cos());
526 /// ipark.calculate();
527 ///
528 /// // These αβ voltages feed into space vector PWM
529 /// let v_alpha_ref = ipark.get_alpha();
530 /// let v_beta_ref = ipark.get_beta();
531 ///
532 /// // Verify transformation preserves magnitude
533 /// let dq_magnitude = (vd_command * vd_command + vq_command * vq_command).sqrt();
534 /// let ab_magnitude = (v_alpha_ref * v_alpha_ref + v_beta_ref * v_beta_ref).sqrt();
535 /// assert!((dq_magnitude - ab_magnitude).abs() < 1e-6);
536 /// ```
537 pub fn calculate(&mut self) {
538 // Inverse Park transformation: dq → αβ
539 self.alpha = self.d * self.cos - self.q * self.sin;
540 self.beta = self.q * self.cos + self.d * self.sin;
541 // Note: zero-sequence component could be passed through as:
542 // self.zero = self.z;
543 }
544}
545
546impl Default for IPark {
547 /// Creates an inverse Park transformer with all values set to zero.
548 ///
549 /// The rotation angle is initialized to θ = 0° (cos = 1.0, sin = 0.0).
550 ///
551 /// # Examples
552 ///
553 /// ```rust
554 /// use libpower::transform::ipark::IPark;
555 ///
556 /// let ipark = IPark::default();
557 /// assert_eq!(ipark.get_d(), 0.0);
558 /// assert_eq!(ipark.get_q(), 0.0);
559 /// assert_eq!(ipark.get_cos(), 1.0); // θ = 0°
560 /// assert_eq!(ipark.get_sin(), 0.0);
561 /// ```
562 fn default() -> Self {
563 Self::new(0.0, 0.0)
564 }
565}