DPsim
Loading...
Searching...
No Matches
DP_Ph1_Switch.cpp
Go to the documentation of this file.
1/* Copyright 2017-2021 Institute for Automation of Complex Power Systems,
2 * EONERC, RWTH Aachen University
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7 *********************************************************************************/
8
9#include <algorithm>
10#include <cmath>
11#include <stdexcept>
12
14
15using namespace CPS;
16
18 : MNASimPowerComp<Complex>(uid, name, false, true, logLevel),
20 mOpeningRequested(mAttributes->create<Bool>("opening_requested")),
21 mPoleClosed(mAttributes->create<Bool>("pole_closed")),
22 mInstantCurrent(mAttributes->create<Real>("i_instantaneous")),
23 mZeroCrossingTime(mAttributes->create<Real>("zero_crossing_time")),
25 mAttributes->create<Bool>("exponential_transition_active")),
27 mAttributes->create<Bool>("exponential_transition_closing")),
28 mExponentialProgress(mAttributes->create<Real>("exponential_progress")),
30 mAttributes->create<Real>("exponential_transition_start_time")),
32 mAttributes->create<Real>("exponential_transition_end_time")),
33 mEffectiveResistance(mAttributes->create<Real>("effective_resistance")) {
35 **mIntfVoltage = MatrixComp::Zero(1, 1);
36 **mIntfCurrent = MatrixComp::Zero(1, 1);
37
38 **mOpeningRequested = false;
39 **mPoleClosed = false;
40
41 **mInstantCurrent = 0.0;
42
43 resetZeroCrossingTime();
44
51}
52
54 auto copy = Switch::make(name, mLogLevel);
55 copy->setParameters(**mOpenResistance, **mClosedResistance, **mIsClosed);
56 copy->setSwitchingMode(mSwitchingMode);
57 copy->setZeroCrossingTolerance(mZeroCrossingTolerance);
58 copy->setExponentialSwitchingTime(mExponentialSwitchingTime);
59 return copy;
60}
61
63 mSwitchingMode = mode;
64
66 **mOpeningRequested = false;
67 resetZeroCrossingTime();
68
69 mPreviousCurrentValid = false;
70 mResetZeroCrossingHistory = false;
71
72 resetExponentialTransition();
73 synchronizeEffectiveResistance(**mIsClosed);
74}
75
77 if (tolerance < 0.0) {
78 throw std::invalid_argument(
79 "DP::Ph1::Switch zero-crossing tolerance must be non-negative.");
80 }
81
82 mZeroCrossingTolerance = tolerance;
83}
84
86 if (switchingTime <= 0.0) {
87 throw std::invalid_argument(
88 "DP::Ph1::Switch exponential switching time must be positive.");
89 }
90
91 mExponentialSwitchingTime = switchingTime;
92}
93
96
97 // An opening command during a closing ramp abandons that ramp. The pole is
98 // still open at that point, so the switch simply stays open.
100 resetExponentialTransition();
101 **mPoleClosed = false;
102 synchronizeEffectiveResistance(false);
103 **mOpeningRequested = false;
104
105 SPDLOG_LOGGER_INFO(mSLog,
106 "DP opening command received during an exponential "
107 "closing transition. Transition abandoned.");
108 return;
109 }
110
111 if (mSwitchingMode == SwitchingMode::Ideal) {
112 **mOpeningRequested = false;
113 **mPoleClosed = false;
114 resetZeroCrossingTime();
115 resetExponentialTransition();
116 synchronizeEffectiveResistance(false);
117 return;
118 }
119
120 if (!**mPoleClosed) {
121 **mOpeningRequested = false;
122 return;
123 }
124
125 if (mSwitchingMode == SwitchingMode::CurrentZero) {
126 **mOpeningRequested = true;
127 resetZeroCrossingTime();
128 resetExponentialTransition();
129 mResetZeroCrossingHistory = true;
130
131 SPDLOG_LOGGER_INFO(mSLog,
132 "DP current-zero opening command received. "
133 "Waiting for the reconstructed physical current zero.");
134 return;
135 }
136
137 validateExponentialResistanceParameters();
138 **mOpeningRequested = true;
139 resetZeroCrossingTime();
140 resetExponentialTransition();
142 synchronizeEffectiveResistance(true);
143 **mPoleClosed = true;
144
145 SPDLOG_LOGGER_INFO(mSLog,
146 "DP exponential ZCS-emulation opening command received. "
147 "Switching duration={:.6e}s.",
148 mExponentialSwitchingTime);
149}
150
153
154 **mOpeningRequested = false;
155 resetZeroCrossingTime();
156 mResetZeroCrossingHistory = false;
157
158 if (mSwitchingMode != SwitchingMode::ExponentialZCSEmulation) {
159 **mPoleClosed = true;
160 resetExponentialTransition();
161 synchronizeEffectiveResistance(true);
162
163 SPDLOG_LOGGER_INFO(mSLog, "DP switch closing command: pole closed.");
164 return;
165 }
166
168 resetExponentialTransition();
169 synchronizeEffectiveResistance(true);
170 return;
171 }
172
173 // Energising a branch is the mirror image of interrupting it: the same
174 // resistance path is traversed from R_open towards R_closed. The DP envelope
175 // cannot represent the voltage step across a capacitance any more than it can
176 // represent an interrupted inductive current, so closing is ramped as well.
177 validateExponentialResistanceParameters();
178 resetExponentialTransition();
181 **mExponentialProgress = 1.0;
182 synchronizeEffectiveResistance(false);
183 **mPoleClosed = false;
184
185 SPDLOG_LOGGER_INFO(mSLog,
186 "DP exponential ZCS-emulation closing command received. "
187 "Switching duration={:.6e}s.",
188 mExponentialSwitchingTime);
189}
190
192
193 Real impedance = (**mIsClosed) ? **mClosedResistance : **mOpenResistance;
194 (**mIntfVoltage)(0, 0) = initialSingleVoltage(1) - initialSingleVoltage(0);
195 (**mIntfCurrent)(0, 0) = (**mIntfVoltage)(0, 0) / impedance;
196
197 // Power-flow initialization defines the initial physical breaker state.
199 **mOpeningRequested = false;
200 resetZeroCrossingTime();
201 resetExponentialTransition();
202 synchronizeEffectiveResistance(**mIsClosed);
203
204 SPDLOG_LOGGER_INFO(mSLog,
205 "\n--- Initialization from powerflow ---"
206 "\nVoltage across: {:s}"
207 "\nCurrent: {:s}"
208 "\nTerminal 0 voltage: {:s}"
209 "\nTerminal 1 voltage: {:s}"
210 "\nSwitching mode: {:s}"
211 "\nInitial breaker state: {:s}"
212 "\n--- Initialization from powerflow finished ---",
217 mSwitchingMode == SwitchingMode::Ideal
218 ? "Ideal"
219 : (mSwitchingMode == SwitchingMode::CurrentZero
220 ? "CurrentZero"
221 : "ExponentialZCSEmulation"),
222 **mIsClosed ? "closed" : "open");
223}
224
226 Attribute<Matrix>::Ptr leftVector) {
228 mTimeStep = timeStep;
229 **mRightVector = Matrix::Zero(0, 0);
230
231 // omega is the DP reference-frame angular frequency supplied by the solver.
232 mShiftOmega = omega;
233
234 // Final safeguard in case setSwitchingMode() was called before setParameters().
236 synchronizeEffectiveResistance(**mIsClosed);
237 resetExponentialTransition();
238
239 mPoleClosedPrev = **mPoleClosed;
241
242 **mInstantCurrent = reconstructInstantaneousCurrent(0.0);
243
244 mPreviousInstantaneousCurrent = **mInstantCurrent;
245 mEffectiveResistancePrev = **mEffectiveResistance;
246
247 mPreviousCurrentTime = 0.0;
248 mPreviousCurrentValid = true;
249 mResetZeroCrossingHistory = false;
250}
251
259
261 Bool closed, SparseMatrixRow &systemMatrix, Int freqIdx) {
262 // Conventional precomputed two-state path. The CurrentZero and exponential
263 // modes bypass this by returning supportsPrecomputedSystemMatrices() == false.
264 Complex conductance = (closed) ? Complex(1. / **mClosedResistance, 0)
265 : Complex(1. / **mOpenResistance, 0);
266
267 MNAStampUtils::stampAdmittance(conductance, systemMatrix, matrixNodeIndex(0),
270}
271
273
275 if (mSwitchingMode == SwitchingMode::Ideal)
276 return **mIsClosed;
277
278 return **mPoleClosed;
279}
280
284
286 // Voltage across component is defined as V1 - V0
287 (**mIntfVoltage)(0, 0) = 0;
288 if (terminalNotGrounded(1))
289 (**mIntfVoltage)(0, 0) =
291 if (terminalNotGrounded(0))
292 (**mIntfVoltage)(0, 0) =
293 (**mIntfVoltage)(0, 0) -
295}
296
298 (**mIntfCurrent)(0, 0) = currentConductance() * (**mIntfVoltage)(0, 0);
299}
300
302 AttributeBase::List &prevStepDependencies,
303 AttributeBase::List &attributeDependencies,
304 AttributeBase::List &modifiedAttributes,
305 Attribute<Matrix>::Ptr &leftVector) {
306
307 attributeDependencies.push_back(leftVector);
308 modifiedAttributes.push_back(mIntfVoltage);
309 modifiedAttributes.push_back(mIntfCurrent);
310
311 modifiedAttributes.push_back(mOpeningRequested);
312 modifiedAttributes.push_back(mPoleClosed);
313
314 modifiedAttributes.push_back(mInstantCurrent);
315 modifiedAttributes.push_back(mZeroCrossingTime);
316
317 modifiedAttributes.push_back(mExponentialTransitionActive);
318 modifiedAttributes.push_back(mExponentialTransitionClosing);
319 modifiedAttributes.push_back(mExponentialProgress);
320 modifiedAttributes.push_back(mExponentialTransitionStartTime);
321 modifiedAttributes.push_back(mExponentialTransitionEndTime);
322 modifiedAttributes.push_back(mEffectiveResistance);
323}
324
326 Attribute<Matrix>::Ptr &leftVector) {
327 mnaCompUpdateVoltage(**leftVector);
328 mnaCompUpdateCurrent(**leftVector);
329
330 **mInstantCurrent = reconstructInstantaneousCurrent(time);
331
332 if (mSwitchingMode == SwitchingMode::CurrentZero) {
333 updateZeroCrossingState(time);
334 } else if (mSwitchingMode == SwitchingMode::ExponentialZCSEmulation) {
335 updateExponentialTransition(time);
336 }
337}
338
340 if (mSwitchingMode == SwitchingMode::Ideal) {
341 // Check if state of switch changed
342 if (mIsClosedPrev != **mIsClosed) {
344 return true; //recompute system matrix
345 }
346
347 return false; // do not recompute system matrix
348 }
349
350 if (mSwitchingMode == SwitchingMode::ExponentialZCSEmulation) {
351 if (**mEffectiveResistance != mEffectiveResistancePrev) {
352 mEffectiveResistancePrev = **mEffectiveResistance;
353 return true;
354 }
355
356 return false;
357 }
358
359 if (**mPoleClosed != mPoleClosedPrev) {
360 mPoleClosedPrev = **mPoleClosed;
361 return true;
362 }
363
364 return false;
365}
366
367void DP::Ph1::Switch::setPoleClosed(Bool closed) {
368 **mPoleClosed = closed;
369
370 // Keep the resistance diagnostics synchronized with the discrete
371 // CurrentZero pole state. Exponential mode manages R(t) independently.
372 if (mSwitchingMode == SwitchingMode::CurrentZero)
373 synchronizeEffectiveResistance(closed);
374}
375
376void DP::Ph1::Switch::resetZeroCrossingTime() { **mZeroCrossingTime = -1.0; }
377
378void DP::Ph1::Switch::synchronizeEffectiveResistance(Bool closed) {
379 **mEffectiveResistance = closed ? **mClosedResistance : **mOpenResistance;
380}
381
382void DP::Ph1::Switch::resetExponentialTransition() {
383 **mExponentialTransitionActive = false;
384 **mExponentialTransitionClosing = false;
385 **mExponentialProgress = 0.0;
386 **mExponentialTransitionStartTime = -1.0;
387 **mExponentialTransitionEndTime = -1.0;
388 mExponentialTransitionStarted = false;
389}
390
391void DP::Ph1::Switch::validateExponentialResistanceParameters() const {
392 if (mExponentialSwitchingTime <= 0.0) {
393 throw std::invalid_argument(
394 "DP::Ph1::Switch exponential switching time must be positive.");
395 }
396
397 if (**mClosedResistance <= 0.0 || **mOpenResistance <= 0.0) {
398 throw std::invalid_argument("DP::Ph1::Switch exponential mode requires "
399 "positive open and closed resistances.");
400 }
401}
402
403Real DP::Ph1::Switch::exponentialResistance(Real alpha) const {
404 const Real rClosed = **mClosedResistance;
405 const Real rOpen = **mOpenResistance;
406 const Real boundedAlpha = std::max(0.0, std::min(1.0, alpha));
407
408 return std::exp(std::log(rClosed) +
409 boundedAlpha * (std::log(rOpen) - std::log(rClosed)));
410}
411
412void DP::Ph1::Switch::updateExponentialTransition(Real time) {
413 if (!**mExponentialTransitionActive)
414 return;
415
416 if (!mExponentialTransitionStarted) {
417 mExponentialTransitionStarted = true;
418 **mExponentialTransitionStartTime = time;
419 **mExponentialTransitionEndTime = time + mExponentialSwitchingTime;
420 }
421
422 const Real targetTime = time + mTimeStep;
423 const Real elapsed = (targetTime - **mExponentialTransitionStartTime) /
424 mExponentialSwitchingTime;
425 const Real boundedElapsed = std::max(0.0, std::min(1.0, elapsed));
426
427 const Bool closing = **mExponentialTransitionClosing;
428
429 // alpha is the position on the resistance path: 0 at R_closed, 1 at R_open.
430 const Real alpha = closing ? 1.0 - boundedElapsed : boundedElapsed;
431
432 **mExponentialProgress = alpha;
433 **mEffectiveResistance = exponentialResistance(alpha);
434
435 if (boundedElapsed >= 1.0) {
436 synchronizeEffectiveResistance(closing);
437 **mPoleClosed = closing;
438 **mOpeningRequested = false;
439 **mExponentialTransitionActive = false;
440 **mExponentialTransitionClosing = false;
441
442 SPDLOG_LOGGER_INFO(
443 mSLog,
444 "DP exponential ZCS-emulation {:s} completed: start={:.9f}s, "
445 "end={:.9f}s, duration={:.6e}s.",
446 closing ? "closing" : "opening", **mExponentialTransitionStartTime,
447 **mExponentialTransitionEndTime, mExponentialSwitchingTime);
448 }
449}
450
451Complex DP::Ph1::Switch::currentConductance() const {
452 if (mSwitchingMode == SwitchingMode::Ideal) {
453 return (**mIsClosed) ? Complex(1. / **mClosedResistance, 0)
454 : Complex(1. / **mOpenResistance, 0);
455 }
456
457 if (mSwitchingMode == SwitchingMode::CurrentZero) {
458 return (**mPoleClosed) ? Complex(1. / **mClosedResistance, 0)
459 : Complex(1. / **mOpenResistance, 0);
460 }
461
462 return Complex(1. / **mEffectiveResistance, 0);
463}
464
465Real DP::Ph1::Switch::reconstructInstantaneousCurrent(Real time) const {
466 // Restore the reference-frequency carrier removed by the DP formulation.
467 // DP::Ph1 envelopes keep the power-flow amplitude scaling, so no RMS-to-peak
468 // conversion is applied here; the zero crossing instant does not depend on it.
469 const Complex carrier = std::polar<Real>(1.0, mShiftOmega * time);
470
471 return std::real((**mIntfCurrent)(0, 0) * carrier);
472}
473
474Bool DP::Ph1::Switch::currentCrossedZero(Real previousCurrent,
475 Real current) const {
476 if (std::abs(current) <= mZeroCrossingTolerance)
477 return true;
478
479 return (previousCurrent > mZeroCrossingTolerance &&
480 current < -mZeroCrossingTolerance) ||
481 (previousCurrent < -mZeroCrossingTolerance &&
482 current > mZeroCrossingTolerance);
483}
484
485Real DP::Ph1::Switch::interpolateZeroCrossingTime(Real previousCurrent,
486 Real current,
487 Real previousTime,
488 Real currentTime) const {
489 const Real denominator = std::abs(previousCurrent) + std::abs(current);
490
491 if (denominator <= mZeroCrossingTolerance)
492 return currentTime;
493
494 const Real fraction = std::abs(previousCurrent) / denominator;
495
496 return previousTime + fraction * (currentTime - previousTime);
497}
498
499void DP::Ph1::Switch::updateZeroCrossingState(Real time) {
500 const Real current = **mInstantCurrent;
501
502 // Keep a current history while no opening command is active.
503 if (!**mOpeningRequested) {
504 mPreviousInstantaneousCurrent = current;
505 mPreviousCurrentTime = time;
506 mPreviousCurrentValid = true;
507 return;
508 }
509
510 // First post-command sample: do not compare against the pre-command sample.
511 if (mResetZeroCrossingHistory || !mPreviousCurrentValid) {
512 if (**mPoleClosed && std::abs(current) <= mZeroCrossingTolerance) {
513 setPoleClosed(false);
514 **mZeroCrossingTime = time;
515
516 SPDLOG_LOGGER_INFO(mSLog,
517 "DP pole opened at sampled physical current zero: "
518 "t={:.9f}s, i={:.6e}A",
519 time, current);
520 }
521
522 mPreviousInstantaneousCurrent = current;
523 mPreviousCurrentTime = time;
524 mPreviousCurrentValid = true;
525 mResetZeroCrossingHistory = false;
526
527 if (!**mPoleClosed)
528 **mOpeningRequested = false;
529
530 return;
531 }
532
533 if (**mPoleClosed &&
534 currentCrossedZero(mPreviousInstantaneousCurrent, current)) {
535 const Real zeroTime = interpolateZeroCrossingTime(
536 mPreviousInstantaneousCurrent, current, mPreviousCurrentTime, time);
537
538 setPoleClosed(false);
539 **mZeroCrossingTime = zeroTime;
540
541 SPDLOG_LOGGER_INFO(mSLog,
542 "DP physical current zero detected: "
543 "t_z={:.9f}s, i_prev={:.6e}A, i={:.6e}A. Pole opened.",
544 zeroTime, mPreviousInstantaneousCurrent, current);
545 }
546
547 mPreviousInstantaneousCurrent = current;
548 mPreviousCurrentTime = time;
549 mPreviousCurrentValid = true;
550
551 if (!**mPoleClosed) {
552 **mOpeningRequested = false;
553
554 SPDLOG_LOGGER_INFO(mSLog, "DP current-zero interruption completed: "
555 "the breaker pole is open.");
556 }
557}
std::vector< Ptr > List
Definition Attribute.h:123
AttributePointer< Attribute< T > > Ptr
Definition Attribute.h:249
const Attribute< Real >::Ptr mClosedResistance
Resistance if switch is closed [ohm].
const Attribute< Real >::Ptr mOpenResistance
Resistance if switch is open [ohm].
virtual void closeSwitch()
virtual void openSwitch()
const Attribute< Bool >::Ptr mIsClosed
Defines if Switch is open or closed.
SimPowerComp< Complex >::Ptr clone(String name) override
Returns a modified copy of the component with the given suffix added to the name and without.
const Attribute< Real >::Ptr mInstantCurrent
Reconstructed physical instantaneous current [A].
void setSwitchingMode(SwitchingMode mode)
void initializeFromNodesAndTerminals(Real frequency) override
Initializes component from power flow data.
const Attribute< Bool >::Ptr mPoleClosed
void closeSwitch() override
Bool mnaIsClosed() override
Check if switch is closed.
void setExponentialSwitchingTime(Real switchingTime)
void mnaCompApplySystemMatrixStamp(SparseMatrixRow &systemMatrix) override
Stamps system matrix.
void mnaCompApplyRightSideVectorStamp(Matrix &rightVector) override
Stamps right side (source) vector.
Bool hasParameterChanged() override
Returns true if one of the element paramters has changed.
void mnaCompAddPostStepDependencies(AttributeBase::List &prevStepDependencies, AttributeBase::List &attributeDependencies, AttributeBase::List &modifiedAttributes, Attribute< Matrix >::Ptr &leftVector) override
Add MNA post step dependencies.
const Attribute< Real >::Ptr mEffectiveResistance
Effective resistance currently stamped into the MNA matrix [ohm].
Switch(String uid, String name, Logger::Level loglevel=Logger::Level::off)
Defines UID, name, component parameters and logging level.
const Attribute< Real >::Ptr mZeroCrossingTime
const Attribute< Bool >::Ptr mExponentialTransitionActive
Exponential-transition diagnostics.
const Attribute< Bool >::Ptr mOpeningRequested
void mnaCompUpdateCurrent(const Matrix &leftVector) override
Update interface current from MNA system result.
const Attribute< Bool >::Ptr mExponentialTransitionClosing
True while the active exponential transition is a closing transition.
void setZeroCrossingTolerance(Real tolerance)
const Attribute< Real >::Ptr mExponentialTransitionEndTime
void mnaCompApplySwitchSystemMatrixStamp(Bool closed, SparseMatrixRow &systemMatrix, Int freqIdx) override
Stamps system matrix considering the defined switch position.
void mnaCompInitialize(Real omega, Real timeStep, Attribute< Matrix >::Ptr leftVector) override
void mnaCompPostStep(Real time, Int timeStepCount, Attribute< Matrix >::Ptr &leftVector) override
MNA post step operations.
void openSwitch() override
void mnaCompUpdateVoltage(const Matrix &leftVector) override
Update interface voltage from MNA system result.
const Attribute< Real >::Ptr mExponentialProgress
const Attribute< Real >::Ptr mExponentialTransitionStartTime
Bool supportsPrecomputedSystemMatrices() const override
String uid()
Returns unique id.
AttributeList::Ptr mAttributes
Attribute List.
spdlog::level::level_enum Level
Definition Logger.h:33
static String phasorToString(const Complex &num)
Definition Logger.cpp:57
MNASimPowerComp(String uid, String name, Bool hasPreStep, Bool hasPostStep, Logger::Level logLevel)
Attribute< Matrix >::Ptr mRightVector
static void stampAdmittance(Complex admittance, SparseMatrixRow &mat, UInt node1Index, UInt node2Index, Bool isTerminal1NotGrounded, Bool isTerminal2NotGrounded, const Logger::Log &mSLog, Int maxFreq=1, Int freqIdx=0)
static Complex complexFromVectorElement(const Matrix &mat, Matrix::Index row, Int maxFreq=1, Int freqIdx=0)
Definition MathUtils.cpp:94
UInt matrixNodeIndex(UInt nodeIndex)
const Attribute< MatrixVar< Complex > >::Ptr mIntfCurrent
const Attribute< MatrixVar< Complex > >::Ptr mIntfVoltage
std::shared_ptr< SimPowerComp< VarType > > Ptr
Bool terminalNotGrounded(UInt index)
Complex initialSingleVoltage(UInt index)
Logger::Level mLogLevel
Component logger control for internal variables.
Logger::Log mSLog
Component logger.
static std::shared_ptr< Switch > make(Args &&...args)
Definition PtrFactory.h:19
Eigen::Matrix< Real, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > Matrix
Dense matrix for real numbers.
Definition Definitions.h:81
std::string String
Definition Definitions.h:65
double Real
Definition Definitions.h:62
int Int
Definition Definitions.h:61
std::complex< Real > Complex
Definition Definitions.h:63
bool Bool
Definition Definitions.h:64
Eigen::SparseMatrix< Real, Eigen::RowMajor > SparseMatrixRow
Sparse matrix for real numbers (row major).
Definition Definitions.h:74