示例#1
0
    def test_call_functions_without_pending_call_stack(self):
        fc = FunctionCall(Name('f'), [Name('x')])
        fd = FunctionDef('f', [FunctionArg('a', '')], Name('a'), [])
        number1 = Number(1234)
        number2 = Number(2345)
        ns1 = DslNamespace({
            'f': fd,
            'x': number1,
        })
        ns2 = DslNamespace({
            'f': fd,
            'x': number2,
        })

        # Call functions with pending call stack.
        queue = Mock()
        fc1 = fc.substitute_names(ns1)
        fc2 = fc.substitute_names(ns2)
        self.assertNotEqual(fd.create_hash(fc1), fd.create_hash(fc2))

        t1 = datetime.datetime(2011, 1, 1)
        expr = fc1.call_functions(pending_call_stack=queue, present_time=t1)

        # Check we got a stub.
        self.assertIsInstance(expr, Stub)
        self.assertEqual(queue.put.call_count, 1)

        # Check the call to the stub was queued.
        first_call = queue.put.mock_calls[0]
        self.assertEqual(first_call[2]['stub_id'], expr.name)
        self.assertEqual(first_call[2]['stacked_function_def'], fd)
        self.assertEqual(first_call[2]['stacked_locals'], {'a': 1234})  # Maybe this should be Number(1234)?
        self.assertEqual(first_call[2]['present_time'], t1)
示例#2
0
def dsl_compile(dsl_source, filename='<unknown>', is_parallel=None, dsl_classes=None, compile_kwds=None, **extraCompileKwds):
    """
    Returns a DSL expression, created according to the given DSL source module.

    That is, if the source module contains a function def and an expression which
    calls that function, then the expression's function call will be evaluated
    and the resulting DSL expression will be substituted for the function call
    in the module's expression, so that calls to user defined functions are eliminated
    and a single DSL expression is obtained.

    If the source module contains a function def, but no expression, the module is compiled
    into a function def object. Calling .apply() on a function def object will return a DSL
    expression object, which can be evaluated by calling its .evaluate() method.
    """
    if compile_kwds is None:
        compile_kwds = DslNamespace()
    assert isinstance(compile_kwds, dict)
    compile_kwds.update(extraCompileKwds)

    # Parse the source into a DSL module object.
    dsl_module = dsl_parse(dsl_source, filename=filename, dsl_classes=dsl_classes)

    assert isinstance(dsl_module, Module)

    # Compile the module into either a dependency graph
    # if 'is_parallel' is True, otherwise a single primitive expression.
    return compile_dsl_module(dsl_module, DslNamespace(), compile_kwds, is_dependency_graph=is_parallel)
示例#3
0
def evaluate_call(call_spec, register_call_result):
    """
    Evaluates the stubbed expr identified by 'call_requirement_id'.
    """
    assert isinstance(call_spec, CallSpecification)

    evaluation_kwds = call_spec.evaluation_kwds.copy()

    # If this call has an effective present time value, use it as the 'present_time' in the evaluation_kwds.
    # This results from e.g. the Wait DSL element. Calls near the root of the expression might not have an
    # effective present time value, and the present time will be the observation time of the evaluation.

    # Evaluate the stubbed expr str.
    # - parse the expr
    try:
        # Todo: Rework this dependency. Figure out how to use alternative set of DSL classes when multiprocessing.
        from quantdsl.domain.services.parser import dsl_parse
        stubbed_module = dsl_parse(call_spec.dsl_expr_str)
    except DslSyntaxError:
        raise

    assert isinstance(stubbed_module, Module), "Parsed stubbed expr string is not a module: %s" % stubbed_module

    # - build a namespace from the dependency values
    dsl_locals = DslNamespace(call_spec.dependency_values)

    # - compile the parsed expr
    dsl_expr = stubbed_module.body[0].reduce(dsl_locals=dsl_locals, dsl_globals=DslNamespace())
    assert isinstance(dsl_expr, DslExpression), dsl_expr

    # - evaluate the compiled expr
    result_value = dsl_expr.evaluate(**evaluation_kwds)

    # - store the result
    register_call_result(call_id=call_spec.id, result_value=result_value)
示例#4
0
def dsl_compile(dsl_source,
                filename='<unknown>',
                dsl_classes=None,
                compile_kwds=None,
                **extraCompileKwds):
    """
    Returns a DSL expression, created according to the given DSL source module.

    That is, if the source module contains a function def and an expression which
    calls that function, then the expression's function call will be evaluated
    and the resulting DSL expression will be substituted for the function call
    in the module's expression, so that calls to user defined functions are eliminated
    and a single DSL expression is obtained.

    If the source module contains a function def, but no expression, the module is compiled
    into a function def object. Calling .apply() on a function def object will return a DSL
    expression object, which can be evaluated by calling its .evaluate() method.
    """
    if compile_kwds is None:
        compile_kwds = DslNamespace()
    # assert isinstance(compile_kwds, dict)
    compile_kwds.update(extraCompileKwds)

    # Parse the source into a DSL module object.
    dsl_module = dsl_parse(dsl_source,
                           filename=filename,
                           dsl_classes=dsl_classes)

    # assert isinstance(dsl_module, Module)

    # Compile the module into either a dependency graph
    # if 'is_parallel' is True, otherwise a single primitive expression.
    return compile_dsl_module(dsl_module, DslNamespace(), compile_kwds)
