/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * GemModelDemo.java * Created: Jul 29, 2005 * By: Bo Ilic */ package org.openquark.cal.samples; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import org.openquark.cal.compiler.CALSourceGenerator; import org.openquark.cal.compiler.MessageLogger; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.Scope; import org.openquark.cal.compiler.SourceModel; import org.openquark.cal.compiler.TypeException; import org.openquark.cal.compiler.io.EntryPointSpec; import org.openquark.cal.module.Cal.Collections.CAL_List; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.module.Cal.Utilities.CAL_Summary; import org.openquark.cal.runtime.CALExecutorException; import org.openquark.cal.services.BasicCALServices; import org.openquark.cal.services.GemCompilationException; import org.openquark.cal.valuenode.LiteralValueNode; import org.openquark.gems.client.CollectorGem; import org.openquark.gems.client.Gem; import org.openquark.gems.client.GemGraph; import org.openquark.gems.client.ReflectorGem; import org.openquark.gems.client.ValueGem; import org.openquark.gems.client.services.GemFactory; /** * Contains a variety of "demo" gems expressed programmatically as {@link GemGraph}s. * <p> * The methods {@link #factorialGemGraph}, {@link #positiveOutlierDetectorGemGraph}, and * {@link #demoMapGemGraph} demonstrate the construction of gem graphs. * <p> * The {@link #main} method of this class demonstrates the conversion of these gem graphs into CAL source text, * as well as the evaluation of these gems with arguments passed into the CAL runtime from Java. * <p> * The steps for creating a GemGraph are: * <ol> * <li> Create an empty GemGraph, and set the name of its target collector * <li> Create a new Gem or obtain an existing Gem * <li> Add the Gem to the GemGraph * <li> Repeat steps 2-3 to add other Gems * <li> Connect the Gems together using the connectGems() method on the GemGraph * <li> Repeat steps 2-5 as necessary to add all the Gems and connections for the GemGraph * <li> Typecheck the GemGraph to ensure that it is consistent * </ol> * <p> * See SourceModelDemo.java for a demo of the same gems expressed programmatically as SourceModels. * See GemModelDemo.cal for the expression using CAL. * * @author Bo Ilic * @author Joseph Wong * * @see SourceModelDemo */ public class GemModelDemo { private static final ModuleName GEM_GRAPH_TYPE_CHECKING_MODULE = ModuleName.make("GemCutterSaveModule"); private static final ModuleName RUN_MODULE = ModuleName.make("GemModelDemo.RunModule"); private static final String WORKSPACE_FILE_NAME = "gemcutter.default.cws"; /** * This demo consists of generating the CAL source of the demo gems defined using gem graphs, * as well as evaluating these gems with some arguments. * * @see #dumpAllGemGraphSourceDefinitions * @see #runGemGraphDemo */ public static void main(String[] args) { System.out.println("Gem Model Demo"); System.out.println("-------------------------------------------------------------------------------------"); MessageLogger messageLogger = new MessageLogger(); // We need to set up the calServices instance that is required for the demo. BasicCALServices calServices = BasicCALServices.makeCompiled(WORKSPACE_FILE_NAME, messageLogger); if (calServices == null) { System.err.println(messageLogger.toString()); System.exit(1); } System.out.println(); System.out.println("The CAL source of the demo gems defined using gem graphs:"); System.out.println("====================================================================================="); dumpAllGemGraphSourceDefinitions(calServices); System.out.println(); System.out.println("Evaluating some gems defined using gem graphs:"); System.out.println("====================================================================================="); runGemGraphDemo(calServices); } /** * Evaluates some demo gems. Here, we test out the factorial and the positiveOutlierDetector gems. * * @param calServices The calServices instance to be used for constructing and running the GemGraphs. * * @see #runFactorialDemo * @see #runPositiveOutlierDetectorDemo */ public static void runGemGraphDemo(BasicCALServices calServices) { // First, we will run the factorial gem. runFactorialDemo(calServices); // Next, we will run the positiveOutlierDetector gem. runPositiveOutlierDetectorDemo(calServices); } /** * Runs some tests with the factorial gem defined by {@link #factorialGemGraph}. * * @param calServices The calServices instance to be used for constructing and running the GemGraphs. * * @see #factorialGemGraph */ public static void runFactorialDemo(BasicCALServices calServices) { //// /// We can use the runCode method on BasicCALServices to evaluate gems defined by gem graphs. // try { GemFactory gemFactory = new GemFactory(calServices); SourceModel.FunctionDefn.Algebraic gemSource = factorialGemGraph(gemFactory, calServices).getCALSource(); EntryPointSpec entryPointSpec = calServices.addNewModuleWithFunction(RUN_MODULE, gemSource); try { BigInteger result; result = (BigInteger)calServices.runFunction(entryPointSpec, new Object[] { BigInteger.valueOf(7) }); System.out.println("(factorial 7) => " + result); result = (BigInteger)calServices.runFunction(entryPointSpec, new Object[] { BigInteger.valueOf(37) }); System.out.println("(factorial 37) => " + result); } catch (CALExecutorException e) { e.printStackTrace(System.err); } } catch (TypeException e) { e.printStackTrace(System.err); } catch (GemCompilationException e) { e.printStackTrace(System.err); } } /** * Runs some tests with the positiveOutlierDetector gem defined by {@link #positiveOutlierDetectorGemGraph}. * * @param calServices The GemGenerator instance to be used for constructing and running the GemGraphs. * * @see #positiveOutlierDetectorGemGraph */ public static void runPositiveOutlierDetectorDemo(BasicCALServices calServices) { //// /// We can use the runCode method on BasicCALServices and GemGenerator to evaluate gems defined by gem graphs. // try { GemFactory gemFactory = new GemFactory(calServices); SourceModel.FunctionDefn.Algebraic gemSource = positiveOutlierDetectorGemGraph(gemFactory, calServices).getCALSource(); EntryPointSpec entryPointSpec = calServices.addNewModuleWithFunction(RUN_MODULE, gemSource); List<?> result; //// /// A Java java.util.List can be passed in as an argument to a CAL function that expects a /// CAL list as an argument. // List<Double> oneToFive = new ArrayList<Double>(); for (int i = 1; i <= 5; i++) { oneToFive.add(new Double(i)); } result = (List<?>)calServices.runFunction(entryPointSpec, new Object[] { oneToFive, new Double(2.0) }); System.out.println("(positiveOutlierDetector [1, 2, 3, 4, 5] 2.0) => " + result); result = (List<?>)calServices.runFunction(entryPointSpec, new Object[] { oneToFive, new Double(1.0) }); System.out.println("(positiveOutlierDetector [1, 2, 3, 4, 5] 1.0) => " + result); result = (List<?>)calServices.runFunction(entryPointSpec, new Object[] { oneToFive, new Double(0.5) }); System.out.println("(positiveOutlierDetector [1, 2, 3, 4, 5] 0.5) => " + result); } catch (CALExecutorException e) { e.printStackTrace(System.err); } catch (GemCompilationException e) { e.printStackTrace(System.err); } catch (TypeException e) { e.printStackTrace(System.err); } } /** * Prints out the CAL source text generated by the various demo GemGraphs. * * @param calServices * * @see #factorialGemGraph * @see #positiveOutlierDetectorGemGraph * @see #demoMapGemGraph */ public static void dumpAllGemGraphSourceDefinitions(BasicCALServices calServices) { GemFactory gemFactory = new GemFactory(calServices); try { System.out.println(getGemGraphSourceDefinition(factorialGemGraph(gemFactory, calServices))); System.out.println(getGemGraphSourceDefinition(positiveOutlierDetectorGemGraph(gemFactory, calServices))); System.out.println(getGemGraphSourceDefinition(demoMapGemGraph(gemFactory, calServices))); } catch (TypeException e) { e.printStackTrace(System.err); } } /** Generates CAL source text from a GemGraph. */ private static String getGemGraphSourceDefinition(GemGraph gemGraph) { return CALSourceGenerator.getFunctionText( gemGraph.getTargetCollector().getUnqualifiedName(), gemGraph.getTargetCollector(), Scope.PUBLIC); } /** * Creates a GemGraph that defines the factorial function. * <p> * Corresponding CAL source for the gem graph: * <pre> * public factorial end = List.product (Prelude.upFromTo (1 :: Prelude.Integer) end); * </pre> * @param calServices */ public static GemGraph factorialGemGraph(GemFactory gemFactory, BasicCALServices calServices) throws TypeException { // This demonstrates the creation of a simple gem graph. The steps required to build a gem graph are: // 1) Create an empty GemGraph, and set the name of its target collector // 2) Create a new Gem or obtain an existing Gem // 3) Add the Gem to the GemGraph // 4) Repeat steps 2-3 to add other Gems // 5) Connect the Gems together using the connectGems() method on the GemGraph // 6) Repeat steps 2-5 as necessary to add all the Gems and connections for the GemGraph // 7) Typecheck the GemGraph to ensure that it is consistent GemGraph gemGraph = new GemGraph(); gemGraph.getTargetCollector().setName("factorial"); Gem productGem = gemFactory.makeFunctionalAgentGem(CAL_List.Functions.product); gemGraph.addGem(productGem); Gem upFromToGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.Functions.upFromTo); gemGraph.addGem(upFromToGem); // Create a Value gem to represent a BigInteger (Prelude.Integer) value for the number 1. Gem oneGem = new ValueGem(new LiteralValueNode(BigInteger.valueOf(1), calServices.getPreludeTypeConstants().getIntegerType())); gemGraph.addGem(oneGem); gemGraph.connectGems(oneGem.getOutputPart(), upFromToGem.getInputPart(0)); gemGraph.connectGems(upFromToGem.getOutputPart(), productGem.getInputPart(0)); gemGraph.connectGems(productGem.getOutputPart(), gemGraph.getTargetCollector().getInputPart(0)); gemGraph.typeGemGraph(calServices.getTypeCheckInfo(GEM_GRAPH_TYPE_CHECKING_MODULE)); return gemGraph; } /** * Creates a GemGraph that defines the positiveOutlierDetector function. * <p> * Corresponding CAL source for the gem graph: * <pre> * public positiveOutlierDetector sourceData x_2 = * let * isPositiveOutlier x_1 = x_1 - avg >= x_2 * stdDev; * stdDev = Summary.populationStandardDeviation sourceData; * avg = Summary.average sourceData; * in * List.filter isPositiveOutlier sourceData * ; * </pre> * @param gemFactory used for creating value and function gems * @param calServices */ public static GemGraph positiveOutlierDetectorGemGraph(GemFactory gemFactory, BasicCALServices calServices) throws TypeException { GemGraph gemGraph = new GemGraph(); gemGraph.getTargetCollector().setName("positiveOutlierDetector"); // A collector targeting the target collector. It will be an argument of the positiveOutlierDetector gem. CollectorGem sourceDataCollector = new CollectorGem(); sourceDataCollector.setName("sourceData"); gemGraph.addGem(sourceDataCollector); // Local collector: avg // Corresponding source: avg = Summary.average sourceData; CollectorGem avgCollector = new CollectorGem(); avgCollector.setName("avg"); gemGraph.addGem(avgCollector); // a ReflectorGem provides as output the value that is collected by the corresponding CollectorGem ReflectorGem sourceDataReflector1 = new ReflectorGem(sourceDataCollector); gemGraph.addGem(sourceDataReflector1); Gem averageGem = gemFactory.makeFunctionalAgentGem(CAL_Summary.Functions.average); gemGraph.addGem(averageGem); gemGraph.connectGems(sourceDataReflector1.getOutputPart(), averageGem.getInputPart(0)); gemGraph.connectGems(averageGem.getOutputPart(), avgCollector.getInputPart(0)); // Local collector: stdDev // Corresponding source: stdDev = Summary.populationStandardDeviation sourceData; CollectorGem stdDevCollector = new CollectorGem(); stdDevCollector.setName("stdDev"); gemGraph.addGem(stdDevCollector); ReflectorGem sourceDataReflector2 = new ReflectorGem(sourceDataCollector); gemGraph.addGem(sourceDataReflector2); Gem populationStdDevGem = gemFactory.makeFunctionalAgentGem(CAL_Summary.Functions.populationStandardDeviation); gemGraph.addGem(populationStdDevGem); gemGraph.connectGems(sourceDataReflector2.getOutputPart(), populationStdDevGem.getInputPart(0)); gemGraph.connectGems(populationStdDevGem.getOutputPart(), stdDevCollector.getInputPart(0)); // Local collector: isPositiveOutlier // Corresponding source: isPositiveOutlier x_1 = x_1 - avg >= x_2 * stdDev; CollectorGem isPositiveOutlierCollector = new CollectorGem(); isPositiveOutlierCollector.setName("isPositiveOutlier"); gemGraph.addGem(isPositiveOutlierCollector); Gem subtractGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.Functions.subtract); gemGraph.addGem(subtractGem); ReflectorGem avgReflector = new ReflectorGem(avgCollector); gemGraph.addGem(avgReflector); // Retarget the first input of subtract to the isPositiveOutlier collector // // This means that the first input of subtract is no longer an argument for the overall Gem defined by the // GemGraph's target collector, but rather a local argument for the isPositiveOutlier collector. gemGraph.retargetInputArgument(subtractGem.getInputPart(0), isPositiveOutlierCollector, -1); gemGraph.connectGems(avgReflector.getOutputPart(), subtractGem.getInputPart(1)); Gem multiplyGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.Functions.multiply); gemGraph.addGem(multiplyGem); ReflectorGem stdDevReflector = new ReflectorGem(stdDevCollector); gemGraph.addGem(stdDevReflector); // Leave the first input of multiply targeting the target collector (it will be an argument of the positiveOutlierDetector gem), // but hook up the second input of multiply to the stdDev reflector gemGraph.connectGems(stdDevReflector.getOutputPart(), multiplyGem.getInputPart(1)); Gem greaterThanEqualsGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.Functions.greaterThanEquals); gemGraph.addGem(greaterThanEqualsGem); gemGraph.connectGems(subtractGem.getOutputPart(), greaterThanEqualsGem.getInputPart(0)); gemGraph.connectGems(multiplyGem.getOutputPart(), greaterThanEqualsGem.getInputPart(1)); gemGraph.connectGems(greaterThanEqualsGem.getOutputPart(), isPositiveOutlierCollector.getInputPart(0)); // Update the reflected inputs of the collector because one of the inputs in the gem tree was retargeted isPositiveOutlierCollector.updateReflectedInputs(); // Construct the gem tree connected to the target collector ReflectorGem isPositiveOutlierReflector = new ReflectorGem(isPositiveOutlierCollector); gemGraph.addGem(isPositiveOutlierReflector); isPositiveOutlierReflector.getInputPart(0).setBurnt(true); ReflectorGem sourceDataReflector3 = new ReflectorGem(sourceDataCollector); gemGraph.addGem(sourceDataReflector3); Gem filterGem = gemFactory.makeFunctionalAgentGem(CAL_List.Functions.filter); gemGraph.addGem(filterGem); gemGraph.connectGems(isPositiveOutlierReflector.getOutputPart(), filterGem.getInputPart(0)); gemGraph.connectGems(sourceDataReflector3.getOutputPart(), filterGem.getInputPart(1)); gemGraph.connectGems(filterGem.getOutputPart(), gemGraph.getTargetCollector().getInputPart(0)); gemGraph.typeGemGraph(calServices.getTypeCheckInfo(GEM_GRAPH_TYPE_CHECKING_MODULE)); return gemGraph; } /** * Creates a GemGraph that defines the demoMap function. * <p> * Corresponding CAL source for the gem graph: * <pre> * public demoMap mapFunction list = Prelude.iff (Prelude.isEmpty list) [] (Prelude.Cons (mapFunction (List.head list)) (demoMap mapFunction (List.tail list))); * </pre> * @param gemFactory * @param calServices */ public static GemGraph demoMapGemGraph(GemFactory gemFactory, BasicCALServices calServices) throws TypeException { GemGraph gemGraph = new GemGraph(); gemGraph.getTargetCollector().setName("demoMap"); // Two collectors forming the two arguments of the demoMap gem. CollectorGem mapFunctionCollector = new CollectorGem(); mapFunctionCollector.setName("mapFunction"); gemGraph.addGem(mapFunctionCollector); CollectorGem listCollector = new CollectorGem(); listCollector.setName("list"); gemGraph.addGem(listCollector); // Construct the test for checking whether the 'list' value is the empty list. Gem isEmptyGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.Functions.isEmpty); gemGraph.addGem(isEmptyGem); ReflectorGem listReflector1 = new ReflectorGem(listCollector); gemGraph.addGem(listReflector1); // Construct the gem tree for the case when 'list' is not empty. // The head of the returned Cons is: mapFunction (List.head list) // which uses the apply gem to apply the mapFunction to its argument. gemGraph.connectGems(listReflector1.getOutputPart(), isEmptyGem.getInputPart(0)); Gem headGem = gemFactory.makeFunctionalAgentGem(CAL_List.Functions.head); gemGraph.addGem(headGem); ReflectorGem listReflector2 = new ReflectorGem(listCollector); gemGraph.addGem(listReflector2); gemGraph.connectGems(listReflector2.getOutputPart(), headGem.getInputPart(0)); Gem applyGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.Functions.apply); gemGraph.addGem(applyGem); ReflectorGem mapFunctionReflector1 = new ReflectorGem(mapFunctionCollector); gemGraph.addGem(mapFunctionReflector1); gemGraph.connectGems(mapFunctionReflector1.getOutputPart(), applyGem.getInputPart(0)); gemGraph.connectGems(headGem.getOutputPart(), applyGem.getInputPart(1)); // The tail of the returned Cons is: demoMap mapFunction (List.tail list) Gem tailGem = gemFactory.makeFunctionalAgentGem(CAL_List.Functions.tail); gemGraph.addGem(tailGem); ReflectorGem listReflector3 = new ReflectorGem(listCollector); gemGraph.addGem(listReflector3); gemGraph.connectGems(listReflector3.getOutputPart(), tailGem.getInputPart(0)); ReflectorGem demoMapReflector = new ReflectorGem(gemGraph.getTargetCollector()); gemGraph.addGem(demoMapReflector); ReflectorGem mapFunctionReflector2 = new ReflectorGem(mapFunctionCollector); gemGraph.addGem(mapFunctionReflector2); Gem consGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.DataConstructors.Cons); gemGraph.addGem(consGem); gemGraph.connectGems(applyGem.getOutputPart(), consGem.getInputPart(0)); gemGraph.connectGems(demoMapReflector.getOutputPart(), consGem.getInputPart(1)); // Construct the conditional branch (using the iff gem). The false case returns the value // generated by the gem tree defined above. In the true case, Nil (the empty list) is returned. Gem iffGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.Functions.iff); gemGraph.addGem(iffGem); Gem nilGem = gemFactory.makeFunctionalAgentGem(CAL_Prelude.DataConstructors.Nil); gemGraph.addGem(nilGem); gemGraph.connectGems(isEmptyGem.getOutputPart(), iffGem.getInputPart(0)); gemGraph.connectGems(nilGem.getOutputPart(), iffGem.getInputPart(1)); gemGraph.connectGems(consGem.getOutputPart(), iffGem.getInputPart(2)); // Connect the gems to the target collector gemGraph.connectGems(iffGem.getOutputPart(), gemGraph.getTargetCollector().getInputPart(0)); gemGraph.connectGems(mapFunctionReflector2.getOutputPart(), demoMapReflector.getInputPart(0)); gemGraph.connectGems(tailGem.getOutputPart(), demoMapReflector.getInputPart(1)); gemGraph.typeGemGraph(calServices.getTypeCheckInfo(GEM_GRAPH_TYPE_CHECKING_MODULE)); return gemGraph; } }