/* * Copyright (c) 2010 Ecole des Mines de Nantes and Fabien Hermenier. * * This file is part of Entropy. * * Entropy is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Entropy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Entropy. If not, see <http://www.gnu.org/licenses/>. */ package entropy.plan.choco.constraint.sliceScheduling; import gnu.trove.TIntIntHashMap; import java.util.Arrays; import java.util.BitSet; import choco.cp.solver.variables.integer.IntVarEvent; import choco.kernel.common.logging.ChocoLogging; import choco.kernel.common.util.tools.ArrayUtils; import choco.kernel.memory.IEnvironment; import choco.kernel.memory.IStateInt; import choco.kernel.solver.ContradictionException; import choco.kernel.solver.constraints.integer.AbstractLargeIntSConstraint; import choco.kernel.solver.variables.integer.IntDomainVar; /** * A constraint to maintains bounds for all * the incoming and the outgoing slices for a node. * <p/> * TODO: Adapt to have a single constraint for all the nodes, not one per node (unjustified) * TODO: Reduce the memory footprint by avoiding to create an array of key for each propagate? * * @author Fabien Hermenier */ public class PlanMySlices extends AbstractLargeIntSConstraint { /** * My CPU capacity. */ private int capacityCPU; /** * My memory capacity. */ private int capacityMem; /** * My identifier. */ private int me; //consuming slice part /** * out[i] = true <=> the consuming slice i will leave me. */ private BitSet out; /** * The hosting variables of the consuming slices. */ private IntDomainVar[] cHosters; /** * The moment the consuming slices ends. Same order as the hosting variables. */ private IntDomainVar[] cEnds; /** * The CPU height for each consuming slice. Same order as the hosting variables. */ private int[] cCPUHeights; /** * The Memory height for each consuming slice. Same order as the hosting variables. */ private int[] cMemHeights; //Demanding slice part /** * in[i] = true <=> the demanding slice i will come to me. */ private BitSet in; /** * The hosting variable for each demanding slice. */ private IntDomainVar[] dHosters; /* * The moment the demanding slices ends. Same order as the hosting variables. */ private IntDomainVar[] dStarts; /** * The CPU height for each demanding slice. Same order as the hosting variable. */ private int[] dCPUHeights; /** * The memory heighr for each demanding slice. Same order as the hosting variable. */ private int[] dMemHeights; /** * The amount of free memory at startup. */ private int startupFreeMem; /** * The amount of free CPU at startup. */ private int startupFreeCPU; private static final int DEBUG = 201; private IStateInt toInstantiate; private IEnvironment env; private int[] associations; private int[] revAssociations; public static final int NO_ASSOCIATIONS = -1; private TIntIntHashMap profileMinCPU = new TIntIntHashMap(); private TIntIntHashMap profileMinMem = new TIntIntHashMap(); private int[] sortedMinProfile; private TIntIntHashMap profileMaxCPU = new TIntIntHashMap(); private TIntIntHashMap profileMaxMem = new TIntIntHashMap(); private int[] sortedMaxProfile; /** * Make a new constraint. * * @param env the solving environment * @param me the identifier of the node * @param capacityCPU the CPU capacity of the node * @param capacityMem the memory capacity of the node * @param cHosters The hoster variable for all the consuming slices * @param cCPUHeights the CPU height for the consuming slices (same order as cHosters) * @param cMemHeights the memory height for the consuming slices (same order as cHosters) * @param cEnds the moments the consuming slices will end (same order as cHosters) * @param dHosters the hoster variable for all the demanding slices * @param dCPUHeights the CPU height for the demanding slices (same order as dHosters) * @param dMemHeights the memory height for the demanding slices (same order as dHosters) * @param dStarts the moments the demanding slices will starts (same order as dHosters) */ public PlanMySlices(IEnvironment env, int me, int capacityCPU, int capacityMem, IntDomainVar[] cHosters, int[] cCPUHeights, int[] cMemHeights, IntDomainVar[] cEnds, IntDomainVar[] dHosters, int[] dCPUHeights, int[] dMemHeights, IntDomainVar[] dStarts, int[] assocs ) { super(ArrayUtils.append(dHosters, cHosters, cEnds, dStarts)); this.associations = assocs; this.me = me; this.env = env; this.capacityCPU = capacityCPU; this.capacityMem = capacityMem; this.cHosters = cHosters; this.cEnds = cEnds; this.cCPUHeights = cCPUHeights; this.cMemHeights = cMemHeights; this.out = new BitSet(this.cHosters.length); this.dHosters = dHosters; this.dStarts = dStarts; this.dCPUHeights = dCPUHeights; this.dMemHeights = dMemHeights; this.in = new BitSet(this.dHosters.length); revAssociations = new int[cCPUHeights.length]; for (int i = 0; i < revAssociations.length; i++) { revAssociations[i] = NO_ASSOCIATIONS; } for (int i = 0; i < associations.length; i++) { if (associations[i] != NO_ASSOCIATIONS) { revAssociations[associations[i]] = i; } } } @Override public void propagate() throws ContradictionException { if (isFull2()) { if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest("PlanMySlices activated"); } this.updateResourceDistribution(); computeProfiles(); checkInvariant(); updateDStartsSup(); updateCEndsSup(); updateDStartsInf(); } } /** * Update the structure of the constraints. * Set the moments for the LB and the UB. * * @throws ContradictionException if an instantiation is not consistent */ private void updateResourceDistribution() throws ContradictionException { in.clear(); for (int i = 0; i < dHosters.length; i++) { if (dHosters[i].isInstantiated() && dHosters[i].getVal() == me) { in.set(i); } } } /** * Translation for a relatives resources changes to an absolute free resources. * * @param changes the map that indicates the free CPU variation * @param sortedMoments the different moments sorted in ascending order */ private void toAbsoluteFreeResources(TIntIntHashMap changes, int[] sortedMoments) { for (int i = 1; i < sortedMoments.length; i++) { int t = sortedMoments[i]; int lastT = sortedMoments[i - 1]; int lastFree = changes.get(lastT); changes.put(t, changes.get(t) + lastFree); } } @Override public void awake() throws ContradictionException { out.clear(); for (int i = 0; i < cHosters.length; i++) { if (cHosters[i].getVal() == me) { out.set(i); } } //The amount of free resources at startup startupFreeMem = capacityMem; startupFreeCPU = capacityCPU; for (int j = out.nextSetBit(0); j >= 0; j = out.nextSetBit(j + 1)) { startupFreeCPU -= cCPUHeights[j]; startupFreeMem -= cMemHeights[j]; } this.toInstantiate = env.makeInt(dHosters.length); //Check wether some hosting variable are already instantiated for (int i = 0; i < dHosters.length; i++) { if (dHosters[i].isInstantiated()) { if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest("Already instantiated:" + dHosters[i]); } toInstantiate.set(toInstantiate.get() - 1); } } if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest("me= " + me + " cpuCapa=" + capacityCPU + ", memCapa=" + capacityMem); ChocoLogging.getBranchingLogger().finest(toInstantiate.get() + " placement variable to instantiate before activation"); } } @Override public void awakeOnInst(int idx) throws ContradictionException { if (idx < dHosters.length) { toInstantiate.set(toInstantiate.get() - 1); if (dHosters[idx].getVal() == me && me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + "-- " + dHosters[idx].getName() + " on me. Still waiting for " + toInstantiate.get()); } } this.constAwake(false); } @Override public boolean isSatisfied() { int[] vals = new int[vars.length]; for (int i = 0; i < vals.length; i++) { vals[i] = vars[i].getVal(); } return isSatisfied(vals); } @Override public boolean isSatisfied(int[] vals) { //Split this use tab to ease the analysis int[] dHostersVals = new int[dHosters.length]; int[] dStartsVals = new int[dStarts.length]; int[] cHostersVals = new int[cHosters.length]; int[] cEndsVals = new int[cEnds.length]; //dHosters, cHosters, cEnds, dStarts for (int i = 0; i < dHosters.length; i++) { dHostersVals[i] = vals[i]; dStartsVals[i] = vals[i + dHosters.length + cHosters.length + cEnds.length]; } for (int i = 0; i < cHosters.length; i++) { cHostersVals[i] = vals[i + dHosters.length]; cEndsVals[i] = vals[i + dHosters.length + cHosters.length]; } //A hashmap to save the changes (relatives to the previous moment) in the resources distribution TIntIntHashMap cpuChanges = new TIntIntHashMap(); TIntIntHashMap memChanges = new TIntIntHashMap(); for (int i = 0; i < dHostersVals.length; i++) { if (dHostersVals[i] == me) { cpuChanges.put(dStartsVals[i], cpuChanges.get(dStartsVals[i]) - dCPUHeights[i]); memChanges.put(dStartsVals[i], memChanges.get(dStartsVals[i]) - dMemHeights[i]); } } int currentFreeCPU = capacityCPU; int currentFreeMem = capacityMem; for (int i = 0; i < cHostersVals.length; i++) { if (cHostersVals[i] == me) { cpuChanges.put(cEndsVals[i], cpuChanges.get(cEndsVals[i]) + cCPUHeights[i]); memChanges.put(cEndsVals[i], memChanges.get(cEndsVals[i]) + cMemHeights[i]); currentFreeCPU -= cCPUHeights[i]; currentFreeMem -= cMemHeights[i]; } } //Now we check the evolution of the absolute free space. if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest("--- " + me + " isSatisfied() ---"); for (int i = 0; i < cHostersVals.length; i++) { ChocoLogging.getBranchingLogger().finest(me + " " + cEnds[i].pretty() + " ends at " + cEndsVals[i]); } for (int i = 0; i < dHostersVals.length; i++) { ChocoLogging.getBranchingLogger().finest(dStarts[i].pretty()); } ChocoLogging.getBranchingLogger().finest(me + " currentFreeCPU=" + currentFreeCPU); ChocoLogging.getBranchingLogger().finest(me + " currentFreeMem=" + currentFreeMem); ChocoLogging.getBranchingLogger().finest(cpuChanges.toString()); ChocoLogging.getBranchingLogger().finest(memChanges.toString()); } for (int i = 0; i < cpuChanges.keys().length; i++) { currentFreeCPU += cpuChanges.get(i); currentFreeMem += memChanges.get(i); if (currentFreeCPU < 0 || currentFreeMem < 0) { ChocoLogging.getMainLogger().severe(me + " at moment " + i + ": freeCPU=" + currentFreeCPU + ", freeMem=" + currentFreeMem); return false; } } return true; } @Override public int getFilteredEventMask(int idx) { return IntVarEvent.INSTINT_MASK; } private boolean isFull2() { return toInstantiate.get() == 0; } private void computeProfiles() { //Sur de ce qui est utilise sur la ressource profileMinCPU.clear(); profileMinMem.clear(); //Maximum simultanee dans le pire des cas sur la ressource profileMaxCPU.clear(); profileMaxMem.clear(); profileMinCPU.put(0, capacityCPU - startupFreeCPU); profileMaxCPU.put(0, capacityCPU - startupFreeCPU); profileMinMem.put(0, capacityMem - startupFreeMem); profileMaxMem.put(0, capacityMem - startupFreeMem); for (int i = out.nextSetBit(0); i >= 0; i = out.nextSetBit(i + 1)) { int t = cEnds[i].getInf(); if (associatedToDSliceOnCurrentNode(i) && dCPUHeights[revAssociations[i]] > cCPUHeights[i]) { if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + " " + cEnds[i].pretty() + " increasing"); } profileMaxCPU.put(t, profileMaxCPU.get(t) - cCPUHeights[i]); profileMaxMem.put(t, profileMaxMem.get(t) - cMemHeights[i]); } else { if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + " " + cEnds[i].pretty() + " decreasing or non-associated (" + dStarts[revAssociations[i]].pretty() + "?)"); } profileMinCPU.put(t, profileMinCPU.get(t) - cCPUHeights[i]); profileMinMem.put(t, profileMinMem.get(t) - cMemHeights[i]); } t = cEnds[i].getSup(); if (associatedToDSliceOnCurrentNode(i) && dCPUHeights[revAssociations[i]] > cCPUHeights[i]) { profileMinCPU.put(t, profileMinCPU.get(t) - cCPUHeights[i]); profileMinMem.put(t, profileMinMem.get(t) - cMemHeights[i]); } else { profileMaxCPU.put(t, profileMaxCPU.get(t) - cCPUHeights[i]); profileMaxMem.put(t, profileMaxMem.get(t) - cMemHeights[i]); } } for (int i = in.nextSetBit(0); i >= 0; i = in.nextSetBit(i + 1)) { int t = dStarts[i].getSup(); profileMinCPU.put(t, profileMinCPU.get(t) + dCPUHeights[i]); profileMinMem.put(t, profileMinMem.get(t) + dMemHeights[i]); t = dStarts[i].getInf(); profileMaxCPU.put(t, profileMaxCPU.get(t) + dCPUHeights[i]); profileMaxMem.put(t, profileMaxMem.get(t) + dMemHeights[i]); } //Now transforms into an absolute profile sortedMinProfile = null; sortedMinProfile = profileMinCPU.keys(); Arrays.sort(sortedMinProfile); sortedMaxProfile = null; sortedMaxProfile = profileMaxCPU.keys(); profileMaxCPU.keys(sortedMaxProfile); Arrays.sort(sortedMaxProfile); toAbsoluteFreeResources(profileMinCPU, sortedMinProfile); toAbsoluteFreeResources(profileMinMem, sortedMinProfile); toAbsoluteFreeResources(profileMaxCPU, sortedMaxProfile); toAbsoluteFreeResources(profileMaxMem, sortedMaxProfile); if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest("---" + me + "--- startup=(" + startupFreeCPU + "; " + startupFreeMem + ") init=(" + capacityCPU + "; " + capacityMem + ")"); for (int i = in.nextSetBit(0); i >= 0; i = in.nextSetBit(i + 1)) { ChocoLogging.getBranchingLogger().finest((dStarts[i].isInstantiated() ? "!" : "?") + " " + dStarts[i].pretty() + " " + dCPUHeights[i] + " " + dMemHeights[i]); } for (int i = out.nextSetBit(0); i >= 0; i = out.nextSetBit(i + 1)) { ChocoLogging.getBranchingLogger().finest((cEnds[i].isInstantiated() ? "!" : "?") + " " + cEnds[i].pretty() + " " + cCPUHeights[i] + " " + cMemHeights[i]); } ChocoLogging.getBranchingLogger().finest("---"); ChocoLogging.getBranchingLogger().finest("profileMin=" + prettyProfile(sortedMinProfile, profileMinCPU, profileMinMem)); ChocoLogging.getBranchingLogger().finest("profileMax=" + prettyProfile(sortedMaxProfile, profileMaxCPU, profileMaxMem)); } } private boolean associatedToDSliceOnCurrentNode(int cSlice) { if (revAssociations[cSlice] != NO_ASSOCIATIONS && in.get(revAssociations[cSlice])) { if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + " " + cEnds[cSlice].getName() + " with " + dStarts[revAssociations[cSlice]]); } return true; } return false; } private boolean associatedToCSliceOnCurrentNode(int dSlice) { if (associations[dSlice] != NO_ASSOCIATIONS && out.get(associations[dSlice])) { if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + " " + dStarts[dSlice].getName() + " with " + cEnds[associations[dSlice]]); } return true; } return false; } private String prettyProfile(int[] ascMoments, TIntIntHashMap cpuProfile, TIntIntHashMap memProfile) { StringBuilder b = new StringBuilder(); for (int i = 0; i < ascMoments.length; i++) { int t = ascMoments[i]; b.append(t); b.append(":("); b.append(cpuProfile.get(t)); b.append(","); b.append(memProfile.get(t)); b.append(")"); if (i != ascMoments.length - 1) { b.append(" "); } } return b.toString(); } private void checkInvariant() throws ContradictionException { for (int i = 0; i < sortedMinProfile.length; i++) { int t = sortedMinProfile[i]; if (profileMinCPU.get(t) > capacityCPU || profileMinMem.get(t) > capacityMem) { //if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + ": Invalid profile at moment " + t + " - " + prettyProfile(sortedMinProfile, profileMinCPU, profileMinMem)); //} fail(); } } } private void updateDStartsInf() throws ContradictionException { for (int i = in.nextSetBit(0); i >= 0; i = in.nextSetBit(i + 1)) { if (!dStarts[i].isInstantiated() && !associatedToCSliceOnCurrentNode(i)) { int dCpu = dCPUHeights[i]; int dMem = dMemHeights[i]; int lastT = -1; for (int x = sortedMinProfile.length - 1; x >= 0; x--) { int t = sortedMinProfile[x]; if (t <= dStarts[i].getInf()) { break; } int prevT = sortedMinProfile[x - 1]; if (t <= dStarts[i].getSup() && (profileMinCPU.get(prevT) + dCpu > capacityCPU || profileMinMem.get(prevT) + dMem > capacityMem)) { lastT = t; break; } } if (lastT != -1) { if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + ": " + dStarts[i].pretty() + " lb =" + lastT); } dStarts[i].setInf(lastT); } } } } private void updateDStartsSup() throws ContradictionException { int lastSup = -1; for (int i = sortedMaxProfile.length - 1; i >= 0; i--) { int t = sortedMaxProfile[i]; if (profileMaxCPU.get(t) <= capacityCPU && profileMaxMem.get(t) <= capacityMem) { lastSup = t; } else { break; } } if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + ": lastSup=" + lastSup); } if (lastSup != -1) { for (int i = in.nextSetBit(0); i >= 0; i = in.nextSetBit(i + 1)) { if (!dStarts[i].isInstantiated() && !associatedToCSliceOnCurrentNode(i) && dStarts[i].getSup() > lastSup) { int s = Math.max(dStarts[i].getInf(), lastSup); if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + ": " + dStarts[i].pretty() + " ub=" + s + ");"); } dStarts[i].setSup(s); } } } } private void updateCEndsSup() throws ContradictionException { for (int i = out.nextSetBit(0); i >= 0; i = out.nextSetBit(i + 1)) { if (!cEnds[i].isInstantiated() && !associatedToDSliceOnCurrentNode(i)) { int cCpu = cCPUHeights[i]; int cMem = cMemHeights[i]; int lastT = -1; for (int x = 0; x < sortedMinProfile.length; x++) { int t = sortedMinProfile[x]; if (t >= cEnds[i].getSup()) { break; } else if (t >= cEnds[i].getInf() && (profileMinCPU.get(t) + cCpu > capacityCPU || profileMinMem.get(t) + cMem > capacityMem)) { lastT = t; break; } } if (lastT != -1) { if (me == DEBUG) { ChocoLogging.getBranchingLogger().finest(me + ": " + cEnds[i].pretty() + " cEndsSup =" + lastT); } cEnds[i].setSup(lastT); } } } } }