Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2002-2024 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
14 : /// @file MSOverheadWire.cpp
15 : /// @author Jakub Sevcik (RICE)
16 : /// @author Jan Prikryl (RICE)
17 : /// @date 2019-12-15
18 : ///
19 : // Member method definitions for MSOverheadWire and MSTractionSubstation.
20 : /****************************************************************************/
21 : #include <config.h>
22 :
23 : #include <cassert>
24 : #include <tuple>
25 : #include <mutex>
26 : #include <string.h>
27 :
28 : #include <utils/vehicle/SUMOVehicle.h>
29 : #include <utils/common/ToString.h>
30 : #include <microsim/MSVehicleType.h>
31 : #include <microsim/MSStoppingPlace.h>
32 : #include <microsim/MSJunction.h>
33 : #include <microsim/MSLane.h>
34 : #include <microsim/MSLink.h>
35 : #include <microsim/MSNet.h>
36 : #include <microsim/devices/MSDevice_ElecHybrid.h>
37 :
38 : // due to gOverheadWireSolver
39 : #include <microsim/MSGlobals.h>
40 :
41 : // due to solving circuit as endEndOfTimestepEvents
42 : #include <utils/common/StaticCommand.h>
43 : #include <utils/common/WrappingCommand.h>
44 : #include <microsim/MSEventControl.h>
45 :
46 : #include <utils/traction_wire/Node.h>
47 : #include "MSOverheadWire.h"
48 :
49 :
50 : Command* MSTractionSubstation::myCommandForSolvingCircuit = nullptr;
51 : static std::mutex ow_mutex;
52 :
53 : // ===========================================================================
54 : // MSOverheadWire
55 : // ===========================================================================
56 :
57 60 : MSOverheadWire::MSOverheadWire(const std::string& overheadWireSegmentID, MSLane& lane, double startPos, double endPos, bool voltageSource) :
58 60 : MSStoppingPlace(overheadWireSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT, std::vector<std::string>(), lane, startPos, endPos),
59 60 : myVoltage(0),
60 60 : myChargingVehicle(false),
61 60 : myTotalCharge(0),
62 60 : myChargingVehicles({}),
63 : // RICE_TODO: think about some better structure storing circuit pointers below
64 60 : myTractionSubstation(nullptr),
65 60 : myVoltageSource(voltageSource),
66 60 : myCircuitElementPos(nullptr),
67 60 : myCircuitStartNodePos(nullptr),
68 120 : myCircuitEndNodePos(nullptr) {
69 60 : if (getBeginLanePosition() > getEndLanePosition()) {
70 0 : WRITE_WARNING(toString(SUMO_TAG_OVERHEAD_WIRE_SEGMENT) + " with ID = " + getID() + " doesn't have a valid range (" + toString(getBeginLanePosition()) + " < " + toString(getEndLanePosition()) + ").");
71 : }
72 60 : }
73 :
74 108 : MSOverheadWire::~MSOverheadWire() {
75 60 : if (myTractionSubstation != nullptr) {
76 : Circuit* circuit = myTractionSubstation->getCircuit();
77 42 : if (circuit != nullptr && myCircuitElementPos != nullptr && myCircuitElementPos->getPosNode() == myCircuitStartNodePos && myCircuitElementPos->getNegNode() == myCircuitEndNodePos) {
78 42 : circuit->eraseElement(myCircuitElementPos);
79 84 : delete myCircuitElementPos;
80 42 : if (myCircuitEndNodePos->getElements()->size() == 0) {
81 16 : circuit->eraseNode(myCircuitEndNodePos);
82 32 : delete myCircuitEndNodePos;
83 : }
84 42 : if (myCircuitStartNodePos->getElements()->size() == 0) {
85 12 : circuit->eraseNode(myCircuitStartNodePos);
86 24 : delete myCircuitStartNodePos;
87 : }
88 : }
89 :
90 42 : if (myTractionSubstation->numberOfOverheadSegments() <= 1) {
91 8 : myTractionSubstation->eraseOverheadWireSegmentFromCircuit(this);
92 : //RICE_TODO We should "delete myTractionSubstation;" here ...
93 : } else {
94 34 : myTractionSubstation->eraseOverheadWireSegmentFromCircuit(this);
95 : }
96 : }
97 168 : }
98 :
99 :
100 : void
101 46 : MSOverheadWire::addVehicle(SUMOVehicle& veh) {
102 : std::lock_guard<std::mutex> guard(ow_mutex);
103 46 : setChargingVehicle(true);
104 46 : myChargingVehicles.push_back(&veh);
105 46 : sort(myChargingVehicles.begin(), myChargingVehicles.end(), vehicle_position_sorter());
106 46 : }
107 :
108 : void
109 28 : MSOverheadWire::eraseVehicle(SUMOVehicle& veh) {
110 : std::lock_guard<std::mutex> guard(ow_mutex);
111 28 : myChargingVehicles.erase(std::remove(myChargingVehicles.begin(), myChargingVehicles.end(), &veh), myChargingVehicles.end());
112 28 : if (myChargingVehicles.size() == 0) {
113 22 : setChargingVehicle(false);
114 : }
115 : //sort(myChargingVehicles.begin(), myChargingVehicles.end(), vehicle_position_sorter());
116 28 : }
117 :
118 : void
119 28 : MSOverheadWire::lock() const {
120 28 : ow_mutex.lock();
121 28 : }
122 :
123 : void
124 28 : MSOverheadWire::unlock() const {
125 : ow_mutex.unlock();
126 28 : }
127 :
128 : void
129 22 : MSTractionSubstation::addVehicle(MSDevice_ElecHybrid* elecHybrid) {
130 22 : myElecHybrid.push_back(elecHybrid);
131 22 : }
132 :
133 : void
134 22 : MSTractionSubstation::eraseVehicle(MSDevice_ElecHybrid* veh) {
135 22 : myElecHybrid.erase(std::remove(myElecHybrid.begin(), myElecHybrid.end(), veh), myElecHybrid.end());
136 22 : }
137 :
138 : void
139 0 : MSTractionSubstation::writeOut() {
140 0 : std::cout << "substation " << getID() << " constrols segments: \n";
141 0 : for (std::vector<MSOverheadWire*>::iterator it = myOverheadWireSegments.begin(); it != myOverheadWireSegments.end(); ++it) {
142 0 : std::cout << " " << (*it)->getOverheadWireSegmentName() << "\n";
143 : }
144 0 : }
145 :
146 :
147 0 : std::string MSOverheadWire::getOverheadWireSegmentName() {
148 0 : return toString(getID());
149 : }
150 :
151 0 : MSTractionSubstation::~MSTractionSubstation() {
152 0 : }
153 :
154 : Circuit*
155 1375 : MSOverheadWire::getCircuit() const {
156 1375 : if (getTractionSubstation() != nullptr) {
157 1363 : return getTractionSubstation()->getCircuit();
158 : }
159 : return nullptr;
160 : }
161 :
162 : double
163 0 : MSOverheadWire::getVoltage() const {
164 0 : return myVoltage;
165 : }
166 :
167 : void
168 0 : MSOverheadWire::setVoltage(double voltage) {
169 0 : if (voltage < 0) {
170 0 : WRITE_WARNING("New " + toString(SUMO_ATTR_VOLTAGE) + " for " + toString(SUMO_TAG_OVERHEAD_WIRE_SEGMENT) + " with ID = " + getID() + " isn't valid (" + toString(voltage) + ").")
171 : } else {
172 0 : myVoltage = voltage;
173 : }
174 0 : }
175 :
176 : void
177 68 : MSOverheadWire::setChargingVehicle(bool value) {
178 68 : myChargingVehicle = value;
179 68 : }
180 :
181 :
182 : void
183 360 : MSTractionSubstation::setChargingVehicle(bool value) {
184 360 : myChargingVehicle = value;
185 360 : }
186 :
187 : bool
188 0 : MSOverheadWire::vehicleIsInside(const double position) const {
189 0 : if ((position >= getBeginLanePosition()) && (position <= getEndLanePosition())) {
190 : return true;
191 : } else {
192 0 : return false;
193 : }
194 : }
195 :
196 :
197 : bool
198 0 : MSOverheadWire::isCharging() const {
199 0 : return myChargingVehicle;
200 : }
201 :
202 :
203 : void
204 816 : MSOverheadWire::addChargeValueForOutput(double WCharged, MSDevice_ElecHybrid* elecHybrid, bool ischarging) {
205 816 : std::string status = "charging";
206 816 : if (!ischarging) {
207 : status = "not-charging";
208 : }
209 :
210 : // update total charge
211 816 : myTotalCharge += WCharged;
212 : // create charge row and insert it in myChargeValues
213 : const std::string vehID = elecHybrid->getHolder().getID();
214 : if (myChargeValues.count(vehID) == 0) {
215 46 : myChargedVehicles.push_back(vehID);
216 : }
217 816 : Charge C(MSNet::getInstance()->getCurrentTimeStep(), elecHybrid->getHolder().getID(), elecHybrid->getHolder().getVehicleType().getID(),
218 : status, WCharged, elecHybrid->getActualBatteryCapacity(), elecHybrid->getMaximumBatteryCapacity(),
219 2448 : elecHybrid->getVoltageOfOverheadWire(), myTotalCharge);
220 816 : myChargeValues[vehID].push_back(C);
221 1632 : }
222 :
223 :
224 : void
225 48 : MSOverheadWire::writeOverheadWireSegmentOutput(OutputDevice& output) {
226 48 : int chargingSteps = 0;
227 : std::vector<SUMOTime> chargingSteps_list;
228 70 : for (const auto& item : myChargeValues) {
229 202 : for (auto it : item.second) {
230 180 : if (std::find(chargingSteps_list.begin(), chargingSteps_list.end(), it.timeStep) == chargingSteps_list.end()) {
231 180 : chargingSteps_list.push_back(it.timeStep);
232 : }
233 180 : }
234 : }
235 48 : chargingSteps = (int) chargingSteps_list.size();
236 48 : output.openTag(SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
237 48 : output.writeAttr(SUMO_ATTR_ID, myID);
238 48 : if (getTractionSubstation() != nullptr) {
239 : output.writeAttr(SUMO_ATTR_TRACTIONSUBSTATIONID, getTractionSubstation()->getID());
240 : } else {
241 : output.writeAttr(SUMO_ATTR_TRACTIONSUBSTATIONID, "");
242 : }
243 48 : output.writeAttr(SUMO_ATTR_TOTALENERGYCHARGED, myTotalCharge);
244 :
245 : // RICE_TODO QUESTION myChargeValues.size() vs. chargingSteps
246 : // myChargeValues.size() is the number of vehicles charging sometimes from this overheadwire segment during simulation
247 : // chargingSteps is now the sum of chargingSteps of each vehicle, but takes also into account that at the given
248 : // step more than one vehicle may be charged from this segment
249 : output.writeAttr(SUMO_ATTR_CHARGINGSTEPS, chargingSteps);
250 : // output.writeAttr(SUMO_ATTR_EDGE, getLane().getEdge());
251 48 : output.writeAttr(SUMO_ATTR_LANE, getLane().getID());
252 :
253 : // Start writing
254 48 : if (myChargeValues.size() > 0) {
255 44 : for (const std::string& vehID : myChargedVehicles) {
256 : int iStart = 0;
257 22 : const auto& chargeSteps = myChargeValues[vehID];
258 44 : while (iStart < (int)chargeSteps.size()) {
259 22 : int iEnd = iStart + 1;
260 22 : double charged = chargeSteps[iStart].WCharged;
261 180 : while (iEnd < (int)chargeSteps.size() && chargeSteps[iEnd].timeStep == chargeSteps[iEnd - 1].timeStep + DELTA_T) {
262 158 : charged += chargeSteps[iEnd].WCharged;
263 158 : iEnd++;
264 : }
265 22 : writeVehicle(output, chargeSteps, iStart, iEnd, charged);
266 : iStart = iEnd;
267 : }
268 : }
269 : }
270 : // close charging station tag
271 96 : output.closeTag();
272 48 : }
273 :
274 :
275 : void
276 22 : MSOverheadWire::writeVehicle(OutputDevice& out, const std::vector<Charge>& chargeSteps, int iStart, int iEnd, double charged) {
277 22 : const Charge& first = chargeSteps[iStart];
278 22 : out.openTag(SUMO_TAG_VEHICLE);
279 22 : out.writeAttr(SUMO_ATTR_ID, first.vehicleID);
280 22 : out.writeAttr(SUMO_ATTR_TYPE, first.vehicleType);
281 : out.writeAttr(SUMO_ATTR_TOTALENERGYCHARGED_VEHICLE, charged);
282 22 : out.writeAttr(SUMO_ATTR_CHARGINGBEGIN, time2string(first.timeStep));
283 22 : out.writeAttr(SUMO_ATTR_CHARGINGEND, time2string(chargeSteps[iEnd - 1].timeStep));
284 22 : out.writeAttr(SUMO_ATTR_MAXIMUMBATTERYCAPACITY, first.maxBatteryCapacity);
285 202 : for (int i = iStart; i < iEnd; i++) {
286 180 : const Charge& c = chargeSteps[i];
287 180 : out.openTag(SUMO_TAG_STEP);
288 180 : out.writeAttr(SUMO_ATTR_TIME, time2string(c.timeStep));
289 : // charge values
290 180 : out.writeAttr(SUMO_ATTR_CHARGING_STATUS, c.status);
291 180 : out.writeAttr(SUMO_ATTR_ENERGYCHARGED, c.WCharged);
292 180 : out.writeAttr(SUMO_ATTR_PARTIALCHARGE, c.totalEnergyCharged);
293 : // charging values of charging station in this timestep
294 180 : out.writeAttr(SUMO_ATTR_VOLTAGE, c.voltage);
295 : // battery status of vehicle
296 180 : out.writeAttr(SUMO_ATTR_ACTUALBATTERYCAPACITY, c.actualBatteryCapacity);
297 : // close tag timestep
298 360 : out.closeTag();
299 : }
300 22 : out.closeTag();
301 22 : }
302 :
303 :
304 : // ===========================================================================
305 : // MSTractionSubstation
306 : // ===========================================================================
307 : // RICE_TODO Split MSTractionSubstation and MSOverheadWire?
308 : // Probably no as the traction substation cannot stand alone and is always
309 : // used together with the overhead wire. It is a bit disorganised, though.
310 :
311 8 : MSTractionSubstation::MSTractionSubstation(const std::string& substationId, double voltage, double currentLimit) :
312 : Named(substationId),
313 8 : myChargingVehicle(false),
314 8 : myElecHybridCount(0),
315 8 : mySubstationVoltage(voltage),
316 8 : myCircuit(new Circuit(currentLimit)),
317 8 : myTotalEnergy(0)
318 8 : {}
319 :
320 :
321 :
322 : void
323 38 : MSTractionSubstation::addOverheadWireSegmentToCircuit(MSOverheadWire* newOverheadWireSegment) {
324 38 : MSLane& lane = const_cast<MSLane&>(newOverheadWireSegment->getLane());
325 38 : if (lane.isInternal()) {
326 0 : return;
327 : }
328 :
329 : // RICE_TODO: consider the possibility of having more segments that belong to one lane.
330 :
331 38 : myOverheadWireSegments.push_back(newOverheadWireSegment);
332 38 : newOverheadWireSegment->setTractionSubstation(this);
333 :
334 38 : if (MSGlobals::gOverheadWireSolver) {
335 : #ifdef HAVE_EIGEN
336 38 : Circuit* circuit = newOverheadWireSegment->getCircuit();
337 : const std::string segmentID = newOverheadWireSegment->getID();
338 :
339 76 : if (circuit->getNode("negNode_ground") == nullptr) {
340 16 : circuit->addNode("negNode_ground");
341 : }
342 :
343 : // convention: pNode is at the beginning of the wire segment, nNode is at the end of the wire segment
344 38 : newOverheadWireSegment->setCircuitStartNodePos(circuit->addNode("pNode_pos_" + segmentID));
345 76 : newOverheadWireSegment->setCircuitEndNodePos(circuit->addNode("nNode_pos_" + segmentID));
346 : // RICE_TODO: to use startPos and endPos of ovhdsegment: set the length of wire here properly
347 38 : newOverheadWireSegment->setCircuitElementPos(
348 76 : circuit->addElement("pos_" + segmentID,
349 38 : (newOverheadWireSegment->getLane().getLength()) * WIRE_RESISTIVITY,
350 : newOverheadWireSegment->getCircuitStartNodePos(),
351 : newOverheadWireSegment->getCircuitEndNodePos(),
352 : Element::ElementType::RESISTOR_traction_wire));
353 : #else
354 : WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
355 : #endif
356 : }
357 :
358 : const MSLane* connection = nullptr;
359 38 : std::string ovrhdSegmentID = ""; //ID of outgoing or incoming overhead wire segment
360 : MSOverheadWire* ovrhdSegment = nullptr; //pointer to outgoing or incoming overhead wire segment
361 :
362 : // RICE_TODO: simplify the code, two similar code-blocks below
363 : // RICE_TODO: to use startPos and endPos of ovhdsegment: if endPos+EPS > newOverheadWireSegment->getLane().getLength(),
364 : // and the outgoing lanes will be skipped as there is no wire at the end of the lane
365 :
366 : /* in version before SUMO 1.0.1 the function getOutgoingLanes() returning MSLane* exists,
367 : in new version of SUMO the funciton getOutgoingViaLanes() returning MSLane* and MSEdge* pair exists */
368 : // std::vector<const MSLane*> outgoing = lane.getOutgoingLanes();
369 38 : const std::vector<std::pair<const MSLane*, const MSEdge*> > outgoingLanesAndEdges = lane.getOutgoingViaLanes();
370 : std::vector<const MSLane*> neigboringInnerLanes;
371 38 : neigboringInnerLanes.reserve(outgoingLanesAndEdges.size());
372 89 : for (size_t it = 0; it < outgoingLanesAndEdges.size(); ++it) {
373 51 : neigboringInnerLanes.push_back(outgoingLanesAndEdges[it].first);
374 : }
375 :
376 : // Check if there is an overhead wire segment on the outgoing lane. If not, do nothing, otherwise find connnecting internal lanes and
377 : // add all lanes (this and inner) to circuit
378 89 : for (std::vector<const MSLane*>::iterator it = neigboringInnerLanes.begin(); it != neigboringInnerLanes.end(); ++it) {
379 102 : ovrhdSegmentID = MSNet::getInstance()->getStoppingPlaceID(*it, NUMERICAL_EPS, SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
380 : // If the overhead wire segment is over the outgoing (not internal) lane
381 51 : if (ovrhdSegmentID != "" && !(*it)->isInternal()) {
382 25 : ovrhdSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace(ovrhdSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
383 : // If the outgoing overhead wire segment belongs to the same substation as newOverheadWireSegment
384 : // RICE_TODO: define what happens if the traction stations are different (overhead wire should continue over inner segments but it is unclear to which traction substation or even circuit it should be connected)
385 25 : if (ovrhdSegment->getTractionSubstation() == newOverheadWireSegment->getTractionSubstation()) {
386 2 : connection = lane.getInternalFollowingLane(*it);
387 2 : if (connection != nullptr) {
388 : //is connection a forbidden lane?
389 6 : if (!(ovrhdSegment->getTractionSubstation()->isForbidden(connection) ||
390 2 : ovrhdSegment->getTractionSubstation()->isForbidden(lane.getInternalFollowingLane(connection)) ||
391 2 : ovrhdSegment->getTractionSubstation()->isForbidden(connection->getInternalFollowingLane(*it)))) {
392 2 : addOverheadWireInnerSegmentToCircuit(newOverheadWireSegment, ovrhdSegment, connection, lane.getInternalFollowingLane(connection), connection->getInternalFollowingLane(*it));
393 : }
394 :
395 : } else {
396 0 : if (MSGlobals::gOverheadWireSolver) {
397 : #ifdef HAVE_EIGEN
398 : Node* const unusedNode = newOverheadWireSegment->getCircuitEndNodePos();
399 0 : for (MSOverheadWire* const ows : myOverheadWireSegments) {
400 0 : if (ows->getCircuitStartNodePos() == unusedNode) {
401 : ows->setCircuitStartNodePos(ovrhdSegment->getCircuitStartNodePos());
402 : }
403 0 : if (ows->getCircuitEndNodePos() == unusedNode) {
404 : ows->setCircuitEndNodePos(ovrhdSegment->getCircuitStartNodePos());
405 : }
406 : }
407 0 : newOverheadWireSegment->getCircuit()->replaceAndDeleteNode(unusedNode, ovrhdSegment->getCircuitStartNodePos());
408 : #else
409 : WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
410 : #endif
411 : }
412 : }
413 : }
414 : }
415 : }
416 :
417 : // RICE_TODO: to use startPos and endPos of ovhdsegment: if startPos-EPS < 0,
418 : // and the incoming lanes will be skipped as there is no wire at the beginning of the lane
419 :
420 : // This is the same as above, only this time checking the wires on some incoming lanes. If some of them
421 : // has an overhead wire segment, find the connnecting internal lanes and add all lanes (the internal
422 : // and this) to the circuit, otherwise do nothing.
423 76 : neigboringInnerLanes = lane.getNormalIncomingLanes();
424 111 : for (std::vector<const MSLane*>::iterator it = neigboringInnerLanes.begin(); it != neigboringInnerLanes.end(); ++it) {
425 146 : ovrhdSegmentID = MSNet::getInstance()->getStoppingPlaceID(*it, (*it)->getLength() - NUMERICAL_EPS, SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
426 : // If the overhead wire segment is over the incoming (not internal) lane
427 73 : if (ovrhdSegmentID != "" && !(*it)->isInternal()) {
428 19 : ovrhdSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace(ovrhdSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
429 : // If the incoming overhead wire segment belongs to the same substation as newOverheadWireSegment
430 : // RICE_TODO: define what happens if the traction stations are different (overhead wire should continue over inner segments but it is unclear to which traction substation or even circuit it should be connected)
431 19 : if (ovrhdSegment->getTractionSubstation() == newOverheadWireSegment->getTractionSubstation()) {
432 17 : connection = (*it)->getInternalFollowingLane(&lane);
433 17 : if (connection != nullptr) {
434 : //is connection a forbidden lane?
435 6 : if (!(ovrhdSegment->getTractionSubstation()->isForbidden(connection) ||
436 2 : ovrhdSegment->getTractionSubstation()->isForbidden((*it)->getInternalFollowingLane(connection)) ||
437 2 : ovrhdSegment->getTractionSubstation()->isForbidden(connection->getInternalFollowingLane(&lane)))) {
438 2 : addOverheadWireInnerSegmentToCircuit(ovrhdSegment, newOverheadWireSegment, connection, (*it)->getInternalFollowingLane(connection), connection->getInternalFollowingLane(&lane));
439 : }
440 : } else {
441 15 : if (MSGlobals::gOverheadWireSolver) {
442 : #ifdef HAVE_EIGEN
443 : Node* const unusedNode = newOverheadWireSegment->getCircuitStartNodePos();
444 60 : for (MSOverheadWire* const ows : myOverheadWireSegments) {
445 45 : if (ows->getCircuitStartNodePos() == unusedNode) {
446 : ows->setCircuitStartNodePos(ovrhdSegment->getCircuitEndNodePos());
447 : }
448 45 : if (ows->getCircuitEndNodePos() == unusedNode) {
449 : ows->setCircuitEndNodePos(ovrhdSegment->getCircuitEndNodePos());
450 : }
451 : }
452 15 : newOverheadWireSegment->getCircuit()->replaceAndDeleteNode(unusedNode, ovrhdSegment->getCircuitEndNodePos());
453 : #else
454 : WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
455 : #endif
456 : }
457 : }
458 : }
459 : }
460 : }
461 :
462 38 : if (MSGlobals::gOverheadWireSolver && newOverheadWireSegment->isThereVoltageSource()) {
463 : #ifdef HAVE_EIGEN
464 22 : newOverheadWireSegment->getCircuit()->addElement(
465 22 : "voltage_source_" + newOverheadWireSegment->getID(),
466 : mySubstationVoltage,
467 : newOverheadWireSegment->getCircuitStartNodePos(),
468 : newOverheadWireSegment->getCircuit()->getNode("negNode_ground"),
469 : Element::ElementType::VOLTAGE_SOURCE_traction_wire);
470 : #else
471 : WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
472 : #endif
473 : }
474 : }
475 :
476 :
477 : void
478 4 : MSTractionSubstation::addOverheadWireInnerSegmentToCircuit(MSOverheadWire* incomingSegment, MSOverheadWire* outgoingSegment, const MSLane* connection, const MSLane* frontConnection, const MSLane* behindConnection) {
479 4 : if (frontConnection == nullptr && behindConnection == nullptr) {
480 : // addOverheadWire from nNode of newOverheadWireSegment to pNode
481 4 : MSOverheadWire* innerSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + connection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
482 4 : myOverheadWireSegments.push_back(innerSegment);
483 4 : innerSegment->setTractionSubstation(incomingSegment->getTractionSubstation());
484 4 : if (MSGlobals::gOverheadWireSolver) {
485 : #ifdef HAVE_EIGEN
486 8 : Element* elem = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + connection->getID(), (connection->getLength()) * WIRE_RESISTIVITY, incomingSegment->getCircuitEndNodePos(), outgoingSegment->getCircuitStartNodePos(), Element::ElementType::RESISTOR_traction_wire);
487 : innerSegment->setCircuitElementPos(elem);
488 : innerSegment->setCircuitStartNodePos(incomingSegment->getCircuitEndNodePos());
489 : innerSegment->setCircuitEndNodePos(outgoingSegment->getCircuitStartNodePos());
490 : #else
491 : UNUSED_PARAMETER(outgoingSegment);
492 : WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
493 : #endif
494 : }
495 0 : } else if (frontConnection != nullptr && behindConnection == nullptr) {
496 0 : MSOverheadWire* innerSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + frontConnection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
497 0 : MSOverheadWire* innerSegment2 = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + connection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
498 :
499 : innerSegment->setTractionSubstation(incomingSegment->getTractionSubstation());
500 0 : myOverheadWireSegments.push_back(innerSegment);
501 : innerSegment2->setTractionSubstation(incomingSegment->getTractionSubstation());
502 0 : myOverheadWireSegments.push_back(innerSegment2);
503 :
504 0 : if (MSGlobals::gOverheadWireSolver) {
505 : #ifdef HAVE_EIGEN
506 0 : Node* betweenFrontNode_pos = incomingSegment->getCircuit()->addNode("betweenFrontNode_pos_" + connection->getID());
507 0 : Element* elem = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + frontConnection->getID(), (frontConnection->getLength()) * WIRE_RESISTIVITY, incomingSegment->getCircuitEndNodePos(), betweenFrontNode_pos, Element::ElementType::RESISTOR_traction_wire);
508 0 : Element* elem2 = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + connection->getID(), (connection->getLength()) * WIRE_RESISTIVITY, betweenFrontNode_pos, outgoingSegment->getCircuitStartNodePos(), Element::ElementType::RESISTOR_traction_wire);
509 :
510 0 : innerSegment->setCircuitElementPos(elem);
511 : innerSegment->setCircuitStartNodePos(incomingSegment->getCircuitEndNodePos());
512 : innerSegment->setCircuitEndNodePos(betweenFrontNode_pos);
513 :
514 0 : innerSegment2->setCircuitElementPos(elem2);
515 : innerSegment2->setCircuitStartNodePos(betweenFrontNode_pos);
516 : innerSegment2->setCircuitEndNodePos(outgoingSegment->getCircuitStartNodePos());
517 : #else
518 : WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
519 : #endif
520 : }
521 0 : } else if (frontConnection == nullptr && behindConnection != nullptr) {
522 0 : MSOverheadWire* innerSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + connection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
523 0 : MSOverheadWire* innerSegment2 = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + behindConnection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
524 :
525 : innerSegment->setTractionSubstation(incomingSegment->getTractionSubstation());
526 0 : myOverheadWireSegments.push_back(innerSegment);
527 : innerSegment2->setTractionSubstation(incomingSegment->getTractionSubstation());
528 0 : myOverheadWireSegments.push_back(innerSegment2);
529 :
530 0 : if (MSGlobals::gOverheadWireSolver) {
531 : #ifdef HAVE_EIGEN
532 0 : Node* betweenBehindNode_pos = incomingSegment->getCircuit()->addNode("betweenBehindNode_pos_" + connection->getID());
533 0 : Element* elem = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + connection->getID(), (connection->getLength()) * WIRE_RESISTIVITY, incomingSegment->getCircuitEndNodePos(), betweenBehindNode_pos, Element::ElementType::RESISTOR_traction_wire);
534 0 : Element* elem2 = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + behindConnection->getID(), (behindConnection->getLength()) * WIRE_RESISTIVITY, betweenBehindNode_pos, outgoingSegment->getCircuitStartNodePos(), Element::ElementType::RESISTOR_traction_wire);
535 :
536 0 : innerSegment->setCircuitElementPos(elem);
537 : innerSegment->setCircuitStartNodePos(incomingSegment->getCircuitEndNodePos());
538 : innerSegment->setCircuitEndNodePos(betweenBehindNode_pos);
539 :
540 0 : innerSegment2->setCircuitElementPos(elem2);
541 : innerSegment2->setCircuitStartNodePos(betweenBehindNode_pos);
542 : innerSegment2->setCircuitEndNodePos(outgoingSegment->getCircuitStartNodePos());
543 : #else
544 : WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
545 : #endif
546 : }
547 0 : } else if (frontConnection != nullptr && behindConnection != nullptr) {
548 0 : MSOverheadWire* innerSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + frontConnection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
549 0 : MSOverheadWire* innerSegment2 = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + connection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
550 0 : MSOverheadWire* innerSegment3 = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + behindConnection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
551 :
552 : innerSegment->setTractionSubstation(incomingSegment->getTractionSubstation());
553 0 : myOverheadWireSegments.push_back(innerSegment);
554 : innerSegment2->setTractionSubstation(incomingSegment->getTractionSubstation());
555 0 : myOverheadWireSegments.push_back(innerSegment2);
556 : innerSegment3->setTractionSubstation(incomingSegment->getTractionSubstation());
557 0 : myOverheadWireSegments.push_back(innerSegment3);
558 :
559 0 : if (MSGlobals::gOverheadWireSolver) {
560 : #ifdef HAVE_EIGEN
561 0 : Node* betweenFrontNode_pos = incomingSegment->getCircuit()->addNode("betweenFrontNode_pos_" + connection->getID());
562 0 : Node* betweenBehindNode_pos = incomingSegment->getCircuit()->addNode("betweenBehindNode_pos_" + connection->getID());
563 0 : Element* elem = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + frontConnection->getID(), (frontConnection->getLength()) * WIRE_RESISTIVITY, incomingSegment->getCircuitEndNodePos(), betweenFrontNode_pos, Element::ElementType::RESISTOR_traction_wire);
564 0 : Element* elem2 = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + connection->getID(), (connection->getLength()) * WIRE_RESISTIVITY, betweenFrontNode_pos, betweenBehindNode_pos, Element::ElementType::RESISTOR_traction_wire);
565 0 : Element* elem3 = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + behindConnection->getID(), (behindConnection->getLength()) * WIRE_RESISTIVITY, betweenBehindNode_pos, outgoingSegment->getCircuitStartNodePos(), Element::ElementType::RESISTOR_traction_wire);
566 :
567 0 : innerSegment->setCircuitElementPos(elem);
568 : innerSegment->setCircuitStartNodePos(incomingSegment->getCircuitEndNodePos());
569 : innerSegment->setCircuitEndNodePos(betweenFrontNode_pos);
570 :
571 0 : innerSegment2->setCircuitElementPos(elem2);
572 : innerSegment2->setCircuitStartNodePos(betweenFrontNode_pos);
573 : innerSegment2->setCircuitEndNodePos(betweenBehindNode_pos);
574 :
575 0 : innerSegment3->setCircuitElementPos(elem3);
576 : innerSegment3->setCircuitStartNodePos(betweenBehindNode_pos);
577 : innerSegment3->setCircuitEndNodePos(outgoingSegment->getCircuitStartNodePos());
578 : #else
579 : WRITE_WARNING(TL("Overhead circuit solver requested, but solver support not compiled in."));
580 : #endif
581 : }
582 : }
583 4 : }
584 :
585 :
586 11 : void MSTractionSubstation::addOverheadWireClampToCircuit(const std::string id, MSOverheadWire* startSegment, MSOverheadWire* endSegment) {
587 11 : PositionVector pos_start = startSegment->getLane().getShape();
588 11 : PositionVector pos_end = endSegment->getLane().getShape();
589 11 : double distance = pos_start[0].distanceTo2D(pos_end.back());
590 :
591 11 : if (distance > 10) {
592 4 : WRITE_WARNING("The distance between two overhead wires during adding overhead wire clamp '" + id + "' defined for traction substation '" + startSegment->getTractionSubstation()->getID() + "' is " + toString(distance) + " m.")
593 : }
594 22 : getCircuit()->addElement(id, distance * WIRE_RESISTIVITY, startSegment->getCircuitStartNodePos(), endSegment->getCircuitEndNodePos(), Element::ElementType::RESISTOR_traction_wire);
595 11 : }
596 :
597 :
598 : void
599 42 : MSTractionSubstation::eraseOverheadWireSegmentFromCircuit(MSOverheadWire* oldSegment) {
600 : //myOverheadWireSegments.push_back(static_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace(overheadWireSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT)));
601 42 : myOverheadWireSegments.erase(std::remove(myOverheadWireSegments.begin(), myOverheadWireSegments.end(), oldSegment), myOverheadWireSegments.end());
602 42 : }
603 :
604 :
605 : bool
606 0 : MSTractionSubstation::isCharging() const {
607 0 : return myChargingVehicle;
608 : }
609 :
610 :
611 : void
612 22 : MSTractionSubstation::increaseElecHybridCount() {
613 22 : myElecHybridCount++;
614 22 : }
615 :
616 :
617 : void
618 22 : MSTractionSubstation::decreaseElecHybridCount() {
619 22 : myElecHybridCount--;
620 22 : }
621 :
622 :
623 0 : void MSTractionSubstation::addForbiddenLane(MSLane* lane) {
624 0 : myForbiddenLanes.push_back(lane);
625 0 : }
626 :
627 :
628 24 : bool MSTractionSubstation::isForbidden(const MSLane* lane) {
629 24 : for (std::vector<MSLane*>::iterator it = myForbiddenLanes.begin(); it != myForbiddenLanes.end(); ++it) {
630 0 : if (lane == (*it)) {
631 : return true;
632 : }
633 : }
634 : return false;
635 : }
636 :
637 :
638 : void
639 11 : MSTractionSubstation::addClamp(const std::string& id, MSOverheadWire* startPos, MSOverheadWire* endPos) {
640 11 : OverheadWireClamp clamp(id, startPos, endPos, false);
641 11 : myOverheadWireClamps.push_back(clamp);
642 11 : }
643 :
644 :
645 : MSTractionSubstation::OverheadWireClamp*
646 22 : MSTractionSubstation::findClamp(std::string clampId) {
647 46 : for (auto it = myOverheadWireClamps.begin(); it != myOverheadWireClamps.end(); it++) {
648 35 : if (it->id == clampId) {
649 : return &(*it);
650 : }
651 : }
652 : return nullptr;
653 : }
654 :
655 :
656 : bool
657 8 : MSTractionSubstation::isAnySectionPreviouslyDefined() {
658 8 : if (myOverheadWireSegments.size() > 0 || myForbiddenLanes.size() > 0 || getCircuit()->getLastId() > 0) {
659 0 : return true;
660 : }
661 : return false;
662 : }
663 :
664 :
665 : void
666 180 : MSTractionSubstation::addSolvingCirucitToEndOfTimestepEvents() {
667 180 : if (!myChargingVehicle) {
668 : // myCommandForSolvingCircuit = new StaticCommand<MSTractionSubstation>(&MSTractionSubstation::solveCircuit);
669 180 : myCommandForSolvingCircuit = new WrappingCommand<MSTractionSubstation>(this, &MSTractionSubstation::solveCircuit);
670 180 : MSNet::getInstance()->getEndOfTimestepEvents()->addEvent(myCommandForSolvingCircuit);
671 180 : setChargingVehicle(true);
672 : }
673 180 : }
674 :
675 :
676 : SUMOTime
677 180 : MSTractionSubstation::solveCircuit(SUMOTime /*currentTime*/) {
678 : /*Circuit evaluation*/
679 180 : setChargingVehicle(false);
680 :
681 : #ifdef HAVE_EIGEN
682 :
683 : // RICE_TODO: Allow for updating current limits in each time step if changed e.g. via traci or similar
684 : // getCircuit()->setCurrentLimit(myCurrentLimit);
685 :
686 : // Solve the electrical circuit
687 180 : myCircuit->solve();
688 :
689 180 : if (myCircuit->getAlphaBest() != 1.0) {
690 0 : WRITE_WARNINGF(TL("The requested total power could not be delivered by the overhead wire. Only % of originally requested power was provided."), toString(myCircuit->getAlphaBest()));
691 : }
692 : #endif
693 :
694 : // RICE_TODO: verify what happens if eigen is not defined?
695 : // Note: addSolvingCirucitToEndOfTimestepEvents() and thus solveCircuit() should be called from notifyMove only if eigen is defined.
696 180 : addChargeValueForOutput(WATT2WATTHR(myCircuit->getTotalPowerOfCircuitSources()), myCircuit->getTotalCurrentOfCircuitSources(), myCircuit->getAlphaBest(), myCircuit->getAlphaReason());
697 :
698 360 : for (auto* it : myElecHybrid) {
699 :
700 : Element* vehElem = it->getVehElem();
701 180 : double voltage = vehElem->getVoltage();
702 180 : double current = -vehElem->getCurrent(); // Vehicle is a power source, hence its current (returned by getCurrent()) flows in opposite direction
703 :
704 180 : it->setCurrentFromOverheadWire(current);
705 180 : it->setVoltageOfOverheadWire(voltage);
706 :
707 : // Calulate energy charged
708 180 : double energyIn = WATT2WATTHR(voltage * current); // [Wh]
709 :
710 : // Compute energy charged into/from battery considering recuperation and propulsion efficiency (not considering battery capacity)
711 180 : double energyCharged = it->computeChargedEnergy(energyIn);
712 :
713 : // Update energy saved in the battery pack and return trully charged energy considering limits of battery
714 180 : double realEnergyCharged = it->storeEnergyToBattery(energyCharged);
715 :
716 180 : it->setEnergyCharged(realEnergyCharged);
717 :
718 : // Add energy wasted to the total sum
719 180 : it->updateTotalEnergyWasted(energyCharged - realEnergyCharged);
720 : // Add the energy provided by the overhead wire segment to the output of the segment
721 180 : it->getActOverheadWireSegment()->addChargeValueForOutput(energyIn, it);
722 : }
723 :
724 180 : return 0;
725 : }
726 :
727 : void
728 180 : MSTractionSubstation::addChargeValueForOutput(double energy, double current, double alpha, Circuit::alphaFlag alphaReason) {
729 180 : std::string status = "";
730 :
731 180 : myTotalEnergy += energy; //[Wh]
732 :
733 180 : std::string vehicleIDs = "";
734 360 : for (std::vector<MSDevice_ElecHybrid*>::iterator it = myElecHybrid.begin(); it != myElecHybrid.end(); it++) {
735 360 : vehicleIDs += (*it)->getID() + " ";
736 : }
737 : //vehicleIDs.erase(vehicleIDs.end());
738 : // TODO vehicleIDs should not be empty, but in some case, it is (due to teleporting of vehicle?)
739 180 : if (!vehicleIDs.empty()) {
740 : vehicleIDs.pop_back();
741 : }
742 :
743 180 : std::string currents = "";
744 180 : currents = myCircuit->getCurrentsOfCircuitSource(currents);
745 :
746 : // create charge row and insert it in myChargeValues
747 : chargeTS C(MSNet::getInstance()->getCurrentTimeStep(), getID(), vehicleIDs, energy, current, currents, mySubstationVoltage, status,
748 905 : (int)myElecHybrid.size(), (int)getCircuit()->getNumVoltageSources(), alpha, alphaReason);
749 180 : myChargeValues.push_back(C);
750 360 : }
751 :
752 : void
753 8 : MSTractionSubstation::writeTractionSubstationOutput(OutputDevice& output) {
754 8 : output.openTag(SUMO_TAG_TRACTION_SUBSTATION);
755 8 : output.writeAttr(SUMO_ATTR_ID, myID);
756 8 : output.writeAttr(SUMO_ATTR_TOTALENERGYCHARGED, myTotalEnergy); //[Wh]
757 8 : double length = 0;
758 50 : for (auto it = myOverheadWireSegments.begin(); it != myOverheadWireSegments.end(); it++) {
759 42 : length += (*it)->getEndLanePosition() - (*it)->getBeginLanePosition();
760 : }
761 : output.writeAttr(SUMO_ATTR_LENGTH, length);
762 16 : output.writeAttr("numVoltageSources", myCircuit->getNumVoltageSources());
763 16 : output.writeAttr("numClamps", myOverheadWireClamps.size());
764 16 : output.writeAttr(SUMO_ATTR_CHARGINGSTEPS, myChargeValues.size());
765 :
766 : // start writting
767 8 : if (myChargeValues.size() > 0) {
768 : // iterate over charging values
769 184 : for (std::vector<MSTractionSubstation::chargeTS>::const_iterator i = myChargeValues.begin(); i != myChargeValues.end(); i++) {
770 : // open tag for timestep and write all parameters
771 180 : output.openTag(SUMO_TAG_STEP);
772 180 : output.writeAttr(SUMO_ATTR_TIME, time2string(i->timeStep));
773 : // charge values
774 180 : output.writeAttr("vehicleIDs", i->vehicleIDs);
775 180 : output.writeAttr("numVehicles", i->numVehicles);
776 : // same number of numVoltageSources for all time, parameter is written in the superordinate tag
777 : //output.writeAttr("numVoltageSources", i->numVoltageSources);
778 : // charging status is always ""
779 : //output.writeAttr(SUMO_ATTR_CHARGING_STATUS, i->status);
780 180 : output.writeAttr(SUMO_ATTR_ENERGYCHARGED, i->energy);
781 180 : output.writeAttr(SUMO_ATTR_CURRENTFROMOVERHEADWIRE, i->current);
782 180 : output.writeAttr("currents", i->currentsString);
783 : // charging values of charging station in this timestep
784 180 : output.writeAttr(SUMO_ATTR_VOLTAGE, i->voltage);
785 180 : output.writeAttr(SUMO_ATTR_ALPHACIRCUITSOLVER, i->alpha);
786 180 : output.writeAttr("alphaFlag", i->alphaReason);
787 : // close tag timestep
788 360 : output.closeTag();
789 : // update timestep of charge
790 : }
791 : }
792 : // close charging station tag
793 8 : output.closeTag();
794 8 : }
795 :
796 : /****************************************************************************/
|