/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.depgraph; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertTrue; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; import com.google.common.collect.Sets; import com.opengamma.engine.ComputationTarget; import com.opengamma.engine.MapComputationTargetResolver; import com.opengamma.engine.depgraph.impl.DependencyGraphImpl; import com.opengamma.engine.depgraph.impl.DependencyNodeImpl; import com.opengamma.engine.function.CompiledFunctionDefinition; import com.opengamma.engine.function.FunctionCompilationContext; import com.opengamma.engine.function.FunctionExecutionContext; import com.opengamma.engine.function.FunctionInputs; import com.opengamma.engine.function.resolver.FunctionPriority; import com.opengamma.engine.target.ComputationTargetType; import com.opengamma.engine.test.MockFunction; import com.opengamma.engine.value.ComputedValue; import com.opengamma.engine.value.ValueProperties; import com.opengamma.engine.value.ValuePropertyNames; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.id.UniqueId; import com.opengamma.util.test.TestGroup; import com.opengamma.util.test.TestLifecycle; /** * Tests the dependency graph building with functions that implement conversion strategies (e.g. for currency) */ @Test(groups = TestGroup.UNIT) public class DepGraphConversionTest extends AbstractDependencyGraphBuilderTest { private static final Logger s_logger = LoggerFactory.getLogger(DepGraphConversionTest.class); public void functionWithStaticConversion() { TestLifecycle.begin(); try { final DepGraphTestHelper helper = helper(); final MockFunction fn1 = helper.addFunctionProducing(helper.getValue1Foo()); final MockFunction fnConv = new MockFunction(helper.getTarget()) { @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { final ValueRequirement req2bar = helper.getRequirement2Bar(); return Collections.singleton(new ValueSpecification(req2bar.getValueName(), target.toSpecification(), req2bar.getConstraints().copy() .with(ValuePropertyNames.FUNCTION, getUniqueId()).get())); } }; fnConv.addRequirement(helper.getRequirement2Foo()); helper.getFunctionRepository().addFunction(fnConv); final MockFunction fn2 = helper.addFunctionRequiringProducing(helper.getRequirement1Foo(), helper.getValue2Foo()); DependencyGraphBuilder builder = helper.createBuilder(null); builder.addTarget(helper.getRequirement2Foo()); DependencyGraph graph = builder.getDependencyGraph(); assertNotNull(graph); graph = DependencyGraphImpl.removeUnnecessaryValues(graph); assertGraphContains(graph, fn1, fn2); builder.addTarget(helper.getRequirement2Bar()); graph = builder.getDependencyGraph(); assertNotNull(graph); graph = DependencyGraphImpl.removeUnnecessaryValues(graph); assertGraphContains(graph, fn1, fn2, fnConv); } finally { TestLifecycle.end(); } } public void functionWithDynamicConversionSingle() { TestLifecycle.begin(); try { final DepGraphTestHelper helper = helper(); final MockFunction fn1 = helper.addFunctionProducing(helper.getValue1Foo()); final MockFunction fn2 = helper.addFunctionRequiringProducing(helper.getRequirement1Foo(), helper.getValue2Foo()); final AtomicBoolean getResultsCalled = new AtomicBoolean(); final MockFunction fnConv = new MockFunction(helper.getTarget()) { @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { final ValueRequirement req = helper.getRequirement2Any(); return Collections.singleton(new ValueSpecification(req.getValueName(), target.toSpecification(), req.getConstraints().copy().with(ValuePropertyNames.FUNCTION, getUniqueId()) .get())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target, Map<ValueSpecification, ValueRequirement> inputs) { s_logger.debug("fnConv late resolving with inputs {}"); getResultsCalled.set(true); return super.getResults(context, target, inputs); } }; fnConv.addRequirement(helper.getRequirement2Any()); helper.getFunctionRepository().addFunction(fnConv); DependencyGraphBuilder builder = helper.createBuilder(new FunctionPriority() { @Override public int getPriority(CompiledFunctionDefinition function) { if (function == fnConv) { return -1; } return 0; } }); builder.addTarget(helper.getRequirement2Bar()); DependencyGraph graph = builder.getDependencyGraph(); assertNotNull(graph); graph = DependencyGraphImpl.removeUnnecessaryValues(graph); assertGraphContains(graph, fn1, fn2, fnConv); assertTrue(getResultsCalled.get()); } finally { TestLifecycle.end(); } } public void functionWithDynamicConversionTwoLevel() { TestLifecycle.begin(); try { final DepGraphTestHelper helper = helper(); final MockFunction fn1 = helper.addFunctionProducing(helper.getValue1Foo()); // This converter will manipulate a value name but preserve a property; requiring late-stage property/constraint composition final MockFunction fnConv1 = new MockFunction("conv1", helper.getTarget()) { @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { final ValueRequirement req = helper.getRequirement2Any(); return Collections.singleton(new ValueSpecification(req.getValueName(), target.toSpecification(), req.getConstraints().copy().with(ValuePropertyNames.FUNCTION, getUniqueId()) .get())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target, Map<ValueSpecification, ValueRequirement> inputs) { s_logger.debug("fnConv1 late resolving with inputs {}", inputs); assertEquals(1, inputs.size()); final ValueSpecification input = inputs.keySet().iterator().next(); return Collections.singleton(new ValueSpecification(helper.getRequirement2().getValueName(), target.toSpecification(), ValueProperties .with(ValuePropertyNames.FUNCTION, getUniqueId()).with("TEST", input.getProperties().getValues("TEST")).get())); } }; fnConv1.addRequirement(helper.getRequirement1Any()); helper.getFunctionRepository().addFunction(fnConv1); // This converter will preserve the value name but manipulate a property; and be selected if a converter is needed on top // of fnConv1 after late-stage composition final MockFunction fnConv2 = new MockFunction("conv2", helper.getTarget()) { @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { final ValueRequirement req = helper.getRequirement2Any(); return Collections.singleton(new ValueSpecification(req.getValueName(), target.toSpecification(), req.getConstraints().copy().with(ValuePropertyNames.FUNCTION, getUniqueId()) .get())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target, Map<ValueSpecification, ValueRequirement> inputs) { s_logger.debug("fnConv2 late resolving with inputs {}", inputs); assertEquals(1, inputs.size()); return super.getResults(context, target, inputs); } }; fnConv2.addRequirement(helper.getRequirement2Any()); helper.getFunctionRepository().addFunction(fnConv2); DependencyGraphBuilder builder = helper.createBuilder(new FunctionPriority() { @Override public int getPriority(CompiledFunctionDefinition function) { if (function == fnConv2) { return -1; } return 0; } }); builder.addTarget(helper.getRequirement2Foo()); DependencyGraph graph = builder.getDependencyGraph(); assertNotNull(graph); graph = DependencyGraphImpl.removeUnnecessaryValues(graph); Map<MockFunction, DependencyNode> nodes = assertGraphContains(graph, fn1, fnConv1); s_logger.debug("fnConv1 - inputs = {}", DependencyNodeImpl.getInputValues(nodes.get(fnConv1))); s_logger.debug("fnConv1 - outputs = {}", DependencyNodeImpl.getOutputValues(nodes.get(fnConv1))); assertTrue(nodes.get(fnConv1).getOutputValue(0).getProperties().getValues("TEST").contains("Foo")); builder.addTarget(helper.getRequirement2Bar()); graph = builder.getDependencyGraph(); assertNotNull(graph); graph = DependencyGraphImpl.removeUnnecessaryValues(graph); nodes = assertGraphContains(graph, fn1, fnConv1, fnConv2); s_logger.debug("fnConv1 - inputs = {}", DependencyNodeImpl.getInputValues(nodes.get(fnConv1))); s_logger.debug("fnConv1 - outputs = {}", DependencyNodeImpl.getOutputValues(nodes.get(fnConv1))); assertTrue(nodes.get(fnConv1).getOutputValue(0).getProperties().getValues("TEST").contains("Foo")); s_logger.debug("fnConv2 - inputs = {}", DependencyNodeImpl.getInputValues(nodes.get(fnConv2))); s_logger.debug("fnConv2 - outputs = {}", DependencyNodeImpl.getOutputValues(nodes.get(fnConv2))); assertTrue(nodes.get(fnConv2).getOutputValue(0).getProperties().getValues("TEST").contains("Bar")); } finally { TestLifecycle.end(); } } public void functionWithDynamicConversionDouble() { TestLifecycle.begin(); try { final DepGraphTestHelper helper = helper(); final MockFunction fn1 = helper.addFunctionProducing(helper.getValue1Foo()); final AtomicInteger getResultsInvoked = new AtomicInteger(); final MockFunction fnConv = new MockFunction("conv", helper.getTarget()) { @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { final ValueRequirement req = helper.getRequirement2Any(); return Collections.singleton(new ValueSpecification(req.getValueName(), target.toSpecification(), req.getConstraints().copy().with(ValuePropertyNames.FUNCTION, getUniqueId()) .get())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target, Map<ValueSpecification, ValueRequirement> inputs) { s_logger.debug("fnConv late resolving with inputs {}", inputs); assertEquals(1, inputs.size()); getResultsInvoked.incrementAndGet(); return super.getResults(context, target, inputs); } }; fnConv.addRequirement(helper.getRequirement1Any()); helper.getFunctionRepository().addFunction(fnConv); DependencyGraphBuilder builder = helper.createBuilder(null); builder.addTarget(helper.getRequirement2Foo()); builder.addTarget(helper.getRequirement2Bar()); DependencyGraph graph = builder.getDependencyGraph(); assertNotNull(graph); graph = DependencyGraphImpl.removeUnnecessaryValues(graph); Map<MockFunction, DependencyNode> nodes = assertGraphContains(graph, fn1, fnConv, fnConv); s_logger.debug("fnConv - inputs = {}", DependencyNodeImpl.getInputValues(nodes.get(fnConv))); s_logger.debug("fnConv - outputs = {}", DependencyNodeImpl.getOutputValues(nodes.get(fnConv))); assertEquals(2, getResultsInvoked.get()); } finally { TestLifecycle.end(); } } public void twoLevelConversion() { TestLifecycle.begin(); try { final DepGraphTestHelper helper = helper(); final ComputationTarget target1 = new ComputationTarget(ComputationTargetType.PRIMITIVE, UniqueId.of("Target", "1")); final ComputationTarget target2 = new ComputationTarget(ComputationTargetType.PRIMITIVE, UniqueId.of("Target", "2")); final ComputationTarget target3 = new ComputationTarget(ComputationTargetType.PRIMITIVE, UniqueId.of("Target", "3")); final String property = "Constraint"; MockFunction source = new MockFunction("source1", target1); source.addResult(new ComputedValue(new ValueSpecification("A", target1.toSpecification(), ValueProperties.with(ValuePropertyNames.FUNCTION, "1").with(property, "Foo").get()), 1.0)); helper.getFunctionRepository().addFunction(source); source = new MockFunction("source2", target2); source.addResult(new ComputedValue(new ValueSpecification("A", target2.toSpecification(), ValueProperties.with(ValuePropertyNames.FUNCTION, "1").with(property, "Bar").get()), 2.0)); helper.getFunctionRepository().addFunction(source); // Constraint preserving A->B helper.getFunctionRepository().addFunction(new TestFunction() { @Override public String getShortName() { return "AtoB"; } @Override public Set<ValueRequirement> getRequirements(FunctionCompilationContext context, ComputationTarget target, ValueRequirement desiredValue) { return Collections.singleton(new ValueRequirement("A", target.toSpecification(), ValueProperties.withAny(property).get())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { return Collections.singleton(new ValueSpecification("B", target.toSpecification(), createValueProperties().withAny(property).get())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target, Map<ValueSpecification, ValueRequirement> inputs) { return Collections.singleton(new ValueSpecification("B", target.toSpecification(), createValueProperties().with(property, inputs.keySet().iterator().next().getProperty(property)) .get())); } @Override public Set<ComputedValue> execute(FunctionExecutionContext executionContext, FunctionInputs inputs, ComputationTarget target, Set<ValueRequirement> desiredValues) { return null; } }); // Constraint converting B->B helper.getFunctionRepository().addFunction(new TestFunction() { @Override public String getShortName() { return "BConv"; } @Override public Set<ComputedValue> execute(FunctionExecutionContext executionContext, FunctionInputs inputs, ComputationTarget target, Set<ValueRequirement> desiredValues) { return null; } @Override public Set<ValueRequirement> getRequirements(FunctionCompilationContext context, ComputationTarget target, ValueRequirement desiredValue) { return Collections.singleton(new ValueRequirement("B", target.toSpecification(), ValueProperties.withAny(property).get())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { return Collections.singleton(new ValueSpecification("B", target.toSpecification(), ValueProperties.all())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target, Map<ValueSpecification, ValueRequirement> inputs) { final Set<ValueSpecification> result = Sets.newHashSetWithExpectedSize(inputs.size()); for (ValueSpecification input : inputs.keySet()) { result.add(new ValueSpecification(input.getValueName(), input.getTargetSpecification(), input.getProperties().copy().withAny(property).get())); } return result; } @Override public int getPriority() { return -1; } }); // Combining B->C; any constraint but must be the same helper.getFunctionRepository().addFunction(new TestFunction() { @Override public String getShortName() { return "BtoC"; } @Override public Set<ComputedValue> execute(FunctionExecutionContext executionContext, FunctionInputs inputs, ComputationTarget target, Set<ValueRequirement> desiredValues) { return null; } @Override public Set<ValueRequirement> getRequirements(FunctionCompilationContext context, ComputationTarget target, ValueRequirement desiredValue) { final Set<ValueRequirement> req = new HashSet<ValueRequirement>(); Set<String> props = desiredValue.getConstraints().getValues(property); if (props == null) { if (target.equals(target3)) { req.add(new ValueRequirement("B", target1.toSpecification(), ValueProperties.withAny(property).get())); req.add(new ValueRequirement("B", target2.toSpecification(), ValueProperties.withAny(property).get())); } else { req.add(new ValueRequirement("B", target.toSpecification(), ValueProperties.withAny(property).get())); } } else { if (target.equals(target3)) { req.add(new ValueRequirement("B", target1.toSpecification(), ValueProperties.with(property, props).get())); req.add(new ValueRequirement("B", target2.toSpecification(), ValueProperties.with(property, props).get())); } else { req.add(new ValueRequirement("B", target.toSpecification(), ValueProperties.with(property, props).get())); } } return req; } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { return Collections.singleton(new ValueSpecification("C", target.toSpecification(), createValueProperties().withAny(property).get())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target, Map<ValueSpecification, ValueRequirement> inputs) { String propValue = null; for (ValueSpecification input : inputs.keySet()) { if (propValue == null) { propValue = input.getProperty(property); } else { if (!propValue.equals(input.getProperty(property))) { throw new IllegalArgumentException("property mismatch - " + propValue + " vs " + input.getProperty(property)); } } } return Collections.singleton(new ValueSpecification("C", target.toSpecification(), createValueProperties().with(property, propValue).get())); } }); // Converting C->C; constraint omitted implies default helper.getFunctionRepository().addFunction(new TestFunction() { @Override public String getShortName() { return "CConv"; } @Override public Set<ComputedValue> execute(FunctionExecutionContext executionContext, FunctionInputs inputs, ComputationTarget target, Set<ValueRequirement> desiredValues) { return null; } @Override public Set<ValueRequirement> getRequirements(FunctionCompilationContext context, ComputationTarget target, ValueRequirement desiredValue) { final Set<String> props = desiredValue.getConstraints().getValues(property); if (props == null) { return Collections.singleton(new ValueRequirement("C", target.toSpecification(), ValueProperties.with(property, "Default").get())); } else { return Collections.singleton(new ValueRequirement("C", target.toSpecification(), ValueProperties.withAny(property).get())); } } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { return Collections.singleton(new ValueSpecification("C", target.toSpecification(), ValueProperties.all())); } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target, Map<ValueSpecification, ValueRequirement> inputs) { final Set<ValueSpecification> result = Sets.newHashSetWithExpectedSize(inputs.size()); for (ValueSpecification input : inputs.keySet()) { result.add(new ValueSpecification(input.getValueName(), input.getTargetSpecification(), input.getProperties().copy().withAny(property).get())); } return result; } @Override public int getPriority() { return -1; } }); final DependencyGraphBuilder builder = helper.createBuilder(new FunctionPriority() { @Override public int getPriority(CompiledFunctionDefinition function) { if (function instanceof TestFunction) { return ((TestFunction) function).getPriority(); } return 0; } }); ((MapComputationTargetResolver) builder.getCompilationContext().getRawComputationTargetResolver()).addTarget(target1); ((MapComputationTargetResolver) builder.getCompilationContext().getRawComputationTargetResolver()).addTarget(target2); ((MapComputationTargetResolver) builder.getCompilationContext().getRawComputationTargetResolver()).addTarget(target3); builder.addTarget(new ValueRequirement("C", target3.toSpecification())); builder.addTarget(new ValueRequirement("C", target2.toSpecification())); builder.addTarget(new ValueRequirement("C", target1.toSpecification())); builder.addTarget(new ValueRequirement("B", target1.toSpecification())); builder.addTarget(new ValueRequirement("B", target2.toSpecification())); DependencyGraph graph = builder.getDependencyGraph(); assertNotNull(graph); graph = DependencyGraphImpl.removeUnnecessaryValues(graph); s_logger.debug("After removeUnnecessaryValues"); //graph.dumpStructureASCII(System.out); } finally { TestLifecycle.end(); } } }