CX Works

A single portal for curated, field-tested and SAP-verified expertise for your SAP C/4HANA suite. Whether it's a new implementation, adding new features, or getting additional value from an existing deployment, get it here, at CX Works.

Non-Deterministic Product Rules Execution in SAP CPQ Engine

SAP CPQ (Configure, Price, and Quote) product rules engine works in non-deterministic fashion. This is its key characteristic and if not taken into account during a project detail design and implementation, the configurator module will certainly perform non-optimally and potentially give invalid results. This article will aim to give detailed insights on this topic.

Table of Contents


In General on the Engine's Characteristic 

The SAP CPQ product rules engine is, as stated, non-deterministic. This means, you cannot know in advance the number of times that product's rules will run.

The engine will execute all rules for a product, per every user action in configuration, as many times as needed, all up until two consecutive iterations result with identical sets of values for all product attributes. We call this state 'stable' and this is when the engine will decide to stop.

This is one of the biggest changes when somebody moves to SAP CPQ modeling (from a tool that runs on a different execution methodology) and will certainly require time to adapt to. The execution pattern described can be the cause of strong performance issues and requires attention when any complex products are built (which is the case with most SAP CPQ implementations).

Why does the engine work that way? The answer lies in SAP CPQ customization potentials and availability to influence one parameter from multiple places and in many different ways. An obvious solution was to allow product implementers to build their models using all means that SAP CPQ provides, then let the engine decide how to reach a stable state for a configuration, after every end-user click.

Do not forget that SAP CPQ allows product owners to add simple rules, formula rules, and IronPython scripts to a product model, all together, and that can all call each other. It would not be difficult to create a dead loop, if the SAP CPQ engine would not intervene and decide when to stop an execution. This is how SAP CPQ deals with problems of this nature and maintains configuration stability.

Example that Illustrates Issues that Might Arise with Implementations 

Let us now observe an example that, if you were not persuaded by now about the importance of rules planning and ordering for usability and acceptability of your implementation, should illustrate the case better for you.

Below is a company that tries to enforce saving measures by cutting down the default number of additional empty slots in hardware utilities they sell. They would like to implement the following rule patterns:


Condition Action
If a salesperson selects between 1 and 5 slots (including the limits) Remove all default free slots
If a salesperson selects between 6 and 15 slots (including the limits) Cut the number of slots to 5
If a salesperson selects between 16 and 22 slots (including the limits) Cut the number of slots to 15
If a salesperson selects between 23 and 25 slots (including the limits) Cut the number of slots to 20


For 'Hardware Utility X', a product that uses the 'Number of Additional Empty Slots' numeric attribute to specify the number of slots, the four product rules below would be implemented. This is the simplest example where, for illustration purposes, values are listed directly without mathematical formulas and there are no other rules in the product but the four given.


Rule Number And
Execution Order
Rule Name Condition Action
1 Set Slots To 0 [IN](<*VALUE(Number Of Additional Empty Slots)*>,1,2,3,4,5) <*SELECTVALUE(Number Of Additional Empty Slots:0)*>
2 Set Slots To 5 [IN](<*VALUE(Number Of Additional Empty Slots)*>,6,7,8,9,10,11,12,13,14,15) <*SELECTVALUE(Number Of Additional Empty Slots:5)*>
3 Set Slots To 15 [IN](<*VALUE(Number Of Additional Empty Slots)*>,16,17,18,19,20,21,22) <*SELECTVALUE(Number Of Additional Empty Slots:15)*>
4 Set Slots To 20 [IN](<*VALUE(Number Of Additional Empty Slots)*>,23,24,25) <*SELECTVALUE(Number Of Additional Empty Slots:20)*>


Now, let us observe a configuration where user just enters '23' for the attribute value:



Due to the rules ordering above, the whole set of rules would run five times. Here is the execution flow for the configuration steps above:


