How to reduce SoftScore with OptaPlanner and Spring Boot? - java

I am trying to specify a rule for a soft constraint in my .drl-file. It is supposed to take two parameters from the PlanningSolution (Schedule-class) and then execute a Java-Function with these. Sadly the code seems not to be executed (there is no SoftScore modified, even if I replace the function call getBlockNumberDifference with a plain -10). Can someone relate to this issue?
I have another rule that specifies a HardConstraint that also uses a function call which is working perfectly fine.
The Planning Solution:
#PlanningSolution
public class Schedule {
private Semester semester;
#PlanningEntityCollectionProperty
public List<Lecture> getLectureList() {
return lectureList;
}
public void setLectureList(List<Lecture> lectureList) {
this.lectureList = lectureList;
}
public Semester getSemester() {
return semester;
}
public void setSemester(Semester semester) {
this.semester = semester;
}
}
The rule:
import function (...).getBlockNumberDifference;
//...
rule "rule"
when
Schedule ( $s : semester != null && $l : lectureList != null)
then
scoreHolder.addSoftConstraintMatch(kcontext, getBlockNumberDifference($l, $s));
end
Test:
public static int getBlockNumberDifference(List<Lecture> lectureList, Semester semester) {
System.out.println("Calling Block number Difference " + lectureList.size() + " and " + semester.getBezeichnung());
return -1;
}
I am using OptaPlanner in Version 7.9.0 with Spring Boot and Java 8.

The planning solution isn't inserted into the working memory of Drools IIRC, so the LHS (the when side) of that rule never matches.
I might be wrong on this - to prove this, make it when Schedule() then System.out.println("not in wm");end and see if you see that appear.

Related

How to understand these two soft constraints in the tsp problem?

I understand that the soft constraint in the tsp problem is to find the shortest total distance, but I see that the two drools constraints in the example seem to express the distance of each segment and the distance from the last visited city to the starting point? It seems that the distance is not calculated. And, can you explain these two constraints in detail? I'm a beginner, and I don't understand a little bit.
rule "distanceToPreviousStandstill"
when
$visit : Visit(previousStandstill != null, $distanceFromPreviousStandstill : distanceFromPreviousStandstill)
then
scoreHolder.addConstraintMatch(kcontext, - $distanceFromPreviousStandstill);
end
rule "distanceFromLastVisitToDomicile"
when
$visit : Visit(previousStandstill != null)
not Visit(previousStandstill == $visit)
$domicile : Domicile()
then
scoreHolder.addConstraintMatch(kcontext, - $visit.getDistanceTo($domicile));
end
In DRL (unlike in Java), calling Visit(..., $distanceFromPreviousStandstill : distanceFromPreviousStandstill) means its calling the getDistanceFromPreviousStandstill() getter:
public class Visit extends AbstractPersistable implements Standstill {
...
public long getDistanceFromPreviousStandstill() {
if (previousStandstill == null) {
return 0L;
}
return getDistanceFrom(previousStandstill);
}
}
If you prefer Java over DRL, take a look at the ConstraintProvider alternative implementation, which is written in Java, uses incremental score calculation too (= can scale), is equally fast (8.3.0+), easier to debug and has code highlighting and code completion in any Java IDE:
public final class TspConstraintProvider implements ConstraintProvider {
#Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[] {
distanceToPreviousStandstill(constraintFactory),
distanceFromLastVisitToDomicile(constraintFactory)
};
}
private Constraint distanceToPreviousStandstill(ConstraintFactory constraintFactory) {
return constraintFactory.from(Visit.class)
.penalizeLong("Distance to previous standstill",
SimpleLongScore.ONE,
Visit::getDistanceFromPreviousStandstill);
}
private Constraint distanceFromLastVisitToDomicile(ConstraintFactory constraintFactory) {
return constraintFactory.from(Visit.class)
.ifNotExists(Visit.class, Joiners.equal(visit -> visit, Visit::getPreviousStandstill))
.join(Domicile.class)
.penalizeLong("Distance from last visit to domicile",
SimpleLongScore.ONE,
Visit::getDistanceTo);
}
}

