Get index difference on Optaplanner - Drools - drools

I'm working on a timetable scheduling problem with Drools. I have a rule that force the solver to always have a lecture on the previous timeslot (given a curriculum), at least since the first timeslot (index = 0) onwards. Example: if you have lectures on Monday from the timeslot 0 to 4 consecutively, you're ok; but if, for instance, you don't have a lecture on timeslot 2, then you have a -1 hard score.
// PeriodsWithoutLectures: All the periods must have consecutive lectures, at least since the 0 index timeslot onwards
rule "periodsWithoutLectures"
when
$curriculum : Curriculum()
Lecture(curriculumList contains $curriculum,
$day : day, $timeslotIndex : timeslotIndex, timeslotIndex > 0, period != null)
not Lecture(curriculumList contains $curriculum,
day == $day, timeslotIndex == ($timeslotIndex - 1))
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
All of the above works, but what I need to do, is to know how many "missing lectures" I have. Giving the previous example, if I have a lecture on timeslot 0 and on timeslot 4, I'm missing 3 lectures in the middle (on timeslot 1, 2 and 3), so I need a -3 hard score (with my current approach, I'll get a -1 hard score).
Any help will be really useful, thanks!
UPDATE
This is what I have now:
rule "periodsWithoutLectures"
when
$curriculum : Curriculum()
Lecture(curriculumList contains $curriculum,
$day : day, $timeslotIndex1 : timeslotIndex, period != null)
Lecture(curriculumList contains $curriculum,
$day == day, $timeslotIndex2 : timeslotIndex, timeslotIndex > $timeslotIndex1 + 1, period != null)
not Lecture(curriculumList contains $curriculum,
day == $day, timeslotIndex > $timeslotIndex1 && < $timeslotIndex2)
then
scoreHolder.addHardConstraintMatch(kcontext, ($timeslotIndex2 - $timeslotIndex1) - 1);
end

Write a rule that does something like this:
Select a lecture
Select a second lecture with a timeslot on the say day and at a later timeSlotIndex
there is no lecture between those 2 lectures (so with a timeslot index between the first and second lecture)
which then
penalize the timeslotIndex delta between the first and second lecture

Related

Assessing which person is the one with the next birthday