示例#5
0
def generate_dependency_graph(contract_specification, call_dependencies_repo, call_dependents_repo, call_leafs_repo,
                              call_requirement_repo):

    assert isinstance(contract_specification, ContractSpecification)
    dsl_module = dsl_parse(dsl_source=contract_specification.specification)
    assert isinstance(dsl_module, Module)
    dsl_globals = DslNamespace()
    function_defs, expressions = extract_defs_and_exprs(dsl_module, dsl_globals)
    dsl_expr = expressions[0]
    assert isinstance(dsl_expr, DslExpression)
    dsl_locals = DslNamespace()

    leaf_ids = []
    all_dependents = defaultdict(list)

    # Generate stubbed call from the parsed DSL module object.
    for stub in generate_stubbed_calls(contract_specification.id, dsl_module, dsl_expr, dsl_globals, dsl_locals):
        # assert isinstance(stub, StubbedCall)

        # Register the call requirements.
        call_id = stub.call_id
        dsl_source = str(stub.dsl_expr)
        effective_present_time = stub.effective_present_time
        call_requirement = register_call_requirement(call_id, dsl_source, effective_present_time)

        # Hold onto the dsl_expr, helps in "single process" modes....
        call_requirement._dsl_expr = stub.dsl_expr
        # - put the entity directly in the cache, otherwise the entity will be regenerated when it is next accessed
        #   and the _dsl_expr will be lost.
        call_requirement_repo.add_cache(call_id, call_requirement)

        # Register the call requirements.
        dependencies = stub.requirements
        register_call_dependencies(call_id, dependencies)

        # Keep track of the leaves and the dependents.
        if len(dependencies) == 0:
            leaf_ids.append(call_id)
        else:
            for dependency_call_id in dependencies:
                all_dependents[dependency_call_id].append(call_id)

    # Register the call dependents.
    for call_id, dependents in all_dependents.items():
        register_call_dependents(call_id, dependents)
    register_call_dependents(contract_specification.id, [])

    # Generate and register the call order.
    link_id = contract_specification.id
    for call_id in generate_execution_order(leaf_ids, call_dependents_repo, call_dependencies_repo):
        register_call_link(link_id, call_id)
        link_id = call_id

    # Register the leaf ids.
    register_call_leafs(contract_specification.id, leaf_ids)
示例#6
0
def compile_dsl_module(dsl_module, dsl_locals=None, dsl_globals=None):
    """
    Returns something that can be evaluated.
    """

    # It's a module compilation, so create a new namespace "context".
    if dsl_locals is None:
        dsl_locals = {}
    dsl_locals = DslNamespace(dsl_locals)
    if dsl_globals is None:
        dsl_globals = {}
    dsl_globals = DslNamespace(dsl_globals)

    # Can't do much with an empty module.
    if len(dsl_module.body) == 0:
        raise DslSyntaxError('empty module', node=dsl_module.node)

    function_defs, expressions = extract_defs_and_exprs(
        dsl_module, dsl_globals)

    # Handle different combinations of functions and module level expressions in different ways.
    # Todo: Simplify this, but support library files first?
    # Can't meaningfully evaluate more than one expression (since assignments are not supported).
    if len(expressions) > 1:
        raise DslSyntaxError('more than one expression in module',
                             node=expressions[1].node)

    # Can't meaningfully evaluate more than one function def without a module level expression.
    elif len(expressions) == 0 and len(function_defs) > 1:
        second_def = function_defs[1]
        raise DslSyntaxError(
            'more than one function def in module without an expression',
            '"def %s"' % second_def.name,
            node=function_defs[1].node)

    # If it's just a module with one function, then return the function def.
    elif len(expressions) == 0 and len(function_defs) == 1:
        return function_defs[0]

    # If there is one expression, reduce it with the function defs that it calls.
    else:
        assert len(expressions) == 1
        dsl_expr = expressions[0]
        # assert isinstance(dsl_expr, DslExpression), dsl_expr

        # Compile the module for a single threaded recursive operation (faster but not distributed,
        # so also limited in space and perhaps time). For smaller computations only.
        dsl_obj = dsl_expr.substitute_names(dsl_globals.combine(dsl_locals))
        return dsl_obj
示例#7
0
def evaluate_dsl_expr(dsl_expr, first_commodity_name, simulation_id,
                      interest_rate, present_time, simulated_value_dict,
                      perturbation_dependencies, dependency_results,
                      path_count):

    evaluation_kwds = {
        'simulated_value_dict': simulated_value_dict,
        'simulation_id': simulation_id,
        'interest_rate': interest_rate,
        'present_time': present_time,
        'first_commodity_name': first_commodity_name,
        'path_count': path_count,
    }

    result_value = None
    perturbed_values = {}

    for perturbation in [None] + perturbation_dependencies:

        evaluation_kwds['active_perturbation'] = perturbation

        # Initialise namespace with the dependency values.
        dependency_values = {}
        for stub_id in dependency_results.keys():
            (dependency_result_value,
             dependency_perturbed_values) = dependency_results[stub_id]
            try:
                dependency_value = dependency_perturbed_values[str(
                    perturbation)]
            except KeyError:
                dependency_value = dependency_result_value
            dependency_values[stub_id] = dependency_value

        dsl_locals = DslNamespace(dependency_values)

        # Compile the parsed expr using the namespace to give something that can be evaluated.
        dsl_expr_reduced = dsl_expr.reduce(dsl_locals=dsl_locals,
                                           dsl_globals=DslNamespace())
        # assert isinstance(dsl_expr, DslExpression), dsl_expr

        expr_value = dsl_expr_reduced.evaluate(**evaluation_kwds)
        if perturbation is None:
            assert result_value is None
            result_value = expr_value
        else:
            perturbed_values[perturbation] = expr_value

    return result_value, perturbed_values