How To solve “Bailing out of neverEnding selector to avoid infinite loop” error when I've used #PlanningPin In Optaplanner?

I want to use "pin" feature of optaplanner for immovable operations. However i get "Bailing out of neverEnding selector (Filtering(FromSolutionEntitySelector(PersonAssignment))) to avoid infinite loop." error. I've tried both with #PlanningPin and movableEntitySelectionFilter. Optaplanner version is 7.32.0 final, also i tried with version 7.44.0.Final. I've searched a lot and as can i see you've solved this problem in version 7.31.0.Final.
I've generated my domain and it is working well. Idea of my problem is there are more then one hotel. People come to hotel at different hours. Each hotel has different capacity and i want to asign people to a hotel if there is enough space. I've also many rules about assignment. My domain model consists of two #PlanningEntity,customShadowVariable and also TimeGrain. Structure is below:
#PlanningEntity(movableEntitySelectionFilter = PersonAssignmentSelectionFilter.class)
public class PersonAssignment extends AbstractPersistable {
private Person person;
private TimeGrain startingTimeGrain;
private TimeGrain endTime; // This added
private HotelDomain hotelDomain;
public TimeGrain getStartingTimeGrain() {
return startingTimeGrain;
}
public void setStartingTimeGrain(TimeGrain startingTimeGrain) {
this.startingTimeGrain = startingTimeGrain;
}
public TimeGrain getEndTime() {
return endTime;
}
public void setEndTime(TimeGrain endTime) {
this.endTime = endTime;
}
#PlanningVariable(valueRangeProviderRefs = { "hotelDomainRange" }, nullable = true)
public HotelDomain getHotelDomain() {
return hotelDomain;
}
public void setHotelDomain(HotelDomain hotelDomain) {
this.hotelDomain = hotelDomain;
}
......
}
#DeepPlanningClone
public class HotelDomain extends AbstractPersistable {
private String hotelName;
#CustomShadowVariable(variableListenerRef = #PlanningVariableReference(variableName = "tightOccupancy, personAssignment))
private Map<Integer, HotelOccupancyPerSlot> hotelOccupancyMap;
......
}
#PlanningEntity
public class HotelOccupancyPerSlot extends AbstractPersistable {
#CustomShadowVariable(variableListenerClass = HotelDomainVariableListener.class, sources = {
#PlanningVariableReference(entityClass = PersonAssignment.class, variableName = "hotelDomain") })
private Integer tightOccupancy; // days
#CustomShadowVariable(variableListenerRef = #PlanningVariableReference(variableName = "tightOccupancy"))
private List<PersonAssignment> personAssignments;
.......
}
public class HotelDomainVariableListener implements VariableListener<PersonAssignment> {
......
}
config.xml is:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<solver>
<moveThreadCount>3</moveThreadCount> <!-- To solve faster by saturating multiple CPU cores -->
<solutionClass>com.domain.HotelAccomodation</solutionClass>
<entityClass>com.domain.PersonAssignment</entityClass>
<entityClass>com.domain.HotelOccupancyPerSlot</entityClass>
<scoreDirectorFactory>
<scoreDrl>solver/solverScoreRules.drl</scoreDrl>
</scoreDirectorFactory>
<termination>
<minutesSpentLimit>15</minutesSpentLimit>
</termination>
</solver>
If i use #PlannigPin in HotelOccupancyPerSlot there is no problem but i want to use it(or filter) only in PersonAssignment class because it is my basic class. Is there any suggestion? Should i add something to config?
Thank You :)
The "Bailing out" warning happens when a move selection filter is filtering out every move (it never returns true). It's not an error, but a warning, because there if you see it only once, it could be a harmless fluke in theory in some cases.
Anyway, when does a filter return false for all selected moves? In these cases:
A) you configured a custom move filter in solverConfig.xml that never returns true
B) the solution value range has no values - OptaPlanner checks for that ins 7.44+ and does the right thing automatically. Not sure if it does the right thing if there are 2 planning variables and only 1 has no values...
C) the solution value range has a single value - that would mean the move isDoable() false. I don't recall this being a problem though.
D) the solution value range has no non-pinned value. See B)
E) the solution value range has only 1 non-pinned value. See C)
Take a look at your input data. Are you dealing with case B, C, D or E by any chance?