In Stata I am trying to assess which of the given birthdays is the next one compared with a given date. My data looks like this:
All dates are in daily format (%dD_m_Y), e.g. 18mar1926
Variable date which is the reference date with which all other dates should be compared
Variables birth1, birth2, birth3, birth4, birth5, birth6 contain the birthday of all possible household members.
For example: A household with two adults A and B. The birthday of A is 20th Nov 1977 and the birthday of person B is 30th March 1978. The reference date is 29.11.2020. I want to know who is the person who has the next birthday, in the example above it is person B, because person A has had its birthday one week before the reference date, so the next birthday in this household will be celebrated on the 30 March 2021.
Example data:
date
birth1
birth2
birth3
birth4
birth5
birth6
02feb2021
15jan1974
27nov1985
30nov2020
31aug1945
27jun1999
07apr1997
19nov2020
27sep1993
30dec1996
29jan2021
29mar1973
05dec2020
21jan1976
02oct1976
21jan1976
25may1995
15feb1997
25nov2020
25nov1943
29nov1946
02feb2021
28apr1979
EDITED to account for Feb 29
*The edit will treat people who have a February 29 birthday as if it were March 1 in cases when the year of date is not a leap year. If that doesn't make sense for your particular use case, it should be easy to alter the code below as you see fit.
Since you want the next birthday in the year rather than the closest birthday, you can use the year of date and the month and day from birth{i} to create a date for each person's next birthday. Then you can sinmply take the earliest value from each household. I reshape long, and generate a person and household id in order to do this.
Make example data
clear
set obs 6
set seed 1996
generate date = floor((mdy(12,31,2020)-mdy(12,1,2015)+1)*runiform() + mdy(12,1,2015))
format date %td
forvalue i = 1/6 {
gen birth`i' = floor((mdy(12,31,1996)-mdy(12,1,1980)+1)*runiform() + mdy(12,1,1980)) if _n < `i' == 0
format birth`i' %td
}
replace birth6 = birth4 in 6 // want a tie
replace birth2 = date("29feb1996","DMY") in 3 // Feb 29
Find Next Birthday
gen household_id = _n
reshape long birth, i(date household_id) j(person)
drop if mi(birth)
gen person_next_birthday = mdy( month(birth), day(birth), year(date))
* TREAT FEB 29 as if they have a march 1 birthday in non-leap years
replace person_next_birthday = mdy(3,1,year(date)) if month(birth) == 2 ///
& day(birth) == 29 & mod(year(date),4)!=0
replace person_next_birthday = mdy( month(birth), day(birth), year(date) + 1) if person_next_birthday < date
replace person_next_birthday = mdy(3,1,year(date)+1) if month(birth) == 2 ///
& day(birth) == 29 & mod(year(date) + 1,4)!=0 & person_next_birthday < date
format person_next_birthday %td
bysort household_id (person_next_birthday): gen next_bday = person_next_birthday[1]
format next_bday %td
drop person_next_birthday
reshape wide birth, i(date household_id next_bday) j(person)
gen next_bday_persons = ""
* Make a string to present household persons who have next bday
foreach v of varlist birth* {
local person = subinstr("`v'","birth","",.)
local condition = "month(`v') == month(next_bday) & day(`v') == day(next_bday)"
local condition_feb29 = "month(next_bday) == 3 & day(next_bday) == 1 & month(`v') == 2 & day(`v') == 29"
replace next_bday_persons = next_bday_persons + "|`person'" if `condition' | `condition_feb29'
}
replace next_bday_persons = regexr(next_bday_persons,"^\|","")
order next_bday_persons, after(next_bday)
The last loop is unnecessary, but illustrates that this is robust to ties.

Flutter/Dart Date Set firstDayOfTheweek as Sunday

By default, Monday is set as the first day of the week. The middle east region, Sunday is considered the first day of the week.
var now = DateTime.now();
print("todays date is $now");
print("week day is: ${now.weekday}");
// todays date is 2020-09-13 12:20:02.417210
// week day is: 7
since today is Sunday, I want the now.weekday should return as 1.
DateTime.weekday returns an integer in the range 1..7 that corresponds to the DateTime constants of monday, tuesday, etc. The constants are assigned internal values with Monday as the smallest value, but whether you actually treat Monday as the first day of the week is completely up to you.
But if it's easier for you to have weekday values assigned where the value for Sunday is less than the value for Monday, you could just do:
int getAdjustedWeekday(DateTime dateTime) => dateTime.weekday % 7;
And now getAdjustedWeekday will return 0 for Sunday, 1 for Monday, and so on.
If you need values starting from 1 instead of 0, then just add 1:
int getAdjustedWeekday(DateTime dateTime) => 1 + dateTime.weekday % 7;
Note that you would not be able to use the existing DateTime.sunday, DateTime.monday, etc. constants.
You need to convert like below.
var now = DateTime.now();
final now1 = DateFormat("EEEE").format(DateTime.parse(now.toString()));
print("todays date is $now1");
Or if you need int of day then go like below
Declare a day
final day = now1 == "Sunday"
? 1
: now1 == "Monday"
? 2
: now1 == "Tuesday"
? 3
: now1 == "Wednesday"
? 4
: now1 == "Thursday" ? 5 : now1 == "Friday" ? 6 : 7;
And call from widget.
print(day);

Optaplanner newbie: nurse softconstraint for weekends