示例#8
0
def generate_dependency_graph(contract_specification, call_dependencies_repo,
                              call_dependents_repo):

    assert isinstance(contract_specification, ContractSpecification)
    dsl_module = dsl_parse(dsl_source=contract_specification.specification)
    assert isinstance(dsl_module, Module)
    dsl_globals = DslNamespace()
    function_defs, expressions = extract_defs_and_exprs(
        dsl_module, dsl_globals)
    dsl_expr = expressions[0]
    assert isinstance(dsl_expr, DslExpression)
    dsl_locals = DslNamespace()

    leaf_call_ids = []
    all_dependents = defaultdict(list)
    # Generate stubbed call from the parsed DSL module object.
    for stub in generate_stubbed_calls(contract_specification.id, dsl_module,
                                       dsl_expr, dsl_globals, dsl_locals):
        assert isinstance(stub, StubbedCall)
        call_id = stub.call_id
        dsl_source = stub.dsl_source
        effective_present_time = stub.effective_present_time
        dependencies = stub.dependencies

        # Register the call requirements.
        register_call_requirement(call_id, dsl_source, effective_present_time)

        # Register the call dependencies.
        register_call_dependencies(call_id, dependencies)

        # Keep track of the leaves and the dependents.
        if len(dependencies) == 0:
            leaf_call_ids.append(call_id)
        else:
            for dependency_call_id in dependencies:
                all_dependents[dependency_call_id].append(call_id)

    # Register the call dependents.
    for call_id, dependents in all_dependents.items():
        register_call_dependents(call_id, dependents)
    register_call_dependents(contract_specification.id, [])
    # Generate and register the call order.
    link_id = contract_specification.id
    for call_id in generate_execution_order(leaf_call_ids,
                                            call_dependents_repo,
                                            call_dependencies_repo):
        register_call_link(link_id, call_id)
        link_id = call_id
示例#9
0
    def visitModule(self, node):
        """
        Visitor method for ast.Module nodes.

        Returns a DSL Module, with a list of DSL expressions as the body.
        """
        # assert isinstance(node, ast.Module)
        body = []

        # Namespace for function defs in module.
        module_namespace = DslNamespace()

        for n in node.body:
            dsl_object = self.visitAstNode(n)

            # Put function defs in module namespace.
            if isinstance(dsl_object, FunctionDef):
                module_namespace[dsl_object.name] = dsl_object

                # Share module namespace with this function.
                if dsl_object.module_namespace is None:
                    dsl_object.module_namespace = module_namespace

            # Include imported things.
            if isinstance(dsl_object, list):
                for _dsl_object in dsl_object:
                    if isinstance(_dsl_object, FunctionDef):
                        module_namespace[_dsl_object.name] = _dsl_object
            else:
                body.append(dsl_object)

        return self.dsl_classes['Module'](body, module_namespace, node=node)
示例#10
0
def evaluate_dsl_expr(dsl_expr, simulation_id, interest_rate, present_time,
                      simulated_value_dict, perturbation_dependencies,
                      dependency_results, path_count, perturbation_factor,
                      periodisation, estimated_cost_of_expr, observation_date,
                      is_double_sided_deltas, involved_market_names):

    evaluation_kwds = {
        'simulated_value_dict': simulated_value_dict,
        'simulation_id': simulation_id,
        'interest_rate': interest_rate,
        'perturbation_factor': perturbation_factor,
        'observation_date': observation_date,
        'present_time': present_time,
        'involved_market_names': involved_market_names,
        'path_count': path_count,
        'periodisation': periodisation,
    }

    result_value = None
    perturbed_values = {}

    # Decide perturbation names.
    perturbation_names = perturbation_dependencies
    if is_double_sided_deltas:
        perturbation_names += ['-' + p for p in perturbation_dependencies]

    for perturbation_name in [''] + perturbation_names:

        evaluation_kwds['active_perturbation'] = perturbation_name

        # Initialise namespace with the dependency values.
        dependency_values = {}
        for stub_id in dependency_results.keys():
            dependency_result = dependency_results[stub_id]
            try:
                dependency_value = dependency_result.perturbed_values[
                    perturbation_name]
            except KeyError:
                dependency_value = dependency_result.result_value
            dependency_values[stub_id] = dependency_value

        # Prepare the namespace with the values the expression depends on.
        dsl_locals = DslNamespace(dependency_values)

        # Substitute Name elements, to give something that can be evaluated.
        dsl_expr_resolved = dsl_expr.substitute_names(dsl_locals)

        # Evaluate the expression.
        expr_value = dsl_expr_resolved.evaluate(**evaluation_kwds)
        if not perturbation_name:
            assert result_value is None
            result_value = expr_value
        else:
            perturbed_values[perturbation_name] = expr_value

        # Publish result value computed event.
        publish(ResultValueComputed(estimated_cost_of_expr))

    return result_value, perturbed_values
示例#11
0
def compile_dsl_module(dsl_module, dsl_locals=None, dsl_globals=None):
    """
    Returns something that can be evaluated.
    """

    # It's a module compilation, so create a new namespace "context".
    if dsl_locals is None:
        dsl_locals = {}
    dsl_locals = DslNamespace(dsl_locals)
    if dsl_globals is None:
        dsl_globals = {}
    dsl_globals = DslNamespace(dsl_globals)

    # Can't do much with an empty module.
    if len(dsl_module.body) == 0:
        raise DslSyntaxError('empty module', node=dsl_module.node)

    function_defs, expressions = extract_defs_and_exprs(dsl_module, dsl_globals)

    # Handle different combinations of functions and module level expressions in different ways.
    # Todo: Simplify this, but support library files first?
    # Can't meaningfully evaluate more than one expression (since assignments are not supported).
    if len(expressions) > 1:
        raise DslSyntaxError('more than one expression in module', node=expressions[1].node)

    # Can't meaningfully evaluate more than one function def without a module level expression.
    elif len(expressions) == 0 and len(function_defs) > 1:
        second_def = function_defs[1]
        raise DslSyntaxError('more than one function def in module without an expression',
                             '"def %s"' % second_def.name, node=function_defs[1].node)

    # If it's just a module with one function, then return the function def.
    elif len(expressions) == 0 and len(function_defs) == 1:
        return function_defs[0]

    # If there is one expression, reduce it with the function defs that it calls.
    else:
        assert len(expressions) == 1
        dsl_expr = expressions[0]
        # assert isinstance(dsl_expr, DslExpression), dsl_expr

        # Compile the module for a single threaded recursive operation (faster but not distributed,
        # so also limited in space and perhaps time). For smaller computations only.
        dsl_obj = dsl_expr.substitute_names(dsl_globals.combine(dsl_locals))
        return dsl_obj