AspectJ inter-type field not recognized in advice

I'm essentially trying to track the number of transfers for an Account class.
Reading the docs here: https://www.eclipse.org/aspectj/doc/released/progguide/language-anatomy.html
And on slide 48 and 49 here: https://www.eclipse.org/aspectj/doc/released/progguide/language-anatomy.html
These tell me I should be able to do something like this:
public aspect LoggingAspect {
private int Account.transferCount = 0;
private int Account.getTransferCount() {
return transferCount;
}
pointcut firstTransfer(Account s, double amount):
withincode(public void transfer (int, int, double))
&& call(public boolean withdraw(int,double))
&& target(s)
&& args(amount);
boolean around(Account s, double amount):
firstTransfer(s, amount){
s.transferCount++; // Not recognized
if (s.getTransferCount() == 0) { // Not recognized
System.out.println("50% markup");
return s.deposit(amount*.5);
}
return false;
}
}
However, as commented in the code above, the fields are not recognized as existing on the class within the aspect. What am I doing wrong?
The error I get is: transferCount cannot be resolved or is not a field
Something is happening in the Account class which unfortunately you didn't share here. Please learn what an MCVE is and why it is so valuable to always provide one. Especially in the context of AOP it is even more important because an aspect does not make much sense without a target class. I cannot debug one without the other, which is why I had to invent my own dummy class. That would actually have been your job.
Probably you are trying to use the declared private members directly from within the Account class. For a reason I do not understand yet, this does not work because it throws off the AspectJ compiler with a The method getTransferCount() from the type Account is not visible or similar error message. This must be a limitation or a bug in AspectJ, I will ask the maintainer and report back here later.
But first let us reproduce your situation:
Application class:
package de.scrum_master.app;
public class Account {
public void transfer(int a, int b, double c) {
withdraw(a, c);
}
public boolean withdraw(int a, double c) {
return true;
}
public boolean deposit(double amount) {
return true;
}
public static void main(String[] args) {
Account account = new Account();
account.transfer(11, 22, 33.33);
account.withdraw(44, 55.55);
account.transfer(66, 77, 88.88);
account.withdraw(99, 11.11);
// [error] The method getTransferCount() from the type Account is not visible
System.out.println(account.getTransferCount());
}
}
Aspect:
First let me mention that I fixed two errors in your code:
Your pointcut will only match if you bind the arguments correctly. double amount is the second of two method parameters, not the only one. Thus you have to write args(*, amount) instead of args(amount)
You increment transferCount before checking for s.getTransferCount() == 0, so the if condition will never match. What you want is s.getTransferCount() == 1.
package de.scrum_master.aspect;
import de.scrum_master.app.Account;
public aspect LoggingAspect {
private int Account.transferCount = 0;
private int Account.getTransferCount() {
return transferCount;
}
pointcut firstTransfer(Account s, double amount) :
withincode(public void transfer (int, int, double)) &&
call(public boolean withdraw(int, double)) &&
target(s) &&
args(*, amount);
boolean around(Account s, double amount) : firstTransfer(s, amount) {
s.transferCount++;
if (s.getTransferCount() == 1) {
System.out.println("50% markup");
return s.deposit(amount * .5);
}
return false;
}
}
Now in Eclipse I see the compile error in the application class and due to the failed compilation the subsequent problem in the aspect itself. As soon as you comment out the last line of the main method, it works. (Maybe you have to re-save the aspect or recompile the project in order to make the squiggly lines disappear.)
Actually the easiest thing to do is to make getTransferCount() public instead of private. Getters are usually public and you then can also use the method from the main method again and the program output would become:
50% markup
2
BTW, inside the aspect you do not need to use getTransferCount(). Just like in the line above, you can directly access the field.
Update: I promised you an answer to the question why the target class cannot access fields and methods declared as private via ITD: because they are private with respect to the aspect itself! This answer comes from the AspectJ maintainer himself, please read the full answer here.