I'm studying Optaplanner, and am doing some experiments with the Nursing Roster.
My goal, for this experiment, is simple: to have nurse "1" be more in favor, and more likely, to work weekends.
I have written the following rules to help make this happen:
rule "nurseNamed1WorksWeekends"
when
$oneNurse: Employee( name = "1")
$wk : ShiftAssignment( isWeekend = true)
then
scoreHolder.addSoftConstraintMatch(kcontext, 1);
end
rule "nurseNamed1MustNotWorkWeekdays"
when
$oneNurse: Employee( name = "1")
not $wk : ShiftAssignment( isWeekend = false)
then
scoreHolder.addSoftConstraintMatch(kcontext, 1);
end
However, after running the sample for some time, nurse "1" still never ends up working weekends.
What am I doing wrong?
Thanks
Edit of rule according to laune's suggestions but optaplanner is still reluctant to put the nurse on weekend shifts:
rule "nurseNamed1WorksWeekends"
when
$oneNurse: Employee( name == "1", )
$wk : ShiftAssignment( isWeekend == true, employee == $oneNurse)
then
scoreHolder.addSoftConstraintMatch(kcontext, 1);
end
rule "nurseNamed1MustNotWorkWeekdays"
when
$oneNurse: Employee( name == "1")
not ShiftAssignment( isWeekend = false, employee == $oneNurse)
then
scoreHolder.addSoftConstraintMatch(kcontext, 1);
end
Don't use = in your constraints - test for equality is expressed using ==.
If the getter for a boolean is called isWeekend, the constraint should be written as
ShiftAssignment( weekend == true )
ShiftAssignment( weekend == false )
or, (for me) preferably
ShiftAssignment( weekend )
ShiftAssignment( ! weekend )
A binding variable in a Conditional Element such as $wk in
not $wk : ShiftAssignment( ! isWeekend )
doesn't make sense. The rule fires if there is no such ShiftAssignment - and then what would $wk being bound to?
The CE
not ShiftAssignment( ! weekend )
is strange: the rule fires if and only if there is no ShiftAssignment for any weekday around at all - not likely.
Adding a value higher than one in the "WorksWeekends" rule should favour nurse 1 on weekends.
Later
rule dislikeNurseOneOnWeekdays
when
$oneNurse: Employee( name == "1")
ShiftAssignment( isWeekend = false, employee == $oneNurse)
then
scoreHolder.addSoftConstraintMatch(kcontext, -1);
end
Using a smaller value (e.g. -10) will make it even harder for the First Nurse to work on weekdays: ten shifts during the weekend are needed to balance one during the week.

How to convert an official DateTime to the astronomically exact DateTime?

