Welcome to the OPA Hub!

Tag Archives: RuleScript

RuleScript Part 3 : The Project

RuleScript Part 3 : The Project

And so, onward into part three. If you missed the other parts in this RuleScript series, they are

  • What, Why, Where (Part One)
  • The Algorithm (Part Two)

In this article, we will review in detail how the RuleScript was used to create a Route Finder. Now, I am not for a moment suggesting that this is the sort of thing everyone should be implementing in their Project, but it does open some doors and give you some ideas of things that you might want to do, one day, of the functionality becomes non-experimental.

This article series came to me while sitting on the Paris Métro, so the final version uses some of the stations from that network, rather than the simple diagram used in the previous article. I used 54 stations in all, from the central district, and the performance was pretty good. I don’t have big O notation speed results.

RuleScript Specifics

This third part will also give me time to mention some of the specifics of working with RuleScript.

RuleScript handles unknown and uncertain in a specific way. Unknown is equivalent to the JavaScript global undefined. To make your life easier, you may wish to set up a variable thus:

var UNKNOWN = undefined;

And then use the strict comparison operator to check if an attribute value is known:

someattribute !== UNKNOWN / someattribute === UNKNOWN

Likewise, the uncertain  from Oracle Policy Automation is represented by null. Again you might set up a variable, to be able to refer to a familiar term and debug more easily:

var UNCERTAIN = null;

Speaking of debugging, be aware that any error messages will appear in the same way as existing determinations errors, which is to say they will be on a red background with potentially some reference to either a JavaScript statement or (if things are really bad) a stack dump from Java.

RuleScript Error in Debugger

In fact you should not underestimate the challenges of sometimes working out exactly where things have gone wrong. And remember that alert and console.log will not provide any output in the Browser. In the case of alert, you will see a warning when attempt, and in the case of console.log, it shows in the Debugger Data Tab, but not in the Browser – because it is not running in the Browser.

Reviewing the Project

Aside from the RuleScript file, which you can find in the OPA Hub Shop, there is only a very small amount of work to be done. I mentioned the 3 entities created earlier, and in the video example, I expanded my route network to contain most of central Paris ( in homage to the RATP, whose Open Data website actually got me thinking about all this in the first place one day last month whilst sitting on the Metro). They have 24 amazing data sets. I’m a great fan of the Open Data initiative ( honorable mention to Australia for all their cycling data sets) because it is where Oracle Policy Automation should be focusing some of it’s firepower – in my humble opinion.

Other than that, one Value List (for the stations) and two screens (one for the origin and destination, the other for the results) and of course an Excel spreadsheet to define all the stations and their children, as well as the cost of getting from A to B.

RuleScript Project Station List

I also placed a single rule in Word, as follows:

RuleScript Project Mapping Boolean

This is used to show the results, or if the route cannot be found, to display an apology instead using standard Show If… logic.

The RuleScript Code

As usual my code is quick, ugly and probably not adhering to many good practices. So it is for entertainment purposes only. Remember also that this “code view” isn’t that good so the PDF in the OPA Hub Shop is probably a safer bet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// RuleScript is an experimental feature in 12.2.6
// Existing RuleScript may not function correctly in future product versions without modification.
// Please read the documentation for details on how to use them.
 
