diff --git a/dpsim-models/include/dpsim-models/Components.h b/dpsim-models/include/dpsim-models/Components.h index 64ef2489c7..d92170fe28 100644 --- a/dpsim-models/include/dpsim-models/Components.h +++ b/dpsim-models/include/dpsim-models/Components.h @@ -87,6 +87,7 @@ #include #include #include +#include #include #include diff --git a/dpsim-models/include/dpsim-models/EMT/EMT_Ph1_Switch.h b/dpsim-models/include/dpsim-models/EMT/EMT_Ph1_Switch.h new file mode 100644 index 0000000000..d78ffd1bc1 --- /dev/null +++ b/dpsim-models/include/dpsim-models/EMT/EMT_Ph1_Switch.h @@ -0,0 +1,75 @@ +/* Copyright 2017-2021 Institute for Automation of Complex Power Systems, + * EONERC, RWTH Aachen University + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + *********************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace CPS { +namespace EMT { +namespace Ph1 { +/// \brief One phase EMT switch +/// +/// The switch can be opened and closed. +/// Each state has a specific resistance value. +class Switch : public MNASimPowerComp, + public Base::Ph1::Switch, + public SharedFactory, + public MNASwitchInterface { + +public: + /// Defines UID, name, component parameters and logging level + Switch(String uid, String name, Logger::Level loglevel = Logger::Level::off); + /// Defines name, component parameters and logging level + Switch(String name, Logger::Level logLevel = Logger::Level::off) + : Switch(name, name, logLevel) {} + + SimPowerComp::Ptr clone(String name) override; + + // #### General #### + /// Initializes component from power flow data + void initializeFromNodesAndTerminals(Real frequency) override; + + // #### General MNA section #### + void mnaCompInitialize(Real omega, Real timeStep, + Attribute::Ptr leftVector) override; + /// Stamps system matrix + void mnaCompApplySystemMatrixStamp(SparseMatrixRow &systemMatrix) override; + /// Stamps right side (source) vector + void mnaCompApplyRightSideVectorStamp(Matrix &rightVector) override; + /// Update interface voltage from MNA system result + void mnaCompUpdateVoltage(const Matrix &leftVector) override; + /// Update interface current from MNA system result + void mnaCompUpdateCurrent(const Matrix &leftVector) override; + + // #### MNA section for switches #### + /// Check if switch is closed + Bool mnaIsClosed() override; + /// Stamps system matrix considering the defined switch position + void mnaCompApplySwitchSystemMatrixStamp(Bool closed, + SparseMatrixRow &systemMatrix, + Int freqIdx) override; + /// MNA post step operations + void mnaCompPostStep(Real time, Int timeStepCount, + Attribute::Ptr &leftVector) override; + + /// Add MNA post step dependencies + void + mnaCompAddPostStepDependencies(AttributeBase::List &prevStepDependencies, + AttributeBase::List &attributeDependencies, + AttributeBase::List &modifiedAttributes, + Attribute::Ptr &leftVector) override; +}; +} // namespace Ph1 +} // namespace EMT +} // namespace CPS diff --git a/dpsim-models/src/CMakeLists.txt b/dpsim-models/src/CMakeLists.txt index 50cafa8a3d..795c44cd97 100644 --- a/dpsim-models/src/CMakeLists.txt +++ b/dpsim-models/src/CMakeLists.txt @@ -69,6 +69,7 @@ list(APPEND MODELS_SOURCES EMT/EMT_Ph1_VoltageSource.cpp EMT/EMT_Ph1_VoltageSourceRamp.cpp EMT/EMT_Ph1_VoltageSourceNorton.cpp + EMT/EMT_Ph1_Switch.cpp EMT/EMT_Ph3_CurrentSource.cpp EMT/EMT_Ph3_VoltageSource.cpp diff --git a/dpsim-models/src/EMT/EMT_Ph1_Switch.cpp b/dpsim-models/src/EMT/EMT_Ph1_Switch.cpp new file mode 100644 index 0000000000..67221aa6bc --- /dev/null +++ b/dpsim-models/src/EMT/EMT_Ph1_Switch.cpp @@ -0,0 +1,116 @@ +/* Copyright 2017-2021 Institute for Automation of Complex Power Systems, + * EONERC, RWTH Aachen University + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + *********************************************************************************/ + +#include + +using namespace CPS; + +EMT::Ph1::Switch::Switch(String uid, String name, Logger::Level logLevel) + : MNASimPowerComp(uid, name, false, true, logLevel), + Base::Ph1::Switch(mAttributes) { + setTerminalNumber(2); + **mIntfVoltage = Matrix::Zero(1, 1); + **mIntfCurrent = Matrix::Zero(1, 1); +} + +SimPowerComp::Ptr EMT::Ph1::Switch::clone(String name) { + auto copy = Switch::make(name, mLogLevel); + copy->setParameters(**mOpenResistance, **mClosedResistance, **mIsClosed); + return copy; +} + +void EMT::Ph1::Switch::initializeFromNodesAndTerminals(Real frequency) { + + Real resistance = (**mIsClosed) ? **mClosedResistance : **mOpenResistance; + + (**mIntfVoltage)(0, 0) = + RMS3PH_TO_PEAK1PH * (initialSingleVoltage(1) - initialSingleVoltage(0)).real(); + (**mIntfCurrent)(0, 0) = (**mIntfVoltage)(0, 0) / resistance; + + SPDLOG_LOGGER_INFO(mSLog, + "\n--- Initialization from powerflow ---" + "\nVoltage across: {:s}" + "\nCurrent: {:s}" + "\nTerminal 0 voltage: {:s}" + "\nTerminal 1 voltage: {:s}" + "\n--- Initialization from powerflow finished ---", + Logger::matrixToString(**mIntfVoltage), + Logger::matrixToString(**mIntfCurrent), + Logger::phasorToString(RMS3PH_TO_PEAK1PH * initialSingleVoltage(0).real()), + Logger::phasorToString(RMS3PH_TO_PEAK1PH * initialSingleVoltage(1).real())); +} + +void EMT::Ph1::Switch::mnaCompInitialize(Real omega, Real timeStep, + Attribute::Ptr leftVector) { + updateMatrixNodeIndices(); + mMnaTasks.push_back(std::make_shared(*this, leftVector)); +} + +Bool EMT::Ph1::Switch::mnaIsClosed() { return **mIsClosed; } + +void EMT::Ph1::Switch::mnaCompApplySystemMatrixStamp( + SparseMatrixRow &systemMatrix) { + Real conductance; + + conductance = + (**mIsClosed) ? 1. / (**mClosedResistance) : 1. / (**mOpenResistance); + + MNAStampUtils::stampConductance(conductance, systemMatrix, matrixNodeIndex(0), + matrixNodeIndex(1), terminalNotGrounded(0), + terminalNotGrounded(1), mSLog); +} + +void EMT::Ph1::Switch::mnaCompApplySwitchSystemMatrixStamp( + Bool closed, SparseMatrixRow &systemMatrix, Int freqIdx) { + Real conductance; + + conductance = + (closed) ? 1. / (**mClosedResistance) : 1. / (**mOpenResistance); + + MNAStampUtils::stampConductance(conductance, systemMatrix, matrixNodeIndex(0), + matrixNodeIndex(1), terminalNotGrounded(0), + terminalNotGrounded(1), mSLog); +} + +void EMT::Ph1::Switch::mnaCompApplyRightSideVectorStamp(Matrix &rightVector) {} + +void EMT::Ph1::Switch::mnaCompAddPostStepDependencies( + AttributeBase::List &prevStepDependencies, + AttributeBase::List &attributeDependencies, + AttributeBase::List &modifiedAttributes, + Attribute::Ptr &leftVector) { + attributeDependencies.push_back(leftVector); + modifiedAttributes.push_back(mIntfVoltage); + modifiedAttributes.push_back(mIntfCurrent); +} + +void EMT::Ph1::Switch::mnaCompPostStep(Real time, Int timeStepCount, + Attribute::Ptr &leftVector) { + mnaCompUpdateVoltage(**leftVector); + mnaCompUpdateCurrent(**leftVector); +} + +void EMT::Ph1::Switch::mnaCompUpdateVoltage(const Matrix &leftVector) { + // Voltage across component is defined as V1 - V0 + **mIntfVoltage = Matrix::Zero(1, 1); + if (terminalNotGrounded(1)) { + (**mIntfVoltage)(0, 0) = + Math::realFromVectorElement(leftVector, matrixNodeIndex(1, 0)); + } + if (terminalNotGrounded(0)) { + (**mIntfVoltage)(0, 0) = + (**mIntfVoltage)(0, 0) - + Math::realFromVectorElement(leftVector, matrixNodeIndex(0, 0)); + } +} + +void EMT::Ph1::Switch::mnaCompUpdateCurrent(const Matrix &leftVector) { + (**mIntfCurrent)(0, 0) = (**mIsClosed) + ? (**mIntfVoltage)(0, 0) / (**mClosedResistance) + : (**mIntfVoltage)(0, 0) / (**mOpenResistance); +} \ No newline at end of file diff --git a/dpsim/src/pybind/EMTComponents.cpp b/dpsim/src/pybind/EMTComponents.cpp index 633d328dd0..92afccbe00 100644 --- a/dpsim/src/pybind/EMTComponents.cpp +++ b/dpsim/src/pybind/EMTComponents.cpp @@ -102,6 +102,13 @@ void addEMTPh1Components(py::module_ mEMTPh1) { .def("connect", &CPS::EMT::Ph1::Inductor::connect) .def_property("L", createAttributeGetter("L"), createAttributeSetter("L")); + + py::class_, CPS::SimPowerComp, CPS::Base::Ph1::Switch>(mEMTPh1, "Switch", py::multiple_inheritance()) + .def(py::init(), "name"_a, "loglevel"_a = CPS::Logger::Level::off) + .def("set_parameters", &CPS::EMT::Ph1::Switch::setParameters, "open_resistance"_a, "closed_resistance"_a, "closed"_a = false) // cppcheck-suppress assignBoolToPointer + .def("open", &CPS::EMT::Ph1::Switch::open) + .def("close", &CPS::EMT::Ph1::Switch::close) + .def("connect", &CPS::EMT::Ph1::Switch::connect); } void addEMTPh3Components(py::module_ mEMTPh3) { @@ -121,6 +128,7 @@ void addEMTPh3Components(py::module_ mEMTPh3) { .def_property("f_src", createAttributeGetter("f_src"), createAttributeSetter("f_src")); + py::class_, CPS::SimPowerComp>(mEMTPh3, "Resistor", py::multiple_inheritance()) @@ -128,7 +136,7 @@ void addEMTPh3Components(py::module_ mEMTPh3) { .def(py::init()) .def("set_parameters", &CPS::EMT::Ph3::Resistor::setParameters, "R"_a) .def("connect", &CPS::EMT::Ph3::Resistor::connect); - ; + py::class_, diff --git a/examples/Notebooks/Circuits/VS_SW_RL1.ipynb b/examples/Notebooks/Circuits/VS_SW_RL1.ipynb new file mode 100644 index 0000000000..54b5b41a2f --- /dev/null +++ b/examples/Notebooks/Circuits/VS_SW_RL1.ipynb @@ -0,0 +1,658 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Circuit VS_SW_RL1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import math\n", + "import villas.dataprocessing.readtools as rt\n", + "import villas.dataprocessing.plottools as pt\n", + "from villas.dataprocessing.timeseries import TimeSeries as ts\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import dpsimpy\n", + "\n", + "#%matplotlib widget" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## SP for Initialization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sim_name_pf = 'SP_1Ph_VS_SW_RL1_Init'\n", + "dpsimpy.Logger.set_log_dir(\"logs/\" + sim_name_pf)\n", + "\n", + "# Nodes\n", + "gnd_pf = dpsimpy.sp.SimNode.gnd\n", + "n1_pf = dpsimpy.sp.SimNode('n1_pf')\n", + "n2_pf = dpsimpy.sp.SimNode('n2_pf')\n", + "n3_pf = dpsimpy.sp.SimNode('n2_pf')\n", + "\n", + "resistance = 5\n", + "inductance = 0.02\n", + "open_resistance = 1e6\n", + "closed_resistance = 0.001\n", + "\n", + "# Components\n", + "vs_pf = dpsimpy.sp.ph1.VoltageSource('vs')\n", + "vs_pf.set_parameters(V_ref=complex(10,0), f_src=50) # Vref is phase-to-phase RMS\n", + "sw_pf = dpsimpy.sp.ph1.Resistor('r1_pf')\n", + "sw_pf.set_parameters(closed_resistance)\n", + "r1_pf = dpsimpy.sp.ph1.Resistor('r1_pf')\n", + "r1_pf.set_parameters(resistance)\n", + "l1_pf = dpsimpy.sp.ph1.Inductor('l1_pf')\n", + "l1_pf.set_parameters(inductance)\n", + "\n", + "\n", + "# Connections\n", + "vs_pf.connect([gnd_pf, n1_pf])\n", + "sw_pf.connect([n1_pf, n2_pf])\n", + "r1_pf.connect([n2_pf, n3_pf])\n", + "l1_pf.connect([gnd_pf, n3_pf])\n", + "\n", + "# Define system topology\n", + "system_pf = dpsimpy.SystemTopology(50, [n1_pf, n2_pf, n3_pf], [vs_pf, sw_pf, r1_pf, l1_pf])\n", + "\n", + "# Logging\n", + "logger_pf = dpsimpy.Logger(sim_name_pf)\n", + "logger_pf.log_attribute('n1.v', 'v', n1_pf)\n", + "logger_pf.log_attribute('n2.v', 'v', n2_pf)\n", + "logger_pf.log_attribute('n2.v', 'v', n3_pf)\n", + "\n", + "sim_pf = dpsimpy.Simulation(sim_name_pf, dpsimpy.LogLevel.debug)\n", + "sim_pf.set_system(system_pf)\n", + "sim_pf.set_domain(dpsimpy.Domain.SP)\n", + "sim_pf.set_time_step(0.1)\n", + "sim_pf.set_final_time(0.5)\n", + "sim_pf.add_logger(logger_pf)\n", + "sim_pf.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## EMT 1Ph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "time_step = 0.00001\n", + "final_time = 2\n", + "sim_name = 'EMT_1Ph_VS_SW_RL1'\n", + "dpsimpy.Logger.set_log_dir('logs/' + sim_name)\n", + "\n", + "gnd = dpsimpy.emt.SimNode.gnd\n", + "n1 = dpsimpy.emt.SimNode('n1')\n", + "n1.set_initial_voltage(sim_pf.get_idobj_attr(n1_pf.name(), 'v').get()[0][0])\n", + "n2 = dpsimpy.emt.SimNode('n2')\n", + "n2.set_initial_voltage(sim_pf.get_idobj_attr(n2_pf.name(), 'v').get()[0][0])\n", + "n3 = dpsimpy.emt.SimNode('n3')\n", + "n3.set_initial_voltage(sim_pf.get_idobj_attr(n3_pf.name(), 'v').get()[0][0])\n", + "\n", + "resistance = 5\n", + "inductance = 0.02\n", + "open_resistance = 1e6\n", + "closed_resistance = 0.001\n", + "\n", + "vs = dpsimpy.emt.ph1.VoltageSource('vs')\n", + "vs.set_parameters(complex(10, 0)* math.sqrt(2/3), 50) # still using deprecated class that is initialised with Vref phase-to-ground peak voltage\n", + "res = dpsimpy.emt.ph1.Resistor('Resistor')\n", + "res.set_parameters(resistance)\n", + "ind = dpsimpy.emt.ph1.Inductor('Inductance')\n", + "ind.set_parameters(inductance)\n", + "sw = dpsimpy.emt.ph1.Switch('Switch')\n", + "sw.set_parameters(open_resistance, closed_resistance, True)\n", + "\n", + "vs.connect([gnd, n1])\n", + "sw.connect([n1, n2])\n", + "res.connect([n2, n3])\n", + "ind.connect([n3, gnd])\n", + "\n", + "sys = dpsimpy.SystemTopology(50, [n1, n2, n3], [vs, sw, res, ind])\n", + "\n", + "logger = dpsimpy.Logger(sim_name)\n", + "logger.log_attribute('n1.v', 'v', n1)\n", + "logger.log_attribute('n2.v', 'v', n2)\n", + "logger.log_attribute('n3.v', 'v', n3)\n", + "logger.log_attribute('r1.i_intf', 'i_intf', res)\n", + "\n", + "sim = dpsimpy.Simulation(sim_name)\n", + "sim.set_domain(dpsimpy.Domain.EMT)\n", + "sim.set_solver(dpsimpy.Solver.MNA)\n", + "sim.set_system(sys)\n", + "sim.set_time_step(time_step)\n", + "sim.set_final_time(final_time)\n", + "sim.add_logger(logger)\n", + "\n", + "event_sw1 = dpsimpy.event.SwitchEvent(0.5, sw, False)\n", + "event_sw2 = dpsimpy.event.SwitchEvent(1.5, sw, True)\n", + "sim.add_event(event_sw1)\n", + "sim.add_event(event_sw2)\n", + "\n", + "sim.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## DP 1Ph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "time_step = 0.00001\n", + "final_time = 2\n", + "sim_name = 'DP_1Ph_VS_SW_RL1'\n", + "dpsimpy.Logger.set_log_dir(\"logs/\" + sim_name)\n", + "\n", + "# Nodes\n", + "gnd = dpsimpy.dp.SimNode.gnd\n", + "n1 = dpsimpy.dp.SimNode('n1')\n", + "n1.set_initial_voltage(sim_pf.get_idobj_attr(n1_pf.name(), 'v').get()[0][0])\n", + "n2 = dpsimpy.dp.SimNode('n2')\n", + "n2.set_initial_voltage(sim_pf.get_idobj_attr(n2_pf.name(), 'v').get()[0][0])\n", + "n3 = dpsimpy.dp.SimNode('n3')\n", + "n3.set_initial_voltage(sim_pf.get_idobj_attr(n3_pf.name(), 'v').get()[0][0])\n", + "\n", + "resistance = 5\n", + "inductance = 0.02\n", + "open_resistance = 1e6\n", + "closed_resistance = 0.001\n", + "\n", + "# Components\n", + "vs = dpsimpy.dp.ph1.VoltageSource('vs')\n", + "vs.set_parameters(V_ref=complex(10,0))\n", + "res = dpsimpy.dp.ph1.Resistor('r1')\n", + "res.set_parameters(resistance)\n", + "ind = dpsimpy.dp.ph1.Inductor('l1')\n", + "ind.set_parameters(inductance)\n", + "sw = dpsimpy.dp.ph1.Switch('Switch')\n", + "sw.set_parameters(open_resistance, closed_resistance, True)\n", + "\n", + "vs.connect([gnd, n1])\n", + "sw.connect([n1, n2])\n", + "res.connect([n2, n3])\n", + "ind.connect([n3, gnd])\n", + "\n", + "sys = dpsimpy.SystemTopology(50, [gnd, n1, n2, n3], [vs, sw, ind, res])\n", + "\n", + "logger = dpsimpy.Logger(sim_name)\n", + "logger.log_attribute('n1.v', 'v', n1);\n", + "logger.log_attribute('n2.v', 'v', n2);\n", + "logger.log_attribute('n3.v', 'v', n3);\n", + "logger.log_attribute('r1.i_intf', 'i_intf', res);\n", + "\n", + "sim = dpsimpy.Simulation(sim_name)\n", + "sim.set_domain(dpsimpy.Domain.DP)\n", + "sim.set_solver(dpsimpy.Solver.MNA)\n", + "sim.set_system(sys)\n", + "sim.set_time_step(time_step)\n", + "sim.set_final_time(final_time)\n", + "sim.add_logger(logger)\n", + "\n", + "event_sw1 = dpsimpy.event.SwitchEvent(0.5, sw, False)\n", + "event_sw2 = dpsimpy.event.SwitchEvent(1.5, sw, True)\n", + "sim.add_event(event_sw1)\n", + "sim.add_event(event_sw2)\n", + "\n", + "sim.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## SP 1Ph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "time_step = 0.00001\n", + "final_time = 2\n", + "sim_name = 'SP_1Ph_VS_SW_RL1'\n", + "dpsimpy.Logger.set_log_dir(\"logs/\" + sim_name)\n", + "\n", + "# Nodes\n", + "gnd = dpsimpy.sp.SimNode.gnd\n", + "n1 = dpsimpy.sp.SimNode('n1')\n", + "n1.set_initial_voltage(sim_pf.get_idobj_attr(n1_pf.name(), 'v').get()[0][0])\n", + "n2 = dpsimpy.sp.SimNode('n2')\n", + "n2.set_initial_voltage(sim_pf.get_idobj_attr(n2_pf.name(), 'v').get()[0][0])\n", + "n3 = dpsimpy.sp.SimNode('n3')\n", + "n3.set_initial_voltage(sim_pf.get_idobj_attr(n3_pf.name(), 'v').get()[0][0])\n", + "\n", + "resistance = 5\n", + "inductance = 0.02\n", + "open_resistance = 1e6\n", + "closed_resistance = 0.001\n", + "\n", + "# Components\n", + "vs = dpsimpy.sp.ph1.VoltageSource('vs')\n", + "vs.set_parameters(V_ref=complex(10,0))\n", + "res = dpsimpy.sp.ph1.Resistor('r1')\n", + "res.set_parameters(resistance)\n", + "ind = dpsimpy.sp.ph1.Inductor('l1')\n", + "ind.set_parameters(inductance)\n", + "sw = dpsimpy.sp.ph1.Switch('Switch')\n", + "sw.set_parameters(open_resistance, closed_resistance, True)\n", + "\n", + "vs.connect([gnd, n1])\n", + "sw.connect([n1, n2])\n", + "res.connect([n2, n3])\n", + "ind.connect([n3, gnd])\n", + "\n", + "sys = dpsimpy.SystemTopology(50, [gnd, n1, n2, n3], [vs, sw, ind, res])\n", + "\n", + "logger = dpsimpy.Logger(sim_name)\n", + "logger.log_attribute('n1.v', 'v', n1);\n", + "logger.log_attribute('n2.v', 'v', n2);\n", + "logger.log_attribute('n3.v', 'v', n3);\n", + "logger.log_attribute('r1.i_intf', 'i_intf', res);\n", + "\n", + "sim = dpsimpy.Simulation(sim_name)\n", + "sim.set_domain(dpsimpy.Domain.SP)\n", + "sim.set_solver(dpsimpy.Solver.MNA)\n", + "sim.set_system(sys)\n", + "sim.set_time_step(time_step)\n", + "sim.set_final_time(final_time)\n", + "sim.add_logger(logger)\n", + "\n", + "event_sw1 = dpsimpy.event.SwitchEvent(0.5, sw, False)\n", + "event_sw2 = dpsimpy.event.SwitchEvent(1.5, sw, True)\n", + "sim.add_event(event_sw1)\n", + "sim.add_event(event_sw2)\n", + "\n", + "sim.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EMT 3Ph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "time_step = 0.00001\n", + "final_time = 2\n", + "sim_name = 'EMT_3Ph_VS_SW_RL1'\n", + "dpsimpy.Logger.set_log_dir('logs/' + sim_name)\n", + "\n", + "gnd = dpsimpy.emt.SimNode.gnd\n", + "n1 = dpsimpy.emt.SimNode('n1', dpsimpy.PhaseType.ABC)\n", + "n1.set_initial_voltage(sim_pf.get_idobj_attr(n1_pf.name(), 'v').get()[0][0])\n", + "n2 = dpsimpy.emt.SimNode('n2', dpsimpy.PhaseType.ABC)\n", + "n2.set_initial_voltage(sim_pf.get_idobj_attr(n2_pf.name(), 'v').get()[0][0])\n", + "n3 = dpsimpy.emt.SimNode('n3', dpsimpy.PhaseType.ABC)\n", + "n3.set_initial_voltage(sim_pf.get_idobj_attr(n3_pf.name(), 'v').get()[0][0])\n", + "\n", + "resistance = 5\n", + "inductance = 0.02\n", + "open_resistance = 1e6\n", + "closed_resistance = 0.001\n", + "\n", + "vs = dpsimpy.emt.ph3.VoltageSource('vs')\n", + "vs.set_parameters(V_ref=dpsimpy.Math.single_phase_variable_to_three_phase(complex(10, 0)), f_src=50)\n", + "res = dpsimpy.emt.ph3.Resistor('Resistor')\n", + "res.set_parameters(dpsimpy.Math.single_phase_parameter_to_three_phase(resistance))\n", + "ind = dpsimpy.emt.ph3.Inductor('Inductance')\n", + "ind.set_parameters(dpsimpy.Math.single_phase_parameter_to_three_phase(inductance))\n", + "sw = dpsimpy.emt.ph3.Switch('Switch')\n", + "sw.set_parameters(dpsimpy.Math.single_phase_parameter_to_three_phase(open_resistance), dpsimpy.Math.single_phase_parameter_to_three_phase(closed_resistance), True)\n", + "sw.close()\n", + "\n", + "vs.connect([gnd, n1])\n", + "sw.connect([n1, n2])\n", + "res.connect([n2, n3])\n", + "ind.connect([n3, gnd])\n", + "\n", + "sys = dpsimpy.SystemTopology(50, [gnd, n1, n2, n3], [vs, sw, res, ind])\n", + "\n", + "logger = dpsimpy.Logger(sim_name)\n", + "logger.log_attribute('n1.v', 'v', n1);\n", + "logger.log_attribute('n2.v', 'v', n2);\n", + "logger.log_attribute('n3.v', 'v', n3);\n", + "logger.log_attribute('r1.i_intf', 'i_intf', res)\n", + "\n", + "sim = dpsimpy.Simulation(sim_name)\n", + "sim.set_domain(dpsimpy.Domain.EMT)\n", + "sim.set_solver(dpsimpy.Solver.MNA)\n", + "sim.set_system(sys)\n", + "sim.set_time_step(time_step)\n", + "sim.set_final_time(final_time)\n", + "sim.add_logger(logger)\n", + "\n", + "event_sw1 = dpsimpy.event.SwitchEvent3Ph(0.5, sw, False)\n", + "event_sw2 = dpsimpy.event.SwitchEvent3Ph(1.5, sw, True)\n", + "sim.add_event(event_sw1)\n", + "sim.add_event(event_sw2)\n", + "\n", + "sim.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Read results" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## EMT 3Ph results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# read \n", + "work_dir = 'logs/EMT_3Ph_VS_SW_RL1/'\n", + "log_name = 'EMT_3Ph_VS_SW_RL1'\n", + "print(work_dir + log_name + '.csv')\n", + "\n", + "ts_dpsim_emt_3ph = rt.read_timeseries_dpsim(work_dir + log_name + '.csv')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## EMT 1Ph results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "work_dir = 'logs/EMT_1Ph_VS_SW_RL1/'\n", + "log_name = 'EMT_1Ph_VS_SW_RL1'\n", + "print(work_dir + log_name + '.csv')\n", + "\n", + "ts_dpsim_emt_1ph = rt.read_timeseries_dpsim(work_dir + log_name + '.csv')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## DP 1Ph results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "work_dir = 'logs/DP_1Ph_VS_SW_RL1/'\n", + "log_name = 'DP_1Ph_VS_SW_RL1'\n", + "print(work_dir + log_name + '.csv')\n", + "\n", + "ts_dpsim_dp_1ph = rt.read_timeseries_dpsim(work_dir + log_name + '.csv')\n", + "\n", + "# shifted\n", + "ts_dpsim_dp_1ph_shift = ts.frequency_shift_list(ts_dpsim_dp_1ph, 50)\n", + "\n", + "# phase-to-phase RMS to phase-to-ground Peak\n", + "for name, ts in ts_dpsim_dp_1ph_shift.items():\n", + " ts.values = ts.values * math.sqrt(2/3)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## SP 1Ph results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "work_dir = 'logs/SP_1Ph_VS_SW_RL1/'\n", + "log_name = 'SP_1Ph_VS_SW_RL1'\n", + "print(work_dir + log_name + '.csv')\n", + "\n", + "ts_dpsim_sp_1ph = rt.read_timeseries_dpsim(work_dir + log_name + '.csv')\n", + "\n", + "# shifted\n", + "ts_dpsim_sp_1ph_shift = ts.frequency_shift_list(ts_dpsim_sp_1ph, 50)\n", + "\n", + "# phase-to-phase RMS to phase-to-ground Peak\n", + "for name, ts in ts_dpsim_sp_1ph_shift.items():\n", + " ts.values = ts.values * math.sqrt(2/3)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "# Plot results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plt.close('all')\n", + "\n", + "# EMT 3Ph\n", + "ts_dpsim_emt_3ph['n1.v_0'].label = 'v1 EMT 3Ph'\n", + "ts_dpsim_emt_3ph['n2.v_0'].label = 'v2 EMT 3Ph'\n", + "ts_dpsim_emt_3ph['n3.v_0'].label = 'v3 EMT 3Ph'\n", + "ts_dpsim_emt_3ph['r1.i_intf_0'].label = 'i12 EMT 3Ph'\n", + "pt.plot_timeseries(1, ts_dpsim_emt_3ph['n1.v_0'], plt_linestyle=':')\n", + "pt.plot_timeseries(2, ts_dpsim_emt_3ph['n2.v_0'], plt_linestyle=':')\n", + "pt.plot_timeseries(3, ts_dpsim_emt_3ph['n3.v_0'], plt_linestyle=':')\n", + "pt.plot_timeseries(4, ts_dpsim_emt_3ph['r1.i_intf_0'], plt_linestyle=':')\n", + "\n", + "# EMT 1Ph\n", + "ts_dpsim_emt_1ph['n1.v'].label = 'v1 EMT 1Ph'\n", + "ts_dpsim_emt_1ph['n2.v'].label = 'v2 EMT 1Ph'\n", + "ts_dpsim_emt_1ph['n3.v'].label = 'v3 EMT 1Ph'\n", + "ts_dpsim_emt_1ph['r1.i_intf'].label = 'i12 EMT 1Ph'\n", + "pt.plot_timeseries(1, ts_dpsim_emt_1ph['n1.v'], plt_linestyle='-')\n", + "pt.plot_timeseries(2, ts_dpsim_emt_1ph['n2.v'], plt_linestyle='-')\n", + "pt.plot_timeseries(3, ts_dpsim_emt_1ph['n3.v'], plt_linestyle='-')\n", + "pt.plot_timeseries(4, ts_dpsim_emt_1ph['r1.i_intf'], plt_linestyle='-')\n", + "\n", + "# DP 1Ph shifted\n", + "ts_dpsim_dp_1ph_shift['n1.v_shift'].label = 'v1 DP 1Ph'\n", + "ts_dpsim_dp_1ph_shift['n2.v_shift'].label = 'v2 DP 1Ph'\n", + "ts_dpsim_dp_1ph_shift['n3.v_shift'].label = 'v3 DP 1Ph'\n", + "ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].label = 'i12 DP 1Ph'\n", + "pt.plot_timeseries(1, ts_dpsim_dp_1ph_shift['n1.v_shift'], plt_linestyle='-')\n", + "pt.plot_timeseries(2, ts_dpsim_dp_1ph_shift['n2.v_shift'], plt_linestyle='-')\n", + "pt.plot_timeseries(3, ts_dpsim_dp_1ph_shift['n3.v_shift'], plt_linestyle='-')\n", + "pt.plot_timeseries(4, ts_dpsim_dp_1ph_shift['r1.i_intf_shift'], plt_linestyle='-')\n", + "\n", + "# SP 1Ph shifted\n", + "ts_dpsim_sp_1ph_shift['n1.v_shift'].label = 'v1 SP 1Ph'\n", + "ts_dpsim_sp_1ph_shift['n2.v_shift'].label = 'v2 SP 1Ph'\n", + "ts_dpsim_sp_1ph_shift['n3.v_shift'].label = 'v3 SP 1Ph'\n", + "ts_dpsim_sp_1ph_shift['r1.i_intf_shift'].label = 'i12 SP 1Ph'\n", + "pt.plot_timeseries(1, ts_dpsim_sp_1ph_shift['n1.v_shift'], plt_linestyle=':')\n", + "pt.plot_timeseries(2, ts_dpsim_sp_1ph_shift['n2.v_shift'], plt_linestyle=':')\n", + "pt.plot_timeseries(3, ts_dpsim_sp_1ph_shift['n3.v_shift'], plt_linestyle=':')\n", + "pt.plot_timeseries(4, ts_dpsim_sp_1ph_shift['r1.i_intf_shift'], plt_linestyle=':')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Assertions: Compare current through all components" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#1 s ≙ 100'000 steps\n", + "#0.025 s ≙ 2500 steps\n", + "#0.001 s ≙ 100 steps\n", + "\n", + "\n", + "#EMT::Ph1 vs EMT::Ph3 assertions\n", + "\n", + "tolerance = 1e-6\n", + "\n", + "#simulation start: Check between 0.025s and 0.03s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[2500:3000] - ts_dpsim_emt_3ph['r1.i_intf_0'].values[2500:3000]) < tolerance))\n", + "#before open switch: Check between 0.47 and 0.495s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[47000:49500] - ts_dpsim_emt_3ph['r1.i_intf_0'].values[47000:49500]) < tolerance))\n", + "#after open switch: Check between 0.525s and 0.53s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[52500:53000] - ts_dpsim_emt_3ph['r1.i_intf_0'].values[52500:53000]) < tolerance))\n", + "#before close switch: Check between 1.47 and 1.495s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[147000:149500] - ts_dpsim_emt_3ph['r1.i_intf_0'].values[147000:149500]) < tolerance))\n", + "#after close switch: Check between 1.525s and 1.505s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[152500:153000] - ts_dpsim_emt_3ph['r1.i_intf_0'].values[152500:153000]) < tolerance))\n", + "#simulation end: Check between 1.995s and 2s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[199500:200000] - ts_dpsim_emt_3ph['r1.i_intf_0'].values[199500:200000]) < tolerance))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#EMT::Ph1 vs DP\n", + "\n", + "tolerance = 1e-5\n", + "\n", + "#simulation start: Check between 0.025s and 0.03s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[2500:3000] - ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[2500:3000]) < tolerance))\n", + "#before open switch: Check between 0.47 and 0.495s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[47000:49500] - ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[47000:49500]) < tolerance))\n", + "#after open switch: Check between 0.525s and 0.53s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[52500:53000] - ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[52500:53000]) < tolerance))\n", + "#before close switch: Check between 1.47 and 1.495s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[147000:149500] - ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[147000:149500]) < tolerance))\n", + "#after close switch: Check between 1.525s and 1.505s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[152500:153000] - ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[152500:153000]) < tolerance))\n", + "#simulation end: Check between 1.995s and 2s\n", + "assert(np.all(abs(ts_dpsim_emt_1ph['r1.i_intf'].values[199500:200000] - ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[199500:200000]) < tolerance))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#SP vs DP\n", + "\n", + "tolerance = 0.00125\n", + "\n", + "#simulation start: Check between 0.025s and 0.03s\n", + "assert(np.all(abs(ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[2500:3000] - ts_dpsim_sp_1ph_shift['r1.i_intf_shift'].values[2500:3000]) < tolerance))\n", + "#before open switch: Check between 0.47 and 0.495s\n", + "assert(np.all(abs(ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[47000:49500] - ts_dpsim_sp_1ph_shift['r1.i_intf_shift'].values[47000:49500]) < tolerance))\n", + "#after open switch: Check between 0.525s and 0.53s\n", + "assert(np.all(abs(ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[52500:53000] - ts_dpsim_sp_1ph_shift['r1.i_intf_shift'].values[52500:53000]) < tolerance))\n", + "#before close switch: Check between 1.47 and 1.495s\n", + "assert(np.all(abs(ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[147000:149500] - ts_dpsim_sp_1ph_shift['r1.i_intf_shift'].values[147000:149500]) < tolerance))\n", + "#after close switch: Check between 1.525s and 1.505s\n", + "assert(np.all(abs(ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[152500:153000] - ts_dpsim_sp_1ph_shift['r1.i_intf_shift'].values[152500:153000]) < tolerance))\n", + "#simulation end: Check between 1.995s and 2s\n", + "assert(np.all(abs(ts_dpsim_dp_1ph_shift['r1.i_intf_shift'].values[199500:200000] - ts_dpsim_sp_1ph_shift['r1.i_intf_shift'].values[199500:200000]) < tolerance))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}