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!