My main question is, if I can use in OptaPlanner custom implementation of ScoreHolder in drools score calculation? And if yes, how I can do it, as the scoreHolder object is injected implicitly to global variable? Below you can find details, why I would like to use custom implementation of ScoreHolder.
I have found one problem during my work on an application that need to optimize value of some production. I have devices and based on forecasts I calculate production for each device.
I use OptaPlanner and I have rule like following:
when
$device : Device($deviceId : id)
$forecast : Forecast(deviceId == $deviceId)
then
int deviceProduction = $device.calculateProduction($forecast);
scoreHolder.addSoftConstraintMatch(kcontext, deviceProduction);
end
So after this, soft score will contain amount of overall production (overallProduction). What I want to have, is to keep VALUE of overall production in the soft score. The problem is, that value of production is not just overallProduction multiplied by price. There is a limit of production and everything over the limit have negative price. So we have given limit and two prices: positive(positive_price) and negative(negative_price). The schema of value calculation looks like following:
if( overallProduction <= limit)
value = positive_price * overallProduction;
else
value = positive_price * limit + negative_price * (overallProduction - limit);
So I think, I can't include calculation of this value inside rule calculated production for single device, because I'm able to calculate this value of overall production, only after collection of production from all devices.
My only idea how I can handle it, is to have custom implementation of scoreHolder extending HardSoftScoreHolder. This custom ScoreHolder would keep internally, in soft sore, overall production. It will be counted by rule given at the beginning. And then, the proper value will be calculated by custom ScoreHolder, just before returning result from extractScore method.
The question is, if I can use my custom ScoreHolder inside drools score calculation? As global scoreHolder object is injected implicitly..
Or if there is other way how I can put value of overall production into score object?
Thanks for your help and best regards,
Instead of hacking the ScoreHolder, I done it like Geoffrey De Smet suggested, thanks!
rule "SingleProduction"
when
$device : Device($deviceId : id)
$forecast : Forecast(deviceId == $deviceId)
then
int deviceProduction = $device.calculateProduction($forecast);
insertLogical(new SingleProduction(deviceProduction));
end
rule "ValueOfTotalProduction"
when
ProductionLimit( $limit : value )
PositivePrice( $positivePrice : value )
NegativePrice( $negativePrice : value )
Number( $totalProduction : intValue() )
from accumulate( SingleProduction( $value : value ),
sum( $value ) )
then
int productionValue;
if($totalProduction <= $limit)
productionValue = $totalProduction * $positivePrice;
else
productionValue = $limit * $positivePrice +
($totalProduction - $limit) * $negativePrice;
scoreHolder.addSoftConstraintMatch(kcontext, productionValue);
end
Hacking the ScoreHolder is not the way to do this (although by creating a custom ScoreDefinition you can do it that way).
Instead, your rule can use an insertLogical() to insert the intermediate results as a new fact, instead of modifying the ScoreHolder directly. Then another rule matches on that intermediate result (probably with a lower salience) and modifies the ScoreHolder accordingly. See the nurse rostering example for an example.
Laune's way would also work.
Related
We have a use-case where we want to present the user with some human-readable message with why an "assignment" was rejected based on the score of the constraints.
For e.g. in the CloudBalancing problem with 3 computers (Computer-1,2,3) and 1 process (Process-1) we ended up with the below result:
Computer-1 broke a hard constraint (requiredCpu)
Computer-2 lost due to a soft constraint (min cost)
Computer-3 assigned to Process-1 --> (Optimal solution)
We had implemented the BestSolutionChanged listener where we used solution.explainScore() to get some info and enabled DEBUG logging which provided us the OptaPlanner internal logs for intermediate moves and their scores. But the requirement is to provide some custom human readable information on why all the non-optimal solutions (Computer-1, Computer-2) were rejected even if they were infeasible (basically explanation of scores of these two solutions).
So wanted to know how can we achieve the above ?
We did not want to rely on listening to BestSolutionChanged event as
it might not get triggered for other solutions if the LS/CH
phase starts with a solution which is already a "best solution"
(Computer-3). Is this a valid assumption ?
DEBUG logs do provide us with the
information but building a custom message from this log does not seem
like a good idea so was wondering if there is another
listener/OptaPlanner concept which can be used to achieve this.
By "all the non-optimal solutions", do you mean instead a particular non-optimal solution? Search space can get very large very quickly, and OptaPlanner itself probably won't evaluate the majority of those solutions (simply because the search space is so large).
You are correct that BestSolutionChanged event will not fire again if the problem/solution given to the Solver is already the optimal solution (since by definition, there are no solutions better than it).
Of particular interest is ScoreManager, which allows you to calculate and explain the score of any problem/solution:
(Examples taken from https://www.optaplanner.org/docs/optaplanner/latest/score-calculation/score-calculation.html#usingScoreCalculationOutsideTheSolver)
To create it and get a ScoreExplanation do:
ScoreManager<CloudBalance, HardSoftScore> scoreManager = ScoreManager.create(solverFactory);
ScoreExplanation<CloudBalance, HardSoftScore> scoreExplanation = scoreManager.explainScore(cloudBalance);
Where cloudBalance is the problem/solution you want to explain. With the
score explanation you can:
Get the score
HardSoftScore score = scoreExplanation.getScore();
Break down the score by constraint
Collection<ConstraintMatchTotal<HardSoftScore>> constraintMatchTotals = scoreExplanation.getConstraintMatchTotalMap().values();
for (ConstraintMatchTotal<HardSoftScore> constraintMatchTotal : constraintMatchTotals) {
String constraintName = constraintMatchTotal.getConstraintName();
// The score impact of that constraint
HardSoftScore totalScore = constraintMatchTotal.getScore();
for (ConstraintMatch<HardSoftScore> constraintMatch : constraintMatchTotal.getConstraintMatchSet()) {
List<Object> justificationList = constraintMatch.getJustificationList();
HardSoftScore score = constraintMatch.getScore();
...
}
}
and get the impact of individual entities and problem facts:
Map<Object, Indictment<HardSoftScore>> indictmentMap = scoreExplanation.getIndictmentMap();
for (CloudProcess process : cloudBalance.getProcessList()) {
Indictment<HardSoftScore> indictment = indictmentMap.get(process);
if (indictment == null) {
continue;
}
// The score impact of that planning entity
HardSoftScore totalScore = indictment.getScore();
for (ConstraintMatch<HardSoftScore> constraintMatch : indictment.getConstraintMatchSet()) {
String constraintName = constraintMatch.getConstraintName();
HardSoftScore score = constraintMatch.getScore();
...
}
}
In sales order, there is a field discount %. Is it possible to ensure the users only input value lower than x and it will not accept any value higher than x.
You can achieve this with creating a function using #api.onchange('discount') decorator on python code to ensure discount value is not higher than x, if higher then set x to discount field and also possible to show warning popup from that function.
But if python code change is not prefered, you can also achieve this with Automated action where you create a new rule on sale.order.line, set trigger On form modification, action Execute python code and add the following code
if record.discount > 10:
record.discount = 10
Where 10 is the value of x. This will ensure discount is always less than x.
Op1: If you want to ensure that behaviour you can add an sql constraint to the database, this will work fine if your X value never change, something like this:
_sql_constraints = [
('discount_less_than_X', 'CHECK(discount < X)', 'The discount value should be less than X'),
]
This constraint will trigger in all cases(create, write, import, SQL insert).
Replace X with the value desired.
Op2: Use decorator api.constrains, get X value from somewherelse and apply the restriccion, something like this:
#api.constrains('discount')
def _check_discount(self):
# Create a key:value parameter in `ir.config_parameter` table before.
disscount_max_value = self.env['ir.config_parameter'].sudo().get_param('disscount_max_value')
for rec in self:
if rec.disscount > int(disscount_max_value):
raise ValidationError('The discount value should be less than {}'.format(disscount_max_value))
I hope this answer can be helful for you.
My User_ID has several images and with this piece of code I receive the largest IMAGE_NR .
max({$<USER_ID = {'8638087'}> } IMAGE_NR)
Every image number is linked to an IMAGE_ID. How do I get this?
In words:
Give me IMAGE_ID where IMAGE_NR = largest IMAGE_NR of my USER_ID
I tried following that doesn't work:
sum({$<max({<USER_ID={'8638087'}> } IMAGE_NR)>} IMAGE_ID)
Thank you for any thoughts!
Here is a step-by-step solution.
You mention that this already brings back the correct IMAGE_NR:
max({$<USER_ID = {'8638087'}> } IMAGE_NR)
Now you want the IMAGE_ID. The logic is:
=only({CORRECT_IMAGE_NR_SET_ANALYSIS} IMAGE_ID)
I generally prefer to avoid search mode (double quotes inside element list) and instead use a higher evaluation level on the top calculation, so I would recommend:
$(=
'only({<IMAGE_NR={'
& max({$<USER_ID = {'8638087'}> } IMAGE_NR)
& '}>} IMAGE_ID)'
)
This would also provide you a nice preview formula in the formula editor, something like:
only({<IMAGE_NR={210391287}>} IMAGE_ID)
I didn't test it, but i believe it's something like :
only({<IMAGE_NR={'$(=max(IMAGE_NR))'}, USER_ID={'8638087'}>} IMAGE_ID)
If the dimension of the table is USER_ID then this will return the IMAGE_ID of the maximum IMAGE_NR per USER_ID. Should work / return results for any dimension, but then will have to be read as maximum IMAGE_NR per whatever that dimesion is. The minus on IMAGE_NR is to get the largest/last sorted value
firstsortedvalue(IMAGE_ID,-IMAGE_NR)
If you're using it in a text box with no dimension then you can also add the set analysis
firstsortedvalue({<USER_ID={'8638087'}>} IMAGE_ID,-IMAGE_NR)
I'm trying to add a string length field to an index. Ideally, I'd like to use the kibana script feature as I can 'add' this field later but I keep getting a null_pointer_exception with the following code... I'm trying to sort in a visualization based on the fields length.
doc['field'].value ? doc['field'].length() : 0
Is this correct?
I thought it was because my field isn't always set (sparse data), but I added the ?:0 to combat that (which didn't work)
Any ideas?
You can define an scripted field in Kibana, of type int, language painless, and try this:
return (doc['field'].value != null? doc['field'].value.length(): 0);
I want to add lower limit constraints in portfolio optimization in Optaplanner
exercise.
In Portfolio optimization in Optaplanner, we can control ratio limit by controlling sector constraints, and can only control upper limit value which is given as Quantity Maximum.
I've tried 'add lower limit as Quantity Minimum, and change Hardscore by quantity minimum similarly as quantity maximum.' However, I failed to apply it.
Anyway, is there any better way to add lower limit constraints in portfolio optimization in Optaplanner?
Plz Help me!
If it's for example per region, just add quantityMillisMinimum field on Region:
public class Region extends AbstractPersistable {
private String name;
private Long quantityMillisMaximum; // In milli's (so multiplied by 1000)
// New field
private Long quantityMillisMinimum; // In milli's (so multiplied by 1000)
}
and a score rule to add the hard constraint:
// New rule
rule "Region quantity minimum"
when
$region : Region($quantityMillisMinimum : quantityMillisMinimum)
accumulate(
AssetClassAllocation(region == $region, quantityMillis != null, $quantityMillis : quantityMillis);
$quantityMillisTotal : sum($quantityMillis);
$quantityMillisTotal < $quantityMillisMinimum
)
then
scoreHolder.addHardConstraintMatch(kcontext,
$quantityMillisTotal - $quantityMillisMinimum);
end
Are you using portfolio optimization for anything serious? :)