I've been trying to figure out how to convert a birthday (DateTime) to the astronomically "exact" DateTime value. Timezone: UTC+1.
Example:
My friend was born 1984-01-27 11:35
1984 is a leap year. But 1700, 1800 and 1900 were not leap years. So until the 29. February of the year 2000 we are running behind in astronomoically exact time. In 1984 we are "almost" one day behind. So the astronomoically exact time would be after the official DateTime of my friend's birth, right?
These are the Gregorian calendar tweaks I know of:
Every year has 365 days
Every 4th year is a leap year (= has 366 days instead of 365)
Every 100th year is not a leap year
Every 400th year is a leap year (dispite the previous rule)
The additional day is added at the end of February (February has 29 days in a leap year)
Astronomoically a year has 365,2422 days.
Which means that a day is 24,0159254794 hours long.
A time value where the official and astronomoical times are "exactly" the same would be 2000-03-01T00:00:00, right?
So one would need to figure out how big the discrepancy between the official time and the astronomically exact time is at a given official time.
I've been thinking about it for hours, until my head started hurting. I figured I'll share my headache with you. Maybe you guys know any time library that can calculate this?
I came up with a "solution" that seems to be fairly accurate enough. Here's what it does:
The method starts at 1600-03-01T00:00. 18 years after Pope Gregor XIII. (after whom our Gregorian Calendar system is named) fixed the Julian Calendar (named after Julius Caesar) in 1582 by declaring that after the 4th October (Thursday) the next day would be the 15th October (Friday) - so there is actually no 5th to 14th October 1582 in history books - and also adding the 100th and 400th year rules to the calendar system.
The method sums up the discrepany between the official date and the exact date until the given date is reached.
At leap years it applies the correction added by Pope Gregor XIII. It does so at the end of February.
Code:
public static DateTime OfficialDateTimeToExactDateTime(DateTime dtOfficial)
{
const double dExactDayLengthInHours = 24.0159254794;
DateTime dtParse = new DateTime(1600, 3, 1, 0, 0, 0);
double dErrorInHours = 0.0;
while (dtParse <= dtOfficial)
{
dErrorInHours += dExactDayLengthInHours - 24;
dtParse = dtParse.AddDays(1);
if (dtParse.Month == 3 && dtParse.Day == 1 &&
((dtParse.Year % 4 == 0 && dtParse.Year % 100 != 0) ||
(dtParse.Year % 400 == 0)) )
{
dErrorInHours -= 24;
}
}
dErrorInHours += ((double)dtOfficial.Hour + (double)dtOfficial.Minute / 60 + (double)dtOfficial.Second / 3600) * (dExactDayLengthInHours - 24);
return dtOfficial.AddHours(dErrorInHours * -1);
}
I did some sanity testing:
If you pass a date before 2000-03-01T00:00 you get a negative correction. Because we measure days shorter as they in fact are.
If you pass a date after 2000-03-01T00:00 you get a positive correction. This is because 2000 is a leap year (while 1700, 1800 and 1900 are not), but the correction applied is too big. In 24 x 400 = 4800 years the correction would be about one day too big. So in the year 1600 + 4800 = 6400 (if man is still alive), you would need to delcare 6400 a non-leap year, despite the rules of the Gregorian calendar.

return CalendarTime value

User gives: year, month, day, hour and minute and I would like to return CalendarTime value using these values. How can I do that - because I have error: 'Couldn't match expected type IO CalendarTime against inferred type Int'. What is wrong in function getDateTime ?
Proposition: all values are correct, for example month is from range 1 - 12 etc - I deleted validation from below code because otherwise code would be too long.
isInteger i = not (null i) && all isDigit i
getInteger :: String -> IO Int
getInteger q = do
putStr q;
i <- getLine
if isInteger i == False then do
putStrLn "Bad number"
getInt q
else return (read i)
getDateTime :: String -> IO CalendarTime
getDateTime question = do
putStr question;
year <- getInteger "Year: "
month <- getInteger "Month: "
day <- getInteger "Day: "
hour <- getInteger "Hour: "
minute <- getInteger "Minute: "
return CalendarTime(year month day hour minute 0)
This line
return CalendarTime(year month day hour minute 0)
is read by the compiler as
return CalendarTime (year month day hour minute 0)
So it's not a call to CalendarTime. Instead you're feeding CalendarTime and (year month day hour minute 0) as arguments return. This is nonsense as return only takes a single argument and yearis not a function. You probably meant
return (CalendarTime year month day hour minute 0)
though this still doesn't work since CalendarTime requires more arguments than that. (Assuming we're talking about the one in System.Time since you didn't specify the imports).
You probably want
return $ CalendarTime { ctYear = year
, ctMonth = toEnum (month-1)
, ctDay = day
, ctHour = hour
, ctMinute = minute
, ctSec = 0 })
This leaves the missing fields undefined, see the relevant section in Real World Haskell for more information.
You also misspelled getInteger as getInt in the recursive call in the first function, but I'm assuming that's a result of your cleanup of the validation code.
You don't say exactly which line gives the error, but I'm guessing the problem is:
return CalendarTime(year month day hour minute 0)
In Haskell, parentheses are used only for grouping. Function application is implicit by writing one term after the other. return should be applied to the CalendarTime value, so you probably wanted this instead:
return (CalendarTime year month day hour minute 0)
Although this would be more idiomatic:
return $ CalendarTime year month day hour minute 0
It looks like you are trying to use the constructor CalendarTime like a function from other language styles.
return (CalendarTime year month day hour minute 0)