53 resetZeroCrossingTimes();
68 copy->setSwitchingMode(mSwitchingMode);
69 copy->setZeroCrossingTolerance(mZeroCrossingTolerance);
70 copy->setExponentialSwitchingTime(mExponentialSwitchingTime);
75 mSwitchingMode = mode;
80 resetZeroCrossingTimes();
82 mPreviousCurrentValid =
false;
83 mResetZeroCrossingHistory =
false;
85 resetExponentialTransition();
86 synchronizeEffectiveResistance(**
mIsClosed);
90 if (tolerance < 0.0) {
91 throw std::invalid_argument(
92 "EMT::Ph3::Switch zero-crossing tolerance must be non-negative.");
95 mZeroCrossingTolerance = tolerance;
99 if (switchingTime <= 0.0) {
100 throw std::invalid_argument(
101 "EMT::Ph3::Switch exponential switching time must be positive.");
104 mExponentialSwitchingTime = switchingTime;
113 resetExponentialTransition();
115 synchronizeEffectiveResistance(
false);
118 SPDLOG_LOGGER_INFO(
mSLog,
"Opening command received during an exponential "
119 "closing transition. Transition abandoned.");
126 resetZeroCrossingTimes();
127 resetExponentialTransition();
128 synchronizeEffectiveResistance(
false);
132 if (allPolesOpen()) {
139 resetZeroCrossingTimes();
140 resetExponentialTransition();
141 mResetZeroCrossingHistory =
true;
143 SPDLOG_LOGGER_INFO(
mSLog,
"Current-zero opening command received. Waiting "
144 "for phase-current zeros.");
148 validateExponentialResistanceParameters();
150 resetZeroCrossingTimes();
151 resetExponentialTransition();
153 synchronizeEffectiveResistance(
true);
156 SPDLOG_LOGGER_INFO(
mSLog,
157 "Exponential ZCS-emulation opening command received. "
158 "Switching duration={:.6e}s.",
159 mExponentialSwitchingTime);
166 resetZeroCrossingTimes();
167 mResetZeroCrossingHistory =
false;
171 resetExponentialTransition();
172 synchronizeEffectiveResistance(
true);
174 SPDLOG_LOGGER_INFO(
mSLog,
175 "Switch closing command: all three poles closed.");
180 resetExponentialTransition();
181 synchronizeEffectiveResistance(
true);
188 validateExponentialResistanceParameters();
189 resetExponentialTransition();
193 synchronizeEffectiveResistance(
false);
196 SPDLOG_LOGGER_INFO(
mSLog,
197 "Exponential ZCS-emulation closing command received. "
198 "Switching duration={:.6e}s.",
199 mExponentialSwitchingTime);
211 **
mIntfCurrent = (impedance.inverse() * vInitABC).real();
216 resetZeroCrossingTimes();
217 resetExponentialTransition();
218 synchronizeEffectiveResistance(**
mIsClosed);
220 SPDLOG_LOGGER_INFO(
mSLog,
221 "\n--- Initialization from powerflow ---"
222 "\nVoltage across: {:s}"
224 "\nTerminal 0 voltage: {:s}"
225 "\nTerminal 1 voltage: {:s}"
226 "\nSwitching mode: {:s}"
227 "\nInitial breaker state: {:s}"
228 "\n--- Initialization from powerflow finished ---",
237 :
"ExponentialZCSEmulation"),
244 mTimeStep = timeStep;
250 synchronizeEffectiveResistance(**
mIsClosed);
251 resetExponentialTransition();
259 if ((**mIntfCurrent).rows() == 3) {
261 mPreviousCurrentValid =
true;
263 mPreviousCurrent = Matrix::Zero(3, 1);
264 mPreviousCurrentValid =
false;
267 mEffectiveResistancePrev = {{
273 mPreviousCurrentTime = 0.0;
274 mResetZeroCrossingHistory =
false;
281 return allPolesClosed();
296 SPDLOG_LOGGER_TRACE(
mSLog,
"\nConductance matrix: {:s}",
307 closed ? (**mClosedResistance).inverse() : (**mOpenResistance).inverse();
313 SPDLOG_LOGGER_TRACE(
mSLog,
"\nConductance matrix: {:s}",
324 attributeDependencies.push_back(leftVector);
353 updateZeroCrossingState(time);
355 updateExponentialTransition(time);
364 (**mIntfVoltage)(0, 0) =
366 (**mIntfVoltage)(1, 0) =
368 (**mIntfVoltage)(2, 0) =
373 (**mIntfVoltage)(0, 0) -=
375 (**mIntfVoltage)(1, 0) -=
377 (**mIntfVoltage)(2, 0) -=
398 const std::array<Real, 3> resistanceNow{{
404 Bool changed =
false;
405 for (
UInt phase = 0; phase < 3; ++phase) {
406 if (resistanceNow[phase] != mEffectiveResistancePrev[phase]) {
407 mEffectiveResistancePrev[phase] = resistanceNow[phase];
414 const std::array<Bool, 3> poleClosedNow{{
420 Bool changed =
false;
422 for (
UInt phase = 0; phase < 3; ++phase) {
423 if (poleClosedNow[phase] != mPoleClosedPrev[phase]) {
425 mPoleClosedPrev[phase] = poleClosedNow[phase];
432Bool EMT::Ph3::Switch::poleClosed(
UInt phase)
const {
435 return **mPoleClosedA;
437 return **mPoleClosedB;
439 return **mPoleClosedC;
441 throw std::out_of_range(
"EMT::Ph3::Switch phase index must be 0, 1, or 2.");
445void EMT::Ph3::Switch::setPoleClosed(
UInt phase,
Bool closed) {
448 **mPoleClosedA = closed;
451 **mPoleClosedB = closed;
454 **mPoleClosedC = closed;
457 throw std::out_of_range(
"EMT::Ph3::Switch phase index must be 0, 1, or 2.");
462 if (mSwitchingMode == SwitchingMode::CurrentZero &&
463 (**mClosedResistance).rows() >= 3 && (**mOpenResistance).rows() >= 3) {
464 setEffectiveResistance(phase, closed ? (**mClosedResistance)(phase, phase)
465 : (**mOpenResistance)(phase, phase));
469void EMT::Ph3::Switch::setAllPoles(
Bool closed) {
470 **mPoleClosedA = closed;
471 **mPoleClosedB = closed;
472 **mPoleClosedC = closed;
475Bool EMT::Ph3::Switch::allPolesClosed()
const {
476 return **mPoleClosedA && **mPoleClosedB && **mPoleClosedC;
479Bool EMT::Ph3::Switch::allPolesOpen()
const {
480 return !**mPoleClosedA && !**mPoleClosedB && !**mPoleClosedC;
483void EMT::Ph3::Switch::resetZeroCrossingTimes() {
484 **mZeroCrossingTimeA = -1.0;
485 **mZeroCrossingTimeB = -1.0;
486 **mZeroCrossingTimeC = -1.0;
489Real EMT::Ph3::Switch::effectiveResistance(
UInt phase)
const {
492 return **mEffectiveResistanceA;
494 return **mEffectiveResistanceB;
496 return **mEffectiveResistanceC;
498 throw std::out_of_range(
"EMT::Ph3::Switch phase index must be 0, 1, or 2.");
502void EMT::Ph3::Switch::setEffectiveResistance(
UInt phase,
Real resistance) {
505 **mEffectiveResistanceA = resistance;
508 **mEffectiveResistanceB = resistance;
511 **mEffectiveResistanceC = resistance;
514 throw std::out_of_range(
"EMT::Ph3::Switch phase index must be 0, 1, or 2.");
518void EMT::Ph3::Switch::synchronizeEffectiveResistance(
Bool closed) {
519 if ((**mClosedResistance).rows() < 3 || (**mOpenResistance).rows() < 3)
522 for (
UInt phase = 0; phase < 3; ++phase) {
523 setEffectiveResistance(phase, closed ? (**mClosedResistance)(phase, phase)
524 : (**mOpenResistance)(phase, phase));
528void EMT::Ph3::Switch::resetExponentialTransition() {
529 **mExponentialTransitionActive =
false;
530 **mExponentialTransitionClosing =
false;
531 **mExponentialProgress = 0.0;
532 **mExponentialTransitionStartTime = -1.0;
533 **mExponentialTransitionEndTime = -1.0;
534 mExponentialTransitionStarted =
false;
537void EMT::Ph3::Switch::validateExponentialResistanceParameters()
const {
538 if ((**mClosedResistance).rows() < 3 || (**mOpenResistance).rows() < 3) {
539 throw std::invalid_argument(
540 "EMT::Ph3::Switch exponential mode requires 3x3 resistance matrices.");
543 if (mExponentialSwitchingTime <= 0.0) {
544 throw std::invalid_argument(
545 "EMT::Ph3::Switch exponential switching time must be positive.");
548 for (
UInt phase = 0; phase < 3; ++phase) {
549 const Real rClosed = (**mClosedResistance)(phase, phase);
550 const Real rOpen = (**mOpenResistance)(phase, phase);
552 if (rClosed <= 0.0 || rOpen <= 0.0) {
553 throw std::invalid_argument(
554 "EMT::Ph3::Switch exponential mode requires positive diagonal "
555 "open and closed resistances.");
560Real EMT::Ph3::Switch::exponentialResistance(
UInt phase,
Real alpha)
const {
561 const Real rClosed = (**mClosedResistance)(phase, phase);
562 const Real rOpen = (**mOpenResistance)(phase, phase);
563 const Real boundedAlpha = std::max(0.0, std::min(1.0, alpha));
565 return std::exp(std::log(rClosed) +
566 boundedAlpha * (std::log(rOpen) - std::log(rClosed)));
569void EMT::Ph3::Switch::updateExponentialTransition(
Real time) {
570 if (!**mExponentialTransitionActive)
573 if (!mExponentialTransitionStarted) {
574 mExponentialTransitionStarted =
true;
575 **mExponentialTransitionStartTime = time;
576 **mExponentialTransitionEndTime = time + mExponentialSwitchingTime;
581 const Real targetTime = time + mTimeStep;
582 const Real elapsed = (targetTime - **mExponentialTransitionStartTime) /
583 mExponentialSwitchingTime;
584 const Real boundedElapsed = std::max(0.0, std::min(1.0, elapsed));
586 const Bool closing = **mExponentialTransitionClosing;
589 const Real alpha = closing ? 1.0 - boundedElapsed : boundedElapsed;
591 **mExponentialProgress = alpha;
593 for (
UInt phase = 0; phase < 3; ++phase)
594 setEffectiveResistance(phase, exponentialResistance(phase, alpha));
596 if (boundedElapsed >= 1.0) {
597 synchronizeEffectiveResistance(closing);
598 setAllPoles(closing);
599 **mOpeningRequested =
false;
600 **mExponentialTransitionActive =
false;
601 **mExponentialTransitionClosing =
false;
605 "Exponential ZCS-emulation {:s} completed: start={:.9f}s, "
606 "end={:.9f}s, duration={:.6e}s.",
607 closing ?
"closing" :
"opening", **mExponentialTransitionStartTime,
608 **mExponentialTransitionEndTime, mExponentialSwitchingTime);
613 if (mSwitchingMode == SwitchingMode::Ideal) {
615 ? (**mClosedResistance).inverse()
616 : (**mOpenResistance).inverse();
622 for (
UInt phase = 0; phase < 3; ++phase) {
623 Real resistance = 0.0;
625 if (mSwitchingMode == SwitchingMode::CurrentZero) {
626 resistance = poleClosed(phase) ? (**mClosedResistance)(phase, phase)
627 : (**mOpenResistance)(phase, phase);
629 resistance = effectiveResistance(phase);
632 conductance(phase, phase) = 1.0 / resistance;
638Bool EMT::Ph3::Switch::currentCrossedZero(
Real previousCurrent,
639 Real current)
const {
640 if (std::abs(current) <= mZeroCrossingTolerance)
643 return (previousCurrent > mZeroCrossingTolerance &&
644 current < -mZeroCrossingTolerance) ||
645 (previousCurrent < -mZeroCrossingTolerance &&
646 current > mZeroCrossingTolerance);
649Real EMT::Ph3::Switch::interpolateZeroCrossingTime(
Real previousCurrent,
652 Real currentTime)
const {
653 const Real denominator = std::abs(previousCurrent) + std::abs(current);
655 if (denominator <= mZeroCrossingTolerance)
658 const Real fraction = std::abs(previousCurrent) / denominator;
660 return previousTime + fraction * (currentTime - previousTime);
663void EMT::Ph3::Switch::setZeroCrossingTime(
UInt phase,
Real time) {
666 **mZeroCrossingTimeA = time;
669 **mZeroCrossingTimeB = time;
672 **mZeroCrossingTimeC = time;
675 throw std::out_of_range(
"EMT::Ph3::Switch phase index must be 0, 1, or 2.");
679void EMT::Ph3::Switch::updateZeroCrossingState(
Real time) {
680 const Matrix current = **mIntfCurrent;
682 if (current.rows() != 3)
687 if (!**mOpeningRequested) {
688 mPreviousCurrent = current;
689 mPreviousCurrentTime = time;
690 mPreviousCurrentValid =
true;
697 if (mResetZeroCrossingHistory || !mPreviousCurrentValid) {
698 for (
UInt phase = 0; phase < 3; ++phase) {
699 if (poleClosed(phase) &&
700 std::abs(current(phase, 0)) <= mZeroCrossingTolerance) {
701 setPoleClosed(phase,
false);
702 setZeroCrossingTime(phase, time);
704 SPDLOG_LOGGER_INFO(mSLog,
705 "Phase {} opened at sampled current zero: "
706 "t={:.9f}s, i={:.6e}A",
707 phase, time, current(phase, 0));
711 mPreviousCurrent = current;
712 mPreviousCurrentTime = time;
713 mPreviousCurrentValid =
true;
714 mResetZeroCrossingHistory =
false;
717 **mOpeningRequested =
false;
722 for (
UInt phase = 0; phase < 3; ++phase) {
723 if (!poleClosed(phase))
726 const Real previous = mPreviousCurrent(phase, 0);
727 const Real present = current(phase, 0);
729 if (!currentCrossedZero(previous, present))
732 const Real zeroTime = interpolateZeroCrossingTime(
733 previous, present, mPreviousCurrentTime, time);
735 setPoleClosed(phase,
false);
736 setZeroCrossingTime(phase, zeroTime);
738 SPDLOG_LOGGER_INFO(mSLog,
739 "Phase {} current zero detected: "
740 "t_z={:.9f}s, i_prev={:.6e}A, i={:.6e}A. "
742 phase, zeroTime, previous, present);
745 mPreviousCurrent = current;
746 mPreviousCurrentTime = time;
747 mPreviousCurrentValid =
true;
749 if (allPolesOpen()) {
750 **mOpeningRequested =
false;
752 SPDLOG_LOGGER_INFO(mSLog,
"Current-zero interruption completed: "
753 "all three breaker poles are open.");
AttributePointer< Attribute< T > > Ptr
const CPS::Attribute< Matrix >::Ptr mClosedResistance
Resistance if switch is closed [ohm].
virtual void closeSwitch()
const CPS::Attribute< Matrix >::Ptr mOpenResistance
Resistance if switch is open [ohm].
virtual void openSwitch()
const CPS::Attribute< Bool >::Ptr mIsClosed
Commanded/global switch state.
void mnaCompUpdateCurrent(const Matrix &leftVector) override
const Attribute< Bool >::Ptr mPoleClosedB
void setSwitchingMode(SwitchingMode mode)
Select switching model. Configure before simulation initialization.
const Attribute< Real >::Ptr mExponentialTransitionStartTime
const Attribute< Real >::Ptr mExponentialProgress
Bool hasParameterChanged() override
Returns true if one of the element paramters has changed.
const Attribute< Real >::Ptr mEffectiveResistanceC
SimPowerComp< Real >::Ptr clone(String name) override
Returns a modified copy of the component with the given suffix added to the name and without.
const Attribute< Bool >::Ptr mOpeningRequested
const Attribute< Bool >::Ptr mPoleClosedC
const Attribute< Real >::Ptr mExponentialTransitionEndTime
void mnaCompApplySwitchSystemMatrixStamp(Bool closed, SparseMatrixRow &systemMatrix, Int freqIdx) override
const Attribute< Real >::Ptr mEffectiveResistanceB
void closeSwitch() override
void mnaCompPostStep(Real time, Int timeStepCount, Attribute< Matrix >::Ptr &leftVector) override
const Attribute< Real >::Ptr mZeroCrossingTimeB
Switch(String uid, String name, Logger::Level logLevel=Logger::Level::off)
void setExponentialSwitchingTime(Real switchingTime)
Set total transition time for ExponentialZCSEmulation [s].
@ ExponentialZCSEmulation
void mnaCompUpdateVoltage(const Matrix &leftVector) override
void mnaCompInitialize(Real omega, Real timeStep, Attribute< Matrix >::Ptr leftVector) override
void initializeFromNodesAndTerminals(Real frequency) override
Initializes Component variables according to power flow data stored in Nodes.
Bool supportsPrecomputedSystemMatrices() const override
Only Ideal is representable by the conventional two precomputed matrices.
void openSwitch() override
const Attribute< Bool >::Ptr mExponentialTransitionClosing
True while the active exponential transition is a closing transition.
void mnaCompApplySystemMatrixStamp(SparseMatrixRow &systemMatrix) override
void setZeroCrossingTolerance(Real tolerance)
Absolute current tolerance used by CurrentZero mode [A].
const Attribute< Real >::Ptr mEffectiveResistanceA
Effective phase resistances currently stamped into the MNA matrix [ohm].
const Attribute< Bool >::Ptr mExponentialTransitionActive
Exponential-transition diagnostics.
void mnaCompAddPostStepDependencies(AttributeBase::List &prevStepDependencies, AttributeBase::List &attributeDependencies, AttributeBase::List &modifiedAttributes, Attribute< Matrix >::Ptr &leftVector) override
void mnaCompApplyRightSideVectorStamp(Matrix &rightVector) override
const Attribute< Bool >::Ptr mPoleClosedA
Physical pole states.
const Attribute< Real >::Ptr mZeroCrossingTimeC
Bool mnaIsClosed() override
Check if switch is closed.
const Attribute< Real >::Ptr mZeroCrossingTimeA
Estimated physical zero-crossing times [s], -1 if none has been detected.
String uid()
Returns unique id.
AttributeList::Ptr mAttributes
Attribute List.
spdlog::level::level_enum Level
static String matrixToString(const Matrix &mat)
static String phasorToString(const Complex &num)
MNASimPowerComp(String uid, String name, Bool hasPreStep, Bool hasPostStep, Logger::Level logLevel)
Attribute< Matrix >::Ptr mRightVector
static void stampConductanceMatrix(const Matrix &conductanceMat, SparseMatrixRow &mat, UInt node1Index, UInt node2Index, Bool isTerminal1NotGrounded, Bool isTerminal2NotGrounded, const Logger::Log &mSLog)
static Real realFromVectorElement(const Matrix &mat, Matrix::Index row)
UInt matrixNodeIndex(UInt nodeIndex)
const Attribute< MatrixVar< Real > >::Ptr mIntfCurrent
const Attribute< MatrixVar< Real > >::Ptr mIntfVoltage
std::shared_ptr< SimPowerComp< VarType > > Ptr
Bool terminalNotGrounded(UInt index)
Complex initialSingleVoltage(UInt index)
void updateMatrixNodeIndices()
void setTerminalNumber(UInt num)
Logger::Level mLogLevel
Component logger control for internal variables.
Logger::Log mSLog
Component logger.
static std::shared_ptr< Switch > make(Args &&...args)
Eigen::Matrix< Real, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > Matrix
Dense matrix for real numbers.
Eigen::Matrix< Real, rows, cols, Eigen::ColMajor > MatrixFixedSize
Dense matrix for real numbers with fixed dimension.
Eigen::Matrix< Complex, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > MatrixComp
Dense matrix for complex numbers.
Eigen::SparseMatrix< Real, Eigen::RowMajor > SparseMatrixRow
Sparse matrix for real numbers (row major).