// RuleScript(theresults,resultstep,totaltime,resultstepnumber) <- (origin,destination, thestations,thechildstations,childstation_cost_minutes,child_station,station) function GetResults(global) { /** * Richard Napier The OPA Hub Website January 2019 * Educational Example of RuleScript File * I will remember this is for demonstration and educational purposes only */ const problem = global.thestations.get(); const myorigin = global.origin; const mydestination = global.destination; var UNKNOWN = undefined; const lowestCostNode = (costs, processed) => {
		return Object.keys(costs).reduce((lowest, node) => {
			if (lowest === null || costs[node] < costs[lowest]) { if (!processed.includes(node)) { lowest = node; } } return lowest; }, null); }; // function that returns the minimum cost and path to reach Finish if (global.origin !== UNKNOWN && global.destination !== UNKNOWN) { const dijkstra = (origin, destination) => {
 
			// Convert Stations and Child Stations to Objects
			var thestationlist = global.thestations.get();
			var thestationlistasobject = {};
			var stationarraylength = thestationlist.length;
 
			for (var s = 0; s < stationarraylength; s++) {
				console.log("Station " + thestationlist[s]);
 
				var mychildstations = thestationlist[s].thechildstations.get();
				var mychildstationsasobject = {}
				var arrayLength = mychildstations.length;
				for (var i = 0; i < arrayLength; i++) { console.log("Child " + mychildstations[i]); //Do something mychildstationsasobject = Object.assign({ [mychildstations[i].child_station]: mychildstations[i].childstation_cost_minutes }, mychildstationsasobject) } thestationlistasobject = Object.assign({ [thestationlist[s].station]: mychildstationsasobject }, thestationlistasobject) } console.log("Created Objects " + JSON.stringify(thestationlistasobject)); //Get Costs from the children of the station used for the start let costs = {}; console.log("Destination " + destination); costs[destination] = 9999999; costs = Object.assign(costs, thestationlistasobject[myorigin]); console.log("Set Up Costs " + JSON.stringify(costs)); // track paths const parents = { destination: null }; for (let child in thestationlistasobject[origin]) { parents[child] = origin; } // track nodes that have already been processed const processed = []; let node = lowestCostNode(costs, processed); while (node) { let cost = costs[node]; console.log("Cost is " + parseInt(cost)); let children = thestationlistasobject[node]; for (let n in children) { if (String(n) === String(origin)) { console.log("WE DON'T GO BACK TO START"); } else { let newCost = cost + children[n]; console.log("New Cost: " + newCost); if (!costs[n] || costs[n] > newCost) {
							costs[n] = newCost;
							parents[n] = node;
							console.log("Updated cost and parents");
						} else {
							console.log("A shorter path already exists");
						}
					}
				}
				processed.push(node);
				console.log("Node " + node.toString() + " pushed to processed array");
				node = lowestCostNode(costs, processed);
			}
 
			let optimalPath = [destination];
			let parent = parents[destination];
			while (parent) {
				optimalPath.push(parent);
				parent = parents[parent];
			}
			optimalPath.reverse();
 
			const results = {
				distance: costs[destination],
				path: optimalPath
			};
			console.log("Distance " + results.distance);
			console.log("Steps " + results.path.length);
			return results;
		};
 
		var myresult = dijkstra(myorigin, mydestination);
 
		// Infer instances of the result set
		console.log("Route Result " + JSON.stringify(myresult));
		let resultsset = myresult["path"];
		console.log("Number of Results " + resultsset.length);
		console.log("Path to insert as results " + JSON.stringify(resultsset));
 
		var resultsLength = resultsset.length;
		var resultobject = {};
		var resultarray = [];
		for (var r = 0; r < resultsLength; r++) {
			console.log("Result Item " + resultsset[r]);
 
			resultobject = {
				["resultstep"]: resultsset[r],
				["resultstepnumber"]: r + 1
			};
			resultarray.push(resultobject);
			console.log("Ongoing Concatenation" + JSON.stringify(resultobject));
			global.theresults.update(resultarray);
			global.totaltime = myresult["distance"];
		}
 
		console.log("Final Concatenation" + JSON.stringify(resultarray));
	}
 
}

What’s Going On Where

Lines 18 to 27 uses reduce to return the node that has the lowest cost to reach it. It is initialised with a value of null, and is updated if the node being examined has a lower cost that other nodes of the same station. So if node A has 2 child stations, we examine them and look at the costs, before identifying the lowest cost. The function also used the processed array to check that the station we are about to examine has not already been processed.