示例#12
0
 def test_substitute_names(self):
     fc = FunctionCall(Name('f'), [Name('x')])
     fd = FunctionDef('f', [], Name('a'), [])
     ns = DslNamespace({
         'f': fd,
         'x': Number(1),
     })
     fc1 = fc.substitute_names(ns)
     self.assertEqual(fc1.functionDef, fd)
     self.assertEqual(fc1.callArgExprs[0], Number(1))
示例#13
0
    def test_call_functions_with_pending_call_stack(self):
        fc = FunctionCall(Name('f'), [Name('x')])
        fd = FunctionDef('f', [FunctionArg('a', '')], Name('a'), [])
        namespace = DslNamespace({fd.name: fd})
        fd.module_namespace = namespace
        number = Number(1234)
        ns = DslNamespace({
            'f': fd,
            'x': number,
        })

        # Substitute names.
        fc1 = fc.substitute_names(ns)
        self.assertEqual(fc1.functionDef, fd)
        self.assertEqual(fc1.callArgExprs[0], number)

        # Call functions.
        expr = fc1.call_functions()
        self.assertEqual(expr, number)
示例#14
0
    def test_substitute(self):
        # Maybe if Name can take a string, perhaps also other things can?
        # Maybe the parser should return Python string, numbers etc?
        # Maybe String and Number etc don't add anything?
        obj = Name('a')
        self.assertEqual(obj.name, 'a')

        obj = Name(String('a'))
        self.assertEqual(obj.name, 'a')

        ns = DslNamespace()
        with self.assertRaises(DslNameError):
            obj.substitute_names(ns)

        ns = DslNamespace({'a': 1})
        self.assertEqual(obj.substitute_names(ns), Number(1))

        ns = DslNamespace({'a': datetime.timedelta(1)})
        self.assertEqual(obj.substitute_names(ns), TimeDelta(datetime.timedelta(1)))

        function_def = FunctionDef('f', [], Number(1), [])
        ns = DslNamespace({'a': function_def})
        self.assertEqual(obj.substitute_names(ns), function_def)
示例#15
0
 def calc_value(self, dsl_source, observation_date):
     # Todo: Rename 'allRvs' to 'simulatedPrices'?
     evaluation_kwds = DslNamespace({
         'observation_date': observation_date,
         'interest_rate': '2.5',
         'market_calibration': {
             '#1-LAST-PRICE': 10,
             '#1-ACTUAL-HISTORICAL-VOLATILITY': 50,
             '#2-LAST-PRICE': 10,
             '#2-ACTUAL-HISTORICAL-VOLATILITY': 50,
             '#1-#2-CORRELATION': 0.0,
             'NBP-LAST-PRICE': 10,
             'NBP-ACTUAL-HISTORICAL-VOLATILITY': 50,
             'TTF-LAST-PRICE': 11,
             'TTF-ACTUAL-HISTORICAL-VOLATILITY': 40,
             'BRENT-LAST-PRICE': 90,
             'BRENT-ACTUAL-HISTORICAL-VOLATILITY': 60,
             'NBP-TTF-CORRELATION': 0.4,
             'BRENT-TTF-CORRELATION': 0.5,
             'BRENT-NBP-CORRELATION': 0.3,
         },
         'path_count': 200000,
         # 'simulated_price_repo': {
         #     'sim1#12011-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1#12011-01-03': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1#12011-06-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1#12012-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1#12012-01-02': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1#12012-01-03': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1#12012-06-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1#12013-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #
         #     'sim1#22011-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1#22012-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #
         #     'sim1TTF2012-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1NBP2012-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1TTF2013-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         #     'sim1NBP2013-01-01': Mock(spec=SimulatedPrice, value=numpy.array([10])),
         # },
         'simulation_id': 'sim1',
         'first_market_name': '#1',
     })
     return dsl_eval(dsl_source, evaluation_kwds=evaluation_kwds)
示例#16
0
    def generate_contract_valuation(self, dependency_graph_id, market_simulation):
        assert isinstance(dependency_graph_id, six.string_types), dependency_graph_id
        assert isinstance(market_simulation, MarketSimulation)
        v = self.register_contract_valuation(dependency_graph_id)

        for call_id in regenerate_execution_order(dependency_graph_id, self.call_link_repo):

            call = self.call_requirement_repo[call_id]
            assert isinstance(call, CallRequirement)

            # Evaluate the call requirement.
            dependency_values = get_dependency_values(call_id, self.call_dependencies_repo, self.call_result_repo)

            # - parse the expr
            stubbed_module = dsl_parse(call.dsl_source)

            assert isinstance(stubbed_module, Module), "Parsed stubbed expr string is not a module: %s" % stubbed_module

            # - build a namespace from the dependency values
            dsl_locals = DslNamespace(dependency_values)

            # - compile the parsed expr
            dsl_expr = stubbed_module.body[0].reduce(dsl_locals=dsl_locals, dsl_globals=DslNamespace())
            assert isinstance(dsl_expr, DslExpression), dsl_expr

            # - evaluate the compiled expr
            first_market_name = market_simulation.market_names[0] if market_simulation.market_names else None
            evaluation_kwds = {
                'simulated_price_repo': self.simulated_price_repo,
                'simulation_id': market_simulation.id,
                'interest_rate': market_simulation.interest_rate,
                'present_time': call.effective_present_time or market_simulation.observation_date,
                'first_market_name': first_market_name,
            }
            result_value = dsl_expr.evaluate(**evaluation_kwds)

            # - store the result
            register_call_result(call_id=call_id, result_value=result_value)
