EMDtool briefly
So, are you wondering how EMDtool works in practice? After reading this page you’ll have an idea.
Analysis workflow
A typical analysis workflow is described below.
Creating a model
Dimensions and parameters for Geometry Templates
First of all, you’ll be using geometry templates the majority of the time. A geometry template is a ´Matlab class´, representing a parametric component of your model. For instance, the Stator is a rather general-purpose template of a slotted stator. And, the SPM1 template represents a surface-PM rotor with or without a rotor retaining sleeve, eddy-current shield, regular or Halbach array magnets, segmented magnets, arced or regular magnet faces, and so forth.
In other words, there’s no beating a good template in terms of ease of use, or code reusability for that matter. EMDtool
already offers a quite decent selection of templates, and creating your own is not such a formidable task once you get the hang of it (please see here and follow the breadcrumbs to get started). Besides, the initial investment of time here quickly pays itself back.
Anyways, creating a templated model is simple. You simply create a struct
, and assign into it (as fields
) the parameters and dimensions required. These typically include
-
stator and rotor temperature (
.temperature_stator
and.temperature_rotor
) respectively. In the future, it might be possible to change these later on, but for now they are simple hardcoded dimensions specified in advance -
stack length
.leff
-
Any other geometric dimensions (think number of pole-pairs, stator OD and ID, rotor magnet pitch)
-
Materials used: either indices to default built-in materials, or custom Material objects either returned by say the PMlibrary or SteelLibrary classes or custom-made by you.
-
Specifications for polyphase circuits: such as number of turns, layers, parallel paths, phases, etc… A winding specification class is what you’ll need here.
Creating the geometries
Once you have the dimensions set (say in a structure called dim
), you’ll instantiate your geometry templates. Typically, you’ll also visualize them to make sure everything is working correctly.
Here’s how it can look like - code and plot directly from the EMDtool examples:
stator = Stator(dim);
rotor = VIPM1(dim);
%plotting geometries
figure(1); clf; hold on; box on; axis equal;
stator.plot_geometry();
rotor.plot_geometry();
Creating a model
Next, your templates are meshed.
stator.mesh_geometry();
rotor.mesh_geometry();
Finally, your newly-meshed templates are added and bound to a Model. Most users will only be using the simple RFmodel class - a simple class for radial-flux machines.
Here’s how it works - again from the included Examples.
motor = RFmodel(dim, stator, rotor);
figure(2); clf; hold on; box on; axis equal;
motor.visualize('plot_axial', false);
Note: It doesn’t have to end here - the RFmodel class and its parent MotorModelBase also support some other things, like adding more components beside a single stator and rotor. A semi-common example would be an outrunner motor, with a stationary aluminum frame outside the rotor. One must be careful for eddy losses in such a case, after all!
Running some simple analysis
Creating a problem
The next step is running some analysis. This begins by creating a MagneticsProblem
object.
problem = MagneticsProblem(motor);
Like it name sugggests, this class represents a magnetics problem, and contains several methods to solve itself, in a manner of speaking.
Setting some excitation
Those familiar with finite-element analysis know that a problem needs some boundary conditions to be solvable. EMDtool
handles the actual boundary conditions automatically (in typical problems at least), but you must still specify what kind of excitation is used.
For a very simple example, let’s solve the no-load flux density of our IPM motor model created earlier. We’ll use static analysis (and ignore e.g. magnet eddy currents) and only solve it for a single rotor position. In this case, we’ll set an all-zero current density into the stator winding:
phase_circuit = stator.winding;
phase_circuit.set_source('uniform coil current', zeros(stator.winding_spec.phases, 1));
Setting some parameters
Moreover, we’ll need to specify some parameters
for our problem
. These can include the supply frequency, number of time-steps per electrical period and the number of periods to analyse, whether or not to print the progress in the command window, and so on.
In our simply example, we’ll simply specify the rotor angle:
pars = SimulationParameters('rotorAngle', 0);
Solving the problem
Finally, we are ready to solve our problem! Here’s how it goes:
static_solution = problem.solve_static(pars);
Simple enough, right? The solve_static
method solves the problem
using static analysis, i.e. ignoring all eddy currents and induced voltages. (We can post-process e.g. phase or terminal voltages from a static solution too - we simply can’t have them influence the current waveforms without re-computing the solution.)
The method returns a StaticSolution
object, which again does exactly what its name suggests.
Visualizing the solution
As we have only solved a static no-load problem without motion or any excitation, there isn’t too much to do with it. But, we can always ~create some nice figures for the management~ evaluate our no-load behaviour.
figure(5); clf; hold on; box on;
motor.plot_flux(static_solution);
title('No-load flux density');
Running some more complex analysis
Now, let’s do something a little more complex. We’ll solve the same problem using fully transient time-stepping analysis. We’ll specify the net terminal currents, but let everything else be solved according to the circuit (and other constitutive) equations:
%interesting circuits
phase_circuit = stator.winding;
spec = stator.winding_spec;
%setting parameters
pars = SimulationParameters('f', rpm/60*dim.p, 'isDC', true, 'N_periods', 1, ...
'N_stepsPerPeriod', 400, 'silent', false);
%setting a current source
Is = spec.xy([id; iq], 2*pi*pars.f*pars.ts()); %(id,iq) to phase quantities
phase_circuit.set_source('terminal current source', Is);
Then, we’ll solve the problem in the frequency-domain first to get some initial conditions, and then run time-stepping analysis for one electrical period.
%solving harmonic
harmonic_solution = problem.solve_quasistatic(pars);
%solving stepping
stepping_solution = problem.solve_stepping(pars);
Analysing the results
After a moment (usually between low tens of seconds to some minutes, depending on the model size), we’ll have our results. Now let’s do something with them.
For instance, the typical user looking for purely numerical data should be perfectly fine with the results_summary
returned by the motor
model.
summary = motor.results_summary(stepping_solution, 'verbose', true);
The .results_summary
method returns a rather colossal structure
, and with the verbose
option also prints the summary to the command prompt for some at-glance evaluation of the results:
Summary of results:
rpm : 6000
torque_waveform : (array)
torque_mean : 180.8283
shaft_power : 113617.768
efficiency : 0.97509
total_losses : 2902.7222
input_power_from_power_balance : 116520.4902
input_power_from_terminal_waveforms : 115551.3129
**********************************************************
phase_circuit_data:
input_power : 115551.3129
input_power_waveform : (array)
apparent_input_power : 133530.7152
displacement_power_factor : 0.87787
I_phase_waveform : (array)
I_phase_dq : -225.5573 398.6571
I_phase_rms : 323.8854
I_terminal_waveform : (array)
I_terminal_rms : 323.8854
U_phase_waveform : (array)
U_phase_induced : (array)
U_phase_dq : -162.6688 101.1975
U_phase_rms : 136.3425
U_terminal_waveform : (array)
U_terminal_rms : 236.1521
coil_current_density_rms : 11317778.1767
**********************************************************
total_iron_losses : 1005.2843
**********************************************************
iron_loss_data:
P_total : 1005.2843
P_total_time : (array)
P_hysteresis : 620.7535
P_rotor : 102.5572
P_hysteresis_rotor : 72.5448
P_eddy_rotor : 30.0124
P_eddy : 384.5308
P_excess : 0
p_hysteresis_elementwise : (array)
p_eddy_elementwise : (array)
p_excess_elementwise : (array)
**********************************************************
total_circuit_losses : 1897.4379
total_Phasewinding_losses : 1886.9455
**********************************************************
Phasewinding_loss_data:
mean_total_losses : 1886.9455
mean_AC_losses : 0
mean_DC_losses : 1886.9455
**********************************************************
total_Magnets_losses : 10.4924
**********************************************************
Magnets_loss_data:
mean_losses_per_conductor : 4.8488 5.6436
conductor_loss_waveform : (array)
**********************************************************
total_Shaft_losses : 1.1607e-05
**********************************************************
Shaft_loss_data:
mean_losses_per_conductor : 1.1607e-05
conductor_loss_waveform : (array)
**********************************************************
But then again, we humans are just great at interpreting visual data, so here are some examples of what we can do.
We can, for instance, plot the flux density at load on time-step no. 75, as well as our torque waveform:
figure(5); clf; hold on; box on;
motor.plot_flux( stepping_solution, 75 );
%plotting torque
T = motor.compute_torque(stepping_solution);
plot(stepping_solution.ts, T);
xlabel('Time (s)');
ylabel('Torque (Nm)');
Or, we can plot both the line-to-line voltage waveforms as well as the amplitude of the (phase) voltage space vector:
%plotting voltage and voltage space vector
E = phase_circuit.terminal_voltage(stepping_solution);
Edq = phase_circuit.phase_voltage(stepping_solution, 'output', 'space vector');
Edq_norm = colnorm( Edq );
figure(7); clf; hold on; box on; grid on;
plot(stepping_solution.ts, E', 'b');
plot(stepping_solution.ts, Edq_norm, 'k', 'linewidth', 2);
xlabel('Time (s)');
ylabel('Voltage (A)');
title('Terminal voltage and (phase) voltage vector amplitude');