Create Design of Experiments for PMSM and Run Design Study
This example shows you how to use Model-Based Calibration Toolbox™ (MBC) design of experiments (DOE) methods and the PMSM_IdIqInput.slx Simulink® model that contains a permanent magnet synchronous motor (PMSM) to characterize a PMSM. Characterizing a PMSM is an important step in calibrating optimal PMSM torque control. The data generated from the PMSM characterization is needed to optimize control lookup tables.
First, create a DOE to generate d- and q-axis current test points for a PMSM. Then, create a design study to run simulations at each d- and q-axis current combination to generate torque and d- and q-axis flux linkage data. Finally, export the simulation results.
This example requires the Parallel Computing Toolbox™ with the parallel preferences set to automatically create a parallel pool. For more information, see Specify Your Parallel Settings (Parallel Computing Toolbox).
Create DOE
Design an experiment to generate torque and d- and q-axis flux linkage data at various d- and q-axis currents, and . To accurately model torque and produce optimal calibration tables for the d- and q-axis flux linkage data, more points are needed at low . The experiment has low and high current regions, where the boundary between the two regions is at ten percent of the maximum current magnitude.
Set Current Ranges
Set the current range values based on the current limit. The current values are limited by the maximum current magnitude .
currentMagnitudeLimit =
615;
currentIdRange = [-currentMagnitudeLimit, 0];
currentIqRange = [0, currentMagnitudeLimit];
inputRange = {currentIdRange, currentIqRange};
iqBorder = 0.10*currentMagnitudeLimit;Define Torque Model Inputs
Define the torque model inputs. The currents and are limited by and .
varNames = ["IdCmd", "IqCmd"]; mdlInputObj = mbcmodel.modelinput( ... "Symbol", varNames, "Name", varNames, "Range", inputRange);
Create Space-Filling Design
Use the CreateDesign method to create a Sobol sequence design based on the model input object mdlInputObj for the low and high current regions.
spaceFillDesignType ="Sobol Sequence"; highIqCurrSpacefillDesign = CreateDesign(mdlInputObj, ... "Type", spaceFillDesignType); lowIqCurrSpacefillDesign = CreateDesign(mdlInputObj, ... "Type", spaceFillDesignType);
Create Design Constraint
Use the CreateConstraint method to create an ellipsoid constraint for the current magnitude. The current magnitude is constrained by .
ellipsoidConstraintMatrix = 1/currentMagnitudeLimit^2*eye(2); currLimitConstraint = CreateConstraint(highIqCurrSpacefillDesign, ... 'Type', 'Ellipsoid'); currLimitConstraint.Matrix = ellipsoidConstraintMatrix; currLimitConstraint.CenterPoint = [0,0];
Add Design Constraints
Use the AddConstraint method to add the constraints to the space-filling designs.
highIqCurrSpacefillDesign = AddConstraint(highIqCurrSpacefillDesign, ... currLimitConstraint); lowIqCurrSpacefillDesign = AddConstraint(lowIqCurrSpacefillDesign, ... currLimitConstraint);
Generate Space-Filling Design
Define the number of boundary points and generate the points at the boundary. Then, add the points to a table.
numBoundaryPoints =25; boundAngles = linspace(pi/2, pi, numBoundaryPoints-1)'; boundPts = [cos(boundAngles), sin(boundAngles)]*currentMagnitudeLimit; boundPts = [boundPts; [0,0]]; % Add point at Id = 0 and Iq = 0 boundTbl = array2table(boundPts, "VariableNames", varNames);
Define the number of points in the low current space and high current space. Then, use the ConstrainedGenerate method to generate the constrained space-filling designs, each with a size of 10.
numHighCurrSpaceFillPoints =150; numLowCurrSpaceFillPoints =
75; highIqCurrSpacefillDesign.Generator.Limits = ... [currentIdRange; iqBorder currentMagnitudeLimit]; highIqCurrSpacefillDesign = ... ConstrainedGenerate(highIqCurrSpacefillDesign, ... numHighCurrSpaceFillPoints, ... 'UnconstrainedSize', 5*numHighCurrSpaceFillPoints, ... 'MaxIter', 10); lowIqCurrSpacefillDesign.Generator.Limits = ... [currentIdRange; 0 iqBorder]; lowIqCurrSpacefillDesign = ... ConstrainedGenerate(lowIqCurrSpacefillDesign, ... numLowCurrSpaceFillPoints, ... 'UnconstrainedSize', 5*numLowCurrSpaceFillPoints, ... 'MaxIter', 10);
Merge the resulting high and low current space-filling designs.
spacefillDesign = Merge( ... highIqCurrSpacefillDesign, lowIqCurrSpacefillDesign); fillTbl = array2table(spacefillDesign.Points, "VariableNames", varNames);
Save the boundary and space-filling points to the table doeTable.
doeTbl = [boundTbl; fillTbl];
Plot Input Points
Display the input points IdCmd and IqCmd. Note that boundary points are covered and that the low current space has extra points.
plot(doeTbl.IdCmd, doeTbl.IqCmd, 's', 'MarkerFaceColor', 'b') xlabel("Id [A]") ylabel("Iq [A]") title("Experiment Set Points") axis([-currentMagnitudeLimit, 0, 0, currentMagnitudeLimit]);
![Figure contains an axes object. The axes object with title Experiment Set Points, xlabel Id [A], ylabel Iq [A] contains a line object which displays its values using only markers.](../../examples/mbc/win64/CreateDoEForPMSMAndRunDesignStudyExample_01.png)
Save DOE Data
Select the check box to save the DOE and command points to a file.
iffalse writetable(doeTbl,"PMSMDoEPoints.xlsx"); end
Create Design Study
Using a simulink.multisim.DesignStudy object and the parsim function, create a design study to run simulations on the PMSM_IdIqInput.slx model in parallel at each d- and q-axis current combination.
Specify the values from the table doeTble to the simulink.multisim.Variable object designVariables.
mdlName = "PMSM_IdIqInput"; for i=1:length(varNames) designVariables(i) = simulink.multisim.Variable( ... varNames(i), doeTbl.(varNames(i))); end
Create a sequential parameter combination with the simulink.Multisim.Sequential object.
sequentialDoE = simulink.multisim.Sequential(designVariables);
Create a simulink.multisim.DesignStudy object.
designStudy = simulink.multisim.DesignStudy(mdlName, sequentialDoE);
Use the PostSimFcn function to return only the final values.
function simOut = getResults(simOut) % Get final values simOut.Fluxd = simOut.Fluxd.Data(end); simOut.Fluxq = simOut.Fluxq.Data(end); simOut.Trq = simOut.Trq.Data(end); end designStudy.PostSimFcn = @getResults;
Run simulations with the simulink.multisim.DesignStudy object using the parsim function.
Note that the parsim function runs the simulations in serial if a parallel pool cannot be created or if Parallel Computing Toolbox is not used.
jobObj = parsim(designStudy, "UseFastRestart", "on");
[15-Jan-2026 12:52:42] Checking for availability of parallel pool...
Starting parallel pool (parpool) using the 'Processes' profile ...
Preserving jobs with IDs: 1 because they contain crash dump files.
You can use 'delete(myCluster.Jobs)' to remove all jobs created with profile Processes. To create 'myCluster' use 'myCluster = parcluster('Processes')'.
15-Jan-2026 12:54:27: Job Queued. Waiting for parallel pool job with ID 2 to start ...
15-Jan-2026 12:55:29: Job Queued. Waiting for parallel pool job with ID 2 to start ...
Connected to parallel pool with 4 workers.
[15-Jan-2026 12:56:50] Starting Simulink on parallel workers...
[15-Jan-2026 12:57:57] Configuring simulation cache folder on parallel workers...
[15-Jan-2026 12:57:58] Loading model on parallel workers...
[15-Jan-2026 12:58:31] Running simulations...
Extract the results.
simOut = jobObj.fetchOutputs();
Iterate through the results to extract the final steady-state values and save the values to a table.
exportVarNames = ["I_d", "I_q", "Psi_d", "Psi_q", "Trq_msr"]; resultsTbl = array2table(zeros(length(simOut), length(exportVarNames)), ... "VariableNames", exportVarNames); for i = 1:length(simOut) % Inputs resultsTbl.I_d(i) = doeTbl.IdCmd(i); resultsTbl.I_q(i) = doeTbl.IqCmd(i); % Logged values resultsTbl.Psi_d(i) = simOut(i).Fluxd; resultsTbl.Psi_q(i) = simOut(i).Fluxq; resultsTbl.Trq_msr(i) = simOut(i).Trq; end
Visually Check Results
Plot with respect to and .
stem3(resultsTbl.I_d,resultsTbl.I_q,resultsTbl.Psi_d,"LineStyle","none"); title("Flux d [Wb]"); xlabel("Id [A]");ylabel("Iq [A]"); zlabel("Flux d [Wb]")
![Figure contains an axes object. The axes object with title Flux d [Wb], xlabel Id [A], ylabel Iq [A] contains an object of type stem.](../../examples/mbc/win64/CreateDoEForPMSMAndRunDesignStudyExample_02.png)
Plot with respect to and .
stem3(resultsTbl.I_d, resultsTbl.I_q, resultsTbl.Psi_q,"LineStyle","none"); title("Flux q [Wb]"); xlabel("Id [A]"); ylabel("Iq [A]"); zlabel("Flux q [Wb]")
![Figure contains an axes object. The axes object with title Flux q [Wb], xlabel Id [A], ylabel Iq [A] contains an object of type stem.](../../examples/mbc/win64/CreateDoEForPMSMAndRunDesignStudyExample_03.png)
Plot and with respect to .
stem3(resultsTbl.I_d, resultsTbl.I_q, resultsTbl.Trq_msr, ... "LineStyle","none"); title("Measured Torque [Nm]"); xlabel("Id [A]"); ylabel("Iq [A]"); zlabel("Measured Torque [Nm]");
![Figure contains an axes object. The axes object with title Measured Torque [Nm], xlabel Id [A], ylabel Iq [A] contains an object of type stem.](../../examples/mbc/win64/CreateDoEForPMSMAndRunDesignStudyExample_04.png)
Export Results
Select the check box to export the simulation results to an Excel® file.
iffalse writetable(resultsTbl,"PMSMDesignStudyResultsExample.xlsx"); end
To preprocess the exported data and generate current controller calibration tables for optimal torque control, see Preprocess Permanent Magnet Synchronous Motor (PMSM) Data and Autogenerate Current Controller Calibration Tables.
See Also
Running Multiple Simulations in Simulink | simulink.multisim.DesignStudy | parsim