Rules Iteration Cycle Rule Number (Currently Running) Condition Check Action (if the condition is met, otherwise just continue with the next rule) Conclusion Of The Cycle
I 1 23 is not between 1 and 5
An attribute value has changed, run rules once more.
2 23 is not between 6 and 15
3 23 is not between 16 and 22
4 23 is between 23 and 25 Set value of 'Number Of Additional Empty Slots' to 20.
II 1 20 is not between 1 and 5
An attribute value has changed, run rules once more.
2 20 is not between 6 and 15
3 20 is between 16 and 22 Set value of 'Number Of Additional Empty Slots' to 15
4 20 is not between 23 and 25
III 1 15 is not between 1 and 5
An attribute value has changed, run rules once more.
2 15 is between 6 and 15 Set value of 'Number Of Additional Empty Slots' to 5
3 15 is not between 16 and 22
4 15 is not between 23 and 25 
IV 1 5 is between 1 and 5  Set value of 'Number Of Additional Empty Slots' to 0 An attribute value has changed, run rules once more.
2 5 is not between 6 and 15
3 5 is not between 16 and 22
4 5 is not between 23 and 25
V 1 0 is not between 1 and 5
No attribute values changed, rules execution stops.
2 0 is not between 6 and 15
3 0 is not between 16 and 22
4 0 is not between 23 and 25


A significant performance issue starts to be visible here.

In this simple example, only four rules were created for the product but products easily can have 100-200 rules. If the four were only part of those, the loop would make all 100-200 rules iterate for five times. It is important for performance of some configurations that rules are implemented correctly and in proper order.

Do not think about repeated execution cycles just as a direct consequence of some business logic. Those are occurring more frequently because of accumulative updates to product models. The updates are usually done with incomplete maintenance and unawareness of implementers where similar rules were already created that work a bit differently but use the same attributes. Overlapping of rules like this can easily result with multiple rule execution cycles.

Another and more important repercussion from the example above, is that from a business perspective, the product will behave incorrectly. For example, the user has entered '23' and instead of reducing the slots number to '20', he was limited to 0 additional slots in the end.

Example of a Dead Loop

Performance can be affected in the case of a dead loop. A dead loop is when product rules run for an infinite number of times. It is easy to make a dead loop by modifying the 'Set Slots to Zero' rule to assign 'Number Of Additional Empty Slots' to '23' instead of '0' in our example above.

In this case, SAP CPQ will not block nor spend time endlessly trying to reach a stable state. It will break rule execution after an internal maximum number of iterations has been reached and will leave the configuration with the attributes' selections from the last executed iteration.



Additionally, all dead loops need to be identified and resolved before a product model is sent for testing.

Potential Solutions for the Problem

The problem with rule repetitions could be resolved by ordering the above rules in the opposite direction. Then, the number of cycles would be just two (as the second cycle confirms that no attribute values have changed) with 60% savings in execution time. The ordering of the rules at the top is the most natural and probable way to implement. The downside, however, is on the performance perspective.

Another problem that rules reordering would not resolve is the ending value of 'Number of Additional Empty Slots'. It should not be reduced from '23' to '0', but down to '20'. This can be resolved in few ways:

  1. By introducing one additional flag attribute with True/False values telling whether a rule should run.
    Every rule should include the flag value check (if True) as part of its condition and the action of every rule should set the flag to False (after 'Number Of Additional Empty Slots' value is set).
    Besides this, an extra Python script running at 'OnProductRulesExecutionStart' should be used to initialize the flag to True. The reason to go for the script would be that the referenced event runs only once, before the first rules execution cycle, not before every repeated cycle.
  2. Migrate the four rules to the selection trigger of the 'Number Of Additional Empty Slots' attribute to trigger run only once.
    The trigger formula would contain all if/branches to integrate the four rules above.
    Pay attention that a trigger could do the job only in this very particular example, while the article discusses loops of rules in general. 

If you want to learn how this problem could have easily been noticed, inspected, and resolved with the help of SAP CPQ development tools, please study content of the following article in the series. The article will also show you how to identify dead loops, not just the regular repetitions, and it will provide more in depth information on how to make your model ready for testing.


Conclusion

Ordering and organization of product rules is one of the key factors for optimal performance and functional validity of product models. Rules should be planned in advance, as a whole, the same as those should be revised and reorganized with all following, relevant updates.

If you want to read the official SAP CPQ help pages for product rules, go to this link  https://help.webcomcpq.com/doku.php?id=adminhelp:productadministration:products:rulesadmin&s[]=rules

If you want to learn how the problem depicted in this article can be noticed, analyzed, and resolved, please read the next article in the series.