Lines 31 to 120 are the definition  algorithm itself. We don’t use the function unless we know the origin and destination. Station and child station instances are converted into objects to be able to reuse the code from the example I found.

Line 64 is where the cost object is set up, to start with it only includes the children of the starting point, and the destination point has a cost of 99999, in other words we don’t know how to get there yet.

Line 78 is the core, where each node is assessed for the lowest costs, and the child stations costs used to build the total costs.

Line 111 is where the results set, which should be the correct path from A to B, only in reverse order, is switched to the correct way round for users to view.

Line 122 is where the code actually calls the algorithm based on the user’s selections.

Line 124 onwards parse the content of the results and reorganize them into instances of an entity in Oracle Policy Automation.

The Video Walk-Through

To finish, here is the walk-through of the finished Project.

Terminus!

It’s been a fun ride. Of course, now we need something appropriate for the end of this series. Play it!

RuleScript Part 2 : The Algorithm

RuleScript Part 2 : The Algorithm

In part one of this series, we looked at RuleScript at a high level – what it is, where it is and why you are likely to want to use it. As in the previous part of the article, you must remember that this is experimental and that it may therefore change, be removed or otherwise mutate. So don’t go betting the house on it. But from an intellectual perspective and because we want to be ahead of the curve, let’s dig in. We are going to create a route finding Project. It will help users find the quickest route from A to B. Go big or go home!

Dijkstra’s Algorithm to create a Route Finder

This algorithm (named after it’s inventor) can be traced back to a single event. Here is the man himself talking about it:

What is the shortest way to travel from Rotterdam to Groningen, in general: from given city to given city? It is the algorithm for the shortest path, which I designed in about twenty minutes. One morning I was shopping in Amsterdam with my young fiancée, and tired, we sat down on the café terrace to drink a cup of coffee and I was just thinking about whether I could do this, and I then designed the algorithm for the shortest path. As I said, it was a twenty-minute invention. In fact, it was published in ’59, three years late. The publication is still readable, it is, in fact, quite nice. One of the reasons that it is so nice was that I designed it without pencil and paper. I learned later that one of the advantages of designing without pencil and paper is that you are almost forced to avoid all avoidable complexities. Eventually that algorithm became, to my great amazement, one of the cornerstones of my fame.