示例#17
0
    def assertDslExprTypeValue(self, dsl_source, expectedDslType,
                               expectedDslValue, **compile_kwds):
        # Assumes dsl_source is just one statement.
        dsl_module = dsl_parse(dsl_source)

        assert isinstance(dsl_module, Module)

        # Check the parsed DSL can be rendered as a string that is equal to the original source.
        self.assertEqual(str(dsl_module), dsl_source.strip())

        # Assume this test is dealing with modules that have one statement only.
        dsl_expr = dsl_module.body[0]

        # Check expression type.
        assert isinstance(dsl_expr, DslExpression)
        self.assertIsInstance(dsl_expr, expectedDslType)

        # Compile the module into a simple DSL expression object (no variables or calls to function defs).
        dsl_expr = dsl_expr.substitute_names(DslNamespace(compile_kwds))

        # Evaluate the compiled expression.
        dsl_value = dsl_expr.evaluate()

        self.assertEqual(dsl_value, expectedDslValue)
示例#18
0
def dsl_eval(dsl_source, filename='<unknown>', is_parallel=None, dsl_classes=None, compile_kwds=None,
             evaluation_kwds=None, price_process_name=None, is_multiprocessing=False, pool_size=0, is_verbose=False,
             is_show_source=False, **extra_evaluation_kwds):
    """
    Returns the result of evaluating a compiled module (an expression, or a user defined function).

    An expression (with optional function defs) will evaluate to a simple value.

    A function def will evaluate to a DSL expression, will may then be evaluated (more than one
    function def without an expression is an error).
    """
    if price_process_name is None:
        price_process_name = DEFAULT_PRICE_PROCESS_NAME

    if evaluation_kwds is None:
        evaluation_kwds = DslNamespace()
    assert isinstance(evaluation_kwds, dict)
    evaluation_kwds.update(extra_evaluation_kwds)

    if is_show_source:
        print_("Reading DSL source:")
        print_()
        print_('"""')
        print_(dsl_source.strip())
        print_('"""')
        print_()

    if is_verbose:
        print_("Compiling DSL source, please wait...")
        print_()
    compile_start_time = datetime.datetime.now()

    # Compile the source into a primitive DSL expression, with optional dependency graph.
    dsl_expr = dsl_compile(dsl_source, filename=filename, is_parallel=is_parallel, dsl_classes=dsl_classes,
                           compile_kwds=compile_kwds)

    # Measure the compile_dsl_module time.
    compile_time_delta = datetime.datetime.now() - compile_start_time

    # Check the result of the compilation.
    # Todo: This feels unnecessary?
    if is_parallel:
        assert isinstance(dsl_expr, DependencyGraph), type(dsl_expr)
    else:
        assert isinstance(dsl_expr, DslExpression), type(dsl_expr)

    if is_verbose:
        if isinstance(dsl_expr, DependencyGraph):

            print_("Compiled DSL source into %d partial expressions (root ID: %s)." % (
                len(dsl_expr.stubbed_calls), dsl_expr.root_stub_id))
            print_()

        print_("Duration of compilation: %s" % compile_time_delta)
        print_()

        if isinstance(dsl_expr, DependencyGraph):
            if is_show_source:
                print_("Expression stack:")
                for stubbed_exprData in dsl_expr.stubbed_calls:
                    print_("  " + str(stubbed_exprData[0]) + ": " + str(stubbed_exprData[1]))
                print_()

    # If the expression has any stochastic elements, the evaluation kwds must have an 'observation_date' (datetime).
    if dsl_expr.has_instances(dsl_type=StochasticObject):
        observation_date = evaluation_kwds['observation_date']
        assert isinstance(observation_date, datetime.date)

        if is_verbose:
            print_("Observation time: %s" % observation_date)
            print_()

        # Avoid any confusion with the internal 'present_time' variable.
        if 'present_time' in evaluation_kwds:
            msg = ("Don't set present_time here, set observation_date instead. "
                   "Hint: Adjust effective present time with Fixing or Wait elements.")
            raise DslError(msg)

        # Initialise present_time as observation_date.
        evaluation_kwds['present_time'] = observation_date

        # If the expression has any Market elements, a market simulation is required
        if dsl_expr.has_instances(dsl_type=Market):

            # If a market simulation is required, evaluation kwds must have 'path_count' (integer).
            if 'path_count' not in evaluation_kwds:
                evaluation_kwds['path_count'] = DEFAULT_PATH_COUNT
            path_count = evaluation_kwds['path_count']
            assert isinstance(path_count, int)

            # If a market simulation is required, evaluation_kwds must have 'market_calibration' (integer).
            market_calibration = evaluation_kwds['market_calibration']
            assert isinstance(market_calibration, dict)

            # If a market simulation is required, generate the simulated prices using the price process.
            if not 'all_market_prices' in evaluation_kwds:

                if is_verbose:
                    print_("Price process: %s" % price_process_name)
                    print_()

                price_process = get_price_process(price_process_name)

                if is_verbose:
                    print_("Path count: %d" % path_count)
                    print_()

                if is_verbose:
                    print_("Finding all Market names and Fixing dates...")
                    print_()

                # Extract market names from the expression.
                # Todo: Avoid doing this on the dependency graph, when all the Market elements must be in the original.
                market_names = find_market_names(dsl_expr)

                # Extract fixing dates from the expression.
                # Todo: Perhaps collect the fixing dates?
                fixing_dates = list_fixing_dates(dsl_expr)

                if is_verbose:
                    print_("Simulating future prices for Market%s '%s' from observation time %s through fixing dates: %s." % (
                        '' if len(market_names) == 1 else 's',
                        ", ".join(market_names),
                        "'%04d-%02d-%02d'" % (observation_date.year, observation_date.month, observation_date.day),
                        # Todo: Only print first and last few, if there are loads.
                        ", ".join(["'%04d-%02d-%02d'" % (d.year, d.month, d.day) for d in fixing_dates[:8]]) + \
                        (", [...]" if len(fixing_dates) > 9 else '') + \
                        ((", '%04d-%02d-%02d'" % (fixing_dates[-1].year, fixing_dates[-1].month, fixing_dates[-1].day)) if len(fixing_dates) > 8 else '')
                    ))
                    print_()

                # Simulate the future prices.
                all_market_prices = price_process.simulate_future_prices(market_names, fixing_dates, observation_date, path_count, market_calibration)

                # Add future price simulation to evaluation_kwds.
                evaluation_kwds['all_market_prices'] = all_market_prices

    # Initialise the evaluation timer variable (needed by showProgress thread).
    evalStartTime = None

    if is_parallel:
        if is_verbose:

            len_stubbed_exprs = len(dsl_expr.stubbed_calls)
            lenLeafIds = len(dsl_expr.leaf_ids)

            msg = "Evaluating %d expressions (%d %s) with " % (len_stubbed_exprs, lenLeafIds, 'leaf' if lenLeafIds == 1 else 'leaves')
            if is_multiprocessing and pool_size:
                msg += "a multiprocessing pool of %s workers" % pool_size
            else:
                msg += "a single thread"
            msg += ", please wait..."

            print_(msg)
            print_()

            # Define showProgress() thread.
            def showProgress(stop):
                progress = 0
                movingRates = []
                while progress < 100 and not stop.is_set():
                    time.sleep(0.3)
                    if evalStartTime is None:
                        continue
                    # Avoid race condition.
                    if not hasattr(dsl_expr, 'runner') or not hasattr(dsl_expr.runner, 'resultIds'):
                        continue
                    if stop.is_set():
                        break

                    try:
                        lenResults = len(dsl_expr.runner.resultIds)
                    except IOError:
                         break
                    resultsTime = datetime.datetime.now()
                    movingRates.append((lenResults, resultsTime))
                    if len(movingRates) >= 15:
                        movingRates.pop(0)
                    if len(movingRates) > 1:
                        firstLenResults, firstTimeResults = movingRates[0]
                        lastLenResults, lastTimeResults = movingRates[-1]
                        lenDelta = lastLenResults - firstLenResults
                        resultsTimeDelta = lastTimeResults - firstTimeResults
                        timeDeltaSeconds = resultsTimeDelta.seconds + resultsTimeDelta.microseconds * 0.000001
                        rateStr = "%.2f expr/s" % (lenDelta / timeDeltaSeconds)
                    else:
                        rateStr = ''
                    progress = 100.0 * lenResults / len_stubbed_exprs
                    sys.stdout.write("\rProgress: %01.2f%% (%s/%s) %s " % (progress, lenResults, len_stubbed_exprs, rateStr))
                    sys.stdout.flush()
                sys.stdout.write("\r")
                sys.stdout.flush()
            stop = threading.Event()
            thread = threading.Thread(target=showProgress, args=(stop,))

            # Start showProgress() thread.
            thread.start()

    # Start timing the evaluation.
    evalStartTime = datetime.datetime.now()
    try:
        # Evaluate the primitive DSL expression.
        if is_parallel:
            if is_multiprocessing:
                dependency_graph_runner_class = MultiProcessingDependencyGraphRunner
            else:
                dependency_graph_runner_class = SingleThreadedDependencyGraphRunner
            value = dsl_expr.evaluate(dependency_graph_runner_class=dependency_graph_runner_class, pool_size=pool_size, **evaluation_kwds)
        else:
            value = dsl_expr.evaluate(**evaluation_kwds)
    except:
        if is_parallel:
            if is_verbose:
                if thread.isAlive():
                    # print "Thread is alive..."
                    stop.set()
                    # print "Waiting to join with thread..."
                    thread.join(timeout=1)
                    # print "Joined with thread..."
        raise

    # Stop timing the evaluation.
    evalTimeDelta = datetime.datetime.now() - evalStartTime

    if isinstance(dsl_expr, DependencyGraph):
        if is_verbose:
            # Join with showProgress thread.
            thread.join(timeout=3)

    if is_verbose:
        timeDeltaSeconds = evalTimeDelta.seconds + evalTimeDelta.microseconds * 0.000001
        if is_parallel:
            len_stubbed_exprs = len(dsl_expr.stubbed_calls)
            rateStr = "(%.2f expr/s)" % (len_stubbed_exprs / timeDeltaSeconds)
        else:
            rateStr = ''
        print_("Duration of evaluation: %s    %s" % (evalTimeDelta, rateStr))
        print_()

    # Prepare the result.
    import scipy
    if isinstance(value, scipy.ndarray):
        mean = value.mean()
        stderr = value.std() / math.sqrt(path_count)
        return {
            'mean': mean,
            'stderr': stderr
        }
    else:
        return value
