Skip to content

Commit

Permalink
Rock tour: first constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
ge0ffrey committed May 25, 2018
1 parent 18df0ac commit 5448f4b
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 13 deletions.
Binary file modified optaplanner-examples/data/rocktour/unsolved/48shows.xlsx
Binary file not shown.
Expand Up @@ -27,6 +27,8 @@ public class RockBus extends AbstractPersistable implements RockStandstill {
private RockLocation endLocation;
private LocalDate endDate;

private RockShow nextShow;

public RockBus() {
}

Expand All @@ -35,6 +37,11 @@ public RockLocation getDepartureLocation() {
return startLocation;
}

@Override
public LocalDate getDepartureDate() {
return startDate;
}

@Override
public RockLocation getArrivalLocation() {
return endLocation;
Expand Down Expand Up @@ -75,4 +82,15 @@ public LocalDate getEndDate() {
public void setEndDate(LocalDate endDate) {
this.endDate = endDate;
}

@Override
public RockShow getNextShow() {
return nextShow;
}

@Override
public void setNextShow(RockShow nextShow) {
this.nextShow = nextShow;
}

}
Expand Up @@ -20,9 +20,13 @@
import java.util.NavigableSet;

import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.variable.AnchorShadowVariable;
import org.optaplanner.core.api.domain.variable.CustomShadowVariable;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.api.domain.variable.PlanningVariableGraphType;
import org.optaplanner.core.api.domain.variable.PlanningVariableReference;
import org.optaplanner.examples.common.domain.AbstractPersistable;
import org.optaplanner.examples.rocktour.domain.solver.RockShowDateUpdatingVariableListener;

@PlanningEntity
public class RockShow extends AbstractPersistable implements RockStandstill {
Expand All @@ -37,6 +41,16 @@ public class RockShow extends AbstractPersistable implements RockStandstill {
@PlanningVariable(valueRangeProviderRefs = {"busRange", "showRange"}, graphType = PlanningVariableGraphType.CHAINED)
private RockStandstill previousStandstill;

private RockShow nextShow;

@AnchorShadowVariable(sourceVariableName = "previousStandstill")
private RockBus bus;

@CustomShadowVariable(variableListenerClass = RockShowDateUpdatingVariableListener.class,
sources = {@PlanningVariableReference(variableName = "previousStandstill"),
@PlanningVariableReference(variableName = "bus")})
private LocalDate date;

public RockShow() {
}

Expand All @@ -45,11 +59,24 @@ public RockLocation getDepartureLocation() {
return location;
}

@Override
public LocalDate getDepartureDate() {
return date;
}

@Override
public RockLocation getArrivalLocation() {
return location;
}

public long getDistanceFromPreviousStandstill() {
return previousStandstill.getDepartureLocation().getDrivingTimeTo(location);
}

public long getDistanceToBusArrivalLocation() {
return location.getDrivingTimeTo(bus.getArrivalLocation());
}

@Override
public String toString() {
return venueName;
Expand Down Expand Up @@ -106,4 +133,31 @@ public RockStandstill getPreviousStandstill() {
public void setPreviousStandstill(RockStandstill previousStandstill) {
this.previousStandstill = previousStandstill;
}

@Override
public RockShow getNextShow() {
return nextShow;
}

@Override
public void setNextShow(RockShow nextShow) {
this.nextShow = nextShow;
}

public RockBus getBus() {
return bus;
}

public void setBus(RockBus bus) {
this.bus = bus;
}

public LocalDate getDate() {
return date;
}

public void setDate(LocalDate date) {
this.date = date;
}

}
Expand Up @@ -16,13 +16,24 @@

package org.optaplanner.examples.rocktour.domain;

import java.time.LocalDate;

import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.variable.InverseRelationShadowVariable;

@PlanningEntity
public interface RockStandstill {

/**
* @return never null;
*/
RockLocation getDepartureLocation();

/**
* @return sometimes null;
*/
LocalDate getDepartureDate();

/**
* @return never null;
*/
Expand All @@ -36,4 +47,9 @@ default long getDrivingTimeTo(RockStandstill standstill) {
return getDepartureLocation().getDrivingTimeTo(standstill.getArrivalLocation());
}

@InverseRelationShadowVariable(sourceVariableName = "previousStandstill")
RockShow getNextShow();

void setNextShow(RockShow nextShow);

}
Expand Up @@ -24,7 +24,7 @@
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.examples.common.domain.AbstractPersistable;

@PlanningSolution
Expand All @@ -35,14 +35,15 @@ public class RockTourSolution extends AbstractPersistable {
@ProblemFactProperty
private RockTourParametrization parametrization;

@ProblemFactProperty
private RockBus bus;

@PlanningEntityCollectionProperty
@ValueRangeProvider(id = "showRange")
private List<RockShow> showList;

@PlanningScore
private HardSoftScore score = null;
private HardSoftLongScore score = null;

public RockTourSolution() {
}
Expand Down Expand Up @@ -92,11 +93,11 @@ public void setShowList(List<RockShow> showList) {
this.showList = showList;
}

public HardSoftScore getScore() {
public HardSoftLongScore getScore() {
return score;
}

public void setScore(HardSoftScore score) {
public void setScore(HardSoftLongScore score) {
this.score = score;
}

Expand Down
@@ -0,0 +1,83 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.optaplanner.examples.rocktour.domain.solver;

import java.time.LocalDate;
import java.util.Objects;

import org.optaplanner.core.impl.domain.variable.listener.VariableListener;
import org.optaplanner.core.impl.score.director.ScoreDirector;
import org.optaplanner.examples.rocktour.domain.RockShow;
import org.optaplanner.examples.rocktour.domain.RockStandstill;

public class RockShowDateUpdatingVariableListener implements VariableListener<RockShow> {

@Override
public void beforeEntityAdded(ScoreDirector scoreDirector, RockShow show) {
// Do nothing
}

@Override
public void afterEntityAdded(ScoreDirector scoreDirector, RockShow show) {
updateDate(scoreDirector, show);
}

@Override
public void beforeVariableChanged(ScoreDirector scoreDirector, RockShow show) {
// Do nothing
}

@Override
public void afterVariableChanged(ScoreDirector scoreDirector, RockShow show) {
updateDate(scoreDirector, show);
}

@Override
public void beforeEntityRemoved(ScoreDirector scoreDirector, RockShow show) {
// Do nothing
}

@Override
public void afterEntityRemoved(ScoreDirector scoreDirector, RockShow show) {
// Do nothing
}

protected void updateDate(ScoreDirector scoreDirector, RockShow sourceShow) {
RockStandstill previousStandstill = sourceShow.getPreviousStandstill();
LocalDate arrivalDate = calculateArrivalDate(sourceShow,
previousStandstill == null ? null : previousStandstill.getDepartureDate());
RockShow shadowShow = sourceShow;
while (shadowShow != null && !Objects.equals(shadowShow.getDate(), arrivalDate)) {
scoreDirector.beforeVariableChanged(shadowShow, "date");
shadowShow.setDate(arrivalDate);
scoreDirector.afterVariableChanged(shadowShow, "date");
LocalDate previousDepartureDate = shadowShow.getDepartureDate();
shadowShow = shadowShow.getNextShow();
arrivalDate = calculateArrivalDate(shadowShow, previousDepartureDate);
}
}

private LocalDate calculateArrivalDate(RockShow show, LocalDate previousDepartureDate) {
if (show == null || previousDepartureDate == null) {
return null;
}
LocalDate arrivalDate = previousDepartureDate.plusDays(1);
arrivalDate = show.getAvailableDateSet().ceiling(arrivalDate);
return arrivalDate;
}

}
Expand Up @@ -23,10 +23,10 @@
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.swing.JPanel;

Expand All @@ -42,7 +42,8 @@ public class RockTourWorldPanel extends JPanel {

private static final int TEXT_SIZE = 12;
private static final int LOCATION_NAME_TEXT_SIZE = 8;
private static final NumberFormat NUMBER_FORMAT = new DecimalFormat("#,##0.00");
protected static final DateTimeFormatter DAY_FORMATTER
= DateTimeFormatter.ofPattern("E yyyy-MM-dd", Locale.ENGLISH);

private final RockTourPanel rockTourPanel;

Expand Down Expand Up @@ -79,16 +80,21 @@ public void resetPanel(RockTourSolution solution) {

Graphics2D g = createCanvas(width, height);
g.setFont(g.getFont().deriveFont((float) LOCATION_NAME_TEXT_SIZE));
g.setColor(TangoColorFactory.PLUM_2);
List<RockShow> showList = solution.getShowList();
int maxAvailableDateSetSize = showList.stream().mapToInt(show -> show.getAvailableDateSet().size()).max().orElse(-1);
for (RockShow show : showList) {
RockLocation location = show.getLocation();
int x = translator.translateLongitudeToX(location.getLongitude());
int y = translator.translateLatitudeToY(location.getLatitude());
double percentage = (double) show.getAvailableDateSet().size() / maxAvailableDateSetSize;
g.setColor(TangoColorFactory.buildPercentageColor(TangoColorFactory.PLUM_3, TangoColorFactory.PLUM_1, percentage));
g.fillRect(x - 1, y - 1, 3, 3);
if (location.getCityName() != null && showList.size() <= 500) {
g.drawString(StringUtils.abbreviate(location.getCityName(), 20), x + 3, y - 3);
}
if (show.getDate() != null) {
g.drawString(DAY_FORMATTER.format(show.getDate()), x + 3, y - 3 + LOCATION_NAME_TEXT_SIZE * 3 /2);
}
}
g.setColor(TangoColorFactory.ALUMINIUM_4);
RockLocation busStartLocation = bus.getStartLocation();
Expand Down Expand Up @@ -120,13 +126,14 @@ public void resetPanel(RockTourSolution solution) {
}
}
}
g.setFont(g.getFont().deriveFont((float) TEXT_SIZE));
// Legend
g.setColor(TangoColorFactory.ALUMINIUM_4);
g.fillRect(5, (int) height - 15 - TEXT_SIZE, 5, 5);
g.fillRect(5, (int) height - 17 - TEXT_SIZE, 5, 5);
g.drawString("Bus start", 15, (int) height - 10 - TEXT_SIZE);
g.setColor(TangoColorFactory.PLUM_2);
g.fillRect(6, (int) height - 9, 3, 3);
g.drawString("Show", 15, (int) height - 5);
g.fillRect(6, (int) height - 11, 3, 3);
g.drawString("Show (darker means less available)", 15, (int) height - 5);
repaint();
}

Expand Down
Expand Up @@ -17,18 +17,40 @@
package org.optaplanner.examples.rocktour.solver;
dialect "java"

import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScoreHolder;

import org.optaplanner.examples.rocktour.domain.RockBus;
import org.optaplanner.examples.rocktour.domain.RockShow;
import org.optaplanner.examples.rocktour.domain.RockStandstill;
import org.optaplanner.examples.rocktour.domain.RockTourParametrization;

global HardSoftScoreHolder scoreHolder;
global HardSoftLongScoreHolder scoreHolder;

// ############################################################################
// Hard constraints
// ############################################################################

rule "Required show"
when
RockShow(required == true, date == null)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end

// ############################################################################
// Soft constraints
// ############################################################################

rule "Distance from previous standstill"
when
RockShow(previousStandstill != null, $distanceFromPreviousStandstill : distanceFromPreviousStandstill)
then
scoreHolder.addSoftConstraintMatch(kcontext, - $distanceFromPreviousStandstill);
end

rule "Distance to bus arrival location"
when
RockShow(nextShow == null, bus != null, $distanceToBusArrivalLocation : distanceToBusArrivalLocation)
then
scoreHolder.addSoftConstraintMatch(kcontext, - $distanceToBusArrivalLocation);
end
Expand Up @@ -19,6 +19,7 @@
<!--<environmentMode>FAST_ASSERT</environmentMode>-->
<solutionClass>org.optaplanner.examples.rocktour.domain.RockTourSolution</solutionClass>
<entityClass>org.optaplanner.examples.rocktour.domain.RockShow</entityClass>
<entityClass>org.optaplanner.examples.rocktour.domain.RockStandstill</entityClass>

<scoreDirectorFactory>
<scoreDrl>org/optaplanner/examples/rocktour/solver/rockTourScoreRules.drl</scoreDrl>
Expand Down

0 comments on commit 5448f4b

Please sign in to comment.