// We have to ensure that there are enough batteries to // support the shift schedule. There must be at least enough batteries to // supply the two largest adjacent shifts, and there must also be enough // to power the two largest adjacent shifts, in case a single battery // cannot power an entire shift. void validateBatteries() { int minBatteries = 0; Shift s1 = null; Shift s2 = null; for (int i = 0; i < shiftSchedule.length; i++) { Shift s = shiftSchedule[i]; if (null == s) { s1 = s2; } else if (s2 != s) { s1 = s2; s2 = s; if (null != s1) { int n1 = s1.getTrucks(); int d1 = s1.getDuration(); int n2 = s2.getTrucks(); int d2 = s2.getDuration(); double neededBatteries = (n1 * d1 + n2 * d2) * truckKW / getBatteryCapacity(); minBatteries = (int) Math.max(minBatteries, (n1 + n2)); minBatteries = (int) Math.max(minBatteries, Math.ceil(neededBatteries)); } } } int neededBatteries = minBatteries - nBatteries; if (neededBatteries > 0) { log.error("Not enough batteries (" + nBatteries + ") for " + getName()); // Add discharged batteries to fill out battery complement log.warn("Adding " + neededBatteries + " batteries for " + getName()); setNBatteries(getNBatteries() + neededBatteries); } }
// ======== per-timeslot activities ======== @Override public void step() { // check for end-of-shift Shift newShift = shiftSchedule[indexOfShift(getNowInstant())]; if (newShift != currentShift) { log.info(getName() + " start of shift"); // Take all batteries out of service double totalEnergy = getEnergyCharging() + getEnergyInUse(); setEnergyCharging(getEnergyCharging() + getEnergyInUse()); setCapacityInUse(0.0); setEnergyInUse(0.0); // Put the strongest batteries in trucks for the next shift if (null != newShift) { setCapacityInUse(newShift.getTrucks() * batteryCapacity); setEnergyInUse(Math.min(getCapacityInUse(), totalEnergy)); setEnergyCharging(totalEnergy - getEnergyInUse()); } log.info( getName() + ": new shift cInUse " + capacityInUse + ", eInUse " + energyInUse + ", eCharging " + energyCharging); currentShift = newShift; } // discharge batteries on active trucks if (null != currentShift) { double usage = Math.max(0.0, normal.sample() * truckStd + truckKW * currentShift.getTrucks()); double deficit = usage - getEnergyInUse(); log.debug(getName() + ": trucks use " + usage + " kWh"); if (deficit > 0.0) { log.warn(getName() + ": trucks use more energy than available by " + deficit + " kWh"); addEnergyInUse(deficit); addEnergyCharging(-deficit); } addEnergyInUse(-usage); } // use energy on chargers, accounting for regulation double regulation = getSubscription().getRegulation(); log.info(getName() + ": regulation " + regulation); double energyUsed = useEnergy(regulation); // Record energy used getSubscription().usePower(energyUsed); log.info( getName() + " cInUse " + capacityInUse + ", eInUse " + energyInUse + ", eCharging " + energyCharging); }
ShiftEnergy(Instant start, int end, int duration) { super(); this.start = start; this.endIndex = end; Shift next = shiftSchedule[end]; if (null != next) { energyNeeded = next.getTrucks() * next.getDuration() * getTruckKW(); } this.duration = duration; }
// Computes constraints on future energy needs // Amounts are energy needed to run the chargers. Energy input to trucks // will be smaller due to charge efficiency. ShiftEnergy[] getFutureEnergyNeeds(Instant start, int horizon, double initialCharging) { Instant seStart = start; int index = indexOfShift(start); // current time is likely to be partway into first shift Shift currentShift = shiftSchedule[index]; // might be null int duration = 0; while (shiftSchedule[index] == currentShift) { duration += 1; index = nextShiftIndex(index); } Shift nextShift = shiftSchedule[index]; // this gives us the info we need to start the sequence ArrayList<ShiftEnergy> data = new ArrayList<ShiftEnergy>(); data.add(new ShiftEnergy(seStart, index, duration)); seStart = seStart.plus(duration * TimeService.HOUR); int elapsed = duration; // add shifts until we run off the end of the horizon // keep in mind that a shift can be null while (elapsed < horizon) { duration = 0; while (nextShift == shiftSchedule[index]) { index = nextShiftIndex(index); duration += 1; } nextShift = shiftSchedule[index]; data.add(new ShiftEnergy(seStart, index, duration)); elapsed += duration; seStart = seStart.plus(duration * TimeService.HOUR); } // now we convert to array, then walk backward and fill in energy needs ShiftEnergy[] result = data.toArray(new ShiftEnergy[data.size()]); double shortage = 0.0; for (int i = result.length - 1; i >= 0; i--) { int endx = result[i].endIndex; int prev = previousShiftIndex(endx); currentShift = shiftSchedule[prev]; Shift end = shiftSchedule[endx]; double needed = 0.0; if (null != end) { // Assume we need, at the end of each shift, enough energy to // run the next shift needed = (end.getTrucks() * end.getDuration() * getTruckKW()) / getChargeEfficiency(); } // chargers is min of charger capacity and battery availability int chargers = getNChargers(); int availableBatteries = nBatteries; if (null != currentShift) { availableBatteries -= currentShift.getTrucks(); } chargers = (int) Math.min(chargers, availableBatteries); double available = getMaxChargeKW() * result[i].getDuration() * chargers / getChargeEfficiency(); double surplus = available - needed - shortage; shortage = Math.max(0.0, -(available - needed - shortage)); result[i].setEnergyNeeded(needed); result[i].setMaxSurplus(surplus); } // finally, we need to update the first element with // the current battery charge. double finalSurplus = result[0].getMaxSurplus(); if (finalSurplus > 0.0) { result[0].setMaxSurplus(finalSurplus + initialCharging); } else if (shortage > 0.0) { result[0].setMaxSurplus(initialCharging - shortage); } return result; }
// make sure we have enough charging capacity to support // the shift schedule void validateChargers() { // ToDo -- A single charging should be able to charge a truck worth // of batteries in a single shift // The total output of the availableChargers should be at least enough // to power the trucks over a 24-hour period. Note that the shift schedule // starts at midnight, which may not be the start of the current shift. double maxNeeded = 0.0; int offset = 0; while (null == shiftSchedule[offset]) { offset += 1; } Shift currentShift = shiftSchedule[offset]; int remainingDuration = 0; int hoursInShift = (HOURS_DAY - currentShift.getStart()) % HOURS_DAY; remainingDuration = currentShift.getDuration() - hoursInShift; for (int i = offset; i < (shiftSchedule.length - HOURS_DAY); i++) { double totalEnergy = 0.0; Shift thisShift = shiftSchedule[i]; if (thisShift != currentShift) { currentShift = thisShift; if (null != currentShift) { // first block of energy in 24h window starting at i remainingDuration = currentShift.getDuration(); } } if (null != currentShift) { totalEnergy += currentShift.getTrucks() * remainingDuration * truckKW; } // now run fwd 24h and add energy from future shifts Shift current = currentShift; // int shiftStart = i; for (int j = i + 1; j < (i + HOURS_DAY); j++) { Shift newShift = shiftSchedule[j]; if (null != newShift && current != newShift) { int durationInWindow = (int) Math.min((i + HOURS_DAY - j), newShift.getDuration()); totalEnergy += newShift.getTrucks() * durationInWindow * truckKW; current = newShift; } } maxNeeded = Math.max(maxNeeded, totalEnergy); remainingDuration -= 1; } double chargeEnergy = nChargers * maxChargeKW * HOURS_DAY; if (maxNeeded > chargeEnergy) { double need = (maxNeeded - chargeEnergy) / (maxChargeKW * HOURS_DAY); int add = (int) Math.ceil(need); log.error( "Insufficient charging capacity for " + getName() + ": have " + chargeEnergy + ", need " + maxNeeded + ". Adding " + add + " availableChargers."); setNChargers(getNChargers() + add); } }