logic using functional-style exception handling with java and Vavr

I'm trying to get into basics of functional programming with Java 8 and I have a simple task which is to set a property on the object and then persist it. The database proper type is ltree so it might fail if it contains not allowed characters. I want to process items one-by-one and log exceptions/successes.
I choose to use the Vavr library because Try.of() exception handling and I want to learn to just use it as it seems very helpful.
here is what I came up with but I'm not satisfied enough:
public class PathHandler {
private final DocVersionDAO dao;
public void processWithHandling() {
Try.of(this::process)
.recover(x -> Match(x).of(
Case($(instanceOf(Exception.class)), this::logException)
));
}
private Stream<Try<DocVersion>> logException(Exception e) {
//log exception now but what to return? also I would like to have DocVersion here too..
return null;
}
public Stream<Try<DocVersion>> process() {
return dao.getAllForPathProcessing() //returns Stream<DocVersion>
.map(this::justSetIt)
.map(this::save);
}
public DocVersion justSetIt(DocVersion v) {
String path = Optional.ofNullable(v.getMetadata().getAdditionals().get(Vedantas.PATH))
.orElse(null);
log.info(String.format("document of uuid %s has matadata path %s; setting it", v.getDocument2().getUUID(), path));
v.getDocument2().setPath(path);
return v;
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Try<DocVersion> save(DocVersion v) {
return Try.of(() -> dao.save(v));
}
}
the goal is quite simple so could you teach me proper way to do it?
I'm afraid, this will become highly opinionated. Anyway, I try something.
... which happened before I realized, what Vavr actually provides. It attempts to cover everything mentioned here, like immutable data structures and monad syntax sugaring (with the For statement), and goes beyond that by coming up even with pattern matching. It takes a comprehensive set of FP concepts and rebuilds them using Java and it is no surprise Scala comes into one's mind seeing this ("Vavr is greatly inspired by Scala").
Now the foundations of functional programming can't be covered by a single SO post. And it might be problematic to get familiar with them in a language like Java which isn't geared towards it. So perhaps it is better to approach them in their natural habitat like the Scala language, which is still in some proximity to Java, or Haskell, which is not.
Coming back from this detour applying the features of Vavr may be more straight foward for the initiated. But likelely not for the Java developer sitting next to you in the office, who is less willing to go the extra mile and comes up with arguments that can't be just dismissed, like this one: "If we wanted to it that way, we would be a Scala shop". Therefore I'd say, applying Vavr asks for a pragmatic attitute.
To corroborate the Vavra-Scala argument, let's take Vavra's For construct (all Lists mentioned are io.vavr.collection.List), it looks like this:
Iterator<Tuple2<Integer, String>> tuples =
For(List.of(1, 2, 3), i ->
For(List.of(4, 5, 6))
.yield(a -> Tuple.of(i, String.valueOf(a))));
In Scala you'd encounter For and yield this way.
val tuples = for {
i <- 1 to 3
a <- 4 to 6
} yield (i, String.valueOf(a))
All the monad machinery remains under the hood, where Vavra brings more of an approximation, necessarily leaking some internals. For the purpose of learning it might be puzzling to start with Vavra's hybrid creatures.
So what remains of my post is a small time treatment of some FP basics, using the example of the OP, elaborating on immutability and Try on a trench-level, but omitting pattern matching. Here we go:
One of the defining characteristics of FP are functions free of side effects ("pure functions"), which naturally (so to speak) comes along with immutable data structures/objects, which may sound kind of weird. One obvious pay off is, that you don't have to worry, that your operations create unintended changes at some other place. But Java doesn't enforce that in any way, also its immutable collections are only so on a superficial level. From the FP signature characteristics Java only offers higher order functions with java-lambdas.
I used the functional style quite a bit on the job manipulating complicated structures where I stuck to those 2 principles. E.g. load a tree T of objects from a db, do some transformations on it, which meant producing another tree of objects T', sort of one big map operation, place the changes in front of the user to accept or reject them. If accepted, apply the changes to the related JPA entities and persist them. So after the functional transformation two mutations were applied.
I'd propose, to apply FP in this sense and tried to formulate an according version of your code, using an immutable DocVersion class. I chose to simplify the Metadata part for the sake of the example.
I also tried to highlight, how the "exception-free" Try approach (some of it poached from here) could be formulated and utilized some more. Its a small time version of Vavr's Try, hopefully focusing on the essentials. Note its proximity to Java's Optional and the map and flatMap methods in there, which render it an incarnation of the FP concept called monad. It became notorious in a sweep of highly confusing blog posts some years ago usually starting with "What is a monad?" (e.g. this one). They have cost me some weeks of my life, while it is rather easy to get a good intuition of the issue just by using Java streams or Optionals. Miran Lipovaca's "Learn Yourself a Haskell For Great Good" later made good for it to some extent, and Martin Odersky's Scala language.
Boasting with of, map and flatMap, Try would, roughly speaking, qualify for a syntax-sugaring like you find it in C# (linq-expressions) or Scala for-expressions. In Java there is no equivalent, but some attempts to at least compensate a bit are listed here, and Vavr looks like another one. Personally I use the jool library occasionally.
Passing around streams as function results seems not quite canonical to me, since streams are not supposed to get reused. That's also the reason to create a List as an intermediary result in process().
public class PathHandler {
class DocVersionDAO {
public void save(DocVersion v) {
}
public DocVersion validate(DocVersion v) {
return v;
}
public Stream<DocVersion> getAllForPathProcessing() {
return null;
}
}
class Metadata {
#Id
private final Long id;
private final String value;
Metadata() {
this.id = null;
this.value = null;
}
Metadata(Long id, String value) {
this.id = id;
this.value = value;
}
public Optional<String> getValue() {
return Optional.of(value);
}
public Metadata withValue(String value) {
return new Metadata(id, value);
}
}
public #interface Id {
}
class DocVersion {
#Id
private Long id;
private final Metadata metadatata;
public Metadata getMetadatata() {
return metadatata;
}
public DocVersion(Long id) {
this.id = id;
this.metadatata = new Metadata();
}
public DocVersion(Long id, Metadata metadatata) {
this.id = id;
this.metadatata = metadatata;
}
public DocVersion withMetadatata(Metadata metadatata) {
return new DocVersion(id, metadatata);
}
public DocVersion withMetadatata(String metadatata) {
return new DocVersion(id, this.metadatata.withValue(metadatata));
}
}
private DocVersionDAO dao;
public List<DocVersion> process() {
List<Tuple2<DocVersion, Try<DocVersion>>> maybePersisted = dao.getAllForPathProcessing()
.map(d -> augmentMetadata(d, LocalDateTime.now().toString()))
.map(d -> Tuple.of(d, Try.of(() -> dao.validate(d))
.flatMap(this::trySave)))
.peek(i -> i._2.onException(this::logExceptionWithBadPracticeOfUsingPeek))
.collect(Collectors.toList());
maybePersisted.stream()
.filter(i -> i._2.getException().isPresent())
.map(e -> String.format("Item %s caused exception %s", e._1.toString(), fmtException(e._2.getException().get())))
.forEach(this::log);
return maybePersisted.stream()
.filter(i -> !i._2.getException().isPresent())
.map(i -> i._2.get())
.collect(Collectors.toList());
}
private void logExceptionWithBadPracticeOfUsingPeek(Exception exception) {
logException(exception);
}
private String fmtException(Exception e) {
return null;
}
private void logException(Exception e) {
log(fmtException(e));
}
public DocVersion augmentMetadata(DocVersion v, String augment) {
v.getMetadatata().getValue()
.ifPresent(m -> log(String.format("Doc %d has matadata %s, augmenting it with %s", v.id, m, augment)));
return v.withMetadatata(v.metadatata.withValue(v.getMetadatata().value + augment));
}
public Try<DocVersion> trySave(DocVersion v) {
return new Try<>(() -> {
dao.save(v);
return v;
});
}
private void log(String what) {
}
}
Try looks like this
public class Try<T> {
private T result;
private Exception exception;
private Try(T result, Exception exception) {
this.result = result;
this.exception = exception;
}
public static <T> Try<T> of(Supplier<T> f)
{
return new Try<>(f);
}
T get() {
if (result == null) {
throw new IllegalStateException();
}
return result;
}
public void onException(Consumer<Exception> handler)
{
if (exception != null)
{
handler.accept(exception);
}
}
public <U> Try<U> map(Function<T, U> mapper) {
return exception != null ? new Try<>(null, exception) : new Try<>(() -> mapper.apply(result));
}
public <U> Try<U> flatMap(Function<T, Try<U>> mapper) {
return exception != null ? null : mapper.apply(result);
}
public void onError(Consumer<Exception> exceptionHandler) {
if (exception != null) {
exceptionHandler.accept(exception);
}
}
public Optional<Exception> getException() {
return Optional.of(exception);
}
public Try(Supplier<T> r) {
try {
result = r.get();
} catch (Exception e) {
exception = e;
}
}
}