示例#19
0
def dsl_eval(dsl_source,
             filename='<unknown>',
             is_parallel=None,
             dsl_classes=None,
             compile_kwds=None,
             evaluation_kwds=None,
             price_process_name=None,
             is_multiprocessing=False,
             pool_size=0,
             is_verbose=False,
             is_show_source=False,
             **extra_evaluation_kwds):
    """
    Returns the result of evaluating a compiled module (an expression, or a user defined function).

    An expression (with optional function defs) will evaluate to a simple value.

    A function def will evaluate to a DSL expression, will may then be evaluated (more than one
    function def without an expression is an error).
    """
    if price_process_name is None:
        price_process_name = DEFAULT_PRICE_PROCESS_NAME

    if evaluation_kwds is None:
        evaluation_kwds = DslNamespace()
    assert isinstance(evaluation_kwds, dict)
    evaluation_kwds.update(extra_evaluation_kwds)

    if is_show_source:
        print_("Reading DSL source:")
        print_()
        print_('"""')
        print_(dsl_source.strip())
        print_('"""')
        print_()

    if is_verbose:
        print_("Compiling DSL source, please wait...")
        print_()
    compile_start_time = datetime.datetime.now()

    # Compile the source into a primitive DSL expression, with optional dependency graph.
    dsl_expr = dsl_compile(dsl_source,
                           filename=filename,
                           is_parallel=is_parallel,
                           dsl_classes=dsl_classes,
                           compile_kwds=compile_kwds)

    # Measure the compile_dsl_module time.
    compile_time_delta = datetime.datetime.now() - compile_start_time

    # Check the result of the compilation.
    # Todo: This feels unnecessary?
    if is_parallel:
        assert isinstance(dsl_expr, DependencyGraph), type(dsl_expr)
    else:
        assert isinstance(dsl_expr, DslExpression), type(dsl_expr)

    if is_verbose:
        if isinstance(dsl_expr, DependencyGraph):

            print_(
                "Compiled DSL source into %d partial expressions (root ID: %s)."
                % (len(dsl_expr.stubbed_calls), dsl_expr.root_stub_id))
            print_()

        print_("Duration of compilation: %s" % compile_time_delta)
        print_()

        if isinstance(dsl_expr, DependencyGraph):
            if is_show_source:
                print_("Expression stack:")
                for stubbed_exprData in dsl_expr.stubbed_calls:
                    print_("  " + str(stubbed_exprData[0]) + ": " +
                           str(stubbed_exprData[1]))
                print_()

    # If the expression has any stochastic elements, the evaluation kwds must have an 'observation_date' (datetime).
    if dsl_expr.has_instances(dsl_type=StochasticObject):
        observation_date = evaluation_kwds['observation_date']
        assert isinstance(observation_date, datetime.date)

        if is_verbose:
            print_("Observation time: %s" % observation_date)
            print_()

        # Avoid any confusion with the internal 'present_time' variable.
        if 'present_time' in evaluation_kwds:
            msg = (
                "Don't set present_time here, set observation_date instead. "
                "Hint: Adjust effective present time with Fixing or Wait elements."
            )
            raise DslError(msg)

        # Initialise present_time as observation_date.
        evaluation_kwds['present_time'] = observation_date

        # If the expression has any Market elements, a market simulation is required
        if dsl_expr.has_instances(dsl_type=Market):

            # If a market simulation is required, evaluation kwds must have 'path_count' (integer).
            if 'path_count' not in evaluation_kwds:
                evaluation_kwds['path_count'] = DEFAULT_PATH_COUNT
            path_count = evaluation_kwds['path_count']
            assert isinstance(path_count, int)

            # If a market simulation is required, evaluation_kwds must have 'market_calibration' (integer).
            market_calibration = evaluation_kwds['market_calibration']
            assert isinstance(market_calibration, dict)

            # If a market simulation is required, generate the simulated prices using the price process.
            if not 'all_market_prices' in evaluation_kwds:

                if is_verbose:
                    print_("Price process: %s" % price_process_name)
                    print_()

                price_process = get_price_process(price_process_name)

                if is_verbose:
                    print_("Path count: %d" % path_count)
                    print_()

                if is_verbose:
                    print_("Finding all Market names and Fixing dates...")
                    print_()

                # Extract market names from the expression.
                # Todo: Avoid doing this on the dependency graph, when all the Market elements must be in the original.
                market_names = find_market_names(dsl_expr)

                # Extract fixing dates from the expression.
                # Todo: Perhaps collect the fixing dates?
                fixing_dates = list_fixing_dates(dsl_expr)

                if is_verbose:
                    print_("Simulating future prices for Market%s '%s' from observation time %s through fixing dates: %s." % (
                        '' if len(market_names) == 1 else 's',
                        ", ".join(market_names),
                        "'%04d-%02d-%02d'" % (observation_date.year, observation_date.month, observation_date.day),
                        # Todo: Only print first and last few, if there are loads.
                        ", ".join(["'%04d-%02d-%02d'" % (d.year, d.month, d.day) for d in fixing_dates[:8]]) + \
                        (", [...]" if len(fixing_dates) > 9 else '') + \
                        ((", '%04d-%02d-%02d'" % (fixing_dates[-1].year, fixing_dates[-1].month, fixing_dates[-1].day)) if len(fixing_dates) > 8 else '')
                    ))
                    print_()

                # Simulate the future prices.
                all_market_prices = price_process.simulate_future_prices(
                    market_names, fixing_dates, observation_date, path_count,
                    market_calibration)

                # Add future price simulation to evaluation_kwds.
                evaluation_kwds['all_market_prices'] = all_market_prices

    # Initialise the evaluation timer variable (needed by showProgress thread).
    evalStartTime = None

    if is_parallel:
        if is_verbose:

            len_stubbed_exprs = len(dsl_expr.stubbed_calls)
            lenLeafIds = len(dsl_expr.leaf_ids)

            msg = "Evaluating %d expressions (%d %s) with " % (
                len_stubbed_exprs, lenLeafIds,
                'leaf' if lenLeafIds == 1 else 'leaves')
            if is_multiprocessing and pool_size:
                msg += "a multiprocessing pool of %s workers" % pool_size
            else:
                msg += "a single thread"
            msg += ", please wait..."

            print_(msg)
            print_()

            # Define showProgress() thread.
            def showProgress(stop):
                progress = 0
                movingRates = []
                while progress < 100 and not stop.is_set():
                    time.sleep(0.3)
                    if evalStartTime is None:
                        continue
                    # Avoid race condition.
                    if not hasattr(dsl_expr, 'runner') or not hasattr(
                            dsl_expr.runner, 'resultIds'):
                        continue
                    if stop.is_set():
                        break

                    try:
                        lenResults = len(dsl_expr.runner.resultIds)
                    except IOError:
                        break
                    resultsTime = datetime.datetime.now()
                    movingRates.append((lenResults, resultsTime))
                    if len(movingRates) >= 15:
                        movingRates.pop(0)
                    if len(movingRates) > 1:
                        firstLenResults, firstTimeResults = movingRates[0]
                        lastLenResults, lastTimeResults = movingRates[-1]
                        lenDelta = lastLenResults - firstLenResults
                        resultsTimeDelta = lastTimeResults - firstTimeResults
                        timeDeltaSeconds = resultsTimeDelta.seconds + resultsTimeDelta.microseconds * 0.000001
                        rateStr = "%.2f expr/s" % (lenDelta / timeDeltaSeconds)
                    else:
                        rateStr = ''
                    progress = 100.0 * lenResults / len_stubbed_exprs
                    sys.stdout.write(
                        "\rProgress: %01.2f%% (%s/%s) %s " %
                        (progress, lenResults, len_stubbed_exprs, rateStr))
                    sys.stdout.flush()
                sys.stdout.write("\r")
                sys.stdout.flush()

            stop = threading.Event()
            thread = threading.Thread(target=showProgress, args=(stop, ))

            # Start showProgress() thread.
            thread.start()

    # Start timing the evaluation.
    evalStartTime = datetime.datetime.now()
    try:
        # Evaluate the primitive DSL expression.
        if is_parallel:
            if is_multiprocessing:
                dependency_graph_runner_class = MultiProcessingDependencyGraphRunner
            else:
                dependency_graph_runner_class = SingleThreadedDependencyGraphRunner
            value = dsl_expr.evaluate(
                dependency_graph_runner_class=dependency_graph_runner_class,
                pool_size=pool_size,
                **evaluation_kwds)
        else:
            value = dsl_expr.evaluate(**evaluation_kwds)
    except:
        if is_parallel:
            if is_verbose:
                if thread.isAlive():
                    # print "Thread is alive..."
                    stop.set()
                    # print "Waiting to join with thread..."
                    thread.join(timeout=1)
                    # print "Joined with thread..."
        raise

    # Stop timing the evaluation.
    evalTimeDelta = datetime.datetime.now() - evalStartTime

    if isinstance(dsl_expr, DependencyGraph):
        if is_verbose:
            # Join with showProgress thread.
            thread.join(timeout=3)

    if is_verbose:
        timeDeltaSeconds = evalTimeDelta.seconds + evalTimeDelta.microseconds * 0.000001
        if is_parallel:
            len_stubbed_exprs = len(dsl_expr.stubbed_calls)
            rateStr = "(%.2f expr/s)" % (len_stubbed_exprs / timeDeltaSeconds)
        else:
            rateStr = ''
        print_("Duration of evaluation: %s    %s" % (evalTimeDelta, rateStr))
        print_()

    # Prepare the result.
    import scipy
    if isinstance(value, scipy.ndarray):
        mean = value.mean()
        stderr = value.std() / math.sqrt(path_count)
        return {'mean': mean, 'stderr': stderr}
    else:
        return value
