main.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // This code shows how to perfom a magnetostatic analysis on a 2D cross-section of a
  2. // rotating PMSM (permanent magnet synchronous) electric motor. The motor torque is
  3. // calculated based on the virtual work principle for an increasing mechanical angle.
  4. //
  5. // Antiperiodicity is used to reduce the computational domain to only 45 degrees of
  6. // the total geometry (the motor has 4 pairs of poles). In order to link the rotor and
  7. // stator domains at their interface a general mortar-based continuity condition is used.
  8. // This allows to work with the non-matching mesh at the interface when the rotor moves.
  9. //
  10. // P-adaptivity is used.
  11. #include "sparselizardbase.h"
  12. using namespace mathop;
  13. // Input is rotor angular position in degrees. Output is torque in Nm.
  14. double sparselizard(double alpha)
  15. {
  16. // Give names to the physical region numbers :
  17. int rotmagmat = 1, magnet = 2, magnetgap = 3, gaprot = 4, gapstat = 5, statmagmat = 6, windslot = 7, winda = 8, windb = 9, windc = 10;
  18. int gammarot = 11, gammastat = 12, gamma1rot = 13, gamma2rot = 14, gamma1stat = 15, gamma2stat = 16, inarc = 17, outarc = 18;
  19. // Load the rotor and stator mesh without merging the interface. Set verbosity to 0.
  20. mesh mymesh(false, {"rotor.msh", "stator.msh"}, 0);
  21. // Define new physical regions for convenience:
  22. int rotor = regionunion({rotmagmat, magnet, magnetgap, gaprot});
  23. int windings = regionunion({winda, windb, windc});
  24. int stator = regionunion({gapstat, windslot, statmagmat, windings});
  25. int nonmag = regionunion({magnet, magnetgap, gaprot, gapstat, windslot, windings});
  26. int gamma1 = regionunion({gamma1rot,gamma1stat});
  27. int gamma2 = regionunion({gamma2rot,gamma2stat});
  28. int all = regionunion({rotor,stator});
  29. mymesh.rotate(rotor, 0,0,alpha);
  30. // Peak winding current [A] times number of turns:
  31. double Imax = 300;
  32. // Calculate the area of a winding:
  33. double windarea = expression(1).integrate(winda, 5);
  34. // Nodal shape functions 'h1' for the z component of the vector potential.
  35. field az("h1"), x("x"), y("y"), z("z");
  36. // In 2D the vector potential only has a z component:
  37. expression a = array3x1(0,0,az);
  38. // Use interpolation order 2 to evaluate the initial p-adaptivity criterion:
  39. az.setorder(all, 2);
  40. // The interpolation order of az is increased where its gradient is sharper (p-adaptivity).
  41. az.setorder(norm(grad(az)), {az}, 2, 5);
  42. // Put a magnetic wall at the inner rotor and outer stator boundaries:
  43. az.setconstraint(inarc);
  44. az.setconstraint(outarc);
  45. // The remanent induction field in the magnet is 0.5 Tesla perpendicular to the magnet:
  46. expression normedradialdirection = array3x1(x,y,0)/sqrt(x*x+y*y);
  47. expression bremanent = 0.5 * normedradialdirection;
  48. // Vacuum magnetic permeability [H/m]:
  49. double mu0 = 4.0*getpi()*1e-7;
  50. // Define the permeability in all regions.
  51. //
  52. // Taking into account saturation and measured B-H curves can be easily done
  53. // by defining an expression based on a 'spline' object (see documentation).
  54. //
  55. parameter mu;
  56. mu|all = 2000.0*mu0;
  57. // Overwrite on non-magnetic regions:
  58. mu|nonmag = mu0;
  59. formulation magnetostatics;
  60. // The strong form of the magnetostatic formulation is curl( 1/mu * curl(a) ) = j, with b = curl(a):
  61. magnetostatics += integral(all, 1/mu* curl(dof(a)) * curl(tf(a)) );
  62. // Add the remanent magnetization of the rotor magnet:
  63. magnetostatics += integral(magnet, -1/mu* bremanent * curl(tf(a)));
  64. // Add the current density source js [A/m2] in the z direction in the stator windings.
  65. // A three-phased actuation is used. The currents are dephased by the mechanical angle
  66. // times the number of pole pairs. This gives a stator field rotating at synchronous speed.
  67. // Change the phase (degrees) to tune the electric angle:
  68. double phase = 0;
  69. parameter jsz;
  70. jsz|winda = Imax/windarea * sin( (phase + 4.0*alpha - 0.0) * getpi()/180.0);
  71. jsz|windb = Imax/windarea * sin( (phase + 4.0*alpha - 60.0) * getpi()/180.0);
  72. jsz|windc = Imax/windarea * sin( (phase + 4.0*alpha - 120.0) * getpi()/180.0);
  73. magnetostatics += integral(windings, -array3x1(0,0,jsz) * tf(a));
  74. // Rotor-stator continuity condition (including antiperiodicity settings with factor '-1'):
  75. magnetostatics += continuitycondition(gammastat, gammarot, az, az, {0,0,0}, alpha, 45.0, -1.0);
  76. // Rotor and stator antiperiodicity condition:
  77. magnetostatics += periodicitycondition(gamma1, gamma2, az, {0,0,0}, {0,0,45.0}, -1.0);
  78. // Simple p-adaptivity loop:
  79. for (int i = 0; i <= 2; i++)
  80. solve(magnetostatics);
  81. az.write(all, "a"+std::to_string((int)alpha)+".vtu", 2);
  82. curl(a).write(all, "b"+std::to_string((int)alpha)+".vtu", 2);
  83. // Write the adapted field order:
  84. getfieldorder(az).write(all, "fieldorderaz"+std::to_string((int)alpha)+".vtu");
  85. // The magnetostatic force acting on the motor is computed below.
  86. // This field will hold the x and y component of the magnetic forces:
  87. field magforce("h1xy");
  88. magforce.setorder(all, 1);
  89. // The magnetic force is projected on field 'magforce' on the solid stator region.
  90. // This is done with a formulation of the type dof*tf - force calculation = 0.
  91. formulation forceprojection;
  92. forceprojection += integral(statmagmat, dof(magforce)*tf(magforce));
  93. forceprojection += integral(stator, - predefinedmagnetostaticforce(tf(magforce, statmagmat), 1/mu*curl(a), mu));
  94. solve(forceprojection);
  95. // Calculate the torque:
  96. expression leverarm = array3x1(x,y,0);
  97. double torque = compz(crossproduct(leverarm, magforce)).integrate(statmagmat, 5);
  98. // The torque has to be scaled to the actual motor z length (50 mm) and multiplied
  99. // by 8 to take into account the full 360 degrees of the motor.
  100. // A minus sign gives the torque on the rotor (opposite sign than the stator torque).
  101. torque = - torque * 8.0 * 0.05;
  102. // To compute the torque with Arkkio's formula replace the above by the code below.
  103. //
  104. // expression ez = array3x1(0,0,1);
  105. // expression rer = array3x1(x,y,z);
  106. // expression ret = crossproduct(ez,rer);
  107. // double area = expression(1).integrate(gaprot, 5);
  108. // double torque = 0.05 * (2.0*getpi()/area * curl(a)*rer * 1.0/mu0 * curl(a)*ret).integrate(gaprot, 5);
  109. return torque;
  110. }
  111. int main(void)
  112. {
  113. SlepcInitialize(0,{},0,0);
  114. wallclock clk;
  115. double torque;
  116. std::cout << "Mechanical angle [degrees] and torque [Nm]:" << std::endl;
  117. for (double alpha = 0.0; alpha <= 45.0; alpha += 1.0)
  118. {
  119. torque = sparselizard(alpha);
  120. std::cout << alpha << " " << torque << std::endl;
  121. }
  122. clk.print("Total run time:");
  123. SlepcFinalize();
  124. // Code validation line. Can be removed:
  125. std::cout << (torque < 3.54898 && torque > 3.54896);
  126. return 0;
  127. }