• No results found

Basic Patterns in Symbolic Computing

Examples of execution patterns in symbolic computing can be easily drawn from the manipulation capabilities that are offered by existing CASs. Most often, the CASs han-dle mathematical formulae and structures as objects and list of objects. One of the most developed CAS system is GAP. Using GAP, with a list of objects the user is able to execute several types of operations: apply certain transformations to all the objects of the list, analyse the properties of those objects, create new lists based on certain criteria.

Depending on the nature of the problem, manipulations on objects can even be possible in parallel, on remote machines, if the computational gain motivates it.

The control structures used by general algorithms are often part of the standard program-ming constructs. While their syntax may vary from one CAS to another, systems such as GAP [3], Maple [10], KANT/KASH [21] all provide control structures for control flow and repetitive executions. A peculiarity of those systems is that repetitive constructs are usually available in conjunction with lists of objects. Therefore these systems are not

different with regard to their capabilities to describe arbitrary complicated algorithms, in comparison with popular imperative languages such as Java or C/C++.

Algorithmic solutions of complex problems are obtained through execution of atomic steps in a predefined order. It is the same case for solutions that are specific to symbolic computing. Specific language constructs that control the execution flow within a CAS can be mapped on control flow patterns used to compose Web Services. The actual pro-cessing steps requested by a symbolic computing algorithm can also be mapped on Web Services invocations. It is thus possible to translate an implementation of a symbolic algorithm so it can be expressed in terms of workflow patterns and services invocations.

Languages that are currently used for describing Web Service workflows are too close to the Web Service orchestration level to be used directly within a CAS. The description of such workflows requires low level details such as the address of the composed Web services and data conversion specification. It is therefore necessary for CASs to provide more abstract and versatile mechanisms to describe such workflows.

By analysing current CASs’ capabilities we can identify a mapping of CAS level con-structs on more general workflow patterns. Even if it is not always obvious, in fact symbolic computations specialists organize the processing instructions using workflow patterns. Only when dealing with an external workflow execution engine these patterns become more visible.

The simplest execution pattern used in symbolic computing is the sequence pattern. This often arises when the user runs several commands one after another or if function com-position is used. Usually, the current state of the system is stored by the command line interface that the application specialist is using. When dealing with external workflow engines, the actual steps of the computation have to be clearly identified and tasks have to be defined explicitly. A hidden sequence pattern implied by a function composition such as:

a:= func1(func2(b));

must be clearly separated in smaller pieces using explicit sequence markers and task isolation that the workflow engine can translate to invoke calls to external services:

sequence { c = func2(b) a = func1(c) }

Conditional patterns are also allowed in most of the CAS processing languages. The typical form in which they may be expressed is:

if ( condition ) {

//execute true branch statements }

else {

//executed false branch statements }

where the CAS is able to evaluate the condition specified within the control structure above. In terms of workflow patterns executed by a workflow engine, this construct may be expressed with a small modification:

boolean_value = evaluate_condition(condition) if ( boolean_value ) {

//execute true branch statements }

else {

//executed false branch statements }

Since general purpose workflow engines are not expected to have any capabilities to evaluate symbolic constructs and only simple numerical and boolean evaluation can be used, expressing conditional patterns has to use additional service calls. As it can be

seen in the code above, the evaluation of the condition may not be possible at workflow engine level. The workaround is to use an additional evaluation service that takes the symbolic condition as an input and offers back the result as a boolean. Whenever a conditions has to be evaluated this way a custom service that can do the evaluation for the workflow engine must be used.

Lists represent the main container of objects allowing the CAS to manipulate symbolic objects, usually through repetitive constructs. Any batch processing that may be exe-cuted by a CAS is thus related to its capabilities of processing lists. While the list itself is stored within the client machine, the processing of objects composing the list may be done on remote execution nodes. Described in pseudo-code, visiting every object contained in a list can be done by applying a repetitive construct such as:

for (item in list) {

//execute transformation on item }

Again, the workflow engine lacks symbolic capabilities and it is not able to understand and evaluate OpenMath objects. All manipulations must be achieved by calling external symbolic services that are able to understand and manipulate the objects. Every step of the repetitive iteration over the elements of a list must be described explicitly by defining tasks that can be executed as remote calls. Here we give an example of a multiple instances with prior knowledge pattern in which the number of elements does not change during execution:

end_index = s_size(list) for (index = 1..end_index) {

item = get_item_with_index(list,index) s_transformation(item)

}

In order to map the two constructs mentioned above, additional external services must be invoked: for finding out the end index we need to invoke an external service that will

determine the size of the list; moreover, the object situated at index in the list must also be retrieved by accessing an external service. The processing over the object itself has also to be done by an external service.

end_index = s_size(list1) list2 = s_create_empty_list() for (index = 1..end_index) {

item = s_get_item(index, list1)

boolean_value = s_evaluate_object(item) if ( boolean_value ) {

s_store_object(item, list2) }

}

list = s_get_list(list2)

Listing 4.1: Implementation of Filtered Pattern

The two simple execution patterns mentioned above may be easily combined in order to create more complicated execution scenarios. For instance, suppose we have a list of objects and a selection function that decides a boolean value based on the value of a object. We want to create a new list containing all the objects for which the condition holds. This execution pattern, referred by symbolic computing specialists as the filtered pattern, may be achieved by creating a workflow that combines the two patterns above and several pre-existing external services, as shown in Listing 4.1 where all function calls having a name beginning with “s ” represent calls to external services.

Using similar approaches, several other processing patterns may be easily implemented, and to name only a few:

• Apply Inplace - apply a certain transformation on all the objects,

• Apply New - create a new list based on the transformed objects of a given list,

• Count - count the objects having certain characteristics,

• For Any - check if all objects in the list have certain characteristics,

• Fold - calculate a global value based on the elements of the list.

Another class of execution patterns derives from the basic ring pattern. In [66] we have implemented this pattern using three interdependent services. The basic idea of this multiple instances with prior knowledge pattern is that the invoke of the services describing the ring is done in sequence, repetitively, while certain conditions hold. The general structure of the pattern is presented in Listing 4.2.

boolean_value = evaluate_condition(condition) while (boolean_value) {

value1 = s_first_service(input) value2 = s_second_service(value1)

boolean_value = evaluate_condition(condition) input = value2

}

Listing 4.2: Basic Ring Pattern

Starting from the examples depicted above, one may imagine an infinite number of com-binations. For instance a particularly useful execution pattern, deferred choice, uses several external services to compute the same result over the same object, but using dif-ferent techniques. Since only one result is needed, the execution ends when any of the calls returns the result. In this pattern, a particular role plays the basic parallel pattern which allows starting multiple calls at the same time.