Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time-of-Use: improvements #2559

Merged
merged 1 commit into from Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,107 @@
package io.openems.edge.controller.ess.timeofusetariff.optimizer;

import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING;
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID;
import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.findFirstPeakIndex;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.findFirstValleyIndex;
import static java.util.Arrays.stream;

import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import com.google.common.collect.ImmutableList;
import com.google.common.math.Quantiles;

import io.jenetics.Genotype;
import io.jenetics.IntegerChromosome;
import io.jenetics.IntegerGene;
import io.openems.edge.common.type.TypeUtils;
import io.openems.edge.controller.ess.timeofusetariff.StateMachine;

public class InitialPopulationUtils {

private InitialPopulationUtils() {
}

/**
* Builds an initial population:
*
* <ol>
* <li>Schedule with all periods BALANCING
* <li>Schedule from currently existing Schedule, i.e. the bestGenotype of last
* optimization run
* </ol>
*
* <p>
* NOTE: providing an "all periods BALANCING" Schedule as first Genotype makes
* sure, that this one wins in case there are other results with same cost, e.g.
* when battery never gets empty anyway.
*
* @param p the {@link Params}
* @return the {@link Genotype}
*/
public static ImmutableList<Genotype<IntegerGene>> buildInitialPopulation(Params p) {
var states = List.of(p.states());
var b = ImmutableList.<Genotype<IntegerGene>>builder(); //

// All BALANCING
b.add(Genotype.of(//
IntStream.range(0, p.numberOfPeriods()) //
.map(i -> states.indexOf(BALANCING)) //
.mapToObj(state -> IntegerChromosome.of(IntegerGene.of(state, 0, p.states().length))) //
.toList()));

if (p.existingSchedule().length > 0 //
&& Stream.of(p.existingSchedule()) //
.anyMatch(s -> s != BALANCING)) {
// Existing Schedule if available
b.add(Genotype.of(//
IntStream.range(0, p.numberOfPeriods()) //
// Map to state index; not-found maps to '-1', corrected to '0'
.map(i -> TypeUtils.fitWithin(0, p.states().length, states.indexOf(//
p.existingSchedule().length > i //
? p.existingSchedule()[i] //
: BALANCING))) //
.mapToObj(state -> IntegerChromosome.of(IntegerGene.of(state, 0, p.states().length))) //
.toList()));
}

// Suggest different combinations of CHARGE_GRID and DELAY_CHARGE
{
var peakIndex = findFirstPeakIndex(findFirstValleyIndex(0, p.prices()), p.prices());
var firstPrices = stream(p.prices()).limit(peakIndex).toArray();
if (firstPrices.length > 0 && states.contains(CHARGE_GRID) && states.contains(DELAY_DISCHARGE)) {
b.add(generateInitialGenotype(p.numberOfPeriods(), firstPrices, states, 5, 50));
b.add(generateInitialGenotype(p.numberOfPeriods(), firstPrices, states, 5, 75));
b.add(generateInitialGenotype(p.numberOfPeriods(), firstPrices, states, 10, 50));
b.add(generateInitialGenotype(p.numberOfPeriods(), firstPrices, states, 10, 75));
}
}

return b.build();
}

private static Genotype<IntegerGene> generateInitialGenotype(int numberOfPeriods, double[] prices,
List<StateMachine> states, int chargeGridPercentile, int delayDischargePercentile) {
var percentiles = Quantiles.percentiles().indexes(chargeGridPercentile, delayDischargePercentile)
.compute(prices);
return Genotype.of(//
IntStream.range(0, numberOfPeriods) //
.mapToObj(i -> {
if (i >= prices.length) {
return BALANCING;
}
var price = prices[i];
return price <= percentiles.get(chargeGridPercentile) //
? CHARGE_GRID //
: price <= percentiles.get(delayDischargePercentile) //
? DELAY_DISCHARGE //
: BALANCING;
}) //
.map(state -> IntegerChromosome.of(IntegerGene.of(states.indexOf(state), 0, states.size()))) //
.toList());
}

}
Expand Up @@ -4,6 +4,7 @@
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Simulator.calculateCost;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.calculateExecutionLimitSeconds;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.createSimulatorParams;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.initializeRandomRegistryForProduction;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.logSchedule;
import static java.lang.Thread.sleep;

Expand All @@ -12,14 +13,12 @@
import java.time.ZonedDateTime;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.random.RandomGeneratorFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableList;

import io.jenetics.util.RandomRegistry;
import io.openems.common.exceptions.InvalidValueException;
import io.openems.common.test.TimeLeapClock;
import io.openems.common.worker.AbstractImmediateWorker;
Expand All @@ -39,16 +38,7 @@ public class Optimizer extends AbstractImmediateWorker {

public Optimizer(Supplier<Context> context) {
this.context = context;

/* Initialize 'Random' */
// Default RandomGenerator "L64X256MixRandom" might not be available. Choose
// best available.
System.setProperty("io.jenetics.util.defaultRandomGenerator", "Random");
var rgf = RandomGeneratorFactory.all() //
.filter(RandomGeneratorFactory::isStatistical) //
.sorted((f, g) -> Integer.compare(g.stateBits(), f.stateBits())).findFirst()
.orElse(RandomGeneratorFactory.of("Random"));
RandomRegistry.random(rgf.create());
initializeRandomRegistryForProduction();

// Run Optimizer thread in LOW PRIORITY
this.setPriority(Thread.MIN_PRIORITY);
Expand Down
Expand Up @@ -124,7 +124,7 @@ protected Builder existingSchedule(StateMachine... existingSchedule) {
public Params build() {
var numberOfPeriods = min(this.productions.length, min(this.consumptions.length, this.prices.length));
var essChargeInChargeGrid = calculateParamsChargeEnergyInChargeGrid(this.essMinSocEnergy,
this.essMaxSocEnergy, this.productions, this.consumptions);
this.essMaxSocEnergy, this.productions, this.consumptions, this.prices);

return new Params(numberOfPeriods, //
this.time, //
Expand Down
Expand Up @@ -2,7 +2,7 @@

import static io.jenetics.engine.EvolutionResult.toBestGenotype;
import static io.jenetics.engine.Limits.byExecutionTime;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.buildInitialPopulation;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.InitialPopulationUtils.buildInitialPopulation;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.calculateBalancingEnergy;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.calculateChargeGridEnergy;
import static io.openems.edge.controller.ess.timeofusetariff.optimizer.Utils.calculateMaxChargeEnergy;
Expand All @@ -29,7 +29,7 @@
public class Simulator {

/** Used to incorporate charge/discharge efficiency. */
public static final double EFFICIENCY_FACTOR = 1.2;
public static final double EFFICIENCY_FACTOR = 1.17;

/**
* Calculates the cost of a Schedule.
Expand Down Expand Up @@ -86,7 +86,8 @@ protected static double calculatePeriodCost(Params p, int i, StateMachine[] stat
final var essMaxChargeInBalancing = calculateMaxChargeEnergy(//
p.essTotalEnergy() /* unlimited in BALANCING */, //
p.essMaxEnergyPerPeriod(), essInitial);
final var essMaxDischarge = calculateMaxDischargeEnergy(p, essInitial);
final var essMaxDischarge = calculateMaxDischargeEnergy(p.essMinSocEnergy(), //
p.essMaxEnergyPerPeriod(), essInitial);
final var essChargeDischargeInBalancing = calculateBalancingEnergy(essMaxChargeInBalancing, essMaxDischarge,
production, consumption);
final int essChargeDischarge;
Expand Down