Heterogeneous resources in shift rostering - optaplanner

In a shift rostering problem, how would you model a situation in which the number of employees needed depends on how good the employees are?
The advice given in the optaplanner documentation and elsewhere is that you should divide a many-to-many relationship into a many-to-one and one-to-many. In the nurserostering example, this results in a Shift, ShiftAssignment and Employee.
But in nurserostering, Shift has a fixed requiredEmployeeSize property. In my problem, I can't have a fixed value here. The number of employees required is determined by the capacity of the employees.
How would you do this?
Thanks!

Firstly, define a numeric capacity variable in your Employee class and a numeric need variable in Shift class.
Then, you can use a rule like the following one. For each shift, this rule will apply a penalty if insufficient total capacity is assigned.
rule "Insufficient Capacity Assignment"
when
$shift : Shift(need > 0, $need : need)
$totalCapacity : Number() from accumulate(
$assignment : ShiftAssignment(employee != null, shift == $shift, $capacity : employee.getCapacity() ),
sum($capacity)
)
eval($totalCapacity.intValue() < $need)
then
scoreHolder.addHardConstraintMatch(kcontext, 1, -10);
end

Related

How to model domain index with subset of set in minizinc

I am working on a nurse scheduling problem with the constraint stating that there should not be a day shift assigned to nurse after a night shift in previous day.
The constraint looks like this:
The shift set is "set of int: shift=1..7",
set of int: dayshift ={1,3,4};
set of int: dayshift={2,5,6,7};
How to model this constraint in minizinc?
I've tried:
constraint
forall(e in Employee, d in Day where d != Day[card(Day)])(
Assign[e, d, sh in NightShfit] + Assign[e, d+1, sh in DayShift] < 1
);
error: MiniZinc: type error: undefined identifier `sh'
The solution to your problem is to compute the sum of the "Assign" variables for both of the shift:
constraint forall(e in Employee, d in Day where d != Day[card(Day)])(
sum(sh in NightShift)(Assign[e, d, sh]) + sum(sh in DayShift)(Assign[e, d+1, sh]) < 1
);
As a side note I would like to remark that using 0/1 variables for these kinds of problems is only a good way of modelling for mathematical optimisation solvers (MIP). Constraint Programming (CP) solvers and Lazy Clause Generation (LCG) solvers will not work efficiently even though they are great for these kinds of problems.
My recommendation would be to have the different kinds of shift (for every day) as an enum and then assign one version of it for every employee for every day. Constraints like the ones your expressing here then often fit well into a regular constraint which performs great of both MIP and CP/LCG solvers. (In case of MIP it gets automatically transformed into a flow model).

Aggregate data in a SSAS hierarchy

I have a hierarchy in a dimension in my cube, and a fact table that references that dimension. Let's call that a hierarchy of departments and the fact table contains billing targets set for each department (on a record-per-day basis if that's of any interest).
The data is aggregated as in a usual hierarchy in a multidimensional cube.
The problem is that the data in the fact table is not just limited to the "leaf" departments in the hierarchy tree, and so each node/department may or may not have an actual value for a billing target.
I need to have my measure returning the actual value for any node if there is a value, or the sum of the values of it's descendants (if they themselves have actual values, then ignore their descendants).
I can return the value at any given node by using the .DATAMEMBER property and add a condition to substitute the null (if that is the case) with the sum of descendants as so:
with member DirectD as (iif(([Department].[Departments].currentmember.datamember, [Measures].[Department Billing Target]) = null,
([Measures].[Department Billing Target]),
([Department].[Departments].currentmember.datamember, [Measures].[Department Billing Target])))
The problem is that I cannot ensure that the same logic applies to every node "down" the hierarchy.
The MembersWithData setting is set to NonLeafDataHidden in the dimension.
[Measures].[Department Billing Target] is a calculated member, so it looks like Aggregate is not an option.
As an example, I'd like to get the value of 3000 if I query the billing target for department A (image 1):
For department C on Image 2 I need to get 1400 (E, while having descendants, has an actual value which takes precedence over the sum of it's children).
Any help would be greatly appreciated.
The Hierarchy that you mention seems to be a parentchild hierarchy since you say "The problem is that the data in the fact table is not just limited to the "leaf" departments in the hierarchy tree". Based on this, the problem is that we need to solve is
1)Foreach member of you hierarchy we call the base attribute.
2)If the measure value for the base attribute is null we retuen the summed up value
3)If the measure value for the base attribute is not null then we retuen the indivisual members value. Below is the code piece, there can be some syntax issues but would explain the idea.
The below code is a similar example on adventure works(I did add FullName attribute in the employee dimension). In the picture on right is a parent child hierarchy's, base attribute. This picture show all the employee who had sales. On the left is the hierarchy, notice how the values change in the Test measure for people who are present on the right hand side. For example take a look at "Amy E. Alberts"
The code is
with member [Measures].test as
case when [Employee].[Employees].currentmember.name='All Employees'
then [Measures].[Reseller Sales Amount]
when (strtomember('[Employee].[Full Name].['+[Employee].[Employees].currentmember.name+']'),[Measures].[Reseller Sales Amount])=null
then [Measures].[Reseller Sales Amount]
else (strtomember('[Employee].[Full Name].['+[Employee].[Employees].currentmember.name+']'),[Measures].[Reseller Sales Amount])
end
select {[Measures].[Reseller Sales Amount],[Measures].test}
on columns,
[Employee].[Employees].members
on rows from [Adventure Works]
In the end, I have added the following solution that is working so far:
IIF(NOT ISEMPTY(([Department].[Departments].CURRENTMEMBER.DATABEMBER, [Measures].[Department Billing Target Canonical])),
([Department].[Departments].CURRENTMEMBER.DATABEMBER, [Measures].[Department Billing Target Canonical]),
IIF(ISEMPTY(SUM(descendants([Department].[Departments].CURRENTMEMBER, [Department].[Departments], AFTER), IIF(
ISEMPTY(([Department].[Departments].CURRENTMEMBER.parent.DATABEMBER, [Measures].[Department Billing Target Canonical])),
([Department].[Departments].CURRENTMEMBER.DATABEMBER, [Measures].[Department Billing Target Canonical]),
NULL))),
([Department].[Departments].CURRENTMEMBER.DATABEMBER, [Measures].[Department Billing Target Canonical]),
SUM(descendants([Department].[Departments].CURRENTMEMBER, [Department].[Departments], AFTER), IIF(
ISEMPTY(([Department].[Departments].CURRENTMEMBER.parent.DATABEMBER, [Measures].[Department Billing Target Canonical])),
([Department].[Departments].CURRENTMEMBER.DATABEMBER, [Measures].[Department Billing Target Canonical]),
NULL))))
Granted it may not be the prettiest solution (in fact I'm pretty sure it's one of the ugliest), but it works for me.
The gist of it is that the algorithm checks if the parent node of the current one has any data (currentmember.parent.datamember) and if so, substitutes the current member's value with NULL. All those values are piled up in a set, and the sum of it's "contents" is the resulting value. The outmost IIF is there to cover a sort of an edge case.
EDIT: There is also a feeling that this whole issue is just a matter of some SSAS setting that has to be changed. This setting continues to elude me.

SSAS MDX Calculated Measure Based on Related Dimension Attribute Value

I have a measure [Measures].[myMeasure] that I would like to create several derivatives of based on the related attribute values.
e.g. if the related [Location].[City].[City].Value = "Austin" then I want the new calculated measure to return the value of [Measures].[myMeasure], otherwise, I want the new calculated measure to return 0.
Also, I need the measure to aggregate correctly meaning sum all of the leaf level values to create a total.
The below works at the leaf level or as long as the current member is set to Austin...
Create Member CurrentCube.[Measures].[NewMeasure] as
iif(
[Location].[City].currentmember = [Location].[City].&[Austin],
[Measures].[myMeasure],
0
);
This has 2 problems.
1 - I don't always have [Location].[City] in context.
2. When multiple cities are selected this return 0.
I'm looking for a solution that would work regardless of whether the related dimension is in context and will roll up by summing the atomic values based on a formula similar to above.
To add more context consider a transaction table with an amount field. I want to convert that amount into measures such as payments, deposits, return, etc... based on the related account.
I don't know the answer but just a couple of general helpers:
1 You should use IS rather than = when comparing to a member
2 You should use null rather than 0 - 0/NULL are effecitvely the same but using 0 will slow things up a lot as the calculation will be fired many more times. (this might help with the second section of your question)
Create Member CurrentCube.[Measures].[NewMeasure] as
iif(
[Location].[City].currentmember IS [Location].[City].&[Austin],
[Measures].[myMeasure],
NULL
);

Set 0 for specific value MDX query

I've been looking around for the answer but I didn't find anything. Sorry if the answer has been given elsewhere.
Here is my problem :
I have a calculated member which is the number of items (of the current member) divided by the total number of items (sumitem).
with
member
sumitem
as
SUM ([FailureReason].[FailureReason].[All],[Measures].[Items])
member
Impact
as
[Measures].[Items]/[Measures].[SumItem]
But for a specific member of my dimension FailureReason, the result of Impact has to be 0. So I tried to add this :
member
ImpactFinal
as
iif ([FailureReason].CurrentMember = [FailureReason].[FailureReason].&[127],
0,
Impact
)
and I select my data like this :
select
{[Measures].[Items],
ImpactFinal
} on columns,
[FailureReason].members on rows
from
NoOTAR
But instead of getting 0 only for this specific member, every members of this dimension have their ImpactFinal equals to 0. What is strange is if I replace 0 by any other value, the result is good.
Just use
[FailureReason].CurrentMember IS [FailureReason].[FailureReason].&[127]
instead of
[FailureReason].CurrentMember = [FailureReason].[FailureReason].&[127]
and it will work.
Update: Several tips:
There is also not necessary to use SUM function, since you can define only tuple, this will be enough for server: ([FailureReason].[FailureReason].[All],[Measures].[Count])
It's quite reasonable to check sumitem measure for dividing by zero in ImpactFinal calculation. Because once some filters are applied, this may cause zeroing this measure and errors in reports.
If you have an opportunity not only to query, but update cube, SCOPE ([FailureReason].[FailureReason].&[127],[Measures].[Impact]) with THIS = 0 is better than additional member because of performance.
Best of luck!
UPDATE to fix totals:
If total should be w/o FailureReason 127, you can substitute your measures with:
member Impact
as
iif ([FailureReason].[FailureReason].CurrentMember is [FailureReason].[FailureReason].&[127],
0,
[Measures].[Items]
)
member ImpactFinal
as
iif ([FailureReason].[FailureReason].CurrentMember is [FailureReason].[FailureReason].[All]
,[Measures].[Items]-([FailureReason].[FailureReason].&[127],[Measures].[Items])
,[Measures].[Impact])/[Measures].[SumItem]
But I have another solution, which is more readable:
member v2_ImpactUncountableFailure
as
iif ([FailureReason].[FailureReason].CurrentMember.Level.Ordinal=0
or
[FailureReason].[FailureReason].CurrentMember is [FailureReason].[FailureReason].&[127]
,([FailureReason].[FailureReason].&[127],[Measures].[Items])
,null)
member v2_ImpactFinal
as
([Measures].[Items]-[Measures].[v2_ImpactUncountableFailure])
/
([FailureReason].[FailureReason].[All],[Measures].[Items])
Use only this two measures instead of set of measures sumitem,Impact,ImpactFinal. First one will show result on failure-127 and total. Second subtracts it from clean unfiltered measure, so in the end we have clean members, zeroed failure-127 and corrected total.
Please let me know if it isn't work, I've tested on my DB and everything is OK.
A simple CASE statement would solve your problem: Try this
With
Member ImpactFinal As
CASE
WHEN [FailureReason].[FailureReason].CurrentMember IS [FailureReason].[FailureReason].&[127] THEN 0
ELSE
[Measures].[Items]
/
([FailureReason].[FailureReason].[All], [Measures].[Items])
END
SELECT
{ [Measures].[Items], ImpactFinal } On Columns
[FailureReason].[FailureReason].Members On Rows
From NoOTAR
Try
with
member sumitem
as
SUM ([FailureReason].[FailureReason].[All],[Measures].[Items])
member LeaveOut
as
[FailureReason].[FailureReason].CurrentMember.Properties("Key")
member Impact
as
IIf([Measures].[LeaveOut]= "127", 0, [Measures].[Items]/[Measures].[SumItem])

Slow MDX Custom Distinct Count Formula

I have a question related to creating a (more efficient) custom Distinct Count Measure using MDX.
Background
My cube has several long many to many relationship chains between Facts and Dimensions and it is important for me to be able to track which members in certain Dimensions do and do not relate to other Dimensions. As such, I have created a "Not Related" record in each of my dimension tables and set those records' ID values to -1. Then in my intermediate mapping fact tables I use the -1 ID to connect to these "Not Related" records.
The issue arises when I try to run a normal out-of-the-box distinct count on any field where the -1 members are present. In the case that a -1 member exists, the distinct count measure will return a result of 1 more than the true answer.
To solve this issue I have written the following MDX:
CREATE MEMBER CURRENTCUBE.[Measures].[Provider DCount]
AS
//Oddly enough MDX seems to require that the PID (Provider ID) field be different from both the linking field and the user sliceable field.
SUM( [Providers].[PID Used For MDX].Children ,
//Don't count the 'No Related Record' item.
IIF( NOT([Providers].[PID Used For MDX].CURRENTMEMBER IS [Providers].[PID Used For MDX].&[-1])
//For some reason this seems to be necessary to calculate the Unknown Member correctly.
//The "Regular Provider DCount Measure" below is the out-of-the-box, non-MDX measure built off the same field, and is not shown in the final output.
AND [Measures].[Regular Provider DCount Measure] > 0 , 1 , NULL )
),
VISIBLE = 1 , DISPLAY_FOLDER = 'Distinct Count Measures' ;
The Issue
This MDX works and always shows the correct answer (yeah!), but it is EXTREMELY slow when users start pulling Pivot Tables with more than a few hundred cells that use this measure. For less than 100 cells, the results are nearly instantaneously. For a few thousand cells (which is not uncommon at all), the results could take up to an hour to resolve (uggghhh!).
Can anyone help show me how to write a more efficient MDX formula to accomplish this task? Your help would be GREATLY appreciated!!
Jon Oakdale
jonoakdale#hotmail.com
Jon
You can use predefined scope to nullify all unnecessary (-1) members and than create your measure.
SCOPE ([Providers].[PID Used For MDX].&[-1]
,[Measures].[Regular Provider DCount Measure]);
THIS = NULL;
END SCOPE;
CREATE MEMBER CURRENTCUBE.[Measures].[Provider DCount]
AS
SUM([Providers].[PID Used For MDX].Children
,[Measures].[Regular Provider DCount Measure]),
VISIBLE = 1;
By the way, I used in my tests [Providers].[PID Used For MDX].[All].Children construction since don't know, what is dimension / hierarchy / ALL-level in your case. It seems like [PID Used For MDX] is ALL-level and [Providers] is name of dimension and hierarchy, and HierarchyUniqueName is set to Hide.