Check week-ends in an interval - optaplanner

I need to check in the last four weeks that my agent has at least one week-end with no work.
I don't know how to join the sunday and saturday (with no work) and count them.
Here is what i tried :
private Constraint oneWeekendForFourWeeks(ConstraintFactory constraintFactory) {
return constraintFactory
.from(Workingday.class)
.filter(Workingday::isSunday) // we keep only sundays
.join(constraintFactory.from(Workingday.class)
.filter(Workingday::isSunday),
equal(Workingday::getAgent),
Joiners.filtering((wd1, wd2)->{
LocalDate quatreSemaineAvant = wd1.getDayJava().minusWeeks(4);
Boolean wd2isAfterfourWeeksBeforeWd1 = wd2.getDayJava().compareTo(fourWeeksBefore) >= 0;
return wd2isAfterfourWeeksBeforeWd1 && wd2.getDayJava().isBefore(wd1.getDayJava());
})
)
.groupBy((sunday1, sunday2) -> sunday2)// i got my 4 sundays
.join(constraintFactory.from(Workingday.class) // i try to get the 4 saturdays
.filter(Workingday::isSaturday),
equal(Workingday::getAgent),
Joiners.filtering((sunday, saturday)->{
LocalDate oneDayBefore = sunday.getDayJava().minusDays(1);;
return saturday.getDayJava().equals(oneDayBefore);
})
)
.filter((saturday, sunday) -> {
return !saturday.hasRestdayBySolver() && !sunday.hasRestdayBySolver();
})
.groupBy((saturday, sunday) -> saturday, countBi())
.filter((saturday, count)->count > 3) // if we got more than 4 weekends without rest day -> we penalize
.penalizeConfigurable(ONE_WEEKEND_FOR_FOUR_WEEKS);
}
In debug, i can count 4 sundays, but for my second join it join me only one saturday. Maybe the problem is here ?
Thanx for your help !
Edit :
this is the solution i found, maybe not optimized :
private Constraint oneWeekendForFourWeeks(ConstraintFactory constraintFactory) {
return constraintFactory
.from(WorkingDay.class)
.filter(WorkingDay::isMonday)
.join(constraintFactory.from(WorkingDay.class)
.filter(WorkingDay::isSaturday),
equal(WorkingDay::getAgent),
Joiners.filtering((monday, saturday) -> {
LocalDate fourWeeksBefore = monday.getDayJava().minusWeeks(4);
Boolean wd2isAfterfourWeeksBeforeWd1 = saturday.getDayJava().compareTo(fourWeeksBefore) >= 0;
return wd2isAfterfourWeeksBeforeWd1 && saturday.getDayJava().isBefore(monday.getDayJava());
})
)
.join(constraintFactory.from(WorkingDay.class)
.filter(WorkingDay::isSunday)
)
.filter((monday, saturday, sunday) -> {
LocalDate unDayAfter = saturday.getDayJava().plusDays(1);
return sunday.getDayJava().equals(unDayAfter) && sunday.getAgent().equals(monday.getAgent());
})
.groupBy((monday, saturday, sunday) -> monday,
sum((monday, saturday, sunday) -> {if (saturday.hasRestdayBySolver() && sunday.hasRestdayBySolver()) {return 1;} else {return 0;}})
)
.filter((monday, sum) -> sum == 0)
.penalizeConfigurable(ONE_WEEKEND_FOR_FOUR_WEEKS);
}

