1 /* $Header: /var/cvs/mbdyn/mbdyn/mbdyn-1.0/modules/module-asynchronous_machine/module-asynchronous_machine.cc,v 1.11 2017/01/12 14:47:25 masarati Exp $ */
2 /*
3 * MBDyn (C) is a multibody analysis code.
4 * http://www.mbdyn.org
5 *
6 * Copyright (C) 1996-2017
7 *
8 * Pierangelo Masarati <masarati@aero.polimi.it>
9 * Paolo Mantegazza <mantegazza@aero.polimi.it>
10 *
11 * Dipartimento di Ingegneria Aerospaziale - Politecnico di Milano
12 * via La Masa, 34 - 20156 Milano, Italy
13 * http://www.aero.polimi.it
14 *
15 * Changing this copyright notice is forbidden.
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation (version 2 of the License).
20 *
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 */
31 /*
32 AUTHOR: Reinhard Resch <r.resch@secop.com>
33 Copyright (C) 2011(-2017) all rights reserved.
34
35 The copyright of this code is transferred
36 to Pierangelo Masarati and Paolo Mantegazza
37 for use in the software MBDyn as described
38 in the GNU Public License version 2.1
39 */
40
41 /**
42 * \mainpage
43 * This code is a simple example on how to implement a user defined element for the free multibody software <A HREF="http://www.aero.polimi.it/mbdyn/">MBDyn</A>.
44 * Since no documentation exists for user defined elements in MBDyn at the time of writing I have written this document in the hope that it will be helpful for others who want to write their own user defined elements for MBDyn.<BR>
45 *
46 * The purpose of this code is the simulation of an asynchronous electrical machine as a part of a multibody model.
47 * With this software the coupling between the mechanical and the electrical differential equations can be considered.
48 * The theoretical background is based on the book <A HREF="http://books.google.at/books?id=z6B3lcxDgkkC&printsec=frontcover&dq=Maschinendynamik++%26+dresig&hl=de&ei=SwvETYi8NJSw4Ab_luX3BA&sa=X&oi=book_result&ct=result&resnum=1&ved=0CDIQ6AEwAA#v=onepage&q&f=false">
49 * Maschinendynamik Springer 2009, Dresig, Holzweißig.</A><BR>
50 * In the formulas in this book the stator resistance is neglected and the free run slip is assumed to be zero.
51 * Also small perturbations around the mean angular velocity are assumed.<BR><BR>
52 * The input parameters for the simulation are:<BR>
53 * Breakdown torque \f$M_K\f$.<BR>
54 * Slip at the breakdown torque \f$s_K\f$.<BR>
55 * Synchronous angular velocity \f$\Omega_S=2\,\pi\,\frac{f}{p}\f$ (might be a function of the time in case of an frequency inverter).<BR>
56 * A flag that determines whether the power supply is turned on or off \f$\gamma(t)\f$.<BR>
57 * Optional the initial value of the motor torque \f$M\f$.<BR>
58 * Optional the initial value of the first derivative of the motor torque \f$\dot{M}\f$.<BR>
59 *
60 * The sense of rotation is determined by the sign of \f$\Omega_S\f$.
61 * If the sign is positive, the sense of rotation is also positive in the reference frame of the stator node.
62 *
63 * This code is implemented as an user defined element for <A HREF="http://www.aero.polimi.it/mbdyn/">MBDyn</A>.
64 * The element is connected to two structural nodes. The rotor node and the stator node.
65 * The axis of rotation is assumed to be axis three in the reference frame of the stator node.
66 * It is assumed that the axis of the rotor node is parallel to the axis of the stator node.
67 * The torque is imposed to the structural nodes in direction of axis three in the reference frame of the stator node.
68 * The synchronous angular velocity can be specified by means of a drive caller. This allows to simulate a frequency inverter.
69 */
70
71 #include "mbconfig.h" // This goes first in every *.c,*.cc file
72
73 #include <cassert>
74 #include <cstdio>
75 #include <cmath>
76 #include <cfloat>
77 #include <iostream>
78 #include <iomanip>
79 #include <limits>
80
81 #include "dataman.h"
82 #include "userelem.h"
83 #include "module-asynchronous_machine.h"
84
85 class asynchronous_machine
86 : virtual public Elem, public UserDefinedElem
87 {
88 public:
89 asynchronous_machine(unsigned uLabel, const DofOwner *pDO,
90 DataManager* pDM, MBDynParser& HP);
91 virtual ~asynchronous_machine(void);
92 virtual void Output(OutputHandler& OH) const;
93 virtual unsigned int iGetNumDof(void) const;
94 virtual DofOrder::Order GetDofType(unsigned int i) const;
95 virtual std::ostream& DescribeDof(std::ostream& out, const char *prefix, bool bInitial) const;
96 virtual std::ostream& DescribeEq(std::ostream& out, const char *prefix, bool bInitial) const;
97 virtual unsigned int iGetNumPrivData(void) const;
98 virtual unsigned int iGetPrivDataIdx(const char *s) const;
99 virtual doublereal dGetPrivData(unsigned int i) const;
100 virtual void SetInitialValue(VectorHandler& X);
101 virtual void Update(const VectorHandler& XCurr,const VectorHandler& XPrimeCurr);
102 virtual void AfterPredict(VectorHandler& X, VectorHandler& XP);
103 virtual void WorkSpaceDim(integer* piNumRows, integer* piNumCols) const;
104 VariableSubMatrixHandler&
105 AssJac(VariableSubMatrixHandler& WorkMat,
106 doublereal dCoef,
107 const VectorHandler& XCurr,
108 const VectorHandler& XPrimeCurr);
109 SubVectorHandler&
110 AssRes(SubVectorHandler& WorkVec,
111 doublereal dCoef,
112 const VectorHandler& XCurr,
113 const VectorHandler& XPrimeCurr);
114 virtual int iGetNumConnectedNodes(void) const;
115 virtual void GetConnectedNodes(std::vector<const Node *>& connectedNodes) const;
116 virtual void SetValue(DataManager *pDM, VectorHandler& X, VectorHandler& XP,
117 SimulationEntity::Hints *ph);
118 virtual std::ostream& Restart(std::ostream& out) const;
119 virtual unsigned int iGetInitialNumDof(void) const;
120 virtual void
121 InitialWorkSpaceDim(integer* piNumRows, integer* piNumCols) const;
122 virtual VariableSubMatrixHandler&
123 InitialAssJac(VariableSubMatrixHandler& WorkMat,
124 const VectorHandler& XCurr);
125 virtual SubVectorHandler&
126 InitialAssRes(SubVectorHandler& WorkVec, const VectorHandler& XCurr);
127
128 private:
129 bool IsMotorOn(void) const;
130
131 private:
132 const StructNode* m_pRotorNode; /**< node of the rotor where the torque \f$\boldsymbol{f}_3\f$ is imposed and the angular velocity \f$\dot{\boldsymbol{\varphi}}_1\f$ is determined */
133 const StructNode* m_pStatorNode; /**< node of the stator where the torque \f$\boldsymbol{f}_4\f$ is applied and the angular velocity \f$\dot{\boldsymbol{\varphi}}_2\f$ is determined (axis 3 of the stator node is assumed to be the axis of rotation) */
134 doublereal m_MK; /**< breakdown torque \f$M_K\f$ */
135 doublereal m_sK; /**< slip at the breakdown torque \f$s_K\f$ */
136 DriveOwner m_OmegaS; /**< drive that returns the synchronous angular velocity \f$\Omega_S=2\,\pi\,\frac{f}{p}\f$ (might be a function of the time in case of an frequency inverter) */
137 DriveOwner m_MotorOn; /**< drive that returns whether the power supply is turned on or off \f$\gamma(t)\f$ */
138 doublereal m_M; /**< holds the motor torque \f$M\f$ after convergence for convenience */
139 doublereal m_dM_dt; /**< holds the first derivative of the motor torque \f$\dot{M}\f$ after convergence */
140 doublereal m_dM_dt2; /**< holds the second derivative of the motor torque \f$\ddot{M}\f$ after convergence */
141 doublereal m_omega; /**< holds the angular velocity \f$\dot{\varphi}\f$ of the rotor relative to the stator around axis 3 of the stator node after convergence */
142 doublereal m_domega_dt; /**< holds the angular acceleration \f$\ddot{\varphi}\f$ of the rotor node relative to the stator node after convergence */
143 static const doublereal sm_SingTol;
144 };
145
146 const doublereal asynchronous_machine::sm_SingTol = std::pow(std::numeric_limits<doublereal>::epsilon(), 0.9);
147
148 /**
149 * \param uLabel the label assigned to this element in the input file
150 * \param pDO
151 * \param pDM pointer to the data manager (needed to read structural nodes for example)
152 * \param HP reference to the math parser (needed to read from the input file)
153 * \brief Constructs the element from the information in the input file.<BR>
154 *
155 * A description of the exact input file syntax can be retrieved by adding the following statement to the MBDyn input file:<BR>
156 * user defined: 1, asynchronous_machine, help;
157 */
asynchronous_machine(unsigned uLabel,const DofOwner * pDO,DataManager * pDM,MBDynParser & HP)158 asynchronous_machine::asynchronous_machine(
159 unsigned uLabel, const DofOwner *pDO,
160 DataManager* pDM, MBDynParser& HP)
161 : Elem(uLabel, flag(0)),
162 UserDefinedElem(uLabel, pDO),
163 m_pRotorNode(0),
164 m_pStatorNode(0),
165 m_MK(0.),
166 m_sK(0.),
167 m_OmegaS(0),
168 m_MotorOn(0),
169 m_M(0.),
170 m_dM_dt(0.),
171 m_dM_dt2(0.),
172 m_omega(0.),
173 m_domega_dt(0.)
174 {
175 if (HP.IsKeyWord("help")) {
176 silent_cout(
177 "\n"
178 "Module: asynchronous_machine\n"
179 "\n"
180 " This module implements an asynchronous electric machine\n"
181 " according to\n"
182 "\n"
183 " Maschinendynamik\n"
184 " Hans Dresig, Franz Holzweißig\n"
185 " Springer, 2009\n"
186 " page 58 equation 1.122 and 1.123\n"
187 "\n"
188 "Syntax:\n"
189 " asynchronous_machine,\n"
190 " rotor, (label) <rotor_node>,\n"
191 " stator, (label) <stator_node>,\n"
192 " MK, (real) <MK>,\n"
193 " sK, (real) <sK>,\n"
194 " OmegaS, (DriveCaller) <omega_s>\n"
195 " [ , motor on , (DriveCaller) <motor_on> ]\n"
196 " [ , M0 , (real) <M0> ]\n"
197 " [ , MP0 , (real) <MP0> ]\n"
198 "\n"
199 " MK ... breakdown torque ( MK > 0 )\n"
200 " sK ... breakdown slip ( sK > 0 )\n"
201 " OmegaS = 2 * pi * f / p * sense_of_rotation ... synchronous angular velocity\n"
202 " motor_on ... power supply is turned off when 0, on otherwise\n"
203 " M0 ... initial torque ( default 0 )\n"
204 " MP0 ... initial torque derivative ( default 0 )\n"
205 "\n"
206 " The axis of rotation is assumed to be axis 3 of the reference frame of the stator node.\n"
207 << std::endl);
208
209 if (!HP.IsArg()) {
210 // Exit quietly if nothing else is provided
211 throw NoErr(MBDYN_EXCEPT_ARGS);
212 }
213 }
214
215 if (!HP.IsKeyWord("rotor")) {
216 silent_cerr("asynchronous_machine(" << GetLabel() << "): keyword \"rotor\" expected at line " << HP.GetLineData() << std::endl);
217 throw ErrGeneric(MBDYN_EXCEPT_ARGS);
218 }
219
220 m_pRotorNode = pDM->ReadNode<const StructNode, Node::STRUCTURAL>(HP);
221
222 if (!m_pRotorNode) {
223 silent_cerr("asynchronous_machine(" << GetLabel() << "): structural node expected at line " << HP.GetLineData() << std::endl);
224 throw ErrGeneric(MBDYN_EXCEPT_ARGS);
225 }
226
227 if (!HP.IsKeyWord("stator")) {
228 silent_cerr("asynchronous_machine(" << GetLabel() << "): keyword \"stator\" expected at line " << HP.GetLineData() << std::endl);
229 throw ErrGeneric(MBDYN_EXCEPT_ARGS);
230 }
231
232 m_pStatorNode = pDM->ReadNode<const StructNode, Node::STRUCTURAL>(HP);
233
234 if (!m_pStatorNode) {
235 silent_cerr("asynchronous_machine(" << GetLabel() << "): structural node expected at line " << HP.GetLineData() << std::endl);
236 throw ErrGeneric(MBDYN_EXCEPT_ARGS);
237 }
238
239 if (!HP.IsKeyWord("MK")) {
240 silent_cerr("asynchronous_machine(" << GetLabel() << "): keyword \"MK\" expected at line " << HP.GetLineData() << std::endl);
241 throw ErrGeneric(MBDYN_EXCEPT_ARGS);
242 }
243 m_MK = HP.GetReal();
244
245 if (!HP.IsKeyWord("sK")) {
246 silent_cerr("asynchronous_machine(" << GetLabel() << "): keyword \"sK\" expected at line " << HP.GetLineData() << std::endl);
247 throw ErrGeneric(MBDYN_EXCEPT_ARGS);
248 }
249 m_sK = HP.GetReal();
250
251 if (!HP.IsKeyWord("OmegaS")) {
252 silent_cerr("asynchronous_machine(" << GetLabel() << "): keyword \"OmegaS\" expected" << std::endl);
253 throw ErrGeneric(MBDYN_EXCEPT_ARGS);
254 }
255 m_OmegaS.Set(HP.GetDriveCaller());
256
257 if (HP.IsKeyWord("motor" "on") || HP.IsKeyWord("motor_on")) {
258 m_MotorOn.Set(HP.GetDriveCaller());
259 } else {
260 m_MotorOn.Set(new OneDriveCaller());
261 }
262
263 if (HP.IsKeyWord("M0")) {
264 m_M = HP.GetReal();
265 }
266
267 if (HP.IsKeyWord("MP0")) {
268 m_dM_dt = HP.GetReal();
269 }
270
271 SetOutputFlag(pDM->fReadOutput(HP, Elem::LOADABLE));
272
273 Vec3 e3(m_pStatorNode->GetRCurr().GetCol(3));
274 const Vec3& omega1 = m_pRotorNode->GetWCurr();
275 const Vec3& omega2 = m_pStatorNode->GetWCurr();
276
277 m_omega = e3.Dot(omega1 - omega2);
278
279 const doublereal OmegaS = m_OmegaS.dGet();
280
281 pDM->GetLogFile() << "asynchronous machine: "
282 << uLabel << " "
283 << m_pRotorNode->GetLabel() << " "
284 << m_pStatorNode->GetLabel() << " "
285 << m_MK << " "
286 << m_sK << " "
287 << OmegaS << " "
288 << IsMotorOn() << " "
289 << m_M << " "
290 << m_dM_dt << " "
291 << m_omega
292 << std::endl;
293
294 // Note: The ODE for the asynchronous machine is defined for positive sign of OmegaS only!
295 // At user level the signs of M, dM/dt and d^2M/dt^2 are defined to be positive
296 // in positive coordinate system direction in the reference frame of the stator.
297 m_M *= copysign(1., OmegaS);
298 m_dM_dt *= copysign(1., OmegaS);
299 m_dM_dt2 *= copysign(1., OmegaS);
300 }
301
302 /**
303 * @param X vector of global degrees of freedom \f$\boldsymbol{y}\f$ after convergence at the current time step.
304 * @param XP derivative of the global degrees of freedom \f$\dot{\boldsymbol{y}}\f$ after convergence at the current time step.<BR>
305 * \brief This member function is called after each iteration in the nonlinear solution phase.
306 *
307 * In our element this member function saves the current state of the private degrees of freedom.
308 * This is needed for the implementation of the Output() and the dGetPrivData() member functions.
309 * If the private degrees of freedom are needed just for Output(), the code that saves it's state should be moved to AfterPredict() since this member function is called only once per time step.
310 */
311 void
Update(const VectorHandler & X,const VectorHandler & XP)312 asynchronous_machine::Update(const VectorHandler& X,const VectorHandler& XP)
313 {
314 const integer iFirstIndex = iGetFirstIndex();
315
316 const integer intTorqueDerivativeRowIndex = iFirstIndex + 1;
317 const integer intTorqueRowIndex = iFirstIndex + 2;
318 const integer intOmegaRowIndex = iFirstIndex + 3;
319
320 // save the current state needed by dGetPrivData() and Output()
321 m_M = X(intTorqueRowIndex);
322 m_dM_dt = X(intTorqueDerivativeRowIndex);
323 m_dM_dt2 = XP(intTorqueDerivativeRowIndex);
324 m_omega = X(intOmegaRowIndex);
325 m_domega_dt = XP(intOmegaRowIndex);
326 }
327
AfterPredict(VectorHandler & X,VectorHandler & XP)328 void asynchronous_machine::AfterPredict(VectorHandler& X, VectorHandler& XP)
329 {
330 Update(X, XP);
331 }
332
333 /**
334 * @return Returns true if the motor is powered on.<BR>
335 * \brief If a drive caller has been specified in the input file the value returned by the drive caller is used to determine if the motor is powered on.
336 * If no drive caller has been specified in the input file it is assumed that the motor is always powered on.
337 */
338 bool
IsMotorOn(void) const339 asynchronous_machine::IsMotorOn(void) const
340 {
341 return (m_MotorOn.dGet() != 0.);
342 }
343
~asynchronous_machine(void)344 asynchronous_machine::~asynchronous_machine(void)
345 {
346 // destroy private data
347 #ifdef DEBUG
348 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __PRETTY_FUNCTION__ << std::endl;
349 #endif
350 }
351
352 /**
353 * @param OH OH.Loadable() returns a reference to the stream intended for the output of loadable elements.<BR>
354 * \brief Writes private data to a file at each time step.
355 *
356 * The output format is as follows:<BR>
357 * <TABLE>
358 * <TR><TD>Column</TD> <TD>Value</TD> <TD>Description</TD></TR>
359 * <TR><TD>1</TD> <TD>GetLabel()</TD> <TD>The label of the element.</TD></TR>
360 * <TR><TD>2</TD> <TD>\f$M\f$</TD> <TD>motor torque</TD></TR>
361 * <TR><TD>3</TD> <TD>\f$\dot{M}\f$</TD> <TD>derivative of the motor torque versus time</TD></TR>
362 * <TR><TD>4</TD> <TD>\f$\ddot{M}\f$</TD> <TD>second derivative of the motor torque versus time</TD></TR>
363 * <TR><TD>5</TD> <TD>\f$\dot{\varphi}\f$</TD> <TD>the angular velocity of the rotor node with respect to the stator node</TD></TR>
364 * <TR><TD>6</TD> <TD>\f$\ddot{\varphi}\f$</TD> <TD>the angular acceleration of the rotor node with respect to the stator node</TD></TR>
365 * <TR><TD>7</TD> <TD>\f$\Omega_S\f$</TD> <TD>the synchronous angular velocity specified by the a drive</TD></TR>
366 * <TR><TD>8</TD> <TD>\f$\gamma\f$</TD> <TD>a flag which specifies if the motor is powered on</TD></TR>
367 * </TABLE>
368 */
369
370 void
Output(OutputHandler & OH) const371 asynchronous_machine::Output(OutputHandler& OH) const
372 {
373 if (bToBeOutput()) {
374 // Note: The ODE for the asynchronous machine is defined for positive sign of OmegaS only!
375 // At user level the signs of M, dM/dt and d^2M/dt^2 are defined to be positive
376 // in positive coordinate system direction in the reference frame of the stator.
377 doublereal OmegaS = m_OmegaS.dGet();
378 doublereal M = m_M*copysign(1., OmegaS);
379 doublereal dM_dt = m_dM_dt*copysign(1., OmegaS);
380 doublereal dM_dt2 = m_dM_dt2*copysign(1., OmegaS);
381
382 if (OH.UseText(OutputHandler::LOADABLE)) {
383 // output the current state: the column layout is as follows
384
385 // 1 2 3 4 5 6 7
386 // M dM_dt dM_dt2 omega domega_dt OmegaS gamma
387 //
388 // 0 label of the element
389 // 1 motor torque
390 // 2 derivative of the motor torque versus time
391 // 3 second derivative of the motor torque versus time
392 // 4 angular velocity of the rotor node relative to the stator node around axis 3
393 // 5 angular acceleration of the rotor node relative to the stator node around axis 3
394 // 6 synchronous angular velocity (might be a function of the time in case of an frequency inverter)
395 // 7 power supply is turned on or off
396
397 OH.Loadable() << GetLabel()
398 << " " << M
399 << " " << dM_dt
400 << " " << dM_dt2
401 << " " << m_omega
402 << " " << m_domega_dt
403 << " " << OmegaS
404 << " " << IsMotorOn()
405 << std::endl;
406 }
407 }
408 }
409
410 /**
411 * @param piNumRows pointer to a variable which receives the number of rows of the contribution to the residual vector and the jacobian matrix
412 * @param piNumCols pointer to a variable which receives the number of columns of the contribution to the jacobian matrix
413 * \brief The size of the contribution to the residual and the jacobian matrix is determined by this member function.
414 */
415 void
WorkSpaceDim(integer * piNumRows,integer * piNumCols) const416 asynchronous_machine::WorkSpaceDim(integer* piNumRows, integer* piNumCols) const
417 {
418 *piNumRows = 9;
419 *piNumCols = 9;
420 }
421
422 /**
423 * @return Returns the number of private degrees of freedom. In our case \f$M\f$, \f$\dot{M}\f$ and \f$\dot{\varphi}\f$ are private degrees of freedom only accessible to this element.
424 * \brief The number of private degrees of freedom is determined by this member function.
425 */
426 unsigned int
iGetNumDof(void) const427 asynchronous_machine::iGetNumDof(void) const
428 {
429 return 3;
430 }
431
432 /**
433 * @param i zero based index of the private degree of freedom (zero for the first private degree of freedom, one for second ...)
434 * @return Returns if the private degrees of freedom specified by i refer to differential or algebraic variables. In our case all private degrees of freedom are differential variables.
435 * \brief The type of the equation of the private degrees of freedom is determined by this member function.
436 */
437 DofOrder::Order
GetDofType(unsigned int i) const438 asynchronous_machine::GetDofType(unsigned int i) const
439 {
440
441 return DofOrder::DIFFERENTIAL;
442 }
443
444 /**
445 * @param out output stream that receives the formated output
446 * @param prefix should be output in the first column
447 *
448 * \brief This member function is called if the statement "print: dof description;" is specified in the input file.
449 *
450 * It prints a short description of the private degrees of freedom \f$M\f$, \f$\dot{M}\f$ and \f$\dot{\varphi}\f$.
451 */
452 std::ostream&
DescribeDof(std::ostream & out,const char * prefix,bool bInitial) const453 asynchronous_machine::DescribeDof(std::ostream& out, const char *prefix, bool bInitial) const
454 {
455 integer iIndex = iGetFirstIndex();
456
457 out
458 << prefix << iIndex + 1 << ": motor torque derivative [MP]" << std::endl
459 << prefix << iIndex + 2 << ": motor torque [M]" << std::endl
460 << prefix << iIndex + 3 << ": relative angular velocity [omega]" << std::endl;
461
462 return out;
463 }
464
465 /**
466 * @param out output stream that receives the formated output
467 * @param prefix should be output in the first column
468 *
469 * \brief This member function is called if the statement "print: equation description;" is specified in the input file.
470 *
471 * It prints a short description of the residual of the private degrees of freedom \f$f_1\f$, \f$f_2\f$ and \f$f_9\f$.
472 */
473 std::ostream&
DescribeEq(std::ostream & out,const char * prefix,bool bInitial) const474 asynchronous_machine::DescribeEq(std::ostream& out, const char *prefix, bool bInitial) const
475 {
476 integer iIndex = iGetFirstIndex();
477
478 out
479 << prefix << iIndex + 1 << ": motor DAE [f1]" << std::endl
480 << prefix << iIndex + 2 << ": motor torque derivative [f2]" << std::endl
481 << prefix << iIndex + 3 << ": angular velocity derivative [f9]" << std::endl;
482
483 return out;
484 }
485
486 /**
487 * @return Returns the number of private data available for this element.
488 * \brief This member function is called when a bind statement or a element drive is used to access private data of this element.
489 *
490 * In our case \f$M\f$, \f$\dot{M}\f$, \f$\ddot{M}\f$, \f$\dot{\varphi}\f$ and \f$\ddot{\varphi}\f$ are available.
491 */
492 unsigned int
iGetNumPrivData(void) const493 asynchronous_machine::iGetNumPrivData(void) const
494 {
495 return 5;
496 }
497
498 /**
499 * @param s specifies the name of the private data to be accessed by a bind statement or an element drive caller.
500 * @return returns the one based index of the private data.
501 * \brief The following string values can be specified in a bind statement or in an element drive caller.
502 *
503 * <TABLE>
504 * <TR><TD>index</TD> <TD>string</TD> <TD>variable</TD></TR>
505 * <TR><TD>1</TD> <TD>M</TD> <TD>\f$M\f$</TD></TR>
506 * <TR><TD>2</TD> <TD>MP</TD> <TD>\f$\dot{M}\f$</TD></TR>
507 * <TR><TD>3</TD> <TD>MPP</TD> <TD>\f$\ddot{M}\f$</TD></TR>
508 * <TR><TD>4</TD> <TD>omega</TD> <TD>\f$\dot{\varphi}\f$</TD></TR>
509 * <TR><TD>5</TD> <TD>omegaP</TD> <TD>\f$\ddot{\varphi}\f$</TD></TR>
510 * </TABLE>
511 */
512 unsigned int
iGetPrivDataIdx(const char * s) const513 asynchronous_machine::iGetPrivDataIdx(const char *s) const
514 {
515 static const struct {
516 int index;
517 char name[7];
518 }
519
520 data[] = {
521 { 1, "M" }, // motor torque
522 { 2, "MP"}, // derivative of the motor torque versus time diff(M,t) (named MP instead of dM_dt for compatibility with other elements)
523 { 3, "MPP"}, // second derivative of the motor torque versus time diff(M,t,2) (named MPP instead of dM_dt2 for compatibility with other elements)
524 { 4, "omega"}, // angular velocity of the rotor node relative to the stator node
525 { 5, "omegaP"} // angular acceleration of the rotor node relative to the stator node (named omegaP instead of domega_dt for compatibility with other elements)
526 };
527
528 for (unsigned i = 0; i < sizeof(data) / sizeof(data[0]); ++i ) {
529 if (0 == strcmp(data[i].name,s)) {
530 return data[i].index;
531 }
532 }
533
534 silent_cerr("asynchronous_machine(" << GetLabel() << "): no private data \"" << s << "\"" << std::endl);
535
536 return 0;
537 }
538
539 /**
540 * @param i the one based index of the private data
541 * @return returns the value of the private data addressed by i.
542 * \brief This member function is called whenever a bind statement or a element drive is used to access private data.
543 */
544 doublereal
dGetPrivData(unsigned int i) const545 asynchronous_machine::dGetPrivData(unsigned int i) const
546 {
547 // Note: The ODE for the asynchronous machine is defined for positive sign of OmegaS only!
548 // At user level the signs of M, dM/dt and d^2M/dt^2 are defined to be positive
549 // in positive coordinate system direction in the reference frame of the stator.
550
551 const doublereal OmegaS = m_OmegaS.dGet();
552
553 switch (i) {
554 case 1:
555 return m_M*copysign(1., OmegaS);
556 case 2:
557 return m_dM_dt*copysign(1., OmegaS);
558 case 3:
559 return m_dM_dt2*copysign(1., OmegaS);
560 case 4:
561 return m_omega;
562 case 5:
563 return m_domega_dt;
564 }
565
566 throw DataManager::ErrGeneric(MBDYN_EXCEPT_ARGS);
567 }
568
569 /**
570 * See iGetInitialNumDof().
571 */
572 void
SetInitialValue(VectorHandler & X)573 asynchronous_machine::SetInitialValue(VectorHandler& X)
574 {
575 return;
576 }
577
578 /**
579 * @param WorkMat sparse or full submatrix which receives the contribution to the jacobian matrix \f$Jac\f$
580 * @param dCoef the derivative coefficient is defined as \f$\Delta\boldsymbol{y} = dCoef \, \Delta\dot{\boldsymbol{y}}\f$
581 * @param XCurr the vector of the global degrees of freedom \f$\boldsymbol{y}\f$ at the current iteration step
582 * @param XPrimeCurr the vector of the derivative of the global degrees of freedom \f$\dot{\boldsymbol{y}}\f$<BR>
583 * \brief Computes the contribution to the jacobian matrix \f$\boldsymbol{Jac}\f$.<BR>
584 *
585 * \f[\boldsymbol{Jac}=-\frac{\partial\boldsymbol{f}}{\partial\dot{\boldsymbol{y}}}-dCoef\,\frac{\partial\boldsymbol{f}}{\partial\boldsymbol{y}}\f]<BR>
586 * For the definition of \f$\boldsymbol{f}\f$ see AssRes().<BR>
587 * \f[\frac{\partial\boldsymbol{f}}{\partial\boldsymbol{y}}=\left(\begin{array}{ccccc} \frac{\partial f_{1}}{\partial y_{1}} & \frac{\partial f_{1}}{\partial y_{2}} & \frac{\partial f_{1}}{\partial\boldsymbol{y}_{3}} & \frac{\partial f_{1}}{\partial\boldsymbol{y}_{4}} & \frac{\partial f_{1}}{\partial y_{5}}\\ \frac{\partial f_{2}}{\partial y_{1}} & \frac{\partial f_{2}}{\partial y_{2}} & \frac{\partial f_{2}}{\partial\boldsymbol{y}_{3}} & \frac{\partial f_{2}}{\partial\boldsymbol{y}_{4}} & \frac{\partial f_{2}}{\partial y_{5}}\\ \frac{\partial\boldsymbol{f}_{3}}{\partial y_{1}} & \frac{\partial\boldsymbol{f}_{3}}{\partial y_{2}} & \frac{\partial\boldsymbol{f}_{3}}{\partial\boldsymbol{y}_{3}} & \frac{\partial\boldsymbol{f}_{3}}{\partial\boldsymbol{y}_{4}} & \frac{\partial\boldsymbol{f}_{3}}{\partial y_{5}}\\ \frac{\partial\boldsymbol{f}_{4}}{\partial y_{1}} & \frac{\partial\boldsymbol{f}_{4}}{\partial y_{2}} & \frac{\partial\boldsymbol{f}_{4}}{\partial\boldsymbol{y}_{3}} & \frac{\partial\boldsymbol{f}_{4}}{\partial\boldsymbol{y}_{4}} & \frac{\partial\boldsymbol{f}_{4}}{\partial y_{5}}\\ \frac{\partial f_{5}}{\partial y_{1}} & \frac{\partial f_{5}}{\partial y_{2}} & \frac{\partial f_{5}}{\partial\boldsymbol{y}_{3}} & \frac{\partial f_{5}}{\partial\boldsymbol{y}_{4}} & \frac{\partial f_{5}}{\partial y_{5}}\end{array}\right)\f]<BR>
588 * \f[\frac{\partial\boldsymbol{f}}{\partial\boldsymbol{\dot{y}}}=\left(\begin{array}{ccccc} \frac{\partial f_{1}}{\partial\dot{y}_{1}} & \frac{\partial f_{1}}{\partial\dot{y}_{2}} & \frac{\partial f_{1}}{\partial\boldsymbol{\dot{y}}_{3}} & \frac{\partial f_{1}}{\partial\boldsymbol{\dot{y}}_{4}} & \frac{\partial f_{1}}{\partial\dot{y}_{5}}\\ \frac{\partial f_{2}}{\partial\dot{y}_{1}} & \frac{\partial f_{2}}{\partial\dot{y}_{2}} & \frac{\partial f_{2}}{\partial\boldsymbol{\dot{y}}_{3}} & \frac{\partial f_{2}}{\partial\boldsymbol{\dot{y}}_{4}} & \frac{\partial f_{2}}{\partial\dot{y}_{5}}\\ \frac{\partial\boldsymbol{f}_{3}}{\partial\dot{y}_{1}} & \frac{\partial\boldsymbol{f}_{3}}{\partial\dot{y}_{2}} & \frac{\partial\boldsymbol{f}_{3}}{\partial\boldsymbol{\dot{y}}_{3}} & \frac{\partial\boldsymbol{f}_{3}}{\partial\boldsymbol{\dot{y}}_{4}} & \frac{\partial\boldsymbol{f}_{3}}{\partial\dot{y}_{5}}\\ \frac{\partial\boldsymbol{f}_{4}}{\partial\dot{y}_{1}} & \frac{\partial\boldsymbol{f}_{4}}{\partial\dot{y}_{2}} & \frac{\partial\boldsymbol{f}_{4}}{\partial\boldsymbol{\dot{y}}_{3}} & \frac{\partial\boldsymbol{f}_{4}}{\partial\boldsymbol{\dot{y}}_{4}} & \frac{\partial\boldsymbol{f}_{4}}{\partial\dot{y}_{5}}\\ \frac{\partial f_{5}}{\partial\dot{y}_{1}} & \frac{\partial f_{5}}{\partial\dot{y}_{2}} & \frac{\partial f_{5}}{\partial\dot{\boldsymbol{y}}_{3}} & \frac{\partial f_{5}}{\partial\boldsymbol{\dot{y}}_{4}} & \frac{\partial f_{5}}{\partial\dot{y}_{5}}\end{array}\right)\f]<BR>
589 * \f[\frac{\partial f_{1}}{\partial y_{1}}=\left(2\, s_{K}\,+\frac{sign\left(\Omega_{S}\right)\,\dot{y}_{5}}{s\,\Omega_{S}^{2}}\right)\,\left|\Omega_{S}\right|\f]
590 * \f[\frac{\partial f_{1}}{\partial\dot{y}_{1}}=1\f]
591 * \f[\frac{\partial f_{1}}{\partial y_{2}}=\left(s_{K}^{2}+s^{2}\right)\,\Omega_{S}^{2}+\frac{sign\left(\Omega_{S}\right)\,\dot{y}_{5}\, sK}{s}\f]
592 * \f[\frac{\partial f_{1}}{\partial y_{5}}=\frac{\partial f_{1}}{\partial s}\,\frac{\partial s}{\partial y_{5}}\f]
593 * \f[\frac{\partial f_{1}}{\partial s}=-\frac{y_{1}\,\dot{y}_{5}}{s^{2}\,\Omega_{S}}+\left(2\, s\,\Omega_{S}^{2}-\frac{sign\left(\Omega_{S}\right)\:\dot{y}_{5}\, s_{K}}{s^{2}}\right)\, y_{2}-2\, M_{K}\, s_{K}\,\Omega_{S}^{2}\f]
594 * \f[\frac{\partial s}{\partial y_{5}}=-\frac{1}{\Omega_{S}}\f]
595 * \f[\frac{\partial f_{1}}{\partial\dot{y}_{5}}=\frac{y_{1}}{s\,\Omega_{S}}+sign\left(\Omega_{S}\right)\,\frac{s_{K}}{s}\, y_{2}\f]
596 * \f[\frac{\partial f_{2}}{\partial y_{1}}=-1\f]
597 * \f[\frac{\partial f_{2}}{\partial\dot{y}_{2}}=1\f]
598 * \f[\frac{\partial f_{3}}{\partial y_{2}}=\boldsymbol{R}_{2}\,\boldsymbol{e}_{3}\, sign\left(\Omega_{S}\right)\f]
599 * \f[\frac{\partial\boldsymbol{f}_{3}}{\partial\boldsymbol{y}_{4}}=-\left\langle \boldsymbol{R}_{2}^{\left(0\right)}\,\boldsymbol{e}_{3}\, y_{2}\, sign\left(\Omega_{S}\right)\right\rangle \f]
600 * \f[\frac{\partial\boldsymbol{f}_{4}}{\partial\boldsymbol{y}_{2}}=-\frac{\partial\boldsymbol{f}_{3}}{\partial\boldsymbol{y}_{2}}\f]
601 * \f[\frac{\partial\boldsymbol{f}_{4}}{\partial\boldsymbol{y}_{4}}=-\frac{\partial\boldsymbol{f}_{3}}{\partial\boldsymbol{y}_{4}}\f]
602 * \f[\frac{\partial f_{5}}{\partial\boldsymbol{y}_{3}}=\boldsymbol{e}_{3}^{T}\,\boldsymbol{R}_{2}^{T}\,\left\langle \boldsymbol{\omega}_{ref_{1}}\right\rangle =-\left(\left\langle \boldsymbol{\omega}_{ref_{1}}\right\rangle \,\boldsymbol{R}_{2}\,\boldsymbol{e}_{3}\right)^{T}\f]
603 * \f[\frac{\partial f_{5}}{\partial\boldsymbol{y}_{4}}=-\boldsymbol{e}_{3}^{T}\,\boldsymbol{R}_{2}^{\left(0\right)^{T}}\,\left\langle \boldsymbol{\omega}_{1}-\boldsymbol{\omega}_{2}\right\rangle -\boldsymbol{e}_{3}^{T}\,\boldsymbol{R}_{2}^{T}\,\left\langle \boldsymbol{\omega}_{ref_{2}}\right\rangle =\left[\left\langle \boldsymbol{\omega}_{1}-\boldsymbol{\omega}_{2}\right\rangle \,\boldsymbol{R}_{2}^{\left(0\right)}\,\boldsymbol{e}_{3}+\left\langle \boldsymbol{\omega}_{ref_{2}}\right\rangle \,\boldsymbol{R}_{2}\,\boldsymbol{e}_{3}\right]^{T}\f]
604 * In order to compute the derivatives versus the global degrees of freedom of structural nodes the following rules can be applied:
605 * \f[\frac{\partial \left(\boldsymbol{R}_1\,\boldsymbol{v}_1\right)}{\partial \boldsymbol{g}_1}\approx-\left\langle \boldsymbol{R}^{\left(0\right)}\,\boldsymbol{v} \right\rangle \f]<BR>
606 * \f[\frac{\partial \left(\boldsymbol{R}_1^{T}\,\boldsymbol{v}\right)}{\partial \boldsymbol{g}_1}\approx\boldsymbol{R}_1^{\left(0\right)T}\,\left\langle\boldsymbol{v}\right\rangle\f]
607 * \f[\frac{\partial\boldsymbol{\omega}_1}{\partial\dot{\boldsymbol{g}}_1}\approx\boldsymbol{I}\f]
608 * \f[\frac{\partial\boldsymbol{\omega}_1}{\partial\boldsymbol{g}_1}\approx-\left\langle\boldsymbol{\omega}_{1_{ref}}\right\rangle\f]
609 * \f$\boldsymbol{R}_1\f$ is the corrected rotation matrix of node 1 at the current iteration which can be obtained by GetRCurr().<BR>
610 * \f$\boldsymbol{R}^{(0)}_1\f$ is the predicted rotation matrix of node 1 at the current time step which can be obtained by GetRRef().<BR>
611 * \f$\boldsymbol{g}_1\f$ is the vector of Gibbs Rodriguez rotation parameters of node 1.<BR>
612 * The rotation matrix \f$\boldsymbol{R}_1\f$ is internally computed as follows:<BR>
613 * \f[\boldsymbol{R}_1=\boldsymbol{R}_{1_{\Delta}}\,\boldsymbol{R}^{(0)}_1\f]
614 * \f$\boldsymbol{R}_{1_{\Delta}}\f$ is the incremental rotation matrix which can be computed by means of the Gibbs Rodriguez rotation parameters \f$\boldsymbol{g}_1\f$.<BR>
615 * \f[\boldsymbol{R}_{1_{\Delta}}=\boldsymbol{I}+\frac{4}{4+\boldsymbol{g}_1^T\,\boldsymbol{g}_1}\,\left(\left\langle\boldsymbol{g}\right\rangle+\frac{1}{2}\,\left\langle\boldsymbol{g}_1\right\rangle\,\left\langle\boldsymbol{g}_1\right\rangle\right)\f]
616 * \f$\boldsymbol{\omega}_1\f$ is the corrected angular velocity of node 1 at the current iteration which can be obtained by GetWCurr().<BR>
617 * \f[\left\langle\boldsymbol{\omega}_1\right\rangle=\dot{\boldsymbol{R}}_1\,\boldsymbol{R}_1^T\f]
618 * \f[\boldsymbol{\omega}_1\approx\dot{\boldsymbol{g}}_1+\boldsymbol{R}_{1_{\Delta}}\,\boldsymbol{\omega}_{1_{ref}}\f]
619 * \f$\boldsymbol{\omega}_{1_{ref}}\f$ is the predicted angular velocity of node 1 at the current time step which can be obtained by GetWRef().<BR>
620 * \f[\left\langle\boldsymbol{\omega}_{1_{ref}}\right\rangle=\dot{\boldsymbol{R}}_1^{(0)}\,\boldsymbol{R}_1^{(0)T}\f]
621 * \f$\boldsymbol{v}\f$ is a const vector in the reference frame \f$\boldsymbol{R}_1\f$.<BR>
622 * \f[\boldsymbol{v}=\left(\begin{array}{c} v_{1}\\ v_{2}\\ v_{3}\end{array}\right)\f]
623 * \f$\left\langle \boldsymbol{v}\right\rangle\f$ is a matrix that rearranges the components of \f$\boldsymbol{v}\f$ according to the cross product rule:<BR>
624 * \f$\left\langle\boldsymbol{v}\right\rangle\,\boldsymbol{a}=-\left\langle\boldsymbol{a}\right\rangle\,\boldsymbol{v}=\boldsymbol{v}\times\boldsymbol{a}=-\boldsymbol{a}\times\boldsymbol{v}\f$<BR>
625 * \f[\left\langle \boldsymbol{v} \right\rangle=\left(\begin{array}{ccc} 0 & -v_{3} & v_{2}\\ v_{3} & 0 & -v_{1}\\ -v_{2} & v_{1} & 0\end{array}\right)\f]
626 */
627 VariableSubMatrixHandler&
AssJac(VariableSubMatrixHandler & WorkMat_,doublereal dCoef,const VectorHandler & XCurr,const VectorHandler & XPrimeCurr)628 asynchronous_machine::AssJac(VariableSubMatrixHandler& WorkMat_,
629 doublereal dCoef,
630 const VectorHandler& XCurr,
631 const VectorHandler& XPrimeCurr)
632 {
633 #ifdef DEBUG
634 std::cerr << __FILE__ << ':' << __LINE__ << ':' << __PRETTY_FUNCTION__ << std::endl;
635 #endif
636
637 integer iNumRows = 0;
638 integer iNumCols = 0;
639
640 WorkSpaceDim(&iNumRows, &iNumCols);
641
642 FullSubMatrixHandler& WorkMat = WorkMat_.SetFull();
643
644 WorkMat.ResizeReset(iNumRows, iNumCols);
645
646 const integer iFirstIndex = iGetFirstIndex();
647
648 const integer intTorqueDerivativeColumnIndex = iFirstIndex + 1;
649 const integer intTorqueColumnIndex = iFirstIndex + 2;
650 const integer intOmegaColumnIndex = iFirstIndex + 3;
651
652 const integer intTorqueDerivativeRowIndex = iFirstIndex + 1;
653 const integer intTorqueRowIndex = iFirstIndex + 2;
654 const integer intOmegaRowIndex = iFirstIndex + 3;
655
656 const integer intRotorPositionIndex = m_pRotorNode->iGetFirstPositionIndex();
657 const integer intStatorPositionIndex = m_pStatorNode->iGetFirstPositionIndex();
658
659 const integer intRotorMomentumIndex = m_pRotorNode->iGetFirstMomentumIndex();
660 const integer intStatorMomentumIndex = m_pStatorNode->iGetFirstMomentumIndex();
661
662 /*
663 * M ... motor torque
664 * diff(M,t) ... derivative of the motor torque versus time
665 * g1 ... rotation parameter of the rotor
666 * g2 ... rotation parameter of the stator
667 * R2 ... rotation matrix of the stator node
668 * omega1 ... rotor angular velocity
669 * omega2 ... stator angular velocity
670 *
671 * e3 = ( 0, 0, 1 )^T ... axis of rotation in the reference frame of the stator node
672 *
673 * omega = e3^T * R2^T * ( omega1 - omega2 )
674 *
675 * s = 1 - omega / OmegaS
676 *
677 * | y1 | | diff(M,t) |
678 * | y2 | | M |
679 * y = | y3 | = | g1 |
680 * | y4 | | g2 |
681 * | y5 | | omega |
682 *
683 * | f1 | | diff(y1,t) + ( 2 * sK + sign(OmegaS) * diff(y5,t) / ( s * OmegaS^2 ) ) * y1 * abs(OmegaS) + ( ( sK^2 + s^2 ) * OmegaS^2 + sign(OmegaS) * diff(y5,t) * sK / s ) * y2 - 2 * MK * sK * s * OmegaS^2 |
684 * | f2 | | diff(y2,t) - y1 | |
685 * f = | f3 | = | R2 * e3 * y2 |
686 * | f4 | | -R2 * e3 * y2 |
687 * | f5 | | y5 - e3^T * R2^T * ( omega1 - omega2 ) |
688 *
689 * 1, 2, 3, 6, 9
690 * | df1/dy1, df1/dy2, df1/dy3, df1/dy4, df1/dy5 | 1
691 * | df2/dy1, df2/dy2, df2/dy3, df2/dy4, df2/dy5 | 2
692 * diff(f,y) = | df3/dy1, df3/dy2, df3/dy3, df3/dy4, df3/dy5 | 3
693 * | df4/dy1, df4/dy2, df4/dy3, df4/dy4, df4/dy5 | 6
694 * | df5/dy1, df5/dy2, df5/dy3, df5/dy4, df5/dy5 | 9
695 *
696 * WorkMat = -diff(f,diff(y,t)) - dCoef * diff(f,y)
697 */
698 WorkMat.PutRowIndex(1, intTorqueDerivativeRowIndex);
699 WorkMat.PutColIndex(1, intTorqueDerivativeColumnIndex);
700
701 WorkMat.PutRowIndex(2, intTorqueRowIndex);
702 WorkMat.PutColIndex(2, intTorqueColumnIndex);
703
704 for (int iCnt = 1; iCnt <= 3; ++iCnt) {
705 WorkMat.PutRowIndex(iCnt + 2, intRotorMomentumIndex + iCnt + 3);
706 WorkMat.PutColIndex(iCnt + 2, intRotorPositionIndex + iCnt + 3);
707
708 WorkMat.PutRowIndex(iCnt + 5, intStatorMomentumIndex + iCnt + 3);
709 WorkMat.PutColIndex(iCnt + 5, intStatorPositionIndex + iCnt + 3);
710 }
711
712 WorkMat.PutRowIndex(9, intOmegaRowIndex);
713 WorkMat.PutColIndex(9, intOmegaColumnIndex);
714
715 Vec3 e3 = m_pStatorNode->GetRCurr().GetCol(3); // corrected axis of rotation of the stator node
716 Vec3 e3_0 = m_pStatorNode->GetRRef().GetCol(3); // predicted axis of rotation of the stator node
717 const Vec3& omega1 = m_pRotorNode->GetWCurr(); // corrected angular velocity of the rotor node
718 const Vec3& omega2 = m_pStatorNode->GetWCurr(); // corrected angular velocity of the rotor node
719 const Vec3& omega1_ref = m_pRotorNode->GetWRef(); // predicted angular velocity of the rotor node
720 const Vec3& omega2_ref = m_pStatorNode->GetWRef(); // predicted angular velocity of the stator node
721
722 const doublereal OmegaS = m_OmegaS.dGet(); // synchronous angular velocity
723
724 const doublereal y1 = XCurr(intTorqueDerivativeRowIndex);
725 const doublereal y2 = XCurr(intTorqueRowIndex);
726 const doublereal y5 = XCurr(intOmegaRowIndex);
727 #if 0 // unused
728 const doublereal y1_dot = XPrimeCurr(intTorqueDerivativeRowIndex);
729 const doublereal y2_dot = XPrimeCurr(intTorqueRowIndex);
730 #endif
731 const doublereal y5_dot = XPrimeCurr(intOmegaRowIndex);
732
733 doublereal s = 1 - y5 / OmegaS; // slip of the rotor
734
735 if ( std::abs(s) < sm_SingTol )
736 {
737 silent_cerr("\nasynchronous_machine(" << GetLabel() << "): warning slip rate s = " << s << " is close to machine precision!\n");
738 //FIXME: avoid division by zero
739 //FIXME: results might be inaccurate
740 s = copysign(sm_SingTol, s);
741 }
742
743 doublereal df1_dy1, df1_dy2, df1_dy5;
744 doublereal df1_dy1_dot, df1_dy2_dot, df1_dy5_dot;
745 doublereal df2_dy1, df2_dy2_dot;
746
747 if (IsMotorOn()) {
748 // power supply is turned on
749
750 // df1_dy1 = diff(f1,y1)
751 df1_dy1 = ( 2 * m_sK + copysign(1., OmegaS) * y5_dot / ( s * std::pow(OmegaS,2) ) ) * abs(OmegaS);
752 // df1_dy2 = diff(f1,y2)
753 df1_dy2 = ( std::pow(m_sK,2) + std::pow(s,2) ) * std::pow(OmegaS,2) + copysign(1., OmegaS) * y5_dot * m_sK / s;
754 // df1_dy1_dot = diff(f1,diff(y1,t))
755 df1_dy1_dot = 1.;
756 // df2_dy1 = diff(f2,dy1)
757 df2_dy1 = -1.;
758 // df2_dy2_dot = diff(f2,diff(y2,t))
759 df2_dy2_dot = 1.;
760 // df1_ds = diff(f1,s)
761 const doublereal df1_ds = -y1 * y5_dot / ( std::pow(s,2) * OmegaS ) + ( 2 * s * std::pow(OmegaS,2) - copysign(1., OmegaS) * y5_dot * m_sK / std::pow(s,2) ) * y2 - 2 * m_MK * m_sK * std::pow(OmegaS,2);
762 // df1_dy5 = diff(f1,y5) = diff(f1,s) * diff(s,y5)
763 df1_dy5 = df1_ds * ( -1. / OmegaS );
764 // df1_dy5_dot = diff(f1,diff(y5,t))
765 df1_dy5_dot = y1 / ( s * OmegaS ) + copysign(1., OmegaS) * y2 * m_sK / s;
766
767 } else {
768 // power supply is turned off
769 df1_dy1 = 0.;
770 df1_dy2 = 1.;
771 df1_dy5 = 0.;
772 df1_dy1_dot = 0.;
773 df1_dy2_dot = 0.;
774 df1_dy5_dot = 0.;
775
776 df2_dy1 = 1.;
777 df2_dy2_dot = 0;
778 }
779
780 // df3_dy2 = diff(f3,y2)
781 const Vec3 df3_dy2 = e3 * copysign(1., OmegaS); // df3_dy2 = R2 * e3 * sign(OmegaS)
782 // df4_dy2 = diff(f4,y2)
783 const Vec3 df4_dy2 = -df3_dy2; // df4_dy2 = -R2 * e3
784 // df3_dy4 = diff(f3,y4)
785 const Mat3x3 df3_dy4 = -Mat3x3(MatCross, e3_0 * (y2 * copysign(1., OmegaS)) ); // df3_dy4 = -skew( R2^(0) * e3 * y2 * sign(OmegaS) )
786 // df4_dy4 = diff(f4,y4)
787 const Mat3x3 df4_dy4 = -df3_dy4;
788 // df5_dy5 = diff(f5,y5)
789 const doublereal df5_dy5 = 1.;
790 // df5_dy3_dot_T = transpose(diff(f5,diff(y3,t)))
791 const Vec3 df5_dy3_dot_T = -e3; // diff(y5,diff(y3,t)) = -e3^T * R2^T
792 // df5_dy4_dot_T = transpose(diff(f5,diff(y4,t)))
793 const Vec3 df5_dy4_dot_T = -df5_dy3_dot_T; // diff(y5,diff(y4,t)) = e3^T * R2^T
794 // df5_dy3_T = transpose(diff(y5,y3))
795 const Vec3 df5_dy3_T = -omega1_ref.Cross(e3);
796 // df5_dy4_T = transpose(diff(y5,y4))
797 const Vec3 df5_dy4_T = ( omega1 - omega2 ).Cross( e3_0 ) + omega2_ref.Cross( e3 );
798
799 WorkMat.PutCoef( 1, 1, -df1_dy1_dot - dCoef * df1_dy1 );
800 WorkMat.PutCoef( 1, 2, - dCoef * df1_dy2 );
801 WorkMat.PutCoef( 1, 9, -df1_dy5_dot - dCoef * df1_dy5 );
802 WorkMat.PutCoef( 2, 1, - dCoef * df2_dy1 );
803 WorkMat.PutCoef( 2, 2, -df2_dy2_dot );
804 WorkMat.Put( 3, 2, df3_dy2 * ( -dCoef ) );
805 WorkMat.Put( 3, 6, df3_dy4 * ( -dCoef ) );
806 WorkMat.Put( 6, 2, df4_dy2 * ( -dCoef ) );
807 WorkMat.Put( 6, 6, df4_dy4 * ( -dCoef ) );
808 WorkMat.PutT( 9, 3, -df5_dy3_dot_T + df5_dy3_T * ( -dCoef ) );
809 WorkMat.PutT( 9, 6, -df5_dy4_dot_T + df5_dy4_T * ( -dCoef ) );
810 WorkMat.PutCoef( 9, 9, df5_dy5 * ( -dCoef ) );
811
812 #ifdef DEBUG
813 std::cerr << __FILE__ ":" << __LINE__ << ":" << __PRETTY_FUNCTION__ << std::endl;
814 std::cerr << "s = " << s << std::endl;
815 std::cerr << "WorkMat=" << std::endl
816 << WorkMat << std::endl << std::endl;
817 #endif
818
819 return WorkMat_;
820 }
821
822 /**
823 * @param WorkVec subvector which receives the contribution to the residual \f$\boldsymbol{f}\f$
824 * @param dCoef the derivative coefficient is defined as \f$\Delta\boldsymbol{y} = dCoef \, \Delta\dot{\boldsymbol{y}}\f$
825 * @param XCurr the vector of the global degrees of freedom \f$\boldsymbol{y}\f$ at the current iteration step
826 * @param XPrimeCurr the vector of the derivative of the global degrees of freedom \f$\dot{\boldsymbol{y}}\f$<BR>
827 * \brief This member function implements the equation of an asynchronous machine according to
828 * <A HREF="http://books.google.at/books?id=z6B3lcxDgkkC&printsec=frontcover&dq=Maschinendynamik++%26+dresig&hl=de&ei=SwvETYi8NJSw4Ab_luX3BA&sa=X&oi=book_result&ct=result&resnum=1&ved=0CDIQ6AEwAA#v=onepage&q&f=false">
829 * Maschinendynamik Springer 2009, Dresig, Holzweißig.
830 * </A><BR>
831 *
832 * \f[\ddot{M}+\left(2\, s_{K}+\frac{sign\left(\Omega_{S}\right)\,\ddot{\varphi}}{s\,\Omega_{S}^{2}}\right)\,\dot{M\,}\left|\Omega_{s}\right|+\left[\left(s_{K}^{2}+s^{2}\right)\,\Omega_{S}^{2}+\frac{sign\left(\Omega_{S}\right)\,\ddot{\varphi}\, s_{K}}{s}\right]\, M=2\, M_{K}\, s_{K}\, s\,\Omega_{S}^{2}\f]<BR>
833 * The term \f$sign\left(\Omega_{S}\right)\f$ and the absolute operator in \f$\left|\Omega_{s}\right|\f$ are modifications of the original formula since the formula in the literature does not handle negative synchronous angular velocities \f$\Omega_{S}\f$.
834 * If the synchronous angular velocity \f$\Omega_{S}\f$ has a negative value, the sense of rotation is assumed to be negative.<BR>
835 * \f$M\f$ is motor torque<BR>
836 * \f$M_{K}\f$ is the breakdown torque<BR>
837 * \f$s_{K}\f$ is the breakdown slip<BR>
838 * \f$\Omega_{S}\f$ is the synchronous angular velocity of the machine<BR>
839 * \f$\Omega_{S}=2\,\pi\frac{f}{p}\f$<BR>
840 * \f$f\f$ is the power frequency<BR>
841 * \f$p\f$ is the number of terminal pairs<BR>
842 * \f$s\f$ is the actual slip<BR>
843 * \f$s=1-\frac{\dot{\varphi}}{\Omega_{S}}\f$<BR>
844 * \f$\dot{\varphi}\f$ is the relative angular velocity of the rotor with respect to the stator<BR>
845 * The axis of rotation is assumed to be axis 3 in the reference frame of the stator node.<BR>
846 * \f$\dot{\varphi}=\boldsymbol{e}_{3}^{T}\cdot\boldsymbol{R}_{2}^{T}\cdot\left(\boldsymbol{\omega}_{1}-\boldsymbol{\omega}_{2}\right)\f$<BR>
847 * \f$\boldsymbol{\omega}_{1}\f$ is the angular velocity of the rotor node in the global reference frame which can be obtained by GetWCurr()<BR>
848 * \f$\boldsymbol{\omega}_{2}\f$ is the angular velocity of the stator node in the global reference frame<BR>
849 * \f$\boldsymbol{R}_{2}\f$ is the rotation matrix of the stator node which can be obtained by GetRCurr()<BR>
850 * The subvector of the global degrees of freedom XCurr used by this element is<BR>
851 * \f[\boldsymbol{y}=\left(\begin{array}{c} y_{1}\\ y_{2}\\ \boldsymbol{y}_{3}\\ \boldsymbol{y}_{4}\\ y_{5}\end{array}\right)=\left(\begin{array}{c}\dot{M}\\M\\ \boldsymbol{g}_{1}\\ \boldsymbol{g}_{2}\\ \dot{\varphi}\end{array}\right)\f]<BR>
852 * \f$\boldsymbol{g}_{1}\f$ is the vector of rotation parameters of the rotor node.<BR>
853 * \f$\boldsymbol{g}_{2}\f$ is the vector of the rotation parameters of the stator node.<BR>
854 * The index of \f$\boldsymbol{g}_1\f$ and \f$\boldsymbol{g}_2\f$ can be obtained by iGetFirstMomentumIndex() + 4 ... 6.<BR>
855 * The index of \f$y_1\f$, \f$y_2\f$ and \f$y_5\f$ in the vector of the global degrees of freedom XCurr can be obtained by iGetFirstIndex() + 1 ... 3.<BR>
856 * The subvector of the derivatives of the global degrees of freedom used by this element is<BR>
857 * \f[\dot{\boldsymbol{y}}=\left(\begin{array}{c} \dot{y_{1}}\\ \dot{y_{2}}\\ \dot{\boldsymbol{y}_{3}}\\ \dot{\boldsymbol{y}_{4}}\\ \dot{y_{5}}\end{array}\right)=\left(\begin{array}{c} \ddot{M}\\ \dot{M}\\ \dot{\boldsymbol{g}_{1}}\\ \dot{\boldsymbol{g}_{2}}\\ \ddot{\varphi}\end{array}\right)\f]<BR>
858 * The additional degree of freedom \f$y_{5}=\dot{\varphi}\f$ is needed since MBDyn does not provide the angular acceleration of a node during the nonlinear solution phase.
859 * The value returned by GetWPCurr() is updated only after convergence and can not be used for this reason.
860 * The contribution to the residual of this element is<BR>
861 * \f[\boldsymbol{f}=\left(\begin{array}{c} f_{1}\\ f_{2}\\ \boldsymbol{f}_{3}\\ \boldsymbol{f}_{4}\\ f_{5}\end{array}\right)\f]<BR>
862 * \f[f_{1}=\dot{y}_{1}+\left(2\, s_{K}+\frac{sign\left(\Omega_{S}\right)\,\dot{y}_{5}}{s\,\Omega_{S}^{2}}\right)\, y_{1}\,\left|\Omega_{S}\right|+\left[\left(s_{K}^{2}+s^{2}\right)\,\Omega_{S}^{2}+\frac{sign\left(\Omega_{S}\right)\,\dot{y}_{5}\, s_{K}}{s}\right]\, y_{2}-2\, M_{K}\, s_{K}\, s\,\Omega_{S}^{2}\f]<BR>
863 * \f[f_{2}=\dot{y}_{2}-y_{1}\f]<BR>
864 * \f$\boldsymbol{f}_3\f$ is the torque imposed to the rotor node.<BR>
865 * \f[\boldsymbol{f}_{3}=\boldsymbol{R}_{2}\,\boldsymbol{e}_{3}\, y_{2}\, sign\left(\Omega_{S}\right)\f]<BR>
866 * \f$\boldsymbol{f}_{4}\f$ is the torque imposed to the stator node.<BR>
867 * \f[\boldsymbol{f}_{4}=-\boldsymbol{f}_{3}\f]<BR>
868 * \f[f_{5}=y_{5}-\boldsymbol{e}_{3}^{T}\,\boldsymbol{R}_{2}^{T}\,\left(\boldsymbol{\omega}_{1}-\boldsymbol{\omega}_{2}\right)\f]<BR>
869 */
870
871 SubVectorHandler&
AssRes(SubVectorHandler & WorkVec,doublereal dCoef,const VectorHandler & XCurr,const VectorHandler & XPrimeCurr)872 asynchronous_machine::AssRes(SubVectorHandler& WorkVec,
873 doublereal dCoef,
874 const VectorHandler& XCurr,
875 const VectorHandler& XPrimeCurr)
876 {
877 #ifdef DEBUG
878 std::cerr << __FILE__ << ':' << __LINE__ << ':' << __PRETTY_FUNCTION__ << std::endl;
879 #endif
880 integer iNumRows = 0;
881 integer iNumCols = 0;
882
883 WorkSpaceDim(&iNumRows, &iNumCols);
884
885 WorkVec.ResizeReset(iNumRows);
886
887 const integer iFirstIndex = iGetFirstIndex();
888
889 const integer intTorqueDerivativeRowIndex = iFirstIndex + 1;
890 const integer intTorqueRowIndex = iFirstIndex + 2;
891 const integer intOmegaRowIndex = iFirstIndex + 3;
892
893 const integer intRotorMomentumIndex = m_pRotorNode->iGetFirstMomentumIndex();
894 const integer intStatorMomentumIndex = m_pStatorNode->iGetFirstMomentumIndex();
895
896 WorkVec.PutRowIndex(1, intTorqueDerivativeRowIndex);
897 WorkVec.PutRowIndex(2, intTorqueRowIndex);
898
899 for (int iCnt = 1; iCnt <= 3; ++iCnt) {
900 WorkVec.PutRowIndex(iCnt + 2, intRotorMomentumIndex + iCnt + 3);
901 WorkVec.PutRowIndex(iCnt + 5, intStatorMomentumIndex + iCnt + 3);
902 }
903
904 WorkVec.PutRowIndex(9, intOmegaRowIndex);
905
906 Vec3 e3 = m_pStatorNode->GetRCurr().GetCol(3);
907 const Vec3& omega1 = m_pRotorNode->GetWCurr();
908 const Vec3& omega2 = m_pStatorNode->GetWCurr();
909
910 const doublereal OmegaS = m_OmegaS.dGet();
911
912 const doublereal y1 = XCurr(intTorqueDerivativeRowIndex); // y1 = diff(M,t)
913 const doublereal y2 = XCurr(intTorqueRowIndex); // y2 = M
914 const doublereal y5 = XCurr(intOmegaRowIndex); // y5 = omega
915 const doublereal y1_dot = XPrimeCurr(intTorqueDerivativeRowIndex); // diff(y1,t) = diff(M,t,2)
916 const doublereal y2_dot = XPrimeCurr(intTorqueRowIndex); // diff(y2,t) = diff(M,t)
917 const doublereal y5_dot = XPrimeCurr(intOmegaRowIndex); // diff(y5,t) = diff(omega,t)
918
919 doublereal s = 1 - y5 / OmegaS;
920
921 if ( std::abs(s) < sm_SingTol )
922 {
923 silent_cerr("\nasynchronous_machine(" << GetLabel() << "): warning slip rate s = " << s << " is close to machine precision!\n");
924 //FIXME: avoid division by zero
925 //FIXME: results might be inaccurate
926 s = copysign(sm_SingTol, s);
927 }
928
929 doublereal f1, f2;
930
931 if (IsMotorOn()) {
932 // power supply is switched on
933 f1 = y1_dot + ( 2 * m_sK + copysign(1., OmegaS) * y5_dot / ( s * std::pow(OmegaS,2) ) ) * y1 * abs(OmegaS)
934 + ( ( std::pow(m_sK,2) + std::pow(s,2) ) * std::pow(OmegaS,2) + copysign(1., OmegaS) * y5_dot * m_sK / s ) * y2 - 2 * m_MK * m_sK * s * std::pow(OmegaS,2);
935 f2 = y2_dot - y1;
936
937 } else {
938 // power supply is switched off: torque must be zero
939 f1 = y2;
940 f2 = y1;
941 }
942
943 const Vec3 f3 = e3 * (y2 * copysign(1., OmegaS));
944 // const Vec3 f4 = -f3;
945
946 const doublereal f5 = y5 - e3.Dot( omega1 - omega2 );
947
948 WorkVec.PutCoef( 1, f1 );
949 WorkVec.PutCoef( 2, f2 );
950 WorkVec.Put( 3, f3 );
951 WorkVec.Sub( 6, f3 );
952 WorkVec.PutCoef( 9, f5 );
953
954 #ifdef DEBUG
955 std::cerr
956 << __FILE__ ":" << __LINE__ << ":" << __PRETTY_FUNCTION__ << std::endl
957 << "s = " << s << std::endl
958 << "y1 = " << y1 << std::endl
959 << "y1_dot = " << y1_dot << std::endl
960 << "y2 = " << y2 << std::endl
961 << "y2_dot = " << y2_dot << std::endl
962 << "f1 = " << f1 << std::endl
963 << "f2 = " << f2 << std::endl
964 << "f3 = " << f3 << std::endl
965 << "f4 = " << -f3 << std::endl
966 << "f5 = " << f5 << std::endl
967 << "WorkVec=" << std::endl
968 << WorkVec << std::endl << std::endl;
969 #endif
970
971 return WorkVec;
972 }
973 /**
974 * @return returns the number of connected nodes.
975 * \brief This member function is called if the statement "print: node connection;" is specified in the input file.
976 * */
977 int
iGetNumConnectedNodes(void) const978 asynchronous_machine::iGetNumConnectedNodes(void) const
979 {
980 return 2;
981 }
982
983 /**
984 * @param connectedNodes vector which receives pointers to the connected nodes
985 * \brief This member function is called if the statement "print: node connection;" is specified in the input file.
986 */
987 void
GetConnectedNodes(std::vector<const Node * > & connectedNodes) const988 asynchronous_machine::GetConnectedNodes(std::vector<const Node *>& connectedNodes) const
989 {
990 connectedNodes.resize(iGetNumConnectedNodes());
991 connectedNodes[0] = m_pRotorNode;
992 connectedNodes[1] = m_pStatorNode;
993 }
994
995 /**
996 * @param X vector of global degrees of freedom \f$\boldsymbol{y}\f$ that receives the initial values provided by this element.
997 * @param XP vector of the derivative of the global degrees of freedom \f$\dot{\boldsymbol{y}}\f$
998 * \brief This member function is called before the integration starts in order to set the initial values for the private degrees of freedom.
999 *
1000 * In our case the initial values for \f$M\f$, \f$\dot{M}\f$ and \f$\dot{\varphi}\f$ are set in this routine to the values specified in the input file.
1001 */
1002 void
SetValue(DataManager * pDM,VectorHandler & X,VectorHandler & XP,SimulationEntity::Hints * ph)1003 asynchronous_machine::SetValue(DataManager *pDM,
1004 VectorHandler& X, VectorHandler& XP,
1005 SimulationEntity::Hints *ph)
1006 {
1007 const integer intFirstIndex = iGetFirstIndex();
1008
1009 // 1 2 3
1010 X.Put( intFirstIndex + 1, Vec3(m_dM_dt, m_M, m_omega) );
1011 XP.Put(intFirstIndex + 1, Vec3( 0., m_dM_dt, 0.) );
1012 }
1013
1014 /**
1015 * @param out stream of the restart file
1016 * \brief This member function is called if "make restart file;" is specified in the input file.
1017 *
1018 * It should reproduce the syntax in the input file used to create this element.
1019 */
1020 std::ostream&
Restart(std::ostream & out) const1021 asynchronous_machine::Restart(std::ostream& out) const
1022 {
1023 // FIXME: mbdyn crashes when "make restart file;" is specified in the input file before this member function is invoked!
1024 out << "asynchronous_machine, "
1025 "rotor, " << m_pRotorNode->GetLabel() << ", "
1026 "stator, " << m_pStatorNode->GetLabel() << ", "
1027 "MK, " << m_MK << ", "
1028 "sK, " << m_sK << ", "
1029 "OmegaS, ";
1030 m_OmegaS.pGetDriveCaller()->Restart(out) << ", "
1031 "motor on, ";
1032 m_MotorOn.pGetDriveCaller()->Restart(out) << ", "
1033 "M0, " << m_M * copysign(1., m_OmegaS.dGet()) << ", "
1034 "MP0, " << m_dM_dt * copysign(1., m_OmegaS.dGet()) << ";" << std::endl;
1035
1036 return out;
1037 }
1038
1039 /**
1040 * \brief This member function must be implemented only if the initial assembly feature is requested.
1041 *
1042 * The initial assembly phase is needed only if initial values are provided which are not consistent with the algebraic constraints.
1043 * Since this element does not use algebraic constraints we do not have to implement
1044 * iGetInitialNumDof(), InitialWorkSpaceDim(), InitialAssJac(), InitialAssRes() and SetInitialValue().
1045 */
1046 unsigned int
iGetInitialNumDof(void) const1047 asynchronous_machine::iGetInitialNumDof(void) const
1048 {
1049 return 0;
1050 }
1051
1052 /**
1053 * See iGetInitialNumDof().
1054 */
1055 void
InitialWorkSpaceDim(integer * piNumRows,integer * piNumCols) const1056 asynchronous_machine::InitialWorkSpaceDim(
1057 integer* piNumRows,
1058 integer* piNumCols) const
1059 {
1060 *piNumRows = 0;
1061 *piNumCols = 0;
1062 }
1063
1064 /**
1065 * See iGetInitialNumDof().
1066 */
1067 VariableSubMatrixHandler&
InitialAssJac(VariableSubMatrixHandler & WorkMat,const VectorHandler & XCurr)1068 asynchronous_machine::InitialAssJac(
1069 VariableSubMatrixHandler& WorkMat,
1070 const VectorHandler& XCurr)
1071 {
1072 WorkMat.SetNullMatrix();
1073
1074 return WorkMat;
1075 }
1076
1077 /**
1078 * See iGetInitialNumDof().
1079 */
1080 SubVectorHandler&
InitialAssRes(SubVectorHandler & WorkVec,const VectorHandler & XCurr)1081 asynchronous_machine::InitialAssRes(
1082 SubVectorHandler& WorkVec,
1083 const VectorHandler& XCurr)
1084 {
1085 WorkVec.ResizeReset(0);
1086
1087 return WorkVec;
1088 }
1089
1090 bool
asynchronous_machine_set(void)1091 asynchronous_machine_set(void)
1092 {
1093 #ifdef DEBUG
1094 std::cerr << __FILE__ <<":"<< __LINE__ << ":"<< __PRETTY_FUNCTION__ << std::endl;
1095 #endif
1096
1097 UserDefinedElemRead *rf = new UDERead<asynchronous_machine>;
1098
1099 if (!SetUDE("asynchronous_machine", rf)) {
1100 delete rf;
1101 return false;
1102 }
1103
1104 return true;
1105 }
1106
1107 #ifndef STATIC_MODULES
1108
1109 extern "C"
1110 {
1111
1112 /**
1113 * \brief This function registers our user defined element for the math parser.
1114 *
1115 * It is called when the "module load" statement appears in the input file.
1116 */
1117 int
module_init(const char * module_name,void * pdm,void * php)1118 module_init(const char *module_name, void *pdm, void *php)
1119 {
1120 if (!asynchronous_machine_set()) {
1121 silent_cerr("asynchronous_machine: "
1122 "module_init(" << module_name << ") "
1123 "failed" << std::endl);
1124 return -1;
1125 }
1126
1127 return 0;
1128 }
1129
1130 } // extern "C"
1131
1132 #endif // ! STATIC_MODULES
1133
1134