— Edsger Dijkstra, in an interview with Philip L. Frana, Communications of the ACM, 2001
So now we have the concept : finding the shortest path from A to B (or indeed A to D or anywhere else on a network “graph”. Take a look at the following as a simple example:

RuleScript Example Network for Algorithm

If we have to ask the question; “what is the shortest path between the start and the finish” (assuming the numbers represent the cost in minutes, or some other way to express cost), then this algorithm will help us find it.

The algorithm works by starting with the initial node (“Start” in the above example), and setting the distance to our “Finish” as infinity, or a very large number indeed. This is done to represent nodes that have “not yet been processed”. The algorithm then looks up the cost of going from the “Start” to each of the connecting points “A” and “B” (which would be 5 and 2). Then the algorithm continues, for example for A and B, calculating the cost of reaching their intersections as well. Assuming you are at A, for example, reaching D would cost 4+2 = 7, which is less than proceeding from A to C (4+5=9). And the algorithm continues in this way, using the lowest costing route across the diagram until we have a solution. If there is no solution (for example, if the nodes are not connected) then the result would be Infinity.

So the shortest path in this diagram is Start > A > D > Finish. This algorithm is not about the “most direct route” but about the “smallest cost”. As Wikipedia puts it – one of the weaknesses of this algorithm is it’s slowness in certain network topologies. But it will be just fine for our little experiment.

If you are interested in reading (a far better) explanation of the algorithm, you will enjoy the linked Wikipedia article.

Our RuleScript Implementation

Thank goodness, someone else has already put this algorithm in JavaScript. Over on the Hackernoon site I found a great article about implementing it in JavaScript and I am very grateful to the author and the commenters. They made the job very easy.

In our Oracle Policy Automation Project we need the following pieces of information:

  1. Global attributes to hold the user’s choice of start, finish and an attribute to hold the time taken
  2. The list of nodes or “Stations” on our route map
  3. The list of nodes connected to those nodes
  4. A place to store the results of the algorithm as steps in the journey.

RuleScript Entities

I went ahead and gaves names to all the attributes, entities and relationships so that I could reference them in the RuleScript file. So the header of my RuleScript file looks like this:

// RuleScript(theresults,resultstep,totaltime,resultstepnumber) <- (origin,destination, thestations,thechildstations,childstation_cost_minutes,child_station,station)

I am reading the origin, destination, the stations (thats the name of the containment relationship), the child stations of each station, the cost per child station, and the name of a station and I am writing to the results, the results steps, the total time and finally each step will be numbered as they need to be displayed in the correct order.

Much of the JavaScript implementation was untouched. Here are the things I had to deal with :

  • In RuleScript, to access a relationship you use the following syntax
global.thestations.get(); // This gets the list of the station instances
thestationlist[s].thechildstations.get() // This gets the child stations for a given station at index "s"

This returns an Array representing the instances of the entity. I had to convert the array into an Object for ease of manipulation and logging using JSON. So in the end, I had an Object with a set of Child Objects to represent the Stations and their children.

RuleScript Object Examples

  • In the original implementation, the value assigned to the initial cost was the JavaScript global “Infinity”. JSON doesn’t like non-finite numeric values so I opted for 99999 instead, or any huge value that is greater than all the values on my network combined.
  • Within the JavaScript it is necessary to build several objects : one to store the stations and their child stations, but another to store the cost of reaching the nodes – including our destination node, which we will need to update as we go along.
  • In order to be able to show the path used to the user at the end of the process, we need to remember the “parent” of each of the nodes we select. For example in our cheapest route, the parent of finish is D, and the parent of D is A, and the parent of A is start. Using this we can give the results to the user, except we would want to display it in reverse order (start > A > D > finish).

Reversing, Sorting

And perhaps this is the most exciting part of the RuleScript idea – since this is pure ECMA Script, you can do things like using JavaScript Array.reverse() to change the order of the array members. We can also sort the Array members, add numbering to their members (step 1, step 2 and so on). We can manipulate the array members before we pass them to Oracle Policy Automation when they become our Entity instances. And once they become Entity instances, Oracle Policy Automation respects the order they were added in.

In the example above the result of the algorithm is stored in an array called myresult. I’m interested in only one area of that array, the “path”. Then I loop through that, according to how many steps there are. I copy the information into an array called resultarray by building an array of objects. Each object contains the result step (“A” or “D” or whatever), and a result step number and push them into my resultarray. I then use the resultarray to create instances of the result entity using

global.theresults.update(resultarray)

So I can create an Array, order it as I please, add an index and then pass it to Oracle Policy Automation to create instances. Stay tuned for part three, where we put it all together and build the Interview to go with it. When part three is published I will put the RuleScript file in the shop.

 

RuleScript Part 1 : What, Why, Where

RuleScript Part 1 : What, Why, Where

I hope you all enjoyed the holiday season. Stepping lightly into 2019, we begin with a series on RuleScript. For a long time I have had an article on the tip of my tongue, and it just needed an example to round it out. So this series has now finally seen the light of day. Before we begin the actual work let us set the stage.

What is RuleScript?

RuleScript is an experimental feature that is not enabled in Oracle Policy Modeling by default. You must request access from Oracle Support.

RuleScript is derived from Rhino, an open source implementation of JavaScript in Java. An implementation of what now? To put it simply, you are going to be able to write JavaScript, that will be executed by a Java engine. Not by your Browser. If you came from Siebel CRM, you will be familiar with eScript – basically a JavaScript derivative that runs on the Server, not on the Client browser. Well, it’s kind of the same deal here : your code will not run in the Browser JavaScript engine, but on the server-side Java engine.

Why RuleScript?

There may be times where you are implementing non-business algorithms that are better placed out of the view of the non-technical people in the team. RuleScript can access Attributes, Entities and everything you know and love about Oracle Policy Automation. Your RuleScript can update other attributes and read values as if it was written in Word or Excel. But it is written somewhere else, out of the way of the policy authors. They benefit from what you have done without getting bogged down in it.

It uses JavaScript (or rather ECMA Script) and therefore it is easy for many developers to leverage existing code. It is also possible to achieve things you cannot easily do in standard Oracle Policy Automation rules.

Why Not RuleScript?

This is an experimental feature. It may disappear tomorrow. It may radically change. It may make upgrading harder.

Where is RuleScript?

You write the rules in your Project, just like anything else. Astute readers will see there is another button in this Project in the Rules tab:

RuleScript Button

If you cannot see the button, as described above, get in touch with Oracle Support. Be ready to explain why you need it, and review the example use cases using the URL link above to see if you are on the right track.

How do I use RuleScript?

Well, since this is JavaScript you write in whatever text editor you want. There are a couple of things you need to know before starting.

  • All RuleScripts need a header to be completed. In this example, a brand new file has been added:

Blank RuleScript Header

Notice the last commented line

// RuleScript() <- ()

This cannot be removed (otherwise your Script is no longer a RuleScript) and must be completed for your code to actually work. You must complete both the right hand and left hand side of the expression. Here is an example:

Edited RuleScript Header

You can read this line :

// RuleScript(theresults,resultstep,totaltime,resultstepnumber) <- (origin,destination, thestations,thechildstations,childstation_cost_minutes,child_station,station)

as “The following RuleScript will need to read the attributes and relationships on the right, and write to the stuff on the left”. This tells you two important things :

  • The attributes, entities and relationships must exist, and have names associated with them, before you can write RuleScript with them.
  • Failure to add all the attributes and so on to the list, causes an error with a remark like “Not authorised to access attribute name

There are several other things that you need to know about the RuleScript experience :

Debugging. You can use console.log (but not alert) to push messages to the console. But this is not the Browser console (in fact they will not show up there, as they are not executed in the Browser). Rather it is in the Data tab of the Debugger (notice the messages at the bottom of the window):

Debugger in RuleScript

Editing. The attributes and other elements of your Project that appear in your RuleScript files can be traced in the usual way, and show up when you right-click the attribute for example in the Modeling interface:

Tracking in RuleScript

RuleScript File Structure : You probably noticed in the previous screenshot, that file takes the form of a function. The function can be called anything you like, because you will never actually call that function yourself. It will be called as needed by the determination engine. The rules you define in the RuleScript file can interact with other rules in Word or Excel and can be debugged and watched in the same way as usual. If you have several RuleScript files in the same Project, they essentially are all in the same JavaScript namespace, so functions in a given file can be accessed from another.

Functions: Your RuleScript file can also contain standard JavaScript functions that you create to modularize your code.

The Scenario for Today

In this series, we are going to implement something different than a traditional rule, in order to show the concepts and also the differences with the Rule authoring you already know. We are going to implement an algorithm in RuleScript and use it in our Project. This is called Dijkstra’s algorithm and is in my opinion relevant today, since it deals with networks and nodes and might well come in handy in your daily life.

Read on into part two for more information about the Project and the rather cool idea (in my opinion) behind it all…

OPA 12 – November 2016 Release New Feature Review #4

Hi There!


This content is accessible only to logged in users of the OPA Hub.

To Register takes only 10 seconds and uses LinkedIn for authentication. It does not take your contacts or use any other permission.

Once registered you can change your OPA Hub password and manage it independently of LinkedIn.

We recommend you keep different passwords for all your sites.

To register, click the Log in link in the menu at the top of your page.

Thanks, the OPA Hub Website.