示例#20
0
def generate_dependency_graph(contract_specification,
                              call_dependencies_repo,
                              call_dependents_repo,
                              call_requirement_repo,
                              dsl_classes=None):
    assert isinstance(contract_specification, ContractSpecification)
    dsl_module = dsl_parse(
        dsl_source=contract_specification.source_code,
        dsl_classes=dsl_classes,
    )
    assert isinstance(dsl_module, Module)
    dsl_globals = dsl_module.namespace.copy()
    function_defs, expressions = extract_defs_and_exprs(
        dsl_module, dsl_globals)
    dsl_expr = expressions[0]
    assert isinstance(dsl_expr, DslExpression)
    dsl_locals = DslNamespace()

    leaf_ids = []
    all_dependents = defaultdict(list)

    # Generate stubbed call from the parsed DSL module object.
    for stubed_call in generate_stubbed_calls(
            contract_specification.id, dsl_expr, dsl_globals, dsl_locals,
            contract_specification.observation_date):
        # assert isinstance(stub, StubbedCall)

        # Estimate the cost of evaluating this expression.
        estimated_cost = stubed_call.dsl_expr.cost_expression()

        # Register the call requirements.
        call_id = stubed_call.call_id
        dsl_source = str(stubed_call.dsl_expr)
        present_time = stubed_call.present_time
        call_requirement = register_call_requirement(
            call_id=call_id,
            dsl_source=dsl_source,
            present_time=present_time,
            contract_specification_id=contract_specification.id,
            cost=estimated_cost,
        )

        # Hold onto the dsl_expr, helps in "single process" modes.
        # - put the entity directly in the cache, otherwise the entity will be
        # regenerated when it is next accessed and the _dsl_expr will be "lost"
        call_requirement._dsl_expr = stubed_call.dsl_expr
        call_requirement_repo.add_cache(call_id, call_requirement)

        # Register the call dependencies (things needed by this call).
        dependencies = stubed_call.requirements
        register_call_dependencies(call_id, dependencies)

        # Keep track of the leaves and the dependents.
        if len(dependencies) == 0:
            leaf_ids.append(call_id)
        else:
            for dependency_call_id in dependencies:
                all_dependents[dependency_call_id].append(call_id)

    # Register the call dependents.
    for call_id, dependents in all_dependents.items():
        register_call_dependents(call_id, dependents)
    register_call_dependents(contract_specification.id, [])

    # Generate and register the call order.
    link_id = contract_specification.id
    for call_id in generate_execution_order(leaf_ids, call_dependents_repo,
                                            call_dependencies_repo):
        register_call_link(link_id, call_id)
        link_id = call_id

    # Register the leaf ids.
    register_call_leafs(contract_specification.id, leaf_ids)