How about something like this?
.from(Workingday.class)
.filter(WorkingDay::isWeekendDay // returns true if Saturday or Sunday
&& WorkingDay::isLastFourWeeks)
.groupBy(Workingday::getAgent,
countDistinct(workingDay
-> workingDay.getLocalDate.toEpochDay() - (isSunday ? 1 : 0))
.filter((agent, weekendCount) -> weekendCount >= 4)
.penalize()
We bascially group by agent and count the distinct number of weekends. Each weekend is identified by the number of days since the epoch of the first day of that weekend (= the Saturday).
Elegant, no?

Related

Difference between Dates - Kotlin

Guys!
I need some help here. It's basically a logical question.
I have a Date of publication of an item.
Example: 10/12/2022
I need a function to calculate this date from now, with a few rules:
If the difference is less than an hour, returns string: "Published 15 minutes ago"
If the difference is less than a day, returns string: "Published 4 hours ago"
If the difference is greater than a day, returns string: "Published in 10/12/2022"
How can I do this?
Thanks a lot for you help!
private fun calculateTime(date: Long): String {
return ""
}
Assuming you don't actually use long as parameter but a temporal unit, the solution could look like this
fun calculateTime(date: Instant): String {
val passedTime = Duration.between(date, Instant.now())
return if (passedTime.toHours() < 1)
"Published 15 minutes ago"
else if (passedTime.toDays() < 1)
"Published 4 hours ago"
else {
DateTimeFormatter.ofPattern("dd/MM/yyyy")
.withZone(ZoneId.systemDefault())
.format(date)
}
}

Solver doesnt try to optimize solution ( one hard constraint, one soft constraint )

I got 2 constraints :
one HARD ( 100 score )
one SOFT ( 100 score )
When i run the solver, it's like he just try to resolve the hard one, and doesn't look for the soft one. I have no softScore, optaplanner return a 0Hard/0Medium/0Soft.
My HARD constraint is : a worker can't work more than 5 days in a row
My SOFT constraint is : try to put the most of working days possible ( calculated by hours of work )
My test is for two weeks. A worker need to be above 66 hours of work, is he is under we penalize more.
Here the two constraints in JAVA:
the SOFT one :
private Constraint averageWorkingHours(ConstraintFactory constraintFactory) {
return constraintFactory
.from(WorkingDay.class)
.filter((wd) -> wd.isFreeToWork())
.groupBy(WorkingDay::getAgent,
WorkingDay::hasBreakDayBySolver,
// hasBreakDayBySolver return if the solver has put my
#PlanningVariable ( a breakDay ) in the WorkingDay
ConstraintCollectors.count())
.filter((agent, hasBreakDayBySolver, count) -> {
return !hasBreakDayBySolver;
})
.penalizeConfigurable(AVERAGE_HOURS_WORKING, ((agent, hasBreakDayBySolver, count) -> {
// a worker need to be above 66 hours of work for 2 weeks
// We penalize more if a worker is under the average of working hours wanted for two weeks ( 66 )
if(count * 7 < 66){ // count * hours worked for one day
return (66 - count * 7) * 2 ;
}
else{
return count * 7 - 66;
}
}));
}
the HARD one :
private Constraint fiveConsecutiveWorkingDaysMax(ConstraintFactory constraintFactory) {
return constraintFactory
.from(WorkingDay.class)
.filter(WorkingDay::hasWork)
.join(constraintFactory.from(WorkingDay.class)
.filter(WorkingDay::hasWork),
Joiners.equal(WorkingDay::getAgent),
Joiners.greaterThan(wd->wd.getDayJava()),
Joiners.filtering((wd1, wd2)->{
LocalDate fourDaysBefore = wd1.getDayJava().minusDays(4);
Boolean wd2isAfterFourDaysBeforeWd1 = wd2.getDayJava().compareTo(fourDaysBefore) >= 0;
return wd2isAfterFourDaysBeforeWd1;
})
)
.groupBy((wd1, wd2) -> wd2, ConstraintCollectors.countBi())
.filter((wd2, count) -> count >= 4)
.penalizeConfigurable(FIVE_CONSECUTIVE_WORKING_DAYS_MAX,((wd2, count)-> count - 3));
}
I hope my explanations are clear.
Thanx !
Unit test your constraints, using a ConstraintVerfier. See also this short video by Lukas.
Verify that your #ConstraintWeight for that soft constraint in your dataset isn't zero.

Reducing downtime (idle time) when arriving early for a visit

Optaplanner is being used to plan the routes of a fleet of vehicles and I am optimizing the route times.
I have a scenario where I have one visit with time windows in the morning and the second visit with time window in the afternoon. However the vehicle leaves when the time window opens, makes the first delivery and heads to the second visit. Since the second visit has a time window in the afternoon, the vehicle has to wait for the time window of this visit to open, which introduces downtime. This downtime (idle time) can be reduced by leaving the depot later. So I would like to ask if:
Is there any rule to q backtrack to the depot or to the previous visit, wait longer to continue, and thereby reduce the downtime or waiting time on the second visit?
I have tried different variants:
1- I implemented a constraint to penalize if early to a visit and penalize with customer -> customer.getReadyTime() - customer.getArrivalTime().
This may optimize but it does not roll back the arrivalTime.
2- Modify my listener (ArrivalTimeUpdatingVariableListener, updateArrivalTime method).When calculating the arrival time, if there is idle time, I go to the previous visit and subtract the idle time. However, in some cases it does not recursively update all previous visits correctly, and in other cases it gives me a "VariableListener corruption". I have had no success for this variant either.
Is there any rule to wait or roll back and update all visits again?
I attach my constraint and listener for better context.
ArrivalTimeUpdatingVariableListener.class:
protected void updateArrivalTime(ScoreDirector scoreDirector, TimeWindowedVisit sourceCustomer) {
Standstill previousStandstill = sourceCustomer.getPreviousStandstill();
Long departureTime = previousStandstill == null ? null
: (previousStandstill instanceof TimeWindowedVisit)
? ((TimeWindowedVisit) previousStandstill).getArrivalTime() + ((TimeWindowedVisit) previousStandstill).getServiceDuration()
: ((PlanningVehicle) previousStandstill).getDepot() != null
? ((TimeWindowedDepot) ((PlanningVehicle) previousStandstill).getDepot()).getReadyTime()
: 0;
TimeWindowedVisit shadowCustomer = sourceCustomer;
Long arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
while (shadowCustomer != null && !Objects.equals(shadowCustomer.getArrivalTime(), arrivalTime)) {
scoreDirector.beforeVariableChanged(shadowCustomer, "arrivalTime");
shadowCustomer.setArrivalTime(arrivalTime);
scoreDirector.afterVariableChanged(shadowCustomer, "arrivalTime");
departureTime = shadowCustomer.getDepartureTime();
shadowCustomer = shadowCustomer.getNextVisit();
arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
}
}
private Long calculateArrivalTime(TimeWindowedVisit customer, Long previousDepartureTime) {
long arrivalTime = 0;
if (customer == null || customer.getPreviousStandstill() == null) {
return null;
}
if (customer.getPreviousStandstill() instanceof PlanningVehicle) {
arrivalTime = Math.max(customer.getReadyTime(),
previousDepartureTime + customer.distanceFromPreviousStandstill());
} else {
arrivalTime = previousDepartureTime + customer.distanceFromPreviousStandstill();
// to reach backwards and (attempt to) shift the previous arrival time.
Standstill previousStandstill = customer.getPreviousStandstill();
long idle = customer.getReadyTime() - arrivalTime;
if (previousStandstill != null && idle > 0) {
arrivalTime += idle;
if (previousStandstill instanceof TimeWindowedVisit) {
long previousArrival = ((TimeWindowedVisit) previousStandstill).getArrivalTime() + idle;
if (previousArrival > ((TimeWindowedVisit) previousStandstill).getDueTime()){
System.out.println("Arrival es mayor que el duetime");
previousArrival = ((TimeWindowedVisit) previousStandstill).getDueTime() - ((TimeWindowedVisit) previousStandstill).getServiceDuration();
}
((TimeWindowedVisit) previousStandstill).setArrivalTime(previousArrival);
}
}
}
// breaks
return arrivalTime;
}
ConstraintProvider.class:
private Constraint arrivalEarly(ConstraintFactory constraintFactory) {
return constraintFactory.from(TimeWindowedVisit.class)
.filter((customer) -> !customer.getVehicle().isGhost() && customer.getArrivalTime() < customer.getReadyTime())
.penalizeConfigurableLong(
VehicleRoutingConstraintConfiguration.MINIMIZE_IDLE_TIME,
customer -> customer.getReadyTime() - customer.getArrivalTime());
}

Changing displayed value depending on a second digit from a number

I have a vue filter and display some values. I would like to change the value when the value's last digit is between 2 and 4. I need to have an automatised code so it works for all possible intervals, for example: (22-24, 32-34, 622-624...).
I managed to find the last digit of a value: value.toString()[value.toString().length - 1]. I am not sure how to solve this problem, maybe I should come up with a special matematical formula?
myfilter: (value): => {
if(value >= 22 && value <= 24) {
return 'my new value'
}
}
You can use the modulus operator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Remainder_()
myfilter: (value): => {
if(value % 10 <= 4 && value % 10 >= 2 ) {
return 'my new value'
}
}

Getting multiple date ranges between SQL query and adding up the data accordingly

I need to select a certain range of dates after I have already completed the query SELECT * FROM <DB>.
For example:
var (date string
views int
impressions int)
for query.Next() {
err := query.Scan(&date, &views, &impressions)
// handle the err
// get the range of dates for each month
// add up all the views and impressions in that specific range
}
The 'date' var will obviously be all of the dates in the database query.
Dates are formatted as: 2017-10-01 (October 1st as an example) and there are about 300 in October and 100 in November.
From here, I need to add up all the values (views and impressions), but only per date range.
So I would get something like:
2017-10-01 to 2017-10-31 has 54 impressions
2017-10-01 to 2017-10-07 has 5 impressions as an example.
Any idea how I'd come about this issue?
So, the best bet in these cases is to use a map-style strategy to keep track. For example, a map[date]data would allow you to keep a unique entry for each date. Dates, however, have a beneficial optimization, in that they can easily be represented by integers (the day of the year), and the number of options is small enough to not be a memory issue. This means we can use a slice instead of a map and get the benefits of ordering (Go maps are randomly ordered in a for loop) while still using it like a map. For example:
type Data struct {
// fields
}
const dateFormat = "2006-01-02" // only parse the date
dayStats := make([]Data, 366) // account for leap years
for query.Next() {
var datestr string // can make this a time.Time, if your date format scans properly
var dr Data
if err := query.Scan(datestr, /* other fields */ ); err != nil {
log.Fatal(err)
}
date, err := time.Parse(datestr)
if err != nil {
log.Fatal(err)
}
dayStats[date.YearDay()].someField += dr.someField
dayStats[date.YearDay()].someOtherField += dr.someOtherField
// other fields...
}
Now let's say we want to calculate the stats between a 01 October and 31 October:
start := time.Date(2017, time.October, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2017, time.October, 31, 0, 0, 0, 0, time.UTC)
var total Data
for day := start.YearDay(); day <= end.YearDay(); day++ {
total.someField += dayStats[day].someField
total.someOtherField += dayStats[day].someOtherField
// other fields...
}