Drools: Rules involving a Map from String to Object

I need help writing a Drools rule. I have two classes named Context and CreditReport.
Context is inserted as a fact into the knowledge session before the rules are fired.
I need to write a rule that prints 'Excellent' on the console when the Credit Score is more than 800.
Ideally, I'd insert CreditReport directly into the session, but unfortunately I do not have that option.
The rule that I've written doesn't look good as:
The then part has an if statement
I am type-casting Object to CreditReport
Thanks for your help!
// Context.java
public class Context {
private Map<String, Object> data = Maps.newHashMap();
public <T> T getData(final String key, final Class<T> clazz) {
return clazz.cast(data.get(key));
}
public void putData(final String key, final Object value) {
this.data.put(key, value);
}
}
// CreditReport.java
public class CreditReport {
private final String name;
private final int creditScore;
public String getName() {
return this.name;
}
public int getCreditScore() {
return this.creditScore;
}
}
// Main method
context.put("creditReport", new CreditReport("John", 810));
session.insert(Arrays.asList(context));
session.fireAllRules();
// Rule
rule "Excellent_Score"
when Context($creditReportObject : getData("creditReport"))
then
final CreditReport creditReport = (CreditReport) $creditReportObject;
if (creditReport.getCreditScore() >= 800) {
System.out.println("Excellent");
}
end
What makes you insert a List<Context> containing a single Context object? The Java code should do
context.put("creditReport", new CreditReport("John", 810));
session.insert( context );
session.fireAllRules();
The rule can now be written as
rule "Excellent_Score"
when
Context( $creditReportObject : getData("creditReport") )
CreditReport( creditScore > 800 ) from $creditReportObject
then
System.out.println("Excellent");
end
You could, of course, get and insert the CreditReport from Context. - I suspect it's more convoluted that what you've shown, but "I do not have that option" is a code smell anyway.
Edit A single rule for more than one reason for printing "excellent" could be written like the one below, although this isn't much better that two rules, considering that you can wrap the RHS into a method or DRL function.
rule "Excellent_Score_2"
when
Context( $creditReport : getData("creditReport"),
$account: getData("account") )
( CreditReport( creditScore > 800 ) from $creditReport
or
Account( balance >= 5000 ) from $account )
then
System.out.println("Excellent");
end

Categories